Building a RESTful Web application with PHP

Recently I’ve been putting together a Web application for a research project. I decided it was about time I really looked properly into REST so my Web interfaces are better structured. I won’t go into all the benefits here, you can read for yourself. Suffice to say it seems like a good approach to take.

This is quite a long article and you might only be interested in some of it so here are the sections:

If you have an suggestions for improvement, please let me know – this was a first attempt!

Some credit for this article should go to lornajane for her PHP REST Server articles (Part 1, Part 2, Part 3) which I used as a good starting point.

Getting access to request data

Getting access to HTTP GET arguments or POST data is well known and easy. With a REST interface you are likely to support other HTTP verbs such as PUT and DELETE. Getting data from these requests is not obvious but luckily lornajane had worked this out for me:

parse_str(file_get_contents('php://input'), $arguments);

Returning appropriate responses

With REST you should really make use of the HTTP Status Codes for all requests (rather that using 200 OK for everything). In PHP, this can be done with the header function in a couple of ways:

// When setting just the status code
header('HTTP/1.1 405 Method Not Allowed');
// When you're setting another header at the same time
header('Allow: GET, HEAD, POST, DELETE', true, 405);

I haven’t found a way of sending just the status code number (e.g. 405) without specifying the exact text (‘Method Not Allowed’) but these are part of a fixed specification so it’s not a huge problem.

The following is a list of status codes that I’ve used and why:

Handling different data formats

As URLs are supposed to represent resources in REST, it is a feasible requirement that client applications would like to receive responses from the same URL in different formats. Clients can specify the data formats they are capable of understanding using the HTTP Accept header. In Javacript this can be set using the setRequestHeader method on the XMLHttpRequest object. Using jQuery as my current Javascript library of choice, the code for doing this is as follows:

$.ajax({
  type: method, // GET, POST, PUT, DELETE etc.
  url: url, // The actual URL to make the request
  data: data, // Any data/parameters to send to the server
  beforeSend: function(xmlHttpRequest) {
    xmlHttpRequest.setRequestHeader('Accept', format); // MIME Type
  }
});

This then needs to be handled by the server. In PHP this data can be retrieved using $_SERVER['HTTP_ACCEPT']. I also found a handy library for parsing this header according to the specification (not trivial), cleverly named ‘HTTP_ACCEPT‘. I then wanted a way of knowing the most appropriate format from a list of formats I would support so I threw the following code together:

$accept = new HTTP_Accept($_SERVER['HTTP_ACCEPT']);
foreach ($supportedFormats as $supportedFormat) {
  $supportedFormatQuality = $accept->getQuality($supportedFormat);
  if ((!isset($bestFormat) && $supportedFormatQuality > 0) ||
       $bestQuality < $supportedFormatQuality) {
    $bestFormat = $supportedFormat;
    $bestQuality = $supportedFormatQuality;
  }
}

The server also needs to specify the type of content it is returning; this can be done using the Content-Type header as follows:

header("Content-Type: $format");

Passing all requests to a single PHP script

I needed all requests with a certain URL prefix to be handled by a single script. This was easily solved using the following URL re-writing rule in a .htaccess file:

Options +FollowSymLinks
RewriteEngine on
RewriteRule ^.*$ index.php

Note that I had to enable the mod_rewrite module in Apache and ensure that directives could be overridden with the following:

<Directory /var/www/>
  AllowOverride All
</Directory>

Determining the full requested URL

Another requirement of my application complicated by URL rewriting was that I needed access to the full URL. I achieved this with the following bit of code:

$protocol = $_SERVER['HTTPS'] == 'on' ? 'https' : 'http';
$location = $_SERVER['REQUEST_URI'];
if ($_SERVER['QUERY_STRING']) {
  $location = substr($location, 0, strrpos($location, $_SERVER['QUERY_STRING']) - 1);
}
$url = $protocol.'://'.$_SERVER['HTTP_HOST'].$location;

A generic REST Service class

To allow me to reuse some of the REST code I'd created, I put it into a generic REST Service class which I subclassed and replaced different methods for different means:

class RestService {

  private $supportedMethods;

  public function __construct($supportedMethods) {
    $this->supportedMethods = $supportedMethods;
  }

  public function handleRawRequest($_SERVER, $_GET, $_POST) {
    $url = $this->getFullUrl($_SERVER);
    $method = $_SERVER['REQUEST_METHOD'];
    switch ($method) {
      case 'GET':
      case 'HEAD':
        $arguments = $_GET;
        break;
      case 'POST':
        $arguments = $_POST;
        break;
      case 'PUT':
      case 'DELETE':
        parse_str(file_get_contents('php://input'), $arguments);
        break;
    }
    $accept = $_SERVER['HTTP_ACCEPT'];
    $this->handleRequest($url, $method, $arguments, $accept);
  }

  protected function getFullUrl($_SERVER) {
    $protocol = $_SERVER['HTTPS'] == 'on' ? 'https' : 'http';
    $location = $_SERVER['REQUEST_URI'];
    if ($_SERVER['QUERY_STRING']) {
      $location = substr($location, 0, strrpos($location, $_SERVER['QUERY_STRING']) - 1);
    }
    return $protocol.'://'.$_SERVER['HTTP_HOST'].$location;
  }

  public function handleRequest($url, $method, $arguments, $accept) {
    switch($method) {
      case 'GET':
        $this->performGet($url, $arguments, $accept);
        break;
      case 'HEAD':
        $this->performHead($url, $arguments, $accept);
        break;
      case 'POST':
        $this->performPost($url, $arguments, $accept);
        break;
      case 'PUT':
        $this->performPut($url, $arguments, $accept);
        break;
      case 'DELETE':
        $this->performDelete($url, $arguments, $accept);
        break;
      default:
        /* 501 (Not Implemented) for any unknown methods */
        header('Allow: ' . $this->supportedMethods, true, 501);
    }
  }

  protected function methodNotAllowedResponse() {
    /* 405 (Method Not Allowed) */
    header('Allow: ' . $this->supportedMethods, true, 405);
  }

  public function performGet($url, $arguments, $accept) {
    $this->methodNotAllowedResponse();
  }

  public function performHead($url, $arguments, $accept) {
    $this->methodNotAllowedResponse();
  }

  public function performPost($url, $arguments, $accept) {
    $this->methodNotAllowedResponse();
  }

  public function performPut($url, $arguments, $accept) {
    $this->methodNotAllowedResponse();
  }

  public function performDelete($url, $arguments, $accept) {
    $this->methodNotAllowedResponse();
  }

}
This entry was posted in techy solutions and tagged , , , , , , , , , , , , , , , , , , , , , . Bookmark the permalink.
  • Ace
    Thanks for the great write-up. I'm planning on giving your directions a spin in a few days. Wasn't easy to find an article as good as yours to get me started, so thanks a million.

    -Ace
  • No worries, good luck with it.
  • PepperHill
    This is excellent. Does anyone have any sample code showing the contents of index.php as well as an example of a class that implements RestService?
  • Your index.php could be as simple as the following:

    $service = new RestService();
    $service->handleRequest($_SERVER, $_GET, $_POST);
  • gargantuan
    nice, REST finally started to make sense to me a few days ago when I was messing with headers. This article filled in a lot of the gaps. Thanks.
  • Great, nice to know it's of use to someone. Sometimes you write these things and wonder whether it'll actually be of use to anyone!
  • webninja
    Nice introductory article. What do you think about Konstrukt? (http://www.konstrukt.dk)
  • I've not seen Konstrukt before but looks interesting - will check it out. Thanks for the pointer. Glad you liked the article too :)
  • ma
    nice article! really the first well web resource on PHP-REST web service i found! It's simple and clear!
    REST is simple in its concepts, but i think there is still much confusion around what's the differences between the diffrent kind of web-services. I find this article a good starting point for those who doesnt know (or believe to know) about web-services.
  • Hey, glad you liked the article! REST is definitely confused by many people, really pleased that this helps a little.
blog comments powered by Disqus