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

This is the second post in our tutorial series on networking in Swift. If you followed along in the introductory article, you have installed MAMP on your local machine, created an .htaccess file and set up the basic file structure for the project. In this tutorial, we'll begin building the API for our Super PHPlumbing Bros web service.

This RESTful API in PHP will serve content to the Swift-based iOS client application we'll create in the final part of the series. Our API will perform five distinct tasks when queried. It will provide: 1) inventory items response logic for our Plumbing Tools; 2) inventory items response logic for Plumbing Supplies; 3) inventory item response logic for Plumbing Tool with Description; 4) Inventory item response logic for Plumbing Supply with Description; 5) Error response logic.

In this article, we will cover the structure of our API queries, provide a simple bare bones interface for the service, and then build the first half of our custom API to serve up our plumbing supplies inventory.

API Queries
Our application will communicate with the web api through a simple url scheme. If you are not familiar with url schemes, take a brief detour through the linked Wikipedia page to learn about how they work (simply memorizing the components of a url scheme outright will save you lots of needless lookups later).

Our first stab at a url scheme looks crude. Here is the URL scheme that we will use to access our entire inventory of plumbing tools: http://localhost:8888/v1/PlumbingAPI.php?method=copper_pipes_and_fittings&format=json.

Let's take a closer look at this scheme. Any text in a URL that comes after the question mark (?) is known as the URL query component. Each key/value pair that follows is logically separated by an ampersand (&). In this example the query string contains two key/value pairs. These key/value pairs will get passed along to our PlumbingAPI.php script via an associative array known as $_GET[]. Within our PHP script we will access the values for the 'method' and 'format' keys as follows: $_GET['method'] and $_GET['format'] would return 'copper_pipes_and_fittings' and 'json', respectively, in the crude example above. We use these values to determine what was requested of our API.

A Bare Bones API
Let's get down to it. Using your text editor, open PlumbingAPI.php for both versioning folders, v1 and v2. Until further noted, we'll be entering the same code in both versions. For the skeletal structure of our API, we are going to adapt the code from Mark Roland's helpful tutorial on "How to Build a RESTful API Web Service in PHP." We'll explain each component as we add flesh to our bare-bones script. Here's a Gist of the script:


After reading through the code, copy it into the PlumbingAPI.php file in your v1 folder. Using the URL scheme from above, point your web browser at http://localhost:8888/v1/PlumbingAPI.php?method=copper_pipes_and_fittings&format=json, and you will see the API's response to the given method and format:

Simple API call to bare bones script


If you now navigate to http://localhost:8888/v1/PlumbingAPI.php?method=plumbing_tools&format=json, you'll see the response from the plumbing_tools method:

Second API call to bare bones script
  
If this is not working for you, then go back and make sure that you followed all the steps. If you do see a response in your web browser, then congratulations! You've successfully built and deployed your locally hosted web service. The various responses from our api are made possible by the following conditional code structure, from line 120 to the end of the file above:
// Copper Pipes and Fittings API //
if( strcasecmp($_GET['method'],'copper_pipes_and_fittings') == 0){
    echo 'Copper Pipes and Fittings API Call. <br>';
}

// Plumbing Tools API //
else if( strcasecmp($_GET['method'],'plumbing_tools') == 0){
    echo 'Plumbing Tools API Call. <br>';
}

// ** Deliver Response ** //
// Return Response to browser //
deliver_response($_GET['format'], $response);
With the exception of the function definitions, our script is executed from top to bottom. Notice the conditional if else if structure here. In our first API call we passed in the value 'copper_pipes_and_fittings' for the 'method' key. Recall that the parameters of a query string are passed in to the $_GET[] global array, which we read from in our first conditional if check. Since it evaluates to true in the first API call, we fall in that conditional block and therefore echo "Copper Pipes and Fittings API Call", followed by the function call: deliver_response($_GET['format'], $response). This last function call is what prints out the second line in the browser.

In our second api call, we passed in the value 'plumbing_tools' for the 'method' key in the URL. With this query, we fell into the conditional else-if check above. This gave us our alternate response. That's all there is to it!

Moving forward, we'll build on top of this primitive structure of conditional checks followed with a response using the echo call.  For explanations of built-in methods such as  strcasecmp() and echo, check out php.net and search for those calls to get documentation along with code samples on how to use them.

