Tous droits réservés

FOSRestBundle et Symfony à la rescousse

Force est de constater que le code dans nos contrôleurs est assez répétitifs. Toutes les réponses sont en JSON via l’objet JsonResponse, la logique de formatage de celles-ci est dupliqué et toutes les routes suivent un même modèle.

Nous avons là un schéma classique de code facilement factorisable et justement Symfony nous propose beaucoup d’outils via les composants et les bundles afin de gérer ce genre de tâches courantes et/ou répétitifs.

Nous allons donc utiliser les avantages qu’offre le framework Symfony à travers le bundle FOSRestBundle afin de mieux gérer les problématiques d’implémentation liées au contrainte REST et gagner ainsi en productivité.

Installation de FOSRestBundle

Comme pour tous les bundles de Symfony, la méthode la plus simple pour l’installateur est d’utiliser le gestionnaire de dépendances Composer. Pour les besoins du cours, nous allons installer la version ^2.1 (2.1.0 pour mon cas) qui apporte un support plus complet de Symfony 3. Depuis la console, il suffit de lancer la commande :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
composer require friendsofsymfony/rest-bundle "^2.1"

# Réponse
#> ./composer.json has been updated
#> Loading composer repositories with package informatio
#> Updating dependencies (including require-dev)
#>   - Installing willdurand/jsonp-callback-validator (v
#>     Downloading: 100%
#> 
#>   - Installing willdurand/negotiation (1.5.0)
#>     Downloading: 100%
#> 
#>   - Installing friendsofsymfony/rest-bundle (2.1.0)
#>     Downloading: 100%

