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.
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:
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.
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:
$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.
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>
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!
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 :)
@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
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.
[...] Simple REST server in PHP – Supports JSON & AMF – Jacob Wright – Flex, AIR, PHP, etc. (tags: php json amf) Leave a Comment [...]
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
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.
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!!
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?
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.
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.
hmm….looks like i may have to get PHP upgraded:
ReflectionParameter::getPosition
(PHP 5 >= 5.2.3)
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?
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.
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.
Nice article , great work thank you very much.