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>
Update 2: I’ve put the code for RestServer up on github at https://github.com/jacwright/RestServer. It has some changes and may not work exactly like I’ve described here in this post. Be sure to read the documentation, when I get around to writing it. ;)
November 16th, 2009 at 2:01 pm
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!
November 16th, 2009 at 3:18 pm
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 :)
November 16th, 2009 at 3:49 pm
@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
November 16th, 2009 at 4:26 pm
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.
November 17th, 2009 at 12:38 pm
[...] Simple REST server in PHP – Supports JSON & AMF – Jacob Wright – Flex, AIR, PHP, etc. (tags: php json amf) Leave a Comment [...]
November 26th, 2009 at 5:28 am
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
November 30th, 2009 at 12:52 pm
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.
December 15th, 2009 at 9:38 pm
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!!
December 17th, 2009 at 7:56 pm
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?
December 18th, 2009 at 1:59 pm
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.
December 29th, 2009 at 12:23 pm
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.
December 29th, 2009 at 1:13 pm
hmm….looks like i may have to get PHP upgraded:
ReflectionParameter::getPosition
(PHP 5 >= 5.2.3)
December 29th, 2009 at 2:30 pm
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?
December 29th, 2009 at 2:39 pm
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.
December 30th, 2009 at 2:40 pm
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.
March 2nd, 2010 at 1:53 am
Nice article , great work thank you very much.
March 22nd, 2010 at 2:49 pm
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.
March 22nd, 2010 at 3:00 pm
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.
March 30th, 2010 at 2:10 am
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
March 30th, 2010 at 8:04 am
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.
March 31st, 2010 at 1:01 pm
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” );
March 31st, 2010 at 2:14 pm
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.
March 31st, 2010 at 4:22 pm
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.
April 3rd, 2010 at 3:07 pm
Thanks for your reply Jacob. I’ll see where I end up finally.
April 10th, 2010 at 5:40 am
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
April 10th, 2010 at 8:41 am
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.
April 10th, 2010 at 10:40 am
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!
April 10th, 2010 at 3:09 pm
Oh, thanks for the bug find. Will fix the source file.
April 14th, 2010 at 8:14 am
Can you explain me how can i use your code within flex ? Thanks
April 14th, 2010 at 9:32 am
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.
April 14th, 2010 at 10:50 am
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 ?
April 14th, 2010 at 12:06 pm
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.
April 15th, 2010 at 7:44 am
Execuse me, i’m really beginner in flex and web design. Can you explain more how can implement your code in flex ;;; thank you
April 15th, 2010 at 7:45 am
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
April 16th, 2010 at 1:08 am
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
=)
April 16th, 2010 at 3:17 am
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”
}
}
April 19th, 2010 at 10:18 am
These are just notices. They shouldn’t affect your code and you can eliminate them with error_reporting(E_ALL &~ E_NOTICE);
June 4th, 2010 at 1:04 pm
Hi, and thanks for this class.
How do you invoke a DELETE method with a flex HTTPService?
June 4th, 2010 at 1:48 pm
You do a POST with a ?method=DELETE at the end of the URL
June 4th, 2010 at 4:17 pm
thanks it works.
Do you know why I have the method variable in $_GET rather than in $_POST php global variable?
June 7th, 2010 at 10:48 am
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.
June 8th, 2010 at 6:03 am
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
June 8th, 2010 at 4:06 pm
cla, I think that’s a great idea.
June 9th, 2010 at 8:05 am
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
July 14th, 2010 at 3:55 am
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
July 14th, 2010 at 7:47 am
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.
July 16th, 2010 at 3:21 am
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
July 19th, 2010 at 6:56 pm
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
July 20th, 2010 at 8:54 am
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.
July 20th, 2010 at 5:40 pm
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….
July 26th, 2010 at 4:01 am
Hi,
I need an advice to implement a rest service that aims to add a photo in a gallery, using your server. (the client is done in Flex).
I have to store the data and some metadata (such as comment, date) on my server. What’s the best practice to send the metadata and the binary data to the rest server?
Do you have an example of such a use case?
thanks
July 26th, 2010 at 7:28 am
Sorry gude, the RestServer is meant to be a really simple broker between REST calls and your own code. How you write your application is up to you. If you want a more robust framework that helps with all the other stuff you need to do I would recommend looking at Lithium, CakePHP, or Zend Framework if you need help building the app part.
November 6th, 2010 at 2:49 pm
What do i need to do to get rid of the double slash like:
http://www.testserver.nl//testje instead of http://www.testserver.nl/testje
that Raymond mentioned, my requests wont work without the double slash.
Thanks!
November 8th, 2010 at 8:15 am
I’ve added RestServer to github. There have been some changes and bug fixes. Be sure to check out the latest there and contribute back any fixes.
November 11th, 2010 at 9:51 pm
Great, I look forward to the documentation. Simply replacing the class broke all my requests so I will have to wait until I have time to look through it closer.
Thanks!
November 12th, 2010 at 10:32 am
Just a quick note for others until Jacob has a chance to get the documentation out.
I simply replaced the old class with the new one and in my index.php file replaced the “:” for the sent variables with “$” which I’m guessing Jacob changed as it’s more natural in PHP to use the dollar sign for variable notation than the colon.
December 11th, 2010 at 2:01 pm
So close but maybe I am finding a bug?
From my class:
/**
* Gets the user by id or current user
*
* @url GET /potatos/$id
* @url GET /potatos/current
*/
function hello($id = null)
{
return($id);
}
myservice/potatos/123
returns 123, which is great
myservice/potatos/current
returns ‘current’
Am I missing something?
December 11th, 2010 at 2:26 pm
Also, with multiple instances of this on the same server, the APC cache key is the same so there may be issues. I’ll try and change the code and fix the other issue and let you have my code.
December 11th, 2010 at 3:47 pm
Jon, RestServer does not contain smarts to know that /url/current matches better over /url/$id. They both match the URL you’re using. You need to put the rule “/potatos/current” first because the matching goes through all the rules until it finds the first accurate match.
You’re right about the APC cache. You’ll need to give it an API name or a random hash to store the data in.
December 19th, 2010 at 5:35 am
Hi Jacob,
I’m looking into how to easily implement a RESTful server, and your RestServer has been very interesting. Just one question:
As far as I can understand, you’ve implemented HTTP authentication since this article was written. I understand the the authorization of the user should happen in an authorize() method of the registered class in question, but how should the rest client pass the username and password to the rest server, and how the the authorize method pick it up?
January 5th, 2011 at 6:08 am
Hi Jacob
I have had some success with your Rest server and thanks for sharing btw.
I tried to parse the json object, which the rest server returns, but my “client” doesn’t recognise it. This is what i get:
Notice: Undefined index: HTTP_ACCEPT in /opt/lampp/htdocs/RestServer.php on line 346
{
}
line 346 in RestServer.php:
$accept = explode(‘,’, $_SERVER['HTTP_ACCEPT']);
January 5th, 2011 at 1:37 pm
In order to tell the RestServer to use JSON the Accept header must send application/json, or you need to use format=json in the query params.
e.g.
header should be:
Accept: application/json
or url should be
/api/example?format=json
Side note: PHP notices are not errors. Though they can help point to issues as the one you posted to me above clued me onto the fact that you’re not sending any accept headers.
January 28th, 2011 at 6:50 am
Hello Jacob. I think I am doing something really stupid, but when I run index.php I keep getting a 404 Error:
hello world{
“error”: {
“code”: 404,
“message”: “Not Found”
}
}
Here is my index.php file.
require “RestServer.php”;
require “TestController.php”;
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->handle();
?>
did I do something stupid?
January 30th, 2011 at 3:46 pm
The 404 is probably because you are not working at the root “/” of the web server. This script is structured to work only if it exists at the root level. to make it work, you’ll need to go thru and sort of hack the $this->root and make sure it evals to the proper location.
Would love it if the author took care of this. I’m messing around with backbone.js and this is one of the best examples i’ve seen for working with php as a json server without using any of the other frameworks.
January 31st, 2011 at 8:30 am
Hi Thanks. That was the problem!
Once I moved it to the root level, everything worked.
However, how would I “hack” this …
if, for example, I wanted to set these files one level beneath documentroot at http:///restservice/ …
What would I need to change?
February 2nd, 2011 at 10:05 am
JSIDHU, John, I left root as a public property so you could set it after creating the server. I admit without documentation it can be frustrating to use, and I mean to get to that when I have time.
Here is an updated example using John’s index.php file, assuming he wants his root API at /api/v1/:
require “RestServer.php”;
require “TestController.php”;
spl_autoload_register(); // don’t load our classes unless we use them
$mode = ‘debug’; // ‘debug’ or ‘production’
$server = new RestServer($mode);
$server->root = ‘/api/v1/’;
// $server->refreshCache(); // uncomment momentarily to clear the cache if classes change in production mode
$server->addClass(‘TestController’);
$server->handle();
February 4th, 2011 at 12:38 pm
Hi Jacob,
Thank you for sharing the code. I tried it on my local wamp server, it worked perfectly. However, when I tried it on a remote live site, it gave error like this:
Fatal error: spl_autoload() [function.spl-autoload]: Class RestServer could not be loaded in /xxx/xxx/rest_test/index.php on line 4
I couldn’t find any clue after googling a little bit. Do you know what could caused the error?
Thank you!
Rubin
February 9th, 2011 at 1:18 am
Fixed :
ReflectionParameter::getPosition
(PHP 5 >= 5.2.3)
Replace in restserver.php
####################
foreach ($params as $param)
{
$args[$param->getName()] = $param->getPosition();
}
####################
BY
####################
foreach ($params as $k => $param)
{
$args[$param->getName()] = $k;
}
####################
February 9th, 2011 at 1:48 pm
Init() & Authorize() example Sir ? You rest server help me to create a CMS system restfull…very much.
Ooops, sorry for my english, i’m French…
Examples please ? :)
########################
function authorize($username, $password) {
if (!isset($_SERVER['PHP_AUTH_USER'])
|| $username != $_SERVER['PHP_AUTH_USER']
|| $password != $_SERVER['PHP_AUTH_PW']) {
header(‘WWW-Authenticate: Basic realm=”Private”‘);
header(‘HTTP/1.0 401 Unauthorized’);
return false;
}
return true;
}
#####################
Like this ?
Thanks
February 9th, 2011 at 4:03 pm
Jérémy, you’ve got the right idea, except you don’t need to send the unauthorized headers because the server will do that when you return false. Also, authorize is called without any parameters.
function authorize() {
if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) return false;
$username = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
// lookup user in database or text file or wherever
$user = $this->userLookup($username, $password);
if ($user) {
$this->user = $user;
return true;
}
return false;
}
February 10th, 2011 at 10:26 am
Ok, thanks :)
Sorry for my english again :D
Do you have an full controller example ? with use of init() method ? @oauth etc…
For inspiration please ;)
March 21st, 2011 at 2:57 am
I could not seem to get any of the parameter call examples to work. It seems that the code from github does not completely match this article.
The problem is that in your example you define parameter mappings using a colon (@url GET /users/:id); however, the logic within the findUrl method is actually looking for a dollar sign (@url GET /users/$id).
Also it may not be immediately obvious – you need PHP version 5.2 to run this. The $param->getPosition() call is not otherwise supported in 5.1 (Which was my case).
Thanks for providing this great example. Very light weight and effective model. Great job!
April 23rd, 2011 at 8:28 am
first of all, great work, really helpful~
as your previous comment point out ,I can set $server->root when my files are under a folder beneath the root.here is my case:
/test/index.php
/test/TestController.php
I set $server->root=’/test/’ it doesn’t work.
if i set @url GET /test in TestController.php,it works.but this way I need to set all methods, what should i do?
April 23rd, 2011 at 10:07 am
anthor problem is when i use flash client to get data, it continue shows Error #2006 index out of range error at flash.utils::ByteArray/readObject(), does anyone have this problem?
April 24th, 2011 at 3:12 am
Jacob,I found the flash side code request.requestHeaders.push(new URLRequestHeader(“Accept”, “application/x-amf”));
actually not work anytime in flash player, as Adobe method help shows:
custom header is only supported when POST method is used,not GET method. It spend me one day to find this problem,so I post here in case others encounter the same problem. again thanks your great RestServer class.
April 24th, 2011 at 8:28 pm
finally get this work without errors.Cheers!
here is some tips:
1.RestServer const AMF = ‘applicaton/x-amf’; should be application/x-amf. type error here.
2.latest Zend Amf (v1.11.5 in my case) AMF3/Deserializer need one line:require_once ‘Zend/Amf/Constants.php’; or maybe there are other ways to include this reference, I am not familiar with PHP.
3.use url param ?format=amf to specify object encoding when use GET method in flash player.
Hope this can save others life. please delete my previous comments.
May 21st, 2011 at 6:53 pm
A Restful API should be stateless hence no sessions.
July 7th, 2011 at 10:22 am
Kudos to you Jacob. This is a breeze of fresh air up to now. Just saw that the Recess-Framework was your rolemodel. Guess I have to drill into that next. Would you mind to tell me if it supports HTTP-digest-auth. What was your intention to start a spin-off of Recess? Were there any issues you stumbled upon?
Bigup to Milan Zdimal. I already spotted “if (!strstr($url, ‘$’))…” in findURL() and then searched the comments to get myself assured. So the simple solution to get the parameters running is to swap “* @url PUT /users/:id” with “* @url PUT /users/$id” in the doc-comments, to let everyone know.
July 17th, 2011 at 4:02 am
[...] of open source development tools and personal experience. A nice base for a REST server in PHP is RestServer. It’s an elegant, light-weight class to make your functions instantly available as JSON via [...]
July 17th, 2011 at 4:02 am
[...] of open source development tools and personal experience. A nice base for a REST server in PHP is RestServer. It’s an elegant, light-weight class to make your functions instantly available as JSON via [...]
July 20th, 2011 at 4:18 am
Hi!
What should I have to modify to reach the restserver from the following link:
http://www.exapmle.com/restapi
I made some rewrite rules and everything after …/restapi mapped to the server.php but if I try http://www.example.com/restapi/ then i get “404 not found” instead “Hello World”. I debugged the problem and i saw that when the request mapped then the algorithm couldnt find any function matching restapi/. I’m not surprised because its true that i haven’t added any controller class with @url GET restapi/. I can add a basepath to the controller class but it would be nicer if i can run the server with the url mentioned before.
Anyway its a very good api but i havent got enough experince in php to solve my problem. THX.
July 20th, 2011 at 4:36 am
ok I think i missed to set the appropriate value to the root property of the server.
With working rewrite rules and the root property setted to “restapi/” everything works fine.
Thanx again for this API it rocks and saved me a lot of time.
Nice Work m8
July 24th, 2011 at 2:19 am
The link “http://jacwright.com/resources/RestServer.txt” is dead. Do you have a backup? Thanks
July 25th, 2011 at 9:45 am
Updated the link to point to the github version.
August 2nd, 2011 at 9:04 pm
Has the implementation changed for POST methods? Even when copy and pasting the method from the examples, ex: @url POST /login, it does not seem to work.
Both gets with and without parameters function as expected.
Thanks for any assistance :)
August 27th, 2011 at 10:25 am
Hi Jacob,
I have a method like:
/**
* Parses Invoice
*
* @url POST /parseInvoiceByClient/$clientGroup/$fileName
*/
public function parseInvoiceByClient($clientGroup = null, $fileName = null)
{
return $fileName;
}
My request is
/parseInvoiceByClient/1/36ed42de42a59ffb7250a4de1bb3dba9b58a7dc1.pdf
but my Rest Server returns: 36ed42de42a59ffb7250a4de1bb3dba9b58a7dc1
Somewhere everything after period gets dumped. Do you know why?
Adam
September 3rd, 2011 at 8:36 am
Thank you Jacob Wright,
This RestServer is the starting point and inspiration to create Luracast Restler (http://luracast.com/products/restler) an open source micro-framework that maps PHP Methods as RESTful APIs. We have just released version Restler v2 with lot of improvements.
Live examples at http://bit.ly/RestlerLiveExamples are good starting point to get started.
Looking forward to your contributions there :)
Regards,
Arul
September 3rd, 2011 at 9:03 am
Works great! Thanks for creating this. It’s come in very handy for an API I developed for my accounting system.
September 12th, 2011 at 11:28 pm
Great blog post and class.
Has the route syntax changed? I consistently get 404 for routes of the form:
/resource/:id
but success with routes of the form:
/resource/$id
Again, many thanks for the excellent work and the valuable contribution to the community.
September 18th, 2011 at 8:41 pm
Helpful information. Lucky me I found your web site by chance, and I’m stunned why this accident didn’t happened in advance! I bookmarked it.
September 22nd, 2011 at 4:45 am
Dear All,
How to integrate rest webservice using php + json. how to call iphone to our php service anybody know please help me. i know SOAP but i dont know rest + JSON..
Please help me on this regards.
Thanks,
mayasakthi
October 7th, 2011 at 5:23 am
Very interesting post. Thanks.
October 10th, 2011 at 5:01 am
hi jacob
i’ve got the same problem as Rubin had some months ago. no one tried to find a solution for this one, yet:
Fatal error: Uncaught exception ‘LogicException’ with message ‘Class MyClass could not be loaded’ in /myPath/lib/RestServer.php:157 Stack trace: #0 [internal function]: spl_autoload(‘CheckLoginContr…’) #1 /myPath/lib/RestServer.php(157): class_exists(‘CheckLoginContr…’) #2 /myPath/checkLogin/index.php(14): RestServer->addClass(‘CheckLoginContr…’) #3 {main} thrown in /myPath/lib/RestServer.php on line 157
same situation: locally with xampp and php 5.3.1 everything works fine, remotely i get the error above. i tried several different things, enabling apc, including classes manually, providing all sorts of different base-paths when submitting the controller-class, but nothing helped. can anyone help?
October 10th, 2011 at 6:31 am
I found the problem on my own. I didn’t adapt the @url annotations in the Controllers to fit the production environment. Sorry for bothering you.
November 11th, 2011 at 9:53 am
Hi… nice code… but i dont understand
public function saveUser($id = null, $data)
the param $data? how it works?
you wrote this: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.
but how can i used this?
there is no example with a url!!!!!
like this : http://example/{ “username”: “bob”, “password”: “supersecretpassword”, “firstName”: “Bob”, “lastName”: “Smith” }
…
please show me a an example i dont understand that to allow posting like json style.. thx
November 14th, 2011 at 11:44 am
You can do it using an Ajax library. Using jQuery you would do this:
var user = { "username": "bob", "password": "supersecretpassword", "firstName": "Bob", "lastName": "Smith" }
$.ajax({ url: "/api/saveUser", type: "POST", data: JSON.stringify(user)).then(function(results) { ... });
That should do it for you.
November 14th, 2011 at 9:51 pm
I’ve bumped into a very funny situation, which is, I have all my files put into the sub-directory rest/, so when I was accessing the http://localhost/rest/, it was successfully print out the “Hello World.”, yet I wrote another function in the TestController.php as below:
/**
* Return an testing message.
*
* @url GET /testing
*/
public function dog()
{
return “Print testing.”;
}
When I tried to access the http://localhost/rest/testing, Then it returned the error message:
Not Found
The requested URL /rest/testing was not found on this server.
Does anyone know how can I solve this problem? Thanks!!
November 16th, 2011 at 1:28 pm
Sounds like you don’t have server rewrite rules set up correctly.
January 11th, 2012 at 9:07 am
I get error when my url is like:
SOMEIP/myapp/service/user/test
but
SOMEIP//myapp/service/user/test it works , note the (//)
and
SOMEIP/myapp/service/user/get/56
all the @url GET /get/:id type methods wont work
i used the .htcaccess from this page
January 16th, 2012 at 7:37 pm
I did this….
public function getData()
{
$data = file_get_contents(‘php://input’);
if ($this->format == rest_format::AMF) {
require_once ‘Zend/Amf/Parse/InputStream.php’;
require_once ‘Zend/Amf/Parse/Amf3/Deserializer.php’;
$stream = new Zend_Amf_Parse_InputStream($data);
$deserializer = new Zend_Amf_Parse_Amf3_Deserializer($stream);
$data = $deserializer->readTypeMarker();
} else {
$data = json_decode($data);
}
if(!$data && $this->method == ‘GET’){
$data = (object) $_GET;
}
if(!$data && $this->method == ‘POST’){
$data = (object) $_POST;
}
return $data;
}
and removed the POST && PUT if for
$this->data = $this->getData();
So that it will pass data to the standard GET and use POST if data is not set.
January 18th, 2012 at 6:32 am
could any one please explain how ot write this .htaccess file.i have the same error as KRISS.
January 19th, 2012 at 5:35 pm
Be careful if you are running this rest server on linux
Maybe you can’t get rid of these 404 errors, (and I experimented this for hours),
try this, because it resolved my case :
on RestServer.php, inside the __construct method, after this line :
$dir = dirname(str_replace($_SERVER['DOCUMENT_ROOT'], ”, $_SERVER['SCRIPT_FILENAME']));
Add :
$dir = ($dir[0] == ‘/’ ? substr($dir, 1) : $dir);
The problem was that on Linux, for $_SERVER['DOCUMENT_ROOT'], i got “/var/www” whereas on windows i got “C:/wamp/www/”
So on Linux the root was miscalculated because there is no ‘/’ at the end of $_SERVER['DOCUMENT_ROOT'], so $this->url was miscalculated too, and nothing could match… hope that helped
January 31st, 2012 at 8:43 am
I get the same problem here too…
I can define a @url GET /foo/bar and that works well.
However the @url GET /foo/:id always comes back with a JSON formatted 404
I believe it is also running on a linux server.
Any ideas on how to fix this ?