Buy Access to Course
03.

Decorar el proveedor de estado principal

|

Share this awesome video!

|

Para rellenar la propiedad no persistente de nuestra entidad, utilizaremos un proveedor de estado personalizado. Crea uno con:

php bin/console make:state-provider

Llamémoslo DragonTreasureStateProvider.

Gira y abre esto en src/State/. Vale, implementa un ProviderInterfaceque requiere un método: provide(). Nuestro trabajo consiste en devolver el objeto DragonTreasurepara la petición actual de la API, que en nuestra prueba es una petición Patch.

15 lines | src/State/DragonTreasureStateProvider.php
// ... lines 1 - 7
class DragonTreasureStateProvider implements ProviderInterface
{
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
// Retrieve the state from somewhere
}
}

Antes de pensar en hacerlo, dd($operation) para que podamos ver si se ejecuta. Cuando probamos la prueba... la respuesta es que no se llama. Obtenemos el mismo error que antes.

15 lines | src/State/DragonTreasureStateProvider.php
// ... lines 1 - 9
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
dd($operation);
}
// ... lines 14 - 15

Así pues, crear un proveedor de estado e implementar ProviderInterface no es suficiente para que se utilice nuestra clase. ¡Y esto es genial! Podemos controlar esto recurso por recurso... o incluso operación por operación.

En DragonTreasure, muy arriba, dentro del atributo ApiResource, añadeprovider y luego el ID del servicio, que es la clase en nuestro caso:DragonTreasureStateProvider::class.

275 lines | src/Entity/DragonTreasure.php
// ... lines 1 - 19
use App\State\DragonTreasureStateProvider;
// ... lines 21 - 30
#[ApiResource(
// ... lines 32 - 64
provider: DragonTreasureStateProvider::class,
// ... lines 66 - 68
)]
// ... lines 70 - 90
class DragonTreasure
// ... lines 92 - 275

Así que ahora, siempre que API Platform necesite "cargar" un tesoro dragón, llamará a nuestro proveedor. Y nuestra prueba es un ejemplo perfecto. Cuando hagamos una petición a PATCH, lo primero que hará la API Platform será pedir al proveedor de estado que cargue este tesoro. Luego lo actualizará utilizando el JSON.

Observa, cuando ahora ejecutemos la prueba

symfony php bin/phpunit --filter=testOwnerCanSeeIsPublishedAndIsMineFields

¡Llegamos al vertedero!

Decorar el proveedor

Pero... No quiero hacer todo el trabajo de consultar la base de datos en busca de los tesoros del dragón... ¡porque ya existe un proveedor de entidades básico que hace todo eso! Así que ¡utilicémoslo!

Añade un constructor... oh y por ahora lo mantendré en dd(). Añade un argumento privado ProviderInterface $itemProvider.

19 lines | src/State/DragonTreasureStateProvider.php
// ... lines 1 - 5
use ApiPlatform\State\ProviderInterface;
// ... line 7
class DragonTreasureStateProvider implements ProviderInterface
{
public function __construct(private ProviderInterface $itemProvider)
{
}
// ... lines 13 - 17
}

Como recordatorio: las operaciones Get uno, Patch, Put y Delete utilizan todas ItemProvider, que sabe consultar un único elemento. Como nuestra prueba utilizaPatch, vamos a centrarnos primero en utilizar ese proveedor.

Si ejecutamos la prueba ahora, falla. El error es

No se puede autoconectar el servicio DragonTreasureStateProvider: argumento itemProvider hace referencia a ProviderInterface, pero no existe tal servicio.

A menudo en Symfony, si hacemos una sugerencia de tipo a una interfaz, Symfony nos pasará lo que necesitamos. Pero en el caso de ProviderInterface, hay múltiples servicios que implementan esto - incluyendo el núcleo ItemProvider y CollectionProvider.

Esto significa que tenemos que decirle a Symfony cuál queremos. Hazlo con el práctico atributo#[Autowire] con service ajustado a ItemProvider::class - asegúrate de obtener el de ORM.

23 lines | src/State/DragonTreasureStateProvider.php
// ... lines 1 - 7
use Symfony\Component\DependencyInjection\Attribute\Autowire;
// ... line 9
class DragonTreasureStateProvider implements ProviderInterface
{
public function __construct(
#[Autowire(service: ItemProvider::class)] private ProviderInterface $itemProvider
)
{
}
// ... lines 17 - 21
}

Y ¡sí! Es un identificador de servicio válido. También hay un identificador de servicio más difícil de recordar, pero API Platform proporciona un alias de servicio para que podamos utilizarlo. ¡Encantador!

Vale, ¡a probar! ¡Sí! Hemos llegado al vertedero, lo que significa que se ha inyectado el proveedor de elementos. Así que ahora, somos peligrosos. $treasure es igual a $this->itemProvider->provide()pasando los 3 args.

32 lines | src/State/DragonTreasureStateProvider.php
// ... lines 1 - 18
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
$treasure = $this->itemProvider->provide($operation, $uriVariables, $context);
// ... lines 22 - 29
}
// ... lines 31 - 32

En este punto, $treasure será null o un objeto valioso DragonTreasure. Si no es una instancia de DragonTreasure, devuelve null.

Pero si es un tesoro, ¡ya está! Llama a setIsOwnedByAuthenticatedUser()y codifica por ahora verdadero. Luego devuelve $treasure.

32 lines | src/State/DragonTreasureStateProvider.php
// ... lines 1 - 18
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
// ... lines 21 - 22
if (!$treasure instanceof DragonTreasure) {
return $treasure;
}
$treasure->setIsOwnedByAuthenticatedUser(true);
return $treasure;
}
// ... lines 31 - 32

Vale, ¡a probar!

symfony php bin/phpunit --filter=testOwnerCanSeeIsPublishedAndIsMineFields

¡Shazam! ¡Estamos en verde! Así que vamos a establecer ese valor de verdad. Esto es bastante fácil: añade un argumentoprivate Security... y asegúrate de que el primer arg tiene una coma.

Entonces esto es cierto si $this->security->getUser() es igual a $treasure->getOwner().

34 lines | src/State/DragonTreasureStateProvider.php
// ... lines 1 - 11
class DragonTreasureStateProvider implements ProviderInterface
{
public function __construct(
// ... line 15
private Security $security,
)
{
}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
// ... lines 23 - 28
$treasure->setIsOwnedByAuthenticatedUser($this->security->getUser() === $treasure->getOwner());
return $treasure;
}
}

Y... entonces... la prueba sigue pasando. ¡Campo personalizado conseguido! Y, lo más importante, está documentado dentro de nuestra API.

Sin embargo, acabamos de romper nuestra ruta GetCollection. Vamos a arreglarlo a continuación.