Introduction

My name is Ilori Stephen Adejuwon and I am a Fullstack software developer (Backend Heavy) based in West Africa Lagos, Nigeria. In this tutorial, I will show you how to build a REST API in PHP.

But before we proceed, I know you are curious to know what the acronym REST and API means. Well, we will cover a lot on those two in this Article. Meanwhile, let’s talk about why you would ever want to build a REST API first.

However, you can download the project from Git by visiting PHP Rest API.

Why Would You Want To Build A REST API.

PHP is a server-side language and it’s perfect for doing a lot of server-side tasks such as Receiving, Handling HTTP Requests, Authentication, communicating with a Database. and building an API.

That doesn’t still explain why you want to build a REST API. Above all, Well you see, you’d want to build a REST API because it gives room for two applications to talk to each other.

Now I bet you have a lot of questions flowing through your mind. Although I might not be able to answer all of those questions but I believe you will find answers once you continue reading.

Super excited huh? Let’s talk about some terms or technical jargon related to this lesson.

Glossary

While preparing this project, I came across a whole lot of terms or technical jargon related to this topic. Therefore in order not to get you confused, I will do my very best to explain each of them.

  1. REST: The term REST also known as REPRESENTATIONAL STATE TRANSFER can be defined as a service that defines a set of functions that programmers can use to send requests and receive responses using HTTP protocol such as GET and POST.

  2. API: The term API which stands for APPLICATION PROGRAMMING INTERFACE, and it’s a software intermediary that allows two applications to talk to each other.

  3. HTTP VERBS: This actually means your HTTP PROTOCOL such as your GET, POST, PUT and PATCH requests.

  4. ENDPOINTS: The term endpoint in the simplest form is one end of a communication channel. When an API interacts with another system, the touchpoints of this communication are considered endpoints. Each endpoint is the location from which APIs can access the resources they need to carry out their function.

  5. Middleware: This is a service that exists in between your application making it so that you focus on the specific purpose of your application.

In conclusion, I believe we are on the same page now. Therefore, let’s take our time to talk about the Project Requirement next.

Project Requirements.

  1. A Localhost Server: You can download the latest version of any of the following. XAMPP, LAMPP, MAMPP, and AMPPS depending on your operating system.

  2. Composer: Composer is a package manager for PHP. This will help us install some external packages which we are going to use in building our REST API. You can download and install composer via Get Composer.

  3. Git: This is kinda optional but I still recommend having git installed. This will prove useful if you ever want to deploy your code or push to a remote repository. You can download and install git through Git.

  4. PHP: Yup! PHP. I also think it’s best to have at least Basic PHP Knowledge. The coding is really easy. Nothing scary but I still recommend you know the basics of PHP to continue.

  5. Postman: We will need Postman to consume our Api’s. This is just for testing our Api locally. It provides a whole lot of extra features but for now, just download Postman.

  6. Text Editor: We will need a text editor to write our codes with. You can go online and check out any text editors but I recommend atom or visual studio code.

  7. Determination: I am not a motivational speaker but if you are a beginner, you shouldn’t get intimated by the sound of this topic. In addition, it’s something really easy and I will do my best to break it down. But still, I recommend that you encourage yourself.

In short, that’s our project requirement. Let’s talk about our project directory and begin Hacking!

Project Directory.

*/ PHP-REST-API
*/ App (Our Application Logic)
*/ public (Our public directory)
composer.json
index.php (Root Directory)

Isn’t it beautiful? That’s our project directory. A parent folder called php-rest-api and two other subfolders called App and public.

With that setup, open the composer.json File in the project’s root directory and paste in the code snippet below. I will explain the contents later.

{
"require": {
"klein/klein": "^2.1",
"firebase/php-jwt": "^5.2"
},
"autoload": {
"psr-4": {
"App\\": "App/"
},
"classmap": [
"App/Controller",
"App/Middleware",
"App/Model"
]
}
}
view raw package.json hosted with ❤ by GitHub

Inside of the composer.json File, we have a require Object. This object holds all our project dependencies or external packages, whichever clause you are comfortable with.

We also have an autoload Object. This Object enables us to use namespaces in our project and it also helps in autoloading our classes.

With that said, you can now open the project up in your terminal or cmd and run the command composer install. This will install the Klein package and a Firebase JWT package.

This will also create a vendor folder. This is where the installed packages will live with a few composer configurations and lastly, it will generate/create a new composer.lock file.

In short, We just installed some packages needed for our application. Therefore, Let’s move on and edit the index.php file inside of our project’s root directory.

1. Let’s Edit Our Index.php (Root Directory).

Our index.php will serve as an Entry point into the application. This acts as the file that starts the application. It doesn’t contain much though. You can open your index.php file and paste the snippet below.

<?php
/**
* @author Ilori Stephen A
**/
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/App/routes/api.php';
?>
view raw index.php hosted with ❤ by GitHub

The index.php does nothing other than autoloading all of our packages and also the router which contains all of our API Endpoints. And it does all of that in just two lines! That’s the beauty of composer and namespaces.

2. Creating The Endpoints (App/routes Directory).

With our index.php setup, If you try to run the application, You might find some errors. Well, that’s because we are yet to create our api.php file.

The api.php file is where all of our REST API Endpoints are defined. With that said, create an api.php file inside of the routes folder in the App directory. Once done, paste the code snippet below into the api.php file.

<?php
namespace App;
use App\UserController;
use App\CatalogController;
use App\ProductController;
$Klein = new \Klein\Klein();
/******************** User Routes || Authentication Routes **********************/
$Klein->respond('POST', '/api/v1/user', [ new UserController(), 'createNewUser' ]);
$Klein->respond('POST', '/api/v1/user-auth', [ new UserController(), 'login' ]);
/******************** Catalog Routes **********************/
$Klein->respond('POST', '/api/v1/catalog', [ new CatalogController(), 'createNewCatalog' ]);
$Klein->respond(['PATCH', 'PUT'], '/api/v1/catalog/[:id]', [ new CatalogController(), 'updateCatalog']);
$Klein->respond(['GET', 'HEAD'], '/api/v1/fetch-catalog-by-id/[:id]', [ new CatalogController(), 'fetchCatalogById' ]);
$Klein->respond(['GET', 'HEAD'], '/api/v1/fetch-catalog-by-name/[:name]', [ new CatalogController(), 'fetchCatalogByName' ]);
$Klein->respond(['GET', 'HEAD'], '/api/v1/catalogs', [ new CatalogController(), 'fetchCatalogs' ]);
$Klein->respond('DELETE', '/api/v1/del-catalog/[:id]', [ new CatalogController(), 'deleteCatalog' ]);
/******************** Product Routes **********************/
$Klein->respond('POST', '/api/v1/product', [ new ProductController(), 'createProduct' ]);
$Klein->respond('POST', '/api/v1/product/[:id]', [ new ProductController(), 'updateProduct' ]);
$Klein->respond('GET', '/api/v1/fetch/[:id]', [ new ProductController(), 'getProductById' ]);
$Klein->respond('GET', '/api/v1/products', [ new ProductController(), 'fetchProducts' ]);
$Klein->respond('DELETE', '/api/v1/delete-product/[:id]', [ new ProductController(), 'deleteProduct' ]);
// Dispatch all routes....
$Klein->dispatch();
?>
view raw api.php hosted with ❤ by GitHub

Now, at the top level of the api.php file, we declared a namespace. This namespace makes it possible to use any functions, Classes, or Constants defined within the namespace App.

With its definition at the top level of our script, we can begin using other classes by issuing the keyword use with the namespace path App\ClassName‘ to the class.

You have to admit, this is a lot better than using the require keyword or function. Above all, it makes our code look more modern and neat.

Because we required the autoload installed from issuing the composer install command in our index.php, this makes it easy for us to use any of the installed composer packages.

Meanwhile, We created a few Endpoints by creating a new instance of the Klein Package installed via composer. You can create a simple Endpoint with the Klein Package by following the syntax below.

    $Klein-&gt;respond('HTTP VERB', 'DESIRED_URL', CALLBACK_FUNCTION);

Similarly, you can read more about the Klein Package by visiting their Github Repo. Therefore, let’s continue by creating some Models for our application.

3. Spinning Up The Base Model (App\Model Directory).

It’s an API but still, we need a database to store some Data. Let’s begin by creating a Model.php file inside of the Model folder in the App directory.

This acts as the Base Model for every model file in the Model folder. That is, every other Model must extend or require this Class or File. This Class is also declared within the App namespace.

The Model.php Class creates a new Database Connection, and share a few private methods for handling some Database operations. With that completed, create a Model.php file inside of the Model folder in the App Directory and paste the code snippet below.

