Aller au contenu principal

Comment générer une interface Swagger pour une API (custom) ?

Sur un projet Symfony, il est possible d’utiliser des annotations dans notre code pour commenter les endpoints et modèles du projet afin de générer une documentation OpenAPI. Combiné au projet SwaggerUI, on obtient une interface documentée de notre API.

La génération du fichier se fait grâce à la librairie zircote/swagger-php et l’interface avec l’image docker de SwaggerUI

Contexte

Il nous arrive fréquemment chez ZOL de travailler sur des projets scindés en deux parties techniques : un backend PHP (Symfony) et un frontend SPA react.

Concernant la partie backend, les APIs développées sont très souvent bien spécifiques : pas de CRUD “génériques” mais des endpoints sur mesure pour le frontend, le tout imaginé conjointement par les deux équipes. C’est possible car nous maîtrisons les deux parties en interne, nos pré-requis et contraintes sont identifiés et uniques à chaque projet.

Nous cherchions donc un moyen d’obtenir une documentation des endpoints de l’API agréable pour le développeur front, avec un processus risquant le moins possible de devenir obsolète par l’oubli de mise à jour par le développeur back.

Aujourd’hui nous allons vous présenter dans cet article comment nous avons adressé cette problématique à l’aide de la librairie zircote/swagger-php et de l’interface SwaggerUI.

Installation et premières annotations

Les exemples ci-dessous se basent sur un projet symfony mais le processus peut s’appliquer à tout projet php.

L’installation de zircote/swagger-php se fait dans notre cas avec composer :

composer require zircote/swagger-php

Il y a plusieurs façon d’utiliser cette librairie, vous pouvez :

  • générer un fichier statique (json, yaml) compatible avec OpenAPI 3.0
  • le servir sur un endpoint dédié, généré à la volée. Ici, nous testerons le fichier généré.

Au moment de la rédaction de cet article, le site mentionné pour la documentation zircote.com ne fonctionne plus (le nom de domaine nécessite un renouvellement à priori). Il existe cependant un autre site web de zicote sur GitHub (peut être la version “voulue” de la documentation ?) avec la majeure partie de la documentation sur la librairie.

Pour s’approprier les annotations et la manière d’utiliser la librairie, nous nous sommes beaucoup inspirés de la documentation et des exemples sur le projet.

Fonctionnement

La librairie génère un fichier compatible avec OpenAPI 3.0 en parsant des annotations doctrine posées dans le code.

Voici un exemple de contrôleur (PHP 7.3):

Ainsi que du fichier UserDTO.php associé :

L’objectif ici n’est pas de montrer comment faire, car votre cas d’utilisation est probablement plus complexe et spécifique qu’un simple exemple de code bateau que vous pouvez déjà trouver dans les exemples de la librairie ou sur le net. Le but est de se servir de ce cas pour présenter les avantages et inconvénients de cette façon de procéder.

Avantages

Annoter UserController directement plutôt que de maintenir une documentation ailleurs (json schema ou système tiers) permet d’avoir tout le nécessaire sous les yeux du développeur directement. Si la route change (nouveau paramètre, url différente), si le retour n’est plus le même, l’annotation peut être modifiée en même temps.

Les propriétés de UserDTO sont “devinées” par la librairie pour la génération de la documentation grâce au commentaire “@var” déjà présent (utile à PHPStorm). A noter qu’avec le typage des propriétés en PHP 7.4il sera bientôt (on l’espère) possible de se passer du “@var”.

Les annotations ne sont qu’une “traduction” des propriétés OpenAPI. Même si les exemples ne montrent pas explicitement le cas qui vous intéresse, vous pouvez, après quelques temps passés sur l’utilisation de la librairie, deviner vous même comment composer avec les possibilités pour arriver à vos fins. La prise en main est assez rapide : à peine un jour sur le sujet pour rencontrer un cas “off charts”, très facilement résolu en lisant les spécifications de Swagger pour trouver la réponse.

Inconvénients

L’exemple ci-dessus n’est pas explicite pour voir l’inconvénient, en voici un autre concernant la soumission de données.

Ici, on a un exemple de la soumission d’un objet JSON.

Sur des précédents projets où nous utilisions des fichiers json schema pour gérer la validation des inputs (sans passer par le validator de Symfony), les spécificités d’un champ sont définies qu’une seule fois.

Sur ce projet, nous utilisons les asserts comme système de validation. Un des inconvénients à relever est la nécessité de dupliquer l’information concernant les contraintes des champs. Sur cet exemple, la documentation générée ne mentionne pas le fait que les champs sont obligatoires et ne peuvent pas être vides. Il faut modifier les lignes “@OA\Property()” pour y faire figurer les contraintes.

Autre inconvénient plus général, subjectif, mais dans la même veine : le code des contrôleurs se retrouve être assez verbeux en annotation, ce qui peut irriter certains développeurs et générer de la frustration dans l’équipe.

Enfin un dernier inconvénient non négligeable : il semblerait au vu du temps de traitement des issues du projet (celle concernant la documentation inaccessible date de novembre 2019, soit depuis 6 mois lors de la rédaction de cet article) que ce dernier soit en stand by. L'écosystème open source étant ce qu’il est aujourd’hui avec ses innombrables avantages mais aussi ses inconvénients. Maintenir un projet de ce genre demande beaucoup de temps et d’énergie et il n’est pas rare de voir des “horrors stories” à ce sujet (vous vous souvenez d’event-stream fin 2018 ?). Ce sont des cas extrêmes, certes, mais c’est un risque à garder en tête, ne serait ce que celui d’abandon de la librairie, si vous visez un projet sur plusieurs années.

Génération et exploitation du fichier statique

Nous générons le fichier statique à l’aide du vendor mis à disposition par la librairie :

php vendor/bin/openapi src -o swagger.json

Il ne reste plus qu’à exploiter ce fichier dans l’interface SwaggerUI ! Pour cela, notre façon préférée de procéder est d’utiliser un container docker tel que décrit dans la documentation de Swagger.

Nos projets étant déjà sous docker et gérés avec docker-compose (+ traefik comme reverse proxy), voici comment nous configurons le container dédié à l’exposition de l’interface :

Le container swaggerapi/swagger-ui expose par défaut le port 8080 pour l’interface web, à vous de jouer avec et d’en faire ce que bon vous semble !