core icon indicating copy to clipboard operation
core copied to clipboard

Custom filters not applied in GraphQL

Open masonmcelvain opened this issue 4 years ago • 2 comments

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

masonmcelvain avatar Jan 03 '22 23:01 masonmcelvain

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.

BernardA avatar Apr 11 '22 19:04 BernardA

graphql: collection_query: filters: [ 'document.search_filter' ]

filters still not working(. YAML files

ilyamon avatar May 19 '22 18:05 ilyamon

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 ?

ArnoudThibaut avatar Oct 19 '22 18:10 ArnoudThibaut

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.

masonmcelvain avatar Oct 19 '22 20:10 masonmcelvain

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 ?

ArnoudThibaut avatar Oct 20 '22 11:10 ArnoudThibaut

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!

masonmcelvain avatar Oct 20 '22 15:10 masonmcelvain