Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Filtrar y buscar

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

¡Lo estamos haciendo muy bien! Entendemos cómo exponer una clase como recurso de la API, podemos elegir qué operaciones queremos, y tenemos un control total sobre los campos de entrada y salida, incluyendo algunos campos "falsos" como textDescription. Hay mucho más que saber, ¡pero lo estamos haciendo bien!

Entonces, ¿qué más necesita toda API? Se me ocurren algunas cosas, como la paginación y la validación. Pronto hablaremos de ambas cosas. Pero, ¿qué pasa con el filtrado? Tu cliente de la API -que podría ser simplemente tu código JavaScript- no va a querer siempre obtener cada uno de los CheeseListing del sistema. ¿Y si necesitas la posibilidad de ver sólo los listados publicados? ¿O qué pasa si tienes una búsqueda en el front-end y necesitas encontrar por título? Esto se llama "filtros": formas de ver un "subconjunto" de una colección en función de algún criterio. ¡Y la Plataforma API viene con un montón de ellos incorporados!

Filtrar por publicado/no publicado

Empecemos por hacer posible que sólo se devuelvan los listados de quesos publicados. Bueno, en un futuro tutorial, vamos a hacer posible ocultar automáticamente los listados no publicados de la colección. Pero, por ahora, nuestra colección de listados de queso lo devuelve todo. Así que, al menos, hagamos posible que un cliente de la API pida sólo los publicados.

En la parte superior de CheeseListing, activa nuestro primer filtro con @ApiFilter(). Luego, elige el filtro específico por su nombre de clase: BooleanFilter::class... porque estamos filtrando sobre una propiedad booleana. Termina pasando la opciónproperties={} a "isPublished".

... lines 1 - 11
/**
... lines 13 - 22
* @ApiFilter(BooleanFilter::class, properties={"isPublished"})
... line 24
*/
class CheeseListing
... lines 27 - 145

¡Genial! ¡Vamos a ver qué ha hecho esto! ¡Refrescar! Oh... ¡lo que hizo fue romper nuestra aplicación!

La clase de filtro BooleanFilter no implementa FilterInterface.

No está súper claro, pero ese error significa que hemos olvidado una sentencia use. Este BooleanFilter::class está haciendo referencia a una clase específica y necesitamos una sentencia usepara ella. Es... una forma un poco extraña de utilizar los nombres de las clases, por eso PhpStorm no lo autocompletó para nosotros.

No hay problema, al principio de tu clase, añade use BooleanFilter. Pero... cuidado... la mayoría de los filtros soportan Doctrine ORM y Doctrine con MongoDB. Asegúrate de elegir la clase para el ORM.

... lines 1 - 6
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\BooleanFilter;
... lines 8 - 146

Bien, ahora muévete y actualiza de nuevo.

¡Volvemos a la vida! Haz clic en "Probar". ¡Tenemos un pequeño cuadro de entrada del filtro isPublished! Si lo dejamos en blanco y lo ejecutamos... parece que hay 4 resultados.

Elige true en lugar de isPublished e inténtalo de nuevo. ¡Nos quedamos con dos resultados! Y comprueba cómo funciona con la URL: sigue siendo /api/cheeses, pero con un precioso ?isPublished=true o ?isPublished=false. Así que, sin más, nuestros usuarios de la API pueden filtrar una colección en un campo booleano.

Además, en la respuesta hay una nueva propiedad hydra:search. OoooOOO. Es un poco técnico, pero esto explica que ahora puedes buscar utilizando un parámetro de consulta isPublished. También da información sobre a qué propiedad se refiere en el recurso.

Búsqueda de texto: SearchFilter

¿De qué otra forma podemos filtrar? ¿Qué tal si buscamos por texto? Sobre la clase, añade otro filtro: @ApiFilter(). Éste se llama SearchFilter::class y tiene la misma opción properties... pero con un poco más de configuración. Digamos que title está configurado como partial. También hay configuraciones para que coincida con una cadena exact, con la startde una cadena, con la end de una cadena o con la word_start.

De todos modos, esta vez, recuerdo que tenemos que añadir la declaración use manualmente. Digamos use SearchFilter y autocompletar la del ORM.

... lines 1 - 7
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
... lines 9 - 13
/**
... lines 15 - 25
* @ApiFilter(SearchFilter::class, properties={"title": "partial"})
... line 27
*/
class CheeseListing
... lines 30 - 148

Ah, y antes de comprobarlo, haré clic para abrir SearchFilter. Éste vive en un directorio llamado Filter y... si hago doble clic en él... ¡eh! Podemos ver un montón de otros: ExistsFilter, DateFilter, RangeFilter,OrderFilter y más. Todos ellos están documentados, pero también puedes entrar directamente y ver cómo funcionan.

En cualquier caso, ve a refrescar los documentos, abre la operación de la colección GET y haz clic para probarla. Ahora tenemos un cuadro de filtro title. Prueba... um... cheese y... Ejecuta.

Oh, ¡magnífico! Añade ?title=cheese a la URL... y coincide con tres de nuestros cuatro listados. La propiedad hydra:search contiene ahora una segunda entrada que anuncia esta nueva forma de filtrar.

Si queremos poder buscar por otra propiedad, podemos añadirla también:description ajustada a partial.

Esto es fácil de configurar, pero este tipo de búsqueda en la base de datos sigue siendo bastante básico. Afortunadamente, aunque no lo cubriremos en este tutorial, si necesitas una búsqueda realmente robusta, la Plataforma API puede integrarse con Elasticsearch: exponiendo tus datos de Elasticsearch como recursos legibles de la API. ¡Es una maravilla!

Vamos a ver dos filtros más: un filtro de "rango", que será súper útil para nuestra propiedad de precio y otro que es... un poco especial. En lugar de filtrar el número de resultados, permite que el cliente de la API elija un subconjunto de propiedades para devolverlas en el resultado. Eso a continuación.

Leave a comment!

45
Login or Register to join the conversation
Dima Avatar
Dima Avatar Dima | posted hace 1 año | edited

there

Hey! Thanks for the great job as always!

I still have an error while trying to add use statement for Boolean Type. It says:

"The filter class "phpDocumentor\Reflection\Types\Boolean" does not implement "ApiPlatform\Core\Api\FilterInterface". Did you forget a use statement?"

...
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\BooleanFilter;
...

This 'use' line is grey in IDE, looks like it doesn't see it.

#[ApiFilter(Boolean::class, properties: ['isPublished'])]

I didn't find a solution in the internet. Maybe you guys know how to fix it?

Reply

Hey Dima

I think I just spotted your problem. You're using "Boolean::class" instead of "BooleanFilter::class". Change that, and give it a try :)

Cheers!

Reply
Dima Avatar

Aah, yeah, indeed! My bad! Thank you

1 Reply
Raul M. Avatar
Raul M. Avatar Raul M. | posted hace 1 año

Hi Ryan,

I have some get colletionOperations in one of my entities and I want to know if is possible configure filters for each operation instead for the whole entity.

Thanks!

Raúl.

Reply

Hey Raul M.!

Hmm. I have no idea! I've never used it, but it looks like you can configure your filters inside ApiResource if you know the service id of each filter - 2nd code block - https://api-platform.com/do...

And so... you MIGHT be able to add a "filters=" option under the collectionOperation's config (instead of inside ApiResource). But, I'm doing some guessing. And for this to work, I think you need to look up the service id of the filter you want.

So... maybe? But it's definitely not a common use-case.

Cheers!

Reply
Anton B. Avatar
Anton B. Avatar Anton B. | posted hace 2 años | edited

there , please advice the best practice for the following:
I made a custom filter for the entity (e.g. Cheeses),
Cheeses has embedded property (e.g. CheeseListings (Collection))
CheeseListings has embedded property (e.g. Translations (Collection))

The question is how can I properly filter the Translations Collection if I need to search Cheeses where CheeseListings.translations.language = 'EN'?
I know about Extensions, but I need to return just EN translations in the Translations Collection, it can be only achieved by

return $queryBuilder->getQuery()->getResult(); right from the Custom Filter

another words I need to perform the next query (Pseudo Code):

select * from cheeses left join cheese_listings ON (...) left join cheese_listings_translations ON (...) where cheese_listings_translations.language = 'EN'

Example:


    {
        "@id": "\/cheeses\/f60f1768-ebad-4eac-9f54-959b2afe54cf",
        "@type": "Cheeses",
        "id": "f60f1768-ebad-4eac-9f54-959b2afe54cf",
        "title": "Yummy",
        "cheeseListings": {
              "@id": "\/cheese_listings\/4e0d4ac2-4acf-4ae3-a399-252094040822",
              "@type": "cheeseListings",
              "id": "4e0d4ac2-4acf-4ae3-a399-252094040822",
              "title": "Titlte",
              "translations": {
                   {
                       "@id": "\/cheese_listings_translations\/cheese_listing=4e0d4ac2-4acf-4ae3-a399-252094040822;language=EN",
                       "@type": "CheeseListingTranslation",
                       "language": {
                           "@id": "\/languages\/de",
                           "@type": "Language",
                           "isoCode": "EN"
                   },
              "content": "Hello"
            }
          }
        }
    }
Reply

Hey Anton B.!

Sorry for my slow reply! This is a tricky question. Hmmm. Would it be possible to implement this entirely as a query extension? Here's the idea: in the query extension, you read the query parameter directly from the request. If it's present, you perform the joins necessary and modify the query to only return the en language. You could (and probably should) still have a Filter, because that's what causes the filter to show up in your documentation... you just wouldn't actually do the filtering logic in the filter class.

Buuuut, I have a feeling that I'm not thinking about some complication. Because you mentioned:

it can be only achieved by return $queryBuilder->getQuery()->getResult(); right from the Custom Filter

I don't understand why that's the case. Why can't you modify the query in the custom filter and allow the system to actually execute like normal? You would modify the query to have the joins and the WHERE for the language. Am I missing a complication?

Cheers!

Reply
Anton B. Avatar
Anton B. Avatar Anton B. | weaverryan | posted hace 2 años | edited

weaverryan , thank you for the reply, I thought a lot about this, and my conclusion is that my case is not a proper way of using the API Platform and REST itself. I think we shouldn't modify the child collection results. But if we still need to modify(filter) the child collection, the best way is to perform the separate request to that collection with another filter.

Reply
Mark V. Avatar
Mark V. Avatar Mark V. | posted hace 2 años | edited

What are your thoughts on adding a search filter on the id property to the collection endpoint of a resource?

I want to be able to retrieve multiple, specific items.

Feel like the GET collection endpoint is the right place for this. Annotation is not the problem and it does work.

` * @ApiFilter(SearchFilter::class, properties={

  • "id": "exact",
  • ...
  • })
    `

But is this ReSTful? Is there a better approach?

The only downside I can see is hitting the query string length limit when retrieving many items.

Reply

Hey Mark V.!

This looks perfectly fine to me - I would do it the exact same way. You are hitting the collection endpoint, then filtering the results - I don't see any problems with that :).

> The only downside I can see is hitting the query string length limit when retrieving many items

That's true... but there is really no other way to send data up on a GET request - this IS the proper way. Hopefully, if you hit the situation, there would be some other way to filter... or... I'm not sure - I'm having a hard time imagining how you could send any GET request up there with SO much data (maybe as header?). Hopefully you don't have this problem ;).

Cheers!

Reply
Daniel K. Avatar
Daniel K. Avatar Daniel K. | posted hace 2 años

Hi, can i use SearchFilter with 2 config on the same field something like "blabla": "exact", "blabla": "partial"?
Maybe i need to create second entity?

Reply

Hey Daniel K.

I believe you can't but give it a try. What are you trying to achieve? Perhaps you need a custom filter instead

Cheers!

Reply

Hi SymfonyCasts, how are you all doing? Thank you for these great tutorials.
And of course I have a question.

While filtering / searching how can I make sure only data is shown that is authorized to be shown.
Right now I'm using voters in my symfony applications and thinking about using elasticsearch.
But I cannot find any information about using elasticsearch in combination with voter security.
Also no info on making search queries myself that take the voters into account.

Can you guys help me out?
Thank you very much in advance.

Reply

Hey truuslee

Thanks for your kind words. Related to your question, I'm not totally sure because we don't talk about integrating ES with ApiPlatform but I found this in the docs:
https://api-platform.com/do...

It says that you should create a custom extension, so here is the link to it: https://api-platform.com/do...

I hope it helps. Cheers!

Reply
Michel B. Avatar
Michel B. Avatar Michel B. | posted hace 3 años

Hi, I was wondering if It possible to apply a searchFilter on concatenated fields/properties.
For example I would like to do an ipartial on firstname + " " + lastname
How would I do that with the searchFilter option , if possible at all?

Reply

Hey Michel B.

I'm afraid there is not a built-in filter for such task. I believe you have to create a custom filter, you can read how to do so here https://api-platform.com/do...

I hope it helps. Cheers!

Reply
Default user avatar

Hi again... and sorry for so many questions... But for some reason every filter I add to any entity is being ignored with no reason (explained). I even created a StackOverflow question where I explain. Basically it is not documented at all at Swagger and also it shows "filter ignored". Any suggestions?

Reply

Hey Martin

My first guess is that you may not have a getter for the status property. Another thing to check is the imports, double-check that you imported the right ones

Cheers!

Reply
Default user avatar
Default user avatar Martin | MolloKhan | posted hace 3 años | edited

Hey MolloKhan!

Thanks for replying!

I have created the entity using <a href="https://packagist.org/packages/symfony/maker-bundle&quot;&gt;maker bundle</a>, so the property getter is there. Also, this is what my Question imports looks like:

`
use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\QuestionRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter; // here
use ApiPlatform\Core\Annotation\ApiFilter; // and here
use Symfony\Component\Serializer\Annotation\Groups;

/**

  • @ORM\Entity(repositoryClass=QuestionRepository::class)
  • @ApiResource(normalizationContext={"groups"={"question"}})
  • @ApiFilter(SearchFilter::class, properties={"status": "partial"})
    */
    class Question
    {
    //....
    }
    `

I have followed <a href="https://api-platform.com/docs/core/filters/&quot;&gt;this guide</a> too.

Reply

Hmm, that's very strange. The setup looks right. What happens when you play with the API? Can you do a GET and POST request successfully?
What version of Symfony and ApiPlatform are you using?
If you create a new entity does it behaves the same?

Reply
Default user avatar
Default user avatar Martín Fernández | MolloKhan | posted hace 3 años

Diego, I have tried to replicate the issue on a new project and it worked. So, I assume the problem was some alternate configuration on the project. I was able to play with the API, posting and getting entities. Not sure. But I will try to copy the project with a new configuration. Thank you anyway!

Reply

Ohh, that's interesting. Perhaps if you upgrade ApiPlatform the error goes away but I might be wrong. Cheers!

Reply
Benjamin K. Avatar
Benjamin K. Avatar Benjamin K. | posted hace 3 años

Hi
Update: After testing Custom Filters its ok to use them. i didnt see yet a reason to write them because most filters are allready there. But for special purposes its ok. If i am right select - where is allready done. A custom Filter is AndWhere from queryBuilder. That makes sense we filter a resource and not build a complete query ...

https://api-platform.com/do...

Reply

Hey Benjamin K.!

> If i am right select - where is allready done. A custom Filter is AndWhere from queryBuilder. That makes sense we filter a resource and not build a complete query

That's correct :). It makes the filters fairly easy to build.

Cheers!

Reply
Default user avatar

Hi, great tutorial!!. Just a question, how you would search / filter in some JSON property (like user's roles)?

Reply

Hey Martin!

Hmm, good question! A normal SearchFilter would, in theory, work (as you would be searching through the JSON string)... but it would be fairly imprecise. To do this correctly, you would probably need to create a API Platform custom filter. Then, assuming you're using a database with a proper JSON type (like pgsql or MySQL 5.7 or higher), you would need to do a bit more work to query for the item via Doctrine. Basically, because JSON types are not supported across all database systems, as far as I know, there is no built-in way to properly query a JSON type in Doctrine. But there are resources out there - e.g. https://blog.liplex.de/sear... - about how to do this.

Let me know if that helps!

Cheers!

1 Reply
Default user avatar
Default user avatar Martin | weaverryan | posted hace 3 años | edited

Hey weaverryan.

Thanks for replying. It really helped. Thank you. I am not sure why but is not documented in Swagger UI. I guess this is because of the value returned (return $this->json($users);. But it's working!

Reply
Manuka L. Avatar
Manuka L. Avatar Manuka L. | posted hace 3 años

Hi, How do I do an OR operation on the filters? Lets say I want to search ?firstName=abc&lastName=abc. If "either" firstname OR lastname has "abc", how do I select the result?
The following seems to do and AND, where it would only return results if both firstname and lastname has the passed value.

user.filter_by_name:
parent: api_platform.doctrine.orm.search_filter
arguments: [{ firstname: ipartial, lastname: ipartial }]
tags: ['api_platform.filter']

Reply

Hi Manuka L.!

Sorry for the slow reply! I believe you will need to create a custom filter for this. The built-in filters always use AND logic... I think it's, sort of, just a missing feature when you configure the search filter (in my opinion). Here is an issue about this - https://github.com/api-plat... - and a gist that will hopefully help: https://gist.github.com/axe...

Cheers!

Reply

Hey!

small question: Is it possible to add a query param kind like a filter but for post resource ?
Then the request url will be:
POST: http://api.domain.local:8785/users?newQuery=true then the swagger will add a field under the doc ?

Reply

hi ahmedbhs!

Hmm. Maybe? :) I'm not sure I fully understand. Your Swagger docs (or more technically accurate, the OpenAPI specification) are "static" - you can view them by going to /api/docs.json. So, this is not something that can be affected by query parameters to a specific endpoint - it's a 100% static definition of your API.

So, what are you trying to accomplish exactly? Do you want to make it possible to POST a field... but ONLY when the URL has newQuery=true? If so, that might be done with a context builder - https://symfonycasts.com/screencast/api-platform-security/context-builder - if the query param exists, then add a new group that the field is in. Or do you want something different?

Cheers!

Reply
Olivier Avatar

Hi!

Little question, take for example that I have a "User" entity with a username property. Somewhere in my application I need to query a user from the API with the EXACT username, and at another place I want to query the samething but this time with PARTIAL. Is it possible to do that?

I know it's not the correct solution but something like that:
@ApiFilter(SearchFilter::class, properties={"username": "exact/partial"}

Then the request url will be:
http://api.domain.local:8785/users?username=tony&matchStrategy=exact

I'm not the best in english, hope you understand what I mean.
Thanks!

Reply

Hey Olivier

That's a good question and by looking at the ApiPlatform docs I couldn't find anything related to define 2 strategies on the same property. So, I think what you need to do is to create your own filter by following this steps: https://api-platform.com/do...

Cheers!

1 Reply

Hi SymfonyCasts,

I have lots of data in lots of different languages, like about 26 languages.
Should i give the client the opportunity to add a 'language' FILTER to the get request like ?language=en or should the client add an 'accept language' to the request header?
And if the 'accept language' is the answer, how do i deal with this in api platform?

Thank you for your answer .

Reply

Hey truuslee!

Sorry for my slow reply. Honestly, I'm not sure if there is a strictly-agreed-upon best practice for this or not. You've listed 2 possible options - and there is (technically) one more - having the language in the URL - e.g. /api/en/articles, though this last one doesn't feel very RESTful to me (as it would mean that, in a philosophical level, /api/en/articles/1 and /api/es/articles/1 are different rest "resources").

So, I would choose between the two options that you listed - choosing which ever one would be most convenient for whoever is using your API. Maybe the "accept language" header is a better practice, but I wouldn't stress out over it if the query parameter is more convenient. Let's look at the implementation details of each:

A) If you used, ?language=en, then probably it would mean that your API is designed so that if, for example, I make a request to /api/articles that it returns the list of ALL articles in ALL languages. Then adding the language filter is just a matter of using the built-in API Platform filters.

B) If you used the "accept language" header, you have a little bit more work to do. You would need to parse the "accept language" header to determine which language to ultimately use. I would then probably create an "API Platform Doctrine extension" - https://api-platform.com/docs/core/extensions/ - that would limit the collection or choose the item result based which language you determine is correct. Then you should also add a Content-Language response header set to which language you ultimately chose. I would do that via a listener - https://api-platform.com/docs/core/events/#custom-event-listeners - the POST_RESPOND one (which just means you add a listener to the kernel.response event - called ResponseEvent in Symfony 4.3 and higher.

Let me know if this helps! Mostly, follow what will be most useful in your API - it sounds like you are only considering solid option.

Cheers!

Reply

Thank you very much for your reply. You're a big help as always. I've chosen to do it with a filter, far less work.
I have other questions for you, I hope you don't mind?

1.
I have an entity Brand that is related to another entity called Product. (One product has one Brand, one Brand has many Products).
Now the api-client wants a list of all the brands a customer is using.
Something like this:
/api/customer/{id}/brands

But entity Brand is not related to the Customer entity, I can only get the brands via entity1 related to entity2, related to entity Product, finally related to entity Brand.
How can i do this?
I am thinking about creating a new entity with customer ids and brand ids together in a 'cross' table. But i hope there is a better way?

  1. I have created a Custom Doctrine ORM Extension.
    Because I want to add a groupby to a query.
    This works great, it makes sure the result set is grouped.
    But one thing is wrong. The hydra variable "<b>hydra:totalItems</b>" is still mentioning the original amount of records, as if there were no groupby.
    This is my code:
    `

final class FooExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
{

public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator,
    string $resourceClass, string $operationName = null): void {
    $this->addGroupBy($queryBuilder, $resourceClass);
}

public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator,
    string $resourceClass, array $identifiers,string $operationName = null,array $context = []): void {
    $this->addGroupBy($queryBuilder, $resourceClass);
}

private function addGroupBy(QueryBuilder $queryBuilder, string $resourceClass): void
{
    if (Foo::class !== $resourceClass) {
        return;
    }
    $rootAlias = $queryBuilder->getRootAliases()[0];
   <b> $queryBuilder->addGroupBy($rootAlias . '.foo_field');</b>
}

}
`

How can i make hydra show me the correct number of records?

Thanks in advance and hope to hear from you soon!

Reply

Hey truuslee!

But entity Brand is not related to the Customer entity, I can only get the brands via entity1 related to entity2, related to entity Product, finally related to entity Brand.
How can i do this?
I am thinking about creating a new entity with customer ids and brand ids together in a 'cross' table. But i hope there is a better way?

Hmm. Ideally we can avoid adding duplication to our database to make API Platform happy :). My initial instinct (which is a bit of cheating) is to tell the API client to use a different endpoint. What I mean is: why do they need this specific endpoint? It's definitely possible to add it - but there could be a much simpler solution if we have some flexibility. For example, a URL like /api/brands?customer=/api/customers/1 would probably be easier, though this would still need to be a custom filter (since there is not actually a "customer" property on brands). Another option would be to simply "add" a brands field when you fetch a customer (instead of making it a different URL). You could probably do this by adding a getBrands() method to Customer that loops over all the products and then returns an array of each Brand that is related to all the Products (if I've understood the relationships correctly).

But anyways, to implement this, I would create a custom item operation on Customer for the /api/customers/{id}/brands URL. I try to avoid having custom operations because it usually means you're trying to "bend" the system a little far - but it's not a huge deal, and you need to get your job done :). In this case, you would write a custom controller and be able to do whatever you want.

But one thing is wrong. The hydra variable "hydra:totalItems" is still mentioning the original amount of records, as if there were no groupby.

Interesting! I don't know the answer to this - bit I'm a little surprised by it. The reason is that, behind the scenes, API Platform uses a "Doctrine paginator"... and since we're using Doctrine to build the query, I would expect it to be smart enough to get the proper count. Here is the code behind this: https://github.com/api-platform/core/blob/master/src/Hydra/Serializer/CollectionNormalizer.php#L96-L102

For you (and me), $object is an instance of ApiPlatform\Core\Bridge\Doctrine\Orm which is just a wrapper around Doctrine\ORM\Tools\Pagination\Paginator.

I'd be interested what the count query looks like. Typically (assuming you have a result that requires "pagination" - like more than 25 records) Doctrine will make a query specifically to get the total count (in addition to the query to get the specific results it needs). After making the API request, you can go to /_profiler to find the profiler for that API request. If you click into this and go to the Doctrine tab on the left, you'll be able to see all the queries including a query where Doctrine is trying to take your query and use it to get a count of all items. I'd be interested to know if that query looks "wrong" in some way.

I hope this helps!

Cheers!

Reply
Gustavo Avatar

Hi, thanks for that lesson.
How can i add an ApiFilter like BooleanFilter using a YAML config file?

Reply

Hi Gustavo!

Good question - I actually don't know! Here are how I think it might look (stolen from a few Stack Overflow references):


App\Entity\CheeseListing:
    attributes:
        filters:
            - 'ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\BooleanFilter'
            # I'm not sure how you would pass a filter with options. Maybe this?
            ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter:
                properties: [{title: partial}]

Let me know if it works :).

Cheers!

Reply
Sung L. Avatar

How can I create a /search api endpoint for unrelated tables?

After watching this tutorial, adding search function for a specific entity seems simple.
I was wondering how I can create a /search endpoint to search in multiple tables. For example, I have a CheeseListing and WineListing tables and there are no relations. I want to have a search feature that returns matching records from cheese description and wine description.

Any tips how to do this?

Thanks.

Reply

Hey Sung L.!

Sorry for my slow reply - great question. There are two probable approaches to this:

1) Use the elastic search integration: https://api-platform.com/do... - it's very robust, but could be overkill if you just need to do something simple.

2) Create a custom class (not a Doctrine entity, but you can still put it in the Entity directory) that contains whatever fields you want... which is maybe just a "results" array that will contain a mixture of objects? You will only want the "read" operations to be enabled for this resource (and probably just the collection operation). Then, use a custom data provider (https://api-platform.com/do... to take control of how you fetch the "collection". I'm not exactly sure how this would all look in the end, however... as I'm just "thinking" out loud. Assuming you created some custom class called ListingSearchResult and marked this an ApiResource, I guess you might give that properties like "item", which will be a CheeseListing or WineListing embedded object... and... I'm not sure if you need any other properties. So, the endpoint may look a bit funny.

I don't have a super great answer for this - well for suggestion (2). I think it could work and be nice... but it would need some more investigation. And, we might need to take some inspiration from what the ElasticSearch implementation looks like in Api Platform. Of course, you could always create a traditional endpoint that returns the results... e.g. just query for the array of CheeseListing & WineListing objects, then serialize that collection to the "jsonld" format.

Sorry I couldn't be more clear - this is not totally clear in my head, obviously :).

Cheers!

Reply
Braunstetter Avatar

Ok, nice suggestions. I mean, thats something very common. A customer want to have a nice applications search and want me to write a search for some entities at once. I searched for a while for ElasticSearch implementation. And that reading and filtering process sounds very clear and easy but for writing and persisting thats another issue - I guess. Are there ressources how to get the ElasticSearch entites into the Database with Api-Platform?

Reply
Ouchayan H. Avatar
Ouchayan H. Avatar Ouchayan H. | posted hace 4 años

could I know when you will publish the rest of this amazing training ?

Reply

Hey Hamid,

We're trying to release one video per day! Most probably this course should be completely released in the beginning of the next week. Thank you for your patience!

Cheers!

Reply
Ouchayan H. Avatar
Ouchayan H. Avatar Ouchayan H. | Victor | posted hace 4 años

Thanks for you fast reply.

Reply
Cat in space

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

Este tutorial funciona muy bien para Symfony 5 y la Plataforma API 2.5/2.6.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^2.1", // v2.4.3
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // 1.10.2
        "doctrine/doctrine-bundle": "^1.6", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^2.0", // v2.0.0
        "doctrine/orm": "^2.4.5", // v2.7.2
        "nelmio/cors-bundle": "^1.5", // 1.5.5
        "nesbot/carbon": "^2.17", // 2.19.2
        "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.1
        "symfony/asset": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/console": "4.2.*", // v4.2.12
        "symfony/dotenv": "4.2.*", // v4.2.12
        "symfony/expression-language": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/flex": "^1.1", // v1.17.6
        "symfony/framework-bundle": "4.2.*", // v4.2.12
        "symfony/security-bundle": "4.2.*|4.3.*", // v4.3.3
        "symfony/twig-bundle": "4.2.*|4.3.*", // v4.2.12
        "symfony/validator": "4.2.*|4.3.*", // v4.3.11
        "symfony/yaml": "4.2.*" // v4.2.12
    },
    "require-dev": {
        "symfony/maker-bundle": "^1.11", // v1.11.6
        "symfony/stopwatch": "4.2.*|4.3.*", // v4.2.9
        "symfony/web-profiler-bundle": "4.2.*|4.3.*" // v4.2.9
    }
}
userVoice