Building the Supplies Interface
We'll now begin building out the control flow for the various tasks our API will perform. In the first if conditional control flow replace the code at line 122 (echo 'Copper Pipes and Fittings API Call. <br>';) with the following:
// 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'] = copper_pipe_or_fitting_item_details(strtoupper($_GET['item_id']));
}
// else return our entire inventory of copper pipes and fittings //
else {
    $response['data'] = copper_pipes_and_fittings_inventory_without_description();
}
Save your PlumbingAPI.php file and point your browser to this URL: http://localhost:8888/v1/PlumbingAPI.php?method=copper_pipes_and_fittings&format=json. The response will appear as follows in your web browser:
Copper Pipes and Fittings Inventory without Descriptions.
Copper Pipes and Fittings Inventory.
Delivering Response. $api_response = Array, format = json
Your are seeing this response because our first if check did not fall through as we did not provide an 'item_id' key with an associated value pair as parameters in the query part of our URL. In other words, a parameter with this format, '&item_id=<some item id>', was not appended to the above URL. This resulted in our conditional control flow to fall through the else path. In this else block we call a method that we defined earlier: copper_pipes_and_fittings_inventory_without_description(). In our definition of this method, notice that we call another method: copper_pipes_and_fittings_inventory(). This last method is responsible for fetching our entire inventory of copper pipes and fittings along with all item details. Go to this method, found in lines 27 -33 above, and replace line 32 (echo 'Copper Pipes and Fittings Inventory. <br>';)<code> with the following:


This is an associative array of sub-arrays. Each sub-array contains all the information of one inventory item. There are twelve items in our hypothetical Copper Pipes and Fittings category, the information for which was cobbled together from online sources. In a real world scenario, this or a similar method would normally pull this data from a data store like a database. But for the sake of brevity we will skip this part. Now go to where we defined the method copper_pipes_and_fittings_inventory_without_description() (line 56 in the bare bones files above), and replace lines 57 and 58 with the following: 
return copper_pipes_and_fittings_inventory();
For now we'll simply return our entire inventory with descriptions. We will revisit this method later and write code to omit item descriptions. Now navigate to our response delivery function (function deliver_response($format, $api_response)), line 23 in the bare bones code, and replace its echo command in line 24 with the following:


The response to our API query gets processed here. If a parameter of 'format=json' is provided in the query part of the URL, then our script would fall through our first if check. This parameter informs our API that one of three available formatted responses was requested. We support three formats in this example: json, xml and html. 

Let's now write the code to process json format requests. In the first conditional if-check block (line 14) replace the comment (// JSON Response //) with the following code:
// Set HTTP Response Content Type //
header('Content-Type: application/json; charset=utf-8');

// Format data into a JSON response //
$json_response = json_encode($api_response, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);

// Deliver formatted data //
echo $json_response;
Save your PlumbingAPI.php file and point your browser to this url, http://localhost:8888/v1/PlumbingAPI.php?method=copper_pipes_and_fittings&format=json. The response will appear as follows in your web browser:
{
    "code": 1,
    "status": 200,
    "data": [
        {
            "id": "CP12010",
            "name": "1 inch copper pipe.",
            "image": "http://localhost:8888/assets/1_inch_copper_pipe.png",
            "description": "1 in. x 10 ft. Copper Type M Hard Temper Straight Pipe for a multitude of plumbing and heating purposes. It is NSF and ANSI Standard 61 certified. Made of hard-temper ASTM - B88 copper. For general plumbing and heating purposes. Alloy C12200 (DHP) meets industry standards and is NSF and ANSI Standard 61 certified. Meets or exceeds industry standards to deliver a high quality flow product. Plumbing and mechanical codes govern what types of products may be used for applications. Local codes should always be consulted for minimum requirements"
        },
        {
            "id": "CP12020",
            "name": "1 1/4 inch copper pipe.",
            "image": "http://localhost:8888/assets/1_1_4_inch_copper_pipe.png",
            "description": "1 1/4 in. x 10 ft. Copper Type M Hard Temper Straight Pipe for a multitude of plumbing and heating purposes. It is NSF and ANSI Standard 61 certified. Made of hard-temper ASTM - B88 copper. For general plumbing and heating purposes. Alloy C12200 (DHP) meets industry standards and is NSF and ANSI Standard 61 certified. Meets or exceeds industry standards to deliver a high quality flow product. Plumbing and mechanical codes govern what types of products may be used for applications. Local codes should always be consulted for minimum requirements"
        },
    .
    .
    .
    .
        {
            "id": "CP14040",
            "name": "1 1/2 inch copper elbow fitting.",
            "image": "http://localhost:8888/assets/1_1_2_inch_copper_elbow_fitting.png",
            "description": "1 1/3 in. Copper Type M Hard Temper Straight Pipe for a multitude of plumbing and heating purposes. It is NSF and ANSI Standard 61 certified. Made of hard-temper ASTM - B88 copper. For general plumbing and heating purposes. Alloy C12200 (DHP) meets industry standards and is NSF and ANSI Standard 61 certified. Meets or exceeds industry standards to deliver a high quality flow product. Plumbing and mechanical codes govern what types of products may be used for applications. Local codes should always be consulted for minimum requirements"
        }
    ],
    "api_version": "1.0.0"
}
The vertical ellipses indicate that we trimmed the actual response. You should see a lot more data in your browser. The problem with this response is that the copper pipes and fittings inventory response contains too much data. In our next step we'll trim the response so that item descriptions are omitted. 

