Relaciones incrustadas
Keep on Learning!
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 SubscribeAsí que cuando dos recursos están relacionados en nuestra API, aparecen como una cadena IRI, o colección de cadenas. Pero podrías preguntarte:
Oye, ¿podríamos incluir los datos de
DragonTreasure
aquí mismo en lugar del IRI para que no tenga que hacer una segunda, tercera o cuarta petición para obtener esos datos?
Por supuesto Y, de nuevo, también puedes hacer algo realmente genial con Vulcain... pero aprendamos a incrustar datos.
Incrustar Vs IRI mediante Grupos de Normalización
Cuando se serializa el objeto User
, utiliza los grupos de normalización para determinar qué campos incluir. En este caso, tenemos un grupo llamadouser:read
. Por eso se devuelven email
, username
y dragonTreasures
.
// ... lines 1 - 16 | |
( | |
normalizationContext: ['groups' => ['user:read']], | |
// ... line 19 | |
) | |
// ... lines 21 - 22 | |
class User implements UserInterface, PasswordAuthenticatedUserInterface | |
{ | |
// ... lines 25 - 30 | |
'user:read', 'user:write']) | ([|
// ... lines 32 - 33 | |
private ?string $email = null; | |
// ... lines 35 - 46 | |
'user:read', 'user:write']) | ([|
// ... line 48 | |
private ?string $username = null; | |
// ... lines 50 - 51 | |
'user:read']) | ([|
private Collection $dragonTreasures; | |
// ... lines 54 - 170 | |
} |
Para transformar la propiedad dragonTreasures
en datos incrustados, tenemos que ir aDragonTreasure
y añadir este mismo grupo user:read
al menos a un campo. Observa: encima de name
, añade user:read
. Luego... ve hacia abajo y añade también esto para value
.
// ... lines 1 - 51 | |
class DragonTreasure | |
{ | |
// ... lines 54 - 59 | |
'treasure:read', 'treasure:write', 'user:read']) | ([|
// ... lines 61 - 63 | |
private ?string $name = null; | |
// ... lines 65 - 75 | |
'treasure:read', 'treasure:write', 'user:read']) | ([|
// ... lines 77 - 78 | |
private ?int $value = 0; | |
// ... lines 80 - 209 | |
} |
Sí, en cuanto tengamos al menos una propiedad dentro de DragonTreasure
que esté en el grupo de normalización user:read
, el aspecto del campo dragonTreasures
cambiará totalmente.
Observa: cuando lo ejecutemos... ¡impresionante! En lugar de una matriz de cadenas IRI, es una matriz de objetos, con name
y value
... y, por supuesto, los campos normales @id
y @type
.
Así que: cuando tengas un campo de relación, se representará como una cadena IRI o como un objeto... y esto depende totalmente de tus grupos de normalización.
Incrustar en la otra dirección
Intentemos esto mismo en la otra dirección. Tenemos un treasure
cuyo id es 2. Dirígete a la ruta GET un único tesoro... pruébalo... e introduce 2 para el id.
Sin sorpresa, vemos owner
como una cadena IRI. ¿Podríamos convertirla en un objeto incrustado? ¡Por supuesto! Sabemos que DragonTreasure
utiliza el grupo de normalización treasure:read
. Así que, entra en User
y añádelo a la propiedad username
:treasure:read
.
// ... lines 1 - 22 | |
class User implements UserInterface, PasswordAuthenticatedUserInterface | |
{ | |
// ... lines 25 - 46 | |
'user:read', 'user:write', 'treasure:read']) | ([|
// ... line 48 | |
private ?string $username = null; | |
// ... lines 50 - 170 | |
} |
Sólo con ese cambio... cuando lo probemos... ¡sí! ¡El campo owner
acaba de transformarse en un objeto incrustado!
Incrustado para una ruta, IRI para otra
Vale, vamos a buscar también una colección de treasures
: sólo hay que pedirlos todos. Gracias al cambio que acabamos de hacer, la propiedad owner
de cada tesoro es ahora un objeto.
Esto me da una idea descabellada. ¿Y si disponer de toda la información de owner
cuando obtengo un único DragonTreasure
está bien... pero tal vez resulte exagerado que esos datos se devuelvan desde la ruta de recogida? ¿Podríamos incrustar owner
al obtener un único treasure
... pero utilizar la cadena IRI al obtener una colección?
La respuesta es... ¡no! Estoy bromeando, ¡por supuesto! ¡Podemos hacer las locuras que queramos! Aunque, cuantas más cosas raras añadas a tu API, más complicada se vuelve la vida. ¡Así que elige bien tus aventuras!
Hacer esto es un proceso de dos pasos. Primero, en DragonTreasure
, busca la operación Get
, que es la operación para obtener un único tesoro. Una de las opciones que puedes pasar a una operación es normalizationContext
... que anulará la predeterminada. Añade normalizationContext
, luego groups
ajustado al estándar treasure:read
. A continuación, añade un segundo grupo específico para esta operación: treasure:item:get
.
// ... lines 1 - 25 | |
( | |
// ... lines 27 - 28 | |
operations: [ | |
new Get( | |
normalizationContext: [ | |
'groups' => ['treasure:read', 'treasure:item:get'], | |
], | |
), | |
// ... lines 35 - 38 | |
], | |
// ... lines 40 - 53 | |
) | |
// ... line 55 | |
class DragonTreasure | |
{ | |
// ... lines 58 - 213 | |
} |
Puedes llamarlo como quieras... pero a mí me gusta esta convención: nombre del recurso seguido de item
o collection
y luego el método HTTP, como get
o post
.
Y sí, olvidé la clave groups
: lo arreglaré en un minuto.
En cualquier caso, si hubiera codificado esto correctamente, significaría que cuando se utilice esta operación, el serializador incluirá todos los campos que estén al menos en uno de estos dos grupos.
Ahora podemos aprovechar eso. Copia el nuevo nombre del grupo. Luego, en User
, encima deusername
, en lugar de treasure:read
, pega ese nuevo grupo.
// ... lines 1 - 22 | |
class User implements UserInterface, PasswordAuthenticatedUserInterface | |
{ | |
// ... lines 25 - 46 | |
'user:read', 'user:write', 'treasure:item:get']) | ([|
// ... line 48 | |
private ?string $username = null; | |
// ... lines 50 - 170 | |
} |
¡Vamos a comprobarlo! Prueba de nuevo con la ruta GET. ¡Sí! Volvemos a owner
que es una cadena IRI. Y si probamos con el punto final GET uno... oh, el propietario es... ¿también un IRI aquí? Culpa mía. Volviendo a normalization_context
olvidé decir groups
. Básicamente estaba poniendo dos opciones sin sentido ennormalization_context
.
Intentémoslo de nuevo. Esta vez... ¡lo tengo!
Cuando te pones así, es un poco más difícil saber qué grupos de serialización se están utilizando y cuándo. Aunque puedes utilizar el Perfilador para ayudarte con eso. Por ejemplo, ésta es nuestra petición más reciente para el tesoro único.
Si abrimos el perfilador para esa petición... y bajamos a la sección Serializador, vemos los datos que se están serializando... pero, lo que es más importante, el contexto de normalización... incluido groups
establecido en los dos que esperamos.
Esto también es genial porque puedes ver otras opciones de contexto que establece la API Platform. Éstas controlan ciertos comportamientos internos.
Siguiente: vamos a volvernos locos con nuestras relaciones utilizando una ruta DragonTreasure
para cambiar el campo username
del propietario de ese tesoro. Woh.
Hi, someone else know why normalizationContext & denormalizationContext don't work with Symfony 7 et API Plateform 3.2 ?
When i add this :
API Platform return to me :
If i remove normalization i have :
My entity :
Thanks a lot !