Dans cet article nous allons voir comment créer une API rest avec FOS/rest-bundle, avec une authentification, et un générateur de documentation de type swagger.
Liste des bundle nécessaire
:
friendsofsymfony/rest-bundle : Fournis une
série d’outils d’aide au développement
d’une API restfull
https://github.com/FriendsOfSymfony/FOSRestBundle
jms/serializer-bundle
: Permet la sérialisation
d’objets.
https://packagist.org/packages/jms/serializer-bundle
lexik/jwt-authentication-bundle
: Permet la gestion de token web en json.
https://github.com/lexik/LexikJWTAuthenticationBundle
nelmio/NelmioApiDocBundle
: Permet de générer une documentation
HTML à la swagger
https://github.com/nelmio/NelmioApiDocBundle
Installation dans composer
On part du principe que l’on utilise notre skeleton comme base de départ.
composer require "nelmio/cors-bundle"
composer require "lexik/jwt-authentication-bundle"
composer require "jms/serializer-bundle"
composer require "friendsofsymfony/rest-bundle"
composer require "nelmio/api-doc-bundle"
composer require "annotations"
Il faudra ensuite enregistrer nos bundles dans bundles.php
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
FOS\RestBundle\FOSRestBundle::class => ['all' => true],
Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true],
Pour la documentation, il faudra ajouter la route :
# config/routes.yaml
app.swagger_ui:
path: /api/doc
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger_ui }
app.swagger:
path: /api/doc.json
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger }
Créer la configuration pour la documentation :
# config/packages/nelmio_api_doc.yaml
nelmio_api_doc:
areas:
path_patterns: # an array of regexps
- ^/api(?!/doc$)
host_patterns:
- ^api\.
Il faudra ensuite activer les converters de framework extra :
#/config/packages/sensio_framework_extra.yaml
sensio_framework_extra:
router:
annotations: false
request: { converters: true }
Création de notre première méthode de l’API
On va d’abords créer notre controller
bin/console make:controller api
Dans notre controller, nous devrons inclure notre librairie d’annotation de fosRestBundle pour y utiliser les bonnes annotations ainsi que les librairies NelmioApiDoc
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use FOS\RestBundle\Controller\Annotations\Get;
use JMS\Serializer\SerializationContext;
use Symfony\Component\HttpFoundation\Response;
use Swagger\Annotations as SWG;
use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Annotation\Security;
class ApiController extends AbstractController
{
/**
* @Get(
* path = "/api/test/{id}",
* name = "api_test_id",
* requirements = {"id"="\d+"}
* )
*/
public function index()
{
$detail=['test'=>'value'];
$serializer = $this->get('serializer');
$response = new Response(
$serializer->serialize(['detail' => $detail], 'json'),
Response::HTTP_OK
);
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
Pour gérer les code de réponses, nous devrons utiliser les annotations suivantes :
* @SWG\Response(
* response=200,
* @SWG\Schema(type="object",
* example={"foo": "bar", "hello": "world"}
* ),
* description="Response ok"
* )
* @SWG\Response(
* response=401,
* description="Access Denied"
* )
* @SWG\Response(
* response=403,
* description="Forbidden"
* )
* @SWG\Response(
* response=404,
* description="Not Found"
* )
Authentification de notre API
Pour l’authentification, on a choisi de faire simple. Pas une
authentification par l’entête de type Bearer, mais
juste une clef dans l’url.
C’est beaucoup plus
simple à tester, surtout lorsque vous allez travailler avec
des tiers, et que vous ne souhaitez pas faire du support dans tous
les sens et former leurs stagiaire. Même si ajouter une clef
dans l’entête, c’est franchement pas bien
compliqué. bref.
Donc on va déclarer notre clef dans les annotations de notre méthode.
* @SWG\Parameter(
* name="key",
* in="query",
* type="string",
* description="The authorization key"
* )
Couplé a la définition de notre api cela donne ceci :
/**
* @Get(
* path = "/api/test/{id}",
* name = "api_test_id",
* requirements = {"id"="\d+"}
* )
*
*
* @SWG\Parameter(
* name="key",
* in="query",
* type="string",
* description="The authorization key provided by HMF"
* )
Et dans le swagger cela se matérialise comme cela :
Personnalisation de la documentation
La documentation présente quelques problèmes
d’affichage du style, et notamment affiche un logo du
bundle, alors que l’on souhaites afficher le logo du
client.
Du coup, il suffit de surcharger le template en
créant un fichier twig ici
:
/templates/bundles/NelmioApiDocBundle/SwaggerUi/index.html.twig
{# templates/bundles/NelmioApiDocBundle/SwaggerUi/index.html.twig #}
{#
To avoid a "reached nested level" error an exclamation mark `!` has to be added
See https://symfony.com/blog/new-in-symfony-3-4-improved-the-overriding-of-templates
#}
{% extends '@!NelmioApiDoc/SwaggerUi/index.html.twig' %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('css/custom-swagger-styles.css') }}">
<style>
header #logo img{
height: unset;
}
header::before{
background-color: #FFFFFF;
}
.swagger-ui table tbody tr td, .response-col_description__inner{
padding: 0;
vertical-align: top;
}
.swagger-ui .markdown p{
margin: 10px auto;
}
</style>
{% endblock stylesheets %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript" src="{{ asset('js/custom-request-signer.js') }}"></script>
{% endblock javascripts %}
{% block header %}
<a id="logo" href="#"><img src="{{ asset('images/logo.png') }}" alt="Hyundai"></a>
{% endblock header %}
On voudras aussi ne permettre que le json et le teste en https, car
notre domaine est en https, et si l’utilisateur (le
stagiaire), teste en http alors que la doc est en https, le
navigateur bloquera la requête. C’est con, mais
c’est du vécus 😉
On va lui mettre un
titre, supprimer l’authentification Bearer, et supprimer la
vérification par host.
# config/packages/nelmio_api_doc.yaml
nelmio_api_doc:
areas:
path_patterns: # an array of regexps
- ^/api(?!/doc$)
# host_patterns:
# - ^api\.
#
documentation:
host: api.hyundai.test
#schemes: [http, https]
schemes: [https]
info:
title: Hyundai API
description: hyundai backend webservice
version: 1.0.0
#securityDefinitions:
# Bearer:
# type: apiKey
# description: 'Value: Bearer {jwt}'
# name: Authorization
# in: header
#security:
# - Bearer: []
Et voila ! on est prêt à développer notre
API.
Il nous resteras à créer un CRUD pour les
clefs, et appliquer notre logiques de données à
fournir pour chaque méthodes.