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 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.

Salut BestCoder,

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é, sauf pour la ressource ’/auth-tokens’ pour la création d’un token. cad même la ressource "/users/{id}" de la l’action patchUserAction() (dans la méthode UserController) qui permet à un utilisateur de mettre à jour ses informations personnelles (mdp, email etc). Ce qui reviendra donc à dire qu’il serait impossible à un utilisateur de mettre à jour son mot de passe dans le cas par exemple où il l’a oublié ; puis qu’en voulant accéder à la ressource "/users/{id}" pour le faire il sera bloqué (car son token ne sera pas vérifié). Et ce problème concerne toutes les autres ressources de UserController je crois.

Est-ce que c’est fait exprès ? Ou j’ai raté quelque chose ? Merci par avance pour ta réponse !

+0 -0

Salut,

C’est bien voulu. Une fois que la sécurité est activée, seule la méthode de connexion est accessible.

Mais rien ne t’empêche de mettre en place un système pour autoriser la remise à zéro de mot de passe.

Pour ton cas tu sembles vouloir utiliser la route /users/{id} pour le faire alors que cette route a été conçu pour permettre au utilisateur qui ont un token valide de changer leur mot de passe.

Tu dois donc désigner un autre système pour gérer les mots de passe oubliés.

Voici un modèle qui pourrait convenir. Considérons qu’une demande de réinitialisation de mot de passe est une ressource nommée password-resets. Un utilisateur qui clique sur un éventuel bouton "Mot de passe oublié" crée alors une demande POST /password-resets avec comme payload {"email": "l’adresse mail de l’utilisateur"}.

Après cette requête tu crées une ressource password-reset avec un identifiant aléatoire par exemple 110e8400-e29b-11d4-a716-446655440000 et tu lui envois un mail vers une page qui contient un formulaire de changement de mot de passe.

Si ce formulaire est validé, tu peux faire une requête POST /password-resets/110e8400-e29b-11d4-a716-446655440000 avec comme payload {"password": "mon mot de passe", "repeat_password": "mon mot de passe"} et son mot de passe sera changé.

Il suffira juste de désactiver la sécurité pour la ressource password-reset. Les possibilités de REST sont infinies, il faut juste faire preuve d’imagination :) .

Ce n’est pas mon 1er commentaire sur ce post mais j’en profite pour RE-remercier l’auteur, c’est vraiment très instructif et surtout bien expliqué, rien n’est laissé au hasard, si ce n’est l’étape du User Provider et du AuthToken Authenticator ("Sécuriser l’API 2/2), je trouve que ça a été expliqué un peu rapidement avec 2 gros pavé copiés-collés et je dois bien avouer que je n’ai pas totalement compris, peut-être est-ce un rudiment de Symfony mais je trouve que ça serait cool d’expliquer pourquoi on utilise ces 2 services, comment est-ce qu’ils interagissent entre eux etc, ça peut être en réponse à ce post pour que je vois la réponse plus rapidement :p.

En tous cas je vais définitivement rester abonné à ce site, c’est de la balle bon boulot !

Édité par Zyfraglover

+0 -0

Bonjour,

Je reviens avec une autre question.puisque je travail avec AngularJs coté Frontend donc je fait la validation des formulaires avec angularjs,donc est ce que je peux, ne pas utiliser les FormBuilder l’ors de la persistance? Merci.

+0 -0

Bonjour,

Je reviens avec une autre question.puisque je travail avec AngularJs coté Frontend donc je fait la validation des formulaires avec angularjs,donc est ce que je peux, ne pas utiliser les FormBuilder l’ors de la persistance? Merci.

MohamedBenLakhrech

Salut,

Bien effectivement tu peux ne pas l’utiliser, mais il est préférable de le faire car une autre personne peut développer un client pour cet API et ne pas implémenter tous ces contrôles, dans ce cas le système se doit de l’informer à mon humble avis.

You got it.

+1 -0

Bonjour,

Je reviens avec une autre question.puisque je travail avec AngularJs coté Frontend donc je fait la validation des formulaires avec angularjs,donc est ce que je peux, ne pas utiliser les FormBuilder l’ors de la persistance? Merci.

MohamedBenLakhrech

Salut,

Bien effectivement tu peux ne pas l’utiliser, mais il est préférable de le faire car une autre personne peut développer un client pour cet API et ne pas implémenter tous ces contrôles, dans ce cas le système se doit de l’informer à mon humble avis.

azeupaul

Merci beaucoup, je suis bien convaincu :)

