• 8 hours
  • Hard

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 11/27/23

Créez une API avec API Platform

Créez une API avec API Platform

Jusqu’à présent, nous avons mis en place beaucoup d’éléments en utilisant plusieurs bibliothèques différentes. Cependant, vous avez dû vous rendre compte que très souvent, ce que l’on doit faire pour l’entité Author est presque un copié-collé de ce qu’on fait pour l’entité Book .

Et si nous avions plus d'entités, ce serait encore plus marquant !

Au point que l’on pourrait se demander si une énorme librairie magique ne pourrait pas s’occuper de tout ou presque, à notre place.

Eh bien cette librairie existe et elle s’appelle API Platform

Installez API Platform

Symfony recommande cet outil lorsque l’on veut créer des API, aussi l’installation est-elle particulièrement simple :

composer require api

Et voilà ! API Platform est installé. :-)

Alors évidemment, nous allons réaliser un peu de configuration, essentiellement pour ne pas entrer en conflit avec les routes que nous avons déjà créées.

La première chose à faire sera d’aller dans l’api_platform.yaml qui se trouve dans le dossier route, pour modifier le nom de la route par défaut (ici, api  est simplement devenu apip  ) :

# config\routes\api_platform.yaml
api_platform:
    resource: .
    type: api_platform
    prefix: /apip

Ensuite, allons dans le fichier security.yaml  :

# config\packages\security.yaml
    access_control:
        - { path: ^/api/login, roles: PUBLIC_ACCESS }
        - { path: ^/api/doc, roles: PUBLIC_ACCESS }
        - { path: ^/apip, roles: PUBLIC_ACCESS }

Nous avons simplement ajouté ici la route /apip en précisant que l’accès est public.

Dernier point, quelles sont les entités que nous voulons exposer ? Book  et Author. Donc, pour chacune de ces entités, nous allons ajouter une nouvelle annotation :

<?php 
    // src\Entity\Book.php et src\Entity\Author.php
    // ...

use ApiPlatform\Core\Annotation\ApiResource;

    // ...

#[ORM\Entity(repositoryClass: BookRepository::class)]
#[ApiResource()]
class Book
    // ...

Et voilà. Maintenant regardons un peu ce qui s’est passé, en allant à l’URLhttps://127.0.0.1:8000/apip:

Comme le site web généré par Nelmio, API Platform nous liste nos routes et leurs méthodes.
Une petite araignée s’est invitée sur notre documentation de routes !

Vous reconnaissez ? C’est la documentation de Nelmio, déjà préparée, avec en plus toutes les routes pour les auteurs et les livres.

Et déjà à ce stade, les routes sont fonctionnelles. Nous pouvons par exemple récupérer la liste des livres :

Les livres s’affichent dans la réponse Body, avec un code status 200.
Nos livres sont bien à notre disposition

Comme vous pouvez le voir, nous avons bien la liste de nos livres, mais cette liste est présentée un peu différemment car API Platform a fait d’autres choix. Par exemple, au lieu d’avoir le détail de l’auteur, ici est simplement retournée l’URL à appeler pour accéder aux informations liées à l’auteur. Notez également les champs qui commencent par @  et qui donnent des informations générales plutôt que les données elles-mêmes.

Cependant, voyez qu’en quelques instants, une API complète, fonctionnelle, avec nos livres et nos auteurs, a été réalisée !

Intégrez JWT et API Platform

À ce stade tout fonctionne, mais nous n’avons pas géré la sécurité. Nous allons l'ajouter, en nous basant sur ce que nous avons déjà utilisé, c'est-à-dire JWT et LexikAuthenticator.

D’abord, nous voulons que toutes les routes aient besoin d’une authentification, sauf évidemment celle qui représente la documentation.

Pour cela, nous allons retourner dans le fichier security.yaml  et ajouter une ligne supplémentaire dans la partie access_control :

# config\packages\security.yaml

    access_control:
        - { path: ^/api/login, roles: PUBLIC_ACCESS }
        - { path: ^/api/doc, roles: PUBLIC_ACCESS }
        - { path: ^/apip/, roles: IS_AUTHENTICATED_FULLY }
        - { path: ^/apip, roles: PUBLIC_ACCESS }

Ici nous disons que toutes les routes qui commencent par /apip/ ont besoin du token JWT (par exemple /apip/books  ). Par contre, la route racine qui ne contient que /apip  en est toujours dispensée.

Étape suivante, il faut que nous puissions spécifier le token directement dans le champ Authorize. Pour cela, nous allons dans le fichier api_platform.yaml , qui se trouve dans le dossier packages , et nous ajoutons quelques lignes :

# config\packages\api_platform.yaml

api_platform:
    mapping:
        paths: ['%kernel.project_dir%/src/Entity']
    patch_formats:
        json: ['application/merge-patch+json']
    swagger:
        versions: [3]
        api_keys:
            apiKey:
                name: Authorization
                type: header

