How To Write A Simple PHP/MySQL Web Service for an iOS App

A tutorial on how to write a simple PHP/MYSQL based web service that you can communicate with from an iOS app. By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Verifying PHP/MySQL functionality

Before you start implementing the PHP web service, first run a quick check to make sure PHP is working on your server OK. Create a new directory on your web server called promos, and create a new file inside called index.php with the following:

<?php

class RedeemAPI {
    // Main method to redeem a code
    function redeem() {
        echo "Hello, PHP!";
    }
}

// This is the first thing that gets called when this page is loaded
// Creates a new instance of the RedeemAPI class and calls the redeem method
$api = new RedeemAPI;
$api->redeem();

?>

This is a very basic PHP file that create an instance of a class (RedeemAPI) and calls a method on it that just outputs “Hello, PHP!”

You can test this by navigating to the URL on your web server with your browser. Even better, you can test this on the command line with a handy utility called curl similar to the following (but replace the URL with your own):

Ray-Wenderlichs-Mac-mini-2:~ rwenderlich$ curl http://www.wildfables.com/promos/
Hello, PHP!

Next, extend the class to make sure the service can connect to your database OK by replacing the RedeemAPI class with the following:

class RedeemAPI {
    private $db;

    // Constructor - open DB connection
    function __construct() {
        $this->db = new mysqli('localhost', 'username', 'password', 'promos');
        $this->db->autocommit(FALSE);
    }

    // Destructor - close DB connection
    function __destruct() {
        $this->db->close();
    }

    // Main method to redeem a code
    function redeem() {
        // Print all codes in database
        $stmt = $this->db->prepare('SELECT id, code, unlock_code, uses_remaining FROM rw_promo_code');
        $stmt->execute();
        $stmt->bind_result($id, $code, $unlock_code, $uses_remaining);
        while ($stmt->fetch()) {
            echo "$code has $uses_remaining uses remaining!";
        }
        $stmt->close();
    }
}

This adds a constructor that connects to your database given a username and password and a destructor that closes the database connection. The redeem loop is modified to run a MySQL statement to select all of the entries in rw_promo_code, and loop through to print a line about each entry.

Once again you can test this with curl to make sure it’s working:

Ray-Wenderlichs-Mac-mini-2:~ rwenderlich$ curl http://www.wildfables.com/promos/
test has 10000 uses remaining!

Web Service Strategy: GET vs POST

OK, now that we know things are working, it’s almost time to implement the full behavior. But first, let’s talk about our strategy for the web service.

We know we need to pass some data from the iPhone app to our web service. Specifically, we need to tell the web service the ID of the app, the code to redeem, and the device id that is trying to redeem.

But how can we pass this data? If you aren’t familiar already, there are two ways to pass data to a web service – via GET (the normal way), or via POST (typically used for posting data to a web form).

Depending on which one you choose, the parameters get passed differently:

  • If you choose GET, the parameters are part of the URL.
  • If you choose POST, the parameters are passed as part of the request body.

Either one would work, but usually when you’re trying to “do something” like redeem a code (rather than just passively retrieving data), you would pick the POST method, so that’s what we’re goint to do here.

What does this mean in practice? All it means is if we want to access the passed parameters in PHP, we get them from the built in $_POST array, as follows:

$_POST["rw_app_id"]

And when we’re using ASIHTTPRequest to connect to the web service later, we’ll use the ASIFormDataRequest class, which sends the request as a POST, as follows:

ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:url];
[request setPostValue:@"1" forKey:@"rw_app_id"];

For more information on GET vs POST, check out the Wikipedia entry on the HTTP protocol.

Update: Also, check out @smpdawg’s comment in the forum topic for this tutorial some additional must-read tips and info on this!

Web Service Strategy: The Algorithm

Next, let’s go over the general algorithm the web service will take:

  • Add an entry into rw_promo_code_redeemed to track the redemption.
  • Decrement the uses_remaining in rw_promo_code.
  • Return the unlock_code to the app, so it can give content to the user.
  1. Make sure the required parameters are passed in via POST.
  2. Make sure the code is actually in the database.
  3. Make sure the code still has uses remaining.
  4. Make sure the device hasn’t already used the code.
  5. If we got this far, success!

OK now that we have a strategy in hand, onto the implementation!

Web Service Implementation

First, add two helper methods to the top of your PHP file that you’ll use to easily return HTTP status messages on success and failure:

