It is becoming ever more important for web applications to have an API allowing others to connect and integrate third-party resources and apps to enhance the overall user experience of your product.

Creating a PHP RESTful API in PHP

Last time we took a look at what a REST API was; the various HTTP methods, responses and headers that an API should be able to handle. Read our first part on Designing a REST API for your Mobile Application.

We briefly touched on how to create your own API, but with the demand for a successful, scalable and powerful API increasing, we wanted to re-visit where we left off. In this example we're going to look into the full power behind the Slim Framework. Though we've previously discussed other PHP RESTful routing frameworks that you can use in your application.

Why the Slim Framework?

For those that don't know much about Slim, we're quite big fans and we're really keen on developing our API's using it. The framework is only classed as a 'micro framework' but will relieve you of a massive overhead. Primarily, it's one hell of a powerful router. It's able to handle all the standard HTTP methods and you can even create your own. You can setup routes, define the parameters, catch any wildcards and then decide whether to redirect, halt or pass onto any middleware you've got running.

What Makes Slim so Powerful?

Slim is built on a middleware and hook architecture that lets you tweak every aspect of its core without actually having to dive into any code. The framework implements a version of the Rack protocol that allows any middleware you've setup to inspect, analyse, or modify the application environment, request, and response before and/or after the Slim application is invoked.

Taken from the Slim documentation:

"Think of a Slim application as the core of an onion. Each layer of the onion is middleware. When you invoke the Slim application’s run() method, the outer-most middleware layer is invoked first. When ready, that middleware layer is responsible for optionally invoking the next middleware layer that it surrounds. This process steps deeper into the onion - through each middleware layer - until the core Slim application is invoked. This stepped process is possible because each middleware layer, and the Slim application itself, all implement a public call() method. When you add new middleware to a Slim application, the added middleware will become a new outer layer and surround the previous outer middleware layer (if available) or the Slim application itself."

Getting Your Own API up and Running

Honestly, Slim is super powerful and can really ease the volume of coding you actually have to do. We've put together code that'll cover setting up your own routing, handling errors, catching nested URLs, pattern matching, setting up conditions, using middleware and sending HTTP error statuses.

<?php
/** 
 * APILogWriter: Custom log writer for our application
 *
 * We must implement write(mixed $message, int $level)
*/
class APILogWriter {
	public function write($message, $level = SlimLog::DEBUG) {
		# Simple for now
		echo $level.': '.$message.'<br />';
	}
}
# Load the Slim framework
require 'vendor/autoload.php';
# Fire up an app
$app = new SlimSlim(array(
		'mode' => 'development',
		'log.enabled' => true,
		'log.level' => SlimLog::DEBUG,
		'log.writer' => new APILogWriter()
	)
);
/**
 * Setup a GET path for /foo
 */
$app->get('/users', function() use ($app) {
	# We can render a view
    $app->render('users.php');
});
$app->get('/users/:id', function($id) use ($app) {
	$profile = ModelUsers::getUserByID($id);
	# Render the view, with some data
	$app->render('profile.php', array(
   			'profile' => $profile,
			'view' => true
		)
	);
});
/**
 * We can have multiple parameters, as complex as the URL
 * patterns need to be.
 */
$app->get('/users/:id/:action', function($id, $action) use ($app) {
	# Render the view using the action route
	$this->render('profile/'.$action.'.php');
});
/** 
 * We can have simple BNF regular expressions that allow us
 * to match repeated patterns.
 *
 * In this instance, this can match /this/is/a/url OR /this/url
 */
$app->get('/profile/:name+', function ($name) {
	/**
	 * In this instance $name will now be an array
	 *
	 * $name = array('this', 'is', 'a', 'url')
	*/
})->conditions(array('name' => '[a-z]{10,}'));
/**
 * We can have optional parameters in the URL where you want to
 * have a partial match on a given pattern
 */
$app->get('/profile(/:year(/:month(/:day)))', function($year = 0, $month = 0, $day = 0) {
	#  Stop anything else happening if invalid
	if(!is_int($year)) {
		$app->stop();
	}
	/**
	 * Search for users from the model that match the parameters.
	 * We have to set default values because they're optional
	 * so we can have /profile/2014 for all users created in 2014 or
	 * /profile/2014/03 for all users created in March 2014.
	*/
	$data = ModelUsers::filterByRange($year, $month, $day);
})->name('profileDateFilter')->conditions(array('year' => '(19|20)dd'));
/**
 *  Above we named the route we just created 'profileDateFilter' which allows
 * us to use the urlFor method to reverse the route. We are also using a constraint
 * on the accepted values for 'year'. It must prefix with 19xx or 20xx
 */
$output = $app->urlFor('profileDateFilter', array('year' => 2013, 'month' => 12, 'day' => 25));
/**
 * We can have a single route respond to multiple
 * HTTP methods by using the map() function
 */
$app->map('/users/validate', function() {
})->via('GET', 'POST');
/**
 * Middleware allow us to nest the route down multiple
 * layers before we make a decision on what to do
 */
function middleOne(SlimRoute $route) {
	echo $route->getName();	
}
function middleTwo(SlimRoute $route) {
	echo $route->getName();	
}
# When we go to the URL, pass down each layer
$app->get('/middleware', 'middleOne', 'middleTwo', function() {
	# Catch any errors
	try {
        ModelUser::someGenericMethod();
		/**
		 * This is a pre-built redirect method to take
		 * you to another URL
		*/
        $app->redirect('/middleware/success');
    } catch(Exception $e) {
		/**
		 * Show the error message view
		*/
        $app->flash('error', $e->getMessage());
        $app->redirect('/middleware/error');
    }
});
/**
 * Slim can also handle error message and
 * send the appropriate HTTP responses
*/
$app->get('/denied', function() use ($app) {
	# Create the data
	$errorData = array('error' => 'Permission Denied');
	# Send a HTTP status of 403
    $app->render('error.php', $errorData, 403);
	# Or we can send
	$app->halt(403, 'Permission Denied');
});
# Run the Slim application
$app->run();
?>

You can use the Slim framework as a great starting point for any API. The documentation goes in depth on how you can extend the framework to handle multi-level middleware layers that allow you to nest and condition each of your routes.

Next time we'll look into using the middleware layers, custom views, HTTP caching, ETag caching and Hooks.

32
Ignite your brand, utilise user-generated content no matter where you or your audience are ›