To demonstrate how FOSRestBundle can be used, we shall create a very simple application. It will allow CRUDL operations on two entities: organisations and users. Each user will belong to one organisation.
To configure the application, we first need to add the FOSRestBundle to composer.json:
Next, register the FOSRestBundle and JMSSerializerBundle
Disable the default view annotations and set up the FOSRest response listener:
The RESTful routing is handled implicitly by the FOSRestBundle, we just need to tell it which controllers are RESTful:
And then tell the application to read our bundle’s routing file:
Due to the implicit routing, routes will be automatically generated for the actions within each controller.
Now that the application is configured, we can create the two entities the application requires. These can be created using the Doctrine2 entity generator tool:
Create an entity with the name NmpoloRestBundle:Organisation and then add a string field called name. Tell the tool to create an empty repository class and then confirm generation. Next, do the same again to create a NmpoloRestBundle:User entity.
Now, we need to add the user => organisation relationship:
The getters and setters can automatically be generated for this relationship by running:
Now that the entities have been created, we can create the controllers and actions used to manipulate them.
GET /organisations - List the organisations
We can easily get all organisations using the entity’s repository:
GET /organisations/id - Get a specific organisation
Likewise, we can do something similar to get a specific organisation:
POST /organisations - Create an organisation
Creating an organisation first requires a form we can use to bind a request to. You can automatically generate a form for a Doctrine entity using the command line tool: php app/console generate:doctrine:form NmpoloRestBundle:Organisation.
By default, Symfony2 enables CSRF protection for forms. CSRF protection doesn’t make much sense for a REST API so you can disable it by adding 'csrf_protection' => false to the array passed to $resolver->setDefaults().
Symfony2 will also set the form name as “nmpolo_restbundle_organisationtype” so this can be changed to just “organisation” or removed entirely if you don’t require a root name.
Now that we have the form, we can write the method to handle post requests:
PUT /organisations/id - Update a specific organisation
DELETE /organisations/id - Delete a specific organisation
As we said in the routing that organisation was the parent of user, all the user routes are appended to the organisation route.
For example, to create a new user, we must POST /organisations/id/users. To get a specific user, we must GET /organisations/id/users/id.
Please see the source code on github for the user controller.
JMSSerializerBundle allows us to specify in our request what content type we expect to be returned. For example, if we send the header Accept: application/json, we will receive json. Likewise with application/xml. If we want to receive HTML, we will also have to create views to output the data. To see example view scripts, please checkout the example code on github.
The JMSSerializerBundle will, by default, expose all of an entity’s properties. This is not ideal if you’re storing, say, a user’s password. Fortunately, the bundle has a great way to specify which properties to expose and which to exclude:
Using @ExclusionPolicy("all") we set the serializer to exlude everything by default and then we define @Expose on anything that we do want to expose. In this example, the user’s id and name is exposed but not their organisation.
Response is an integral part of a REST API.
In our application, we throw a not found exception when an organisation or user doesn’t exist. This causes a 404 Not Found to be returned by the API.
If our form validation fails, a 400 Bad Request is automatically returned along with any validation error messages.
For get requests, so long as the entity (or entities) is (are) found, we return a 200 OK and a representation of the entity (entities) in our requested format.
When creating a new entity, a 201 Created header is returned with a location header describing where the new entity can be found.
Updates to and deletions of existing entities will result in a 204 No Content header.
The source code for this example is available on github.