+0 -0

Et je rajouterais à la réponse de azeupaul qu’il ne faut jamais faire confiance aux données provenant du front.

Un utilisateur de ton application pourra récupérer la requête que tu exécutes et la rejouer avec n’importe quelles informations sans passer par ton application Angular JS.

Le backend doit toujours valider les données qu’il reçoit.

Bonjour @BestCoder,

Je tiens encore à te remercier pour tes cours qui sont très bien conçu. J’ai pu m’en servir pour construire une API pour un projet personnel avec AngularJS. Cependant, j’ai un petit soucis concernant l’authentification.

Sur la partie "Exploitons le token grâce à Symfony", tu nous montres comment créer un token une fois que l’utilisateur se connecte, et utiliser ce token comme cookie (en fixant une durée de vie) ; de telle sorte quand l’utilisateur entre une url, si le token a déjà été fixé dans le header du navigateur, une connexion à l’api ne sera plus nécessaire, ceci tant que la durée de vie du token est valide.

Mais, une chose m’intrigue, toi tu as fixé le token dans Postman de façon manuel, juste pour faire une démonstration je sais. Mais dans un cas pratique comment le faire ? Moi, avec angularJS, quand un utilisateur se connecte, je récupère le X-Auth-Token avec le service "$http.defaults.headers.common", ce qui fonctionne très bien, mais le problème est qu’une fois quitter la page, X-Auth-Token revient à "undefined".

Quel est le meilleur moyen dans un cas pratique (à partir du client / du serveur) de fixer le X-Auth-Token du user connecté, et y donc appliquer la durée de vie correspondante ?

Merci

+0 -0

Bonjour @BestCoder,

Je tiens encore à te remercier pour tes cours qui sont très bien conçu. J’ai pu m’en servir pour construire une API pour un projet personnel avec AngularJS. Cependant, j’ai un petit soucis concernant l’authentification.

Sur la partie "Exploitons le token grâce à Symfony", tu nous montres comment créer un token une fois que l’utilisateur se connecte, et utiliser ce token comme cookie (en fixant une durée de vie) ; de telle sorte quand l’utilisateur entre une url, si le token a déjà été fixé dans le header du navigateur, une connexion à l’api ne sera plus nécessaire, ceci tant que la durée de vie du token est valide.

Mais, une chose m’intrigue, toi tu as fixé le token dans Postman de façon manuel, juste pour faire une démonstration je sais. Mais dans un cas pratique comment le faire ? Moi, avec angularJS, quand un utilisateur se connecte, je récupère le X-Auth-Token avec le service "$http.defaults.headers.common", ce qui fonctionne très bien, mais le problème est qu’une fois quitter la page, X-Auth-Token revient à "undefined".

Quel est le meilleur moyen dans un cas pratique (à partir du client / du serveur) de fixer le X-Auth-Token du user connecté, et y donc appliquer la durée de vie correspondante ?

Merci

briceouabo

Bonjour, coté AngularJs tu peux utiliser "sessionStorage" pour sauvegarder le token et de l’utiliser dans n’importe quel controlleur ;)

+0 -0

Merci > > Bonjour @BestCoder,

Je tiens encore à te remercier pour tes cours qui sont très bien conçu. J’ai pu m’en servir pour construire une API pour un projet personnel avec AngularJS. Cependant, j’ai un petit soucis concernant l’authentification.

Sur la partie "Exploitons le token grâce à Symfony", tu nous montres comment créer un token une fois que l’utilisateur se connecte, et utiliser ce token comme cookie (en fixant une durée de vie) ; de telle sorte quand l’utilisateur entre une url, si le token a déjà été fixé dans le header du navigateur, une connexion à l’api ne sera plus nécessaire, ceci tant que la durée de vie du token est valide.

Mais, une chose m’intrigue, toi tu as fixé le token dans Postman de façon manuel, juste pour faire une démonstration je sais. Mais dans un cas pratique comment le faire ? Moi, avec angularJS, quand un utilisateur se connecte, je récupère le X-Auth-Token avec le service "$http.defaults.headers.common", ce qui fonctionne très bien, mais le problème est qu’une fois quitter la page, X-Auth-Token revient à "undefined".

