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>

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. ;)

125 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. links for 2009-11-17 « xLight的蓝闪小窝 Says:

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

  6. John McKerrell Says:

    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. Brian Coleman Says:

    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. Brian Coleman Says:

    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….

  51. gude Says:

    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

  52. Jacob Wright Says:

    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.

  53. lordsiris Says:

    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!

  54. Jacob Wright Says:

    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.

  55. lordsiris Says:

    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!

  56. lordsiris Says:

    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.

  57. Jon Says:

    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?

  58. Jon Says:

    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.

  59. Jacob Wright Says:

    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.

  60. Eirik Johansen Says:

    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?

  61. Morten Says:

    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']);

  62. Jacob Wright Says:

    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.

  63. John Chung Says:

    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?

  64. JSIDHU Says:

    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.

  65. John Chung Says:

    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?

  66. Jacob Wright Says:

    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();

  67. rubin Says:

    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

  68. Jérémy Says:

    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;
    }
    ####################

  69. Jérémy Says:

    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

  70. Jacob Wright Says:

    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;
    }

  71. Jeremy Says:

    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 ;)

  72. Milan Zdimal Says:

    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!

  73. Longyu Says:

    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?

  74. Longyu Says:

    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?

  75. Longyu Says:

    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.

  76. Longyu Says:

    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.

  77. Lee Howarth Says:

    A Restful API should be stateless hence no sessions.

  78. JackLeEmmerdeur Says:

    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.

  79. Give your mobile app some REST | Cloudified Says:

    [...] 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 [...]

  80. Give your mobile app some REST | Cloudified Says:

    [...] 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 [...]

  81. grigo Says:

    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.

  82. grigo Says:

    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

  83. Will Strimling Says:

    The link “http://jacwright.com/resources/RestServer.txt” is dead. Do you have a backup? Thanks

  84. Jacob Wright Says:

    Updated the link to point to the github version.

  85. Queue Queue Says:

    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 :)

  86. AdamS Says:

    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

  87. Arul Says:

    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

  88. Stephen Says:

    Works great! Thanks for creating this. It’s come in very handy for an API I developed for my accounting system.

  89. David Weinraub Says:

    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.

  90. Lowes Ceiling Fans Says:

    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.

  91. mayasakthi Says:

    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

  92. Albulescu Cosmin Says:

    Very interesting post. Thanks.

  93. Fabian Says:

    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?

  94. Fabian Says:

    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.

  95. Oli Says:

    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

  96. Jacob Wright Says:

    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.

  97. Kevin Says:

    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!!

  98. Jacob Wright Says:

    Sounds like you don’t have server rewrite rules set up correctly.

  99. Kriss Says:

    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

  100. Chuck Says:

    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.

  101. vinay Says:

    could any one please explain how ot write this .htaccess file.i have the same error as KRISS.

  102. kirby Says:

    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

  103. Andi Says:

    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 ?

  104. Kelly Jones Says:

    Awesome post. Here’s a tool that lets your build your online database without programming. There is no need to hand code PHP. Cut your development time by 90%
    http://www.caspio.com/

  105. ketan Says:

    RestServer is nice one…
    but i can’t bind my controller to index.php or my method with specific url…
    for example:
    i try to put
    RestServer.php and index.php and sample.htaccess at root
    and my controller at root/demosite/rest/

    change @url /product/ for test method

    in index.php i add
    $server->addClass(‘DemoController’, ‘/demosite/rest’);

    and i call localhost/product/ ==== it gives
    Object not found! that apache is not found error…

    what is my mistake? can anyone help me…..?

  106. Andrew Says:

    Hi, trying to use the RestServer in a sub-directory (http://mysite.com/rest) – the rewrite in my .htaccess is set up correctly (phpinfo in the index.php file prints correctly given any url in that directory) – but as soon as I try to use the RestServer with a test controller, I get a json formatted 404 no matter what.

    index.php:

    require “RestServer.php”;
    require “AccountsController.php”;

    spl_autoload_register();

    $mode=’debug’;
    $server = new RestServer($mode);
    $server->root = ‘/rest/’;
    $server->refreshCache();

    $server->addClass(‘AccountsController’);
    $server->handle();

    AccountsController.php:

    class AccountsController
    {
    /*
    * Returns all accounts
    *
    * @url GET /
    */
    public function getAllAccounts()
    {
    return “Hello World”;
    }
    }

    What am I missing?

  107. Cristián Says:

    First of all, Great Post Jacob

    I’ve been working around your RestServer this week end and I’ve found it very helpfull as an introductory class to REST

    I have had then same problem that Andrew (last post) , I didn’t figure out until today that the comments where been scanned for parameters.

    Andrew, you have to modify the comments in the controller, the following line should be modified:

    FROM HERE
    * @url GET /

    TO HERE
    * @url GET /rest

    So you will be able to run this url http://mysite.com/rest and get your results :)

  108. thessoro Says:

    Hi!

    It seems the author has changed the format of the placeholder. Reading the code is clear it looks for a dollar sign:

    So, instead of:

    * @url GET /users/:id

    use:

    * @url GET /users/$id

    Hope it helps.

  109. A REST service for Meaningful Healthcare | I'm Wesley Brand and I work as a freelance software developer in Birmingham, AL. Says:

    [...] If you plan to write a full end-to-end application in PHP, to include a REST service, I’d recommend using the Zend Framework.  That buys you an MVC setup, and REST formatting/routing without much effort on your part.  However, if you just need a quick and dirty service, have a look at this one page php REST solution (in depth explanation here). [...]

  110. James Says:

    Firstly thanks for this :)

    Took a while to get this going without docs hehe. But for other guys reference, to get this going in a subdirectory (api in this case), the index file needs a change. After this line:

    $server = new RestServer($mode);

    Add this link:

    $server->root = ‘api’;

    Note NO slashes. There was an example above with slashes but this broke it. This makes requests work at yourdomain.com/api

  111. James Says:

    Actually, with my previous comment, no slashes makes it work for the directory root path endpoint (‘/’), but breaks all others. I fixed this with a trailing slash only

    $server->root = ‘api/’;

  112. Luis Says:

    Jacod, thanks for your work.
    I am trying to use your restful server but I could not make it works.
    My server is configured with Apache 2.2 and php 5.2.11 on windows xp, url rewriting is configures on xxx and it is working fine.
    It doesn’t matter if the url is xxx/login or xxx/ or xxx/user I always get:

    Warning: Call-time pass-by-reference has been deprecated in C:\Apache2.2\htdocs\www\tomaestado\RestServer.php on line 365
    {
    “error”: {
    “code”: 404,
    “message”: “Not Found”
    }
    }

  113. Stephan Says:

    I think you have to replace

    if(preg_match(‘/\.(\w+)$/i’, $_SERVER['REQUEST_URI'], &$matches)) {

    with

    if(preg_match(‘/\.(\w+)$/i’, $_SERVER['REQUEST_URI'], $matches)) {

    to make it work.

  114. Klaas Says:

    Good tutorial, I am about to implement my first REST webservice for an Android app. I choose REST above SOAP for several reasons but good tutorials are scarce.
    I have this tutorial working right now on my Linux/Apache system and can surely use it for my project!

    One question, my project involves logging in to a MySQL database. In an normal Web application, a HTTP session is used for the user keeping logged in (until he explicitly logs off).
    But (REST) Webservices are stateless, so how to implement this? Am I forced to send the credentials in each subsequent request? I would not like to send the password, or even the MD5 hash, as a GET request parameter while pulling user information.

    Probably I am not the first to do this but I have not found a satisfying solution, so hints are more than welcome.

  115. Jeremy Says:

    With REST you have to send the credentials up with each request if you are going to keep a true to REST, but you can cache state as a session if you are opposed to sending up credentials all the time.

  116. penis enlargement questions Says:

    You really make it seem so easy with your presentation but I find this matter to be really something that I
    think I would never understand. It seems too complex and very broad
    for me. I’m looking forward for your next post, I
    will try to get the hang of it!

  117. almond milk benefits Says:

    Please let me know if you’re looking for a writer for your weblog.
    You have some really great articles and I feel I
    would be a good asset. If you ever want to take some of the load off,
    I’d love to write some material for your blog in exchange for a link back to mine.
    Please shoot me an email if interested. Thanks!

  118. Virgilio Oliveira Says:

    Hi need help about the returning values, i’m using the rest server with the pentaho data integration and i need to recieve just an integer value as return value but I always get a string like “234″ with the quotes included is there any way to just send a number or a string without the quotes?

    My congratulations, it is simple and easy to use and set to work, thank you.

  119. scam Says:

    Thanks for sharing your thoughts. I truly appreciate
    your efforts and I will be waiting for your further post
    thanks once again.

    My website scam

  120. Google Says:

    Hi there! Someone in my Facebook group shared this site with us so I came to give it a look.
    I’m definitely loving the information. I’m bookmarking and
    will be tweeting this to my followers! Superb blog and excellent style and
    design.

  121. Delia Says:

    whoah this weblog is magnificent i really like studying
    your articles. Keep up the great work! You realize, many people are
    searching around for this info, you could aid them greatly.

  122. http://kylepoint.com/ElectricPressureCookers-HonestReviewOfNescoAndCusinartPressureCookers.html Says:

    Hi there it’s me, I am also visiting this website daily,
    this web page is genuinely fastidious and the people are really sharing pleasant thoughts.

  123. Darrell Says:

    Wonderful website. A lot of useful information here. I’m sending it to a few pals ans
    additionally sharing in delicious. And of course, thanks to your sweat!

  124. Adrian Says:

    I think that is among the such a lot important info for me.
    And i’m satisfied studying your article. But wanna observation on few basic
    issues, The web site style is great, the articles is in reality nice : D.
    Excellent activity, cheers

  125. bit.ly Says:

    Hey! Quick question that’s entirely off topic. Do you know how to make your
    site mobile friendly? My weblog looks weird when browsing from my iphone4.

    I’m trying to find a template or plugin that might be able to fix this problem.
    If you have any recommendations, please share. Cheers!

Leave a Reply