Par rapport à ce qui est présent de base, nous avons simplement précisé le champ apiKey , et le fait que nous voulions un champ Authorization dans le header.

Maintenant, comme nous l’avions fait avec la documentation de Nelmio, nous voudrions ajouter une route qui nous permette de spécifier les login et mot de passe pour obtenir notre token.

Il faut, en particulier, créer un decorator  dans  src/OpenApi/JwtDecorator , qui va ajouter cet élément à notre interface. Cela peut sembler complexe, mais il s’agit ici d’un simple copié-collé trouvé dans la documentation :

<?php
// src/OpenApi/JwtDecorator.php

declare(strict_types=1);

namespace App\OpenApi;

use ApiPlatform\Core\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\Core\OpenApi\OpenApi;
use ApiPlatform\Core\OpenApi\Model;

final class JwtDecorator implements OpenApiFactoryInterface
{
    public function __construct(
        private OpenApiFactoryInterface $decorated
    ) {}

 
    public function __invoke(array $context = []): OpenApi
    {
        $openApi = ($this->decorated)($context);
        $schemas = $openApi->getComponents()->getSchemas();

 
        $schemas['Token'] = new \ArrayObject([
            'type' => 'object',
            'properties' => [
                'token' => [
                    'type' => 'string',
                    'readOnly' => true,
                ],
            ],
        ]);
        $schemas['Credentials'] = new \ArrayObject([
            'type' => 'object',
                'properties' => [
                'username' => [
                    'type' => 'string',
                    'example' => 'admin@bookapi.com',
                ],
                'password' => [
                    'type' => 'string',
                    'example' => 'password',
                ],
            ],
        ]);
 
        $pathItem = new Model\PathItem(
            ref: 'JWT Token',
            post: new Model\Operation(
                operationId: 'postCredentialsItem',
                tags: ['Token'],
                responses: [
                    '200' => [
                        'description' => 'Get JWT token',
                        'content' => [
                            'application/json' => [
                                'schema' => [
                                         '$ref' => '#/components/schemas/Token',
                                ],
                            ],
                        ],
                    ],
                ],
                summary: 'Get JWT token to login.',
                requestBody: new Model\RequestBody(
                    description: 'Generate new JWT Token',
                    content: new \ArrayObject([
                        'application/json' => [
                            'schema' => [
                                         '$ref' => '#/components/schemas/Credentials',
                            ],
                        ],
                    ]),
                ),
            ),
        );
        $openApi->getPaths()->addPath('/api/login_check', $pathItem);

        return $openApi;
    }
}

J’ai simplement changé les valeurs par défaut pour le login et le mot de passe (il faut désormais un username), et la route d’authentification. Ici j’ai repris la route /api/login_check pour plus de facilité, et surtout pour ne pas créer de conflit avec les deux systèmes qui tournent sur le même projet (ce qui n’est pas recommandé).

En résumé, il s’agit ici de reprendre le site créé par API Platform et de lui donner les informations nécessaires pour créer la route qui nous manque.

Il nous reste encore à faire en sorte que ce décorateur soit utilisé ; pour cela, nous devons aller dans le fichier service.yaml et rajouter ces lignes :

# config\services.yaml

    App\OpenApi\JwtDecorator:
        decorates: 'api_platform.openapi.factory'
        arguments: ['@.inner']

Et voilà, la gestion du token est désormais fonctionnelle. La route est disponible, le token peut maintenant être renseigné dans le champ Authorize, et les routes seront sécurisées et fonctionnelles !

Nous pouvons générer des tokens JWT et les renseigner dans Authorize pour nous authentifier sur API Platform.
Nous pouvons générer des tokens

Ce chapitre n’est évidemment qu’une introduction rapide à API Platform, cependant en quelques minutes nous avons réalisé un CRUD complet avec gestion de l’authentification, ce n’est pas rien !

Il est important de noter que les principes qu’API Platform met en œuvre sont exactement les mêmes que ceux que nous avons vus ; simplement ils ont été plus poussés et travaillés pour être particulièrement faciles à utiliser.

Dans tous les cas, je vous invite à nouveau, si vous voulez en savoir plus, à consulter la documentation sur le site d’API Platform (en anglais). 

En résumé

  • API Platform est basé sur les principes que nous avons vus jusqu’à présent, mais les automatise pour permettre de générer très rapidement une API complète et fonctionnelle.

  • API Platform est recommandé par Symfony, et hautement configurable. 

  • API Platform utilise en particulier Nelmio que nous avons déjà vu, mais possède ses propres moyens de configurer les routes et JWT. 

  • Même s’il est possible de mélanger API Platform et une API traditionnelle, c’est cependant déconseillé.

Félicitations, il ne vous reste plus qu’un petit quiz pour valider ce que vous avez compris, et ce cours sera complètement fini ! 

Bravo à vous qui êtes arrivé jusqu’ici !

Example of certificate of achievement
Example of certificate of achievement