Simple REST server in PHP – Supports JSON & AMF

After building a couple of RESTful services using the Zend Framework, I decided to create a dead simple REST server that allowed me to skip all the features I didn't need as well as a tons of classes that came with Zend Framework MVC. There are still useful features to add (XML support for example), but overall I'm quite happy with what I've come up with.

My solution, RestServer, is a JSON REST server, so far. It should be trivial to add support for XML or other formats, but there would have to be assumptions on what your object would look like in XML (XML-RPC style, your own custom XML format, etc). First we'll look at the classes that you write to handle the requests, then we'll look at how to tie it together in your index.php file.

REST Controllers

The RestServer class assumes you are using URL rewriting and looks at the URL from the request to map to the necessary actions. The map that gets a request from URL to class method is all in the doc-comments of the classes. Here is an example of a class that would handle some user actions:

class TestController
{
    /**
     * Returns a JSON string object to the browser when hitting the root of the domain
     *
     * @url GET /
     */

    public function test()
    {
        return "Hello World";
    }
   
    /**
     * Logs in a user with the given username and password POSTed. Though true
     * REST doesn't believe in sessions, it is often desirable for an AJAX server.
     *
     * @url POST /login
     */

    public function login()
    {
        $username = $_POST['username'];
        $password = $_POST['password'];
        // validate input and log the user in
    }
   
    /**
     * Gets the user by id or current user
     *
     * @url GET /users/:id
     * @url GET /users/current
     */

    public function getUser($id = null)
    {
        if ($id) {
            $user = User::load($id); // possible user loading method
        } else {
            $user = $_SESSION['user'];
        }
       
        return $user; // serializes object into JSON
    }
   
    /**
     * Saves a user to the database
     *
     * @url POST /users
     * @url PUT /users/:id
     */

    public function saveUser($id = null, $data)
    {
        // ... validate $data properties such as $data->username, $data->firstName, etc.
        $data->id = $id;
        $user = User::saveUser($data); // saving the user to the database 
        return $user; // returning the updated or newly created user object
    }
}

Let's walk through the above TestController class to talk about the features demonstrated. First we'll look at the test method. You'll notice there is a new kind of doc-comment tag in the docblock. @url maps a URL to the method below it and is in the form:

@url <REQUEST_METHOD> <URL>

In this particular example, when someone does a GET on http://www.example.com/ (assuming example.com is where our service is located) it will print out:

"Hello World"

which is a valid representation of a string in JSON.

Moving on to the next method, login, we see the @url maps any POSTs to http://www.example.com/login to the login method. Getting data from a regular web-type POST is the same as any PHP application, allowing you to use your own validation or other framework in conjunction with this REST server. Sessions can also be kept if desired. Though keeping sessions isn't true REST style, often all we want a REST server for is to serve up data to our ajax application, and it can be easier to just use sessions than something more RESTful.

Next we have our getUser method (you'll notice that it doesn't really matter what I name my methods because our @url directives define what URLs map to the method). You can see a couple of things here. First, we have multiple @url mappings for this method. And second, there is an odd "/:id" in that first URL mapping. The RestServer treats any ":keyword" placeholders as wildcards in the URL and will take that section of the URL and pass it into the parameter with the same name in the method. In this example, when hitting http://www.example.com/users/1234, $id will equal 1234. When hitting http://www.example.com/users/current, $id will equal null. It doesn't matter what order your parameters are in, so long as they have the same name as the placeholder (:id and $id, :username and $username). You'll also want to be sure to make your parameters optional ($id = null) when you have several URL mappings that don't all require a parameter. Otherwise you'll have an error thrown telling you that you didn't pass in a required parameter.

One last thing to note in getUser is that this method simply returns a User object. This gets serialized into JSON (or potentially XML) and printed out for consumption by the application.

Finally we get to saveUser. You see here we have multiple URL mappings again. This time they also have different HTTP methods (POST and PUT) for creating and updating a user. The new thing here is the $data variable. This is a special keyword parameter that will contain the value of whatever was POSTed or PUT to the server. This is different than your regular web POST in that it doesn't need to only be name-value pairs, but can be as robust as JSON, sending complex objects. For example, the body of a regular web POST, let's say the login request, might look like this:

username=bob&password=supersecretpassw0rd

but POSTing a new user object for our saveUser method could look like this:

{ "username": "bob", "password": "supersecretpassword", "firstName": "Bob", "lastName": "Smith" }

So you're able to allow POSTing JSON in addition to regular web style POSTs.

I call these classes that handle the requests Controllers. And they can be completely self-contained with their URL mappings, database configs, etc. so that you could drop them into other RestServer services without any hassle.

REST index.php

In order to get the whole server kicked off, you'll want to create an index.php file, have your URL rewriting direct requests to it (another topic which you can learn about elsewhere), and create the RestServer and add controller classes to it for handling. RestServer will cache the URL mappings between requests using APC or a file to speed up requests. You won't have to load every controller file on every request if you use autoload and this cache, only the one needed for the request. The cache only runs in production mode. Here is an example index.php file:

spl_autoload_register(); // don't load our classes unless we use them

$mode = 'debug'; // 'debug' or 'production'
$server = new RestServer($mode);
// $server->refreshCache(); // uncomment momentarily to clear the cache if classes change in production mode

$server->addClass('TestController');
$server->addClass('ProductsController', '/products'); // adds this as a base to all the URLs in this class

$server->handle();

That's it. You can add as many classes as you like. If there are conflicts, classes added later will overwrite duplicate URL mappings that were added earlier. And the second parameter in addClass can be a base URL which will be prepended to URL mappings in the given class, allowing you to be more modular.

You can view the RestServer class, copy it and use it for your own purposes. It is under the MIT license. Features to be added include XML support and HTTP Authentication support. If you make this class better please share your updates with everyone by leaving a comment. I will try and keep this class updated with new features as they are shared. I hope you enjoy!

I changed the title of this post to remove the AMF portion but was asked to cover it, so I will quickly talk about the AMF support. RestServer supports the AMF format in addition to the JSON format. This is a binary format used by Adobe Flash in their remoting services, but because their remoting services are not RESTful, you can't use classes such as RemoteObject with REST.

In order to use the AMF format with this service, you'll need to have the Zend Framework in your classpath so that the classes to serialize and deserialize AMF are present (e.g. Zend/Amf/Parse/Amf3/Serializer.php). Then you're ready so server up AMF. The way you consume AMF in Flash is using URLLoader or URLStream to load the data and ByteArray to convert it into an object. To tell the server you want AMF rather than JSON your URLRequest object will need to add an Accept header of "application/x-amf". Below I will show you how this could be done.

public function getUser():void
{
    var loader:URLLoader = new URLLoader();
    loader.dataFormat = URLLoaderDataFormat.BINARY;
    var request:URLRequest = new URLRequest("http://www.example.com/users/current");
    request.requestHeaders.push(new URLRequestHeader("Accept", "application/x-amf"));
    loader.addEventListener(Event.COMPLETE, onComplete);
    loader.load(request);
}

private function onComplete(event:Event):void
{
    var loader:URLLoader = event.target as URLLoader;
    var byteArray:ByteArray = loader.data as ByteArray;
    var user:Object = byteArray.readObject();
    // do something with the user
}

You can even make your PHP objects cast into their actionscript equivalents using the $_explicitType property or getASClassName method in PHP as defined in the documentation and by using registerAlias in Flash.

Good luck and let me know if you end up using it!

Update: I am including an example .htaccess file for anyone who might need it. It will only rewrite requests to files that don't exist, so you can have images, css, or other PHP files in your webroot and they will still work. Anything that would give a 404 will redirect to your index.php file.

DirectoryIndex index.php
<IfModule mod_rewrite.c>
	RewriteEngine On
	RewriteRule ^$ index.php [QSA,L]
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteCond %{REQUEST_FILENAME} !-d
	RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>


