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 providerpara 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, $uriVariablesy $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.
8 Comments
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?
Hey @Rudi-T!
Hmm, you literally have
path/실례.htmlin your schema? That appears to be... a Korean character? And the.htmlis also weird. I have no idea what would be causing this. The mechanism in API Platform is pretty simple for figuring out if an IRI will be generated:A) API Platform looks at the class that it is generated the schema for - e.g.
UserApiB) It looks at the
dragonTreasuresproperty and tries to figure out its type. It does this by looking at PHPDoc, the property type, etc. IF it determines thatdragonTreasuresis a class that has#[ApiResource]above it (or it's an array of this class), then it will generate a schema with IRI strings.So this looks SUPER bizarre to me...
Could it be possible there might be a bug in the latest version of API Platform ?
Since my last
composer updateon an already existing project (API Platform v3.2.3 and Symfony 6.3.7), I have the same thing happen too. IRIs are weirdly shown in Swagger doc, also withpath/실례.htmlinstead of the IRI.Looking at the two other messages in this conversation posted just a few days ago, it raises a legitimate concern.
Thank you very much.
Clément
Hello!!
I'm having the exact same issue!
It appears in every embedded relation
And sometimes it shows "../словник" instead of the embedded uri.
Wow, this is crazy! So, it's NEVER and correct IRI string? And you see the
path/실례.htmlsometimes? And other times../словник?So, looking at API Platform, the string
실례DOES show up in 1 place (thoughсловникdoes not show up anywhere): in aswagger-ui-bundle.jsfile shipped by API Platform.See https://github.com/api-platform/core/issues/5900#issuecomment-1773752880 - that looks like the same thing. So yes, I think this may be a bug or some sort of unexpected change.
I checked the GitHub issue and it seems to be the same thing indeed.
I changed the
swagger-ui-bundle.jsfile to an older version (April 23), and my previous issue is somewhat fixed but still not ideal (no actual IRI, just "string") :But it still gives a hint the issue may be from the
swagger-ui-bundle.jsfile.Another strange thing is that on version 3.2.2, my redocs are no showing the respective IRI, its showing "entity/1".
The "embedded" resource should show "/embedded/{id}", or something like this, but it is showing "/entity/1".
I've got the same strange string on the redocs for every embedded relation. In the Swagger UI, I have "path/실례.html" instead. So, in both cases I dont have the correct IRI.
Yes, I never get the correct IRI.
When using api-platform version 3.1, I got: "../словник" instead of the IRI,
when using api-platform version 3.2.2, I got: "path/실례.html".
I've tried other versions and had same bug in every embedded relation.
"Houston: no signs of life"
Start the conversation!