Quel est le meilleur moyen dans un cas pratique (à partir du client / du serveur) de fixer le X-Auth-Token du user connecté, et y donc appliquer la durée de vie correspondante ?

Merci

briceouabo

Bonjour, coté AngularJs tu peux utiliser "sessionStorage" pour sauvegarder le token et de l’utiliser dans n’importe quel controlleur ;)

MohamedBenLakhrech

J’ai lu sur cet article : https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage, que c’était pas très sécurisé et qu’il fallait directement utilisé les cookies, mais je ne sais pas comment m’y prendre

+1 -0

Bonsoir,

Je tente de suivre ce tutoriel mais je bloque sur un point. Lorsque j’active le routage manuel avec FOSRestBundle, les routes avec paramètres sont mal générées. Je m’explique, si je retire le paramètre $id dans le définition de ma fonction au sein de mon contrôleur et que ce paramètre est bien déclaré dans l’annotation Get alors le paramètre n’est pas reconnu et donc la route est générée sans paramètre et est inutilisable. Par contre si je conserve le paramètre, alors ma route est correctement générée et est par conséquent utilisable. Est-ce normal de devoir conserver ce paramètre dans la définition de la fonction (le tutoriel mentionne que le paramètre peut disparaitre) ?

Merci d’avance pour vos réponses.

+0 -0

Salut,

Est-ce que tu peux créer un sujet sur le forum avec des extraits de code qui illustrent ton problème ? Le système de routage manuel suit les mêmes règles que le système de Symfony. Le comportement est exactement le même.

BestCoder

Ok le sujet est créé.

Édité par hl10n3l

+0 -0

Bonsoir @BestCoder,

Merci pour ce tutoriel qui est top. J’en suis à l’étape de validation des données et une question me vient à l’esprit, pourquoi utiliser la validation de formulaire plutôt que les asserts (Symfony\Component\Validator\Constraints) ?

+0 -0

De rien :) .

Les formulaires Symfony utilisent ce composant validator pour valider les données. Mais en plus de cela, en utilisant les formulaires tu disposes de plein d’avantages comme :

  • Si la requête est une édition, le form pourra appliquer les modifications sur une entité existante sans effort de notre part ;
  • Les formulaires invalides sont sérialisés automatiquement par FOSRestBundle. Sans les formulaires, nous devrions définir une méthode pour traiter les erreurs du composant de validation et les afficher dans les réponses d’API.

@BestCoder,

J’en suis aux relations entre els ressources, avec la dernière version de Symfony (3.2.4), je ne rencontre pas l’erreur suivante lorsque je POST le prix:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "error": {
    "code": 500,
    "message": "Internal Server Error",
    "exception": [
      {
        "message": "A circular reference has been detected (configured limit: 1).",
        "class": "Symfony\\Component\\Serializer\\Exception\\CircularReferenceException",
        "trace": [ "..." ]
      }
    ]
  }
}

Tout fonctionne, y-a-t-il eu des changement au niveau du serializer ?

Autre question au passage, sur la méthode suivante :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public function getThemesAction(Request $request)
    {
        $place = $this->get('doctrine.orm.entity_manager')
                ->getRepository('AppBundle:Place')
                ->find($request->get('id'));
        /* @var $place Place */

        if (empty($place)) {
            return $this->placeNotFound();
        }

        return $place->getThemes();
    }

ne serait-il pas judicieux d’ajouter :

1
2
3
4
5
$themes = $place->getThemes()
if (empty($themes)) {
      return $this->themeNotFound();
  }
  return $themes;

Édité par sbruhat

+0 -0

J’en suis aux relations entre els ressources, avec la dernière version de Symfony (3.2.4), je ne rencontre pas l’erreur suivante lorsque je POST le prix

Il est préférable d’utiliser le forum pour des questions aussi spécifiques. As-tu bien configurer les annotations Doctrine dans l’entité (mettre la relation en bidirectionnelle) ?

Pour la partie concernant le return $this->themeNotFound();, puisque nous avons une liste de thémes, renvoyer un 404 alors qu’elle est vide n’est pas logique. On renvoie une liste vide pour signaler au client que la ressource themes est une ressource gérée par l’application mais elle est juste vide.

Avec un 404, un client de l’API ne pourrait pas bien distinguer une ressource inexistante. Par exemple /places/1/ressources-inexistantes doit renvoyer un 404 car ressources-inexistantes n’est pas connue par l’application.

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