Networking in Swift: Building a RESTful API in PHP, Part 2

This is the third post in our tutorial series on networking in Swift. In the introductory article, we provided an overview of the project and set up our local server. In the next piece, we began building out our RESTful API in PHP, which will serve the inventory for our hypothetical Super PHPlumbing Bros. supply company to the custom Swift client we will construct in the final segment of the series.

If you've been following along thus far, you've set up your local server and completed building the first half of the API, which serves the inventory for the shop's supplies. In this article, we will complete our API by adding the necessary code to serve the content of our plumbing tools inventory.

Building the Plumbing Tools Interface
In creating the API for our plumbing tools interface, we will follow pretty much the same series of steps we took while building the plumbing supplies interface, continuing to add the necessary code to the bare bones API we started with in part one. Our skeletal structure now has some real meat on it. Your PlumbingAPI.php file should now look like this:

Jumping In . . .
Once again, let's begin toward the bottom, in the final else if block in the script, the plumbing tools API call, which begins at line 263 above. Replace the echo command in line 264 with the following code block, which contains the relevant response logic:
// build payload //
$response['code'] = 1;
$response['api_version'] = '1.0.0';
$response['status'] = $api_response_code[ $response['code'] ]['HTTP Response'];
   
// if an 'item_id' was provided then return details for that item //
if ( $_GET['item_id'] ) {
    $response['item_id'] = strtoupper($_GET['item_id']);
    $response['data'] = plumbing_tool_item_details(strtoupper($_GET['item_id']));
}
// else return our entire inventory of plumbing tools //
else {
    $response['data'] = plumbing_tools_inventory_without_description();
}
Save your PlumbingAPI.php file and point your browser to this url, http://localhost:8888/v1/PlumbingAPI.php?method=plumbing_tools&format=json. The response will appear as follows in your web browser:
Plumbing Tools Inventory without Descriptions.
Plumbing Tools Inventory.
{ "code": 1, "status": 200, "data": null, "api_version": "1.0.0" }
Now let's add in our plumbing tools inventory. The relevant function begins at line 132 above (function plumbing_tools_inventory()). Replace the echo command with the following associative array:

As in part one, this array constitutes the actual content of the relevant inventory, including item ids, names, image locations, and descriptions. Let's now write the necessary code to serve up our tools inventory without the lengthy descriptions. Appropriately, this will be placed in the plumbing_tools_inventory_without_description() function, located at line 140 in the full script file above. Replace the two lines of placeholder code in that function (lines 141 and 142) with the following snippet:

// pull our entire inventory of plumbing tools //
$inventory = plumbing_tools_inventory();
   
// container for plumbing tools inventory with omitted description //
$inventory_without_details = array();
   
// iterate over the array, duplicate the inventory without descriptions//
foreach ($inventory as $key=>$value) {
    if (is_array($value)) {
        $inventory_item = array();
        foreach ($value as $subkey=>$subvalue) {
            if ( strcasecmp($subkey,'description') != 0 )
                $inventory_item[$subkey] = $subvalue;
        }
        $inventory_without_details[$key] = $inventory_item;
    }
}
return $inventory_without_details;
Save your file and point your browser to this endpoint: http://localhost:8888/v1/PlumbingAPI.php?method=plumbing_tools&format=json. You should see a json response for our entire inventory of plumbing tools.

Now we'll work on serving individual plumbing tools along with their descriptions. Find the plumbing_tools_item_details($item_id) function at line 176 in the script above, and replace the place-holder code in lines 177 and 178 with the following block:
// pull our entire inventory of plumbing tools //
$inventory = plumbing_tools_inventory();
   
// container for item matching the provided item_id //
$inventory_item = array();
   
