The way RESTful APIs rely on descriptive URIs for providing access to data makes them very easy for consumers to utilize, as the URIs provide information on what the request does, and wh
Trang 1Let's go with REST
REST is now a very popular API architecture, with most social networks providing
REST-based APIs The way RESTful APIs rely on descriptive URIs for providing access
to data makes them very easy for consumers to utilize, as the URIs provide information
on what the request does, and what data it will return Some implementations, such
as the Twitter API even make it possible to change the format of the data returned,
simply by changing a part of the URI
Requests
Requests to a RESTful API use HTTP verbs to describe what the consumer is trying
to do The API requests are made to specific URIs, which define the resource that the
consumer is trying to perform the action (determined by the verbs) upon
HTTP verbs
The HTTP verbs and their usage are described as follows:
GET Retrieve information POST Create records PUT Update records DELETE Delete records
Resources
RESTful APIs relate URIs to resources Below are some examples:
• http://ourdomain.com/profiles: To list or create profiles
• http://ourdomain.com/profiles/1: A specific user's profile
Our RESTful API will be based within an API controller, thus prefixing all URLs with api/, which goes slightly against the REST concept of a resource
Trang 2Resources and verbs—the requests
Let's look at how resources and verbs combined result in API requests
API operation HTTP verb Resource
Creating a user POST http://ourdomain.com/api/profiles
Listing users GET http://ourdomain.com/api/profiles
Viewing a user's profile GET http://ourdomain.com/api/profiles/1
Updating a profile PUT http://ourdomain.com/api/profiles/1
Deleting a profile DELETE http://ourdomain.com/api/profiles/1
In the above resources, the number 1 represents the
ID of a user's profile
Responses
The response to an API request is generally made up of two parts The first part
of the response is the HTTP header containing an appropriate status code Some
examples of HTTP status codes are below:
HTTP status code Meaning
400 Bad request
Within PHP, HTTP status codes are set as follows:
header("HTTP/1.0 404 Not Found");
The second part of the response is the data itself; for instance, if the API request was
for a list of users, the response would be the list of users Commonly, response data
is sent as XML or JSON Some APIs allow the consumer to request the format of the
response by supplying the format to the API We are going to use JSON If we have
an array of data that we want to return as JSON, we simply do the following:
Trang 3Further reading
There are numerous resources available regarding web services and REST
Following are the resources you may find particularly useful
RESTful PHP Web Services
Packt has a book dedicated to creating RESTful APIs in PHP—RESTful PHP Web
Services, by Samisa Abeysinghe,
https://www.packtpub.com/restful-php-web-services/book This book details the concepts of a REST architecture, how
to make use of existing RESTful APIs in your framework, how to create a RESTful
API for other applications to interact with, as well as debugging information and
case studies
Conference talks
Lorna Jane Mitchell (http://www.lornajane.net), a widely-respected developer
and conference speaker on PHP-related topics, has recently spoken at a number
of conferences on the subject of web service design Slides from related talks are
available online:
http://www.slideshare.net/lornajane/best-practices-in-web-service-design,
http://www.slideshare.net/lornajane/php-and-web-services-perfect-partners
Implementation
Now that we know what sort of API we are going to develop, we can move onto the
implementation In this chapter we will only implement a small sub-set of the API's
functionality Feel free to extend this to match the entire functionality of Dino Space,
if you wish
Data format
Most commonly, RESTful APIs either return their data in XML format or as JSON
Some APIs allow the consumer to specify the return type by adding xml or json
to the end of the URL For the purposes of our implementation, let's stick to JSON,
as it is simpler to convert data to JSON (simply by passing the data to the
json_encode function)
Trang 4API controller
Our API controller itself won't do very much; instead it will pass control to
delegate controllers, which contain logic specific to the various sections of
the site
<?php
/**
* API Controller
*/
class Apicontroller{
To indicate which files are available for control to be delegated to, we should
maintain an array of allowable API controllers For our work in this chapter,
we will create the profiles delegate
/**
* Allowable API Controllers, for control to be delegated to
*/
private $allowableAPIControllers = array( 'profiles' );
/**
* Request data
*/
private $requestData = array();
The object's constructor simply sets the registry object, gets the value of the API
delegate that should be used, and calls the delegator method (delegateControl)
/**
* API Controller Constructor
* @param Registry $registry the registry
* @param boolean $directCall
* @return void
*/
public function construct( Registry $registry, $directCall=true )
{
$this->registry = $registry;
$apiController = $registry->getObject('url')->getURLBit(1);
Trang 5The delegateControl method checks that the delegate controller is within the
allowed delegates If it is, then it includes the appropriate controller, instantiates
it, and passes the registry and the API controller object to it There are a number of
methods that will be common to all API delegates These methods are stored in this
object, and called by the delegate referencing this object If the requested controller
is not allowable, then we generate an appropriate HTTP status code; in this case:
404 Not Found
/**
* Pass control to a delegate
* @param String $apiController the delegate
* @return void
*/
private function delegateControl( $apiController )
{
if( $apiController != '' && in_array( $apiController,
$this->allowableAPIControllers ) )
{
require_once( FRAMEWORK_PATH 'controllers/api/'
$apiController '.php' );
$api = new APIDelegate( $this->registry, $this );
}
else
{
header('HTTP/1.0 404 Not Found');
exit();
}
}
A shared method is required by our delegates This is called if a delegate requires the
API user to be an authenticated user on the site It generates a basic authentication
prompt (this is presented to users viewing the site in their browsers, but for API
users the username and password are passed as part of the HTTP request)
Trang 6Alternatives to basic authentication
Basic authentication isn't the best option in terms of security, especially
if many websites begin offering services utilizing our API Our users'
passwords could be stored (with their permission) within these websites,
putting reliance on the integrity and security of those sites and their
owners An alternative is OAuth, where the API provider deals with the
authentication, and provides consumers with an API key for their users If
a user then wishes to stop a third-party service utilizing their account via
the API, they can simply revoke access We will discuss this option more
in the security section of this chapter
If the authentication fails, then the 401 Unauthorized status code is issued
/**
* Request authentication for access to API methods, called by
delegates
* @return void
*/
public function requireAuthentication()
{
if( !isset( $_SERVER['PHP_AUTH_USER'] ) )
{
header('WWW-Authenticate: Basic realm="DinoSpace API Login"');
header('HTTP/1.0 401 Unauthorized');
exit();
}
else
{
$user = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
$this->registry->getObject('authenticate')->postAuthenticate(
$user, $password, false );
if( ! $this->registry->getObject('authenticate')-
>isLoggedIn() )
{
header('HTTP/1.0 401 Unauthorized');
exit();
Trang 7PUT and DELETE data (technically, there should never be DELETE data sent on a
DELETE request) cannot be accessed through super globals as POST and GET data
can ($_POST and $_GET), so we need a mechanism to get the request data, regardless
of the type of request
/**
* Get the type of request
* @return array
*/
public function getRequestData()
{
if( $_SERVER['REQUEST_METHOD'] == 'GET' )
{
$this->requestData = $_GET;
}
elseif( $_SERVER['REQUEST_METHOD'] == 'POST' )
{
$this->requestData = $_POST;
}
elseif( $_SERVER['REQUEST_METHOD'] == 'PUT' )
{
parse_str(file_get_contents('php://input'),
$this->requestData );
}
elseif( $_SERVER['REQUEST_METHOD'] == 'DELETE' )
{
parse_str(file_get_contents('php://input'),
$this->requestData );
}
return $this->requestData;
}
}
?>
php://input
php://input is an input stream wrapper in PHP, which allows us to
read raw request data More detailed information is available on the PHP
website: http://php.net/manual/en/wrappers.php.php/
Trang 8Wait—no models?
That's right; we don't need to create any models for our API All of the functionality
our API needs to provide already exists through the various models we have created
So instead of creating API-specific models, we will create some additional API
controllers, which work with the pre-existing models to get the data and present
it to the consumer
Authentication
Keeping with the RESTful way of leveraging HTTP, we can make use of HTTP
authentication to authenticate the user This is where authentication details are
passed as part of the HTTP request from our API consumer You will have seen
examples of this if you have ever visited a web page, and your browser has opened
a pop up prompting for authentication details before loading the page In this case,
your browser reads the server's request for authentication, and then requests login
details before sending the authentication request to the server
More information
You can read more about HTTP authentication with PHP here:
http://php.net/manual/en/features.http-auth.php
Sessions lead to unREST!
REST is a stateless architecture, which means all of the information required for a
particular operation or request should be included within that request It shouldn't
rely on information from a previous request or other information such as sessions
and cookies To that end, we should amend our authenticate registry object and
our index.php file
Amending the authenticate registry object
We need to amend the authenticate registry class (registry/authenticate
class.php) to only set $_SESSION data if that is required, so that we can indicate,
from our API controller, that we don't want $_SESSION data to be created
Trang 9We should add an optional parameter to the postAuthenticate method to indicate
if $_SESSION data should be set, with a default value of true so it doesn't impact on
other aspects of our site, which we have already implemented
public function postAuthenticate( $u, $p, $sessions=true )
{
$this->justProcessed = true;
require_once(FRAMEWORK_PATH.'registry/user.class.php');
$this->user = new User( $this->registry, 0, $u, $p );
if( $this->user->isValid() )
{
if( $this->user->isActive() == false )
{
$this->loggedIn = false;
$this->loginFailureReason = 'inactive';
}
elseif( $this->user->isBanned() == true )
{
$this->loggedIn = false;
$this->loginFailureReason = 'banned';
}
else
{
$this->loggedIn = true;
If the sessions parameter for this method has been set to true, then we set the
appropriate session If it has been set to false (for example, by our API controller),
then it is not set
if( $sessions == true )
{
$_SESSION['sn_auth_session_uid'] = $this->user->getUserID();
}
}
}
else
{
$this->loggedIn = false;
$this->loginFailureReason = 'invalidcredentials';
}
}
Trang 10Amending index.php
Our index.php file by default checks for SESSION data for authentication
See Chapter 2, or take a look at the index.php file to refresh your memory
<?php
session_start();
DEFINE("FRAMEWORK_PATH", dirname( FILE ) "/" );
require('registry/registry.class.php');
$registry = new Registry();
// setup our core registry objects
$registry->createAndStoreObject( 'template', 'template' );
$registry->createAndStoreObject( 'mysqldb', 'db' );
$registry->createAndStoreObject( 'authenticate', 'authenticate' );
$registry->createAndStoreObject( 'urlprocessor', 'url' );
$registry->getObject('url')->getURLData();
// database settings
include(FRAMEWORK_PATH 'config.php');
// create a database connection
$registry->getObject('db')->newConnection( $configs['db_host_sn'],
$configs['db_user_sn'], $configs['db_pass_sn'],
$configs['db_name_sn']);
Firstly, we need to move the line that sets the controller variable to just before
authentication is checked We then wrap the authentication check line in an IF
statement, so that it is only executed if the controller being requested isn't the
API controller
$controller = $registry->getObject('url')->getURLBit(0);
if( $controller != 'api' )
{
$registry->getObject('authenticate')->checkForAuthentication();
}