Tous droits réservés

Créez une API REST avec Symfony 3

Tout au long de ce cours, nous allons apprendre à mettre en œuvre les principes de REST pour concevoir et développer une API web avec Symfony 3.

Dernière mise à jour :
Auteur :
Catégories :
Temps de lecture estimé : 15 heures

REST s’est imposé dans le monde du web comme étant un paradigme approuvé et éprouvé pour concevoir des APIs (Application Programming Interface).

De grandes entreprises comme Github, Facebook (Graph) ou YouTube l’utilisent pour fournir des APIs largement utilisées pour accéder à leurs services.

À l’ère des sites web en Single Page Applications et des applications mobiles (Android, IOS ou encore Windows Phone), savoir développer une API est devenu incontournable.

Pourquoi utiliser REST plutôt qu’une autre technologie ou architecture ? Quels avantages cela peut-il nous apporter ? Comment développer une API REST avec Symfony ?

Tout au long de ce cours, nous allons apprendre à mettre en œuvre les principes de REST pour développer rapidement une application web fiable et extensible avec le framework Symfony et l’un de ses bundles phares FOSRestBundle.

Un tour d'horizon des concepts REST

  1. REST en quelques mots
  2. Pourquoi utiliser REST

Développement de l'API REST

  1. Notre environnement de développement

    1. Environnement technique

    2. Création d'un projet Symfony

  2. Premières interactions avec les ressources

    1. Lire une collection

    2. Lire une ressource

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

  3. FOSRestBundle et Symfony à la rescousse

    1. Installation de FOSRestBundle

    2. Routage avec FOSRestBundle

    3. Quid de l'attribut _format ?

    4. Gestion des réponses avec FOSRestBundle

    5. Pratiquons avec notre code

  4. Créer et supprimer des ressources

    1. Création d'une ressource

    2. Suppression d'une ressource

  5. Mettre à jour des ressources

    1. Mise à jour complète d'une ressource

    2. Mise à jour partielle d'une ressource

    3. Notre application vu selon le modèle de Richardson

  6. Relations entre ressources

    1. Hiérarchie entre ressources : la notion de sous-ressources

    2. Les groupes avec le sérialiseur de Symfony

    3. Mise à jour de la suppression d'une ressource

  7. TP : Le clou du spectacle - Proposer des suggestions aux utilisateurs

    1. Énoncé

    2. Détails de l'implémentation

    3. Travail préparatoire

    4. Proposer des suggestions aux utilisateurs

  8. REST à son paroxysme

    1. Supporter plusieurs formats de requêtes et de réponses

    2. L'Hypermédia

Amélioration de l'API REST

  1. Sécurisation de l'API 1/2

    1. Connexion et déconnexion avec une API

    2. Login et mot de passe pour les utilisateurs

    3. Création d'un token

  2. Sécurisation de l'API 2/2

    1. Exploitons le token grâce à Symfony

    2. Gestion des erreurs avec FOSRestBundle

    3. 401 ou 403, quand faut-il utiliser ces codes de statut ?

    4. Suppression d'un token ou la déconnexion

  3. Créer une ressource avec des relations

    1. Rappel de l'existant

    2. Création d'un lieu avec des tarifs

    3. Bonus : Une validation plus stricte

  4. Quand utiliser les query strings ?

    1. Pourquoi utiliser les query strings ?

    2. Gestion des query strings avec FOSRestBundle

    3. Paginer et Trier les réponses

  5. JMSSerializer : Une alternative au sérialiseur natif de Symfony

    1. Pourquoi utiliser JMSSerializerBundle ?

    2. Installation et configuration de JMSSerializerBundle

    3. Impact sur l'existant

  6. La documentation avec OpenAPI (Swagger RESTFul API)

    1. Qu'est-ce que OpenAPI ?

    2. Rédaction de la documentation

    3. Installer et utiliser Swagger UI

  7. Automatiser la documentation avec NelmioApiDocBundle

    1. Installation de NelmioApiDocBundle

    2. L'annotation ApiDoc

    3. Étendre NelmioApiDocBundle

    4. Le bac à sable

    5. Générer une documentation compatible OpenAPI

  8. FAQ

    1. Comment générer des pages HTML depuis l'application Symfony 3 ?

    2. Comment autoriser l'accès à certaines urls avec notre système de sécurité ?