`

Ensuite, il suffit d’activer le bundle dans Symfony en éditant le fichier AppKernel.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# app/AppKernel.php
<?php

use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = [
            // ... D'autres bundles déjà présents
            new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
            new FOS\RestBundle\FOSRestBundle(),
            new AppBundle\AppBundle(),
        ];
        // ...
    }

    // ...
}

À l’état actuel, l’installation n’est pas encore complète. Si nous lançons la commande php bin/console debug:config fos_rest une belle exception est affichée.

1
2
3
 [InvalidArgumentException]
  Neither a service called "jms_serializer.serializer" nor "serializer" is available and no serializer is explicitly configured. You must either enable the JMSSerializerBundle, enable the Framework
  Bundle serializer or configure a custom serializer.

En effet, pour traiter les réponses, ce bundle a besoin d’un outil de sérialisation.

La sérialisation est un processus permettant de convertir des données (une instance d’une classe, un tableau, etc.) en un format prédéfini. Pour le cas de notre API, la sérialisation est le mécanisme par lequel nos objets PHP seront transformés en un format textuel (JSON, XML, etc.).

Heureusement pour nous, l’installation standard de Symfony contient un composant de sérialisation que nous pouvons utiliser.

Par ailleurs, FOSRestBundle supporte le sérialiseur fourni par le bundle JMSSerializerBundle qui fournit plus de possibilités.

Mais pour nos besoins, le sérialiseur standard suffira largement. Nous allons donc l’activer en modifiant la configuration de base dans le fichier app/config/config.yml.

1
2
3
4
5
# app/config/config.yml
framework:
    # ...
    serializer:
        enabled: true

Maintenant en retapant la commande php bin/console debug:config fos_rest, nous obtenons :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
php bin/console debug:config fos_rest
Current configuration for extension with alias "fos_rest"
=========================================================

fos_rest:
    disable_csrf_role: null
    access_denied_listener:
        enabled: false
        service: null
        formats: {  }
    unauthorized_challenge: null
    param_fetcher_listener:
        enabled: false
    ...

Et voilà !

Le bundle FOSRestBundle fournit un ensemble de fonctionnalités permettant de développer une API REST. Nous allons en explorer une bonne partie tout au long de ce cours. Mais commençons d’abord par le système de routage et de gestion des réponses.

Routage avec FOSRestBundle

Le système de routage avec ce bundle est assez complet et facile à prendre en main. Il existe un système basé sur des conventions de nommages des méthodes et un autre basé sur des annotations.

Routage automatique

Afin de bien voir les effets de nos modifications, nous allons d’abord afficher les routes existantes avec la commande php bin/console debug:router.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
php bin/console debug:router
 -------------------------- -------- -------- ------ -----------------------------------
  Name                       Method   Scheme   Host   Path
 -------------------------- -------- -------- ------ -----------------------------------
  _wdt                       ANY      ANY      ANY    /_wdt/{token}
  _profiler_home             ANY      ANY      ANY    /_profiler/
  ...
  _twig_error_test           ANY      ANY      ANY    /_error/{code}.{_format}
  homepage                   ANY      ANY      ANY    /
  tests_list                 GET      ANY      ANY    /tests
  places_list                GET      ANY      ANY    /places
  places_one                 GET      ANY      ANY    /places/{place_id}
  users_list                 GET      ANY      ANY    /users
  users_one                  GET      ANY      ANY    /users/{user_id}
 -------------------------- -------- -------- ------ -----------------------------------

Les routes qui nous intéressent ici sont au nombre de 4 :

  • GET /places
  • GET /places/{place_id}
  • GET /users
  • GET /users/{user_id}

FOSRestBundle nous permet d’obtenir le même résultat avec beaucoup moins de code. Nous allons donc commencer par supprimer toutes les annotations dans notre contrôleur PlaceController.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# src/AppBundle/Controller/PlaceController.php
<?php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Entity\Place;

class PlaceController extends Controller
{

    public function getPlacesAction(Request $request)
    {
        $places = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:Place')
                ->findAll();
        /* @var $places Place[] */

        $formatted = [];
        foreach ($places as $place) {
            $formatted[] = [
               'id' => $place->getId(),
               'name' => $place->getName(),
               'address' => $place->getAddress(),
            ];
        }

        return new JsonResponse($formatted);
    }

    public function getPlaceAction(Request $request)
    {
        $place = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:Place')
                ->find($request->get('place_id'));
        /* @var $place Place */

        if (empty($place)) {
            return new JsonResponse(['message' => 'Place not found'], Response::HTTP_NOT_FOUND);
        }

        $formatted = [
           'id' => $place->getId(),
           'name' => $place->getName(),
           'address' => $place->getAddress(),
        ];

        return new JsonResponse($formatted);
    }
}

En relançant la commande php bin/console debug:router, nous voyons maintenant qu’il n’existe aucune route pour les lieux. Nous allons donc configurer Symfony pour que FOSRestBundle s’occupe du routage. Les routes seront directement déclarées dans app/config/routing.yml. FOSRestBundle introduit un RouteLoader qui supporte les routes de type rest. C’est donc la seule nouveauté dans la configuration des routes dans Symfony.

1
2
3
4
5
6
7
8
# app/config/routing.yml
app:
    resource: "@AppBundle/Controller/DefaultController.php"
    type:     annotation

places:
    type:     rest
    resource: AppBundle\Controller\PlaceController

Dans la clé app, la déclaration a été changée pour dire à Symfony de ne plus charger nos contrôleurs REST, la clé app.resource passe ainsi de @AppBundle/Controller à @AppBundle/Controller/DefaultController.php.

Nous pouvons constater avec la commande php bin/console debug:router que deux routes ont été générées pour les lieux :

  • get_places /places.{_format}
  • get_place /place.{_format}

Nous reviendrons plus tard sur la présence de l’attribut _format dans la route.

Il suffit de tester les nouvelles routes générées pour nous rendre compte que le fonctionnement de l’application reste entièrement le même.

Mais comment FOSRestBundle génère-t-il nos routes ?

Tout le secret réside dans des conventions de nommage. Les noms que nous avons utilisé pour le contrôleur et les actions permettent de générer des routes RESTful sans efforts de notre part.

Ainsi, le nom du contrôleur sans le suffixe Controller permet d’identifier le nom de notre ressource. PlaceController permet de désigner la ressource places. Il faut noter aussi que si le contrôleur s’appelait PlacesController (avec un « s »), la ressource serait aussi places. Ce nom constitue donc le début de notre URL.

Ensuite, pour le reste de l’URL et surtout le verbe HTTP, FOSRestBundle se base sur le nom de la méthode. La méthode getPlacesAction peut être vu en deux parties : get qui désigne le verbe HTTP à utiliser GET, et Places au pluriel qui correspond exactement au même nom que notre ressource.

Cette méthode dit donc à FOSRestBundle que nous voulons récupérer la collection de lieux de notre application qui le traduit en REST par GET /places.

Le paramètre Request $request est propre à Symfony et donc est ignoré par FOSRestBundle.

De la même façon, la méthode getPlaceAction (sans un ­­­« s » à « Place ») dit à FOSRestBundle que nous voulons récupérer un seul lieu.

Mais la différence ici réside dans le fait que nous avons besoin d’un paramètre pour identifier le lieu que nous voulons récupérer. Pour que la route générée soit correcte, il est obligatoire de rajouter un paramètre identifiant la ressource.

La signature de la méthode devient alors :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# src/AppBunble/PlaceController.php
<?php
public function getPlaceAction($id, Request $request)
    {
        $place = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:Place')
                ->find($id); // L'identifiant est utilisé directement
        /* @var $place Place */
        // ...

        return new JsonResponse($formatted);
    }

Les nouvelles routes deviennent :

  • get_places GET /places.{_format} qui permet de récupérer tous les lieux de l’application (get_places est le nom de la route générée) ;
  • get_place GET /places/{id}.{_format} qui permet de récupérer un seul lieu de l’application (get_place est le nom de la route générée).

Nous retrouvons deux routes totalement opérationnelles. En suivant cet ensemble de normes, les routes sont alors générées automatiquement avec les bonnes URL, les bons paramètres et les bons verbes HTTP.

Routage manuel

Bien que très pratique, le routage automatique peut rapidement montrer ses limites. D’abord, il nous impose des règles de nommage pour nos méthodes. Si nous voulons nommer autrement nos actions dans le contrôleur, nous faisons face à une limitation vu que les URL et les verbes HTTP peuvent être impactés. Ensuite, pour avoir des routes correctes, il faudra connaitre l’ensemble des règles de nommage qu’utilise FOSTRestBundle, ce qui est loin d’être évident.

Heureusement, nous avons à disposition une méthode manuelle permettant de définir nos routes facilement.

L’avantage du routage manuel réside dans le fait qu’il se rapproche au plus du système de routage natif de Symfony avec SensioFrameworkExtraBundle et permet donc de moins se perdre en tant que débutant. En plus, les annotations permettant de déclarer les routes sont plus lisibles.

FOSRestBundle propose donc plusieurs annotations de routage :

  • FOS\RestBundle\Controller\Annotations\Get;
  • FOS\RestBundle\Controller\Annotations\Head;
  • FOS\RestBundle\Controller\Annotations\Put;
  • FOS\RestBundle\Controller\Annotations\Delete;
  • FOS\RestBundle\Controller\Annotations\Post;
  • FOS\RestBundle\Controller\Annotations\Patch;

Chacune de ces annotations désigne une méthode HTTP et prend exactement les mêmes paramètres que l’annotation Route que nous avions déjà utilisée.

Pour l’appliquer dans le cas du contrôleur PlaceController, nous aurons :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# src/AppBunble/PlaceController.php
<?php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use FOS\RestBundle\Controller\Annotations\Get; // N'oublons pas d'inclure Get
use AppBundle\Entity\Place;

class PlaceController extends Controller
{

    /**
     * @Get("/places")
     */
    public function getPlacesAction(Request $request)
    {
        // ...
    }

    /**
     * @Get("/places/{id}")
     */
    public function getPlaceAction(Request $request)
    {
        $place = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:Place')
                ->find($request->get('id')); // L'identifiant en tant que paramétre n'est plus nécessaire
        // ...
    }
}

Les nouvelles routes restent inchangées :

  • get_places GET /places.{_format}
  • get_place GET /places/{id}.{_format}

Si une de ces annotations est utilisée sur une action du contrôleur, le système de routage automatique abordé précédemment n’est plus utilisable sur cette même action.

Quid de l'attribut _format ?

Dans chacune des routes générées, nous avons un attribut _format qui apparaît. FOSRestBundle introduit automatiquement ce paramètre afin de gérer le format des réponses. Vu que pour notre cas nous forçons toujours une réponse JSON, les URL rest-api.local/places, rest-api.local/places.json, rest-api.local/places.nimportequoi correspondent toutes à la même route et renvoient du JSON.

Pour gérer plusieurs formats de réponse, HTTP propose une solution plus élégante avec l’entête Accept que nous aborderons plus tard. Nous allons donc désactiver l’ajout automatique de cet attribut en reconfigurant FOSRestBundle.

Il faut rajouter une entrée dans le fichier de configuration :

1
2
3
4
5
6
7
# app/config/config.yml

# ...

fos_rest:
    routing_loader:
        include_format: false

Si nous relançons php bin/console debug:config fos_rest, le format n’est plus présent dans les routes :

  • get_places GET /places
  • get_place GET /places/{id}

Pratiquons en redéfinissant les routes du contrôleur UserController avec les annotations de FOSRestBundle.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# src/AppBunble/UserController.php
<?php
namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use FOS\RestBundle\Controller\Annotations\Get;
use AppBundle\Entity\User;

class UserController extends Controller
{
    /**
     * @Get("/users")
     */
    public function getUsersAction(Request $request)
    {
        // ...
    }

    /**
     * @Get("/users/{user_id}")
     */
    public function getUserAction(Request $request)
    {
        // ...
    }
}

`

