Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Filtrar relaciones

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

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

Login Subscribe

Antes hemos añadido un montón de filtros a DragonTreasure. Vamos a añadir unos cuantos más -empezando por User - para que podamos mostrar algunos superpoderes de filtrado en las relaciones.

Utilizar PropertyFilter en relaciones

Empieza como de costumbre: ApiFilter y utilicemos primero PropertyFilter::class. Recuerda: se trata de una especie de filtro falso que permite a nuestro cliente de la API seleccionar los campos que desee. Y todo esto es bastante familiar hasta ahora.

... lines 1 - 4
use ApiPlatform\Metadata\ApiFilter;
... line 6
use ApiPlatform\Serializer\Filter\PropertyFilter;
... lines 8 - 22
#[ApiFilter(PropertyFilter::class)]
... lines 24 - 25
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
... lines 28 - 174
}

Cuando nos dirigimos, actualizamos y vamos a la ruta de recolección GET... vemos un nuevo campoproperties[]. Podríamos elegir devolver sólo username... o usernamey dragonTreasures.

Cuando pulsamos "Ejecutar"... ¡perfecto! Vemos los dos campos... donde dragonTreasureses una matriz de objetos, cada uno de los cuales contiene los campos que elegimos incrustar.

De nuevo, esto es super duper normal. Así que vamos a intentar algo más interesante. De hecho, lo que vamos a intentar no está soportado directamente en los documentos interactivos.

Así que, copia esta URL... pégala y añade .jsonld al final.

Éste es el objetivo: quiero devolver el campo username y después sólo el campo namede cada tesoro dragón. La sintaxis es un poco fea: es [dragonTreasures], seguido de []=name.

Y así... ¡sólo muestra name! Así que, directamente,PropertyFilter nos permite llegar a través de las relaciones.

Buscar campos de relación

Hagamos otra cosa. Volvamos a DragonTreasure. Podría ser útil filtrar por $owner: podríamos obtener rápidamente una lista de todos los tesoros de un usuario concreto.

¡No te preocupes! Sólo tienes que añadir ApiFilter por encima de la propiedad $owner, pasando el fiel SearchFilter::class seguido de strategy: 'exact'.

... lines 1 - 55
class DragonTreasure
{
... lines 58 - 101
#[ApiFilter(SearchFilter::class, strategy: 'exact')]
private ?User $owner = null;
... lines 104 - 215
}

Volviendo a los documentos, si abrimos la ruta de la colección de tesoros GET y le damos una vuelta... veamos... aquí está: "propietario". Introduce algo como /api/users/4... suponiendo que se trate de un usuario real en nuestra base de datos, y... ¡sí! ¡Aquí están los cinco tesoros propiedad de ese usuario!

Pero quiero volverme más loco: quiero encontrar todos los tesoros que sean propiedad de un usuario que coincida con un nombre de usuario concreto. Así que en lugar de filtrar porowner, tenemos que filtrar por owner.username.

¿Cómo? Bueno, cuando queremos filtrar simplemente por owner, podemos poner el ApiFilterjusto encima de esa propiedad. Pero como queremos filtrar por owner.username, no podemos ponerlo encima de una propiedad... porque owner.username no es una propiedad. Éste es uno de los casos en los que necesitamos poner el filtro encima de la clase. Y... eso también significa que tenemos que añadir una opción properties establecida en una matriz. Dentro, digamos 'owner.username' y establecerla en la estrategia partial.

... lines 1 - 55
#[ApiFilter(SearchFilter::class, properties: [
'owner.username' => 'partial',
])]
class DragonTreasure
{
... lines 61 - 218
}

¡Vale! Vuelve y actualiza. Sabemos que tenemos un propietario cuyo nombre de usuario es "Smaug"... así que volvamos a la ruta de la colección GET y... aquí en owner.username, busquemos "maug"... y pulsemos "Ejecutar".

Veamos... ¡Ha funcionado! Esto muestra todos los tesoros propiedad de cualquier usuario cuyo nombre de usuario contenga maug. ¡Genial!

Bien, escuadrón: prepárate para la gran final: los Subrecursos. Éstos han cambiado mucho en API Platform 3. Vamos a sumergirnos en ellos.

Leave a comment!

7
Login or Register to join the conversation
David-S Avatar
David-S Avatar David-S | posted hace 13 días | edited

Hi everyone,

so I have an interesting error. Instead of integer ids I use uuids (as primary key).
When searching "DreagonTreasures" with a specific user e.g. /api/users/0189d986-1e5c-7205-8e1c-466b139ceda1
I get a 200 response with

"hydra:totalItems": 0,
"hydra:member": [],