It is not good practice to submit details of items when requesting them in bulk. In a real-world scenario, internet access is often spotty and unreliable. And serving up too much data could negatively impact your users' experience. And since users are often not aware that your application is not to blame for a given issue, but rather their connectivity to the network, they will associate this bad experience with your application. So, in an effort to mitigate connectivity issues, we should always prefer to send the least amount of data as possible over a network. Also, it is very unlikely that you can fit all this data for one item onto a screen when presenting the user with a list of items.

To omit item details, navigate to where we defined the method copper_pipes_and_fittings_inventory_without_description(), and replace our return command (return copper_pipes_and_fittings_inventory();) with this chunk of code:
    // pull our entire inventory of copper pipes and fittings //
    $inventory = copper_pipes_and_fittings_inventory();
   
    // container for inventory minus descriptions//
    $inventory_without_details = array();
   
    // iterate through inventory and duplicate all attributes except 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;    
This code is straight forward. It iterates through the json objects and all the items into another container with the exception of key value pairs that are associated with a key name of 'description.' Save your PlumbingAPI.php file and point your browser to this url, http://localhost:8888/v1/PlumbingAPI.php?method=copper_pipes_and_fittings&format=json. The response will appear as follows in your web browser:
{
    "code": 1,
    "status": 200,
    "data": [
        {
            "id": "CP12010",
            "name": "1 inch copper pipe.",
            "image": "http://localhost:8888/assets/1_inch_copper_pipe.png"
        },
        {
            "id": "CP12020",
            "name": "1 1/4 inch copper pipe.",
            "image": "http://localhost:8888/assets/1_1_4_inch_copper_pipe.png"
        },
          .
    .
    .
    .
        {
            "id": "CP14040",
            "name": "1 1/2 inch copper elbow fitting.",
            "image": "http://localhost:8888/assets/1_1_2_inch_copper_elbow_fitting.png"
        }
    ],
    "api_version": "1.0.0"
}
Again, the vertical ellipses indicate that we trimmed the actual response. Notice that the descriptions have been omitted from the response. 

We will now write code to serve an individual copper pipe or fitting with a description. Find where the method function copper_pipe_or_fitting_item_details($item_id) is defined (line 66 in our bare bones API) and replace the two lines filling out the function (i.e. 67 and 68), with the following:
// pull our entire inventory of copper pipes and fittings //
$inventory = copper_pipes_and_fittings_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=copper_pipes_and_fittings&item_id=CP12010&format=json. This is an endpoint to the first item from our json inventory response above. You should see the response below:
{
    "code": 1,
    "status": 200,
    "data": {
        "id": "CP12010",
        "name": "1 inch copper pipe.",
        "image": "http://localhost:8888/assets/1_inch_copper_pipe.png",
        "description": "1 in. x 10 ft. Copper Type M Hard Temper Straight Pipe for a multitude of plumbing and heating purposes. It is NSF and ANSI Standard 61 certified. Made of hard-temper ASTM - B88 copper. For general plumbing and heating purposes. Alloy C12200 (DHP) meets industry standards and is NSF and ANSI Standard 61 certified. Meets or exceeds industry standards to deliver a high quality flow product. Plumbing and mechanical codes govern what types of products may be used for applications. Local codes should always be consulted for minimum requirements"
    },
    "api_version": "1.0.0",
    "item_id": "CP12010"
}

Congratulations! We have completed the first half of our web API, serving up content for our copper pipes and fittings inventory. In the next article in the series, we'll complete the API to serve up our inventory of plumbing tools, and then begin building our Swift application to bring that content to our end user. Follow the line for part two on building a RESTful API in PHP.

This project can be found on GitHub.

As always, comments, questions, and suggestions are welcome below!

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. 

2 comments:

  1. Hi, great tutorial but I would like to try pulling the data from a database like MAMP MySQL. Any example/link on how that could be done?

    ReplyDelete
    Replies
    1. We are glad that you like the tutorial! I don't know of a proper example of how to do this but here is a subreddit that might be helpful. https://www.reddit.com/r/UofRIntro2PHP/

      Delete