Automatiser la documentation avec NelmioApiDocBundle

Bien que les outils de l’écosystème de OpenAPI (Swagger RESTFull API) soient assez bien fournis, rédiger manuellement toute la documentation peut se montrer assez rapidement rébarbatif.

En plus, à cause de la séparation entre le code et la documentation, cette dernière risque de ne pas être mise à jour si le code évolue.

Nous allons donc voir comment automatiser la génération de la documentation dans Symfony avec le bundle NelmioApiDocBundle.

Cette partie n’abordera pas toutes les fonctionnalités de ce bundle mais permettra d’avoir assez de bagages pour être autonome.

Installation de NelmioApiDocBundle

La référence en matière de documentation d’une API avec Symfony est le bundle NelmioApiDocBundle. Comme pour tous les bundles de Symfony, l’installation est particulièrement simple. Avec Composer, nous allons rajouter la dépendance :

1
2
3
composer require nelmio/api-doc-bundle
# Using version ^2.12 for nelmio/api-doc-bundle
./composer.json has been updated

Nous pouvons maintenant activer le bundle :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
# app/AppKernel.php

use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = [
            // ...
            new Nelmio\ApiDocBundle\NelmioApiDocBundle(),
            new AppBundle\AppBundle(),
        ];

       // ...
        return $bundles;
    }
// ...
}

L'annotation ApiDoc

Configuration

Pour générer de la documentation, le bunble NelmioApiDocBundle se base sur une fonctionnalité principale : l’annotation ApiDoc.

À son installation, ce bundle met à notre disposition cette annotation qui va nous permettre de rédiger notre documentation.

Il faut garder en tête que la documentation avec NelmioApiDocBundle est grandement liée au code.

Sans plus attendre, nous allons l’utiliser pour documenter l’appel qui liste les lieux 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
32
<?php
# src/AppBundle/Controller/PlaceController.php

namespace AppBundle\Controller;

// ...
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
// ...

class PlaceController extends Controller
{

    /**
     * @ApiDoc(
     *    description="Récupère la liste des lieux de l'application"
     * )
     *
     *
     * @Rest\View(serializerGroups={"place"})
     * @Rest\Get("/places")
     * @QueryParam(name="offset", requirements="\d+", default="", description="Index de début de la pagination")
     * @QueryParam(name="limit", requirements="\d+", default="", description="Nombre d'éléments à afficher")
     * @QueryParam(name="sort", requirements="(asc|desc)", nullable=true, description="Ordre de tri (basé sur le nom)")
     */
    public function getPlacesAction(Request $request, ParamFetcher $paramFetcher)
    {
        // ...

        return $places;
    }
// ...
}

Avec juste cette annotation, il est possible de consulter la documentation de notre API. Mais avant d’y accéder, nous devons avoir une URL dédiée. Et pour ce faire, le bundle propose un fichier de routage qui permet de configurer cette URL.

1
2
3
4
5
6
7
8
# app/config/routing.yml

# ...
nelmio-api-doc:
    resource: "@NelmioApiDocBundle/Resources/config/routing.yml"
    prefix:   /documentation

`

Nous allons aussi rajouter une règle dans le pare-feu de Symfony afin d’autoriser l’accès à la documentation sans authentification.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# app/config/secrity.yml
security:

# ...
    firewalls:
        # disables authentication for assets and the profiler, adapt it according to your needs
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        doc:
            pattern: ^/documentation
            security: false
# ...

Notre documentation est maintenant accessible depuis l’URL http://rest-api.local/documentation.

En y accédant depuis un navigateur, nous obtenons une page générée automatiquement :

Documentation générée par NelmioApiDocBundle

Pour avoir une vue complète (comme sur l’image), il faut cliquer sur la méthode GET /places pour dérouler les détails concernant les filtres. La mise en page de la documentation est grandement inspiré de Swagger UI.

Intégration avec FOSRestBundle

Le premier point qui devrait vous interpeller est la présence des filtres de FOSRestBundle dans la documentation. NelmioApiDocBundle a été conçu pour interagir avec la plupart des bundles utilisés dans le cadre d’une API. Ainsi, les annotations de FOSRestBundle sont utilisées pour compléter la documentation.

Bien sûr, si nous n’utilisons pas FOSRestBundle, nous pouvons rajouter manuellement des filtres en utilisant l’attribut filters de l’annotation ApiDoc.

De la même façon, le verbe HTTP utilisé est GET avec une URL /places. Là aussi, les routes générées par Symfony sont utilisées par NelmioApiDocBundle.

Définir le type des réponses de l’API

Notre documentation n’est pas encore complète. Le type des réponses renvoyées par notre API n’est pas encore documenté.

Pour ce faire, il existe un attribut nommé output qui prend comme paramètre le nom d’une classe ou encore une collection. Cet attribut supporte aussi les groupes de sérialisation que nous avons déjà définis.

Pour le cas des lieux, nous devons renvoyer une collection de lieux. La documentation s’écrit 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
<?php
# src/AppBundle/Controller/PlaceController.php

namespace AppBundle\Controller;
// ...
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
// ...

class PlaceController extends Controller
{

    /**
     * @ApiDoc(
     *    description="Récupère la liste des lieux de l'application",
     *    output= { "class"=Place::class, "collection"=true, "groups"={"place"} }
     * )
     * @Rest\View(serializerGroups={"place"})
     * @Rest\Get("/places")
     * @QueryParam(name="offset", requirements="\d+", default="", description="Index de début de la pagination")
     * @QueryParam(name="limit", requirements="\d+", default="", description="Nombre d'éléments à afficher")
     * @QueryParam(name="sort", requirements="(asc|desc)", nullable=true, description="Ordre de tri (basé sur le nom)")
     */
    public function getPlacesAction(Request $request, ParamFetcher $paramFetcher)
    {
    // ...
    }
// ...
}

La documentation devient :

Type de réponse pour la liste des lieux

La documentation est complétée et les attributs ont exactement les bon types définis dans les annotations Doctrine. Pour obtenir de telles informations, NelmioApiDocBundle utilise le sérialiseur de JMSSerializerBundle.

Par contre, si nous étions restés sur le sérialiseur natif de Symfony qui n’est pas encore supporté, nous n’aurions pas pu obtenir ces informations.

Les descriptions de tous les attributs sont vides. Pour les renseigner, il suffit de rajouter dans les entités une description dans le bloc de PHPDoc.

Pour l’entité Place, nous pouvons rajouter :

1
2
3
4
5
6
7
8
9
<?php
    /**
     * Identifiant unique du lieu
     *
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    protected $id;

La documentation générée devient alors :

Description de l’identifiant du lieu dans la documentation

Définir le type des payloads des requêtes

De la même façon, pour définir la structure des payloads des requêtes, nous pouvons utiliser un attribut nommé input qui peut prendre en paramètre, entre autres, une classe qui implémente l’interface PHP JsonSerializable mais aussi un formulaire Symfony. Et cela tombe bien puisse que tous nos payloads se basent sur ces formulaires.

Pour tester le bon fonctionnement de cet attribut, nous allons rajouter de la documentation pour la méthode de création d’un lieu.

 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
<?php
# src/AppBundle/Controller/PlaceController.php

namespace AppBundle\Controller;

// ...
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
// ...

class PlaceController extends Controller
{
    // ...
     /**
     * @ApiDoc(
     *    description="Crée un lieu dans l'application",
     *    input={"class"=PlaceType::class, "name"=""}
     * )
     *
     * @Rest\View(statusCode=Response::HTTP_CREATED, serializerGroups={"place"})
     * @Rest\Post("/places")
     */
    public function postPlacesAction(Request $request)
    {
       // ...
    }
    // ...
}
Documentation générée par NelmioApiDocBundle

Pour rajouter des descriptions pour les différents attributs des formulaires, nous pouvons utiliser une option nommée description rajoutée aux formulaires Symfony par NelmioApiDocBundle.

 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
<?php
# src/AppBundle/Form/Type/PlaceType.php

namespace AppBundle\Form\Type;

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

class PlaceType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name', TextType::class, [
            'description' => "Nom du lieu"
        ]);
        $builder->add('address', TextType::class, [
            'description' => "Adresse complète du lieu"
        ]);
        $builder->add('prices', CollectionType::class, [
            'entry_type' => PriceType::class,
            'allow_add' => true,
            'error_bubbling' => false,
            'description' => "Liste des prix pratiqués"
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\Place',
            'csrf_protection' => false
        ]);
    }
}
Documentation complétée avec les descriptions des attributs

Gérer plusieurs codes de statut

En définissant l’attribut output, le code de statut associé par défaut est 200. Mais pour la création d’un lieu, nous devons avoir un code 201. Et de la même façon si le formulaire est invalide, nous voulons renvoyer une erreur 400 avec les messages de validation. Pour obtenir un tel résultat, NelmioApiDocBundle met à notre disposition un attribut responseMap.

 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
<?php
# src/AppBundle/Controller/PlaceController.php

namespace AppBundle\Controller;

// ...
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
// ...

class PlaceController extends Controller
{
    // ...
     /**
     * @ApiDoc(
     *    description="Crée un lieu dans l'application",
     *    input={"class"=PlaceType::class, "name"=""},
     *    statusCodes = {
     *        201 = "Création avec succès",
     *        400 = "Formulaire invalide"
     *    },
     *    responseMap={
     *         201 = {"class"=Place::class, "groups"={"place"}},
     *         400 = { "class"=PlaceType::class, "form_errors"=true, "name" = ""}
     *    }
     * )
     *
     * @Rest\View(statusCode=Response::HTTP_CREATED, serializerGroups={"place"})
     * @Rest\Post("/places")
     */
    public function postPlacesAction(Request $request)
    {
       // ...
    }
    // ...
}

Le paramètre form_errors permet de spécifier le type de retour que nous voulons à savoir les erreurs de validation.

Documentation de la création avec succès
Documentation de la création avec des erreurs de validation

Ici, nous avons bien deux réponses selon le code de statut mais pour la réponse lors d’un requête invalide, le format n’est pas correct (pas d’attribut children, l’attribut status_code s’appelle code, etc.).

Étendre NelmioApiDocBundle

Pourquoi étendre le bundle ?

Pour corriger les petits manquements de NelmioApiDocBundle, nous allons étendre le code de celui-ci. L’objectif n’est pas d’apprendre le code source de ce bundle mais plutôt de maximiser son efficacité en l’adaptant à nos besoins.

Il est possible d’obtenir de la documentation en redéfinissant manuellement toutes ces informations manquantes. Mais l’intérêt réel de ce bundle réside dans le fait d’utiliser les composants déjà existants pour générer la documentation automatiquement. N’hésitez donc pas à consulter la documentation officielle de NelmioApiDocBundle pour plus d’informations.

Correction du format de sortie des réponses en erreur

Il n’y a pas de documentation sur comment étendre NelmioApiDocBundle. Mais vu que ce bundle est open source, il suffit de relire avec attention son code pour comprendre son fonctionnement.

Il en ressort que pour traiter les informations disponibles dans les attributs input et output de l’annotation ApiDoc, le bundle utilise des parseurs.

Et la documentation officielle nous explique comment en créer et comment l’utiliser.

Nous allons donc créer un parseur capable de générer les erreurs de validation au même format que FOSRestBundle.

Ce code est grandement inspiré du parseur déjà existant (FormErrorsParser).

  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
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<?php
# src/Component/ApiDoc/Parser/FOSRestFormErrorsParser.php

namespace Component\ApiDoc\Parser;

use Nelmio\ApiDocBundle\DataTypes;
use Nelmio\ApiDocBundle\Parser\ParserInterface;
use Nelmio\ApiDocBundle\Parser\PostParserInterface;

class FOSRestFormErrorsParser implements ParserInterface, PostParserInterface
{

    public function supports(array $item)
    {
        return isset($item['fos_rest_form_errors']) && $item['fos_rest_form_errors'] === true;
    }

    public function parse(array $item)
    {
        return array();
    }


    public function postParse(array $item, array $parameters)
    {
        $params = [];

        // Il faut d'abord désactiver tous les anciens paramètres créer par d'autres parseurs avant de reformater
        foreach ($parameters as $key => $parameter) {
            $params[$key] = null;
        }

        $params['code'] = [
            'dataType' => 'integer',
            'actualType' => DataTypes::INTEGER,
            'subType' => null,
            'required' => false,
            'description' => 'The status code',
            'readonly' => true
        ];

        $params['message'] = [
            'dataType' => 'string',
            'actualType' => DataTypes::STRING,
            'subType' => null,
            'required' => true,
            'description' => 'The error message',
            'default' => 'Validation failed.',
        ];

        $params['errors'] = [
            'dataType' => 'errors',
            'actualType' => DataTypes::MODEL,
            'subType' => sprintf('%s.FormErrors', $item['class']),
            'required' => true,
            'description' => 'List of errors',
            'readonly' => true,
            'children' => [
                'children' => [
                    'dataType' => 'List of form fields',
                    'actualType' => DataTypes::MODEL,
                    'subType' => sprintf('%s.Children', $item['class']),
                    'required' => true,
                    'description' => 'Errors',
                    'readonly' => true,
                    'children' => []
                ]
            ]
        ];

        foreach ($parameters as $name => $parameter) {
            $params['errors']['children']['children']['children'][$name] = $this->doPostParse($parameter, $name, [$name], $item['class']);
        }

        return $params;
    }

    protected function doPostParse($parameter, $name, array $propertyPath, $type)
    {
        $data = [
            'dataType' => 'Form field',
            'actualType' => DataTypes::MODEL,
            'subType' => sprintf('%s.FieldErrors[%s]', $type, implode('.', $propertyPath)),
            'required' => true,
            'description' => 'Field name',
            'readonly' => true,
            'children' => [
                'errors'=> [
                    'dataType' => 'errors',
                    'actualType' => DataTypes::COLLECTION,
                    'subType' => 'string',
                    'required' => false,
                    'description' => 'List of field error messages',
                    'readonly' => true
                ]
            ]
        ];

        if ($parameter['actualType'] == DataTypes::COLLECTION) {
            $data['children']['children'] = [
                'dataType' => 'List of embedded forms fields',
                'actualType' => DataTypes::COLLECTION,
                'subType' => sprintf('%s.FormErrors', $parameter['subType']),
                'required' => true,
                'description' => 'Validation error messages',
                'readonly' => true,
                'children' =>  [
                    'children' => [
                        'dataType' => 'Embedded form field',
                        'actualType' => DataTypes::MODEL,
                        'subType' => sprintf('%s.Children', $parameter['subType']),
                        'required' => true,
                        'description' => 'List of errors',
                        'readonly' => true,
                        'children' => []
                    ]
                ]
            ];

            foreach ($parameter['children'] as $cName => $cParameter) {
                $cPropertyPath = array_merge($propertyPath, [$cName]);

                $data['children']['children']['children']['children']['children'][$cName] =   $this->doPostParse($cParameter, $cName, $cPropertyPath, $parameter['subType']);
            }

        }

        return $data;
    }
}

Ce parseur doit toujours être utilisé avec FormTypeParser qui apporte l’ensemble des informations issues du formulaire Symfony. Pour l’activer, il faut utiliser l’attribut : fos_rest_form_errors (voir la méthode supports).

Pour le déclarer en tant parseur prêt à l’emploi, nous devons créer un service avec le tag nelmio_api_doc.extractor.parser.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# app/config/services.yml
services:
    # ...

    app_bundle.api_doc.fos_rest_form_errors_parser:
        class: Component\ApiDoc\Parser\FOSRestFormErrorsParser
        tags:
            - { name: nelmio_api_doc.extractor.parser, priority: 1 }

`

Tous les parseurs natifs du bundle sont déclarés avec une priorité de 0. En utilisant une priorité de 1, nous nous assurons que notre parseur est toujours appelé en dernier.

Pour utiliser notre parseur, nous allons ajuster l’annotation sur le contrôleur des lieux en utilisant l’attribut fos_rest_form_errors.

 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
<?php
# src/AppBundle/Controller/PlaceController.php

namespace AppBundle\Controller;

// ...
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
// ...

class PlaceController extends Controller
{

    // ...

     /**
     * @ApiDoc(
     *    description="Crée un lieu dans l'application",
     *    input={"class"=PlaceType::class, "name"=""},
     *    statusCodes = {
     *        201 = "Création avec succès",
     *        400 = "Formulaire invalide"
     *    },
     *    responseMap={
     *         201 = {"class"=Place::class, "groups"={"place"}},
     *         400 = { "class"=PlaceType::class, "fos_rest_form_errors"=true, "name" = ""}
     *    }
     * )
     *
     * @Rest\View(statusCode=Response::HTTP_CREATED, serializerGroups={"place"})
     * @Rest\Post("/places")
     */
    public function postPlacesAction(Request $request)
    {
        // ...
    }
    // ...
}

La réponse pour un formulaire invalide est maintenant correctement formatée.

Documentation de la réponse pour un formulaire invalide

Le bac à sable

Comme pour OpenAPI (Swagger RESTFul API), NelmioApiDocBundle propose un bac à sable permettant de tester la documentation. Avant d’utiliser ce bac à sable, nous allons rajouter quelques informations de configuration.

Configuration du bac à sable

La documentation officielle sur le bac à sable est concise et simple. Les paramètres disponibles sont d’ailleurs assez proches de ceux d’OpenAPI.

Voyez donc par vous-même.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# app/config/config.yml

nelmio_api_doc:
    sandbox:
        enabled:  true  # Juste pour la lisibilité car true est déjà la valeur par défaut
        endpoint: http://rest-api.local

        authentication:
            name: X-Auth-Token
            delivery: header

        accept_type: application/json   # valeur par défaut de l'entête Accept

        body_format:
            formats: [ json, xml ]                                 
            default_format: json                

        request_format:
            formats:                           
                json: application/json         
                xml: application/xml           

            method: accept_header     
            default_format: json    

Cette configuration est assez explicite et se passe donc de commentaires. En accédant à la documentation avec l’URL http://rest-api.local/documentation, nous obtenons :

Bac à sable de NelmioApiDocBundle

Le bac à sable est disponible en cliquant sur l’onglet Sandbox.

