Tous droits réservés

Premières interactions avec les ressources

Pourquoi parle-t-on tant des ressources ?

Au-delà de connaitre la définition d’une ressource en REST, un des principaux problèmes lorsque nous développons une API est de savoir quelles sont les entités de notre projet qui sont éligibles. Dans cette partie du cours, nous allons donc voir comment bien identifier une ressource avec une expression du besoin plus claire et aussi comment les exposer avec une URI (Uniform Resource Identifier).

Lire une collection

Notre première ressource

Une interface REST gravite autour de ressources. À partir du moment où vous devez interagir avec une entité de votre application, créer une entité, la modifier, la consulter ou encore l’identifier de manière unique, vous avez pour la plupart des cas une ressource.

L’application que nous devons développer enregistre plusieurs lieux (monuments, centres de loisirs, châteaux, etc.) et fait des suggestions de sorties/visites à des utilisateurs.

Dans notre application, nous aurons donc un lieu avec éventuellement les informations permettant de le décrire (nom, adresse, thème, réputation, etc.).

Nous serons surement appelés à le consulter ou à l’éditer. Voici donc notre première ressource : un lieu.

Le choix des ressources dans une API REST est très important mais leur nommage l’est autant car c’est cela qui permettra d’avoir une API cohérente.

Les collections dans REST

A ce stade du cours, la notion de ressource doit être bien comprise. Mais il existe aussi une autre notion qui sera utile dans la conception d’une API REST : les collections.

Une collection désigne simplement un ensemble de ressources d’un même type. Dans notre cas, la liste de tous les lieux référencés dans l’application représente une collection. Et c’est idem pour la liste des utilisateurs.

Le nommage d’une ressource

Une règle d’or à respecter, c’est la cohérence. Il faut choisir des noms de ressources simples et suivre une même logique de nommage. Si par exemple, une ressource est nommée au pluriel alors elle doit l’être sur toute l’API et toutes les ressources doivent être aussi au pluriel. La casse est également très importante pour la cohérence. Il faudra ainsi respecter la même casse pour toutes les ressources.

Pour le cas de notre exemple, toutes nos ressources seront en minuscule, au pluriel et en anglais. C’est un choix assez répandu dans les différentes API publiques à notre disposition.

Donc pour une collection représentant les lieux à visiter, nous aurons places. Dans notre URL, nous aurons alors rest-api.local/places.

Accéder aux lieux déclarés dans l’application

Pour commencer, nous considérons qu’un lieu a un nom et une adresse. L’objectif est d’avoir un appel de notre API permettant d’afficher tous les lieux connus par notre application.

La sémantique HTTP

La ressource avec laquelle nous souhaitons interagir est places. Notre requête HTTP doit donc se faire sur l’URL rest-api.local/places.

Quelle méthode (ou verbe) HTTP utiliser : GET, POST, ou DELETE ?

Comme expliqué dans le modèle de maturité de Ridcharson, une API qui se veut RESTful doit utiliser les méthodes HTTP à bon escient pour interagir avec les ressources. Dans notre cas, nous voulons lire des données disponibles sur le serveur. Le protocole HTTP nous propose la méthode GET qui, selon la RFC 7231, est la méthode de base pour récupérer des informations.

Cinématique de récupération des lieux

Implémentation

Nous allons commencer par mettre en place notre appel API avec de fausses données, ensuite nous mettrons en place la persistance de celles-ci avec Doctrine.

Tout d’abord, il faut créer une entité nommée Place contenant un nom et une adresse :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# src/AppBundle/Entity/Place.php
<?php
namespace AppBundle\Entity;

class Place
{
    public $name;

    public $address;

    public function __construct($name, $address)
    {
        $this->name = $name;
        $this->address = $address;
    }
}

Créons maintenant un nouveau contrôleur nommé PlaceController qui s’occupera de la gestions des lieux avec, pour l’instant, une seule méthode permettant de les lister.

 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
