Symfony a RESTFul app: REST Levels 0, 1, 2 ( FOSRestBundle, JMSSerializerBundle )

REST Levels 0,1,2:

As Martin Fowler explains about Leonard Richardson REST model the first three levels are bout to use the resources provided by the HTTP Protocol to communicate actions in a more verbose way. Using URLs as Resources to uniquely identify it by identity. Using HTTP Request Method as action verb, so a request to a Resource /posts can be one of this five:

GET http://domain/posts                 // get a collection of posts
GET http://domain/posts/1            // get the post id 1
POST http://domain/posts             // create a new post
PUT http://domain/posts/1           // update the post id 1
PATCH http://domain/posts/1     // update the post id 1
DELETE http://domain/posts/1    // delete the post id 1

There is a difference between the PUT and PATCH methods, as explained on this article; The PUT action should replace the whole Resource with the new information provided, instead the PATCH action should replace only the fields with the provided information.

In this post we will try to create an example using symfony2. As always all the code on this series can be found on this github repository under the symfony2_restful_example branch.

FOSRestBundle:

This great bundle comes to be so popular that even have its own page on the symfony documentation. It expose configurations and enable features that allow us to build REST applications with symfony in a quick and easy way. So lets follow the steps described on the page.

Installation, follow the easy steps on the documentation page. For this bundle to work a serializer is required and the proposed one its JMSSerializerBundle.

Until now only installation and including the bundles on the AppKernel.php has been made.

Configuration:

fos_rest:
    disable_csrf_role: ROLE_API
    param_fetcher_listener: true
    body_listener: true
    allowed_methods_listener: true
    unauthorized_challenge: "Basic realm=\"Restricted Area\""
    access_denied_listener:
        json: true
        xml: true
        html: true
    view:
        view_response_listener: force
        force_redirects:
          html: true
        formats:
            json: true
            xml: true
    format_listener:
        rules:
            - { path: ^/, priorities: [ json, xml ], fallback_format: json, prefer_extension: true }

What this configuration means, a line by line break down:

disable_csrf_role: ROLE_API

Disable the csrf validation on form submissions only if the authenticated user have the ROLE_API assigned.

param_fetcher_listener: true

Enable the symfony param fetcher strategy

body_listener: true

Decode the data sent in the request so the Request Object can be populated with the desired method like application/x-www-form-urlencode or application/json.

allowed_methods_listener: true

adds the Allow HTTP header to each request appending all allowed methods for a given resource.

unauthorized_challenge: "Basic realm=\"Restricted Area\""
access_denied_listener:
    json: true
    xml: true
    html: true

This listener will enter before the symfony firewall exception listener and handle the response for all the configured formats.

view:
    view_response_listener: force
    force_redirects:
      html: true
    formats:
        json: true
        xml: true

The view listener its the most complex so i will leave it to the documentation. As a quick explanation it handle the response from the controller as a View object and finally converted as the expected format.

format_listener:
    rules:
        - { path: ^/, priorities: [ json, xml ], fallback_format: json, prefer_extension: true }

Finally the format listener set the rules to be taken into consideration on the routes that match the path regex, on what response formats are allowed, the priority to choose them if detection fails, if it will have a fallback format by default and much more.

This is a really simple configuration, the bundle expose a more complete one that allow you to configure your project at the specific level you need, but the explanation of the configuration its not on the scope of this post.

Routing:

greetings:
    resource: "AppBundle\Controller\GreetingsController"
    type:     rest

This way we are telling symfony to use the FOSRestBundle Routing generator that will parse our controller class and generate routes for all our public functions with the HTTP Method that is defined on the method name, more about this can be found on the documentation.

Lets add a simple model called Hello following the clean architecture on a directory called Greetings

/**
 * Class Hello
 */
class Hello
{
    /**
     * @var string
     */
    private $greet;

    /**
     * Hello constructor.
     */
    public function __construct()
    {
        $this->greet = "Hello World!!!";
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->greet;
    }
}

And add it to the autoload

$loader->addPsr4("Greetings\\", __DIR__.'/../../Greetings', true);

Our controller looks like this, using or Hello model

<?php

namespace AppBundle\Controller;

use FOS\RestBundle\Controller\Annotations as FOSRestBundleAnnotations;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Routing\ClassResourceInterface;
use Greetings\Hello;

/**
 * @FOSRestBundleAnnotations\View()
 */
class GreetingsController extends FOSRestController implements ClassResourceInterface
{
    public function helloAction()
    {
        return new Hello();
    }
}

This will generate the following path:

hello_greetings          GET    ANY    ANY  /greetings/hello.{_format}

If we request that url we should see the following response:

{
  greet: "Hello World!!!"
}

What gave us this configuration:

  • A set of rules for the routes that will follow the level 1 of the REST model
  • Easy way to handle several response formats like json and xml

Conclusion:

At this point our solution follow the first three levels of the REST model, FOSRestBundle enable us to easily start building a REST app with good practices.

Series:

  1. Motivation
  2. REST Levels 0, 1, 2 ( FOSRestBundle, JMSSerializerBundle )
  3. REST Levels 0, 1, 2 ( FOSUserBundle )
  4. REST Levels 0, 1, 2 ( NelmioApiDocBundle )
  5. REST Levels 3 ( BazingaHateoasBundle )
  6. Security ( FOSOAuthServerBundle )
  7. Security ( Securing the token path )
  8. Security ( Securing the token path – FIXED )

7 Comments

Leave a comment