52 Responses to “Simple REST server in PHP – Supports JSON & AMF”

  1. Troy Gilbert says:

    Awesome, love it. Been wanting something like this for a while, and have used frameworks like CakePHP to get it, but felt like there was an easier path since I didn’t need all of their view management, etc. Cheers!

  2. Jon Toland says:

    I’m a former Java guy who did a lot of PHP and would highly commend checking out Recess Framework. It’s very Rails-y and restful. BTW I enjoy your blog keep it up :)

  3. Syed Mazhar Hasan says:

    @Jac: love you man, looks awesome, cant wait to implement and play with it, bound to deadlines though :s
    @Jon: nice one, thanx for sharing the recess

  4. Jacob Wright says:

    Recess is what inspired me to use the doc comments to declare the URL mapping. Though I don’t like the custom comment style that Recess uses and wish they would have kept it the same style as doc comments.

  5. [...] Simple REST server in PHP – Supports JSON & AMF – Jacob Wright – Flex, AIR, PHP, etc. (tags: php json amf) Leave a Comment [...]

  6. Hi

    This is really handy, I was looking for just such a thing a few months ago and ended up using Zend Framework instead. Was very glad to find this when doing another “simple REST API PHP” search a few days ago.

    One bug I’ve found is that the $data variable isn’t being passed or made available in any way as far as I can tell. It gets assigned to $this->data in the server but then doesn’t get passed to the controller. I’ve just cheated for now by using $server->data in my controller but thought you might like to know.

    John

  7. Jacob Wright says:

    John, you’re right. The data property is accessed on the controller, it is not passed in to the method parameters. I will update.

    Update: I fixed the class and updated it so that the keyword $data parameter would work as documented in the post.

  8. Jacob Wright says:

    I fixed a bug that prevented the RestServer from working correctly when a GET string of variables was used in the URL (e.g. /mypage?var1=value&var2=foobar).

    Yeah, I know, basic functionality. But I haven’t given this a real work over yet. Any bugs let me know and I’ll fix them relatively quickly. Thanks!!

  9. Steve Edwards says:

    Hi Jacob,

    This looks like what I’m looking for.

    Excuse the ignorance, though, how should I be rewriting my urls for this? I’m a bit confused about how the urls map to the classes and methods. A snippet of .htaccess would help.

    Also, all I’m using is your classes. There’s nothing else required is there?

  10. Jacob Wright says:

    I’ve added an example .htaccess file at the end of this post. Should get you what you need.

    All you need are the two classes I provide, unless you are working with Flash AND want to use AMF format rather than just JSON, then you’ll need to have the Zend Framework in your class path.

    That’s why I wrote this. So I don’t have to deal with all the extra stuff that other libraries add in. Keeps it simple.

  11. Brian says:

    Wanting to try this out, but getting:
    Fatal error: Call to undefined method ReflectionParameter::getPosition() in …\RestServer.php on line 264
    Not seeing the getPosition function declaration anywhere within the Class. Any help would be appreciated.

  12. Brian says:

    hmm….looks like i may have to get PHP upgraded:

    ReflectionParameter::getPosition
    (PHP 5 >= 5.2.3)

  13. Brian says:

    Aargh! Still running into issues. I got my PHP updated, but now the only thing I get returned when viewing index.php is:

    {“error”:{“code”:404,”message”:”Not Found”}}

    Any suggestions?

  14. Brian says:

    Uhm….yeah. Don’t u luv it when someone requests help and then answers their own questions?

    I was trying to get this to work in a sub-directory (../rest) instead of in the root directory. Got to adjust the .htaccess file.

  15. Jacob Wright says:

    I didn’t think to post what version this had to work with. I guess PHP 5 >= 5.2.3! :)

    And if you want it to work in a subdirectory you can, just have to add the classes with “/rest” in front, like $server->addClass(‘ProductsController’, ‘/rest/products’);

    Maybe I’ll add a global prefix for all classes.

  16. Asanka lakmal says:

    Nice article , great work thank you very much.

  17. stan says:

    This is a neat little framework. just saved me tons of time. I am running into one issue.

    If I want to map a GET URL with Query paramters it doesn’t seem to work.

    Let’s say I want the URL http://localhost/runtime?executeid=123 to map to the method below then what should the doc comments for GET look like? This is what I have and it fails. Obviously, hard coding the executeid is not what I want but I’m just trying to get this to work for now.

    /**

    * @url GET runtime?:executeid=123 Output of the executed code.
    */

    public static function execute($executeid, $codeType)
    {

    }

    Thanks.

  18. stan says:

    Update:

    Changing to:
    @url GET /runtime maps to the method however the name/value pairs do not result in the param variables created.

    So invoking using:
    http://localhost/runtime?executeid=123

    Does not result in the $executeid variable being available in the method.

  19. Anders says:

    Hi,

    I really like the simplicity of this implementation. Really nice work.

    I’m a newbie to the RESTful principles and have a couple of questions:
    If it isn’t possible to do url rewriting on my server, how do I solve that? I guess I could modify the code somewhat to take the url

    http://mydomain.com/index.php?resource=users/1

    instead of

    http://mydomain.com/users/1

    for an example. I don’t know if that is considered to be RESTful? Is it?

    Regards Anders

  20. Jacob Wright says:

    For me to remember what RESTful should be like, I always think of everything as documents. It might be generated dynamically from the database, and it might be JSON or XML or something, but if I could grab it like a document, POST a new document under a URL, and PUT a new version of the document up, oh, and DELETE it, and if it behaves like I would think it should if they were just files on the server, then I consider it a good REST service. I would say you strive for real URLs because it makes more sense under that view, but you have to do what you have to do.

    In PHP you can get the full query string, it doesn’t have to be broken out into variables. I think you could do this:

    http://mydomain.com/?users/1

    and $_SERVER['QUERY_STRING'] will hold the whole string for you. And if you need additional query parameters then

    http://mydomain.com/?users/&orderBy=firstName

    // replace anything after an ‘&’
    $resource = preg_replace(‘/&.*/’, ”, $_SERVER['QUERY_STRING']);
    $order = $_GET['orderBy'];

    would be pretty cool. But your way would work great too (and probably make more sense anyway).

    There’s the spirit of REST and the letter of the law. The spirit is treating resources like documents. The letter is using HTTP methods (GET, POST) and different URLs (which params are) to interact with the web service. Good luck.

  21. Are you using any require_once calls in your index.php?

    I see you use $server = new RestServer($mode);

    Not sure how that works w/out something like
    require_once( “RestServer.php” );

  22. Jacob Wright says:

    spl_autoload_register() will load ./RestServer.php automatically when the class is used. It will also only load the controller needed when used. This prevents all the controllers being loaded up on every request.

    You can use require_once too.

  23. It appears to be a case sensitive issue.
    Are you on a Windows server?

    After changing my file names to restserver.php, etc, it worked fine.

  24. Anders says:

    Thanks for your reply Jacob. I’ll see where I end up finally.

  25. Anton Kobzev says:

    Hello, Jacob!
    Thank you very much for this lightweight class. I’m going to use it in my open-source project.

    Could you, please, explain how to set HTTP status code for response?

    For example I need to set “201 Created” HTTP status code. I can write this line inside controller method:
    header(‘HTTP/1.1 201 Created’);
    but are there more elegant solutions?

    I see there is a public method setStatus($code) in RestServer.php, but don’t know how to call it from within controller class’s method?

    I’m using PHP

  26. Jacob Wright says:

    Hey Anton,

    The RestController sets a reference to itself on the controller after it is created and before the method is run. You can declare it officially with

    public $server;

    and then inside the method

    $this->server->setStatus(201);

    That should work. Also, if it is an error, you can do

    throw new RestException(404); // or
    throw new RestException(500, “You really botched it up this time!”);

    Hope that is helpful.

  27. Anton Kobzev says:

    Jacob, thanks for rapid reply.

    I guess you meant “RestServer” when you said “The RestController sets a reference to itself on the controller”.

    I also modified method setStatus($code)
    It puts to the header the message (text) but omits the number (code).

    I changed:

    $code = $this->codes[strval($code)];
    header(“{$_SERVER['SERVER_PROTOCOL']} $code”);

    to:

    $message = $this->codes[strval($code)];
    header(“{$_SERVER['SERVER_PROTOCOL']} $code $message”);

    And it worked for me. Thank you!

  28. Jacob Wright says:

    Oh, thanks for the bug find. Will fix the source file.

  29. automation_man says:

    Can you explain me how can i use your code within flex ? Thanks

  30. Jacob Wright says:

    a_man, the last code example is what you would do for Flex. The key parts that are different from what you would do if you were loading an XML file (for example) are:

    loader.dataFormat = URLLoaderDataFormat.BINARY;

    and

    var byteArray:ByteArray = loader.data as ByteArray;
    var user:Object = byteArray.readObject();

    Also note, if you are not in AIR the Accept header will not work. Instead add ?format=amf at the end of the URL and ignore the line that adds the Accept header.

  31. Anth0 says:

    First of all, congrats for your work !

    I’m having trouble integrating your little REST framework.

    I’m working with Copix CMS and want to implement a REST api to handle webservices. I have a module named services with inside of it :
    -actiongroup folder (my controllers)
    -classes
    -templates (views)

    I decided to create a RestServer.class.php file with your RestServer class (inside classes folder).
    I then created an actiongroup named “wsaction” containing in constructor what you put in your index.php.

    I added to my controller a function named getAgences() with following comment :
    /**
    * Get all agences
    *
    * @url GET /agences
    */

    In my .htaccess I have (among others) following rules :
    RewriteRule ^ws/(.*)$ index.php?module=services&action=HabiterREST [NC,L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d

    If I type http://myhostname/ws/ and print my variables I get something good (array with methods, params, etc).

    BUT, if I type http://myhostname/ws/agences my variables are printed hundred of times and I got a PHP error :

    Fatal error: Maximum function nesting level of ’100′ reached, aborting! in D:\web\applications\copix\habiter\modules\services\classes\restserver.class.php on line 161
    Call Stack
    # Time Memory Function Location
    1 0.0007 114368 {main}( ) ..\index.php:0
    2 0.2715 4563936 CopixCoordination->process( ) ..\index.php:21
    3 0.3038 6100600 CopixCoordination->_doAction( ) ..\CopixCoordination.class.php:189
    4 0.3038 6102528 CopixActionGroup->process( ) ..\CopixCoordination.class.php:206
    5 0.3043 6128728 ActionGroupRESTHabiter->ActionGroupRESTHabiter( ) ..\CopixActionGroup.class.php:101
    6 0.3080 6277120 RestServer->handle( ) ..\resthabiter.actiongroup.php:32

    It looks like the “handle” method is called several times. Why is that ?

  32. Jacob Wright says:

    That looks to my like it is an issue outside of my RestServer class. Whatever is calling handle is getting run over and over again.

    Seems to me that the CopixCoordination.class.php is getting it into a loop. I can’t vouch for other systems.

  33. automation_man says:

    Execuse me, i’m really beginner in flex and web design. Can you explain more how can implement your code in flex ;;; thank you

  34. automation_man says:

    Execuse me, i’m really beginner in flex and web design. Can you explain more how i can implement your code in flex ;;; thank you

  35. Anth0 says:

    I solved my problem. Sometimes it’s better to sleep a little bit :p

    I had put in my actiongroup’s constructor the creation of restserver with handle method. Moreover, my methods were in this actiongroup thus I was asking RestServer to load my methods in this action group -> in other words I was asking it to recreate my actiongroup meaning to recreate a server etc… Loop

    =)

  36. automation_man says:

    I have found some errors in RestServer.php :

    Notice: Undefined variable: mode in C:\wamp\www\rest\RestServer.php on line 187

    Notice: Uninitialized string offset: 0 in C:\wamp\www\rest\RestServer.php on line 139

    Notice: Uninitialized string offset: -1 in C:\wamp\www\rest\RestServer.php on line 142

    Notice: Undefined index: format in C:\wamp\www\rest\RestServer.php on line 302
    {
    “error”: {
    “code”: 404,
    “message”: “Not Found”
    }
    }

  37. Jacob Wright says:

    These are just notices. They shouldn’t affect your code and you can eliminate them with error_reporting(E_ALL &~ E_NOTICE);

  38. gude says:

    Hi, and thanks for this class.

    How do you invoke a DELETE method with a flex HTTPService?

  39. Jacob Wright says:

    You do a POST with a ?method=DELETE at the end of the URL

  40. gude says:

    thanks it works.
    Do you know why I have the method variable in $_GET rather than in $_POST php global variable?

  41. Jacob Wright says:

    It has to be $_GET because if it were $_POST you couldn’t send any data in the POST body of the message. You need to be able to do that with PUTs. When using AMF it sends the AMF data for PUTs and POSTs as the body of the message. So you have to override the HTTP method in the URL.

  42. cla says:

    Hey Jacob,

    looking for a small REST server solution, I just stumbled over your class, and it worked like a charm right away. I really like it very much, thanx for the work!

    I think I need to go for XML instead of JSON results, so I think about extending it that way.

    However, instead of putting the source code into the RestServer class itself, I thought about introducing a base codec class, which could then be derived by the user and registered to the server to implement a new format. Two subclasses for the formats LSON and AMF could already come with the RestServer.php and be registered automatically, XML could also be a candidate for a supplied codec class.

    Would you say that is feasable? Then I would try that and give back the result. Or would that be too much of a change ?

    thanx cla

  43. Jacob Wright says:

    cla, I think that’s a great idea.

  44. cla says:

    Hi again,

    thanks to your work it wasn’t hard at all to implemented the codecs for the four formats introduced by your class :-)

    However, in order to handle subclassing properly (it should be a good example, right?), I have few basic questions on subclassing since I am not that familiar in depth with PHP (as well as with JSON BTW). I wouldn’t want to clutter this place with such, so if you wouldn’t mind to send PM, we could easily do the final polish and then make it available.

    Thanx CLA

  45. Raymond Visser says:

    I got GET / working and it gives “Hello World” now.
    If i understand correctly I can replace

    * @url GET /
    to
    * @url GET /test

    so when I do a http://www.testserver.com/test instead of http://www.testserver.com it will say “Hello World” right?

    it just says to me HTTP 404 not found.

    I did put in the .htaccess file with the stuff mentioned and the apache module is loaded and working.

    also the other functions in TestController arent working like :
    * @url GET /users/:id
    * @url GET /users/current

    what am I missing here?

    thanks in advance

  46. Jacob Wright says:

    Raymond, you shouldn’t need to be doing anything else. It should be working. If you put a die(‘getting here?’) in your index.php you’ll know for sure if your .htaccess file is working correctly. If it is, I’m not sure what the issue would be.

  47. Raymond Visser says:

    Ok I found out whats wrong:
    Look at the url functions it created from
    $urls = $this->map[$this->method];

    [] => Array
    (
    [0] => TestController
    [1] => test
    [2] => Array
    (
    )

    )

    [/testje] => Array
    (
    [0] => TestController
    [1] => testje
    [2] => Array
    (
    )

    )

    So when I did http://www.testserver.nl//testje instead of http://www.testserver.nl/testje it worked. lol

  48. Oliver HA says:

    Very nice framework.

    However, I have a weird problem. It works for the first request and after, for any request, always returns
    { “error”: { “code”: 404, “message”: null }}

    To make it work again, I have to upload once again to the server my index.php and RESTServer.php and MyController.php.

    It seems that the $map is emptied after the first call, I tried to comment code related to the cache to force the map to be repopulated each time but nothing works.

    Any ideas ?? My server runs Linux/Apache/PHP 5.2.13

  49. Jacob Wright says:

    Running it in debug mode will recreate the cache each time. I’ve been unable to duplicate your issue even when I delete my cache, alter the permissions, mess with it by hand, or otherwise try and sabotage it in both debug and production mode and using apc cache and file cache. I could look over your code if you’d like.

  50. Oliver HA says:

    Thanks for your help, but now I realize that everything works perfectly from my local server running Apache 2.2 and PHP 5.3.

    So I think it’s my shared linux hosting server that has a configuration problem… I will have to figure out by myself….

Leave a Reply