Créer et supprimer des ressources

Notre API ne permet pour l’instant que la lecture de données. Une API en lecture seule étant loin d’être courante (ni amusante à développer), nous allons voir comment créer et supprimer une ressource en suivant les principes REST.

Création d'une ressource

Le schéma que nous allons adopter doit maintenant être familier. Plus tôt dans ce cours, nous avions :

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 ?

Nous allons donc suivre ce conseil, et rajouter une action permettant de créer un lieu dans notre application.

Quelle est la ressource cible ?

La première question que nous devons nous poser est sur quelle ressource pouvons-nous faire un appel de création ?

De point de vue sémantique, nous pouvons considérer qu’une entité dans une application est accessible en utilisant la collection (places) ou en utilisant directement la ressource à travers son identifiant (places/1). Mais comme vous vous en doutez, une ressource que nous n’avons pas encore créé ne peut pas avoir d’identifiant.

Il faut donc voire la création d’une ressource comme étant l’ajout de celle-ci dans une collection.

Créer un lieu revient donc à rajouter un lieu à notre liste déjà existante. Pour créer une ressource, il faudra donc utiliser la collection associée.

Quel verbe HTTP ?

Pour identifier notre collection, nous utiliserons l’URL rest-api.local/places. Mais quel appel doit-on faire ? Les verbes HTTP ont chacun une signification et une utilisation bien définie. Pour la création, la méthode POST est bien appropriée. Pour s’en convaincre, il suffit de consulter la RFC 7231 qui dit :

For example, POST is used for the following functions (among others

  • Providing a block of data, such as the fields entered into an HTML form, to a data-handling process;
  • Posting a message to a bulletin board, newsgroup, mailing list, blog, or similar group of articles;
  • Creating a new resource that has yet to be identified by the origin server;

-

POST est utilisé pour les fonctions suivantes (entre autres) :

  • Création d’une nouvelle ressource qui n’a pas encore été identifiée par le serveur d’origine;

Le corps de notre requête

Maintenant que nous savons qu’il faudra une requête du type POST rest-api.local/places, nous allons nous intéresser au corps de notre requête : le payload (dans le jargon API).

Lorsque nous soumettons un formulaire sur une page web avec la méthode POST, le contenu est encodé en utilisant les encodages application/x-www-form-urlencoded ou encore multipart/form-data que vous avez sûrement déjà rencontrés.

Pour le cas d’une API, nous pouvons utiliser le format que nous voulons dans le corps de nos requêtes tant que le serveur supporte ce format. Nous allons donc choisir le JSON comme format.

Ce choix n’est en aucun cas lié au format de sortie de nos réponses. Le JSON reste un format textuel largement utilisé et supporté et représente souvent le minimum à supporter par une API REST. Ceci étant dit, supporter le format JSON n’est pas une contrainte REST.

Quel code de statut HTTP ?

Pour rappels, les codes de statut HTTP peuvent être regroupés par famille. Le premier chiffre permet d’identifier la famille de chaque code. Ainsi les codes de la famille 2XX (200, 201, 204, etc.) décrivent une requête qui s’est effectué avec succès, la famille 4XX (400, 404, etc.) pour une erreur côté client et enfin la famille 5XX (500, etc.) pour une erreur serveur. La liste complète des codes de statut et leur signification est disponible dans la section 6 de la RFC 7231. Mais pour notre cas, une seule nous intéresse: 201 Created. Le message associé à ce code parle de lui-même, si une ressource a été créée avec succès, nous utiliserons donc le code 201.

Cinématique de création d’un lieu

Créer un nouveau lieu

Mettons en pratique tout cela en donnant la possibilité aux utilisateurs de notre API de créer un lieu. Un utilisateur devra faire une requête POST sur l’URL rest-api.local/places avec comme payload :

1
2
3
4
{
    "name": "ici un nom",
    "address": "ici une adresse"
}

Le corps de la requête ne contient pas l’identifiant vu que nous allons le créer côté serveur.

Pour des soucis de clarté, les méthodes déjà existantes dans le contrôleur PlaceController ne seront pas visibles dans les extraits de code. Commençons donc par créer une route et en configurant le routage comme il faut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# src/AppBundle/Controller/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\Post("/places")
     */
    public function postPlacesAction(Request $request)
    {

    }
}

Pour tester la méthode, nous allons tout d’abord simplement renvoyer les informations qui seront dans le payload.

 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
# src/AppBundle/Controller/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\Post("/places")
     */
    public function postPlacesAction(Request $request)
    {
         return [
            'payload' => [
                $request->get('name'),
                $request->get('address')
             ]
        ];
    }
}