Documentation pour la création de token

Avant de tester ce bac à sable, nous allons rajouter de la documentation pour la création de token d’authentification. Cela facilitera grandement nos tests.

 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
<?php
# src/AppBundle/Controller/AuthTokenController.php

namespace AppBundle\Controller;

// ...
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
// ...

class AuthTokenController extends Controller
{
    /**
     * @ApiDoc(
     *    description="Crée un token d'authentification",
     *    input={ "class" = CredentialsType::class, "name"=""},
     *    statusCodes = {
     *        201 = "Création avec succès",
     *        400 = "Formulaire invalide"
     *    },
     *    responseMap={
     *         201 = {"class"=AuthToken::class, "groups"={"auth-token"}},
     *         400 = { "class"=CredentialsType::class, "fos_rest_form_errors"=true, "name" = ""}
     *    }
     * )
     *
     * @Rest\View(statusCode=Response::HTTP_CREATED, serializerGroups={"auth-token"})
     * @Rest\Post("/auth-tokens")
     */
    public function postAuthTokensAction(Request $request)
    {
        //...
    }
//...
}

Nous pouvons maintenant créer un token depuis le bac à sable.

Tester le bac à sable

Vu que toutes nos méthodes nécessites une authentification, il faut d’abord crée un token d’authentification. Ce token doit être renseigné dans le formulaire api_key.