Here is what id looks like in my class:

 #[ORM\Id]
 #[ORM\Column(type: UuidType::NAME, unique: true)]
 #[ORM\GeneratedValue(strategy: 'CUSTOM')]
 #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
 private ?UUid $id;

I'm using MySQL. The id (with uuid value) apparently is of type binary(16) in MySQL. Does it have something to do with that?
What am I missing / how can I fix this?

(When I use integer ids it works as it should.)

Thanks for your help.

Reply

Hey @David-S

I think that's unexpected. Have you watched this chapter? You may find it helpful https://symfonycasts.com/screencast/api-platform-extending/uuid-identifier
By the way, I don't recommend using UUIDs as the primary key because it will slow down your queries. It's better to keep a traditional integer id as the primary key and add a UUID as your "public id"

Cheers!

Reply
David-S Avatar

Hey @MolloKhan,

thanks for your reply. I wasn't sure that "slowing down" is a real world problem (at least for my application).
So, to keep it simple, I wanted to use the uuid instead of integer id. But Ok, I will use int as primary and add uuids as identifiers.

I used Symfony\Component\Uid\Uuid . In that Symfony 5 API Tutorial Ramsey\Uuid\Uuid is used. Which one shpuld be uses now with Symfony 6 and API Platform 3?

Reply

Good question. Since Symfony 5.2 there's a UID component that basically replaces Ramsey's library. You can read more about it here https://symfony.com/doc/current/components/uid.html#storing-uuids-in-databases

Cheers!

Reply
David-G Avatar
David-G Avatar David-G | posted hace 3 meses | edited

Hi everyone,

I am working on an application with API Platform 3. I have an Entity called "Users" and another Entity called "Prospects". I have created a route: /api/users/{partner_id}/prospects/{id}, where {partner_id} represents the custom id of users and {id} represents the id of prospects.

This setup is functioning correctly, but I want to apply a filter on the subresource "prospects," and I am having trouble understanding how to do this. I have encountered some issues, but I haven't found a solution yet.

Here is a portion of my code:

#[ApiResource(
    operations: [ 
        new Get(
            shortName: "Users",
            uriTemplate: '/users/{partner_id}/prospects/{id}',
            uriVariables: [
                'partner_id' => new Link(fromClass: Users::class, fromProperty: 'prospects'),
                'id' => new Link(fromClass: Prospects::class)       
            ],             
        ) 
    ],    
)]
#[ApiFilter(SearchFilter::class, properties:[
    'status',
    'lastname' => 'ipartial',
    'upline'
])]

Please let me know if you have any suggestions or solutions. Thank you!

Reply

Hey @David-G!

This is not a use-case I had thought of before! You said this isn't working. What does it do? An error? Just not work at all?

Cheers!

Reply
David-G Avatar

Hi,
The bug was between the chair and the screen because I didn't realize that I was trying to apply a filter on a Get operation when filters only work on GetCollection operations.
Thanks !

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^3.0", // v3.0.8
        "doctrine/annotations": "^1.0", // 1.14.2
        "doctrine/doctrine-bundle": "^2.8", // 2.8.0
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.14", // 2.14.0
        "nelmio/cors-bundle": "^2.2", // 2.2.0
        "nesbot/carbon": "^2.64", // 2.64.1
        "phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
        "phpstan/phpdoc-parser": "^1.15", // 1.15.3
        "symfony/asset": "6.2.*", // v6.2.0
        "symfony/console": "6.2.*", // v6.2.3
        "symfony/dotenv": "6.2.*", // v6.2.0
        "symfony/expression-language": "6.2.*", // v6.2.2
        "symfony/flex": "^2", // v2.2.4
        "symfony/framework-bundle": "6.2.*", // v6.2.3
        "symfony/property-access": "6.2.*", // v6.2.3
        "symfony/property-info": "6.2.*", // v6.2.3
        "symfony/runtime": "6.2.*", // v6.2.0
        "symfony/security-bundle": "6.2.*", // v6.2.3
        "symfony/serializer": "6.2.*", // v6.2.3
        "symfony/twig-bundle": "6.2.*", // v6.2.3
        "symfony/ux-react": "^2.6", // v2.6.1
        "symfony/validator": "6.2.*", // v6.2.3
        "symfony/webpack-encore-bundle": "^1.16", // v1.16.0
        "symfony/yaml": "6.2.*" // v6.2.2
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "symfony/debug-bundle": "6.2.*", // v6.2.1
        "symfony/maker-bundle": "^1.48", // v1.48.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/stopwatch": "6.2.*", // v6.2.0
        "symfony/web-profiler-bundle": "6.2.*", // v6.2.4
        "zenstruck/foundry": "^1.26" // v1.26.0
    }
}
userVoice