// iterate through our inventory and find the requested item //
foreach ($inventory as $key=>$value) {
    if (is_array($value) && strcasecmp($value['id'], $item_id) == 0) {
        foreach ($value as $subkey=>$subvalue) {
                $inventory_item[$subkey] = $subvalue;
        }
        break;
    }
}
return $inventory_item;
Save your php file and point your browser to this url:http://localhost:8888/v1/PlumbingAPI.php?method=plumbing_tools&item_id=PT12010&format=json. This is an endpoint to the first item from our json inventory response for plumbing tools. You should see the following response in your browser:
{
    "code": 1,
    "status": 200,
    "data": {
        "id": "PT12010",
        "name": "Plunger.",
        "image": "http://localhost:8888/assets/plunger.png",
        "description": "Super-pliable industrial-rubber cup with tiered ridges forms ultra-tight seal on any size drain. The heavy-duty steel handle allows for maximum pressure forced down drain to source of clog. Designed to work effectively at any angle for hard-to-reach, low-clearance applications."
    },
    "api_version": "1.0.0",
    "item_id": "PT12010"
}
Our API is almost complete! Besides json, you will typically encounter web APIs that support multiple formats. Let's have our API also support XML and HTML responses. Find the deliver_response($format, $api_response) function at line 23 in the API script above, and locate the "XML Response" else if block that begins at line 49. Currently, it only has a place holder comment indicating that this is where we will our XML response logic will live. Add the following code to that block:
// Set HTTP Response Content Type //
header('Content-Type: application/xml; charset=utf-8');
       
// initializing or creating array //
$data = $api_response['data'];

// Format data into an XML response //
$xml = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";
arrayToXml($data, $xml);
       
// Deliver formatted data //
echo $xml;
This is the next line. Notice that the HTML response logic is in the final else block of the same function. Replace the place-holder "// HTML Response //" comment with the following snippet:
// Set HTTP Response Content Type //
header('Content-Type: text/html; charset=utf-8');

// initializing ro creating array //
$data = $api_response['data'];
       
$payload = '';
$html = '';
if (is_array($data)) {
    arrayToHTML($data, $html);
    $payload = $html;
}
else {
    $payload = $data;
}
       
// Deliver formatted data //
echo $payload;
Finally, we have to fill out the arrayToXml($data, $xml) and arrayToHTML($data, $html) functions referenced in these two snippets. Find the empty arrayToXml function at line 214 in the PHP script above, and add the following to its body:
// wrap XML with $wrap TAG //
if ($wrap != null) {
    $xml .= "<$wrap>\n";
}
// main loop //
foreach ($array as $key=>$value) {
  
    if(is_array($value)) {
        // recursive call //
        arrayToXml($value, $xml,'ITEM');
    } else {
        // set tags in uppercase if needed //
        if ($upper == true) {
              $key = strtoupper($key);
            }
            // append to XML string //
            $xml .= "<$key>" . htmlspecialchars(trim($value)) . "</$key>";
    }
}
// close tag if needed //
if ($wrap != null) {
    $xml .= "</$wrap>\n";
}
Now locate the arrayToHTML function, located at line 224 in our PHP script above. Like its XML counterpart, it is currently empty. Add the following code to the body of the function:
// wrap html with $tag //
if ($tag != null) {
    $html .= "<HTML>\n";
}
// main loop //
foreach ($array as $key=>$value) {
   
    if(is_array($value)) {
        // recursive call //
        arrayToHTML($value, $html,'h1');
    } else {
        // set tags in uppercase if needed //
        if ($upper == true) {
            $key = strtoupper($key);
        }
        // append to XML string //
        $html .= "<$key>" . strtoupper($key) . ' : ' . htmlspecialchars(trim($value)) . "</$key><br>";
    }
}
// close tag if needed //
if ($tag != null) {
    $html .= "</HTML>\n";
}
Save your file and point your browser to this endpoint: http://localhost:8888/v1/PlumbingAPI.php?method=plumbing_tools&item_id=PT12010&format=json . You should see a json response for our entire inventory of plumbing tools. If you type in xml instead of json, in the url, you should see an xml response. Also, typing in html will result in an html response. 

Rejoice! We have completed what is arguably the hardest part of this tutorial!

Clean URLs
Although our URLs up to now were well-formed for the task, you will at times be restricted to use what is known as clean URLs to query a web service. Clean URLs are structured in such a way that the query portion of the URLs that we have been using thus far are instead inserted within the URL path. Simply put, instead of using this url, http://localhost:8888/v1/PlumbingAPI.php?method=plumbing_tools&format=json, to access our inventory of plumbing, we will setup a mechanism that supports this type, http://localhost:8888/api/v1/plumbing_tools.json, of URL as well.

