Hire the author: Stephen I
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.
-
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.
-
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.
-
HTTP VERBS: This actually means your HTTP PROTOCOL such as your GET, POST, PUT and PATCH requests.
-
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.
-
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.
-
A Localhost Server: You can download the latest version of any of the following. XAMPP, LAMPP, MAMPP, and AMPPS depending on your operating system.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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" | |
] | |
} | |
} |
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'; | |
?> |
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(); | |
?> |
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->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(); | |
} | |
} | |
?> |
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; | |
} | |
} | |
?> |
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' => [] | |
); | |
} | |
} | |
?> |
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' => [] | |
); | |
} | |
} | |
?> |
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' => [] | |
); | |
} | |
} | |
?> |
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 []; } | |
} | |
} | |
?> |
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.
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'; | |
} | |
} | |
?> |
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; | |
} | |
} | |
} | |
?> |
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->respond('POST', '/api/v1/user', [ new UserController(), 'createNewUser' ]);
$Klein->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
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->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' ]);
Below are some screenshots of consuming the Api in Postman.
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 | |
*/ | |