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.

30 thoughts on “Building A RESTful PHP Server: Understanding the Request

  1. There are some things about this post that are just fundamentally wrong if you want to put the word REST in the title... First, how you're determining what the response body format should be (what the content-type header will be). And second, how you're confusing the POST/PUT payload with the URI query parameters.

    The Request class has a property called "parameters". This property is set in the parseIncomingParams method following these steps: first, you assign the URI query parameters to the $parameters variable. Then, if it's a POST or a PUT (well, actually, only if the content-type header is set and is application/json or application/x-www-form-urlencoded), you replace URI query parameters with the values specified by the POST/PUT payload.

    URI query parameters should not be overridden by the payload. They are completely independent functionally...

    Finally, the format should be determined by the accept* headers. The Accept header specifies a list of media types. application/json, application/xml, text/plain, etc... It also specifies priority using the "q score". The HTTP spec does a much better job of explaining than I will. But again, format, unless you're dealing with some type of crippled client, shouldnt be determined this way. It's not RESTful. Not even a little bit.

    I love seeing people write about REST, but it's hard to watch when the explanations are just plain wrong and at best, confusing.

    There are a lot of projects out there that are doing really great things with HTTP. In PHP: Symfony 2, Zend 2, 360i Sonno... Java has some excellent resources as well... JAX-RS specification and the Jersey Framework.

    • "URI query parameters should not be overridden by the payload. They are completely independent functionally..."

      That part is correct. Lorna should not be overwriting any of the query params she parsed with data from the payload (and she is using parse_str() on REQUEST_URI, which makes no sense, since she could simply use $_GET for this purpose).

      The rest of your comment is nonsense:

      1) The Content-Type header of a request (and parsing a request is what this article is about) is used by the *client* to tell the *server* what format the data in the request body is in. So if I, as a client, POST or PUT some JSON to a URI, I must (well, should as per the HTTP spec) include a Content-Type: application/json header so the server can parse the entity body enclosed in the request and does not have to resort to sniffing. You are confusing this with the other use for a Content-Type header, where a server sends it along with a response to indicate to the client what the type of the payload is.

      2) The Accept header is used by a client to indicate his preferred response format(s) to a server. The server may or may not follow this (it's perfectly adequate for a server to send a response in a format not indicated in the Accept header, clients must be able to deal with this situation)

      3) You say "It's not RESTful. Not even a little bit." without elaborating much, but it seems to refer to the use of Accept (or Content-Type?) headers to perform content negotiation. Given how the identification of resources through URIs and the representation of resources through different media types are conceptually separate in REST (please read Fielding's dissertation), I need to conclude that you don't know what you're talking about.

      All this, however, is really just about HTTP in the end, and not about REST, which so far has not been addressed in this series of posts (but we're only one article in), because RESTful interfaces must use Hypermedia formats (and application/json is not one and neither is application/xml, since they both have no attached semantics) to implement something called "Hypermedia As The Engine Of Application State", also called "HATEOAS" or "the most important thing about REST that most people ignore or just plain do not understand". If you don't believe me, believe The Roy: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

      That, then, would be my main criticism of this article, no mention of Hypermedia, but there's two more articles to go apparently, so I'll spare judgement until the end.

      Other feedback: parsing JSON into parameters might not be a good idea, since ultimately, the request should be handled like a document, not like a set of keys and values, if the server wants to handle unknown fields in a non-destructive manner (and it precludes the support of an XML based media type since you cannot map elements with attributes in a similar manner). HTML forms really have a whole range of edge cases that make them rather special; I'd start out purely with JSON and/or XML and go from there. The code of course could also simply be $parameters = array_merge($_GET, json_decode($body, true));, and again, if it's urlencoded form data, why not just use $_POST instead of parsing stuff by hand?

      • Thanks both for your comments, there are some good points here. Mixing parameters across verbs is absolutely laziness on my part - there would never be the same fields coming in from more than one place but I realise now it could be confusing.

        The discussion on the content negotiation was good and the way I've pulled in fragments of code makes it look a bit ... nonsense. Usually I use the Accept header with the q values, but I also like to add a format parameter on the URL in case anyone needs to override it for any reason (or, for example, doesn't understand how to set their headers! It does happen, sadly, not everyone is a purist) and I've found this can be useful but agree that it shouldn't be the main way that the content type is determined. Thanks for making that clear!

  2. .. or use one of the seemingly infinite number of frameworks to accomplish the above? ZF (Zend_Rest_Controller), FRAPI and Rails immediately come to mind.

  3. Dave: Whilst I agree that URL parameters should be maintained as separate from the payload parameters there are also situations where "special" url params are used in place of other, more RESTful best practice. For example, many APIs offer an action parameter to support clients that can't PUT / DELETE (i.e. most web browsers) whilst others use a section of the URL to determine the response type of the resource.

    And on that topic, the example is actually correct in that "Content-Type" should be used to determine the format of the *incoming* data. Accepts is used for the response format and the two can be completely different as required.

    Finally, yes there are load of projects that already do REST / HTTP APIs but sometimes it's very useful to look at the underlying technology from scratch. Not great for production use but brilliant for learning a technology.

    • "Finally, yes there are load of projects that already do REST / HTTP APIs but sometimes it's very useful to look at the underlying technology from scratch. Not great for production use but brilliant for learning a technology."

      Agreed, but the number one problem with our (PHP) community, is that every tom/dick/harry decides that rolling their own is preferable to banding together behind a community driven platform - I've had to tear through enough muck, from self-taught devs, that I may be forever jaded; in 10 years, we'll all be enslaved to the Django/Rails (ha, maybe Sinatra) of the world, which would be a shame.

  4. Pingback: Building a RESTful PHP Server: Output Handlers | LornaJane

  5. Pingback: Building A RESTful PHP Server: Routing the Request | LornaJane

  6. Pingback: PHP Rest Server (part 2 of 3) | LornaJane

  7. Pingback: PHP Rest Server (part 3 of 3) | LornaJane

  8. I've heard so much about REST but never really took the time out to use or understand it. At my jobs we do most of our projects with ZF and seeing how some of the underlying code may be working is quite useful to me. Thank you for taking the time to put together this series.

  9. Thanks the article and comments. I built a RESTful PHP service a few years ago and am revisiting it after reading all of this. One thing I did notice, is that $_SERVER['PATH_INFO'] does not exist in my environment. I use $_SERVER['REQUEST_URI'].

  10. Pingback: Practical: creating a REST application with Silex | Patrick's playground

  11. I need help please !!
    I created .htaccess file and I copy pasted same info that you post, but I am still not able to redirect the requests to index.php. why is this problem?
    just for more info, I am using my localhost server Apach 2 with Windows 7.

    • You might not have the mod_rewrite module enabled. In WAMP, you click on the icon, then it's Apache -> Apache modules -> rewrite_module. Make sure that is checked, and if it isn't, click it. The process will be different if you're using a different server though.

  12. I am really sad .. I thought I did understand what is going on, but it seemd get confused later!!!
    How could I redirect my web service urls to one page with out losing the orginal URL?!?!?!

    for example:
    I am suposed to recive the url request in index.php,
    which (the url) is : http://localhost/services/index.php

    but the real uri resource should look likes this :
    http://localhost/services/resource/create/user.

    what is happen I lose the long url when I redirect to index.php !!!!
    how could I solve this prople?? please any help!!

    • Is your mod_rewrite enabled in apache? You will need this in order for the example to work. The rewrite module doesn't redirect the user; it re-interprets the URL but doesn't actually change it so when you get to index.php, you can still parse the original URL. Hope that helps!

  13. Hello:

    Thanks for the post, I have a problem with the .htaccess file too, now for all the relative requests I get(css and JS) it redirects to the index.php rather than actually downloading the assets, how would I modify the .htaccess to let the css/js go through?

    Thanks.

    • The fourth and fifth lines will work if the files actually exist at the URLs given, otherwise you can add rewrites before the final line to catch these requests before they get rewritten to index.php ... Hope that helps!

  14. Pingback: NWRestful – PHP REST framework | nick|whyte

  15. I was using your Rewrite code and it works great on HTTP but it does not work on HTTPS. I have the Rewrite in my default-ssl it looks like this.
    [code]
    ServerAdmin webmaster@localhost
    DocumentRoot /webroot

    Options FollowSymLinks MultiViews
    AllowOverride FileInfo
    Order allow,deny
    allow from all
    RewriteEngine On
    RewriteBase /

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

    [/code]
    Any idea why it would it would work in HTTP and not HTTPS?

    • Check that you are using the rewrite log and what level of info is being logged; I can't guess from the information here what might be wrong, but you should be able to find out more by checking the logs. Good luck!

  16. I like the article, but myself am having a problem with the combination of apache RewriteRule and the POST method. I have found a number of online articles saying it is not possible to accept post data when using apache RewriteRule because it causes a redirect and post data is lost. But, obviously, you and everyone else writing RESTful APIs (as you've coded above) gets this to work just fine. I, however am losing post data during the rewrite despite using pretty much your exact code just to see where I was going wrong. Perhaps there is some other apache conf setting somewhere higher up that is causing the problem but I haven't found that yet. In fact, I had been just using the $_POST array as always, but tried your [code] file_get_contents("php://input");[/code] method above with no luck. Any thoughts on what I must be doing wrong would be greatly appreciated.

  17. Pingback: RESTful client-server with MVC Design Pattern | MAURO DONADIO

  18. Pingback: REST web service uncovered | noddyCha Speaks

Leave a Reply

Please use [code] and [/code] around any source code you wish to share.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>