If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeCuando intentamos crear un DragonTreasure
con este owner
, establecimos el campo con el id de la base de datos del propietario. Y nos dimos cuenta de que a API Platform no le gustaba eso. Decía "IRI esperado". Pero, ¿qué es un IRI
?
Ya mencionamos este término una vez en el tutorial. Vuelve al punto final de la colección GET /api/users
. Sabemos que cada recurso tiene un campo @id
establecido en la URL donde puedes obtener ese recurso. Esto es el IRI o "Identificador Internacional de Recursos". Está pensado para ser un identificador único en toda tu API, como en todos los recursos.
Piénsalo: el número "1" no es un identificador único -podríamos tener unDragonTreasure
con ese id y un User
. Pero el IRI es único. Y, además, una URL es mucho más manejable que un número entero.
Así que cuando queramos establecer una propiedad de relación, tendremos que utilizar también el IRI, como/api/users/1
.
Cuando pulsamos Ejecutar, ¡funciona! Un código de estado 201
. En el JSON devuelto, no es de extrañar, el campo owner
también aparece como un IRI.
Las conclusiones de todo esto son deliciosamente sencillas. Las relaciones son campos normales... pero las obtenemos y establecemos a través de su cadena IRI. Es una forma muy bonita y limpia de manejarlo.
Bien, hablemos del otro lado de esta relación. Actualiza toda la página y ve a la ruta GET
one user endpoint. Inténtalo con un identificador de usuario real, como el 1 para mí. Y... ahí están los datos.
Así que la pregunta que me hago ahora es: ¿podríamos añadir un campo dragonTreasures
que muestre todos los tesoros que posee este usuario?
Bueno, vamos a pensarlo. Sabemos que el serializador funciona cogiendo todas las propiedades accesibles de un objeto que están en el grupo de normalización. Y... tenemos una propiedad dragonTreasures
en User
.
... lines 1 - 22 | |
class User implements UserInterface, PasswordAuthenticatedUserInterface | |
{ | |
... lines 25 - 50 | |
#[ORM\OneToMany(mappedBy: 'owner', targetEntity: DragonTreasure::class)] | |
private Collection $dragonTreasures; | |
... lines 53 - 169 | |
} |
Así que... ¡debería funcionar! Para exponer el campo a la API, añádelo al grupo de serialización user:read
. Más adelante, hablaremos de cómo podemos escribir en un campo de colección... pero por ahora, basta con hacerlo legible.
... lines 1 - 22 | |
class User implements UserInterface, PasswordAuthenticatedUserInterface | |
{ | |
... lines 25 - 50 | |
#[ORM\OneToMany(mappedBy: 'owner', targetEntity: DragonTreasure::class)] | |
'user:read']) ([ | |
private Collection $dragonTreasures; | |
... lines 54 - 170 | |
} |
Vale Actualiza... y mira la misma ruta GET
. Aquí abajo, ¡genial! Muestra un nuevo campo dragonTreasures
en la respuesta del ejemplo. Vamos a probarlo: utiliza el mismo id, pulsa "Ejecutar" y... ¡oh, estupendo: devuelve una matriz de cadenas IRI! ¡Me encanta! Y, por supuesto, si necesitamos más información sobre ellas, podemos hacer una petición a cualquiera de estas URL para obtener todos los detalles brillantes.
Y para ser realmente extravagante, podrías utilizar Vulcain para que los usuarios puedan "precargar" esas relaciones... lo que significa que el servidor enviará los datos directamente al cliente.
Pero aunque esto es genial, me lleva a una pregunta: ¿y si necesitar los datos deDragonTreasure
para un usuario es tan habitual que, para evitar peticiones adicionales, queremos incrustar los datos aquí mismo, como objetos JSON en lugar de cadenas IRI?
¿Podemos hacerlo? Por supuesto que sí. Averigüemos cómo a continuación.
// 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
}
}
No subtitles on video? :(