<?php
namespace App;
use PDO;
use Exception;
/**
* Model - The Base Model for all other Models.... All Other Model extends this Model.
*
* @author Ilori Stephen A <stephenilori458@gmail.com>
* @link https://github.com/learningdollars/php-rest-api/App/Model/Model.php
* @license MIT
*/
class Model {
protected static $dbHost = '127.0.0.1';
protected static $dbName = 'php_mini_rest_api';
protected static $dbUser = 'root';
protected static $dbPass = '';
protected static $dbConn;
protected static $stmt;
/**
* __construct
*
* Creates a New Database Connection...
*
* @param void
* @return void
*/
public function __construct()
{
// Create a DSN...
$Dsn = "mysql:host=" . Self::$dbHost . ";dbname=" . Self::$dbName;
$options = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
try {
Self::$dbConn = new PDO($Dsn, Self::$dbUser, Self::$dbPass, $options);
} catch(Exception $e) {
$Response = array(
status => 500,
data => [],
message => $e->getMessage()
);
return $Response;
}
}
/**
* query
*
* Takes advantage of PDO prepare method to create a prepared statement.
*
* @param string $query Sql query from extending Models
* @return void Anonymos
*/
protected static function query($query)
{
Self::$stmt = Self::$dbConn->prepare($query);
return true;
}
/**
* bindParams
*
* Binds the prepared statement using the bindValue method.
*
* @param mixed $param, $value, $type The parameter to bind the value to and the data type which is by default null.
* @return void Anonymos
*/
protected static function bindParams($param, $value, $type = null)
{
if ($type == null) {
switch(true) {
case is_int($value):
$type = PDO::PARAM_INT;
break;
case is_bool($value):
$type = PDO::PARAM_BOOL;
break;
case is_null($value):
$type = PDO::PARAM_NULL;
break;
default:
$type = PDO::PARAM_STR;
break;
}
}
Self::$stmt->bindValue($param, $value, $type);
return;
}
/**
* execute
*
* Executes the Sql statement and returns a boolean status
*
* @param void
* @return boolean Anonymos
*/
protected static function execute()
{
Self::$stmt->execute();
return true;
}
/**
* fetch
*
* Executes the Sql statement and returns a single array from the resulting Sql query.
*
* @param void
* @return array Anonymos
*/
protected static function fetch()
{
Self::execute();
return Self::$stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* fetchAll
*
* Executes the Sql statement and returns an array from the resulting Sql query.
*
* @param void
* @return array Anonymos
*/
protected static function fetchAll()
{
Self::execute();
return Self::$stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* lastInsertedId
*
* Makes use of the database connection and returns the last inserted id in the database.
*
* @param void
* @return int Anonymos
*/
protected static function lastInsertedId()
{
return Self::$dbConn->lastInsertId();
}
}
?>
view raw Model.php hosted with ❤ by GitHub

You can replace the Model Class Properties with your own Environment Variables. However the $stmt and the $dbConn should be untouched.

I won’t spend much time explaining what this file does as I have explained how this file works in a different article. In conclusion, you can read How To Create A Login And System In PHP Using PDO in order to get the full list.

In short, I will tell you that this class creates a new PDO Connection and it also provides an abstraction layer by hiding some business logic and exposing some reusable Method.

Meanwhile, I think it’s time to create other Models. In the next line, we will talk about the UserModel.

4. Coding The UserModel (App\Model Directory).

UserModel.php file is the Model which is responsible for creating, reading, updating and deleting users. This Model extends the Base Model making it possible to use Methods created or owned by the Base Model.

Create a UserModel.php file inside the Model Folder within the App Directory that should look like the following.

<?php
namespace App;
use App\Model;
/**
* UserModel - This Model is consumed basically by the UserController and is also consumed by other controllers and Middlewares...
*
* @author Ilori Stephen A <stephenilori458@gmail.com>
* @link https://github.com/learningdollars/php-rest-api/App/Model/UserModel.php
* @license MIT
*/
class UserModel extends Model {
/**
* createUser
*
* creates a new User
*
* @param array $payload Contains all the fields that will be created.
* @return array Anonymos
*/
public static function createUser($payload)
{
$Sql = "INSERT INTO `db_users` (firstName, lastName, email, password, created_at, updated_at) VALUES (:firstName, :lastName, :email, :password, :created_at, :updated_at)";
Parent::query($Sql);
// Bind Params...
Parent::bindParams('firstName', $payload['firstName']);
Parent::bindParams('lastName', $payload['lastName']);
Parent::bindParams('email', $payload['email']);
Parent::bindParams('password', $payload['password']);
Parent::bindParams('created_at', $payload['created_at']);
Parent::bindParams('updated_at', $payload['updated_at']);
$newUser = Parent::execute();
if ($newUser) {
$user_id = Parent::lastInsertedId();
$payload['user_id'] = $user_id;
$Response = array(
'status' => true,
'data' => $payload
);
return $Response;
}
$Response = array(
'status' => false,
'data' => []
);
return $Response;
}
/**
* fetchUserById
*
* fetches a user by it's Id
*
* @param int $Id The Id of the row to be fetched...
* @return array Anonymos
*/
public static function fetchUserById($Id)
{
$Sql = "SELECT id, firstName, lastName, email, created_at, updated_at FROM `db_users` WHERE id = :id";
Parent::query($Sql);
// Bind Params...
Parent::bindParams('id', $Id);
$Data = Parent::fetch();
if (!empty($Data)) {
return array(
'status' => true,
'data' => $Data
);
}
return array(
'status' => false,
'data' => []
);
}
/**
* checkEmail
*
* fetches a user by it's email
*
* @param string $email The email of the row to be fetched...
* @return array Anonymos
*/
public static function checkEmail($email)
{
$Sql = "SELECT * FROM `db_users` WHERE email = :email";
Parent::query($Sql);
// Bind Params...
Parent::bindParams('email', $email);
$emailData = Parent::fetch();
if (empty($emailData)) {
$Response = array(
'status' => false,
'data' => []
);
return $Response;
}
$Response = array(
'status' => true,
'data' => $emailData
);
return $Response;
}
}
?>
view raw UserModel.php hosted with ❤ by GitHub

Inside of the UserModel Class, we have the createUser Static Method which makes it possible to create a new User record in the Database by reusing methods like query, bind params and execute from the Base Model Class.

This Method accepts an Array of User Information and it returns a new Array containing the status of the operation.

Inside of this class, we also have the fetchUserById Static Method which makes it possible to fetch a User by ID. This method accepts and Integer and it returns an Array containing the status of the operation.

And last but not least, we have the checkEmail Method which fetches a User based on his/her Email Address. This method accepts an Email String and it returns an Array containing the status of the operation.

In conclusion, we have successfully created our UserModel. Later on, this model will prove useful within the Controllers and Middlewares. It’s time to shift our focus to the next Model we will be creating. The TokenModel.

5. The TokenModel (App\Model Directory).

TokenModel.php File or Class inherits the Base Model Class and it’s responsible for storing Access Tokens that can be used for communicating with this API.

You can think of the Access Tokens as a required Key which is needed to communicate with the API. You pass an Access Token to the API, it checks the Token if it’s valid and then, it proceeds with your request. If the validation goes sideways, The request will fail.

create a TokenModel.php file inside the Model folder in the App directory with the code snippet below.

<?php
namespace App;
use App\Model;
/**
* TokenModel - This Model is consumed basically by the UserController and is also consumed by other controllers and Middlewares...
*
* @author Ilori Stephen A <stephenilori458@gmail.com>
* @link https://github.com/learningdollars/php-rest-api/App/Model/TokenModel.php
* @license MIT
*/
class TokenModel extends Model {
/**
* createToken
*
* creates a new Token
*
* @param array $payload Contains all the fields that will be created.
* @return array Anonymous
*/
public function createToken($payload)
{
$Sql = "INSERT INTO db_token (user_id, jwt_token) VALUES (:user_id, :jwt_token)";
Parent::query($Sql);
// Bind Params...
Parent::bindParams('user_id', $payload['user_id']);
Parent::bindParams('jwt_token', $payload['jwt_token']);
$Token = Parent::execute();
if ($Token) {
return array(
'status' => true,
'data' => $payload
);
}
return array(
'status' => false,
'data' => []
);
}
/**
* fetchToken
*
* fetches an existing Token using the $token
*
* @param string $token The token that will be used in matching the closest token from the database.
* @return array Anonymous
*/
public function fetchToken($token)
{
$Sql = "SELECT * FROM `db_token` WHERE jwt_token = :jwt_token";
Parent::query($Sql);
Parent::bindParams('jwt_token', $token);
$Data = Parent::fetch();
if (!empty($Data)) {
return array(
'status' => true,
'data' => $Data
);
}
return array(
'status' => false,
'data' => []
);
}
}
?>
view raw TokenModel.php hosted with ❤ by GitHub

TokenModel Class houses two Methods. The createToken Method and the fetchToken Method.

createToken Method accepts an Array and returns an Array containing the result of the Insert Operation by taking advantage of the Base Model methods.

fetchToken Method accepts a String and returns the first record that matches the token String in the Database. This operation returns an Array.

To sum up, let’s move on and create the CatalogModel next.

6. Model For Catalogs (App\Model Directory).

UserModel.php File or Class is responsible for creating, updating, fetching, and deleting Catalogs. This File extends the Base Model just like the previous Models. Create a new file inside of the Model Folder and name it CatalogModel.php.

Open the CatalogModel.php and after that, paste the code snippet below into it.

<?php
namespace App;
use App\Model;
/**
* CatalogModel - A Model for the Catalog Controller.
*
* @author Ilori Stephen A <stephenilori458@gmail.com>
* @link https://github.com/learningdollars/php-rest-api/App/Model/CatalogModel.php
* @license MIT
*/
class CatalogModel extends Model {
/**
* createCatalog
*
* Creates a New Catalog
*
* @param array $Payload Contains all the required data needed to create a Catalog.
* @return array Anonymous
*/
public static function createCatalog($Payload)
{
$Sql = "INSERT INTO `db_catalogs` (name, created_at, updated_at) VALUES (:name, :created_at, :updated_at)";
Parent::query($Sql);
Parent::bindParams('name', $Payload['name']);
Parent::bindParams('created_at', $Payload['created_at']);
Parent::bindParams('updated_at', $Payload['updated_at']);
$catalog = Parent::execute();
if ($catalog) {
$catalog_id = Parent::lastInsertedId();
$Payload['catalog_id'] = $catalog_id;
return array(
'status' => true,
'data' => $Payload,
);
}
return array(
'status' => false,
'data' => []
);
}
/**
* updateCatalog
*
* Updates a New Catalog
*
* @param array $Payload Contains all the fields that will be updated.
* @return array Anonymous
*/
public static function updateCatalog($Payload)
{
$Sql = "UPDATE `db_catalogs` SET name = :name, updated_at = :updated_at WHERE id = :id";
Parent::query($Sql);
Parent::bindParams('id', $Payload['id']);
Parent::bindParams('name', $Payload['name']);
Parent::bindParams('updated_at', $Payload['updated_at']);
$catalog = Parent::execute();
if ($catalog) {
return array(
'status' => true,
'data' => $Payload,
);
}
return array(
'status' => false,
'data' => []
);
}
/**
* fetchCatalogByID
*
* Returns the first Catalog that matches the ID
*
* @param int $Id The Id of the Row to be updated.
* @return array Anonymous
*/
public static function fetchCatalogByID($Id)
{
$Sql = "SELECT * FROM `db_catalogs` WHERE id = :id";
Parent::query($Sql);
Parent::bindParams('id', $Id);
$catalog = Parent::fetch();
if (empty($catalog)) {
return array(
'status' => false,
'data' => []
);
}
return array(
'status' => true,
'data' => $catalog
);
}
/**
* fetchCatalogByName
*
* Returns the first Catalog that matches the name
*
* @param string $name The name of the row to be updated.
* @return array Anonymous
*/
public static function fetchCatalogByName($name)
{
$Sql = "SELECT * FROM `db_catalogs` WHERE name = :name";
Parent::query($Sql);
Parent::bindParams('name', $name);
$catalog = Parent::fetch();
if (empty($catalog)) {
return array(
'status' => false,
'data' => []
);
}
return array(
'status' => true,
'data' => $catalog
);
}
/**
* fetchCatalogs
*
* Returns a list of catalogs.
*
* @param void
* @return array Anonymous
*/
public static function fetchCatalogs()
{
$Sql = "SELECT * FROM `db_catalogs`";
Parent::query($Sql);
$catalogs = Parent::fetchAll();
if (empty($catalogs)) {
return array(
'status' => false,
'data' => []
);
}
return array(
'status' => true,
'data' => $catalogs
);
}
/**
* deleteCatalog
*
* Deletes a catalog
*
* @param int $Id The Id of the catalog to be deleted.
* @return array Anonymous
*/
public static function deleteCatalog($Id)
{
$Sql = "DELETE FROM `db_catalogs` WHERE id = :id";
Parent::query($Sql);
Parent::bindParams('id', $Id);
$product = Parent::execute();
if (!empty($product)) {
return array(
'status' => true,
'data' => []
);
}
return array(
'status' => false,
'data' => []
);
}
}
?>
view raw CatalogModel.php hosted with ❤ by GitHub

This Operation is similar to other Models we have created. Similarly, it creates, reads, updates and it deletes.

CatalogModel Class contains 6 Methods which gives it CRUD (Create, Read, Update, Delete) functionalities.

createCatalog Method creates a new catalog entry. This method accepts and Array containing the required information needed in creating a new Catalog. However, a new Array is returned depending on the operation.

updateCatalog Method updates an existing Catalog based on the Catalog ID. This method accepts an Array of data to be updated and it also returns an Array whether the operation is successful or not.

fetchCatalogById Method queries the Database for a catalog that matches the Id passed into the Method as an Argument. This method accepts an Integer as it’s Parameter and returns an Array containing the result of the operation.

fetchCatalogByName Method queries the Database for a catalog that matches the name passed into the Method as an Argument. This method accepts a String as it’s Parameter and it returns an Array containing the result of the operation.

fetchCatalogs Method. This method accepts no Parameter and it fetches all of the Catalogs created or stored in the Database. This method returns an Array.

deleteCatalog Method. This method accepts a numeric Id as it’s parameter and it deletes all Records in the Database matching the Id. This method returns an Array regardless of the status of the operation.

We are almost done working with the Model directory. We have just one more File to create inside of the Model directory before we begin working on our Middlewares.

7. Creating A ProductModel (App\Model Directory).

This is the last file I will create inside of the Model Folder for this lesson. Create a new file inside of the Model Folder and call it ProductModel.php.

This File or Class extends the Base Model and it reuses Methods from the Base Model. In short, this Model makes it possible to C.R.U.D products.

Open the ProductModel.php File and after that, paste the following code snippet.

<?php
namespace App;
use App\Model;
/**
* ProductModel - This Model is consumed basically by the ProductController and is also consumed by other controllers...
*
* @author Ilori Stephen A <stephenilori458@gmail.com>
* @link https://github.com/learningdollars/php-rest-api/App/Model/ProductModel.php
* @license MIT
*/
class ProductModel extends Model {
/**
* createProduct
*
* creates a new product
*
* @param array $payload Contains all the fields that will be created.
* @return array Anonymous
*/
public static function createProduct($payload)
{
$Sql = "INSERT INTO db_products (name, catalog_id, price, color, size, banner, created_at, updated_at) VALUES (:name, :catalog_id, :price, :color, :size, :banner, :created_at, :updated_at)";
Parent::query($Sql);
Parent::bindParams('name', $payload['name']);
Parent::bindParams('catalog_id', $payload['catalog_id']);
Parent::bindParams('price', $payload['price']);
Parent::bindParams('color', $payload['color']);
Parent::bindParams('size', $payload['size']);
Parent::bindParams('banner', $payload['banner']);
Parent::bindParams('created_at', $payload['created_at']);
Parent::bindParams('updated_at', $payload['updated_at']);
$product = Parent::execute();
if ($product) {
$product_id = Parent::lastInsertedId();
$payload['product_id'] = $product_id;
return array(
'status' => true,
'data' => $payload
);
}
return array(
'status' => false,
'data' => []
);
}
/**
* findProductById
*
* fetches a product by it's ID
*
* @param int $Id The Id of the row to be returned...
* @return array Anonymous
*/
public static function findProductById($Id)
{
$Sql = "SELECT * FROM `db_products` WHERE id = :id";
Parent::query($Sql);
Parent::bindParams('id', $Id);
$product = Parent::fetch();
if (!empty($product)) {
return array(
'status' => true,
'data' => $product
);
}
return array(
'status' => false,
'data' => []
);
}
/**
* fetchProducts
*
* fetches a list of products..
*
* @param void
* @return array Anonymous
*/
public static function fetchProducts()
{
$Sql = "SELECT * FROM `db_products`";
Parent::query($Sql);
$products = Parent::fetchAll();
if (!empty($products)) {
return array(
'status' => true,
'data' => $products
);
}
return array(
'status' => false,
'data' => []
);
}
/**
* updateProduct
*
* update a product based on the product ID
*
* @param array $Payload An array of values to be updated...
* @return array Anonymous
*/
public static function updateProduct($Payload)
{
$Sql = "UPDATE `db_products` SET name = :name, catalog_id = :catalog_id, price = :price, color = :color, size = :size, price = :price, banner = :banner, updated_at = :updated_at WHERE id = :id";
Parent::query($Sql);
Parent::bindParams('id', $Payload['id']);
Parent::bindParams('name', $Payload['name']);
Parent::bindParams('catalog_id', $Payload['catalog_id']);
Parent::bindParams('price', $Payload['price']);
Parent::bindParams('color', $Payload['color']);
Parent::bindParams('size', $Payload['size']);
Parent::bindParams('price', $Payload['price']);
Parent::bindParams('banner', $Payload['banner']);
Parent::bindParams('updated_at', $Payload['updated_at']);
$product = Parent::execute();
if ($product) {
return array(
'status' => true,
'data' => $Payload,
);
}
return array(
'status' => false,
'data' => []
);
}
/**
* deleteProduct
*
* deletes a product based on the product ID
*
* @param int $Id An array of values to be deleted...
* @return array Anonymous
*/
public static function deleteProduct($Id)
{
$Sql = "DELETE FROM `db_products` WHERE id = :id";
Parent::query($Sql);
Parent::bindParams('id', $Id);
$product = Parent::execute();
if (!empty($product)) {
return array(
'status' => true,
'data' => []
);
}
return array(
'status' => false,
'data' => []
);
}
}
?>
view raw ProductModel.php hosted with ❤ by GitHub

createProduct Method. This method accepts an Array of information required in creating the Product. The method also returns a new Array containing the status of the operation.

findProductById Method. This method accepts a Numeric Id used in querying the database to the first record that matches the Id passed in as a Parameter. This method returns an Array depending on the operation.

fetchProducts Method. This method accepts no parameter and it returns an Array of products regardless of the operation status.

updateProduct Method. This method accepts an Array of values to be updated as it’s parameter where the record Id matches the Id in the Array and it returns a new Array depending on the status of the operation.

deleteProduct Method. This method accepts a Numeric Id as it’s parameter and it deletes all documents or records matching the Id passed in as an Argument. This method returns an Array regardless of the operation status.

Congratulations. Our REST API is almost complete. We just completed working on our Model directory. The next step is to begin making use of the created Models within the Middleware and the Controller.

In the next line, we will create some Middlewares to guard access to our API.

8. Creating Middlewares (App\Middleware Directory)

In this section, I will create some middleware that will help us in Guarding our REST API in order to prevent Unauthorized Access and also rules our REST API should check before granting access.

Inside of this folder, we will create our first Middleware and call it RequestMiddleware.php. This File houses a class that is also within the App namespace.

This File contains 3 static methods and one protected property in order to validate incoming requests. Open the RequestMiddleware.php File and after that, paste the code snippet below.

<?php
namespace App;
/**
* RequestMiddleware - The RequestMiddleware. This Controller makes use of a few Models, Classes and packages for authenticating requests....
*
* @author Ilori Stephen A <stephenilori458@gmail.com>
* @link https://github.com/learningdollars/php-rest-api/App/Middleware/RequestMiddleware.php
* @license MIT
*/
class RequestMiddleware {
protected static $Request;
/**
* __construct
*
* Initializes the middleware
*
* @param void
* @return void
*/
public function __construct()
{
Self::$Request = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '';
return;
}
/**
* acceptsJson
*
* Determines if the request is of a JSON Content type
*
* @param void
* @return boolean
*/
public static function acceptsJson()
{
if (strtolower(Self::$Request) == 'application/json') {
return true;
}
return false;
}
/**
* acceptsFormData
*
* Determines if the request is of a Form Data Content type
*
* @param void
* @return boolean
*/
public static function acceptsFormData()
{
Self::$Request = explode(';', Self::$Request)[0];
if (strtolower(Self::$Request) == 'multipart/form-data') {
return true;
}
return false;
}
}
?>

$Request static property. This property can only be used within this class or within any other class that extends the RequestMiddleware Class. However, this Property doesn’t do much other than storing a String.

__construct Magic Method. This method is executed when a new instance of the Class is created. The method fetches the request content type and saves it to the $Request static property.

acceptsJson Method. This method checks if an incoming request CONTENT_TYPE HEADER matches application/json which is the required content type for JSON resource.

acceptsFormData Method. This method checks if an incoming request CONTENT_TYPE HEADER matches multipart/form-data which is the required content type for Form Data resource.

9. Implementing The JwtMiddleware (App\Middleware Directory).

I mentioned generating Access Tokens when we were working on the TokenModel. Well, I am glad you asked. Although this Middleware doesn’t generate Access Tokens it checks and validates them.

Create a new file called JwtMiddleware.php after that, paste the following into the file.

<?php
namespace App;
use Firebase\JWT\JWT;
use Exception;
use App\TokenModel;
use App\UserModel;
/**
* JwtMiddleware - The JwtMiddleware. This Controller makes use of a few Models, Classes and packages for authenticating requests....
*
* @author Ilori Stephen A <stephenilori458@gmail.com>
* @link https://github.com/learningdollars/php-rest-api/App/Middleware/JWTMiddleware.php
* @license MIT
*/
class JwtMiddleware {
protected static $user;
protected static $token;
protected static $user_id;
/**
* JWTSecret
*
* Returns a JWT Secret...
*
* @param void
* @return string
*/
private static function JWTSecret()
{
return 'K-lyniEXe8Gm-WOA7IhUd5xMrqCBSPzZFpv02Q6sJcVtaYD41wfHRL3';
}
/**
* getToken
*
* Fetches and return the JWT Token from the request Header
*
* @param void
* @return string
*/
protected static function getToken()
{
Self::$token = $_SERVER['HTTP_AUTHORIZATION'];
return $_SERVER['HTTP_AUTHORIZATION'];
}
/**
* validateToken
*
* Validates the JWT Token and returns a boolean true...
*
* @param void
* @return boolean
*/
protected static function validateToken()
{
Self::getToken();
if (Self::$token == '' || Self::$token == null) {
return false;
}
try {
$Token = explode('Bearer ', Self::$token);
if (isset($Token[1]) && $Token == '') {
return false;
}
return $Token[1];
} catch (Exception $e) { return false; }
}
/**
* getAndDecodeToken
*
* Decodes and returns a boolean true or the user_id.
*
* @param void
* @return mixed
*/
public function getAndDecodeToken()
{
$token = Self::validateToken();
try {
if ($token !== false) {
// Query the database for the token and decode it....
$TokenModel = new TokenModel();
// Check the database for the query before decoding it...
$tokenDB = $TokenModel::fetchToken($token);
if ($tokenDB['status']) {
// decode the token and pass the result on to the controller....
$decodedToken = (Array) JWT::decode($token, Self::JWTSecret(), array('HS256'));
if (isset($decodedToken['user_id'])) {
Self::$user_id = $decodedToken['user_id'];
return $decodedToken['user_id'];
}
return false;
}
return false;
}
return false;
} catch (Exception $e) {
return false;
}
}
/**
* getUser
*
* Fetches the user ID from the JWT Token and returns a User Array.
*
* @param void
* @return array
*/
public function getUser()
{
try {
$UserModel = new UserModel();
$user = $UserModel::fetchUserById(Self::$user_id);
if ($user['status']) {
return $user['data'];
}
return [];
} catch (Exception $e) { return []; }
}
}
?>
view raw JwtMiddleware.php hosted with ❤ by GitHub

JwtMiddleware File houses a Class which is declared within the app namespace. This file makes use of the Firebase JWT package which has been installed via Composer.

The file also depends on the TokenModel and UserModel created inside of the Model folder. And that’s how we begin utilizing the Classes created inside of our Model folder.

In order to make a request to a route protected by the JWT Token, you pass in the following to your authorization header. Below is an example.

Authorization Header

JwtMiddleware Class contains 5 Methods and it also contains 3 protected property.

$user, $token, and $user_id protected static property are properties that are only meant to be used within the JwtMiddleware Class or Classes that extend the JwtMiddleware Class.

The JWTSecret Method

private JWTSecret Method. This method is a private method that makes sure that the method can only be used within the class it’s defined in or Classes extending the JwtMiddleware Class.

This method returns a String that is used in signing JWT Tokens. You can always modify this script to have the JWT Secret come from a Constant.

The getToken Method

protected getToken Method. This method like the JWTSecret Method can only be used within the RequestMiddleware Class or extending Classes.

getToken Method fetches the JWT Token from an incoming request by getting the HTTP_AUTHORIZATION HEADER. It returns the JWT Token fetched from the header.

The validateToken Method

validateToken Method. This method validates the received JWT Token and returns a boolean true or a boolean false depending on the result of the validation.

The getAndDecodeToken Method

getAndDecodeToken Method. This method decodes the Jwt Token to check it's validity. It also checks if the token has been issued from the application by checking if the token exists in the Database by utilizing the TokenModel Class.

    $TokenModel = new TokenModel();
    // Check the database for the query before decoding it...
    $tokenDB = $TokenModel::fetchToken($token);

Method getAndDecodeToken also makes use of the Firebase\JWT\JWT package for decoding the token to check if the token is still valid.

    if ($tokenDB['status']) {
        // decode the token and pass the result on to the controller....
        $decodedToken = (Array) JWT::decode($token, Self::JWTSecret(), array('HS256'));
            if (isset($decodedToken['user_id'])) {
                Self::$user_id = $decodedToken['user_id'];
                return $decodedToken['user_id'];
            }

                return false;
    }

This Method returns an Integer representing the User Id if the operation is successful else it returns a boolean false.

The getUser Method

getUser Method. This method makes use of the UserModel method by using the $user_id protected static property to fetch the User who issued the request.

    try {
        $UserModel = new UserModel();
        $user = $UserModel::fetchUserById(Self::$user_id);
            if ($user['status']) {
                return $user['data'];
            }

            return [];
    } catch (Exception $e) { return []; }

At this point, we are so close to having a complete REST API. However, The final steps will be creating the Controllers which will actually help with consuming all incoming requests. With that said, this concludes our Middleware.

10. Creating Our Controllers (App\Controller Directory).

In this folder, we will create several Controllers which will make it possible to consume incoming requests. Inside of this folder, we will begin by creating a Base Controller. All other Controllers will extend this Controller.

Creating The Base Controller.

Inside the Controllers folder, create a new file and call it Controller.php. Then you can go ahead and paste in the code snippet below.

<?php
namespace App;
use App\UserModel;
use App\CatalogModel;
use App\ProductModel;
/**
* Controller - The Base Controller for all other Controllers.... All Other Controllers extends this Controller.
*
* @author Ilori Stephen A <stephenilori458@gmail.com>
* @link https://github.com/learningdollars/php-rest-api/App/Controller/Controller.php
* @license MIT
*/
class Controller {
/**
* validation
*
* Validates an array of objects using defined rules...
*
* @param array $Payload Contains an array of Objects that will be validated.
* @return array $response
*/
protected static function validation($payloads)
{
$response = [];
foreach($payloads as $payload) {
$i++;
if ($payload->validator == 'required') {
if ($payload->data == null || $payload->data = '' || !isset($payload->data)) {
array_push($response, [
'key' => $payload->key,
'message' => "The {$payload->key} field is required"
]);
}
}
if ($payload->validator == 'string') {
if (preg_match('/[^A-Za-z]/', $payload->data)) {
array_push($response, [
'key' => $payload->key,
'message' => "Sorry {$payload->key} expects an Alphabet."
]);
}
}
if ($payload->validator == 'numeric') {
if (preg_match('/[^0-9_]/', $payload->data)) {
array_push($response, [
'key' => $payload->key,
'message' => "Sorry {$payload->key} expects a Number."
]);
}
}
if ($payload->validator == 'boolean') {
if (strtolower(gettype($payload->data)) !== 'boolean') {
array_push($response, [
'key' => $payload->key,
'message' => "Sorry {$payload->key} expects a Boolean."
]);
}
}
if (stristr($payload->validator, ':')) {
$operationName = explode(':', $payload->validator)[0];
$operationChecks = (int) explode(':', $payload->validator)[1];
if (strtolower($operationName) == 'min' && $operationChecks > strlen($payload->data)) {
array_push($response, [
'key' => $payload->key,
'message' => "Sorry {$payload->key} is supposed to be less than " . strlen($payload->data)
]);
}
if (strtolower($operationName) == 'max' && $operationChecks < strlen($payload->data)) {
array_push($response, [
'key' => $payload->key,
'message' => "Sorry {$payload->key} is supposed to be greather than " . strlen($payload->data)
]);
}
if (strtolower($operationName) == 'between') {
$operationChecksTwo = (int) explode(':', $payload->validator)[2];
array_push($response, [
'key' => $payload->key,
'message' => "Sorry {$payload->key} is supposed to be between " . $operationChecks . ' and ' . $operationChecksTwo
]);
}
}
if ($payload->validator == 'emailExists') {
try {
$UserModel = new UserModel();
$checkEmail = $UserModel::checkEmail($payload->data);
if ($checkEmail['status']) {
array_push($response, [
'key' => $payload->key,
'message' => "Sorry {$payload->key} already exists. Please try with a different Email."
]);
}
} catch (Exception $e) { /** */ }
}
if ($payload->validator == 'catalogExists') {
try {
$CatalogModel = new CatalogModel();
$checkCatalog = $CatalogModel::fetchCatalogByID($payload->data);
if (!$checkCatalog['status']) {
array_push($response, [
'key' => $payload->key,
'message' => "Sorry, The catalog with this ID could not be found in our database."
]);
}
} catch (Exception $e) { /** */ }
}
if ($payload->validator == 'productExists') {
try {
$ProductModel = new ProductModel();
$checkProduct = $ProductModel::findProductById((int) $payload->data);
if (!$checkProduct['status']) {
array_push($response, [
'key' => $payload->key,
'message' => "Sorry, The product with this ID could not be found in our database."
]);
}
} catch (Exception $e) { /** */ }
}
if ($payload->validator == 'img') {
try {
$files = $payload->data;
if ($files) {
$fileName = $files['name'];
$targetDir = '../../public/img/';
$targetFile = $targetDir . basename($files['name']);
$fileSize = $files['size'];
$fileExtension = strtolower(pathinfo($targetFile, PATHINFO_EXTENSION));
if (!in_array($fileExtension, $payload->acceptedExtension)) {
array_push($response, [
'key' => $payload->key,
'message' => "Sorry, {$payload->key} only accepts the following extensions; " . implode(", ", $payload->acceptedExtension)
]);
}
if ($fileSize > $payload->maxSize) {
array_push($response, [
'key' => $payload->key,
'message' => "Sorry, {$payload->key} File size should be less than " . $payload->maxSize
]);
}
}
} catch (Exception $e) { /** */ }
}
}
if (count($response) < 1) {
$validationErrors = new \stdClass();
$validationErrors->status = false;
$validationErrors->errors = [];
return $validationErrors;
}
$validationErrors = new \stdClass();
$validationErrors->status = true;
$validationErrors->errors = $response;
return $validationErrors;
}
/**
* JWTSecret
*
* Returns a JWT Secret....
*
* @param void
* @return string Annonymous
*/
protected static function JWTSecret()
{
return 'K-lyniEXe8Gm-WOA7IhUd5xMrqCBSPzZFpv02Q6sJcVtaYD41wfHRL3';
}
}
?>
view raw Controller.php hosted with ❤ by GitHub

Controller.php file is also defined with the app namespace and it also uses a few Models such as the UserModel, CatalogModel, and ProductModel.

Inside of the Controller Class, we have two protected static methods. The validation Method and also the JWTSecret Method. The validation Method accepts an Array as it’s parameter.

Array passed in is a set of validation rules that will be checked inside of the Method. After the Array is parsed, a new Array is returned by the Method which contains the status and additional messages from the result of the validation.

The JWTSecret Method

protected JWTSecret Method. This method is a protected method that makes sure that the method can only be used within the class it’s defined in or Classes extending the Base Controller.

With the Base Controller created, we can now proceed to create other Controllers inside of the Controllers Folder that will extend the Base Controller.

11. Creating The UserController (App\Controller Directory).

Inside of the Controller Folder, we will create a new file called UserController.php. After creating the file, open the file and after that, paste in the code snippet below.

<?php
namespace App;
use Exception;
use App\UserModel;
use App\TokenModel;
use App\Controller;
use Firebase\JWT\JWT;
use App\RequestMiddleware;
/**
* UserController - The UserController. This Controller makes use of a few Models for creating, updating, fetching and deleting Users.
*
* @author Ilori Stephen A <stephenilori458@gmail.com>
* @link https://github.com/learningdollars/php-rest-api/App/Controller/UserController.php
* @license MIT
*/
class UserController extends Controller {
/**
* createNewUser
*
* Creates a new User.
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function createNewUser($request, $response)
{
$Response = [];
$sapi_type = php_sapi_name();
$Middleware = new RequestMiddleware();
$Middleware = $Middleware::acceptsJson();
if (!$Middleware) {
array_push($Response, [
'status' => 400,
'message' => 'Sorry, Only JSON Contents are allowed to access this Endpoint.',
'data' => []
]);
$response->code(400)->json($Response);
return;
}
$data = json_decode($request->body());
// Do some validation...
$validationObject = array(
(Object) [
'validator' => 'required',
'data' => isset($data->firstName) ? $data->firstName : '',
'key' => 'First Name'
],
(Object) [
'validator' => 'string',
'data' => isset($data->firstName) ? $data->firstName : '',
'key' => 'First Name'
],
(Object) [
'validator' => 'required',
'data' => isset($data->lastName) ? $data->lastName : '',
'key' => 'Last Name',
],
(Object) [
'validator' => 'string',
'data' => isset($data->lastName) ? $data->lastName : '',
'key' => 'Last Name',
],
(Object) [
'validator' => 'emailExists',
'data' => isset($data->email) ? $data->email : '',
'key' => 'Email'
],
(Object) [
'validator' => 'min:7',
'data' => isset($data->password) ? $data->password : '',
'key' => 'Password'
]
);
$validationBag = Parent::validation($validationObject);
if ($validationBag->status) {
$response->code(400)->json($validationBag);
return;
}
// Trim the response and create the account....
$payload = array(
'firstName' => htmlspecialchars(stripcslashes(strip_tags($data->firstName))),
'lastName' => htmlspecialchars(stripcslashes(strip_tags($data->lastName))),
'email' => stripcslashes(strip_tags($data->email)),
'password' => password_hash($data->password, PASSWORD_BCRYPT),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
);
try {
$UserModel = new UserModel();
$UserData = $UserModel::createUser($payload);
if ($UserData['status']) {
// Initialize JWT Token....
$tokenExp = date('Y-m-d H:i:s');
$tokenSecret = Parent::JWTSecret();
$tokenPayload = array(
'iat' => time(),
'iss' => 'PHP_MINI_REST_API', //!!Modify:: Modify this to come from a constant
"exp" => strtotime('+ 7 Days'),
"user_id" => $UserData['data']['user_id']
);
$Jwt = JWT::encode($tokenPayload, $tokenSecret);
// Save JWT Token...
$TokenModel = new TokenModel();
$TokenModel::createToken([
'user_id' => $UserData['data']['user_id'],
'jwt_token'=> $Jwt
]);
$UserData['data']['token'] = $Jwt;
// Return Response............
$Response['status'] = 201;
$Response['message'] = '';
$Response['data'] = $UserData;
$response->code(201)->json($Response);
return;
}
$Response['status'] = 500;
$Response['message'] = 'An unexpected error occurred and your account could not be created. Please, try again later.';
$Response['data'] = [];
$response->code(500)->json($Response);
return;
} catch (Exception $e) {
$Response['status'] = 500;
$Response['message'] = $e->getMessage();
$Response['data'] = [];
$response->code(500)->json($Response);
return;
}
}
/**
* login
*
* Authenticates a New User.
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function login($request, $response)
{
$Response = [];
$sapi_type = php_sapi_name();
$Middleware = new RequestMiddleware();
$Middleware = $Middleware::acceptsJson();
if (!$Middleware) {
array_push($Response, [
'status' => 400,
'message' => 'Sorry, Only JSON Contents are allowed to access this Endpoint.',
'data' => []
]);
$response->code(400)->json($Response);
return;
}
$data = json_decode($request->body());
// Do some validation...
$validationObject = array(
(Object) [
'validator' => 'required',
'data' => isset($data->email) ? $data->email : '',
'key' => 'Email'
],
(Object) [
'validator' => 'required',
'data' => isset($data->password) ? $data->password : '',
'key' => 'Password'
],
(Object) [
'validator' => 'min:7',
'data' => isset($data->password) ? $data->password : '',
'key' => 'Password'
]
);
$validationBag = Parent::validation($validationObject);
if ($validationBag->status) {
$response->code(400)->json($validationBag);
return;
}
// Trim the response and create the account....
$payload = array(
'email' => stripcslashes(strip_tags($data->email)),
'password' => $data->password,
'updated_at' => date('Y-m-d H:i:s')
);
try {
$UserModel = new UserModel();
$UserData = $UserModel::checkEmail($payload['email']);
if ($UserData['status']) {
if (password_verify($payload['password'], $UserData['data']['password'])) {
// Initialize JWT Token....
$tokenExp = date('Y-m-d H:i:s');
$tokenSecret = Parent::JWTSecret();
$tokenPayload = array(
'iat' => time(),
'iss' => 'PHP_MINI_REST_API', //!!Modify:: Modify this to come from a constant
"exp" => strtotime('+ 7 Days'),
"user_id" => $UserData['data']['id']
);
$Jwt = JWT::encode($tokenPayload, $tokenSecret);
// Save JWT Token...
$TokenModel = new TokenModel();
$TokenModel::createToken([
'user_id' => $UserData['data']['id'],
'jwt_token'=> $Jwt
]);
$UserData['data']['token'] = $Jwt;
// Return Response............
$Response['status'] = 201;
$Response['message'] = '';
$Response['data'] = $UserData;
$response->code(201)->json($Response);
return;
}
$Response['status'] = 401;
$Response['message'] = 'Please, check your Email and Password and try again.';
$Response['data'] = [];
$response->code(401)->json($Response);
return;
}
$Response['status'] = 500;
$Response['message'] = 'An unexpected error occurred and your action could not be completed. Please, try again later.';
$Response['data'] = [];
$response->code(500)->json($Response);
return;
} catch (Exception $e) {
$Response['status'] = 500;
$Response['message'] = $e->getMessage();
$Response['data'] = [];
$response->code(500)->json($Response);
return;
}
}
}
?>
view raw UserController.php hosted with ❤ by GitHub

Class inside of the UserController is also defined within the app namespace. This Class depends on the UserModel, TokenModel, the Base Controller, the Firebase Jwt Package, and the RequestMiddleware. On the other hand, the Class also depends on the inbuilt PHP Exception Base Class.

UserController Class contains two methods namely the createNewUser Method and the login Method. Each of these two methods are declared public so that we can access them in our api.php file.

The createNewUser Method

createNewUser Method. This method accepts a $request and a $response as it’s parameter. The method accepts $request and the $response as it’s parameter because the method is used as a callback inside of the api.php file.

createNewUser Method as its name implies is responsible for creating a new User, generating a new Access Token, and doing some validations. After the validation, the createNewUser Method returns a JSON Response with an HTTP STATUS CODE of 201 Created if the operation is successful. You can read more about the various HTTP STATUS CODE at HTTP Statuses.

The login Method

login Method. This method accepts a $request and $response as it’s parameter because the method is being used a callback inside of the api.php file. The $request contains a whole lot of information about the incoming request so, it makes it easier to fetch payloads from the request.

login Method returns a JSON Response with an HTTP STATUS CODE of 200 if the operation is successful. This Method depends on the validation Method, from the BASE CONTROLLER, the TokenModel, and the UserModel.

With that out of the way, let’s test this method by creating a new User. Open the project directory in your terminal or cmd and run the command php -S 127.0.0.1:8000.

This will run our application on the localhost server through the Port 8000. This will also print some exceptions if any to the terminal console. After you have successfully started the application, You can comment out all other Routes in the api.php except the snippets below.

    /******************** User Routes || Authentication Routes **********************/
    $Klein-&gt;respond('POST', '/api/v1/user', [ new UserController(), 'createNewUser' ]);
    $Klein-&gt;respond('POST', '/api/v1/user-auth', [ new UserController(), 'login' ]);

We can now open Postman and consume both endpoints. Below are some screenshots of how I consumed the API

Create User

Login User

Congratulations once again. We just created and consumed our User Endpoints. It will become easier as soon as we create a few more controllers and Endpoints. Up next, we will create a CatalogController.

12. Creating The CatalogController (App\Controller Directory).

We just created a UserController Class and exposed a few Endpoints which creates a new user by making use of the UserController Class and the methods within the controller. In this section, we will create a CatalogController.php file and expose a few Endpoints.

After creating the CatalogController.php file, copy and paste the code snippet below into the CatalogController.php file.

<?php
namespace App;
use Exception;
use App\Controller;
use App\CatalogModel;
use App\JwtMiddleware;
use App\RequestMiddleware;
/**
* CatalogController - The CatalogController. This Controller makes use of a few Models for creating, updating, fetchingand deleting Catalogs.
*
* @author Ilori Stephen A <stephenilori458@gmail.com>
* @link https://github.com/learningdollars/php-rest-api/App/Controller/CatalogController.php
* @license MIT
*/
class CatalogController extends Controller {
/**
* createNewCatalog
*
* Creates a new Catalog.
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function createNewCatalog($request, $response)<?php
namespace App;
use Exception;
use App\Controller;
use App\CatalogModel;
use App\JwtMiddleware;
use App\RequestMiddleware;
/**
* CatalogController - The CatalogController. This Controller makes use of a few Models for creating, updating, fetchingand deleting Catalogs.
*
* @author Ilori Stephen A <stephenilori458@gmail.com>
* @link https://github.com/learningdollars/php-rest-api/App/Controller/CatalogController.php
* @license MIT
*/
class CatalogController extends Controller {
/**
* createNewCatalog
*
* Creates a new Catalog.
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function createNewCatalog($request, $response)
{
$Response = [];
// Call the JSON Middleware
$JsonMiddleware = new RequestMiddleware();
$acceptsJson = $JsonMiddleware::acceptsJson();
if (!$acceptsJson) {
array_push($Response, [
'status' => 400,
'message' => 'Sorry, Only JSON Contents are allowed to access this Endpoint.',
'data' => []
]);
$response->code(400)->json($Response);
return;
}
$JwtMiddleware = new JwtMiddleware();
$jwtMiddleware = $JwtMiddleware::getAndDecodeToken();
if (isset($jwtMiddleware) && $jwtMiddleware == false) {
$response->code(400)->json([
'status' => 401,
'message' => 'Sorry, the authenticity of this token could not be verified.',
'data' => []
]);
return;
}
$Data = json_decode($request->body(), true);
$validationObject = array(
(Object) [
'validator' => 'required',
'data' => isset($Data['name']) ? $Data['name'] : '',
'key' => 'Catalog Name'
]
);
$validationBag = Parent::validation($validationObject);
if ($validationBag->status) {
$response->code(400)->json($validationBag);
return;
}
try {
$CatalogModel = new CatalogModel();
$Payload = [
'name' => $Data['name'],
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
];
$catalog = $CatalogModel::createCatalog($Payload);
if ($catalog['status']) {
$Response['status'] = 201;
$Response['data'] = $catalog['data'];
$Response['message'] = '';
$response->code(201)->json($Response);
return;
}
} catch (Exception $e) {
$Response['status'] = 500;
$Response['message'] = $e->getMessage();
$Response['data'] = [];
$response->code(500)->json($Response);
return;
}
return;
}
/**
* updateCatalog
*
* Updates a Catalog.
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function updateCatalog($request, $response)
{
$Response = [];
// Call the JSON Middleware
$JsonMiddleware = new RequestMiddleware();
$acceptsJson = $JsonMiddleware::acceptsJson();
if (!$acceptsJson) {
array_push($Response, [
'status' => 400,
'message' => 'Sorry, Only JSON Contents are allowed to access this Endpoint.',
'data' => []
]);
$response->code(400)->json($Response);
return;
}
$JwtMiddleware = new JwtMiddleware();
$jwtMiddleware = $JwtMiddleware::getAndDecodeToken();
if (isset($jwtMiddleware) && $jwtMiddleware == false) {
$response->code(400)->json(array(
'status' => 401,
'message' => 'Sorry, the authenticity of this token could not be verified.',
'data' => []
));
return;
}
$Data = json_decode($request->body(), true);
$validationObject = array(
(Object) [
'validator' => 'required',
'data' => isset($Data['name']) ? $Data['name'] : '',
'key' => 'Catalog Name'
]
);
$validationBag = Parent::validation($validationObject);
if ($validationBag->status) {
$response->code(400)->json($validationBag);
return;
}
try {
$Payload = [
'id' => $request->id,
'name' => $Data['name'],
'updated_at' => date('Y-m-d H:i:s')
];
$CatalogModel = new CatalogModel();
$catalog = $CatalogModel::updateCatalog($Payload);
if ($catalog['status']) {
// fetch the updated catalog...
$Response['status'] = 200;
$Response['data'] = $CatalogModel::fetchCatalogByID($Payload['id'])['data'];
$Response['message'] = '';
$response->code(200)->json($Response);
return;
}
$Response['status'] = 500;
$Response['message'] = 'An unexpected error occurred and your Catalog could not be updated at the moment. Please, try again later.';
$Response['data'] = [];
$response->code(500)->json($Response);
return;
} catch (Exception $e) {
$Response['status'] = 500;
$Response['message'] = $e->getMessage();
$Response['data'] = [];
$response->code(500)->json($Response);
return;
}
}
/**
* fetchCatalogById
*
* Fetches a catalog by an ID
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function fetchCatalogById($request, $response)
{
$Response = [];
$JwtMiddleware = new JwtMiddleware();
$jwtMiddleware = $JwtMiddleware::getAndDecodeToken();
if (isset($jwtMiddleware) && $jwtMiddleware == false) {
$response->code(401)->json([
'status' => 401,
'message' => 'Sorry, the authenticity of this token could not be verified.',
'data' => []
]);
return;
}
$validationObject = array(
(Object) [
'validator' => 'required',
'data' => isset($request->id) ? $request->id : '',
'key' => 'Catalog Name'
],
(Object) [
'validator' => 'numeric',
'data' => isset($request->id) ? $request->id : '',
'key' => 'Catalog ID'
]
);
$validationBag = Parent::validation($validationObject);
if ($validationBag->status) {
$response->code(400)->json($validationBag);
return;
}
try {
$CatalogModel = new CatalogModel();
$catalog = $CatalogModel::fetchCatalogByID($request->id);
if ($catalog['status']) {
$Response['status'] = true;
$Response['data'] = $catalog['data'];
$Response['message'] = '';
$response->code(200)->json($Response);
return;
}
$Response['status'] = 500;
$Response['message'] = 'Sorry, An unexpected error occured and your catalog could be retrieved.';
$Response['data'] = [];
$response->code(500)->json($Response);
return;
} catch (Exception $e) {
$Response['status'] = 500;
$Response['message'] = $e->getMessage();
$Response['data'] = [];
$response->code(500)->json($Response);
return;
}
}
/**
* fetchCatalogByName
*
* Fetches a catalog by it's name
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function fetchCatalogByName($request, $response)
{
$Response = [];
$JwtMiddleware = new JwtMiddleware();
$jwtMiddleware = $JwtMiddleware::getAndDecodeToken();
if (isset($jwtMiddleware) && $jwtMiddleware == false) {
$response->code(401)->json([
'status' => 401,
'message' => 'Sorry, the authenticity of this token could not be verified.',
'data' => []
]);
return;
}
$validationObject = array(
(Object) [
'validator' => 'required',
'data' => isset($request->name) ? $request->name : '',
'key' => 'Catalog Name'
],
(Object) [
'validator' => 'string',
'data' => isset($request->name) ? $request->name : '',
'key' => 'Catalog Name'
]
);
$validationBag = Parent::validation($validationObject);
if ($validationBag->status) {
$response->code(400)->json($validationBag);
return;
}
try {
$CatalogModel = new CatalogModel();
$catalog = $CatalogModel::fetchCatalogByName($request->name);
if ($catalog['status']) {
$Response['status'] = true;
$Response['data'] = $catalog['data'];
$Response['message'] = '';
$response->code(200)->json($Response);
return;
}
$Response['status'] = 500;
$Response['message'] = 'Sorry, An unexpected error occured and your catalog could be retrieved.';
$Response['data'] = [];
$response->code(500)->json($Response);
return;
} catch (Exception $e) {
$Response['status'] = 500;
$Response['message'] = $e->getMessage();
$Response['data'] = [];
$response->code(500)->json($Response);
return;
}
return;
}
/**
* fetchCatalogs
*
* Fetches an array of catalogs
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function fetchCatalogs($request, $response)
{
$Response = [];
$JwtMiddleware = new JwtMiddleware();
$jwtMiddleware = $JwtMiddleware::getAndDecodeToken();
if (isset($jwtMiddleware) && $jwtMiddleware == false) {
$response->code(401)->json([
'status' => 401,
'message' => 'Sorry, the authenticity of this token could not be verified.',
'data' => []
]);
return;
}
try {
$CatalogModel = new CatalogModel();
$catalogs = $CatalogModel::fetchCatalogs();
if ($catalogs['status']) {
$Response['status'] = true;
$Response['data'] = $catalogs['data'];
$Response['message'] = '';
$response->code(200)->json($Response);
return;
}
$Response['status'] = 500;
$Response['message'] = 'Sorry, An unexpected error occured and your catalogs could be retrieved.';
$Response['data'] = [];
$response->code(500)->json($Response);
return;
} catch (Exception $e) {
$Response['status'] = 500;
$Response['message'] = $e->getMessage();
$Response['data'] = [];
$response->code(500)->json($Response);
return;
}
return;
}
/**
* deleteCatalog
*
* Deletes a catalog by it's ID
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function deleteCatalog($request, $response)
{
$Response = [];
$JwtMiddleware = new JwtMiddleware();
$jwtMiddleware = $JwtMiddleware::getAndDecodeToken();
if (isset($jwtMiddleware) && $jwtMiddleware == false) {
$response->code(401)->json([
'status' => 401,
'message' => 'Sorry, the authenticity of this token could not be verified.',
'data' => []
]);
return;
}
$validationObject = array(
(Object) [
'validator' => 'required',
'data' => isset($request->id) ? $request->id : '',
'key' => 'Catalog Name'
],
(Object) [
'validator' => 'numeric',
'data' => isset($request->id) ? $request->id : '',
'key' => 'Catalog ID'
]
);
$validationBag = Parent::validation($validationObject);
if ($validationBag->status) {
$response->code(400)->json($validationBag);
return;
}
try {
$CatalogModel = new CatalogModel();
$catalog = $CatalogModel::deleteCatalog($request->id);
if ($catalog['status']) {
$Response['status'] = true;
$Response['data'] = [];
$Response['message'] = '';
$response->code(200)->json($Response);
return;
}
$Response['status'] = 500;
$Response['message'] = 'Sorry, An unexpected error occured and your catalog could be deleted.';
$Response['data'] = [];
$response->code(500)->json($Response);
return;
} catch (Exception $e) {
$Response['status'] = 500;
$Response['message'] = $e->getMessage();
$Response['data'] = [];
$response->code(500)->json($Response);
return;
}
}
}
?>

CatalogController.php file contains a Class which is declared within the app namespace. The file also depends on the Base Controller, the CatalogModel, JwtMiddleware and the RequestMiddleware.

CatalogController.php file contains 6 public methods which all accepts a $request and a $response variable as their parameter.

The createNewCatalog Method

The createNewCatalog Method accepts a $request and a $response variable as it’s parameter. This method creates a new Catalog by making use of the CatalogModel. The method returns an HTTP STATUS CODE OF 201 CREATED with a Valid JSON Response. However, a different status code is returned if something broke during the operation.

The updateCatalog Method

updateCatalog Method accepts a $request and a $response variable as it’s parameter. This method updates an existing Catalog by making use of the CatalogModel. If everything works, the method returns an HTTP STATUS CODE OF 200 OK. However, a different status would be returned if something broke during the process.

fetchCatalogById Method

The fetchCatalogById Method accepts a $request and a $response variable as it’s parameter. This method returns an existing Catalog by making use of the CatalogModel to fetch the catalog based on the catalog ID. The method returns an HTTP STATUS CODE OF 200 OK and a JSON response.

The fetchCatalogByName Method

The fetchCatalogByName Method accepts a $request and a $response variable as it’s parameter. This method returns an existing Catalog by making use of the CatalogModel to fetch the catalog based on the catalog Name. The operation should return an HTTP STATUS CODE OF 200 OK. A different status would be returned if something broke during the operation.

The fetchCatalogs Method

The fetchCatalogs Method accepts a $request and a $response variable as it’s parameter. This method returns all Catalog by making use of the CatalogModel to fetch all the created catalogs. The method should return an HTTP STATUS CODE OF 200 OK.

The deleteCatalog Method

The deleteCatalog Method accepts a $request and a $response variable as it’s parameter. This method deletes all Catalog that matches the Catalog ID via the CatalogModel. If everything works, The method returns an HTTP STATUS CODE OF 200 OK.

With that setup, let’s open our api.php file and uncomment the code snippet below.

    /******************** Catalog Routes **********************/
    $Klein-&gt;respond('POST', '/api/v1/catalog', [ new CatalogController(), 'createNewCatalog' ]);
    $Klein-&gt;respond(['PATCH', 'PUT'], '/api/v1/catalog/[:id]', [ new CatalogController(),  'updateCatalog']);
    $Klein-&gt;respond(['GET', 'HEAD'], '/api/v1/fetch-catalog-by-id/[:id]', [ new CatalogController(), 'fetchCatalogById' ]);
    $Klein-&gt;respond(['GET', 'HEAD'], '/api/v1/fetch-catalog-by-name/[:name]', [ new CatalogController(), 'fetchCatalogByName' ]);
    $Klein-&gt;respond(['GET', 'HEAD'], '/api/v1/catalogs', [ new CatalogController(), 'fetchCatalogs' ]);
    $Klein-&gt;respond('DELETE', '/api/v1/del-catalog/[:id]', [ new CatalogController(), 'deleteCatalog' ]);

Below are some screenshots of consuming the Api in Postman.

Create New Catalog

Update Catalog

Fetch Catalog By Id

Fetch Catalog By Name

Fetch All Catalogs

Delete Catalog

13. Creating The ProductController (App\Controller Directory).

After creating the CatalogController.php file, we can now begin creating the ProductController.php file. This will help us in Creating, Updating, Fetching, and Deleting Products. Create a new file called ProductController.php and after that, paste the following code snippet below into it.

<?php
namespace App;
use App\UserModel;
use App\Controller;
use App\ProductModel;
use App\JwtMiddleware;
use App\RequestMiddleware;
/**
* ProductController - The ProductController. This Controller makes use of a few Models for creating, updating, fetching and deleting Products.
*
* @author Ilori Stephen A <stephenilori458@gmail.com>
* @link https://github.com/learningdollars/php-rest-api/App/Controller/ProductController.php
* @license MIT
*/
class ProductController extends Controller {
/**
* createProduct
*
* Creates a new Product.
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function createProduct($request, $response)
{
$Response = [];
// Call the JSON Middleware
$FormDataMiddleware = new RequestMiddleware();
$formData = $FormDataMiddleware::acceptsFormData();
if (!$formData) {
array_push($Response, [
'status' => 400,
'message' => 'Sorry, Only Multipart Form Data Contents are allowed to access this Endpoint.',
'data' => []
]);
$response->code(400)->json($Response);
return;
}
$JwtMiddleware = new JwtMiddleware();
$jwtMiddleware = $JwtMiddleware::getAndDecodeToken();
if (isset($jwtMiddleware) && $jwtMiddleware == false) {
$response->code(400)->json([
'status' => 401,
'message' => 'Sorry, the authenticity of this token could not be verified.',
'data' => []
]);
return;
}
$Data = $request->paramsPost();
$validationObject = array(
(Object) [
'validator' => 'required',
'data' => isset($Data->name) ? $Data->name : '',
'key' => 'Product Name'
],
(Object) [
'validator' => 'required',
'data' => isset($Data->catalog_id) ? $Data->catalog_id : '',
'key' => 'Product Catalog'
],
(Object) [
'validator' => 'catalogExists',
'data' => isset($Data->catalog_id) ? $Data->catalog_id : '',
'key' => 'Product Catalog'
],
(Object) [
'validator' => 'required',
'data' => isset($Data->price) ? $Data->price : '',
'key' => 'Product Price'
],
(Object) [
'validator' => 'numeric',
'data' => isset($Data->price) ? $Data->price : '',
'key' => 'Product Price'
],
(Object) [
'validator' => 'required',
'data' => isset($Data->color) ? $Data->color : '',
'key' => 'Product Color'
],
(Object) [
'validator' => 'string',
'data' => isset($Data->color) ? $Data->color : '',
'key' => 'Product Color'
],
(Object) [
'validator' => 'required',
'data' => isset($Data->size) ? $Data->size : '',
'key' => 'Product Size'
],
(Object) [
'validator' => 'required',
'data' => !empty($request->files()->banner) ? $request->files()->banner : '',
'key' => 'Product Banner',
],
(Object) [
'validator' => 'img',
'data' => !empty($request->files()->banner) ? $request->files()->banner : '',
'key' => 'Product Banner',
'file_name' => 'banner',
'acceptedExtension' => ['jpg', 'png', 'gif', 'jpeg'],
'maxSize' => 5000000
],
);
$validationBag = Parent::validation($validationObject);
if ($validationBag->status) {
$response->code(400)->json($validationBag);
return;
}
// Work the banner image...
$bannerPath = './public/img/';
$bannerName = time() . '_' . basename($request->files()->banner['name']);
if (!move_uploaded_file($request->files()->banner['tmp_name'], $bannerPath . $bannerName)) {
$Response['status'] = 400;
$Response['data'] = [];
$Response['message'] = 'An unexpected error occuured and your file could not be uploaded. Please, try again later.';
$response->code(400)->json($Response);
return;
}
// create the product...
$Payload = array(
'name' => htmlentities(stripcslashes(strip_tags($Data->name))),
'catalog_id' => (int) htmlentities(stripcslashes(strip_tags($Data->catalog_id))),
'color' => htmlentities(stripcslashes(strip_tags($Data->color))),
'price' => (float) htmlentities(stripcslashes(strip_tags($Data->price))),
'size' => \htmlentities(\stripcslashes(strip_tags($Data->size))),
'banner' => 'public/img/' . $bannerName,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
);
try {
$ProductModel = new ProductModel();
$product = $ProductModel::createProduct($Payload);
if ($product['status']) {
$Response['status'] = 201;
$Response['data'] = $product['data'];
$Response['message'] = '';
$response->code(201)->json($Response);
return;
}
$Response['status'] = 400;
$Response['data'] = [];
$Response['message'] = 'An unexpected error occurred and your product could not be created. Please, try again later.';
$response->code(400)->json($Response);
return;
} catch (Exception $e) {
$Response['status'] = 500;
$Response['message'] = $e->getMessage();
$Response['data'] = [];
$response->code(500)->json($Response);
return;
}
}
/**
* updateProduct
*
* Updates a Product.
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function updateProduct($request, $response)
{
$Response = [];
// Call the JSON Middleware
$ProductModel = new ProductModel();
$FormDataMiddleware = new RequestMiddleware();
$formData = $FormDataMiddleware::acceptsFormData();
if (!$formData) {
array_push($Response, [
'status' => 400,
'message' => 'Sorry, Only Multipart Form Data Contents are allowed to access this Endpoint.',
'data' => []
]);
$response->code(400)->json($Response);
return;
}
$JwtMiddleware = new JwtMiddleware();
$jwtMiddleware = $JwtMiddleware::getAndDecodeToken();
if (isset($jwtMiddleware) && $jwtMiddleware == false) {
$response->code(400)->json([
'status' => 401,
'message' => 'Sorry, the authenticity of this token could not be verified.',
'data' => []
]);
return;
}
$Data = $request->paramsPost();
$validationObject = array(
(Object) [
'validator' => 'required',
'data' => isset($request->id) ? $request->id : '',
'key' => 'Product ID'
],
(Object) [
'validator' => 'productExists',
'data' => isset($request->id) ? $request->id : '',
'key' => 'Product Id'
],
(Object) [
'validator' => 'required',
'data' => isset($Data->name) ? $Data->name : '',
'key' => 'Product Name'
],
(Object) [
'validator' => 'required',
'data' => isset($Data->catalog_id) ? $Data->catalog_id : '',
'key' => 'Product Catalog'
],
(Object) [
'validator' => 'catalogExists',
'data' => isset($Data->catalog_id) ? $Data->catalog_id : '',
'key' => 'Product Catalog'
],
(Object) [
'validator' => 'required',
'data' => isset($Data->price) ? $Data->price : '',
'key' => 'Product Price'
],
(Object) [
'validator' => 'numeric',
'data' => isset($Data->price) ? $Data->price : '',
'key' => 'Product Price'
],
(Object) [
'validator' => 'required',
'data' => isset($Data->color) ? $Data->color : '',
'key' => 'Product Color'
],
(Object) [
'validator' => 'string',
'data' => isset($Data->color) ? $Data->color : '',
'key' => 'Product Color'
],
(Object) [
'validator' => 'required',
'data' => isset($Data->size) ? $Data->size : '',
'key' => 'Product Size'
],
(Object) [
'validator' => !empty($request->files()->banner) ? 'img' : 'nullable',
'data' => !empty($request->files()->banner) ? $request->files()->banner : '',
'key' => 'Product Banner',
'file_name' => 'banner',
'acceptedExtension' => ['jpg', 'png', 'gif', 'jpeg'],
'maxSize' => 5000000
],
);
$validationBag = Parent::validation($validationObject);
if ($validationBag->status) {
$response->code(400)->json($validationBag);
return;
}
// Work the banner image...
$banner = 'public/img/';
if (!empty($request->files()->banner)) {
$product = $ProductModel::findProductById($request->id)['data'];
if (file_exists($product['banner'])) {
unlink($product['banner']);
}
$bannerPath = './public/img/';
$bannerName = time() . '_' . basename($request->files()->banner['name']);
if (!move_uploaded_file($request->files()->banner['tmp_name'], $bannerPath . $bannerName)) {
$Response['status'] = 400;
$Response['data'] = [];
$Response['message'] = 'An unexpected error occuured and your file could not be uploaded. Please, try again later.';
$response->code(400)->json($Response);
return;
}
$banner .= $bannerName;
}
// create the product...
$Payload = array(
'id' => $request->id,
'name' => htmlentities(stripcslashes(strip_tags($Data->name))),
'catalog_id' => (int) htmlentities(stripcslashes(strip_tags($Data->catalog_id))),
'color' => htmlentities(stripcslashes(strip_tags($Data->color))),
'price' => (float) htmlentities(stripcslashes(strip_tags($Data->price))),
'size' => \htmlentities(\stripcslashes(strip_tags($Data->size))),
'banner' => $banner,
'updated_at' => date('Y-m-d H:i:s')
);
try {
$product = $ProductModel::updateProduct($Payload);
if ($product['status']) {
$product['data'] = $ProductModel::findProductById($request->id)['data'];
$Response['status'] = 200;
$Response['data'] = $product['data'];
$Response['message'] = '';
$response->code(200)->json($Response);
return;
}
$Response['status'] = 400;
$Response['data'] = [];
$Response['message'] = 'An unexpected error occurred and your product could not be updated. Please, try again later.';
$response->code(400)->json($Response);
return;
} catch (Exception $e) {
$Response['status'] = 500;
$Response['message'] = $e->getMessage();
$Response['data'] = [];
$response->code(500)->json($Response);
return;
}
}
/**
* getProductById
*
* Gets a Product by it's ID
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function getProductById($request, $response)
{
$Response = [];
// Call the Middleware
$ProductModel = new ProductModel();
$JwtMiddleware = new JwtMiddleware();
$jwtMiddleware = $JwtMiddleware::getAndDecodeToken();
if (isset($jwtMiddleware) && $jwtMiddleware == false) {
$response->code(400)->json([
'status' => 401,
'message' => 'Sorry, the authenticity of this token could not be verified.',
'data' => []
]);
return;
}
$validationObject = array(
(Object) [
'validator' => 'required',
'data' => isset($request->id) ? $request->id : '',
'key' => 'Product ID'
],
(Object) [
'validator' => 'productExists',
'data' => isset($request->id) ? $request->id : '',
'key' => 'Product Id'
],
);
$validationBag = Parent::validation($validationObject);
if ($validationBag->status) {
$response->code(400)->json($validationBag);
return;
}
try {
$ProductModel = new ProductModel();
$product = $ProductModel::findProductById($request->id);
if ($product['status']) {
$Response['status'] = 200;
$Response['data'] = $product['data'];
$Response['message'] = '';
$response->code(200)->json($Response);
return;
}
$Response['status'] = 400;
$Response['data'] = [];
$Response['message'] = 'An unexpected error occurred and your product could not be retrieved. Please, try again later.';
$response->code(400)->json($Response);
return;
} catch (Exception $e) {
$Response['status'] = 500;
$Response['message'] = $e->getMessage();
$Response['data'] = [];
$response->code(500)->json($Response);
return;
}
}
/**
* fetchProducts
*
* Fetches an Array of products....
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function fetchProducts($request, $response)
{
$Response = [];
// Call the Middleware
$ProductModel = new ProductModel();
$JwtMiddleware = new JwtMiddleware();
$jwtMiddleware = $JwtMiddleware::getAndDecodeToken();
if (isset($jwtMiddleware) && $jwtMiddleware == false) {
$response->code(400)->json([
'status' => 401,
'message' => 'Sorry, the authenticity of this token could not be verified.',
'data' => []
]);
return;
}
try {
$ProductModel = new ProductModel();
$products = $ProductModel::fetchProducts();
if ($products['status']) {
$Response['status'] = 200;
$Response['data'] = $products['data'];
$Response['message'] = '';
$response->code(200)->json($Response);
return;
}
$Response['status'] = 400;
$Response['data'] = [];
$Response['message'] = 'An unexpected error occurred and your product could not be retrieved. Please, try again later.';
$response->code(400)->json($Response);
return;
} catch (Exception $e) {
$Response['status'] = 500;
$Response['message'] = $e->getMessage();
$Response['data'] = [];
$response->code(500)->json($Response);
return;
}
}
/**
* deleteProduct
*
* Deletes a Product by it'd ID
*
* @param mixed $request $response Contains the Request and Respons Object from the router.
* @return mixed Anonymous
*/
public function deleteProduct($request, $response)
{
$Response = [];
// Call the Middleware
$ProductModel = new ProductModel();
$JwtMiddleware = new JwtMiddleware();
$jwtMiddleware = $JwtMiddleware::getAndDecodeToken();
if (isset($jwtMiddleware) && $jwtMiddleware == false) {
$response->code(400)->json([
'status' => 401,
'message' => 'Sorry, the authenticity of this token could not be verified.',
'data' => []
]);
return;
}
$validationObject = array(
(Object) [
'validator' => 'required',
'data' => isset($request->id) ? $request->id : '',
'key' => 'Product ID'
],
(Object) [
'validator' => 'productExists',
'data' => isset($request->id) ? $request->id : '',
'key' => 'Product Id'
],
);
$validationBag = Parent::validation($validationObject);
if ($validationBag->status) {
$response->code(400)->json($validationBag);
return;
}
try {
$ProductModel = new ProductModel();
$product = $ProductModel::deleteProduct($request->id);
if ($product['status']) {
$Response['status'] = 200;
$Response['data'] = [];
$Response['message'] = '';
$response->code(200)->json($Response);
return;
}
$Response['status'] = 400;
$Response['data'] = [];
$Response['message'] = 'An unexpected error occurred and your product could not be deleted. Please, try again later.';
$response->code(400)->json($Response);
return;
} catch (Exception $e) {
$Response['status'] = 500;
$Response['message'] = $e->getMessage();
$Response['data'] = [];
$response->code(500)->json($Response);
return;
}
}
}
?>

The ProductController.php file contains a Class declared within the app namespace. The file or the Class depends on the Base Controller, the UserModel, the ProductModel, JwtMiddleware, and the RequestMiddleware.

The createProduct Method

The createProduct Method creates a new Product by taking advantage of the ProductModel. This method is protected by both middlewares we created inside of our middleware folder. This operation returns an HTTP STATUS CODE of 201 Created with a JSON Response otherwise, it returns a different STATUS CODE when the operation fails.

The updateProduct Method

The updateProduct Method updates an existing Product by taking advantage of the ProductModel. It updates a product based on the product Id. This method is also protected by both middlewares we created inside of the middleware folder. This process returns an HTTP STATUS CODE of 200 Ok with a JSON Response else, it returns a different STATUS CODE when the operation fails.

The getProductById Method

The getProductById Method fetches an existing Product by it’s Id through the ProductModel. It fetches a product based on the product Id. This method is guarded by the RequestMiddleware we created inside of the middleware folder. The method returns an HTTP STATUS CODE of 200 Ok with a JSON Response.

The fetchProducts Method

The fetchProducts Method fetches all Products via the ProductModel. The method is protected by the RequestMiddleware created inside of the middleware folder. The method returns an HTTP STATUS CODE of 200 OK with a JSON Response containing all the products. However, a different STATUS CODE is returned if the operation fails.

The deleteProduct Method

The deleteProduct Method deletes all Products that match the product Id via the ProductModel. This method is protected by the RequestMiddleware we created inside of the middleware folder. This method returns an HTTP STATUS CODE of 200 OK with a JSON Response. However, it returns a different STATUS CODE when the operation breaks.

With that setup, let’s open our api.php file and uncomment the code snippet below.

    /******************** Product Routes  **********************/
    $Klein-&gt;respond('POST', '/api/v1/product', [ new ProductController(), 'createProduct' ]);
    $Klein-&gt;respond('POST', '/api/v1/product/[:id]', [ new ProductController(), 'updateProduct' ]);
    $Klein-&gt;respond('GET', '/api/v1/fetch/[:id]', [ new ProductController(), 'getProductById' ]);
    $Klein-&gt;respond('GET', '/api/v1/products', [ new ProductController(), 'fetchProducts' ]);
    $Klein-&gt;respond('DELETE', '/api/v1/delete-product/[:id]', [ new ProductController(), 'deleteProduct' ]);

Below are some screenshots of consuming the Product Endpoints in Postman.

Create New Product

Update Product

Fetch Product By Id

Fetch All Products

Delete Product

Learning Tools

There are a lot of learning tools online. But in case you are looking, I recommend you visit the following URL.

  1. PHP php Official Website. This is the #1 goto place to learn how PHP works. This website contains a whole lot of documentation about PHP. with this in your rearview, You’d never get lost with PHP.

  2. Udemy: This website holds a whole lot of tutorials about PHP and several web technologies alike. You can easily get started by visiting their website at Udemy.com.

Learning Strategy

I used the tools above and a few PDF documentations to get up to speed. I also used Google and Stack Overflow to get more information whenever I get stuck. Above all, I recommend you take advantage of the internet!

Reflective Analysis

It is a simple process for me but I have been able to gain some deeper insights into PHP and programming in general. Working with composer and namespaces for me acted as a benchmark and I really learned a lot in the process. Although I faced a few problems, I have been able to fix those problems by visiting Stack Overflow.

In order to get more knowledge, I recommend that you create a new Middleware or a new Method inside of the Controller.php that will return an XML or a CSV response. However, there are tons of packages out there that you can integrate into the application.

In addition, you can visit packagist to check packages that can help you achieve the solution.

Conclusion

In conclusion, this project mainly focuses on building a REST API in PHP. PHP has been around for a while and it’s still looking promising. The language is still very relevant. In addition, there are some companies out there paying huge amounts of money for PHP programmers.

Building a REST API is definitely something that you need to have under your belt as a programmer as most often than not you will be creating more of them. Therefore, I recommend that you practice a lot.

I hope you have learnt a lot from this lesson. Therefore, I will be looking forward to seeing you build something great with PHP or even better, Hack this solution.

Get the complete project from github.