For this, we will add some code to the .htaccess file created in the introductory article to this series. Remember, using .htaccess files is not recommended for production environments, but it will serve us just fine for the purposes of our testing our Swift app. In your usual text editor, type in the following lines of code to the .htaccess file:
# Turn on the rewrite engine
RewriteEngine on

# Rewrite Rules for version 1.0.0 api
# ###################################
# Access to a single inventory item
RewriteRule ^api/v1/([^\/]*)[/]([^\/]*)[//.](html|json|xml) /v1/PlumbingAPI.php?method=$1&item_id=$2&format=$3 [L]
# Access to entire inventory
RewriteRule ^api/v1/([^\/]*)[//.](html|json|xml) /v1/PlumbingAPI.php?method=$1&format=$2 [L]
Any text after a pound character (#) is ignored by the interpreter of this file—it indicates a comment. The first non-comment line of code notifies the interpreter that we will be using rewrite rules. The other lines of code translate our clean url into a standard url that our web service can understand.

The syntax for the rewrite rule is as follows: RewriteRule Pattern Substitution [flags]. For the pattern argument we provided a regular expression (regex) to conduct the translation for us, as we would like to make it as generic as possible—in order to support multiple API signatures. The first RewriteRule interprets the single item with description inventory request for both copper pipes and fittings as well as plumbing tools. The second RewriteRule addresses requests for entire inventories. See this guide (.pdf) for more on regular expressions.

In the same file but just below the our last line, we'll now create the rewrite rules for version two of our API. Here are the rewrite rules for version two of our web service:

# Rewrite Rules for version 2.0.0 api
# ###################################
# Access to a single inventory item
RewriteRule ^api/v2/([^\/]*)[/]([^\/]*)[//.](html|json|xml) /v2/PlumbingAPI.php?method=$1&item_id=$2&format=$3 [L]
# Access to entire inventory
RewriteRule ^api/v2/([^\/]*)[//.](html|json|xml) /v2/PlumbingAPI.php?method=$1&format=$2 [L]
Save your .htaccess file and point your browser to the following url: http://localhost:8888/api/v1/plumbing_tools.json. The web service should respond by sending you the entire inventory of plumbing tools.

This url will also work: http://localhost:8888/api/v1/plumbing_tools/json. You can replace the json path with any other supported format.

Now point your browser here: http://localhost:8888/api/v1/plumbing_tools/pt12010.json. PT12010 is an item id value. This last web service request will respond by transmitting the relevant information along with description for the item matching the provided id.


Versioning
Placing web services with expanded or modified functionality into a separate folder is arguably the simplest versioning scheme to implement. There are other more complicated, and necessary, versioning schemes in use today. In our scheme, folder v1 is the earliest web service and v2 is the most recent. A number of arguments speak in favor of this implementation: there is no ambiguity about which web service version you are talking to, backward compatibility is maintained and you don't have to worry about creating dependency issues because each versioning folder contains the complete set of services.


Moving Forward
With that, we have completed our Super PHPlumbing Bros inventory API. Next week, we will move on to the final segment of this tutorial series and begin building the Swift client application that we will use to query the API and serve the content up to the end user. As always, comments, questions and suggestions are welcome below.



This project can be found on GitHub.

Index
Introduction
Introduction and Overview: From the Back End API to the End User Application

The Web API
Building a RESTful API in PHP, Part 1
Building a RESTful API in PHP, Part 2

The Swift Client App
Networking in Swift: Building the Swift Client, Part 1
Networking in Swift: Building the Swift Client, Part 2
Networking in Swift: Building the Swift Client, Part 3
Networking in Swift: Building the Swift Client, Part 4
Networking in Swift: Building the Swift Client, Part 5


This tutorial was authored by Stefan Agapie, a Senior iOS Software Engineer, and adapted for the present piece.

No comments:

Post a Comment