Symfony how to use FOSRestBundle

In this post, I would like to talk about how to properly build the RESTfull API for AngularJS and other front-end frameworks with a backend on Symfony.
And, as you probably already guessed, I will use the FOSRestBundle - a wonderful bundle that will help us implement the backend.
There will be no examples of how to work with Angular, I will only describe working with Symfony FosRestBundle.

For work, we also need a JMSSerializerBundle for serializing data from Entity to JSON or other formats, excluding some fields for an entity (for example, a password for the API for the method of obtaining a list of users) and much more. You can read more in the documentation.


Installation and configuration
1) Download the required dependencies in our composer.json 2) Configuration

"friendsofsymfony/rest-bundle": "^1.7",
"jms/serializer-bundle": "^1.1"



// app/AppKernel.php
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...
            new JMS\SerializerBundle\JMSSerializerBundle(),
            new FOS\RestBundle\FOSRestBundle(),
        );
        // ...
    }
}


And now we are editing our config.yml.
First, we will configure our FOSRestBundle
fos_rest:
    body_listener: true
    view:
      view_response_listener: true
    serializer:
        serialize_null: true
    body_converter:
        enabled: true
        format_listener:
        rules:
            - { path: '^/api',  priorities: ['json'], fallback_format: json, exception_fallback_format: html, prefer_extension: true }
            - { path: '^/', priorities: [ 'html', '*/*'], fallback_format: html, prefer_extension: true }

body_listener includes EventListener to monitor what format the user response is needed, based on its Accept- * headers
view_response_listener - This setting allows you to simply return the View for a particular query
serializer.serialize_null - This setting tells that we also want so that NULL is serialized, like everything else, if it is not set or set to false, then all fields that are null simply will not be displayed in the server response.
PS: thank you for reminding lowadka
body_converter.rules - it contains an array for settings focused on a particular address, in this example we are for all requests that have the / api prefix, we will return JSON, in all other cases - html.

Now let's start customizing our JMSSerializeBundle
jms_serializer:
    property_naming:
        separator:  _
        lower_case: true
    metadata:
        cache: file
        debug: "%kernel.debug%"
        file_cache:
            dir: "%kernel.cache_dir%/serializer"
        directories:
            FOSUserBundle:
                namespace_prefix: FOS\UserBundle
                path: %kernel.root_dir%/config/serializer/FosUserBundle
            AppBundle:
                namespace_prefix: AppBundle
                path: %kernel.root_dir%/config/serializer/AppBundle
        auto_detection: true


It makes sense to dwell on the moment with jms_serializer.metadata.directories , where we tell the serializer that the configuration for a particular class (entity) is located there or there :)
We agree that we need to display the entire list of users, I personally use FosUserBundle in my projects and here is my essence:
balance = $balance;
        return $this;
    }
    /**
     * Get balance
     *
     * @return integer
     */
    public function getBalance()
    {
        return $this->balance;
    }
}


I give just an example of this entity, which inherits from the main FosUserBundle model. This is important because both classes will have to be configured separately for the JmsSerializerBundle.
So back to jms_serializer.metadata.directories :
directories:
            FOSUserBundle:
                namespace_prefix: FOS\UserBundle
                path: %kernel.root_dir%/config/serializer/FosUserBundle
            AppBundle:
                namespace_prefix: AppBundle
                path: %kernel.root_dir%/config/serializer/AppBundle

Here we just indicate that for the AppBundle classes we will look for the configuration for serialization in app / config / serializer / AppBundle, and for FosUserBundle in app / config / serializer / FosUserBundle.
The configuration for the class will be automatically in the format:
For the class AppBundle \ Entity \ User - app / config / serializer / AppBundle / Entity.User. (Yml | xml | php)
For the class of the base model FosUserBundle - app / config / serializer / FosUserBundle / Model.User. (Yml | xml | php)

Personally, I prefer to use YAML. We will finally begin to tell the JMSSerializer how we need it to configure this or that class.
app / config / serializer / AppBundle / Entity.User.yml
AppBundle\Entity\User:
    exclusion_policy: ALL
    properties:
        balance:
            expose: true


app / config / serializer / FosUserBundle / Model.User.yml
FOS\UserBundle\Model\User:
    exclusion_policy: ALL
    group: user
    properties:
        id:
            expose: true
        username:
            expose: true
        email:
            expose: true
        balance:
            expose: true


Just like that, we were able to talk about what we want to see about the following response format from the server when receiving data from 1 user:
{"id":1,"username":"admin","email":"admin","balance":0}


In principle, this configuration is not necessary to register and the server will return all data about the entity. Only in this case it is illogical for us to show many things, for example, such as a password. Therefore, I considered it necessary to demonstrate just such an implementation in this example.

Now let's start creating the controller.
First, create a route:
backend_user:
    resource: "@BackendUserBundle/Resources/config/routing.yml"
    prefix:   /api

Pay attention to / api - do not forget to add it, but if you want to change, you will have to change the configuration for fos_rest in config.yml.

Now BackendUserBundle / Resources / config / routing.yml itself:
backend_user_users:
  type: rest
  resource: "@BackendUserBundle/Controller/UsersController.php"
  prefix: /v1


Now you can start creating the controller itself:
getDoctrine()->getRepository('AppBundle:User')->findAll();
        $view = $this->view($users, 200);
        return $this->handleView($view);
    }
    /**
     * @param $id
     * @return \Symfony\Component\HttpFoundation\Response
     * @View(serializerGroups={"user"})
     */
    public function getUserAction($id)
    {
        $user = $this->getDoctrine()->getRepository('AppBundle:User')->find($id);
        if (!$user instanceof User) {
            throw new NotFoundHttpException('User not found');
        }
        $view = $this->view($user, 200);
        return $this->handleView($view);
    }
}


Note that we are now inheriting from FOS \ RestBundle \ Controller \ FOSRestController .
By the way, you probably noticed the annotation View (serializerGroups = {"user"}).
The fact is, because we want to see the data of App \ Entity \ User and the main FosUserBundle model, in which all other fields are stored, we need to create a specific group, in this case - “user”.

So, we have 2 actions getUserAction and getUsersAllAction. Now you will understand the essence of the specificity of the names of controller methods.
Let's make debug of all routes:
$ app / console debug: route | grep api
Get:
get_users_all                              GET        ANY      ANY    /api/v1/users/all.{_format}                         
get_user                                      GET        ANY      ANY    /api/v1/users/{id}.{_format} 


Consider the following example with new methods:

Напоминает Laravel Resource Controller, правда?

В комментариях показано по какому адресу и методу запроса будет выполнен тот или иной метод.
В следующий раз я расскажу вам о том, как правильно использовать FOSRestBundle для, например, вывода комментариев определенного пользователя по адресу: "/users/{id}/comments", создавать \ обновлять данные пользователей.

Also popular now: