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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Write A Simple PHP/MySQL Web Service for an iOS App
20 mins
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:
- Make sure the required parameters are passed in via POST.
- Make sure the code is actually in the database.
- Make sure the code still has uses remaining.
- Make sure the device hasn’t already used the code.
- If we got this far, success!
- 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.
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!