Custom filters not applied in GraphQL
API Platform version(s) affected: 2.6.7
Description
Custom filters extending ApiPlatform\Core\Serializer\Filter\FilterInterface are applied in REST but not GraphQL when using a custom data provider (i.e. not Doctrine).
getDescription() is called correctly, so the descriptions show up in the GraphQL schema definition. But the apply() method does not seem to be called.
How to reproduce
Create a new project with symfony and composer. Everything should get autowired/autoconfigured.
Create a resource class App\Entity\MyEntity.php and set up a custom data provider for it that extends ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface. Create a filter class MyCustomFilter that throws an exception when the filter is applied. This is intended to identify when the filter is applied.
// MyEntity.php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Filter\MyCustomFilter;
#[ApiResource(
graphql: ['collection_query'],
collectionOperations: ['get'],
)]
#[ApiFilter(MyCustomFilter::class)]
class MyEntity { ... }
// MyEntityDataProvider.php
namespace App\DataProvider;
use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
use App\Entity\MyEntity;
final class MyEntityDataProvider implements ContextAwareCollectionDataProviderInterface {
public function getCollection(string $resourceClass, string $operationName = null, array $context = []) {
// Assume this returns an iterator of MyEntity stored in a class
// that extends ApiPlatform\Core\DataProvider\PaginatorInterface
return $this->getData();
}
}
// MyCustomFilter.php
namespace App\Filter
use ApiPlatform\Core\Serializer\Filter\FilterInterface;
use Symfony\Component\HttpFoundation\Request;
class MyCustomFilter implements FilterInterface {
public function apply(Request $request, bool $normalization, array $attributes, array &$context) {
throw new Exception("I get thrown in REST but not GraphQL");
}
public function getDescription(string $resourceClass): array {
return [
'filterThatThrows' => [
'property' => 'nameOfRelevantProperty',
'type' => 'string',
'required' => false,
]
];
}
}
It still doesn't work when we use attributes in ApiResource on the entity class.
// MyEntity.php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Filter\MyCustomFilter;
#[ApiResource(
attributes: [
'filters' => [MyCustomFilter::class],
],
graphql: ['collection_query' => [
'filters' => [MyCustomFilter::class],
],
],
collectionOperations: ['get'],
)]
class MyEntity { ... }
In REST, when the client specifies filterThatThrows in the request, we get an exception as expected.
In GraphQL, when the client specifies filterThatThrows in the request, no exception gets thrown, indicating that MyCustomFilter::apply() never got called.
Possible Solution
¯_(ツ)_/¯
Additional Context
Possibly related issues: https://github.com/api-platform/core/issues/3550 https://github.com/api-platform/api-platform/issues/1674
Relevant documentation: https://api-platform.com/docs/core/graphql/#filters https://api-platform.com/docs/core/filters/#creating-custom-filters https://api-platform.com/docs/core/filters/#apifilter-attribute
I have the same issue. I am following the SymfonyCast API-Platform Part 3 course.
More specifically on this lesson: https://symfonycasts.com/screencast/api-platform-extending/filter-arguments#comment-5824301802
That course is mostly REST oriented while I mostly work with GraphQl.
So when I enabled GraphQl and attempted to run the filter I noticed that the apply() method is not called at all.
The filter is called as so:
App\Entity\DailyStat
* @ApiFilter(DailyStatDateFilter::class, arguments={"throwOnInvalid"=true})
With REST it's possible to catch the arguments on the constructor and then on the apply method as so:\
App\ApiPlatform\DailyStatDateFilter
public $throwOnInvalid;
public function __construct( bool $throwOnInvalid = false)
{
$this->throwOnInvalid = $throwOnInvalid;
}
But not so with GraphQl.
Note that the filter as such is set and operational in GraphQl.
The corresponding DataProvider is also called.
Note that the course uses version 2.1, but I had the same issue on a project of mine that uses version 2.6.
For the code, I believe the folks from API-Platform have access the SymfonyCasts material. Let me know otherwise.
graphql: collection_query: filters: [ 'document.search_filter' ]
filters still not working(. YAML files
Hello,
Just to be sure @masonmcelvain, do you really want to create a filter that implements ApiPlatform\Core\Serializer\Filter\FilterInterface and not \ApiPlatform\Doctrine\Orm\Filter\FilterInterface or \ApiPlatform\Doctrine\Odm\Filter\FilterInterface ?
If yes, what is your use case ?
Hi @ArnoudThibaut, thanks for your response.
For context, this issue is with version 2.6. It looks like API Platform has since migrated to a new data provider "state" system in version 3.0, so this might not be an issue in that version.
Yes, implementing ApiPlatform\Core\Serializer\Filter\FilterInterface was intentional. I am not using doctrine in my project. Instead, I am working with custom ORMs for my database. Therefore, it doesn't make sense to extend the doctrine FilterInterface's, because doctrine's QueryBuilder is incompatible with my ORM.
Use case: I'd like to let consumers of my API filter collections returned by my custom CollectionDataProvider.
See 380e1c321c71103ab56a25f39262be2232655f83 for where I think the filter should be applied in GraphQL, based on how it's applied in REST.
For context, this issue is with version 2.6. It looks like API Platform has since migrated to a new data provider "state" system in version 3.0, so this might not be an issue in that version. Nothing change with the 3.0, the issue is still here.
The ApiPlatform\Core\Serializer\Filter\FilterInterface is here to modify the serializer context based on GET parameters passed by the user.
For me it would be better for you to create your own FilterInterface that will extend the ApiPlatform\Api\FilterInterface with an apply method that will fit the need of your ORM to customize the query. You can have a look at \ApiPlatform\Doctrine\Orm\Extension\FilterExtension and ApiPlatform\Core\Bridge\Doctrine\Orm\CollectionDataProvider to find inspiration on how apply your filters.
But maybe I didn't understand well your problem and you really need to modifiy the serializer context with GraphQL. If so could you provide an example on how your are doing that ?
The ApiPlatform\Core\Serializer\Filter\FilterInterface is here to modify the serializer context based on GET parameters passed by the user.
Ah, that would explain why ApiPlatform\Core\Serializer\Filter\FilterInterface is not applied in GraphQL, since it uses POST. Thanks for clarifying the purpose of this class.
For me it would be better for you to create your own FilterInterface that will extend the ApiPlatform\Api\FilterInterface with an apply method that will fit the need of your ORM to customize the query.
I will try this solution, as I don't need to specifically modify the serializer context with GraphQL. Thanks for helping me out @ArnoudThibaut!