Okay, you’ve read LornaJane’s blogpost series on REST, you’ve attended the Techademy Silex/REST workshop to hear Stefan Koopmanschap talk about Silex and Joshua Thijssen about REST, seen the post of Dave Marshall on REST so what now?
Try and create a REST example application with Silex of course! This blogpost describes my steps creating this example application from front to back.
The order in which I’ve written down and done things is not pre-defined. There are multiple orders to achieve the same result. This is just the way I did it.
First off: If you use a web server, be sure to check whether or not the IonCube extension is loaded. This is because IonCube < v 2.0.9 doesn’t work with PHAR files.
So let’s start off by checking if Silex will run on the server:
Set up directory structure. I chose to have a folder ‘lib’ for storing silex.phar and other optional external stuff and to have a folder ‘http’ where all web accessible scripts are located. Purely based on personal taste. (I don’t like a document root full of files).
Download silex.phar from silex.sensiolabs.org and put the phar file in the lib dir. Get the .htaccess file from the website and put it in the http dir. Put create an index.php file with the following contents:
Now navigate your browser to <your webhost url>/<this project’s folder>/http/index.php/hello/<your name>. It should display ‘Hello, <your name>’. If not, the line $app[‘debug’] = true; will help you with a stacktrace. Once you got Silex working, go to the next step.
REST
We want to perform four operations on resources: POST, GET, PUT and DELETE. These correspond to Create, Read, Update and Delete. But note: CRUD =/= REST, but it helps!
As Silex is able to determine the differences between all four HTTP methods, we don’t have to worry about that. Because Silex contains the HttpFoundations of Symfony2, it allows us to define the HTTP status code and message very easy. The only thing left to do is to define when to return what. Let’s look into more detail on that.
Response codes
For creation of a resource, the POST header will be used. On succes, we return a HTTP response code 201, created. The body will contain the newly created resource. On failure we return a response code 400: Bad request.
For reading a resource we use the GET header. On succes, we return a response code 200, OK, with the resource content. When a resource cannot be found, we return an HTTP response code 404: Not found with no content.
For updating a resource the PUT header is used. On success we return a response code 200, OK, with the (updated) resource content. When an error occurs, we return a response code 400: Bad request.
For deletion of a resource the DELETE header is used. On success, we return a response code 204, No content, with (guess what..) no content, as the resource doesn’t exist any more. When the request cannot be processed, we return a response code 400: Bad request.
Data source
So let’s use a database for storing our (small amount of) data. The (SQL) schema can be found in data/database.sql. In Silex we define a connection ($app[‘con’]) which is opened before execution and closed afterwards. For this, the methods before() and after() are used.
Returning data types
Let’s add some complexity.. we want to be able to return xml and json. There are several options to determine which data to return. In this example we’ll return a format based on the extension provided, but it could also be done by looking at the request (which format the requesting party will accept).
For putting the results in the appropriate format we use Twig, which is the default templating engine (although you have to download is ‘install’ it separately). We also assign twig to $app and we’re set to go with it.
For picking up the format itself, we use the {format} variable in the routing. We use this to determine the template for twig to render. The templates are located in lib/twig/templates (which is also defined in the script).
Resource setup
As said earlier: REST =/= CRUD. Here’s another one: database schema =/= resource. Some ORM software implementing the Active Record may give you this idea, but there’s a difference. A resource is a ‘natural’ resource. You can construct a resource from data from various tables and/or rows. This is also done in the books resource: we not only use records from the books table, but also from genre and author.
Creating a resource: POSTing data
Line 121 starts the function which will create a genre resource. First off we check if all data is present to create the resource. In this case it’s simply the genre name. If not all data is present, the request is not okay and we return a 400 Response (Bad Request) to the requester. Next we will check if that genre already exists to prevent duplicate genres. If the genre already exists we provide an HTTP 301 to denote to the requester that that particular genre resource already exists on another resource location. If the genre doesn’t exist yet, we create it and return the newly created resource to the user, with an HTTP 201 (Created).
Getting a resource: GETting data
The get method is defined at line 100. This is pretty straightforward: either a resource exists or it doesn’t exist. We query the database for the requested genre. If it doesn’t exist, we return an HTTP 404 (Not Found), else we return an HTTP 200 with the requested resource.
Updating a resource: PUTting data
At line 168, the put method is defined. This operation checks if all data is present. If not, it returns an HTTP 400. After that it checks if the resource actually exists. If it doesn’t exist we return the common HTTP 404. If it does exist, we update the data and return the updated resource with an HTTP 200 to show that all went well.
Deleting a resource: DELETE data
The delete method is defined at line 219. This is a pretty straightforward again: If the resource doesn’t exist we just return a 404. If it does exist, we delete it from the database. As the resource doesn’t exist any more, we confirm deletion by sending back an empty message to the client with an HTTP 204 (No Content) header.
Time for some code!
The full project, including Silex, twig and the twig templates can be found on my downloads page.
Notes
These are my first steps in creating a RESTful implementation in Silex. I know that the code is not optimal, nor does it separate concerns in an MVC way. This is just a ‘quick’ example as a supplement to all theoretical examples without any practical examples.
However: any feedback is very welcome. I’ll be adding more code and, given enough to talk about, blogposts in the near future.
Nice article, very clear ! I just started playing around with silex and I find it to be just as great as symfony but more freedom. You think it’s a good base for a REST application?
Btw I started working at some company a couple months ago… Infopact sound familiar to you ? ;P
Yes, Silex is kind of the little brother of symfony2. It has a small footprint and there’s a lot of freedom in what to include.
REST is basically about serving plain data over HTTP, which is exactly what Silex is perfectly capable of. Of course you might want to put a data store under it, but you won’t need much more than that. In that sense Silex is perfectly fit for RESTful applications. The one thing I find very neat is the routing part which translates to the named variables in the methods. This gives a quick overview on the calls your application can handle.
Regarding the company.. never heard of it 😉 hehe
I’ve held multiple positions there and also got my chance to grow my developer skills, for which I’m thankful 🙂
Now we’ll define a new resource class that always returns a 402 (payment required) response. This is really not very different from the resources that was defined in previous examples. The fact that it has a response code other than 200 doesn’t change anything else about its role. This will require using the request object, though, which none of the previous examples have done.
Request 416 (Requested Range Not Satisfiable) A server SHOULD return a response with this status code. code if a request included a Range request-header field (section 14.35), and none of the range-specifier values in this field overlap the current extent of the selected resource, and the request did not include an If-Range request-header field. (For byte-ranges, this means that the first- byte-pos of all of the byte-range-spec values were greater than the current length of the selected resource.) When this status code is returned for a byte-range request, the response SHOULD include a Content-Range entity-header field specifying the current length of the selected resource (see section 14.16). This response MUST NOT use the multipart/byteranges content- type.
The precondition given in one or more of the request-header fields evaluated to false when it was tested on the server. This response code allows the client to place preconditions on the current resource metainformation (header field data) and thus prevent the requested method from being applied to a resource other than the one intended.
O link para o download do código está ‘quebrado’.
Você poderia disponibilizar outro link para que possamos baixar o projeto e, se possível, enviar tal link para meu email de contato?
Eu agradeço.
Bom dia.