Et n’oublions pas de déclarer dans notre fichier de routage :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# app/config/routing.yml
app:
    resource: "@AppBundle/Controller/DefaultController.php"
    type:     annotation

places:
    type:     rest
    resource: AppBundle\Controller\PlaceController

users:
    type:     rest
    resource: AppBundle\Controller\UserController

Voyons maintenant les outils que ce bundle nous propose pour la gestion des vues.

Gestion des réponses avec FOSRestBundle

Configuration du gestionnaire de vue

Avec FOSRestBundle, nous disposons d’un service appelé fos_rest.view_handler qui nous permet de gérer nos réponses. Pour l’utiliser, il suffit d’instancier une vue FOSRestBundle, la configurer et laisser le gestionnaire de vue (le view handler) s’occuper du reste. Voyez par vous-même :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# src/AppBunble/PlaceController.php
<?php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use FOS\RestBundle\Controller\Annotations\Get;
use FOS\RestBundle\View\ViewHandler;
use FOS\RestBundle\View\View; // Utilisation de la vue de FOSRestBundle
use AppBundle\Entity\Place;

class PlaceController extends Controller
{

    /**
     * @Get("/places")
     */
    public function getPlacesAction(Request $request)
    {
        $places = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:Place')
                ->findAll();
        /* @var $places Place[] */

        $formatted = [];
        foreach ($places as $place) {
            $formatted[] = [
               'id' => $place->getId(),
               'name' => $place->getName(),
               'address' => $place->getAddress(),
            ];
        }

         // Récupération du view handler
        $viewHandler = $this->get('fos_rest.view_handler');

        // Création d'une vue FOSRestBundle
        $view = View::create($formatted);
        $view->setFormat('json');

        // Gestion de la réponse
        return $viewHandler->handle($view);
    }
}

