Chapters
-
Course Code
Subscribe to download the code!
Subscribe to download the code!
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
¡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.
10 Comments
That's because your api_platform.yaml doesn't contain format for json
api_platform:
title: Hello API Platform
version: 1.0.0
formats:
jsonld: ['application/ld+json']
json: ['application/json']
you should add accept method
json: ['application/json']
Hey @Nizar
If you remove the .json
part does it work? I believe the main docs route does not support file extensions (but the API endpoints do)
Cheers!
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
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!
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
Hi,
Why api platform now gives duplicate properties from hydra docs?
For example, my expected JSON POST input looks like this:
{
"first_name": "string",
"last_name": "string",
"email": "string",
"created_at": "2024-05-04T13:57:39.582Z",
"firstName": "string",
"lastName": "string",
"createdAt": "2024-05-04T13:57:39.582Z"
}
instead of this:
{
"first_name": "string",
"last_name": "string",
"email": "string",
"created_at": "2024-05-04T13:57:39.582Z"
}
I've tried setting up symfony and api platform over and over again, generating the entity via maker bundle, the same things happen.
Thanks in advance!
Hey @Zhivko
That's odd. If you look closely the repeated fields are in snake and camel case. I wonder if you may have both fields in your API resource, or for some reason, ApiPlatform is using both conventions to serialize your resources. I'd guess this is a misconfiguration of ApiPlatform
Cheers!
Hi @MolloKhan ,
Thanks for the answer.
Still not sure how to configure API Platform to avoid this duplication?
Here is my ApiResource enabled Symfony Entity class:
<?php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
#[ApiResource]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
/**
* This is the first name of a user
*/
#[ORM\Column(length: 50, nullable: true)]
private ?string $first_name = null;
#[ORM\Column(length: 50, nullable: true)]
private ?string $last_name = null;
#[ORM\Column(length: 255)]
private ?string $email = null;
#[ORM\Column]
private ?\DateTimeImmutable $created_at = null;
public function getId(): ?int
{
return $this->id;
}
public function getFirstName(): ?string
{
return $this->first_name;
}
public function setFirstName(?string $first_name): static
{
$this->first_name = $first_name;
return $this;
}
public function getLastName(): ?string
{
return $this->last_name;
}
public function setLastName(?string $last_name): static
{
$this->last_name = $last_name;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): static
{
$this->email = $email;
return $this;
}
public function getCreatedAt(): ?\DateTimeImmutable
{
return $this->created_at;
}
public function setCreatedAt(\DateTimeImmutable $created_at): static
{
$this->created_at = $created_at;
return $this;
}
}
As you can see, nothing special, normally created Entity via the Maker bundle.
To me, it seems like API Platform serializes the get methods as well, but why?
Regards,
Zhivko
That's interesting. It could be a bug on ApiPlatform but I'm not sure. I believe the problem happens because you're mixing naming conventions (snake case and camel case). I think the easiest solution would be to stick to one convention, or you can use serialization groups to choose what fields to process
More info here: https://api-platform.com/docs/core/serialization/
Cheers!
"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
}
}
Hi
when I type the following url: https://localhost:8000/api/docs.json in my browser I get the following error:
"title": "An error occurred",
"detail": "Format \"json\" is not supported",
could you help me please ?