Pour tester cette méthode, nous allons utiliser Postman.

Payload pour la création d’un lieu

Il faut choisir comme contenu JSON, Postman rajoutera automatiquement l’entête Content-Type qu’il faut à la requête. Nous explorerons plus en détails ces entêtes plus tard dans ce cours.

Entête rajoutée par Postman

La réponse obtenue est :

Réponse temporaire pour la création d’un lieu

Nous avons maintenant un système opérationnel pour récupérer les informations pour créer notre lieu. Mais avant de continuer, un petit aparté sur FOSRestBundle s’impose.

Le body listener de FOSRestBundle

Il faut savoir que de base, Symfony ne peut pas peupler les paramétres de l’objet Request avec le payload JSON. Dans une application n’utilisant pas FOSRestBundle, il faudrait parser manuellement le contenu en faisant json_decode($request->getContent(), true) pour accéder au nom et à l’adresse du lieu.

Pour s’en convaincre, nous allons désactiver le body listener qui est activé par défaut.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 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', prefer_extension: false }
    # configuration à rajouter pour désactiver le body listener
    body_listener:
        enabled: false

La réponse que nous obtenons est tout autre :

Réponse sans le body listener de FOSRestBundle

En remplaçant, le code actuel par :

1
2
3
4
<?php
return [
    'payload' => json_decode($request->getContent(), true)
];

Nous retrouvons la première réponse.

Là aussi FOSRestBundle nous facilite le travail et tout parait transparent pour nous. Il faut juste garder en tête que ce listener existe et fait la transformation nécessaire pour nous.

Avant de continuer, nous allons le réactiver :

1
2
    body_listener:
        enabled: true

Sauvegarde en base

Maintenant que nous avons les informations nécessaires pour créer un lieu, nous allons juste l’insérer en base avec Doctrine. Pour définir le bon code de statut, il suffit de mettre un paramètre statusCode=Response::HTTP_CREATED dans l’annotation View.

 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
# src/AppBundle/Controller/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(statusCode=Response::HTTP_CREATED)
     * @Rest\Post("/places")
     */
    public function postPlacesAction(Request $request)
    {
        $place = new Place();
        $place->setName($request->get('name'))
            ->setAddress($request->get('address'));

        $em = $this->get('doctrine.orm.entity_manager');
        $em->persist($place);
        $em->flush();

        return $place;
    }
}

Ici, en renvoyant la ressource qui vient d’être créée, nous suivons la RFC 7231.

The 201 response payload typically describes and links to the resource(s) created.

Pour les tester notre implémentation, nous allons utiliser :

1
2
3
4
{
    "name": "Disneyland Paris",
    "address": "77777 Marne-la-Vallée"
}

La réponse renvoyée avec le bon code de statut.

Code de statut de la réponse
Réponse de la création d’un lieu dans Postman

Validation des données

Bien que nous puissions créer avec succès un lieu, nous n’effectuons aucune validation. Dans cette partie, nous allons voir comment valider les informations en utilisant les formulaires de Symfony.

Nous allons commencer par créer un formulaire pour les lieux :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# src/AppBundle/Form/Type/PlaceType.php
<?php
namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class PlaceType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name');
        $builder->add('address');
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\Place',
            'csrf_protection' => false
        ]);
    }
}

Dans une API, il faut obligatoirement désactiver la protection CSRF (Cross-Site Request Forgery). Nous n’utilisons pas de session et l’utilisateur de l’API peut appeler cette méthode sans se soucier de l’état de l’application : l’API doit rester sans état : stateless.

Nous allons maintenant rajouter des contraintes simples pour notre lieu. Le nom et l’adresse ne doivent pas être nulles et en plus, le nom doit être unique. Nous utiliserons le format YAML pour les règles de validations.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Place:
    constraints:
        - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: name
    properties:
        name:
            - NotBlank: ~
            - Type: string
        address:
            - NotBlank: ~
            - Type: string

