Buy Access to Course
12.

Relacionar ApiResources personalizados

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Dentro de DailyQuest, añade una nueva propiedad: public array $treasures.

48 lines | src/ApiResource/DailyQuest.php
// ... lines 1 - 25
class DailyQuest
{
// ... lines 28 - 34
public array $treasures = [];
// ... lines 36 - 46
}

Ésta contendrá una serie de tesoros de dragón que puedes ganar si completas esta búsqueda: tesoros como un elegante sombrero de mago... una rana parlante... el segundo slinky más grande del mundo... ¡o las cuatro esquinas de un brownie! Mmmmmm...

Añadir una propiedad Relaciones de matriz

En la tierra de PHP, esto es como cualquier otra propiedad. En nuestro proveedor, rellénalo: $quest->treasures = ... y luego lo estableceremos a algo. En lugar de un aburrido array vacío, necesitamos algunos objetos DragonTreasure. Arriba, añade public function __construct() para autoconectar unprivate DragonTreasureRepository $treasureRepository.

54 lines | src/State/DailyQuestStateProvider.php
// ... lines 1 - 9
use App\Repository\DragonTreasureRepository;
class DailyQuestStateProvider implements ProviderInterface
{
public function __construct(
private DragonTreasureRepository $treasureRepository,
)
{
}
// ... lines 19 - 52
}

Abajo, coge algunos tesoros:$treasures = $this->treasureRepository->findBy() pasando una matriz vacía por los criterios - así lo devolverá todo - no orderBy, y un límite de 10.

54 lines | src/State/DailyQuestStateProvider.php
// ... lines 1 - 9
use App\Repository\DragonTreasureRepository;
class DailyQuestStateProvider implements ProviderInterface
{
public function __construct(
private DragonTreasureRepository $treasureRepository,
)
{
}
// ... lines 19 - 30
private function createQuests(): array
{
$treasures = $this->treasureRepository->findBy([], [], 10);
// ... lines 34 - 51
}
}

Sí, sólo encontraremos los 10 primeros tesoros de la base de datos. Voy a pegar un código aburrido que cogerá un conjunto aleatorio de estos objetos DragonTreasure. Ponlo en la propiedad treasures.

54 lines | src/State/DailyQuestStateProvider.php
// ... lines 1 - 9
use App\Repository\DragonTreasureRepository;
class DailyQuestStateProvider implements ProviderInterface
{
public function __construct(
private DragonTreasureRepository $treasureRepository,
)
{
}
// ... lines 19 - 30
private function createQuests(): array
{
$treasures = $this->treasureRepository->findBy([], [], 10);
// ... lines 34 - 35
for ($i = 0; $i < 50; $i++) {
// ... lines 37 - 43
$randomTreasuresKeys = array_rand($treasures, rand(1, 3));
$randomTreasures = array_map(fn($key) => $treasures[$key], (array) $randomTreasuresKeys);
$quest->treasures = $randomTreasures;
// ... lines 47 - 48
}
// ... lines 50 - 51
}
}

¡Genial! Y, aunque ahora mismo no nos importe, para asegurarnos de que nuestra prueba sigue pasando, aquí arriba, añade DragonTreasureFactory::createMany(5)... porque si hay cero tesoros, pasarán cosas raras en nuestro proveedor... y los dragones escenificarán su ardiente levantamiento.

// ... lines 1 - 4
use App\Factory\DragonTreasureFactory;
// ... lines 6 - 8
class DailyQuestResourceTest extends ApiTestCase
{
// ... lines 11 - 13
public function testPatchCanUpdateStatus()
{
// quests need at least some treasures to be available
DragonTreasureFactory::createMany(5);
// ... lines 18 - 29
}
}

Vale, ¿aparece esta nueva propiedad en nuestra API? Dirígete a /api/quests.jsonld para ver.. un error familiar:

Debes llamar a setIsOwnedByAuthenticatedUser() antes que a isOwnedByAuthenticatedUser().

Lo sabemos: viene de DragonTreasure... hasta el final.

278 lines | src/Entity/DragonTreasure.php
// ... lines 1 - 93
class DragonTreasure
{
// ... lines 96 - 263
public function isOwnedByAuthenticatedUser(): bool
{
if (!isset($this->isOwnedByAuthenticatedUser)) {
throw new \LogicException('You must call setIsOwnedByAuthenticatedUser() before isOwnedByAuthenticatedUser()');
}
return $this->isOwnedByAuthenticatedUser;
}
// ... lines 272 - 276
}

Aparentemente, el serializador está intentando acceder a este campo, pero nunca lo establecemos... lo cual tiene sentido... porque el proveedor y el procesador para DragonTreasure no se llaman cuando estamos utilizando una ruta DailyQuest.

Por qué la relación está incrustada

Pero... espera un segundo. Esto ni siquiera debería ser un problema. Deja que te muestre lo que quiero decir. Para silenciar temporalmente este error, y entender lo que está pasando, busca esa propiedad... ahí está... y dale un valor por defecto de false.

278 lines | src/Entity/DragonTreasure.php
// ... lines 1 - 93
class DragonTreasure
{
// ... lines 96 - 147
private bool $isOwnedByAuthenticatedUser = false;
// ... lines 149 - 276
}

Gira, actualiza y... ¡whoa! ¡Funciona! Aquí está nuestra búsqueda diaria... y aquí están los tesoros. Pero... esto no es, exactamente, lo que esperábamos. Cada tesoro es un objeto incrustado.

Recuerda: cuando tienes una relación con un objeto que es un ApiResource, comoDragonTreasure, ese objeto sólo debe estar incrustado si la clase padre y la clase hija comparten grupos de serialización. Por ejemplo, si tuviéramos normalizationContextcon groups establecido en quest:read así... donde el grupo quest:read está por encima de$treasures, y, en DragonTreasure, tuviéramos al menos una propiedad que también tuviera quest:read.

Pero, si no te encuentras en esta situación -demonios, no estamos utilizando grupos en absoluto-, entonces el serializador debería representar cada DragonTreasure como una cadena IRI. Debería ser una matriz de cadenas, ¡no objetos incrustados!

El problema es que el serializador mira esta propiedad $treasures y no se da cuenta de que contiene una matriz de objetos DragonTreasure. Sabe que es una matriz, pero antes de empezar a serializar, no sabe qué hay dentro. Y así, en lugar de enviarlos a través del sistema que serializa los objetos ApiResource, los envía a través del código que serializa los objetos normales... lo que hace que sólo serialice todas las propiedades.

Esto no es un problema con las entidades porque el serializador es inteligente: lee los metadatos de la relación Doctrine para averiguar que una propiedad es una colección de algún otro objeto #[ApiResource]. Resumiendo, esto es sencillo de arreglar... sólo que al principio es difícil de entender. Sobre la propiedad, añade algo de PHPDoc para ayudar al serializador: @var DragonTreasure[].

52 lines | src/ApiResource/DailyQuest.php
// ... lines 1 - 9
use App\Entity\DragonTreasure;
// ... lines 11 - 26
class DailyQuest
{
// ... lines 29 - 35
/**
* @var DragonTreasure[]
*/
public array $treasures = [];
// ... lines 40 - 50
}

Pruébalo ahora... ¡bam! ¡Obtenemos cadenas IRI! No me molestaré, pero podríamos deshacer el valor por defecto que añadimos porque este objeto no se serializará... que es lo que nos dio este error en primer lugar.

Así que, aparte de la sorpresa del objeto incrustado, ¡añadir relaciones a nuestro recurso personalizado no es gran cosa! A continuación: en lugar de incrustar directamente objetos DragonTreasure, vamos a ver cómo podemos inventar una nueva clase y una nueva estructura de datos para representar estos tesoros.