L’intérêt d’utiliser un bundle réside aussi dans le fait de réduire les lignes de codes que nous avons à écrire (et par la même occasion, les sources de bogues). N’hésitez pas à retester notre appel afin de vérifier que la réponse est toujours la même.

FOSRestBundle introduit aussi un listener (ViewResponseListener) qui nous permet, à l’instar de Symfony via l’annotation Template du SensioFrameworkExtraBundle, de renvoyer juste une instance de View et laisser le bundle appelait le gestionnaire de vue lui-même.

Pour utiliser l’annotation View, il faut que le SensioFrameworkExtraBundle soit activé. Mais si vous avez utilisé l’installateur de Symfony pour créer ce projet, c’est déjà le cas.

Nous allons donc activer le listener en modifiant notre configuration :

1
2
3
4
5
6
# app/config/config.yml
fos_rest:
    routing_loader:
        include_format: false
    view:
        view_response_listener: true

Ensuite, il ne reste plus qu’à adapter le code (toutes les annotations de FOSRestBundle seront aliasées par Rest):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# src/AppBunble/PlaceController.php
<?php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use FOS\RestBundle\Controller\Annotations as Rest; // alias pour toutes les annotations
use FOS\RestBundle\View\ViewHandler;
use FOS\RestBundle\View\View; // Utilisation de la vue de FOSRestBundle
use AppBundle\Entity\Place;

class PlaceController extends Controller
{

    /**
     * @Rest\View()
     * @Rest\Get("/places")
     */
    public function getPlacesAction(Request $request)
    {
        $places = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:Place')
                ->findAll();
        /* @var $places Place[] */

        $formatted = [];
        foreach ($places as $place) {
            $formatted[] = [
               'id' => $place->getId(),
               'name' => $place->getName(),
               'address' => $place->getAddress(),
            ];
        }

        // Création d'une vue FOSRestBundle
        $view = View::create($formatted);
        $view->setFormat('json');

        return $view;
    }
}

La simplicité qu’apporte ce bundle ne s’arrête pas là. Les données assignées à la vue sont sérialisées au bon format en utilisant le sérialiseur que nous avions configuré au début. Ce sérialiseur supporte aussi bien les tableaux que les objets. Si vous voulez approfondir le sujet, il est préférable de consulter la documentation complète.

Ce qu’il faut retenir dans notre cas, c’est qu’avec nos objets actuels (accesseurs en visibilité public), le sérialiseur de Symfony peut les transformer pour nous. Au lieu de passer un tableau formaté par nos soins, nous allons passer directement une liste d’objets au view handler. Notre code peut être réduit à :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# src/AppBunble/PlaceController.php
<?php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use FOS\RestBundle\Controller\Annotations as Rest; // alias pour toutes les annotations
use FOS\RestBundle\View\ViewHandler;
use FOS\RestBundle\View\View; // Utilisation de la vue de FOSRestBundle
use AppBundle\Entity\Place;

class PlaceController extends Controller
{

    /**
     * @Rest\View()
     * @Rest\Get("/places")
     */
    public function getPlacesAction(Request $request)
    {
        $places = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:Place')
                ->findAll();
        /* @var $places Place[] */

        // Création d'une vue FOSRestBundle
        $view = View::create($places);
        $view->setFormat('json');

        return $view;
    }
}

Et là, nous voyons vraiment l’intérêt d’utiliser les composants que nous propose le framework. L’objectif est d’être le plus concis et productif possible.

La cerise sur le gâteau : Format automatique et réponse sans l’objet View

Pour l’instant, notre API ne supporte qu’un seul format : le JSON. Donc au lieu de le mettre dans tous les contrôleurs, FOSRestBundle propose un mécanisme permettant de gérer les formats et la négociation de contenu : le format listener.

Il y aura un chapitre dédié à la gestion de plusieurs formats et la négociation de contenu.

Pour l’instant, nous allons juste configurer le format listener de FOSRestBundle pour que toutes les URL renvoient du JSON.

1
2
3
4
5
6
7
8
9
# src/app/config/config.yml
fos_rest:
    routing_loader:
        include_format: false
    view:
        view_response_listener: true
    format_listener:
        rules:
            - { path: '^/', priorities: ['json'], fallback_format: 'json' }

La seule règle déclarée dit que pour toutes les URL (path: ^/), le format prioritaire est le JSON (priorities: ['json']) et si aucun format n’est demandé par le client, il faudra utiliser le JSON quand même (fallback_format: 'json').

Vu que maintenant nous n’avons plus à définir le format dans les actions de nos contrôleurs, nous avons même la possibilité de renvoyer directement nos objets sans utiliser l’objet View de FOSRestBundle.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# src/AppBunble/PlaceController.php
<?php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use FOS\RestBundle\Controller\Annotations as Rest; // alias pour toutes les annotations
use AppBundle\Entity\Place;

class PlaceController extends Controller
{

    /**
     * @Rest\View()
     * @Rest\Get("/places")
     */
    public function getPlacesAction(Request $request)
    {
        $places = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:Place')
                ->findAll();
        /* @var $places Place[] */

        return $places;
    }
}

Un dernier test juste pour la forme :

Récupération des lieux avec Postman

Pratiquons avec notre code

Maintenant que nous pouvons produire plus en écrivant moins de lignes de code, nous allons transformer toutes nos actions à l’image de getPlacesAction.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# src/AppBunble/PlaceController.php
<?php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use FOS\RestBundle\Controller\Annotations as Rest; // alias pour toutes les annotations
use AppBundle\Entity\Place;

class PlaceController extends Controller
{

    /**
     * @Rest\View()
     * @Rest\Get("/places")
     */
    public function getPlacesAction(Request $request)
    {
        $places = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:Place')
                ->findAll();
        /* @var $places Place[] */

        return $places;
    }

    /**
     * @Rest\View()
     * @Rest\Get("/places/{id}")
     */
    public function getPlaceAction(Request $request)
    {
        $place = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:Place')
                ->find($request->get('id')); // L'identifiant en tant que paramétre n'est plus nécessaire
        /* @var $place Place */

        if (empty($place)) {
            return new JsonResponse(['message' => 'Place not found'], Response::HTTP_NOT_FOUND);
        }

        return $place;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# src/AppBunble/UserController.php
<?php
namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations as Rest; // alias pour toutes les annotations
use AppBundle\Entity\User;

class UserController extends Controller
{
    /**
     * @Rest\View()
     * @Rest\Get("/users")
     */
    public function getUsersAction(Request $request)
    {
        $users = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:User')
                ->findAll();
        /* @var $users User[] */

        return $users;
    }

    /**
     * @Rest\View()
     * @Rest\Get("/users/{user_id}")
     */
    public function getUserAction(Request $request)
    {
        $user = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:User')
                ->find($request->get('user_id'));
        /* @var $user User */

        if (empty($user)) {
            return new JsonResponse(['message' => 'User not found'], Response::HTTP_NOT_FOUND);
        }

        return $user;
    }
}

FOSRestBundle est l’un des bundles les plus connus pour faire une API REST avec Symfony. Bien qu’ayant abordé pas mal de points dans cette partie du cours, il reste encore beaucoup de fonctionnalités à découvrir et durant ce cours une bonne partie sera présentée. Mais la référence reste la documentation officielle qui vous sera d’une grande aide dans vos futurs développements.

Pour le reste du cours, nous utiliserons ce bundle pour faciliter le travail et ne pas réinventer la roue. Le routage et la gestion des réponses seront calqués sur les cas que nous venons de voir.