// Helper method to get a string description for an HTTP status code
// From http://www.gen-x-design.com/archives/create-a-rest-api-with-php/ 
function getStatusCodeMessage($status)
{
    // these could be stored in a .ini file and loaded
    // via parse_ini_file()... however, this will suffice
    // for an example
    $codes = Array(
        100 => 'Continue',
        101 => 'Switching Protocols',
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        306 => '(Unused)',
        307 => 'Temporary Redirect',
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Timeout',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Request Entity Too Large',
        414 => 'Request-URI Too Long',
        415 => 'Unsupported Media Type',
        416 => 'Requested Range Not Satisfiable',
        417 => 'Expectation Failed',
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Timeout',
        505 => 'HTTP Version Not Supported'
    );

    return (isset($codes[$status])) ? $codes[$status] : '';
}

// Helper method to send a HTTP response code/message
function sendResponse($status = 200, $body = '', $content_type = 'text/html')
{
    $status_header = 'HTTP/1.1 ' . $status . ' ' . getStatusCodeMessage($status);
    header($status_header);
    header('Content-type: ' . $content_type);
    echo $body;
}

If you’re confused why we need this, since this is a web service that conforms to the HTTP protocol, when you send a response you can give a header that specifies any error code and descripton that occurs. There are standard error codes to use, so these methods help make that a bit easier to work with.

As you can see, I found the function to convert status codes to HTML messages from a great tutorial on creating a REST API with PHP.

Next, onto the actual implementation! Replace your redeem method with the following:

function redeem() {

    // Check for required parameters
    if (isset($_POST["rw_app_id"]) && isset($_POST["code"]) && isset($_POST["device_id"])) {
    
        // Put parameters into local variables
        $rw_app_id = $_POST["rw_app_id"];
        $code = $_POST["code"];
        $device_id = $_POST["device_id"];
        
        // Look up code in database
        $user_id = 0;
        $stmt = $this->db->prepare('SELECT id, unlock_code, uses_remaining FROM rw_promo_code WHERE rw_app_id=? AND code=?');
        $stmt->bind_param("is", $rw_app_id, $code);
        $stmt->execute();
        $stmt->bind_result($id, $unlock_code, $uses_remaining);
        while ($stmt->fetch()) {
            break;
        }
        $stmt->close();
        
        // Bail if code doesn't exist
        if ($id <= 0) {
            sendResponse(400, 'Invalid code');
            return false;
        }
        
        // Bail if code already used		
        if ($uses_remaining <= 0) {
            sendResponse(403, 'Code already used');
            return false;
        }	
        
        // Check to see if this device already redeemed	
        $stmt = $this->db->prepare('SELECT id FROM rw_promo_code_redeemed WHERE device_id=? AND rw_promo_code_id=?');
        $stmt->bind_param("si", $device_id, $id);
        $stmt->execute();
        $stmt->bind_result($redeemed_id);
        while ($stmt->fetch()) {
            break;
        }
        $stmt->close();
        
        // Bail if code already redeemed
        if ($redeemed_id > 0) {
            sendResponse(403, 'Code already used');
            return false;
        }
        
        // Add tracking of redemption
        $stmt = $this->db->prepare("INSERT INTO rw_promo_code_redeemed (rw_promo_code_id, device_id) VALUES (?, ?)");
        $stmt->bind_param("is", $id, $device_id);
        $stmt->execute();
        $stmt->close();
        
        // Decrement use of code
        $this->db->query("UPDATE rw_promo_code SET uses_remaining=uses_remaining-1 WHERE id=$id");
        $this->db->commit();
        
        // Return unlock code, encoded with JSON
        $result = array(
            "unlock_code" => $unlock_code,
        );
        sendResponse(200, json_encode($result));
        return true;
    }
    sendResponse(400, 'Invalid request');
    return false;

}

You should be able to understand the general idea of how things work here by looking at the comments inline with the code, and if you’re more curious check out the Mysqli reference. Also, here’s a few things I’d like to point out:

  • isset is a handy function in PHP you can use to tell if a particular variable has been set. We use it here to make sure all the required POST parameters are passed in.
  • Note that instead of appending the passed in variables to the SQL statement ourselves, we use the bind_param method to do that. This is a much safer way, otherwise you make yourself vulnerable to SQL injection attack.
  • Note that the unlock_code is returned encoded with JSON. It’s true we could have passed this as a raw string or such since we’re only returning one thing, but by using JSON it makes it easier to extend later.

And that’s it – your web service is ready to roll! You can test it with the following curl command:

curl -F "rw_app_id=1" -F "code=test" -F "device_id=test" http://www.wildfables.com/promos/
{"unlock_code":"com.razeware.wildfables.unlock.test"}

Note that if you are trying this on my web service, if you get a “code already used” error, you should change your device_id (since each one can only be used once!)

You may also wish to go into your database and check that there’s an entry in your rw_promo_code_redeemed table, that the uses_remaining has decremented, etc. Play around with it a bit!