`

Jusque-là rien de nouveau avec les formulaires Symfony. Si ce code ne vous parait pas assez claire. Il est préférable de consulter la documentation officielle avant de continuer ce cours.

Vu que nous avons une contrainte d’unicité sur le champ name. Il est plus logique de rajouter cela dans les annotations Doctrine.

 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;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 * @ORM\Table(name="places",
 *      uniqueConstraints={@ORM\UniqueConstraint(name="places_name_unique",columns={"name"})}
 * )
 */
class Place
{
    // ...
}

Il ne reste plus qu’à exploiter le formulaire dans notre contrôleur.

 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
# src/AppBundle/Controller/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\Form\Type\PlaceType;
use AppBundle\Entity\Place;

class PlaceController extends Controller
{
     /**
     * @Rest\View(statusCode=Response::HTTP_CREATED)
     * @Rest\Post("/places")
     */
    public function postPlacesAction(Request $request)
    {
        $place = new Place();
        $form = $this->createForm(PlaceType::class, $place);

        $form->submit($request->request->all()); // Validation des données

        if ($form->isValid()) {
            $em = $this->get('doctrine.orm.entity_manager');
            $em->persist($place);
            $em->flush();
            return $place;
        } else {
            return $form;
        }
    }
}

Le format des données attendu lorsqu’on utilise la méthode handleRequest des formulaires Symfony est un peu différent de celui que nous utilisons pour créer un lieu. Avec handleRequest, nous aurions dû utiliser :

1
2
3
4
5
6
7
{
    "place":
        {
            "name": "ici un nom",
            "address": "ici une adresse"
        }
}

Où l’attribut place est le nom de notre formulaire Symfony.

Donc pour mieux répondre aux contraintes REST, au lieu d’utiliser la méthode handleRequest pour soumettre le formulaire, nous avons opté pour la soumission manuelle avec submit. Nous adaptons Symfony à REST et pas l’inverse.

Gestion des erreurs avec FOSRestBundle

Lorsque le formulaire n’est pas valide, nous nous contentons juste de renvoyer le formulaire. Le ViewHandler de FOSRestBundle est conçu pour gérer nativement les formulaires invalides.

Non seulement, il est en mesure de formater les erreurs dans le formulaire mais en plus, il renvoie le bon code de statut lorsque les données soumises sont invalide: 400. Le code de statut 400 permet de signaler au client de l’API que sa requête est invalide.

Pour s’en assurer, essayons de recréer un autre lieu avec les mêmes informations que le précédent.

Code de statut pour un formulaire invalide
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "code": 400,
  "message": "Validation Failed",
  "errors": {
    "children": {
      "name": {
        "errors": [
          "This value is already used."
        ]
      },
      "address": []
    }
  }
}

Si la clé errors d’un attribut existe, alors il y a des erreurs de validation sur cet attribut.

Pratiquons avec les utilisateurs

Comme pour les lieux, nous allons créer une action permettant de rajouter un utilisateur à notre application. Nous aurons comme contraintes :

  • le prénom, le nom et l’adresse mail de l’utilisateur ne doivent pas être nuls ;
  • et l’adresse mail doit être unique.

Pour créer un utilisateur, un client de l’API devra envoyer une requête au format :

1
2
3
4
5
{
    "firstname": "",
    "lastname": "",
    "email": ""
}

Comme pour les lieux, pour créer un utilisateur il faudra une requête POST sur l’URL rest-api.local/users qui désigne notre collection d’utilisateurs.

Allons-y !

Configuration du formulaire et des contraintes de validation :

 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/Form/Type/UserType.php
<?php
namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('firstname');
        $builder->add('lastname');
        $builder->add('email', EmailType::class);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\User',
            'csrf_protection' => false
        ]);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# src/AppBundle/Resources/config/validation.yml

# ...

AppBundle\Entity\User:
    constraints:
        - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: email
    properties:
        firstname:
            - NotBlank: ~
            - Type: string
        lastname:
            - NotBlank: ~
            - Type: string
        email:
            - NotBlank: ~
            - Email: ~

Rajout d’une contrainte d’unicité dans Doctrine :

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

use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity()
* @ORM\Table(name="users",
*      uniqueConstraints={@ORM\UniqueConstraint(name="users_email_unique",columns={"email"})}
* )
*/
class User
{
    // ...
}

Utilisation de notre formulaire dans le contrôleur :

 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\Response;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations as Rest; // alias pour toutes les annotations
use AppBundle\Form\Type\UserType;
use AppBundle\Entity\User;

class UserController extends Controller
{
    // ...  

    /**
     * @Rest\View(statusCode=Response::HTTP_CREATED)
     * @Rest\Post("/users")
     */
    public function postUsersAction(Request $request)
    {
        $user = new User();
        $form = $this->createForm(UserType::class, $user);

        $form->submit($request->request->all());

        if ($form->isValid()) {
            $em = $this->get('doctrine.orm.entity_manager');
            $em->persist($user);
            $em->flush();
            return $user;
        } else {
            return $form;
        }
    }
}

Nous pouvons maintenant créer des utilisateurs grâce à l’API.

Suppression d'une ressource

La suppression d’une ressource reste une action très facile à appréhender. Le protocole HTTP dispose d’une méthode appelée DELETE qui comme son nom l’indique permet de supprimer une ressource. Quant à l’URL de la ressource, il suffit de se poser une seule question :

Que voulons-nous supprimer ?

La méthode DELETE s’appliquera sur la ressource à supprimer. Si par exemple nous voulons supprimer le lieu avec comme identifiant 3, il suffira de faire une requête sur l’URL rest-api.local/places/3.

Une fois n’est pas de coutume, nous allons consulter la RFC 7312

If a DELETE method is successfully applied, the origin server SHOULD send a 202 (Accepted) status code if the action will likely succeed but has not yet been enacted, a 204 (No Content) status code if the action has been enacted and no further information is to be supplied, or a 200 (OK) status code if the action has been enacted and the response message includes a representation describing the status.

Cette citation est bien longue mais ce qui nous intéresse ici se limite à a 204 (No Content) status code if the action has been enacted and no further information is to be supplied. Pour notre cas, lorsque la ressource sera supprimée, nous allons renvoyer aucune information. Le code de statut à utiliser est donc : 204.

Cinématique de suppression d’une ressource

Suppression d’un lieu

Nous allons, sans plus attendre, créer une méthode pour supprimer un lieu de notre application.

 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
# src/AppBundle/Controller/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\Form\Type\PlaceType;
use AppBundle\Entity\Place;

class PlaceController extends Controller
{
    // ...

     /**
     * @Rest\View(statusCode=Response::HTTP_NO_CONTENT)
     * @Rest\Delete("/places/{id}")
     */
    public function removePlaceAction(Request $request)
    {
        $em = $this->get('doctrine.orm.entity_manager');
        $place = $em->getRepository('AppBundle:Place')
                    ->find($request->get('id'));
        /* @var $place Place */

        $em->remove($place);
        $em->flush();
    }
}

Un petit test rapide avec Postman nous donne :

Suppression d’un lieu avec Postman

Le code de statut est aussi correct :

Code de statut pour la suppression d’une ressource

Que faire si nous essayons de supprimer une ressource qui n’existe pas ou plus ?

Si nous essayons de supprimer à nouveau la même ressource, nous obtenons une erreur interne. Mais, il se trouve que dans les spécifications de la méthode DELETE, il est dit que cette méthode doit être idempotente.

Une action idempotente est une action qui produit le même résultat et ce, peu importe le nombre de fois qu’elle est exécutée.

Pour suivre ces spécifications HTTP, nous allons modifier notre code pour gérer le cas où le lieu à supprimer n’existe pas ou plus. En plus, l’objectif d’un client qui fait un appel de suppression est de supprimer une ressource, donc si elle l’est déjà, nous pouvons considérer que tout c’est bien passé.

 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
# src/AppBundle/Controller/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\Form\Type\PlaceType;
use AppBundle\Entity\Place;

class PlaceController extends Controller
{
     /**
     * @Rest\View(statusCode=Response::HTTP_NO_CONTENT)
     * @Rest\Delete("/places/{id}")
     */
    public function removePlaceAction(Request $request)
    {
        $em = $this->get('doctrine.orm.entity_manager');
        $place = $em->getRepository('AppBundle:Place')
                    ->find($request->get('id'));
        /* @var $place Place */

        if ($place) {
            $em->remove($place);
            $em->flush();
        }
    }
}

Pratiquons avec les utilisateurs

Rajoutons une méthode pour supprimer un utilisateur.

 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
# 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\Response;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations as Rest; // alias pour toutes les annotations
use AppBundle\Form\Type\UserType;
use AppBundle\Entity\User;

class UserController extends Controller
{
    // ...

     /**
     * @Rest\View(statusCode=Response::HTTP_NO_CONTENT)
     * @Rest\Delete("/users/{id}")
     */
    public function removeUserAction(Request $request)
    {
        $em = $this->get('doctrine.orm.entity_manager');
        $user = $em->getRepository('AppBundle:User')
                    ->find($request->get('id'));
        /* @var $user User */

        if ($user) {
            $em->remove($user);
            $em->flush();
        }
    }
}

Pour revenir sur nos tableaux récapitulatifs, voici le mode de fonctionnement simplifié d’une API REST :

Opération souhaitée Verbe HTTP
Lecture GET
Création POST
Suppression DELETE
Code statut Signification
200 Tout s’est bien passé et la réponse a du contenu
204 Tout s’est bien passé mais la réponse est vide
400 Les données envoyées par le client sont invalides
404 La ressource demandée n’existe pas
500 Une erreur interne a eu lieu sur le serveur