Dasar Slim Framework Rest API
Dasar Slim Framework Rest API
11 December 2012
Introduction
Until a few years ago, using a framework to develop PHP applications was the exception rather
than the rule. Today, frameworks like CakePHP, Symfony, CodeIgniter, and Zend Framework are
widely used for PHP application development, each one offers unique features to streamline and
simplify application development.
Often a full-fledged framework is overkill. Think about prototyping a web application, creating a
quick and dirty CRUD front end, or deploying a basic REST API. You can do all these tasks with
a traditional framework, but the time and effort involved in learning and using it often outweigh the
benefits. Now consider micro-frameworks which enable rapid web application development and
prototyping without the performance overhead and learning curve of full-fledged frameworks.
In this article, I introduce you to Slim, a PHP micro-framework that's designed for rapid
development of web applications and APIs. Don't be fooled by the name: Slim comes with a
sophisticated URL router and support for page templates, flash messages, encrypted cookies,
and middleware. It's also extremely easy to understand and use, and it comes with great
documentation and an enthusiastic developer community.
This article walks you through the process of building a simple REST API with Slim. In addition to
explaining how to implement the four basic REST methods with Slim's URL router, it demonstrates
Copyright IBM Corporation 2012
Create REST applications with the Slim micro-framework
Trademarks
Page 1 of 22
developerWorks
ibm.com/developerWorks/
how Slim's unique features make it easy to add advanced features such as API authentication and
multi-format support.
Understanding REST
First, refresh your understanding of REST, otherwise known as Representational State Transfer.
REST differs from SOAP in that it is based on resources and actions, rather than on methods
and data types. A resource is simply a URL referencing the object or entity on which you want to
perform the actionsfor example, /users or /photosand an action is one of the four HTTP verbs:
GET (retrieve)
POST (create)
PUT (update)
DELETE (remove)
To better understand this, consider a simple example. Suppose that you have a file-sharing
application and you need API methods for developers to remotely add new files to, or retrieve
existing files from, the application data store. Under the REST approach, you expose a URL
endpoint, such as /files, and examine the HTTP method used to access this URL to understand
the action required. For example, you POST an HTTP packet to /files to create a new file, or send
a request to GET /files to retrieve a list of available files.
This approach is much easier to understand, as it maps existing HTTP verbs to CRUD operations.
It also consumes fewer resources, because no formal definition of data types of request/response
headers is needed.
The typical REST conventions for URL requests, and what they mean, are:
Slim's URL router helps developers "map resource URIs to callback functions for specific HTTP
request methods," such as GET, POST, PUT, and DELETE. Defining a new REST API is simple as
defining these callback methods and filling them with appropriate code. And that's exactly what you
will do in the rest of this article.
Page 2 of 22
ibm.com/developerWorks/
developerWorks
database, and the example REST API developed in this article allows developers to retrieve, add,
delete, and update these articles using normal REST conventions. The majority of this article
assumes JSON request and response bodies. Fore more information on how to handle XML
requests and responses, see Supporting multiple-response formats later in the article.
If you do not use Composer, you can manually download the Slim framework (look in Resources
for a download link) and extract the Slim directory within the download archive to a directory in
your PHP include path, or to $SLIM_ROOT/Slim.
Page 3 of 22
developerWorks
ibm.com/developerWorks/
To interact with this MySQL database, you should also download RedBeanPHP, a low-footprint
ORM library. This library is available as a single file that you can easily include in a PHP script.
Remember to copy the file to a directory in your PHP include path or to $SLIM_ROOT/RedBean.
These lines define a new virtual host, http://example.localhost/, whose document root corresponds
to the $SLIM_ROOT. Restart the web server to activate these new settings. Note that you might
need to update your network's local DNS server to tell it know about the new host.
After you complete this task, point your browser to http://example.localhost/, and you should see
something like Figure 1.
Page 4 of 22
ibm.com/developerWorks/
developerWorks
Page 5 of 22
developerWorks
ibm.com/developerWorks/
Listing 1 loads the Slim and RedBean classes. It also registers the Slim auto-loader to ensure
that other Slim classes are loaded as needed (remember that you don't need to require Slim or to
register its auto-loader if you use Composer). Next, RedBeanPHP's R::setup() method opens a
connection to the MySQL database by passing it the appropriate credentials, and its R::freeze()
method locks the database schema against any RedBean-propagated changes. Finally, a new
Slim application object is initialized; this object serves as the primary control point for defining
router callbacks.
The next step is to define router callbacks for HTTP methods and endpoints. Listing 1 calls the
application object's get() method and passes it the URL route '/articles as its first argument.
The anonymous function passed as the final argument usesRedBeanPHP's R::find() method
to retrieve all the records from the 'articles' database table, turn them into a PHP array with the
R::exportAll() method, and return the array to the caller as a JSON-encoded response body.
Slim's response object also exposes a header() method that you can use to set any response
header; in Listing 1, the header() method sets the 'Content-Type' header so the client knows to
interpret it as a JSON response.
Figure 2 shows the result of a request to this endpoint.
Note that you can use the $app->contentType() helper method instead of the header() method
to directly access the request's content type, or treat Slim's response object as an array (it
implements the ArrayAccess interface) and set headers.
Create REST applications with the Slim micro-framework
Page 6 of 22
ibm.com/developerWorks/
developerWorks
Similarly, you can handle GET requests for specific resources, by adding a callback for the URL
route "/articles/:id". Listing 2 illustrates.
Slim can automatically extract route parameters from URLs. Listing 2 illustrates this as it extracts
the :id parameter from the GET request and passes it to the callback function. Within the
function, the R::findOne() method retrieves the corresponding resource record. This record
is returned to the client as a JSON-encoded response body. In the event that the provided
identifier is invalid and no matching resource is found, the callback function throws a custom
ResourceNotFoundException. The try- catch block then converts this into a 404 Not Found server
response.
Figure 3 displays the result of a successful request.
Page 7 of 22
developerWorks
ibm.com/developerWorks/
Slim also supports route conditions, allowing developers to specify regular expressions for route
parameters. If these parameters are not met, the route callback will not execute. You can specify
conditions on a per-route basis, as shown in thus code:
<?php
$app->get('/articles/:id', function ($id) use ($app) {
// callback code
})->conditions(array('id' => '([0-9]{1,}'));
Or you can specify conditions globally using the setDefaultConditions() method, as shown here:
<?php
// set default conditions for route parameters
\Slim\Route::setDefaultConditions(array(
'id' => '[0-9]{1,}',
));
Page 8 of 22
ibm.com/developerWorks/
developerWorks
A Slim application also exposes a notFound() method, which you can use to define custom code
for scenarios where no route match is possible. This method is an alternative to throwing a custom
exception and manually setting the 404 server response code.
In Listing 3, the callback function retrieves the body of the request (which is assumed to be a
JSON-encoded packet) using the request object's getBody() and converts it to a PHP object
using the json_decode() function. Next, a new RedBeanPHP article object is initialized with the
properties from the PHP object and stored in the database using the R::store() method. The new
object is then exported to an array and returned to the client as a JSON packet.
Figure 5 displays an example POST request and response.
Create REST applications with the Slim micro-framework
Page 9 of 22
developerWorks
ibm.com/developerWorks/
Page 10 of 22
ibm.com/developerWorks/
developerWorks
if ($article) {
$article->title = (string)$input->title;
$article->url = (string)$input->url;
$article->date = (string)$input->date;
R::store($article);
$app->response()->header('Content-Type', 'application/json');
echo json_encode(R::exportAll($article));
} else {
throw new ResourceNotFoundException();
}
} catch (ResourceNotFoundException $e) {
$app->response()->status(404);
} catch (Exception $e) {
$app->response()->status(400);
$app->response()->header('X-Status-Reason', $e->getMessage());
}
});
// run
$app->run();
In Listing 4, the identifier provided as part of the URL route is used to retrieve the corresponding
resource from the database as a RedBeanPHP object. The body of the request (which is assumed
to be a JSON-encoded packet) is converted to a PHP object with the json_decode() function,
and its properties are used to overwrite the properties of the RedBeanPHP object. The modified
object is then saved back to the database, and a JSON representation returned to the caller with
a 200 server response code. In the event that the identifier provided does not match an existing
resource, a custom exception is thrown and a 404 server error response sent back to the client.
Figure 6 displays an example PUT request and response.
Page 11 of 22
developerWorks
ibm.com/developerWorks/
You also can write a callback to handle DELETE requests that remove the specified resource from
the data store. Listing 5 displays the necessary code.
As with Listing 4, Listing 5 uses the resource identifier from the URL to retrieve the corresponding
resource from the database as an object and then uses the R::trash() method to permanently
delete it. The response to a successful DELETE request can be a 200 (OK) with the status in the
response body or a 204 (No Content) with an empty response body; Listing 5 does the latter.
Figure 7 displays an example DELETE request and the API response.
Page 12 of 22
ibm.com/developerWorks/
developerWorks
Page 13 of 22
developerWorks
ibm.com/developerWorks/
The example authenticate() function in Listing 6 is referenced from within the GET route handler as
middleware, and it automatically receives the route object as an argument. It can also access the
Slim application object using the Slim::getInstance() method.
Now, when the user requests the URL "/articles", the authenticate() function will be invoked before
the request is processed. This function uses a custom validation routine to authenticate the
request and allows further processing only if the validation routine returns true. In case validation
fails, the authenticate() function halts processing and sends the client a 401 Authorization
Required response.
Listing 7 provides a more concrete example of how you might implement the authenticate()
method, by checking for cookies with the user identifier "demo" and API key "demo." These
cookies are set by calling a new API method, /demo, that allows a client temporary access to the
API for a five-minute period.
Page 14 of 22
ibm.com/developerWorks/
developerWorks
In this case, the client first sends a request to the API endpoint '/demo", which sets encrypted
cookies with the "demo" credentials valid for five minutes. These cookies will automatically
accompany any subsequent request to the same host. The authenticate() middleware function,
which is attached to the GET handler for the "/articles" URL endpoint, checks each incoming
GET request for these cookies. It then uses the validateUserKey() function to verify the user's
credentials (in this example, simply by checking for the value "demo"). After the cookies expire, the
validateUserKey() function returns false, and the authenticate() middleware terminates the request
and disallows access to the "/article" URL endpoint with a 401 server error.
Figure 8 displays the server error on an unauthenticated GET request.
Page 15 of 22
developerWorks
ibm.com/developerWorks/
The accompanying code archive uses this same middleware to authenticate POST, PUT, and
DELETE requests. For obvious reasons, this system isn't particularly secure and should never be
used in a production environment; it's included in this article purely for illustrative purposes.
As this example illustrates, route middleware comes in handy to perform request pre-processing; it
can also be used for other tasks, such as request filtering or logging.
Listing 8. The handler for GET requests, with XML and JSON format support
<?php
// do initial application and database setup
// initialize app
$app = new \Slim\Slim();
// handle GET requests for /articles
$app->get('/articles', function () use ($app) {
try {
// query database for articles
$articles = R::find('articles');
// check request content type
// format and return response body in specified format
Page 16 of 22
ibm.com/developerWorks/
developerWorks
$mediaType = $app->request()->getMediaType();
if ($mediaType == 'application/xml') {
$app->response()->header('Content-Type', 'application/xml');
$xml = new SimpleXMLElement('<root/>');
$result = R::exportAll($articles);
foreach ($result as $r) {
$item = $xml->addChild('item');
$item->addChild('id', $r['id']);
$item->addChild('title', $r['title']);
$item->addChild('url', $r['url']);
$item->addChild('date', $r['date']);
}
echo $xml->asXml();
} else if (($mediaType == 'application/json')) {
$app->response()->header('Content-Type', 'application/json');
echo json_encode(R::exportAll($articles));
}
} catch (Exception $e) {
$app->response()->status(400);
$app->response()->header('X-Status-Reason', $e->getMessage());
}
});
// run
$app->run();
In Listing 8, the request object's getMediaType() method is used to retrieve the content type of the
request.
For requests where the content type is "application/json", the list of articles is returned as
before, in JSON format.
For requests where the content type is 'application/xml', the list of articles is formatted as an
XML document with SimpleXML and returned in XML format.
Figure 9 illustrates the XML response to a GET request:
Why stop there? Listing 9 adds XML support for POST requests as well:
Create REST applications with the Slim micro-framework
Page 17 of 22
developerWorks
ibm.com/developerWorks/
Listing 9. The handler for POST requests, with XML and JSON format support
<?php
// do initial application and database setup
// initialize app
$app = new \Slim\Slim();
$app->post('articles', function () use ($app) {
try {
// check request content type
// decode request body in JSON or XML format
$request = $app->request();
$mediaType = $request->getMediaType();
$body = $request->getBody();
if ($mediaType == 'application/xml') {
$input = simplexml_load_string($body);
} elseif ($mediaType == 'application/json') {
$input = json_decode($body);
}
// create and store article record
$article = R::dispense('articles');
$article->title = (string)$input->title;
$article->url = (string)$input->url;
$article->date = (string)$input->date;
$id = R::store($article);
// return JSON/XML response
if ($mediaType == 'application/xml') {
$app->response()->header('Content-Type', 'application/xml');
$xml = new SimpleXMLElement('<root/>');
$result = R::exportAll($article);
foreach ($result as $r) {
$item = $xml->addChild('item');
$item->addChild('id', $r['id']);
$item->addChild('title', $r['title']);
$item->addChild('url', $r['url']);
$item->addChild('date', $r['date']);
}
echo $xml->asXml();
} elseif ($mediaType == 'application/json') {
$app->response()->header('Content-Type', 'application/json');
echo json_encode(R::exportAll($article));
}
} catch (Exception $e) {
$app->response()->status(400);
$app->response()->header('X-Status-Reason', $e->getMessage());
}
});
// run
$app->run();
Page 18 of 22
ibm.com/developerWorks/
developerWorks
Conclusion
Slim provides a powerful, extensible framework for building a REST API, making it easy for
application developers to allow third-party access to application functions using an intuitive
architectural pattern. Slim's URL matching and routing capabilities, coupled with its lightweight
API and support for various HTTP methods, make it ideal for rapid API prototyping and
implementation.
See Downloads for all the code implemented in this article, together with a simple jQuery-based
test script that you can use to perform GET, POST, PUT and DELETE requests on the example
API. I recommend that you get the code, start playing with it, and maybe try to add new things to it.
I guarantee you won't break anything, and it will definitely add to your learning.
Page 19 of 22
developerWorks
ibm.com/developerWorks/
Downloads
Description
Name
Size
example-app-slim-rest-api.zip
45KB
Page 20 of 22
ibm.com/developerWorks/
developerWorks
Resources
Learn
Learn more about Slim Framework on its official website and in its documentation.
Take a closer look at the Slim Framework base classes in the API documentation.
Fork the Slim Framework repository.
Read Slim Framework news.
Participate in the Slim Framework community, ask questions and get answers on the Slim
Framework support forum.
Read the Ephemera blog post Rack Middleware Use Case Examples for more about
middleware.
More articles by this author (Vikram Vaswani, developerWorks, August 2007-current): Read
articles about XML, additional Google APIs and other technologies.
New to XML? Get the resources you need to learn XML.
XML area on developerWorks: Find the resources you need to advance your skills in the XML
arena, including DTDs, schemas, and XSLT. See the XML technical library for a wide range
of technical articles and tips, tutorials, standards, and IBM Redbooks.
IBM XML certification: Find out how you can become an IBM-Certified Developer in XML and
related technologies.
developerWorks technical events and webcasts: Stay current with technology in these
sessions.
developerWorks on Twitter: Join today to follow developerWorks tweets.
developerWorks podcasts: Listen to interesting interviews and discussions for software
developers.
developerWorks on-demand demos: Watch demos ranging from product installation and
setup for beginners to advanced functionality for experienced developers.
Discuss
developerWorks profile: Create your profile today and set up a watchlist.
XML zone discussion forums: Participate in any of several XML-related discussions.
The developerWorks community: Connect with other developerWorks users while exploring
the developer-driven blogs, forums, groups, and wikis.
Page 21 of 22
developerWorks
ibm.com/developerWorks/
Page 22 of 22