Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Swagger UI: Documentación interactiva

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

¡La increíble documentación interactiva con la que nos hemos topado no es algo de la API Platform! No, en realidad es una biblioteca de documentación de API de código abierto llamada Swagger UI. Y lo realmente genial de Swagger UI es que, si alguien crea un archivo que describa cualquier API, ¡esa API puede obtener todo esto gratis! ¡Me encantan las cosas gratis! Obtenemos Swagger UI porque la API Platform proporciona ese archivo de descripción de forma inmediata. Pero hablaremos de ello más adelante.

Jugando con nuestra nueva API

Vamos a jugar con esto. Utiliza la ruta POST para crear un nuevo DragonTreasure. Recientemente hemos saqueado unas "Monedas de oro"... que obtuvimos de "Rico McPato". Está loco. Para nuestros propósitos, ninguno de los otros campos importa realmente. Aquí abajo, pulsa "Ejecutar" y... ¡boom! Cuando te desplaces hacia abajo, podrás ver que se ha realizado una petición POST a /api/dragon_treasures y se han enviado todos los datos como JSON Entonces, nuestra API devolvió un código de estado "201". Un estado 201 significa que la petición tuvo éxito y se creó un recurso. Luego devolvió este JSON, que incluye un id de 1. Así que, como he dicho, esto no es sólo documentación: ¡realmente tenemos una API que funciona! Aquí también hay algunos campos adicionales: @context, @id, y @type De ellos hablaremos pronto.

Ahora que tenemos un DragonTreasure con el que trabajar, abre esta ruta "GET", haz clic en "Probar" y luego en "Ejecutar". Me encanta. Swagger acaba de hacer una petición GET a /api/dragon_treasures - este ?page=1 es opcional. Nuestra API devolvió información dentro de algo llamado hydra:member, que aún no es especialmente importante. Lo que importa es que nuestra API devolvió una lista de todos los DragonTreasures que tenemos actualmente, que es justo éste.

Así que, en sólo unos minutos de trabajo, tenemos una API completa para nuestra entidad Doctrine. Eso es genial.

Negociación del contenido

Copia la URL de la ruta de la API, abre una nueva pestaña y pégala. ¡Guau! Esto... ¿ha devuelto HTML? Pero hace un segundo, Swagger dijo que hizo una petición GET a esa URL... y devolvió JSON. ¿Qué está pasando?

Una característica de la API Platform se llama "Negociación de contenido". Significa que nuestra API puede devolver el mismo recurso -como DragonTreasure - en varios formatos, como JSON, o HTML... o incluso cosas como CSV. Un formato ASCII sería genial. En cualquier caso, le decimos a la API Platform qué formato queremos pasando una cabecera Accept en la petición. Cuando utilizamos los documentos interactivos, nos pasa esta cabecera Accept configurada como application/ld+json. Pronto hablaremos de la parte ld+json... pero, gracias a esto, ¡nuestra API devuelve JSON!

Y aunque no lo veamos aquí, cuando vas a una página en tu navegador, éste envía automáticamente una cabecera Accept que dice que queremos text/html. Así que esto es la API Platform mostrándonos la "representación HTML" de nuestros tesoros dragón..., que no es más que la documentación. Observa: cuando abro la ruta para la que está esta URL, la ejecuta automáticamente.

La cuestión es: si queremos ver la representación JSON de nuestros tesoros dragón, tenemos que pasar esta cabecera Accept... lo cual es superfácil, por ejemplo, si estás escribiendo JavaScript.

Pero pasar una cabecera personalizada Accept no es tan fácil en un navegador... y estaría bien poder ver la versión JSON de esto. Afortunadamente, la API Platform nos da una forma de hacer trampas. Elimina el ?page=1 para simplificar las cosas. Luego, al final de cualquier ruta, puedes añadir . seguido de la extensión del formato que quieras: como .jsonld.

Ahora vemos el recurso DragonTreasure en ese formato. La API Platform también admite JSON normal de fábrica, así que podemos ver lo mismo, pero en JSON puro y estándar.

¿De dónde vienen las nuevas Rutas?

El hecho de que todo esto funcione significa que... aparentemente tenemos una nueva ruta para /api, así como un montón de otras rutas nuevas para cada operación -como GET /api/dragon_treasures. Pero... ¿de dónde vienen? ¿Cómo se añaden dinámicamente a nuestra aplicación?

Para responder a esto, ve a tu terminal y ejecuta:

./bin/console debug:router

Haré esto un poco más pequeño para que podamos verlo todo. ¡Sí! Cada ruta está representada por una ruta normal, tradicional. ¿Cómo se añaden? Cuando instalamos la API Platform, su receta añadió un archivo config/routes/api_platform.yaml. Esto es en realidad una importación de rutas. Parece un poco raro, pero activa la API Platform cuando el sistema de rutas se está cargando. A continuación, la API Platform encuentra todos los recursos API de nuestra aplicación y genera una ruta para cada ruta.

La cuestión es que lo único en lo que tenemos que centrarnos es en crear estas bonitas clases PHP y decorarlas con ApiResource. La API Platform se encarga de todo el trabajo pesado de conectar esas rutas. Por supuesto, tendremos que ajustar la configuración y hablar de cosas más avanzadas, pero ¡eh! Ese es el objetivo de este tutorial. Y ya hemos tenido un comienzo épico.

Lo siguiente: Quiero hablar del secreto que hay detrás de cómo se genera esta documentación Swagger UI. Se llama OpenAPI.

Leave a comment!

3
Login or Register to join the conversation

Hi Ryan,

I have an issue. I'm using mysql instead of postgresql (created a docker standard mysql 8 container from mysql:latest).
I was able to create the entity with the maker bundle, the migration, create the database (./bin/console doctrine:database:create) and execute the migration using CLI commands (./bin/console doctrine:migrations:migrate). The database was created and the tables as well.
All steps went successfully until I tried to submit a post request from the https://127.0.0.1:8000/api. I get the following error:

An exception occurred in the driver: SQLSTATE[HY000] [1049] Unknown database 'root'

The problem is that for some reason it is trying to query a database named root, when in fact the database that was earlier created is called app.

Created database app for connection named default
was the message I got after executing ./bin/console doctrine:database:create

In my .env file I have:

DATABASE_URL="mysql://root:testpassword@127.0.0.1:3306/app?serverVersion=8&charset=utf8mb4"

Any thoughts on this one?

Thanks,
Radu

Reply

Hey Radu!

Hmm. Do you have a docker-compose.yml file? If you do and you have Docker running AND you have a container called database, then when you access the site, the symfony binary is overriding the DATABASE_URL env var and setting it to point at that Docker container. If you're setting up your database locally (without Docker), just don't bother starting Docker and delete the docker-compose.yml file entirely (or at least the matching service).

If my guess is incorrect, let me know!

Cheers!

1 Reply

Hey @weaverryan,

Thanks for the quick reply! You were indeed correct! I've recreated my local container after renaming the database service in the docker-compose.yml file to my_app_database and the issue went away! :)

Cheers,
Radu

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^3.0", // v3.0.8
        "doctrine/annotations": "^1.0", // 1.14.2
        "doctrine/doctrine-bundle": "^2.8", // 2.8.0
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.14", // 2.14.0
        "nelmio/cors-bundle": "^2.2", // 2.2.0
        "nesbot/carbon": "^2.64", // 2.64.1
        "phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
        "phpstan/phpdoc-parser": "^1.15", // 1.15.3
        "symfony/asset": "6.2.*", // v6.2.0
        "symfony/console": "6.2.*", // v6.2.3
        "symfony/dotenv": "6.2.*", // v6.2.0
        "symfony/expression-language": "6.2.*", // v6.2.2
        "symfony/flex": "^2", // v2.2.4
        "symfony/framework-bundle": "6.2.*", // v6.2.3
        "symfony/property-access": "6.2.*", // v6.2.3
        "symfony/property-info": "6.2.*", // v6.2.3
        "symfony/runtime": "6.2.*", // v6.2.0
        "symfony/security-bundle": "6.2.*", // v6.2.3
        "symfony/serializer": "6.2.*", // v6.2.3
        "symfony/twig-bundle": "6.2.*", // v6.2.3
        "symfony/ux-react": "^2.6", // v2.6.1
        "symfony/validator": "6.2.*", // v6.2.3
        "symfony/webpack-encore-bundle": "^1.16", // v1.16.0
        "symfony/yaml": "6.2.*" // v6.2.2
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "symfony/debug-bundle": "6.2.*", // v6.2.1
        "symfony/maker-bundle": "^1.48", // v1.48.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/stopwatch": "6.2.*", // v6.2.0
        "symfony/web-profiler-bundle": "6.2.*", // v6.2.4
        "zenstruck/foundry": "^1.26" // v1.26.0
    }
}
userVoice