En este artículo, veremos cómo crear una API REST con FOS/RestBundle, incluyendo la autenticación y un generador de documentación similar a Swagger.

Lista de paquetes necesarios:
friendsofsymfony/rest-bundle: Ofrece un conjunto de herramientas para ayudar a desarrollar una API RESTful
https://github.com/FriendsOfSymfony/FOSRestBundle

jms/serializer-bundle: Permite la serialización de objetos.
https://packagist.org/packages/jms/serializer-bundle

lexik/jwt-authentication-bundle: Gestiona tokens web JSON.
https://github.com/lexik/LexikJWTAuthenticationBundle

nelmio/NelmioApiDocBundle: Genera documentación HTML estilo Swagger
https://github.com/nelmio/NelmioApiDocBundle

Instalación con composer

Vamos a asumir que estamos utilizando nuestro esqueleto como un punto de partida.

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"

Luego, necesitaremos registrar nuestros paquetes en 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],

Para la documentación, la ruta necesitará ser añadida:

# 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 }

Crea la configuración para la documentación:

# config/packages/nelmio_api_doc.yaml
nelmio_api_doc:
    areas:
        path_patterns: # an array of regexps
            - ^/api(?!/doc$)
        host_patterns:
            - ^api\.

Luego, debemos activar los convertidores extra del framework:

#/config/packages/sensio_framework_extra.yaml
sensio_framework_extra:
    router:
        annotations: false
    request: { converters: true }

Creación de nuestro primer método API

Primero, vamos a crear nuestro controlador

 bin/console make:controller api

En nuestro controlador, debemos incluir nuestra librería de anotaciones de FOSRestBundle para usar las anotaciones adecuadas, así como las librerías de 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;
    }
}

Para gestionar los códigos de respuesta, tendremos que usar las siguientes anotaciones:

     * @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"
     * )
Sélection_207

Autenticación de nuestra API

Para la autenticación, elegimos mantenerla simple. No una autenticación con cabecera del tipo Bearer, sino solo una clave en la URL.
Es mucho más simple para probar, especialmente cuando trabajas con terceros y no quieres brindar soporte en todas partes y capacitar a sus becarios. Aunque agregar una clave en la cabecera no es realmente tan complicado. De todos modos.

Así que declararemos nuestra clave en las anotaciones de nuestro método.

     * @SWG\Parameter(
     *     name="key",
     *     in="query",
     *     type="string",
     *     description="The authorization key"
     * )

Combinado con nuestra definición de la API, se verá así:

/**
 * @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"
 * )

Y en Swagger, se materializará de esta forma:

image

Personalización de la documentación

La documentación tiene algunos problemas de visualización, particularmente con el estilo, y muestra un logo del paquete cuando queremos mostrar el logo del cliente en su lugar.
Entonces, simplemente necesitamos sobrescribir la plantilla creando un archivo Twig aquí:
/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 %}

También queremos permitir solo JSON y pruebas en HTTPS, ya que nuestro dominio está en HTTPS, y si el usuario (el becario) prueba en HTTP mientras la documentación está en HTTPS, el navegador bloqueará la solicitud. Es tonto, pero es un problema del mundo real. 😊
Vamos a configurar también un título, eliminar la autenticación Bearer y eliminar la verificación del 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: []

¡Y eso es todo! Estamos listos para desarrollar nuestra API.
Lo que resta es crear un CRUD para las claves y aplicar nuestra lógica para los datos que se deben proporcionar para cada método.