Nous avons pu voir tout au long de ce cours que les contraintes REST permettent de mettre en place une API uniforme et facile à prendre en main. La mise en œuvre de ces contraintes offre un ensemble d’avantages et le framework Symfony dispose d’outils suffisamment matures pour aider dans les développements.

Ce cours bien qu’étant assez long n’aborde pas tous les concepts de REST ni toutes les fonctionnalités qu’apportent FOSRestBundle et les différents bundles utilisés. Son objectif est de présenter de manière succincte l’essentiel des notions à comprendre pour pouvoir développer une API RESTFul et l’améliorer en toute autonomie.

Le style d’architecture REST ne s’occupe pas des détails d’implémentations mais plutôt du rôle de chaque composant de notre application.

N’hésitez surtout pas enrichir l’API et à explorer les documentations officielles des différents outils abordés pour mieux cerner tout ce qu’ils peuvent vous apporter.

92 commentaires

Reprise du dernier message de la page précédente

Salut,

Il est préférable deposer les questions sur le forum. Pour utiliser les annotations, il faut les activer :

1
2
3
# app/config/config.yml
framework:
    validation: { enable_annotations: true }

Source: http://symfony.com/doc/current/validation.html#configuration

Merci BestCoder pour ce tuto trés bien conçu. C'est le meilleur tuto Symfony à ma connaissance avec celui d'Alexandre BACCO sur OpenClassrooms.

Serait-il possible de rajouter un petit chapitre sur comment un frontend javascript va récupérer les infos ? par exemple sur DataTables et AJAX.

Je me pose aussi la question sur qui doit faire quoi. Le frontEnd récupère toute une collection et fait les traitements dessus (payload réseau important mais charge coté client) ou il vaut mieux prévoir des API spécifiques donc hors CRUD (charge coté serveur et BDD mais payload faible) ? ==> peut être modifier certains chapitres pour prendre en compte le SCRUD (S pour Search) ?

Un grand merci en tout cas pour le travail fourni

Édité par eric.demarze

+1 -0

Merci à toi ! Ça fait toujours plaisir de lire ce genre de message :) .

Serait-il possible de rajouter un petit chapitre sur comment un frontend javascript va récupérer les infos ? par exemple sur DataTables et AJAX.

Ce tutoriel est malheureusement basé que sur la partie le backend. Mais utiliser une API REST sur le frontend est vraiment simple. Si en plus tu utilises un framework du style Angular JS ou React, il existe beaucoup de ressources qui expliquent comment faire.

Je me pose aussi la question sur qui doit faire quoi. Le frontEnd récupère toute une collection et fait les traitements dessus (payload réseau important mais charge coté client) ou il vaut mieux prévoir des API spécifiques donc hors CRUD (charge coté serveur et BDD mais payload faible).

En utilisant REST, il faudra des fois faire des compromis. Si le front peut faire l'opération de manière simple, alors oui, tu peux le faire ainsi. Mais il arrive des cas où le modèle CRUD de base ne convienne pas. Il ne faut donc pas hésiter à rajouter des méthodes du style PATCH/POST rest-api.local/emails/1/send pour envoyer un mail par exemple. Il est difficile d'avoir une API Restful au sens strict du terme. Mais il faut trouver les bon compromis en REST, les performances et la simplicité de l'API.

peut être modifier certains chapitres pour prendre en compte le SCRUD (S pour Search) ?

L'acronyme SCRUD est moins utilisé que le CRUD et en soi Read et Search c'est proche non ? :-°

Bonjour BestCoder,

