diff --git a/.gitignore b/.gitignore index 6a612f1a173..a7c0aba13bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /website +.idea/ diff --git a/core/filters.md b/core/filters.md index 2956c4720e7..d9e72241c93 100644 --- a/core/filters.md +++ b/core/filters.md @@ -23,7 +23,7 @@ to a Resource in two ways: 1. Through the resource declaration, as the `filters` attribute. - For example having a filter service declaration: + For example having a filter service declaration: ```yaml # api/config/services.yaml @@ -40,9 +40,9 @@ to a Resource in two ways: public: false ``` - We're linking the filter `offer.date_filter` with the resource like this: + We're linking the filter `offer.date_filter` with the resource like this: - [codeSelector] + [codeSelector] ```php ``` - [/codeSelector] + [/codeSelector] -2. By using the `#[ApiFilter]` annotation. +2. By using the `#[ApiFilter]` attribute. - This annotation automatically declares the service, and you just have to use the filter class you want: + This attribute automatically declares the service, and you just have to use the filter class you want: ```php reader) { - throw new \RuntimeException(sprintf('An annotation reader must be provided. Be sure to call "%s::setAnnotationReader()".', __CLASS__)); - } - // The Doctrine filter is called for any query on any entity - // Check if the current entity is "user aware" (marked with an annotation) - $userAware = $this->reader->getClassAnnotation($targetEntity->getReflectionClass(), UserAware::class); - if (!$userAware) { + // Check if the current entity is "user aware" (marked with an attribute) + $userAware = $targetEntity->getReflectionClass()->getAttributes(UserAware::class)[0] ?? null; + + $fieldName = $userAware?->getArguments()['userFieldName'] ?? null; + if ($fieldName === '' || is_null($fieldName)) { return ''; } - $fieldName = $userAware->userFieldName; try { // Don't worry, getParameter automatically escapes parameters $userId = $this->getParameter('id'); @@ -1505,11 +1493,6 @@ final class UserFilter extends SQLFilter return sprintf('%s.%s = %s', $targetTableAlias, $fieldName, $userId); } - - public function setAnnotationReader(Reader $reader): void - { - $this->reader = $reader; - } } ``` @@ -1524,77 +1507,13 @@ doctrine: class: App\Filter\UserFilter ``` -Add a listener for every request that initializes the Doctrine filter with the current user in your bundle services declaration file. - -```yaml -# api/config/services.yaml -services: - # ... - 'App\EventListener\UserFilterConfigurator': - tags: - - { name: kernel.event_listener, event: kernel.request, priority: 5 } - # Autoconfiguration must be disabled to set a custom priority - autoconfigure: false -``` - -It's key to set the priority higher than the `ApiPlatform\Core\EventListener\ReadListener`'s priority, as flagged in [this issue](https://github.com/api-platform/core/issues/1185), as otherwise the `PaginatorExtension` will ignore the Doctrine filter and return incorrect `totalItems` and `page` (first/last/next) data. - -Lastly, implement the configurator class: - -```php -em = $em; - $this->tokenStorage = $tokenStorage; - $this->reader = $reader; - } - - public function onKernelRequest(): void - { - if (!$user = $this->getUser()) { - throw new \RuntimeException('There is no authenticated user.'); - } - - $filter = $this->em->getFilters()->enable('user_filter'); - $filter->setParameter('id', $user->getId()); - $filter->setAnnotationReader($this->reader); - } - - private function getUser(): ?UserInterface - { - if (!$token = $this->tokenStorage->getToken()) { - return null; - } - - $user = $token->getUser(); - return $user instanceof UserInterface ? $user : null; - } -} -``` - -Done: Doctrine will automatically filter all "UserAware" entities! +Done: Doctrine will automatically filter all `UserAware`entities! -## ApiFilter Annotation +## ApiFilter Attribute -The annotation can be used on a `property` or on a `class`. +The attribute can be used on a `property` or on a `class`. -If the annotation is given over a property, the filter will be configured on the property. For example, let's add a search filter on `name` and on the `prop` property of the `colors` relation: +If the attribute is given over a property, the filter will be configured on the property. For example, let's add a search filter on `name` and on the `prop` property of the `colors` relation: ```php 'ipartial'])] - public $colors; + public Collection $colors; + + public function __construct() + { + $this->colors = new ArrayCollection(); + } // ... } ``` -On the first property, `name`, it's straightforward. The first annotation argument is the filter class, the second specifies options, here, the strategy: +On the first property, `name`, it's straightforward. The first attribute argument is the filter class, the second specifies options, here, the strategy: ```php #[ApiFilter(SearchFilter::class, strategy: 'partial')] ``` -In the second annotation, we specify `properties` on which the filter should apply. It's necessary here because we don't want to filter `colors` but the `prop` property of the `colors` association. +In the second attribute, we specify `properties` on which the filter should apply. It's necessary here because we don't want to filter `colors` but the `prop` property of the `colors` association. Note that for each given property we specify the strategy: ```php #[ApiFilter(SearchFilter::class, properties: ['colors.prop' => 'ipartial'])] ``` -The `ApiFilter` annotation can be set on the class as well. If you don't specify any properties, it'll act on every property of the class. +The `ApiFilter` attribute can be set on the class as well. If you don't specify any properties, it'll act on every property of the class. For example, let's define three data filters (`DateFilter`, `SearchFilter` and `BooleanFilter`) and two serialization filters (`PropertyFilter` and `GroupFilter`) on our `DummyCar` class: @@ -1687,7 +1608,7 @@ The `DateFilter` given here will be applied to every `Date` property of the `Dum #[ApiFilter(DateFilter::class, strategy: DateFilter::EXCLUDE_NULL)] ``` -The `SearchFilter` here adds properties. The result is the exact same as the example with annotations on properties: +The `SearchFilter` here adds properties. The result is the exact same as the example with attributes on properties: ```php #[ApiFilter(SearchFilter::class, properties: ['colors.prop' => 'ipartial', 'name' => 'partial'])]