Extending Ian Selby’s RESTful API in PHP – Part 1

Follow Me on Pinterest

Extending Ian Selby's RESTful API in PHPWhen working on projects here at Substance, as a PHP developer, it is always rewarding to be able to have creative control over what code you write. It is especially satisfying to me to be able to run with an idea or implementation.

On a recent project which can be found here I was tasked to build an API so that a series of applications could make calls and log achievements into a centralised database from several applications running different programming languages.

What better way to do this than to use a RESTful API ?

I found the following articles on Gen X Design that helped me do just that. I will talk about how I extended Ian Selby’s functionality to build an API but with a few additions such as implementing call by call authentication and also determining what content the user expects back using an extension.

In this blog post I will talk about how I implemented the content type processing which allows the user to add an extension to their url so that they can get content back either in JSON or XML formats.

First, lets look at the .htaccess code used to rewrite the api calls to our server.

Options +FollowSymLinks
RewriteEngine on
#add any preceding folders after /
RewriteBase /

#JSON support
addType application/json .json

# if the filename is not a directory
RewriteCond %{REQUEST_FILENAME} !-d
# if the filename is not a file
RewriteCond %{REQUEST_FILENAME} !-f
#THIS IS THE IMPORTANT LINE
#It checks for api being used after your url and then
#treats everything after that as the context of the call
RewriteRule ^api/(.*)$ api.php?context=$1 [QSA,L]

Now lets look at our configuration file which includes information about the uri and query string that is used by our api installation.


//determine the page we are running
//gets the current page that the server is running
$currentPage = substr(str_replace(".php", "", $_SERVER["SCRIPT_NAME"]), strrpos($_SERVER["SCRIPT_NAME"], "/") + 1);

//determine what the uri is
//this will pick up any folders that the application can be run from
//e.g. www.example.com/folder1/folder2/api/
$uri = ($currentPage == "index") ?  $_SERVER["REQUEST_URI"] : substr($_SERVER["REQUEST_URI"], 0, stripos($_SERVER["REQUEST_URI"], $currentPage));

//remove query string
$uri = explode("?", $uri);

//add uri to constant variable
define("URI", $uri[0]);

//add query string to constant variable
define("QUERY_STRING", $_SERVER["QUERY_STRING"]);

// relative path
define("RELATIVE_PATH", $_SERVER["DOCUMENT_ROOT"] . URI);

// absolute path
define("ABSOLUTE_PATH", "http://" . $_SERVER["HTTP_HOST"] . URI);

//API CREDENTIALS
//set the api login credentials
define("AUTH_REALM", $_SERVER["HTTP_HOST"]);
define("AUTH_USERNAME", "ENTER_API_USERNAME"); //your username
define("AUTH_PASSWORD", "ENTER_API_PASSWORD"); //your password
define("AUTH_ON", false); //switching authentication on or off

/* Setup your database, autoloading & other configurations below. */

Now we have our url being rewritten and configuration we can now look at extending Ian Selby’s processRequest function. In here I am going to add code that processes the url and determines the context of the call and the type of content that we expect back from the server.

public static function processRequest($identifier = "/")
{

	// get our verb
	$requestMethod = strtolower($_SERVER['REQUEST_METHOD']);

	//create a new request server object
	$restServer = new RestRequestServer();

	//data to be stored in array
	$data = array();

	//determine type of request
	switch ($requestMethod)
	{
		// gets are easy...
		case 'get':
			//retrieve GET data
			$data = $_GET;
			break;
		// so are posts
		case 'post':
			//retrieve POST data
			$data = $_POST;
			break;
		// here's the tricky bit...
		case 'put':
			// basically, we read a string from PHP's special input location,
			// and then parse it out into an array via parse_str... per the PHP docs:
			// Parses str  as if it were the query string passed via a URL and sets
			// variables in the current scope.
			parse_str(file_get_contents('php://input'), $putVars);
			//retrieve PUT data
			$data = $putVars;
			break;
	}

	// store the method
	$restServer->SetMethod($requestMethod);

	//get the identifer e.g. api
	$identifier = str_replace("/", "\/", $identifier);

	//get everything after the 'api' part of the url
	$uri = preg_match_all('/' . $identifier . '(.*)/', $_SERVER["REQUEST_URI"], $results, PREG_PATTERN_ORDER);

	//assign the context
	$uri = isset($results[1][0]) ? $results[1][0] : "";

	//first we explode the context
	$parts = explode(".", $uri);

	//allow api introductory screen
	if ($parts[0] != "")
	{

		//check to see if return content type has been supplied
		if (isset($parts[1]))
		{

			//get the content type
			$content = explode("?", $parts[1]);

			//first check that content type is valid
			if ($content[0] != "json" && $content[0] != "xml")
			{

				//force bad request as content extension not provided
				RestUtils::SendResponse(400);

			}

			// store the accept header content type
			$restServer->SetHttpAccept($content[0]);

		}
		else
		{

			//force bad request as content extension not provided
			RestUtils::SendResponse(400);

		}

	}

	//pass context
	$context = $parts[0];

	// store the context
	$restServer->SetContext($context);

	//now we need to find out if we have an id
	//first we explode the context
	$parts = explode("/", $parts[0]);

	//get the last item
	$id = $parts[count($parts)-1];

	//determine if it is a numeric value
	if (is_numeric($id))
	{

		//it is numeric
		//store the id
		$restServer->setId($id);

		//remove id from array stack
		array_pop($parts);

		//get the context
		$context = implode("/", $parts);

		// store the context
		$restServer->SetContext($context);

	}

	// set the raw data, so we can access it if needed (there may be
	// other pieces to your requests)
	$restServer->SetRequestVars($data);

	//check if data has been set
	if(isset($data['data']))
	{

		// translate the JSON to an Object for use however you want
		$restServer->SetData(json_decode($data['data']));

	}

	//return the server request
	return $restServer;

}

