In the previous post we saw what REST services are, along with some examples of how they can be invoked using Postman. In this article, we will see how to create services for basic operations (CRUD: Create, Read, Update, Delete) for managing a resource, using PHP and the MySQL database. Since we need a database as well as a PHP development environment, I chose to use XAMPP, which can be downloaded from this address. XAMPP is a free distribution of Apache containing PHP and MySQL.
Database Definition
Let's start by creating the database:Show / hide code
The resource we will manage is a table containing articles. For simplicity, the table will contain, in addition to an id field (primary key), the fields code, description and quantity, as well as two fields created and updated set, respectively, with the record creation date and the last update date. This is the script for defining the articles table:Show / hide code
The .htaccess file is a simple text file that allows you to configure Apache through directives. When a request is sent to Apache, it first checks for a .htaccess file in the folder of the requested file or in a higher folder. If a .htaccess file is defined, it reads the configuration and modifies the request sent (typically, the URL) to comply with the specified directives. The .htaccess file we will use for our project is this:Show / hide code
# Enable Apache mod_rewrite moduleRewriteEngine On
# Specify the URL sent through the rewrite rules,# that is, set the rewrite base for the specified rules.# This is necessary when PHP files are not in the root but in a specific folderRewriteBase /demo/v1
# Continue processing the rules below if a directory with the specified name in the URL does not existRewriteCond %{REQUEST_FILENAME}!-d
# Continue processing the rules below if a file with the specified name in the URL does not existRewriteCond %{REQUEST_FILENAME}!-f
# Tell Apache to rewrite all requests to the server to send them to an index.php file.# For example, if a user requests the URL http://<root>/products/1,# the rewrite rule will redirect it to http://<root>/index.php?request=products/1RewriteRule ^(.*)$ index.php?request=$1[QSA,NC,L]
It is important to pay attention to the instruction RewriteRule ^(.*)$ index.php?request=$1 [QSA,NC,L]. With this command, we tell Apache to redirect all requests to index.php passing, in the query string, the parameter ?request=$1, where $1 is the part of the original URI minus the root part. For example, if the request URI was www.server.com/resource/35, then $1 = "resource/35", so the request would be redirected to www.example.com/index.php/resource/35. So we need an index.php file to act as an "orchestrator" for requests sent to the server.
Project Structure
Let's create a demo directory inside the root to store the PHP files. Inside, we create a v1 directory, used for versioning, and inside it, we define a directory structure so that we have this situation:
The api directory, inside v1, will contain a directory for each resource managed by our APIs (initially only the articles directory). Inside articles we will define the PHP files for CRUD operations on this resource. The class directory will contain a file for each managed resource, in which we will define a class representing the resource entity along with methods for reading and writing operations. The config directory will contain the configuration files for our APIs, particularly the one related to the database. The index.php file, as mentioned, will be used to handle requests. So the final project structure can be schematized as follows:
As mentioned, here we find the Database class with the parameters for accessing the database (server name, database name, user and password) and the method that provides the connection.
This script is called by any request made to the server, as per the RewriteRule directive defined in the .htaccess file. The logic implemented provides for retrieving the verb with which the service was invoked ($http_verb = $_SERVER['REQUEST_METHOD'];), as well as the resource and any Id on which the service must operate. To read this information, the $_REQUEST array is accessed with the 'request' key, then, by splitting the value retrieved from the array using the '/' separator, the resource and any Id to work on are set. At this point, depending on the verb used (GET, POST, PATCH, DELETE), the relevant script of the resource is called.
Note:
if you use the GET verb (read), there are two possibilities:
if the Id is set, the single_read.php script is called to read a single resource, that is, the one corresponding to the Id;
if the Id is not set, the read.php script is called to read all resources.
calls using the PATCH and DELETE verbs must be made by passing the Id
with the POST call, it is checked that among the query-string parameters there is create; in this way we could use the POST verb also for other functionalities, for example for searches, naturally specifying an ad-hoc parameter in the query-string.
File "articles.php"
Show / hide code
<?phpclassArticles{// Connectionprivate$conn;// Tableprivate$db_table="articles";// Columnspublic$id;public$code;public$description;public$quantity;public$created;public$updated;// Database connectionpublicfunction__construct($db){$this->conn=$db;}// Read all articlespublicfunctiongetArticles(){$sqlQuery="SELECT id, code, description, quantity, created, updated FROM ".$this->db_table."";$stmt=$this->conn->prepare($sqlQuery);$stmt->execute();return$stmt;}// Insert an articlepublicfunctioncreateArticle(){$sqlQuery="INSERT INTO
".$this->db_table."
SET
code = :code,
description = :description,
quantity = :quantity,
created = :created,
updated = :updated";$stmt=$this->conn->prepare($sqlQuery);// Convert HTML tags to text$this->code=htmlspecialchars(strip_tags($this->code));$this->description=htmlspecialchars(strip_tags($this->description));$this->quantity=htmlspecialchars(strip_tags($this->quantity));$this->created=htmlspecialchars(strip_tags($this->created));$this->updated=htmlspecialchars(strip_tags($this->updated));// bind data$stmt->bindParam(":code",$this->code);$stmt->bindParam(":description",$this->description);$stmt->bindParam(":quantity",$this->quantity);$stmt->bindParam(":created",$this->created);$stmt->bindParam(":updated",$this->updated);if($stmt->execute()){$this->id=$this->conn->lastInsertId();returntrue;}returnfalse;}// Read an articlepublicfunctiongetArticle(){$sqlQuery="SELECT
id,
code,
description,
quantity,
created,
updated
FROM
".$this->db_table."
WHERE
id = ?
LIMIT 0,1";$stmt=$this->conn->prepare($sqlQuery);$stmt->bindParam(1,$this->id);$stmt->execute();$dataRow=$stmt->fetch(PDO::FETCH_ASSOC);if($dataRow){$this->code=$dataRow['code'];$this->description=$dataRow['description'];$this->quantity=$dataRow['quantity'];$this->created=$dataRow['created'];$this->updated=$dataRow['updated'];}}// Update an articlepublicfunctionupdateArticle(){$sqlQuery="UPDATE
".$this->db_table."
SET
code = :code,
description = :description,
quantity = :quantity,
updated = :updated
WHERE
id = :id";$stmt=$this->conn->prepare($sqlQuery);$this->code=htmlspecialchars(strip_tags($this->code));$this->description=htmlspecialchars(strip_tags($this->description));$this->quantity=htmlspecialchars(strip_tags($this->quantity));$this->updated=htmlspecialchars(strip_tags($this->updated));$this->id=htmlspecialchars(strip_tags($this->id));// Data binding$stmt->bindParam(":code",$this->code);$stmt->bindParam(":description",$this->description);$stmt->bindParam(":quantity",$this->quantity);$stmt->bindParam(":updated",$this->updated);$stmt->bindParam(":id",$this->id);if($stmt->execute()){returntrue;}returnfalse;}// Delete an articlefunctiondeleteArticle(){$sqlQuery="DELETE FROM ".$this->db_table." WHERE id = ?";$stmt=$this->conn->prepare($sqlQuery);$this->id=htmlspecialchars(strip_tags($this->id));$stmt->bindParam(1,$this->id);if($stmt->execute()){returntrue;}returnfalse;}}?>
The Articles class represents the articles resource entity. Here we find its modeling, along with methods for reading and writing operations. In detail, the following methods have been implemented:
getArticles: retrieves data for all articles;
createArticle: creates an article;
getArticle: reads a single article;
updateArticle: updates the attributes of an article;
deleteArticle: deletes an article.
These methods will be used by the PHP scripts for CRUD operations on the articles resource.
File "create.php"
Show / hide code
<?phpheader("Access-Control-Allow-Origin: *");header("Content-Type: application/json; charset=UTF-8");include_once'config/database.php';include_once'class/articles.php';$database=newDatabase();$db=$database->getConnection();$item=newArticles($db);$data=json_decode(file_get_contents("php://input"));$item->code=$data->code;$item->description=$data->description;$item->quantity=$data->quantity;$item->created=date('Y-m-d H:i:s');$item->updated=date('Y-m-d H:i:s');$result=array();$result["result"]="";$result["data"]=array();if($item->createArticle()){$result["result"]='Article inserted.';$result["data"]=array("id"=>$item->id,"code"=>$item->code,"description"=>$item->description,"quantity"=>$item->quantity,"created"=>$item->created,"updated"=>$item->updated);http_response_code(201);}else{$result["result"]='Article not inserted.';http_response_code(500);}echojson_encode($result);?>
In this PHP script we find the implementation of creating a new article. This operation naturally needs a database connection, which is retrieved through the getConnection method. Then an instance of the Articles class is created, whose properties are set with the content of the body (payload) specified in the service call. The body is read using json_decode(file_get_contents("php://input")), stored in the $data variable and used to set the properties of the class instance through assignments: $item->... = $data->.... The created and updated properties are set with the system date and time. Then the $result variable is defined, that is, the response to the article creation request. This information consists of two elements:
result: indicates whether the operation was successful (text message);
data: contains the values of the attributes of the inserted entity.
The createArticle method of the Articles class is then called, which inserts the record into the articles table of the database. If the insertion was successful, the result attribute is set to Article inserted. and data is populated with the data of the inserted entity, otherwise result takes the value Article not inserted. and data is not defined. Through the http_response_code command we define the HTTP response code of the service.
File "delete.php"
Show / hide code
<?phpheader("Access-Control-Allow-Origin: *");header("Content-Type: application/json; charset=UTF-8");include_once'config/database.php';include_once'class/articles.php';$database=newDatabase();$db=$database->getConnection();$item=newArticles($db);$item->id=isset($resource_id)?$resource_id:die();$result=array();$result["result"]="";if($item->deleteArticle()){$result["result"]='Article deleted.';http_response_code(200);}else{$result["result"]='Article not deleted.';http_response_code(500);}echojson_encode($result);?>
In this PHP script we find the implementation of deleting an article. The part related to the database connection is basically identical to that defined in the create.php file. It is checked that the Id is defined, otherwise the script stops execution. The instance of the Articles class is used to call the deleteArticle method, whose result allows us to define the $result variable with the response returned and the HTTP response code.
File "read.php"
Show / hide code
<?phpheader("Access-Control-Allow-Origin: *");header("Content-Type: application/json; charset=UTF-8");include_once'config/database.php';include_once'class/articles.php';$database=newDatabase();$db=$database->getConnection();$item=newArticles($db);$item->id=isset($resource_id)?$resource_id:die();$result=array();$result["result"]="";$result["data"]=array();$item->getArticle();if($item->code!=null){$result["data"]=array("id"=>$item->id,"code"=>$item->code,"description"=>$item->description,"quantity"=>$item->quantity,"created"=>$item->created,"updated"=>$item->updated);$result["result"]='Article found.';http_response_code(200);}else{$result["result"]='Article not found.';http_response_code(404);}echojson_encode($result);?>
In this PHP script we find the implementation of reading an article, given its Id. The important part of this script is the call to the getArticle method. If the result is positive, that is, if the code property of the article instance is set, the data attribute of the $result variable is set with the array containing the properties of the article instance, as well as the result attribute with the value Article found.. If the result of the read is negative, only the result attribute is set with the value Article not found. and the HTTP response code is set to 404 (Not Found).
File "readAll.php"
Show / hide code
<?phpheader("Access-Control-Allow-Origin: *");header("Content-Type: application/json; charset=UTF-8");include_once'config/database.php';include_once'class/articles.php';$database=newDatabase();$db=$database->getConnection();$articles=newArticles($db);$stmt=$articles->getArticles();$itemCount=$stmt->rowCount();if($itemCount>0){$result=array();$result["body"]=array();$result["itemCount"]=$itemCount;while($row=$stmt->fetch(PDO::FETCH_ASSOC)){extract($row);$e=array("id"=>$id,"code"=>$code,"description"=>$description,"quantity"=>$quantity,"created"=>$created,"updated"=>$updated);array_push($result["body"],$e);}http_response_code(200);echojson_encode($result);}else{http_response_code(404);echojson_encode(array("message"=>"No records found."));}?>
In this PHP script we find the implementation of reading all articles. Unlike the read.php script, the method called is getArticles and the result (variable $result) contains the following attributes:
itemCount: is set with the number of articles read;
body: is an array in which each element contains the data of a read article.
File "update.php"
Show / hide code
<?phpheader("Access-Control-Allow-Origin: *");header("Content-Type: application/json; charset=UTF-8");include_once'config/database.php';include_once'class/articles.php';$database=newDatabase();$db=$database->getConnection();$item=newArticles($db);$data=json_decode(file_get_contents("php://input"));$item->id=isset($resource_id)?$resource_id:die();$item->code=$data->code;$item->description=$data->description;$item->quantity=$data->quantity;$item->updated=date('Y-m-d H:i:s');$result=array();$result["result"]="";$result["data"]=array();if($item->updateArticle()){$result["result"]='Article updated.';$result["data"]=array("id"=>$item->id,"code"=>$item->code,"description"=>$item->description,"quantity"=>$item->quantity,"updated"=>$item->updated);http_response_code(200);}else{$result["result"]='Article not updated.';http_response_code(500);}echojson_encode($result);?>
In this PHP script we find the implementation of updating an article. The method called this time is updateArticle, whose result affects the response provided after the service call. In the absence of errors, the service responds with the updated data of the article and the HTTP response code is 200, otherwise it is 500.
Testing with Postman
After writing some PHP code, it's time to test its operation. To do this we will use POSTMAN, introduced in this article. Let's start by testing the service for creating an article, to populate the relevant table. The service for creating an article uses the POST verb and, in the URI, has the op=create parameter in the query string. In summary, the service has these parameters:
The Status = 200 OK indicates that the service execution ended without errors. Let's check if the record was actually inserted in the articles table. To do this, we call the service that queries all articles, with these parameters:
Again, the Status = 200 OK tells us that the service call was successful and the response lists all articles in the articles table of the database. Let's insert two more articles, specifying these data in the body:
As expected, we find the three articles inserted. To query the single article using its Id, we can use the GET verb, specifying the article Id in the URI:
Verb
GET
URI
http://localhost/demo/v1/articles/2
As we can see, in the URI we specified the article Id right after the resource name articles and using the / separator. The result of the request is:
Another important operation is updating (UPDATE). In this case, the verb to use is PATCH, in the URI we must specify the Id of the article to be modified and in the body we will define all the attributes of the Article entity with the value to assign: