REST webservice with symfony

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:

# apps/frontend/config/routing.yml

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.

// apps/frontend/modules/webservice/actions/actions.class.php

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

curl http://jobeet.localhost/rest/jobeetjob.xml

and the result should be

<?xml version=“1.0” encoding=“utf-8”?>
<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:

// apps/frontend/modules/webservice/actions/actions.class.php

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:

// apps/frontend/modules/webservice/templates/listSuccess.xml.php

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

curl http://jobeet.localhost/rest/jobeetjob/1.xml

and the result should be

<?xml version=“1.0” encoding=“utf-8”?>
<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:

// apps/frontend/modules/webservice/actions/actions.class.php

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:

// apps/frontend/modules/webservice/templates/findSuccess.xml.php

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

Add a Comment

Your email address will not be published. Required fields are marked *