You don’t need to know too much about how the code works but it basically gets the context of the API call e.g. /users/56.json (returns json object with information for user with id 56). It then goes on to check what the server should return the data by looking at the url content type by exploding everything after the “.”, so /users/56.json will return a json object and /users/56.xml will return the user in xml format. Its as easy as that. The identifier part is automatically passed in from the variable in the configuration file above which I will explain in the api handler (below).


// include configuration
require_once('inc/config.inc.php');

//process the request
//passes the uri into the ProcessRequest function
$requestResponse = RestUtils::processRequest(URI . "api/");

//get the request variables
$vars = $requestResponse->getRequestVars();

//get the context of the call
$context = $requestResponse->getContext();

//declare error variable
$error = "";

//declare data array
$data = array();

//set success code (assume not found for now)
$code = 404;

//set the success value
//assume false
$success = false;

//determine the api context
switch($context)
{

	case "users":

		if ($requestResponse->getMethod() == "get")
		{

			if($requestResponse->getId() > 0)
			{

				//return a single user as an id was passed with the call
			}
			else
			{

				//return all users as no id was provided
				//return a list of all users

			}

			//e.g. $data["users"] = $users;

		}
		else if ($requestResponse->getMethod() == "post")
		{

			//check if authentication is on
			if (AUTH_ON)
			{

				//perform authentication on this api call
				RestUtils::performAuthentication($requestResponse);

			}

			//add a user to the database
			//use the $vars array to get and handle data posted to the api
			//perform checks here e.g. $vars array populated with all data etc..

		}
		else if ($requestResponse->getMethod() == "put")
		{

			//check if authentication is on
			if (AUTH_ON)
			{

				//perform authentication on this api call
				RestUtils::performAuthentication($requestResponse);

			}

			if($requestResponse->getId() > 0)
			{

				//update a user using the passed id
				//use the $vars array to get and handle the data to update
				//perform checks here e.g. user exists, $vars array populated 

			}
			else
			{

				//400 > Bad Request
				die(RestUtils::SendResponse(400));

			}

		}
		else if ($requestResponse->getMethod() == "delete")
		{

			//check if authentication is on
			if (AUTH_ON)
			{

				//perform authentication on this api call
				RestUtils::performAuthentication($requestResponse);

			}

			if($requestResponse->getId() > 0)
			{

				//delete a user using the passed id
				//perform checks here e.g. user exists

			}
			else
			{

				//400 > Bad Request
				die(RestUtils::sendResponse(400));

			}

		}
}

//get status information
$status = RestUtils::getStatusCodeMessage($code);

//assign information to data response
$feedback["success"] = $success;
$feedback["context"] = $context;

if ($error != "")
{

	$feedback["error"] = $error;

}

$feedback["method"] = strtoupper($requestResponse->getMethod());
$feedback["request"] = $requestResponse->getUrl();

//determine request and process
switch($requestResponse->GetHttpAccept())
{

	case "json":

		//create json string
		$json = "";

		//create empty array
		$jsonObject = array();

		//add a feedback object
		array_push($jsonObject, array("feedback" => $feedback));

		//check any data exists
		if (count($data) > 0)
		{

			//add content to json string
			foreach($data as $key => $value)
			{

				//add data object
				array_push($jsonObject, array($key => $data[$key]));

			}

		}

		//add feedback to json
		$json .= json_encode($jsonObject);

		//send the response
		RestUtils::sendResponse($code, $json, 'application/json');

		break;

	case "xml":
	default:

		//handle boolean
		$feedback["success"] = ($feedback["success"]) ? "true" : "false";

		//create xml serialiser class
		$XmlSerialiser = new XmlSerialiser();

		//build xml header
		$xml = '<?xml version="1.0" encoding="UTF-8" ?>';
		$xml .= '<root>';

		//add feedback data
		$xml .= $XmlSerialiser->generateValidXmlFromArray($feedback, "feedback");

		//check for success
		if ($code == 200)
		{

			//check any data exists
			if (count($data) > 0)
			{

				//add content to xml
				foreach($data as $key => $value)
				{

					//determine what non-plural context is
					$nodeName = (substr(strtolower($key), -1, 1) === 's') ? substr(strtolower($key), 0, -1) : "data";

					//build xml
					$xml .= $XmlSerialiser->generateValidXmlFromArray($data[$key], $key, $nodeName);

				}

			}

		}

		$xml .= '</root>';

		//do the following to format the output
		$DOMDocument = new DOMDocument();
		$DOMDocument->loadXML($xml);
		$DOMDocument->formatOutput = true;
		$xml = $DOMDocument->saveXml();

		//send the response
		RestUtils::sendResponse($code, $xml, 'application/xml');

		break;
}

The PerformAuthentication method is used to make sure the calling application provides a username and password to connect to those calls. I will be discussing the authentication process in more detail in an upcoming post.

UPDATE: Extending Ian Selby’s RESTful API in PHP – Part 2 is now available which details implementing authentication for selected api calls.

The XmlSerialiser class is based on Sean Barton’s excellent blog post that explains how to turn array objects into valid xml. These functions were adapted into a class which I decided to use as an alternative to the PEAR installation.

I have worked on several RESTful API’s now, some of which are called from Flash applications built masterfully by Craig Beswetherick and uses the API’s to retrieve JSON objects to be processed by the Flash layer.

UPDATE: Craig Beswetherick has written an article showing you how to make GET & POST calls using flash actionscript, this code was used to make calls to this RESTful API code.

Filed Under: Blog, Code Share
Tags: , , , , , , , , .
Bookmark: permalink.

Let me know what you think:

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>