-
-
Notifications
You must be signed in to change notification settings - Fork 966
API Platform "OR" and "IN" filters #639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Hi @anacicconi, this is something really missing right now and yes you can indeed create a custom filter at the moment. There is an issue opened in the api-platform/core#1832 to fill the gap :) |
Thanks for your quick reply @GregoireHebert ! Are developers creating custom endpoints for these cases? Or custom filters are indeed the best option as we can remain restful? |
I would go for a custom filter. Maybe @Simperfit has a different POV on this ? |
Custom filters it is :) We already reasoned to this in another issue IIRC. |
I think you're talking about this one @soyuka: api-platform/core#398 I read you said this feature should not be on core. However, as it was from June 2017, I wasn't sure if it was up to date. Thanks ;) |
Same for me, it should be in a custom filter. But, we are maybe going to implement a where filter, if the RFC is approved. I will prepare the PR anyway ;). |
Can I reuse the issue to ask a question about the custom filters? I have been testing them since I got your replies. Using the Regexp from the documentation, I'm able to reach the filterProperty method when I add the filter to my resource like this:
However, when I try to use the ApiFilter annotation nothing happens:
I tried many combinations of the way I add the filter to the resource and the way I declare my service. There are only two ways I can make this work. First:
This way I have both the filter in the swagger interface and I reach the filterProperty method. Second:
This way I don't have the filter in the swagger interface but still if I curl the url with regexp_website as a parameter I reach the filterProperty method. My service options for both cases:
Even if the first option works, I would prefer to use the ApiFilter annotation. This way I would be able to declare a generic custom filter that I can use in any resource without specifying the properties in the services.yml. And of course I would have the filter in the swagger interface. Am I missing something here? |
Does the multi values not work for the |
Hi @teohhanhui, yes they work. The title of my question is not good. I was starting to deal with filters when I asked it. As you pointed out, the Search Filter checks if it is an array or a single value. If it's an array, it adds a "IN". However, for "OR" queries, I had to do a custom filter. |
Hello, I'll start this conversation again if it's ok. I did a custom "OR" filter for my app which is basically a copy of the "SearchFilter" with some modifications for the query. It works just fine. However, I think it's a pity to have a copy of another file like that. I was wondering if I could have my custom filter to extend the "SearchFilter". I read in one of your issues that you don't advise people to do that. Is there a reason? Besides the fact that eventually the "SearchFilter" could be updated and break my custom filter. Moreover, would it be a good idea to change the original "SearchFilter" so the user could choose what kind of operator he wants? Thanks! |
In case someone is looking for an example of an OR search filter : here it is https://gist.github.com/axelvnk/edf879af5c7dbd9616a4eeb77c7181a3 |
I did one myself (more like a full-text kind-of filter thingy). https://gist.github.com/masseelch/47931f3a745409f8f44c69efa9ecb05c |
I've added multiple search options and swagger doc. https://gist.github.com/Tersoal/d45b0cc75cadf72cd7c0e49b892809b3 |
Here's another example filter you could implement as a temporary workaround extending the SearchFilter while decisions are made on which where filter syntax to implement (#1724) https://gist.github.com/silverbackdan/0a1753735e07210b3f4365a3100b83b7 and using the syntax |
One can accomplish similar results (and more) with FilterLogic: combines existing API Platform ORM Filters with AND, OR and NOT according to client request.
|
Hi, (Sorry for my bad english) With version 2.7 somes changes are applied, Thank's in advance ! John, |
How do i obtian the 2.7 version of api platform ? |
Version v3.0.0.rc2 of FilterLogic has been adapted for API Platform Core 3.0 and 2.7 with metadata_backward_compatibility_layer set to false. BTW, According to https://github.com/api-platform/api-platform/releases there is still no 2.7 version, but let's assume you meant the Core repo (but then this issues should have been created there) then still, the first 2.7 version on https://github.com/api-platform/core/releases is v2.7.0-rc.1 and it is dated jul 20 2022. |
if it can help anyone, i wrote this in order to be able to search on multiple fields with only one query param: protected function filterProperty(
string $property,
$value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
Operation $operation = null,
array $context = []
): void
{
if ($property !== 'search') {
return;
}
$fields = $this->getProperties();
if (empty($fields)) {
throw new \InvalidArgumentException('At least one field must be specified.');
}
$orExpressions = [];
foreach (array_keys($fields) as $field) {
if (!$this->isPropertyEnabled($field, $resourceClass) || !$this->isPropertyMapped($field, $resourceClass)) {
return;
}
$orExpressions[] = sprintf('%s.%s LIKE :search', $queryBuilder->getRootAliases()[0], $field);
}
$queryBuilder
->andWhere(implode(' OR ', $orExpressions))
->setParameter('search', "%$value%");
} Now you can add |
Can you tell me where i can use your code? In which class? In which file? I don't understand |
Sure! here is an improved version which also allows you to search in the subresources. // src/Filter/MultipleFieldsSearchFilter.php
namespace App\Filter;
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\PropertyInfo\Type;
final class MultipleFieldsSearchFilter extends AbstractFilter
{
protected function filterProperty(
string $property,
$value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
Operation $operation = null,
array $context = []
): void
{
if ($property !== 'search') {
return;
}
$fields = $this->getProperties();
if (empty($fields)) {
throw new \InvalidArgumentException('At least one field must be specified.');
}
$alias = $queryBuilder->getRootAliases()[0];
$orExpressions = [];
foreach (array_keys($fields) as $k => $field) {
if ($this->isPropertyNested($field, $resourceClass)) {
$exploded_field = explode('.', $field);
if (!in_array($exploded_field[0], $queryBuilder->getAllAliases())) {
$queryBuilder->leftJoin($alias . '.' . $exploded_field[0], $exploded_field[0]);
}
$orExpressions[] = sprintf('%s.%s LIKE :search', $exploded_field[0], $exploded_field[1]);
} else {
$orExpressions[] = sprintf('%s.%s LIKE :search', $alias, $field);
}
}
$queryBuilder
->andWhere(implode(' OR ', $orExpressions))
->setParameter('search', "%$value%");
}
public function getDescription(string $resourceClass): array
{
// ...
}
} And on the entity side you can declare it as follows // src/Entity/Book.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\ApiFilter;
use App\Filter\MultipleFieldsSearchFilter;
// ...
#[ApiResource(
// ...
)]
#[ApiFilter(MultipleFieldsSearchFilter::class, properties: [
"name",
"description",
"subject.name", "subject.description",
// the other desired fields
])]
#[ORM\Entity(repositoryClass: BookRepository::class)]
class Book
{
// ...
} now you can search in multiple fields with the following query: |
This code may help you solve the problem Exemple for entity user : #[ApiFilter(OrSearchFilter::class, properties: ['firstname', 'lastname', 'country.name'])] <?php
namespace App\Filter;
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\PropertyInfo\Type;
final class OrSearchFilter extends AbstractFilter
{
private const FILTER_KEY = 'search';
protected function filterProperty(
string $property,
$value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
Operation $operation = null,
array $context = []
): void {
if ($property !== self::FILTER_KEY) {
return;
}
foreach ($this->getProperties() as $property => $stat) {
if (!$this->isPropertyEnabled($property, $resourceClass) || !$this->isPropertyMapped($property, $resourceClass, true)) {
return;
}
$alias = $queryBuilder->getRootAliases()[0];
$field = $property;
if ($this->isPropertyNested($property, $resourceClass)) {
[$alias, $field] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass, Join::LEFT_JOIN);
}
$queryBuilder
->orWhere(sprintf('%s.%s Like :search', $alias, $field))
->setParameter('search', "%$value%");
}
}
/** {@inheritdoc} */
public function getDescription(string $resourceClass): array
{
if (!$this->properties) {
return [];
}
$description = [];
foreach ($this->properties as $property => $strategy) {
$description[self::FILTER_KEY . "_" . $property] = [
'property' => $property,
'type' => Type::BUILTIN_TYPE_STRING,
'required' => false,
'description' => 'Filter by ' . $property . ' using Or condition',
];
}
return $description;
}
} |
@AmineOUERTANI as described in this issue using $queryBuilder->orWhere can cause security issues in combination with extensions. The solution of @drennvinn uses seperate or expressions that are combined with the rest of the query through ->andWhere so will not have this problem. |
As I posted above a while ago, this was also another solution I had whereby the 'or' queries are all wrapped within an 'andWhere' - I think it still works. https://gist.github.com/silverbackdan/0a1753735e07210b3f4365a3100b83b7 |
@metaclass-nl here is the solution with andwhere <?php
namespace App\Filter;
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\PropertyInfo\Type;
final class OrSearchFilter extends AbstractFilter
{
private const FILTER_KEY = 'search';
protected function filterProperty(
string $property,
$value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
Operation $operation = null,
array $context = []
): void {
if ($property !== self::FILTER_KEY) {
return;
}
$orExpressions = [];
foreach ($this->getProperties() as $field => $strategy) {
if (!$this->isPropertyEnabled($field, $resourceClass) || !$this->isPropertyMapped($field, $resourceClass, true)) {
return;
}
$alias = $queryBuilder->getRootAliases()[0];
$associations = [];
if ($this->isPropertyNested($field, $resourceClass)) {
[$alias, $field, $associations] = $this->addJoinsForNestedProperty($field, $alias, $queryBuilder, $queryNameGenerator, $resourceClass, Join::LEFT_JOIN);
}
$metadata = $this->getNestedMetadata($resourceClass, $associations);
if ($metadata->hasField($field)) {
$orExpressions[] = sprintf('%s.%s LIKE :search', $alias, $field);
}
}
$queryBuilder
->andWhere(implode(' OR ', $orExpressions))
->setParameter('search', "%$value%");
}
/** {@inheritdoc} */
public function getDescription(string $resourceClass): array
{
if (!$this->properties) {
return [];
}
$description = [];
foreach ($this->properties as $property => $strategy) {
$description[self::FILTER_KEY . "_" . $property] = [
'property' => $property,
'type' => Type::BUILTIN_TYPE_STRING,
'required' => false,
'description' => 'Filter by ' . $property . ' using Or condition',
];
}
return $description;
}
} |
Do any of these solutions work with GraphQL? Out of the box, they are detected by the graphql schema, but don't appear to be effective on query. i.e.
the above in gql would return the query as if the |
In file: api\config\packages\api_platform.yaml Remove: parameters: Just comment this line and the problem is gone: metadata_backward_compatibility_layer |
I have been testing API Platform with the different filters available. I noticed we can do a lot without creating custom endpoints. However, when it comes to "OR" and "IN" filters I couldn't find anything.
The idea is to have behind queries acting as:
"WHERE property1 = foo OR property2 = bar"
or
"WHERE property1 IN (foo, bar...)"
Should we create a custom filter for each entity where we can have this kind of query or has API Platform some default feature?
The text was updated successfully, but these errors were encountered: