Entidad -> DTO Proveedor de estado del elemento
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 Subscribe¿Qué pasa con la ruta del elemento? Si vamos a /api/users/6.jsonld
... parece que funciona... ¡pero es una trampa! Es sólo el formato de colección... ¡con un único elemento!
Sabemos que hay dos proveedores principales: CollectionProvider
y un proveedor de ítems, cuyo trabajo es devolver un ítem o null. Como hemos puesto provider
en EntityToDtoStateProvider
, está utilizando este provider
para cada operación. Y eso está bien... siempre que lo hagamos lo suficientemente inteligente como para manejar ambos casos.
Ya vimos antes cómo hacerlo: $operation
es la clave. Añadeif ($operation instanceof CollectionOperationInterface)
. Ahora podemos deformar todo este código aquí arriba. ¡Precioso!
// ... lines 1 - 4 | |
use ApiPlatform\Doctrine\Orm\Paginator; | |
// ... lines 6 - 7 | |
use ApiPlatform\State\Pagination\TraversablePaginator; | |
// ... lines 9 - 12 | |
class EntityToDtoStateProvider implements ProviderInterface | |
{ | |
// ... lines 15 - 21 | |
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null | |
{ | |
$entities = $this->collectionProvider->provide($operation, $uriVariables, $context); | |
assert($entities instanceof Paginator); | |
// ... lines 26 - 31 | |
return new TraversablePaginator( | |
new \ArrayIterator($dtos), | |
$entities->getCurrentPage(), | |
$entities->getItemsPerPage(), | |
$entities->getTotalItems() | |
); | |
} | |
// ... lines 39 - 50 | |
} |
A continuación, éste será nuestro proveedor de artículos. dd($uriVariables)
.
// ... lines 1 - 4 | |
use ApiPlatform\Metadata\CollectionOperationInterface; | |
// ... lines 6 - 13 | |
class EntityToDtoStateProvider implements ProviderInterface | |
{ | |
// ... lines 16 - 22 | |
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null | |
{ | |
if ($operation instanceof CollectionOperationInterface) { | |
$entities = $this->collectionProvider->provide($operation, $uriVariables, $context); | |
assert($entities instanceof Paginator); | |
$dtos = []; | |
foreach ($entities as $entity) { | |
$dtos[] = $this->mapEntityToDto($entity); | |
} | |
return new TraversablePaginator( | |
new \ArrayIterator($dtos), | |
$entities->getCurrentPage(), | |
$entities->getItemsPerPage(), | |
$entities->getTotalItems() | |
); | |
} | |
dd($uriVariables); | |
} | |
// ... lines 44 - 55 | |
} |
Llamada al proveedor de elementos del núcleo
Cuando probamos la operación elemento... ¡bien! Esto es lo que esperamos ver: el valor id
, que es la parte dinámica de la ruta.
Al igual que con el proveedor de colecciones, no queremos hacer el trabajo de consulta manualmente. En su lugar, vamos a... "delegaremos" en el proveedor de elementos del núcleo de Doctrine. Añadiremos un segundo argumento... podemos simplemente copiar el primero... de tipo ItemProvider
(el de Doctrine ORM), y lo llamaremos $itemProvider
.
// ... lines 1 - 4 | |
use ApiPlatform\Doctrine\Orm\State\ItemProvider; | |
// ... lines 6 - 14 | |
class EntityToDtoStateProvider implements ProviderInterface | |
{ | |
public function __construct( | |
#[Autowire(service: CollectionProvider::class)] private ProviderInterface $collectionProvider, | |
#[Autowire(service: ItemProvider::class)] private ProviderInterface $itemProvider, | |
) | |
{ | |
} | |
// ... lines 24 - 63 | |
} |
¡Me gusta! De vuelta abajo, deja que haga el trabajo con$entity = $this->itemProvider->provide()
pasando $operation
, $uriVariables
y $context
.
// ... lines 1 - 14 | |
class EntityToDtoStateProvider implements ProviderInterface | |
{ | |
// ... lines 17 - 24 | |
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null | |
{ | |
// ... lines 27 - 43 | |
$entity = $this->itemProvider->provide($operation, $uriVariables, $context); | |
// ... lines 45 - 50 | |
} | |
// ... lines 52 - 63 | |
} |
Esto nos dará un objeto $entity
o null. Si no tenemos un objeto $entity
,return null
. Esto provocará un 404. Pero si tenemos un objeto $entity
, no queremos devolverlo directamente. Recuerda que el objetivo de esta clase es tomar el objeto $entity
y transformarlo en un DTO UserApi
.
Así que, en su lugar, return $this->mapEntityToDto($entity)
.
// ... lines 1 - 14 | |
class EntityToDtoStateProvider implements ProviderInterface | |
{ | |
// ... lines 17 - 24 | |
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null | |
{ | |
// ... lines 27 - 43 | |
$entity = $this->itemProvider->provide($operation, $uriVariables, $context); | |
if (!$entity) { | |
return null; | |
} | |
return $this->mapEntityToDto($entity); | |
} | |
// ... lines 52 - 63 | |
} |
Así queda bien. Y... la ruta final funciona de maravilla. Si intentamos un identificador no válido, nuestro proveedor devuelve null y API Platform se encarga del 404.
Mostrar sólo los Tesoros del Dragón publicados
Nota al margen: si sigues algunos de estos tesoros relacionados, también pueden 404. Veamos... tenemos 21 y 27. el 21 me funciona... y para el 27... también funciona... por supuesto. De todos modos, la razón por la que algunos podrían 404 es que, ahora mismo, si vuelvo atrás, la propiedad dragonTreasures
incluye todos los tesoros relacionados con este usuario: incluso los no publicados. Pero en un tutorial anterior, creamos una extensión de consulta que impedía que se cargaran los tesoros no publicados.
Cuando la entidad User
era nuestro recurso API, evitábamos devolver tesoros no publicados desde esta propiedad. Creamos getPublishedDragonTreasures()
y la convertimos en la propiedad dragonTreasures
.
Pero en nuestro proveedor de estado, los estamos estableciendo todos. Esto tiene fácil arreglo: cambia a getPublishedDragonTreasures()
.
// ... lines 1 - 14 | |
class EntityToDtoStateProvider implements ProviderInterface | |
{ | |
// ... lines 17 - 52 | |
private function mapEntityToDto(object $entity): object | |
{ | |
// ... lines 55 - 58 | |
$dto->dragonTreasures = $entity->getPublishedDragonTreasures()->getValues(); | |
// ... lines 60 - 62 | |
} | |
} |
En realidad, deshazlo... y luego actualiza la ruta de la colección. Vale, aquí abajo vemos los tesoros 16 y 40... después de utilizar el nuevo método... ¡sólo 16! "40" está sin publicar.
¡Ha sido fácil! Y pone de relieve algo genial. Para tener un campodragonTreasures
que devolviera algo especial cuando nuestra entidad User
fuera un ApiResource, necesitábamos un método dedicado y un atributo SerializedName
. Pero con una clase personalizada, no necesitamos ninguna rareza. Podemos hacer lo que queramos en el proveedor de estado. ¡Nuestras clases se mantienen brillantes y limpias!
A continuación: Hagamos que nuestros usuarios se guarden con un procesador de estado: un delicado baile que implica manejar usuarios nuevos y existentes.
Hey there,
Everything works great, but any idea why I have this weird schema?
When I run the finish project I see you have something like:
Would be nice to see a IRI example, as it's confusing now.
Guess it's a minor from stateOptions, #[ApiResource() on a Entity itself it works normal.
No idea if it's a deep bug or we are missing something, just wondering if you have a suggestion to get IRI in the schema?