This website recently got a major rebuild; if you're missing something, let Lorna know.

Building A RESTful PHP Server: Understanding the Request



Once upon a time, what seems like a lifetime ago, I was away for a couple of weeks, and I wrote a series of posts about serving RESTful APIs from PHP to keep my blog going while I was away. Fast forward a few years and those posts are outdated and still wildly popular - so I thought it was about time I revisited this and showed how I'm writing RESTful PHP servers today!

In the first part of this (probably) 3-part series, we'll begin with the basics. It might seem boring, but the most important thing to get right with REST is parsing all the various elements of the HTTP request and responding accordingly. I've put in code samples from from a small-scale toy project I created to make me think about the steps involved (should I put the code somewhere so you can see it? Let me know). Without further ado, let's dive in and begin by sending all requests through one bootstrap script:

Send Everything to index.php

The first thing we need to do when we serve a RESTful response is the same as the first thing we do when we serve any other web page: figure out what the user actually wanted and how to deliver that.

To support the "pretty URLs" that we see in RESTful services (actually REST is way more complicated than that, but if I start talking about that as well, you'll still be reading in 3 hours' time! Wikipedia has rather a good explanation), we'll add a .htaccess file to our directory which redirects all the requests to index.php. My .htaccess file therefore looks like this:

RewriteEngine On
RewriteBase /

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]

This assumes you're using apache with mod_rewrite; the same thing can be achieved with other webservers. With this in place, all requests to the service will arrive into the top of index.php – exactly the same way as most of the MVC (model-view-controller) frameworks work. In fact, this whole RESTful service is going to be very MVC in style, because it makes sense to me to build things this way.

That said there are definitely other options; I've also worked with Slim, which is a microframework. This is neat as it lets you register a combination of URL and verb, and specify a callback that should be triggered when a matching request comes in. It's lightweight and fast ... again, probably a post in itself if I start talking about it.

Figure Out What Was Requested

There are a few things we will want to know about what we have received here:

  • The URL of the request
  • The method (GET, POST, PUT or DELETE) of the request
  • Any data that arrived with it

Let's begin by looking at the first two elements; the code for these are common to all requests so we'll put it in index.php as all requests come here in the first instance. I'm going to store all the details about the request in a Request object, which also has the functionality needed to parse all that information. Here are the first few lines of that object:

class Request {
    public $url_elements;
    public $verb;
    public $parameters;

    public function __construct() {
        $this->verb = $_SERVER['REQUEST_METHOD'];
        $this->url_elements = explode('/', $_SERVER['PATH_INFO']);

We use the $_SERVER variable to pull information about the incoming request; the method is in $_SERVER['REQUEST_METHOD']. The URL comes from $_SERVER['PATH_INFO'] and we can split it into it's various parts using explode(). From these parts, we'll see what the user requested and we'll work out how to route the request.

Incoming Data

To parse the incoming data, we'll need different approaches depending on what kind of request it was and what format the data is in. We'll check the verb that was used, however for requests with a body (POST and PUT requests) we will also check the Content-Type header. Bear in mind also that we will want to capture any URL parameters for POST, PUT and DELETE requests as well as for GET requests.

The parameters that arrive on the URL are relevant regardless of which verb was used; we'll always process these. The script to do so is fairly straightforward, but do look out for parse_str()which writes into a variable you pass in by reference. Once we've got that, we can deal with the body of the request, if there is one. POST and PUT requests can have bodies with them, in which case we need to work out what format that data is in. The example here supports both JSON and a standard form post, and we could add more cases to handle a wider selection of Content-Type headers by adding additional cases. Here's the remainder of Request::__construct() and the method in this class for reading incoming variables:

    $this->parseIncomingParams();
    // initialise json as default format
    $this->format = 'json';
    if(isset($this->parameters['format'])) {
        $this->format = $this->parameters['format'];
    }
    return true;
}

public function parseIncomingParams() {
    $parameters = array();

    // first of all, pull the GET vars
    if (isset($_SERVER['QUERY_STRING'])) {
        parse_str($_SERVER['QUERY_STRING'], $parameters);
    }

    // now how about PUT/POST bodies? These override what we got from GET
    $body = file_get_contents("php://input");
    $content_type = false;
    if(isset($_SERVER['CONTENT_TYPE'])) {
        $content_type = $_SERVER['CONTENT_TYPE'];
    }
    switch($content_type) {
        case "application/json":
            $body_params = json_decode($body);
            if($body_params) {
                foreach($body_params as $param_name => $param_value) {
                    $parameters[$param_name] = $param_value;
                }
            }
            $this->format = "json";
            break;
        case "application/x-www-form-urlencoded":
            parse_str($body, $postvars);
            foreach($postvars as $field => $value) {
                $parameters[$field] = $value;

            }
            $this->format = "html";
            break;
        default:
            // we could parse other supported formats here
            break;
    }
    $this->parameters = $parameters;
}

The php://input is the incoming stream to PHP. For a "normal" web application we usually just use $_POST, which has already parsed this stream and decoded the form fields that were sent with the request. For handling JSON data, PUT requests, and anything else that isn't a form POST, we need to parse the stream itself as shown here.

We also set the format property of the Request object - this is the format that we will send the output in. You might have all kinds of logic to work out what that is, but for simplicity, we'll default to JSON.

At this point, we have the data and the request details all understood, and we are in a position to route into the controller and start writing the actual functionality – at last! I'll be writing about the routing, autoloading, and controllers in the next installment.

Edit: You can read the next part here.


In: php
Tags: #.htaccess #apache #http #php #rest #rest2 #RESTful #verb