{Super ce tutorial complet sur lequel je passe un très bon moment durant mon apprentissage sur l'API Rest avec SF3.

J'ai hâte de terminer le cours et concevoir une API basée sur un projet en préparation.}

return BestCoder->thanks();

Édité par rylook

+1 -0

Salut à tous,

J'ai eu un problème avec le tuto "À propos de - Créez une API REST avec Symfony 3", j'ai donc poser ma question en MP à l'auteur du tuto BestCoder qui a résolu mon problème. Afin que d'autres puissent aussi en profiter, je place ici l'historique de la conversion :

Moi :

Salut,

Je tiens d'abord à te dire merci pour ce super tutoriel. Après avoir fouiller le web comme un chien affamé à la recherche de ne ce resqu'un os, je suis content d'avoir trouvé mon bonheur. Franchement c'est le meilleur tutoriel sur Fosrestbundle que j'ai trouvé jusqu'ici. C

Cependant, j'ai eu à adapter ton projet sur un projet personnel et j'ai pu avec succès construire une api fonctionnel. Maintenant, je ne sais pas comment afficher une page html afin de travailler avec AngularJS en front-end, puisque toutes les routes communiquent avec l'api.

Peux-tu m'aider s'il te plaît ?

Mille fois merci par avance

BestCoder :

Salut,

Tout d'abord merci.

Si tu veux que les pages html soient affichées par ton application, il faut changer les urls de ton api. Tu peux par exemple ajouter un préfixe /api dans toutes les routes de ton api, et configurer FOSREstBundle pour qu'il soit actif que sur ces routes.

La déclaration des routes pourrait ressembler à :

1
2
3
4
places:
    type:     rest
    resource: AppBundle\Controller\PlaceController
    prefix: /api

La configuration de FOSRestBundle devra prendre en compte les nouvelles routes.

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

La partie zone permet d'activer le bundle que pour les routes commençants par api. IL ne faut pas aussi oublier de reconfigurer la partie sécurité pour qu'elle corresponde aux nouvelles urls.

PS : Il est préférable de poser les questions sur le forum pour que tout le monde puisse en profiter.

Cordialement.

Édité par briceouabo

+1 -0

Merci de ton retour rapide. Cela va beaucoup m'aider. Je me rends bien vite compte que ma tâche est herculienne quand je regarde ce sur quoi je me suis appuyé.

1
2
3
4
5
format_listener:
        rules:
            - { path: '^/$', priorities: ['html'], fallback_format: 'json' }
            - { path: '^/admin', priorities: ['html'], fallback_format: 'json' }
            - { path: '^/', priorities: ['json'], fallback_format: 'json' }

You got it.

+0 -0

Salut @BestCoder, Je me suis inspiré de ton tutoriel pour construire ma propre API. Je suis actuellement entrain de bâtir la partie frontend avec AngularJS pour récupérer et manipuler les données. Et cependant, je fais face à 3 soucis :

1. Sécurisation de l'API

Tu as effectué 2 chapitres sur la sécurisation de l'api. Dans le 1er chapitre, tu nous montre comment créer un token. Dans le second, tu nous montre comment bloqué l'accès à toutes les ressources tant que le token n'est pas pas vérifié cad même la ressource "/auth-tokens" qui permet pourtant de de créer un token et de se connecter à l'api. Ce qui reviendra donc à dire qu'il serait impossible à un utilisateur d'accéder à l'API sur un navigateur la 1ère fois ; puis qu'en voulant accéder à la ressource "/auth-tokens" pour se logguer il sera bloqué (car son token n'est pas vérifié). Je pense qu'on doit activer la sécurisation sur toutes les ressources sauf "/auth-tokens" qui permet de se logger et de créer un token. Mais je ne sais pas comment le faire, et s'il te plaît dit moi comment le faire.

2. Implementation automatique des tokens dans les headers

J'ai aussi constaté qu'à l'instant où l'utilisateur à un token et veut se connecter à l'api, on est obligé manuellement d'insérer son token dans le header (tu l'as très bien démontré avec Postman). Comment ça se passera en production ? Est-ce que le webmaster devra fournir à chaque fois le token de l'utilisateur et lui donner des indications comment faire pour l'insérer dans le header ? Je suppose que non ! Comment donc faire qu'une fois logguer, si le compte de l'utilisateur est bien valide et si celui-ci a belle et bien un token ; insérer ce token automatiquement (et non manuellement) dans le header ?

3. Limiter la sécurisation via les tokens pour certaines ressources

Dans l'api que je suis actuellement entrain de mettre sur pieds, j'ai 2 types d'utilisateurs : les admins et les clients. Il y a des ressources qui sont propres à l'admin et dont le client ne doit pas y avoir accès. Je pense qu'il faudrai limiter la sécurisation (via un token sûrement) à ces ressources, mais je ne sais pas comment le faire ? Peux tu m'aider stp ?

Mille fois merci par avance d'avoir pris le temps de me lire, et j'espère que tu me répondras.

Cordialement

Édité par briceouabo

+0 -0

Salut,

La plupart des questions que tu poses son en réalité des problèmatiques propres à Symfony et au web de manière générale.

Salut @BestCoder, Je me suis inspiré de ton tutoriel pour construire ma propre API. Je suis actuellement entrain de bâtir la partie frontend avec AngularJS pour récupérer et manipuler les données. Et cependant, je fais face à 3 soucis :

1. Sécurisation de l'API

Si tu regardes bien la méthode createToken de la classe AuthTokenAuthenticator (partie 2/2), il y a une condition qui gère déjà ce cas.

1
2
3
4
5
6
<?php
         $targetUrl = '/auth-tokens';
        // Si la requête est une création de token, aucune vérification n'est effectuée
        if ($request->getMethod() === "POST" && $this->httpUtils->checkRequestPath($request, $targetUrl)) {
            return;
        }

2. Implementation automatique des tokens dans les headers

Une API est en général exploiter par du code. Tu dois avoir sur le front une application mobile, du javascript ou toutes autres technologies qui te permettra d'exploiter l'API REST. Dans ton exemple, une fois que l'utilisateur est logué sur ton site, tu peux utiliser du javascript pour rajouter l'entête X-Auth-Token à chacune de tes requêtes. Ce tutoriel ne présente que la partie API, l'exploitation de celle-ci est laissée à l'appréciation de chacun.

3. Limiter la sécurisation via les tokens pour certaines ressources

Ma réponse en citation juste au dessus répond à ta question. Avec le listener de zone, tu peux activer FOSRestBundle que pour une famille de requête et utiliser le système natif de Symfony pour le reste.

Mille fois merci par avance d'avoir pris le temps de me lire, et j'espère que tu me répondras.

De rien :) .

Édité par BestCoder

Salut @BestCoder,

Dans le chapitre "Quand utiliser les query strings ?", tu nous montres les avantages des query strings avec les opérations de pagination, filtre, tri … Je trouve ça cool, mais je pense aussi que doctrine regorge de méthodes et fonctionnalités pour faire des actions similaires. Que nous recommande tu donc? D'utiliser les query strings ou les fonctionnalités de doctrine ?

Merci

+0 -0

Bonjour,

Un tout grand merci pour ce tuto très complet !

J'aurais une question au niveau structurel en reprenant l'exemple de ce tuto. Je souhaiterais, par exemple, créer une nouvelle Ressource "raccourcie" m'affichant toutes les Adresses existantes parmi les Users et les Places. J'aurai de manière très simplifiée une Réponse ne contenant que des noms: { "name": "Tour Eiffel", "name": "Chez moi", ... }

Mes questions: - Dans quel Controlleur devrais je avoir mon Action getAdresses? - Dois je obligatoirement passer par une Entity ? Et si non, quoi indiquer dans le fichier "serialization.yml" ? - Comment voyez vous le merge des résultats des Adresses provenant de Users et de Places dans l'Action?

Merci pour vos réponses

Édité par Folsh

+0 -0

Limite HS : si tu fais ça, la grande majorité des parsers te donneront un seul name. Utilise une liste (un array en PHP).

1
2
// ce que ton front recevra
JSON.parse('{"name": "foo", "name": "bar"}') //=> { name: 'bar' }

Édité par tleb

Oui, j'ai été un peu vite pour l'exemple JSON: On s'entends bien sur le fait que j'aurai comme réponse un array de noms

Mais donc, la question concerne surtout les actions permettant de combiner/merger deux réponses dont certains éléments sont les mêmes afin d'éviter deux requêtes au serveur

Merci

+0 -0

Salut Folsh,

L'idéal pour ce que tu veux faire est d'avoir une entité adresse.

Dans quel Controlleur devrais je avoir mon Action getAdresses?

Si tu as une entité, il te faudra créé un contrôleur spécifique. Et même si tu n'optes pas pour une entité à part, il faut quand même créer un contrôleur vu que ta notion de raccourci n'est ni lié aux lieux ni aux utilisateurs.

Dois je obligatoirement passer par une Entity ?

Ce n'est pas obligatoire, mais avoir une entité te donnera plus de liberté. Si par exemple tu veux rajouter le pays à l'adresse, tu pourrais simplement rajouter un attribut à ton entité. Si tu veux séparer la rue, le numéro de rue et le code postal, tu pourras le faire plus simplement.

Et si non, quoi indiquer dans le fichier "serialization.yml" ?

Le sérialiseur est en mesure de traiter des tableaux. Mais en utilisant les tableaux, la cohérence globale de ton appication va en pâtir.

Comment voyez vous le merge des résultats des Adresses provenant de Users et de Places dans l'Action?

Faire un merge à la main est vraiment un technique pas assez propre. Mais tu n'auras pas le choix, il faudra lister tous les lieux et tous les utilisateurs et ensuite reconstituer ta liste d'adresse.

En optant pour le choix d'une entité adresse, toutes ces problématiques seront évitées.

De rien :) .

FOSUserBundle est utilisé pour gérer les utilisateurs avec une interface web (avec des formulaires classiques, des boutons, etc.). Là nous utilisons une API à 100%. FOSUserBundle ne peut donc pas nous aider dans cette tâche.

Il y a aussi un autre point que beaucoup d’utilisateurs de Symfony ignorent. FOSUserBundle ne gère pas l’authentification. Il a été conçu pour la gestion d’un espace membre complet (Inscription avec ou sans envoie de mail, gestion des informations personnelles, mot de passe oublié, etc.).

Bonsoir.

Très bon tutoriel, autant du point de vue API REST en général qu’intégration à Symfony, tout est assez clair (bien que lourd à assimiler d’un coup, un retour sur le site sera donc très très envisageable si on construit une API complète :p).

Par contre, comme MohamedBenLakhrech le dit, ne serait-il pas possible d’utiliser FOSUserBundle comme base? Même s’il ne gère pas l’authentification, il "suffirait" de surcharger le module, non? Histoire de garder FOSUserBundle en front et de l’utiliser en back pour l’API (imaginons :p)

En tous cas, merci pour les tutos :-).