# src/AppBundle/Controller/PlaceController.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 AppBundle\Entity\Place;

class PlaceController extends Controller
{
    /**
     * @Route("/places", name="places_list")
     * @Method({"GET"})
     */
    public function getPlacesAction(Request $request)
    {
        return new JsonResponse([
            new Place("Tour Eiffel", "5 Avenue Anatole France, 75007 Paris"),
            new Place("Mont-Saint-Michel", "50170 Le Mont-Saint-Michel"),
            new Place("Château de Versailles", "Place d'Armes, 78000 Versailles"),
        ]);
    }
}

Un appel de type GET sur l’URL rest-api.local/places permet d’obtenir notre liste de lieux.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[
  {
    "name": "Tour Eiffel",
    "address": "5 Avenue Anatole France, 75007 Paris"
  },
  {
    "name": "Mont-Saint-Michel",
    "address": "50170 Le Mont-Saint-Michel"
  },
  {
    "name": "Château de Versailles",
    "address": "Place d'Armes, 78000 Versailles"
  }
]

Avec Postman :

Récupération des lieux avec Postman

Nous allons maintenant récupérer nos lieux depuis la base de données avec Doctrine. Rajoutons un identifiant aux lieux et mettons en place les annotations sur l’entité 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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# src/AppBundle/Entity/Place.php
<?php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 * @ORM\Table(name="places")
 */
class Place
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    protected $id;

    /**
     * @ORM\Column(type="string")
     */
    protected $name;

    /**
     * @ORM\Column(type="string")
     */
    protected $address;

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getAddress()
    {
        return $this->address;
    }

    public function setId($id)
    {
        $this->id = $id;
        return $this;
    }

    public function setName($name)
    {
        $this->name = $name;
        return $this;
    }

    public function setAddress($address)
    {
        $this->address = $address;
        return $this;
    }
}

Pour des raisons de clarté, nous allons aussi modifier le nom de notre base de données.

1
2
3
4
5
6
7
# app/config/parameters.yml
parameters:
    database_host: 127.0.0.1
    database_port: null
    database_name: rest_api
    database_user: root
    database_password: null

Il ne reste plus qu’à créer la base de données et la table pour stocker les lieux.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
php bin/console doctrine:database:create
# Réponse
# Created database `rest_api` for connection named default

php bin/console doctrine:schema:update --dump-sql --force
# Réponse
CREATE TABLE places (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, address VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DE

Updating database schema...
Database schema updated successfully! "1" query was executed

Le jeu de données de test :

1
INSERT INTO `places` (`id`, `name`, `address`) VALUES (NULL, 'Tour Eiffel', '5 Avenue Anatole France, 75007 Paris'), (NULL, 'Mont-Saint-Michel', '50170 Le Mont-Saint-Michel'), (NULL, 'Château de Versailles', 'Place d''Armes, 78000 Versailles')

Nous disposons maintenant d’une base de données pour gérer les informations de l’application. Il ne reste plus qu’à changer l’implémentation dans notre contrôleur pour charger les données avec Doctrine.

 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
# src/AppBundle/Controller/PlaceController.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 AppBundle\Entity\Place;

class PlaceController extends Controller
{
    /**
     * @Route("/places", name="places_list")
     * @Method({"GET"})
     */
    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);
    }
}

En testant à nouveau notre appel, nous obtenons :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[
  {
    "id": 1,
    "name": "Tour Eiffel",
    "address": "5 Avenue Anatole France, 75007 Paris"
  },
  {
    "id": 2,
    "name": "Mont-Saint-Michel",
    "address": "50170 Le Mont-Saint-Michel"
  },
  {
    "id": 3,
    "name": "Château de Versailles",
    "address": "Place d'Armes, 78000 Versailles"
  }
]

Avec Postman :

Récupération des lieux avec Postman

Pratiquons avec les utilisateurs

Objectif

Maintenant que le principe pour récupérer les informations d’une liste est expliqué, nous allons faire de même avec les utilisateurs. Nous considérerons que les utilisateurs ont un nom, un prénom et une adresse mail et que la ressource pour désigner une liste d’utilisateur est users.

L’objectif est de mettre en place un appel permettant de générer la liste des utilisateurs enregistrés en base. Voici le format de la réponse attendue :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[
  {
    "id": 1,
    "firstname": "Ab",
    "lastname": "Cde",
    "email": "ab.cde@test.local"
  },
  {
    "id": 2,
    "firstname": "Ef",
    "lastname": "Ghi",
    "email": "ef.ghi@test.local"
  }
]

Implémentation

Configuration de doctrine

Comme pour les lieux, nous allons commencer par créer l’entité User et la configuration doctrine qui va avec:

 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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# src/AppBundle/Entity/User.php
<?php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity()
* @ORM\Table(name="users")
*/
class User
{
   /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    protected $id;

    /**
     * @ORM\Column(type="string")
     */
    protected $firstname;

    /**
     * @ORM\Column(type="string")
     */
    protected $lastname;

    /**
     * @ORM\Column(type="string")
     */
    protected $email;

    public function getId()
    {
        return $this->id;
    }

    public function setId($id)
    {
        $this->id = $id;
    }

    public function getFirstname()
    {
        return $this->firstname;
    }

    public function setFirstname($firstname)
    {
        $this->firstname = $firstname;
    }

    public function getLastname()
    {
        return $this->lastname;
    }

    public function setLastname($lastname)
    {
        $this->lastname = $lastname;
    }

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

Mettons à jour la base de données :

1
2
3
4
5
6
php bin/console doctrine:schema:update --dump-sql --force
# Réponse
#> CREATE TABLE users (id INT AUTO_INCREMENT NOT NULL, firstname VARCHAR(255) NOT NULL, lastname VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;

#>Updating database schema...
#>Database schema updated successfully! "1" query was executed

N’oublions pas le jeu de données de test :

1
INSERT INTO `users` (`id`, `firstname`, `lastname`, `email`) VALUES (NULL, 'Ab', 'Cde', 'ab.cde@test.local'), (NULL, 'Ef', 'Ghi', 'ef.ghi@test.local');
Création du contrôleur pour les utilisateurs

Nous allons créer un contrôleur dédié aux utilisateurs. Pour l’instant, nous aurons une seule méthode permettant de les lister.

 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
# src/AppBundle/Controller/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 AppBundle\Entity\User;

class UserController extends Controller
{
    /**
     * @Route("/users", name="users_list")
     * @Method({"GET"})
     */
    public function getUsersAction(Request $request)
    {
        $users = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:User')
                ->findAll();
        /* @var $users User[] */

        $formatted = [];
        foreach ($users as $user) {
            $formatted[] = [
               'id' => $user->getId(),
               'firstname' => $user->getFirstname(),
               'lastname' => $user->getLastname(),
               'email' => $user->getEmail(),
            ];
        }

        return new JsonResponse($formatted);
    }
}

En regardant le code, nous pouvons déjà remarqué que le contrôleur UserController ressemble à quelques lignes prés au contrôleur PlaceController. Vu qu’avec REST nous utilisons une interface uniforme pour interagir avec nos ressources, si l’opération que nous voulons effectuer est identique, il y a de forte chance que le code pour l’implémentation le soit aussi. Cela nous permettra donc de gagner du temps dans les développements.

En testant avec Postman :

Récupération des utilisateurs avec Postman

Lire une ressource

Accéder à un seul lieu

Un peu de conception

Maintenant que nous savons comment accéder à un ensemble de ressource (une collection), comment faire pour récupérer un seul lieu ?

D’un point de vue sémantique HTTP, nous savons que pour lire du contenu, il faut utiliser la méthode GET. Le problème maintenant est de savoir comment identifier la ressource parmi toutes celles dans la collection.

Le point de départ est, en général, le nom de la collection (places pour notre cas). Nous devons donc trouver un moyen permettant d’identifier de manière unique un élément de cette collection. Il a une relation entre la collection et chacune de ses ressources.

Pour le cas des lieux, nous pouvons choisir l’identifiant auto-incrémenté pour désigner de manière unique un lieu. Nous pourrons dire alors que l’identifiant 1 désigne la ressource Tour Eiffel.

Pour la représenter dans une URL, nous avons deux choix :

  • rest-api.local/places?id=1
  • rest-api.local/places/1

On pourrait être tenté par la première méthode utilisant le query string id. Mais la RFC 3986 spécifie clairement les query strings comme étant des composants qui contiennent des données non-hiérarchiques. Pour notre cas, il y a une relation hiérarchique claire entre une collection et une de ses ressources. Donc cette méthode est à proscrire.

Notre URL pour désigner un seul lieu sera alors rest-api.local/places/1. Et pour généraliser, pour accéder à un lieu, on aura rest-api.local/places/{place_id}{place_id} désigne l’identifiant de notre lieu.

Implémentation

Mettons maintenant en œuvre un nouvel appel permettant de récupérer un lieu. Nous allons utiliser le 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
# src/AppBundle/Controller/PlaceController.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 AppBundle\Entity\Place;

class PlaceController extends Controller
{

    // code de getPlacesAction

    /**
     * @Route("/places/{place_id}", name="places_one")
     * @Method({"GET"})
     */
    public function getPlaceAction(Request $request)
    {
        $place = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:Place')
                ->find($request->get('place_id'));
        /* @var $place Place */

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

        return new JsonResponse($formatted);
    }
}

Cette action est particulièrement simple et se passe de commentaires. Ce qu’il faut retenir c’est que la méthode renvoie une seule entité et pas une liste.

En testant, nous avons comme réponse :

1
2
3
4
5
{
  "id": 1,
  "name": "Tour Eiffel",
  "address": "5 Avenue Anatole France, 75007 Paris"
}
Récupération d’un lieu avec Postman

Nous pouvons rendre la configuration de la route plus stricte en utilisant l’attribut requirements de l’annotation Route. Puisque les identifiants des lieux sont des entiers, la déclaration de la route pourrait être @Route("/places/{place_id}", requirements={"place_id" = "\d+"}, name="places_one").

Pratiquons avec les utilisateurs

Bis repetita, nous allons mettre en place une méthode permettant de récupérer les informations d’un seul utilisateur.

Comme pour les lieux, pour récupérer un utilisateur, il suffit de créer un nouvel appel GET sur l’URL rest-api.local/users/{id}{id} désigne l’identifiant de l’utilisateur.

Pour cela, éditons le contrôleur UserController pour rajouter cette méthode.

 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
# src/AppBundle/Controller/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 AppBundle\Entity\User;

class UserController extends Controller
{

     // code de getUsersAction

    /**
     * @Route("/users/{id}", name="users_one")
     * @Method({"GET"})
     */
    public function getUserAction(Request $request)
    {
        $user = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:User')
                ->find($request->get('id'));
        /* @var $user User */

        $formatted = [
           'id' => $user->getId(),
           'firstname' => $user->getFirstname(),
           'lastname' => $user->getLastname(),
           'email' => $user->getEmail(),
        ];

        return new JsonResponse($formatted);
    }
}

`

Nous obtenons une belle réponse JSON :

1
2
3
4
5
6
{
  "id": 1,
  "firstname": "Ab",
  "lastname": "Cde",
  "email": "ab.cde@test.local"
}
Récupération d’un utilisateur

Les codes de statut (status code) pour des messages plus expressifs

Quel code de statut utilisé ?

Que se passe-t-il si nous essayons de récupérer un lieu inexistant ?

Vous remarquerez qu’avec le code actuel si le lieu recherché n’existe pas (par exemple rest-api.local/places/42), nous avons une belle erreur nous signifiant que la méthode getId ne peut être appelée sur l’objet null (Fatal error: Call to a member function getId() on null) et le code de statut de la réponse est une erreur 500.

Récupération d’un lieu inexistant

Ce comportement ne respecte pas la sémantique HTTP. En effet dans n’importe quel site, si vous essayez d’accéder à une page inexistante, vous recevez la fameuse erreur 404 Not Found qui signifie que la ressource n’existe pas. Pour que notre API soit le plus RESTful possible, nous devons implémenter un comportement similaire.

Nous ne devons avoir une erreur 500 que dans le cas d’une erreur interne du serveur. Par exemple, s’il est impossible de se connecter à la base de données, il est légitime de renvoyer une erreur 500.

De la même façon, lorsque la ressource est trouvée, nous devons renvoyer un code 200 pour signifier que tout s’est bien passé. Par chance, ce code est le code par défaut lorsqu’on utilise l’objet JsonResponse de Symfony. Nous avons donc déjà ce comportement en place.

Cinématique de récupération des lieux avec le code de statut

Gérer une erreur 404

Pour notre cas, il est facile de gérer ce type d’erreurs. Nous devons juste vérifier que la réponse du repository n’est pas nulle. Au cas contraire, il faudra renvoyer une erreur 404 avec éventuellement un message détaillant le problème.

Pour un lieu, nous aurons donc :

 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
# src/AppBundle/Controller/PlaceController.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 AppBundle\Entity\Place;

class PlaceController extends Controller
{
    // ...

    /**
     * @Route("/places/{place_id}", name="places_one")
     * @Method({"GET"})
     */
    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);
    }
}

Maintenant, une requête GET sur l’URL rest-api.local/places/42 nous renvoie une erreur 404 avec un message bien formaté en JSON. La constante Response::HTTP_NOT_FOUND vaut 404 et est une constante propre à Symfony.

La réponse contient un message en JSON :

1
2
3
{
  "message": "Place not found"
}
Récupération d’un lieu inexistant avec Postman

Pour un utilisateur, les modifications à effectuer restent identiques :

 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
# src/AppBundle/Controller/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 AppBundle\Entity\User;

class UserController extends Controller
{
    // ...

    /**
     * @Route("/users/{id}", name="users_one")
     * @Method({"GET"})
     */
    public function getUserAction(Request $request)
    {
        $user = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:User')
                ->find($request->get('id'));
        /* @var $user User */

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

        $formatted = [
           'id' => $user->getId(),
           'firstname' => $user->getFirstname(),
           'lastname' => $user->getLastname(),
           'email' => $user->getEmail(),
        ];

        return new JsonResponse($formatted);
    }
}

Avec ces modifications, nous avons maintenant une gestion des erreurs propres et l’API respecte au mieux la sémantique HTTP.


Après cette première introduction, nous pouvons retenir qu’en REST les interactions ont lieu avec soit une collection soit une instance de celle-ci : une ressource.

Chaque opération peut alors être décrite comme étant une requête sur une URL bien identifiée avec un verbe HTTP adéquat. Le type de la réponse est décrit par un code de statut.

Voici un petit récapitulatif du mode de fonctionnement :

Opération souhaitée Verbe HTTP
Lecture GET
Code statut Signification
200 Tout s’est bien passé
404 La ressource demandée n’existe pas
500 Une erreur interne a eu lieu sur le serveur

En résumé, chaque verbe est destiné à une action et la réponse est décrite en plus des données explicitées par un code de statut.

Pour concevoir une bonne API RESTful, il faut donc toujours se poser ces questions :

  • Sur quelle ressource mon opération doit s’effectuer ?
  • Quel verbe HTTP décrit le mieux cette opération ?
  • Quelle URL permet d’identifier la ressource ?
  • Et quel code de statut doit décrire la réponse ?