There are a lot of tutorials, links, and publications about REST webservices. And there is something already well documented about the symfony framework and how it’s used to produce webservices with a REST protocol. There are even some plugins that implement REST with symfony. (See here, here and here.)
What we’ll see here is a practical way to write a symfony REST webservice, extending the Jobeet application originally developed for the symfony framework (you can find here the latest code for symfony 1.2, and here for symfony 1.4).
If you don’t know what REST is, check out how Ryan Tomayko explained REST to his wife.
Webservice scenario
First of all, let’s suppose we developed a dynamic website (http://www.jobeet.org) built on top of a PHP framework (symfony) and that uses an ORM engine (Doctrine). The site is up and running, and our client is very happy with her business.

After being successful for a while, she decides that a brand new mobile app is required to make the website shine on iPhones. Without losing any feature on the main website, and to minimize costs and time to develop, the most obvious solution is to develop a webservice build on top of the symfony app, to avoid code duplication (remember the DRY principle?).
So, here we still have a single web server, that now produces HTML, stylesheets and javascript code, and XML or JSON resources following REST standards.

We (ehm, Sensio) already developed the original app, so we will not split the controller in two, but add some new views, and generate a new module.
Routing and webservice module structure
To define a common structure for all the available resources, let’s define some routes:
ws_list:
url: /rest/:model.:sf_format
class: sfRequestRoute
param: { module: webservice, action: list, sf_format: xml }
requirements:
sf_method: [GET]
ws_create:
url: /rest/:model.:sf_format
class: sfRequestRoute
param: { module: webservice, action: create, sf_format: xml }
requirements:
sf_method: [POST]
ws_get:
url: /rest/:model/:id.:sf_format
class: sfRequestRoute
param: { module: webservice, action: get, sf_format: xml, column: id }
requirements:
id: \d+
sf_method: [GET]
ws_update:
url: /rest/:model/:id.:sf_format
class: sfRequestRoute
param: { module: webservice, action: update, sf_format: xml, column: id }
requirements:
id: \d+
sf_method: [PUT]
ws_delete:
url: /rest/:model/:id.:sf_format
class: sfRequestRoute
param: { module: webservice, action: delete, sf_format: xml, column: id }
requirements:
id: \d+
sf_method: [DELETE]
The first rule will get the list of jobs (GET), and to create a new one (POST). The second one will retrieve a single job (GET), will update it (PUT) and will delete it (DELETE).
The module webservice contains methods to perform every actions for the model: if no model is defined, or if the model does not exist, an error will be returned. The list and find methods will forward to the corresponding actions if the methods are set correctly.
class webserviceActions extends sfActions
{
public function preExecute()
{
$this->model = $this->getRequest()->getParameter('model');
if(!class_exists($this->model))
$this->forward('webservice', 'error');
}
public function executeList(sfWebRequest $request)
{
$this->objects = Doctrine_Core::getTable($this->model)->findAll();
}
public function executeCreate(sfWebRequest $request)
{
}
public function executeGet(sfWebRequest $request)
{
}
public function executeUpdate(sfWebRequest $request)
{
}
public function executeDelete(sfWebRequest $request)
{
}
public function executeError(sfWebRequest $request)
{
$model = $request->getParameter('model');
$this->getResponse()->setStatusCode(500);
$this->error = ($model != null) ? "Invalid model: " . $model : "Undefined model";
}
}
Listing all jobs
Following REST standards, we should be able to get all jobs with
and the result should be
<objects>
<object id="1">
<id>1</id>
<category_id>1</category_id>
<type>full-time</type>
<company>Sensio Labs</company>
<logo>sensio-labs.gif</logo>
<url>http://www.sensiolabs.com/</url>
<position>Web Developer</position>
<location>Paris, France</location>
<description>You've already developed websites with symfony and you want to work
with Open-Source technologies. You have a minimum of 3 years
experience in web development with PHP or Java and you wish to
participate to development of Web 2.0 sites using the best
frameworks available.
</description>
<how_to_apply>Send your resume to fabien.potencier [at] sensio.com
</how_to_apply>
<token>job_sensio_labs</token>
<is_public>1</is_public>
<is_activated>1</is_activated>
<email>job@example.com</email>
<expires_at>2010-10-10 00:00:00</expires_at>
<created_at>2011-06-30 17:12:48</created_at>
<updated_at>2011-06-30 17:12:48</updated_at>
</object>
<object id="2">
...
</objects>
The webservice list action retrieves all the jobs:
public function executeList(sfWebRequest $request)
{
if($request->isMethod(sfRequest::POST))
$this->forward('webservice', 'create');
$this->objects = Doctrine_Core::getTable($this->model)->findAll();
}
The xml view cycles through all the objects, and renders all the fields:
<?xml version="1.0" encoding="utf-8"?>
<objects>
<?php foreach ($objects as $object) : ?>
<object id="<?php echo $object['id'] ?>">
<?php foreach ($object as $key => $value): ?>
<<?php echo $key ?>><?php echo $value ?></<?php echo $key ?>>
<?php endforeach ?>
</object>
<?php endforeach ?>
</objects>
Getting one job only
To retrieve only one job, identified by its id, we call
and the result should be
<object id="1">
<id>1</id>
<category_id>1</category_id>
<type>full-time</type>
<company>Sensio Labs</company>
<logo>sensio-labs.gif</logo>
<url>http://www.sensiolabs.com/</url>
<position>Web Developer</position>
<location>Paris, France</location>
<description>You've already developed websites with symfony and you want to work
with Open-Source technologies. You have a minimum of 3 years
experience in web development with PHP or Java and you wish to
participate to development of Web 2.0 sites using the best
frameworks available.
</description>
<how_to_apply>Send your resume to fabien.potencier [at] sensio.com
</how_to_apply>
<token>job_sensio_labs</token>
<is_public>1</is_public>
<is_activated>1</is_activated>
<email>job@example.com</email>
<expires_at>2010-10-10 00:00:00</expires_at>
<created_at>2011-06-30 17:12:48</created_at>
<updated_at>2011-06-30 17:12:48</updated_at>
</object>
The webservice get action retrieves one job, given its ID:
public function executeGet(sfWebRequest $request)
{
$id = $request->getParameter('id');
$this->object = Doctrine_Core::getTable($this->model)->findOneById($id);
$this->setTemplate('resource');
}
Again, the xml view cycles through the object fields:
<?xml version="1.0" encoding="utf-8"?>
<object id="<?php echo $object['id'] ?>">
<?php foreach ($object as $key => $value): ?>
<<?php echo $key ?>><?php echo $value ?></<?php echo $key ?>>
<?php endforeach ?>
</object>
In the next tutorial, we’ll see how to create and update jobs. See there then…
Links
REST on Wikipedia
Web Services and Symfony
The origina Jobeet website
How Ryan Tomayko explained REST to his wife






6 Comments
Hi thanks for your post , it was quite easy to follow and well written. I would like to ask if your example could actually be “repurposed” easily for another simple database that has been just loaded into mysql to set it up as a web service? Would there be any missing steps in between as compared to the well developed Jobeet page ? Thanks !
Hi Jamen, actually defining a simple database and applying what I’m writing here is even easier. I didn’t put a new database since it would take a post by itself, but you just need to define a model here, the relative routing and then the actions that render the correct format. In the next tutorial, I’ll show the create, put and delete methods, so stay tuned! B))
Hi Pietrino ,
Thanks for preparing for your upcoming tutorials as well
Your tutorial really came at a very important time to me. Thanks ! I’ve tried to recreate the Jobeet Tutorial and up till day 3 where all basic data has been loaded up into mySQL. I’ve followed through your steps and the browser returned a “Page not found, Server returned a 404 response ” ? I’ve followed exactly to the dot here , what might be the problem ? Thanks Pietrino !
Hi Jamen,
as I specified in the tutorial, the best is to start from the complete Jobeet tutorial (day 22). Your 404 could be related to the routing, so clear your cache, and check that the routes exist (symfony app:routes frontend). Also, the model in day 3 is Job, not JobeetJob (that’s because Jobeet is defined as a prefix in day 20).
Hi Pietrino ,
Yup sure , I’ve tried it too , still trying to figure out how to work. After clearing cache still doesn’t work.
Actually its stated in page 40 of the tutorial (chapt 3) that Job is the module created for the JobeetJob model … So which one is true ?
Hi Jamen,
in the tutorial shown, the model we are referring to is JobeetJob, and the corresponding routing uses the model. If you want to test it, use something like “curl http://jobeet.localhost/frontend_dev.php/rest/jobeetjob.xml“. You should see the symfony debug log. In the part 3, I’ll show how to test the webservice, so maybe that will help too.