Kohana Simple REST Server

Published on:
Tags: Personal

I’ve been using something like this for a little while now, but initially held back on writing anything about it as I wanted to do something a little better using the Router3 module and/or KO3, however there has been some noise recently about REST, so I thought I would share this implementation as a possible starting point.

REST service implementations seem to be growing in popularity at the moment, I’m sure the likes of de.licio.us, twitter, flickr et al have done a lot to raise it’s profile but essentially its a concept that still suffers from a lack of general understanding.

I’m not going to go into detail on describing the ins and outs of what a REST service should be, the post would quickly turn into something much larger, but instead I’m going to try and show just how basic it is to set up your own Service Oriented Architecture (SOA) in Kohana which adheres to many of the standard REST principles. I’ll then hopefully show some of the benefits of having done so, though they may follow in further posts. One point I will cover briefly however is that of REST ‘method’ names. When exposing data via an interface the information of what to do with that data must also be communicated in some fashion; it’s fine to ask “user”, but what do you want to do with this information? View a user, edit, create, delete perhaps. REST states that the standard HTTP methods of GET, POST, PUT and DELETE suffice to enable the communication of all possible actions on a set of data, thus leaving the URI free of such information. This is in contrast to an XML-RPC implementation which will typically expose all possible actions an a data entity via its own URI.

The use of these 4 HTTP verbs are often the first stumbling block for any budding REST service, and this is for a number of valid reasons: browser support as well as a slight increase in effort required to handle these on the server side are often good enough reasons to make adherence to this a concession (see twitter, de.licio.us and flickr ! ). For more information on this see this apacheweek article . I’m going to propose one such concession for this in my soon-to-follow Kohana implementation but may publish a future article showing how to adhere to this if there is any interest shown.

Kohana Implementation For our Kohana implementation we will need a service ‘gateway’ (an entry point onto our service) and some method of interacting with our data. We can also use some of the benefits of Kohana to look at tidying up our interface and providing some simple but effective service error handling. Because we are not going to support (for now) PUT and DELETE operations, we are going to need some way of discerning our data ‘methods’. I’m going to make use of POST (as opposed to our URI structure) and standardize on a post parameter which will need to be passed in order to designate the required operation. We can then simply throw an error if this is malformed or not present.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
class Service_Controller extends Controller {
  public function __construct()
  {
      header('Content-type: text/xml');
      parent::__construct();
  }

  public function __call($entity, $params)
  {
      $entity = ORM::factory($entity, $params[0]);
      $get = $this->input->get();
      $post = $this->input->post();
      if( count($post) == 0 )
      {
          return $this->get($entity);
      } else {
          switch ($post['action'])
          {
              case 'delete':
                  $this->delete($entity);
                  break;

              case 'update':
                  $this->update($entity, $post);
                  break;

              case 'create':
                  $this->create($entity, $params[0], $post);
                  break;

              default:
                  //throw an error
          }
      }
  }

  protected function get(ORM $entity){}
  protected function update(ORM $entity, Array $post){}
  protected function create(ORM $entity, $identifier, Array $post){}
  public function delete(){}
}

The above code shows a stripped down version of the service controller, it has all the entity method functions taken out (their declarations are left for clarity) and all error handling removed as well. It shows though how simply such a standardized gateway can be set up. We can now access our service at /service// so for example:

  • service/cart/matt
  • service/product/product-slug
  • service/category/2

Serviceable interface I create a serviceable interface, which models that wish to be accessible via this rest interface must implement:

1
2
3
4
5
6
<?php
interface serviceable_Driver {
  public function set_identifier($id);
  public function get_representation($format = 'xml');
  public function read_representation($data, $format = 'xml');
}

Enforcing implementation of this means that we can make sure we only attempt to serve entities which implement our interface, meaning that we can deploy our serviceable module in side any project and not have it enable access to all models

An example implementation of this for a contrived cart example could look something like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public function set_identifier($id)
{
  $this->user = $id;
  return $this;
}

public function get_representation($format = 'xml')
{
  $v = View::factory('cart/cart');
  $v->cart = unserialize($this->cart_items);
  return $v;
}

public function read_representation($data, $format = 'xml')
{
  $xml_Obj = simplexml_load_string($data);
  $items = array();
  //Rebuild the items
  foreach ($xml_Obj->items->item as $item)
  {
      $tmp_Obj = new StdClass;
      $tmp_Obj->id = (string) $item->id;
      $tmp_Obj->name = (string) $item->name;
      $tmp_Obj->price = (string) $item->price;
      $items[] = $tmp_Obj;
  }
  $this->items = $items;
}

The above is just thrown together, but the idea is to handle the transformation between the object and the requested format (currently we just handle xml). With some more effort you could probably create an extension of ORM which went some way towards automatically handling the transformation . .again more for a later post maybe!

Error Handling The last thing I will touch on is how we could handle a unified error handling mechanism. Kohana has a great error handling mechanism as standard, by extending Kohana_Exception we can provide our own sendHeaders function (and return text/xml headers for instance) and our own error output view to format our error output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Kohana_Service_Exception extends Kohana_Exception
{
  protected $template = "service_error";

  public function __construct($message)
  {
      parent::__construct($message);
      $this->code = "Kohana Service Error";
  }
  /**
  * Sends an Internal Server Error header.
  * @return void
  */
  public function sendHeaders()
  {
      // Send the 500 header
      header('HTTP/1.1 500 Internal Server Error');
      header('Content-type: text/xml');
  }
}

This works fine for returning an xml formatted error for consumers of our service to react to.

Conclusion The purpose of this has been to demonstrate how simply such a service could be set up, I have a module which I can deploy which follows this general pattern, but obviously includes error handling as well as the method implementations, if theres any interest in this, then I can tidy it up and put it up on Github for anyone that wants it. I’m going to follow up on this and show how simple it would now be to implement caching for our entire service. I’ll also hopefully talk about how to set up a testing suite for your service using phpUnit.

/Matt