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"
* )
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:
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.