Token renseigné dans le formulaire

Avec la configuration que nous avons mise en place, ce token sera envoyé automatiquement pour toutes nos requêtes.

Maintenant pour récupérer les lieux de l’application, il suffit de cliquer sur le bouton Try it!.

Récupération des lieux grâce au bac à sable

Générer une documentation compatible OpenAPI

Pour profiter des différents outils disponibles dans l’écosystème de Swagger, NelmioApiDocBundle propose d’exporter la configuration au format OpenAPI.

Pour ce faire, il faut rajouter un attribut resource à nos annotations ApiDoc. Ensuite, il suffit d’utiliser la commande php bin/console api:swagger:dump dossier_de_destination. Voici un exemple de configuration qui remplit ce contrat :

 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
<?php
# src/AppBundle/Controller/AuthTokenController.php

namespace AppBundle\Controller;

// ...
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
// ...

class AuthTokenController extends Controller
{
    /**
     * @ApiDoc(
     *    resource=true,
     *    description="Crée un token d'authentification",
     *    input={ "class" = CredentialsType::class, "name"=""},
     *    statusCodes = {
     *        201 = "Création avec succès",
     *        400 = "Formulaire invalide"
     *    },
     *    responseMap={
     *         201 = {"class"=AuthToken::class, "groups"={"auth-token"}},
     *         400 = { "class"=CredentialsType::class, "fos_rest_form_errors"=true, "name" = ""}
     *    }
     * )
     *
     * @Rest\View(statusCode=Response::HTTP_CREATED, serializerGroups={"auth-token"})
     * @Rest\Post("/auth-tokens")
     */
    public function postAuthTokensAction(Request $request)
    {
        // ...
    }

   // ...
}

`

Les métadonnées concernant la documentation peuvent être modifiées en configurant le bundle.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# app/config/config.yml
# ...
nelmio_api_doc:
    # ...
    swagger:
        api_base_path:        /
        swagger_version:      '1.2'
        api_version:          '1.0'
        info:
            title:                Rest API
            description:          'Proposition de sortie aux utilisateurs'

`

Avec la version 2.13.0, ce bundle génère un fichier swagger.json en utilisant la version 1.2 des spécifications d’OpenAPI alors qu’il existe une version 2.0. Le fichier généré ne sera donc pas à jour même si dans la configuration nous mettons 2.0 comme valeur de l’attribut swagger_version.

En exécutant la commande :

1
2
3
php bin/console api:swagger:dump --pretty web/swagger
Dumping resource list to web/swagger/api-docs.json:  OK
Dump API declaration to web/swagger/auth-tokens.json:  OK

Les fichiers ainsi générés dans le dossier web/swagger peuvent être exploités par tous les outils compatibles avec OpenAPI.

Pour les tester, il suffit d’éditer le fichier web/swagger-ui/dist/indext.html et de remplacer la ligne var url ="/swagger.json"; par var url ="/swagger/auth-tokens.json";.

En accédant à l’URL http://rest-api.local/swagger-ui/dist/index.html, la documentation générée s’affiche.

Documentation OpenAPI générée par NelmioApiDocBundle

ApiDocBundle supporte les différents bundles de Symfony et le tout permet d’avoir un ensemble harmonieux et facilite les développements.

L’un des problèmes les plus communs lorsque nous écrivons une documentation est de la maintenir à jour. Avec une documentation proche du code, il est maintenant très facile de la corriger en même temps que le code évolue.

En effet, un utilisant les annotations de FOSRestBundle, les formulaires de Symfony et les fichiers de sérialisation de JMSSerializerBundle, nous avons la garantie que la documentation est toujours à jour avec notre code.

Il ne reste plus qu’à tout mettre en production !