Édité par Zyfraglover

+0 -0

Salut,

Tout dépendra du mode de fonctionnement que tu souhaites avoir. Tu peux décrire ta problématique et les solutions que tu as déjà essayé sur le forum. Je pense que les gens se feront un plaisir de te répondre.

Édité par BestCoder

Salut BestCoder,

Je tiens avant tout à saluer la qualité de ton tutoriel. Vraiment bravo et merci pour cette contribution!

Concernant la gestion des tokens dans les chapitres "II Sécurisation de l’API" : pourquoi ne pas préconiser l’utilisation du bundle "FOSOAuthServerBundle" assez largement utilisé ?

Toujours au sujet de la sécurité, si j’appel la ressource GET rest-api.local/users avec un token quelquonque, ça liste l’ensemble des utilisateurs. Au niveau sécurité, cette fonction ne devrait pas lister uniquement les informations lié au token de l’utilisateur en question?

Merci.

+0 -0

Salut BestCoder,

Je tiens avant tout à saluer la qualité de ton tutoriel. Vraiment bravo et merci pour cette contribution!

Salut, De rien !

Concernant la gestion des tokens dans les chapitres "II Sécurisation de l’API" : pourquoi ne pas préconiser l’utilisation du bundle "FOSOAuthServerBundle" assez largement utilisé ?

FOSOAuthServerBundle est utilisé pour implémenter OAuth. Dans notre cas, nous sommes les seuls consommateurs de notre API avoir OAuth n’apporte pas beaucoup d’intérêt. En plus, pour utiliser le bundle FOSOAuthServerBundle il faut déjà comprendre OAuth, ce qui ne rentre pas dans le cadre de ce tutoriel.

Spoiler !

J’ai un article en validation qui explique la théorie sur OAuth 2.0. Peut-être après il y aura un article à part dédié à FOSOAuthServerBundle.

Toujours au sujet de la sécurité, si j’appel la ressource GET rest-api.local/users avec un token quelconque, ça liste l’ensemble des utilisateurs. Au niveau sécurité, cette fonction ne devrait pas lister uniquement les informations lié au token de l’utilisateur en question?

Ces détails d’implémentation son laisser à l’appréciation du lecteur. Tu peux rajouter des limitations sur les accès aux ressources comme bon te semble. Par contre, si tu veux mettre une limite, il faut juste t’assurer que ça renvoie toujours une liste pour rester cohérent avec le reste de ton API.

Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte