Filters: Searching Results
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 SubscribeSome of our dragon treasures are currently published and some are unpublished. That's thanks to DragonTreasureFactory, where we randomly publish some but not others.
Right now, the API is returning every last dragon treasure. In the future, we're going to make it so that our API automatically returns only published treasures. But to start, let's at least make it possible for our API clients to filter out unpublished results if they want to.
Hello ApiFilter
How? By Leveraging filters. API Platform comes with a bunch of built-in filters that allow you to filter the collections of results by text, booleans, dates and much more.
Here's how it works: above your class, add an attribute called ApiFilter.
There are typically two ingredients that you need to pass to this. The first is which filter class you want to use. And if you look at the documentation, there's a bunch of them, like one called BooleanFilter that we'll use now and another called SearchFilter that we'll use in a few minutes.
Pass this BooleanFilter - the one from ORM, since we're using the Doctrine ORM - because we want to allow the user to filter on a boolean field.
The second thing you need top pass is properties set to an array of which fields or properties you want to use this filter on. Set this to isPublished:
| // ... lines 1 - 4 | |
| use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; | |
| use ApiPlatform\Metadata\ApiFilter; | |
| // ... lines 7 - 38 | |
| (BooleanFilter::class, properties: ['isPublished']) | |
| class DragonTreasure | |
| { | |
| // ... lines 42 - 164 | |
| } |
Using the Filter in the Request
All right! Go back to the documentation and check out the GET collection endpoint. When we try this... there's a new isPublished field! First, just hit "Execute" without setting that. When we scroll all the way down, there we go! hydra:totalItems: 40. Now set isPublished to true and try it again.
Yes! We have hydra:totalItems: 16. It's alive! And check out how the filtering happens. It's dead simple via a query parameter: isPublished=true. Oh, and it gets cooler. Look at the response: we have hydra:view, which shows the pagination and now we also have a new hydra:search. Yea, API Platform actually documents this new way of searching right in the response. It's saying:
Hey, if you want, you can add a
?isPublished=truequery parameter to filter these results.
Pretty stinking cool.
Adding Filters Directly Above Properties
Now, when you read about filters inside of the API Platform docs, they pretty much always show it above the class, like we have. But you can also put the filter above the property it relates to.
Watch: copy the ApiFilter line, remove it, and go down to $isPublished. Paste this above. And now, we don't need the properties option anymore... API Platform figures that out on its own:
| // ... lines 1 - 38 | |
| class DragonTreasure | |
| { | |
| // ... lines 41 - 68 | |
| (BooleanFilter::class) | |
| private bool $isPublished = false; | |
| // ... lines 71 - 164 | |
| } |
The result? The same as before. I won't try it, but if you peek at the collection endpoint, it still has the isPublished filter field.
SearchFilter: Filter by Text
What else can we do? Another really handy filter is SearchFilter. Let's make it possible to search by text on the name property. This looks almost the same: above $name, add ApiFilter. In this case we want SearchFilter: again, get the one for the ORM. This filter also accepts an option. You can see here that, in addition to properties, ApiFilter has an argument called strategy. That doesn't apply to all filters, but it does apply to this one. Set strategy to partial:
| // ... lines 1 - 5 | |
| use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; | |
| // ... lines 7 - 39 | |
| class DragonTreasure | |
| { | |
| // ... lines 42 - 48 | |
| (SearchFilter::class, strategy: 'partial') | |
| private ?string $name = null; | |
| // ... lines 51 - 166 | |
| } |
This will allow us to search on the name property for a partial match. It's a "fuzzy" search. Other strategies include exact, start and more.
Let's give it a shot! Refresh the docs page. And... now the collection endpoint has another filter box. Search for rare and hit Execute. Let's see, down here... yes! Apparently 15 of the results have rare somewhere in the name.
And again, this works by adding a simple ?name=rare to the URL.
Oh, let's also make the description field searchable:
| // ... lines 1 - 5 | |
| use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; | |
| // ... lines 7 - 39 | |
| class DragonTreasure | |
| { | |
| // ... lines 42 - 48 | |
| (SearchFilter::class, strategy: 'partial') | |
| private ?string $name = null; | |
| // ... lines 51 - 53 | |
| (SearchFilter::class, strategy: 'partial') | |
| private ?string $description = null; | |
| // ... lines 56 - 167 | |
| } |
And now... that shows up in the API too!
The SearchFilter is easy to set up... but it's a fairly simple fuzzy search. If you want something more complex - like ElasticSearch - API Platform does support that. You can even create your own custom filters, which we'll do in a future tutorial.
Alrighty: next, let's see two more filters: one simple and one weird... A filter that, instead of hiding results, allows the API user to hide certain fields in the response.
23 Comments
If you have problem with /api Entrypoint:
use this:
not this
Hey Domin,
Thank you for this tip! Yeah, we definitely want the filter for ORM not ODM as we use Doctrine ORM in this project :) But yeah, easy to miss this tiny difference in the long namespace, so it might be helpful for others.
Cheers!
In the "Script" version of this tutorial, you forgot the call to ApiFilter class here:
Hi,
when you want to activate filters for all booleans by default in your resource you need to only put:
#[ApiFilter(BooleanFiler::class)]above class and of course, remember about proper 'use' statements.
Jakub
Ha! I didn't know that - very cool!
Filters are so easy to use!
But from what I get they always work by generating some query parameter.
How can I set headers in swaggerUI similar to query parameters so that the user can set those headers in the UI?
I'd like to have some filters as headers instead of query parameters.
Hey @Fireball
I'm not sure if ApiPlatform gives you that functionality out of the box, it sounds to me like you'll need to create a custom filter
Cheers!
It might be a litte late for the topic starter, but for everybody else trying to do the same – you might want to look into declaring parameters on resources: https://api-platform.com/docs/core/filters/#declaring-parameters
Cool, thank you for sharing it!
Hello @weaverryan
The problem with the ARRAY error is still here "Undefined constant Doctrine\DBAL\Types\Types::ARRAY" despite this https://github.com/symfony/maker-bundle/issues/1437
I didn't want to downgrade to dbal 3.8 has advise by others.
To make it works, i've just add "public const ARRAY = 'array';" in vendor/doctrine/dbal/src/Types/Types.php
So far so good
Hey @weaverryan
I'm updating from version 2.7 to 3.0.
The filters are completely ignored, nothing absolutely changes at all.
I tried even with a vanilla project without success.
I know it is a long shot, but where should I start to look ?
Hey @Thomas-K!
Ah! Hmm. So, just "normal" built-in (not custom) filters don't work? You don't see them in the documentation and trying to manually add the query parameters doesn't work? That's very bizarre, especially in a fresh project. It makes me think (?) that maybe you're doing some subtle formatting wrong (e.g. on the attributes) and API Platform isn't seeing them? It's definitely a very odd thing, which often means it's something really subtle that's causing the issues. Do you have any special config setup - or are you using PHP attributes for the
#[ApiResource]like normal?Cheers!
Across the SearchFilter: Filter by Text section, you mention multiple times the
titleproperty, that should be thenameproperty.Hey @Alex-K
You're right! Ryan got confused. However, there's not a
titleproperty on the entity, so we could say thenameproperty is actually the title :DCheers!
Hi,
I have the following filter
but it returns an array of elements.
Is it possible to make it return a single element like findOneBy method inside Entity Repository?
Hey @Dmitriy!
Hmm, probably not. The problem is that the filters are only used on your "collection" endpoint - e.g.
/api/treasures, and these always return an array of items, even if it's an array of 1 thing. And that's by design: no matter what, when you use the "collection" endpoint, you get a collection back - and you can predict it's structure. It might actually be possible to return a single item (by creating a custom state provider, calling the core provider, then changing the array of 1 item to just an object and returning it from your state provider), but that's a lot of extra work to make your endpoint operate in a non-standard way. So my advice is: keep it as an array and keep it simple :).Cheers!
Thank you. It's a pity that everything is so complicated. I'll keep it simple)
Good morning.
I am very interested in the integration of the Api platform with Elastic Search to implement it.
Would you have a link, manual or video that explains it.
Or are you going to take a course on that?
Thank you.
Hey @Patricio-RM!
We don't have any plans to cover that right now. But API Platform does have docs and official support for Elastic Search - https://api-platform.com/docs/core/elasticsearch/ - and especially in episode 3 - https://symfonycasts.com/screencast/api-platform-extending - we go deeper into understanding how state providers work, which would at least clarify how that is working. I've never used the Elastic Search integration myself, but it looks a lot like the Doctrine support: there is a built-in
CollectionProviderthat knows how to pull data from ElasticSearch and populate your custom class.I hope this short answer at least helps a little bit :).
Cheers!
I see that there are 2 classes BooleanFilter, one for mangoDB(ODM) and one for MySQL5(ORM), how would i make my application support both of them?
Hey @Wael-G
I'm not sure about the answer but your application will run on two different databases?
I am just assuming that I have 2 clients with different Database preferences.
In that case, I think you would have to implement some sort of a "select database" system and remember that option somehow (perhaps in cache). Then, any service communicating to the DB will have to check that config and use the corresponding DB. I'm not sure if there are bundles that can help you with it
Oh, also, you'll have to set up Doctrine to work with multiple databases https://symfony.com/doc/current/doctrine/multiple_entity_managers.html
I hope it helps. Cheers!
"Houston: no signs of life"
Start the conversation!