From f09bb713d53d4d7744eb778a984bb7ac83b23331 Mon Sep 17 00:00:00 2001 From: meyerbaptiste Date: Wed, 3 Apr 2019 11:44:02 +0200 Subject: [PATCH] Fix: don't filter unreadable properties --- src/Api/ContextAwareFilterInterface.php | 27 ++++++++++ src/Api/FilterInterface.php | 2 +- .../Common/Filter/BooleanFilterTrait.php | 15 +++++- .../Common/Filter/DateFilterTrait.php | 15 +++++- .../Common/Filter/ExistsFilterTrait.php | 15 +++++- .../Common/Filter/NumericFilterTrait.php | 15 +++++- .../Common/Filter/OrderFilterTrait.php | 14 ++++- .../Common/Filter/RangeFilterTrait.php | 14 ++++- .../Common/Filter/SearchFilterTrait.php | 14 ++++- .../Doctrine/Common/PropertyHelperTrait.php | 49 ++++++++++++++++++ .../MongoDbOdm/Filter/AbstractFilter.php | 19 ++----- .../MongoDbOdm/Filter/BooleanFilter.php | 14 ++++- .../Doctrine/MongoDbOdm/Filter/DateFilter.php | 14 ++++- .../MongoDbOdm/Filter/ExistsFilter.php | 14 ++++- .../MongoDbOdm/Filter/FilterInterface.php | 2 +- .../MongoDbOdm/Filter/NumericFilter.php | 14 ++++- .../MongoDbOdm/Filter/OrderFilter.php | 19 +++++-- .../MongoDbOdm/Filter/RangeFilter.php | 14 ++++- .../MongoDbOdm/Filter/SearchFilter.php | 19 +++++-- .../Doctrine/Orm/Filter/AbstractFilter.php | 51 +++++++++---------- .../Doctrine/Orm/Filter/BooleanFilter.php | 10 +++- src/Bridge/Doctrine/Orm/Filter/DateFilter.php | 10 +++- .../Doctrine/Orm/Filter/ExistsFilter.php | 10 +++- .../Doctrine/Orm/Filter/NumericFilter.php | 10 +++- .../Doctrine/Orm/Filter/OrderFilter.php | 29 +++++++---- .../Doctrine/Orm/Filter/RangeFilter.php | 10 +++- .../Doctrine/Orm/Filter/SearchFilter.php | 17 +++++-- .../DataProvider/Filter/AbstractFilter.php | 27 +++++++--- .../Filter/AbstractSearchFilter.php | 8 +-- .../DataProvider/Filter/FilterInterface.php | 5 +- .../DataProvider/Filter/OrderFilter.php | 8 +-- .../ApiPlatformProvider.php | 2 +- .../ApiPlatformExtension.php | 3 ++ .../Resources/config/doctrine_mongodb_odm.xml | 7 +++ .../Bundle/Resources/config/doctrine_orm.xml | 7 +++ src/Filter/QueryParameterValidateListener.php | 2 +- .../CollectionFiltersNormalizer.php | 6 +-- .../PropertyMetadataFactoryOptionsTrait.php | 41 +++++++++++++++ src/Serializer/AbstractItemNormalizer.php | 28 ++-------- .../Serializer/DocumentationNormalizer.php | 2 +- 40 files changed, 458 insertions(+), 144 deletions(-) create mode 100644 src/Api/ContextAwareFilterInterface.php create mode 100644 src/Metadata/Property/PropertyMetadataFactoryOptionsTrait.php diff --git a/src/Api/ContextAwareFilterInterface.php b/src/Api/ContextAwareFilterInterface.php new file mode 100644 index 00000000000..f193610d7df --- /dev/null +++ b/src/Api/ContextAwareFilterInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Api; + +/** + * Context aware filter. + * + * @author Baptiste Meyer + */ +interface ContextAwareFilterInterface extends FilterInterface +{ + /** + * {@inheritdoc} + */ + public function getDescription(string $resourceClass, array $context = []): array; +} diff --git a/src/Api/FilterInterface.php b/src/Api/FilterInterface.php index 970ebdaf940..b09200fe458 100644 --- a/src/Api/FilterInterface.php +++ b/src/Api/FilterInterface.php @@ -47,5 +47,5 @@ interface FilterInterface * * @see \ApiPlatform\Core\Swagger\Serializer\DocumentationNormalizer::getFiltersParameters */ - public function getDescription(string $resourceClass): array; + public function getDescription(string $resourceClass/*, array $context = []*/): array; } diff --git a/src/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php index cf09b77b0d4..c92b9b602dd 100644 --- a/src/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php +++ b/src/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php @@ -26,6 +26,8 @@ * For each property passed, if the resource does not have such property or if * the value is not one of ( "true" | "false" | "1" | "0" ) the property is ignored. * + * @internal + * * @author Amrouche Hamza * @author Teoh Han Hui * @author Alan Poulain @@ -37,8 +39,13 @@ trait BooleanFilterTrait /** * {@inheritdoc} */ - public function getDescription(string $resourceClass): array + public function getDescription(string $resourceClass/*, array $context = []*/): array { + if (\func_num_args() < 2 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a second `$context` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 1 < \func_num_args() ? (array) func_get_arg(1) : []; $description = []; $properties = $this->getProperties(); @@ -47,7 +54,11 @@ public function getDescription(string $resourceClass): array } foreach ($properties as $property => $unused) { - if (!$this->isPropertyMapped($property, $resourceClass) || !$this->isBooleanField($property, $resourceClass)) { + if ( + !$this->isPropertyEnabled($property, $resourceClass, $context) || + !$this->isPropertyMapped($property, $resourceClass) || + !$this->isBooleanField($property, $resourceClass) + ) { continue; } diff --git a/src/Bridge/Doctrine/Common/Filter/DateFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/DateFilterTrait.php index 8b2492067a3..0e898cf27d4 100644 --- a/src/Bridge/Doctrine/Common/Filter/DateFilterTrait.php +++ b/src/Bridge/Doctrine/Common/Filter/DateFilterTrait.php @@ -18,6 +18,8 @@ /** * Trait for filtering the collection by date intervals. * + * @internal + * * @author Kévin Dunglas * @author Théo FIDRY * @author Alan Poulain @@ -29,8 +31,13 @@ trait DateFilterTrait /** * {@inheritdoc} */ - public function getDescription(string $resourceClass): array + public function getDescription(string $resourceClass/*, array $context = []*/): array { + if (\func_num_args() < 2 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a second `$context` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 1 < \func_num_args() ? (array) func_get_arg(1) : []; $description = []; $properties = $this->getProperties(); @@ -39,7 +46,11 @@ public function getDescription(string $resourceClass): array } foreach ($properties as $property => $nullManagement) { - if (!$this->isPropertyMapped($property, $resourceClass) || !$this->isDateField($property, $resourceClass)) { + if ( + !$this->isPropertyEnabled($property, $resourceClass, $context) || + !$this->isPropertyMapped($property, $resourceClass) || + !$this->isDateField($property, $resourceClass) + ) { continue; } diff --git a/src/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php index 1fed390cbdf..45e58fae516 100644 --- a/src/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php +++ b/src/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php @@ -20,6 +20,8 @@ /** * Trait for filtering the collection by whether a property value exists or not. * + * @internal + * * @author Teoh Han Hui * @author Alan Poulain */ @@ -30,8 +32,13 @@ trait ExistsFilterTrait /** * {@inheritdoc} */ - public function getDescription(string $resourceClass): array + public function getDescription(string $resourceClass/*, array $context = []*/): array { + if (\func_num_args() < 2 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a second `$context` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 1 < \func_num_args() ? (array) func_get_arg(1) : []; $description = []; $properties = $this->getProperties(); @@ -40,7 +47,11 @@ public function getDescription(string $resourceClass): array } foreach ($properties as $property => $unused) { - if (!$this->isPropertyMapped($property, $resourceClass, true) || !$this->isNullableField($property, $resourceClass)) { + if ( + !$this->isPropertyEnabled($property, $resourceClass, $context) || + !$this->isPropertyMapped($property, $resourceClass, true) || + !$this->isNullableField($property, $resourceClass) + ) { continue; } diff --git a/src/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php index bd6f5ee8525..6882921d74d 100644 --- a/src/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php +++ b/src/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php @@ -20,6 +20,8 @@ /** * Trait for filtering the collection by numeric values. * + * @internal + * * @author Amrouche Hamza * @author Teoh Han Hui * @author Alan Poulain @@ -31,8 +33,13 @@ trait NumericFilterTrait /** * {@inheritdoc} */ - public function getDescription(string $resourceClass): array + public function getDescription(string $resourceClass/*, array $context = []*/): array { + if (\func_num_args() < 2 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a second `$context` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 1 < \func_num_args() ? (array) func_get_arg(1) : []; $description = []; $properties = $this->getProperties(); @@ -41,7 +48,11 @@ public function getDescription(string $resourceClass): array } foreach ($properties as $property => $unused) { - if (!$this->isPropertyMapped($property, $resourceClass) || !$this->isNumericField($property, $resourceClass)) { + if ( + !$this->isPropertyEnabled($property, $resourceClass, $context) || + !$this->isPropertyMapped($property, $resourceClass) || + !$this->isNumericField($property, $resourceClass) + ) { continue; } diff --git a/src/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php index 658ea522945..af40cd02443 100644 --- a/src/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php +++ b/src/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php @@ -18,6 +18,8 @@ /** * Trait for ordering the collection by given properties. * + * @internal + * * @author Kévin Dunglas * @author Théo FIDRY * @author Alan Poulain @@ -34,8 +36,13 @@ trait OrderFilterTrait /** * {@inheritdoc} */ - public function getDescription(string $resourceClass): array + public function getDescription(string $resourceClass/*, array $context = []*/): array { + if (\func_num_args() < 2 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a second `$context` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 1 < \func_num_args() ? (array) func_get_arg(1) : []; $description = []; $properties = $this->getProperties(); @@ -44,7 +51,10 @@ public function getDescription(string $resourceClass): array } foreach ($properties as $property => $propertyOptions) { - if (!$this->isPropertyMapped($property, $resourceClass)) { + if ( + !$this->isPropertyEnabled($property, $resourceClass, $context) || + !$this->isPropertyMapped($property, $resourceClass) + ) { continue; } diff --git a/src/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php index a44cbdd0d28..03b81e41793 100644 --- a/src/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php +++ b/src/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php @@ -20,6 +20,8 @@ /** * Trait for filtering the collection by range. * + * @internal + * * @author Lee Siong Chan * @author Alan Poulain */ @@ -30,8 +32,13 @@ trait RangeFilterTrait /** * {@inheritdoc} */ - public function getDescription(string $resourceClass): array + public function getDescription(string $resourceClass/*, array $context = []*/): array { + if (\func_num_args() < 2 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a second `$context` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 1 < \func_num_args() ? (array) func_get_arg(1) : []; $description = []; $properties = $this->getProperties(); @@ -40,7 +47,10 @@ public function getDescription(string $resourceClass): array } foreach ($properties as $property => $unused) { - if (!$this->isPropertyMapped($property, $resourceClass)) { + if ( + !$this->isPropertyEnabled($property, $resourceClass, $context) || + !$this->isPropertyMapped($property, $resourceClass) + ) { continue; } diff --git a/src/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php index 8ed31a3e129..43222e9cac0 100644 --- a/src/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php +++ b/src/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php @@ -22,6 +22,8 @@ /** * Trait for filtering the collection by given properties. * + * @internal + * * @author Kévin Dunglas * @author Alan Poulain */ @@ -35,8 +37,13 @@ trait SearchFilterTrait /** * {@inheritdoc} */ - public function getDescription(string $resourceClass): array + public function getDescription(string $resourceClass/*, array $context = []*/): array { + if (\func_num_args() < 2 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a second `$context` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 1 < \func_num_args() ? (array) func_get_arg(1) : []; $description = []; $properties = $this->getProperties(); @@ -45,7 +52,10 @@ public function getDescription(string $resourceClass): array } foreach ($properties as $property => $strategy) { - if (!$this->isPropertyMapped($property, $resourceClass, true)) { + if ( + !$this->isPropertyEnabled($property, $resourceClass, $context) || + !$this->isPropertyMapped($property, $resourceClass, true) + ) { continue; } diff --git a/src/Bridge/Doctrine/Common/PropertyHelperTrait.php b/src/Bridge/Doctrine/Common/PropertyHelperTrait.php index 52b553bc8ce..d8e44a54bf9 100644 --- a/src/Bridge/Doctrine/Common/PropertyHelperTrait.php +++ b/src/Bridge/Doctrine/Common/PropertyHelperTrait.php @@ -13,6 +13,9 @@ namespace ApiPlatform\Core\Bridge\Doctrine\Common; +use ApiPlatform\Core\Exception\PropertyNotFoundException; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Property\PropertyMetadataFactoryOptionsTrait; use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\DBAL\Types\Type; @@ -26,8 +29,54 @@ */ trait PropertyHelperTrait { + use PropertyMetadataFactoryOptionsTrait; + + /** @var PropertyMetadataFactoryInterface|null */ + protected $propertyMetadataFactory; + abstract protected function getManagerRegistry(): ManagerRegistry; + /** + * Determines whether the given property is enabled. + */ + protected function isPropertyEnabled(string $property/*, string $resourceClass, array $context = []*/): bool + { + if (\func_num_args() < 3 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + if (\func_num_args() < 2) { + @trigger_error(sprintf('Method %s() will have a second `$resourceClass` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.1.', __FUNCTION__), E_USER_DEPRECATED); + } + + @trigger_error(sprintf('Method %s() will have a third "$context" argument in API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $resourceClass = 1 < \func_num_args() ? (string) func_get_arg(1) : null; + $context = 2 < \func_num_args() ? (array) func_get_arg(2) : []; + + if (null !== $this->properties) { + return \array_key_exists($property, $this->properties); + } + + if (null !== $resourceClass && null !== $this->propertyMetadataFactory) { + try { + $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property, $this->getPropertyMetadataFactoryOptions($context)); + } catch (PropertyNotFoundException $e) { + return false; + } + + // to ensure sanity, unreadable properties must still be explicitly enabled + if (!$propertyMetadata->isReadable()) { + return false; + } + } + + // to ensure sanity, nested properties must still be explicitly enabled + if (null === $resourceClass) { + return !$this->isPropertyNested($property); + } + + return !$this->isPropertyNested($property, $resourceClass); + } + /** * Determines whether the given property is mapped. */ diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php b/src/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php index a5fe637fdc7..d0907d2c754 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php +++ b/src/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php @@ -15,6 +15,7 @@ use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\PropertyHelperTrait as MongoDbOdmPropertyHelperTrait; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\ODM\MongoDB\Aggregation\Builder; use Psr\Log\LoggerInterface; @@ -31,16 +32,17 @@ */ abstract class AbstractFilter implements FilterInterface { - use PropertyHelperTrait; use MongoDbOdmPropertyHelperTrait; + use PropertyHelperTrait; protected $managerRegistry; protected $logger; protected $properties; - public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $logger = null, array $properties = null) + public function __construct(ManagerRegistry $managerRegistry, PropertyMetadataFactoryInterface $propertyMetadataFactory, LoggerInterface $logger = null, array $properties = null) { $this->managerRegistry = $managerRegistry; + $this->propertyMetadataFactory = $propertyMetadataFactory; $this->logger = $logger ?? new NullLogger(); $this->properties = $properties; } @@ -74,17 +76,4 @@ protected function getLogger(): LoggerInterface { return $this->logger; } - - /** - * Determines whether the given property is enabled. - */ - protected function isPropertyEnabled(string $property, string $resourceClass): bool - { - if (null === $this->properties) { - // to ensure sanity, nested properties must still be explicitly enabled - return !$this->isPropertyNested($property, $resourceClass); - } - - return \array_key_exists($property, $this->properties); - } } diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/BooleanFilter.php b/src/Bridge/Doctrine/MongoDbOdm/Filter/BooleanFilter.php index 7b72bbfaec9..61df3a7418b 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/Filter/BooleanFilter.php +++ b/src/Bridge/Doctrine/MongoDbOdm/Filter/BooleanFilter.php @@ -34,20 +34,30 @@ */ final class BooleanFilter extends AbstractFilter { - use BooleanFilterTrait; + use BooleanFilterTrait { + getDescription as private getDescriptionLegacy; + } public const DOCTRINE_BOOLEAN_TYPES = [ MongoDbType::BOOL => true, MongoDbType::BOOLEAN => true, ]; + /** + * @inheritdoc} + */ + public function getDescription(string $resourceClass, array $context = []): array + { + return $this->getDescriptionLegacy($resourceClass, $context); + } + /** * {@inheritdoc} */ protected function filterProperty(string $property, $value, Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) { if ( - !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass) || !$this->isBooleanField($property, $resourceClass) ) { diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/DateFilter.php b/src/Bridge/Doctrine/MongoDbOdm/Filter/DateFilter.php index 275a153e369..81cc02e2f06 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/Filter/DateFilter.php +++ b/src/Bridge/Doctrine/MongoDbOdm/Filter/DateFilter.php @@ -30,12 +30,22 @@ */ class DateFilter extends AbstractFilter implements DateFilterInterface { - use DateFilterTrait; + use DateFilterTrait { + getDescription as private getDescriptionLegacy; + } public const DOCTRINE_DATE_TYPES = [ MongoDbType::DATE => true, ]; + /** + * @inheritdoc} + */ + public function getDescription(string $resourceClass, array $context = []): array + { + return $this->getDescriptionLegacy($resourceClass, $context); + } + /** * {@inheritdoc} */ @@ -44,7 +54,7 @@ protected function filterProperty(string $property, $values, Builder $aggregatio // Expect $values to be an array having the period as keys and the date value as values if ( !\is_array($values) || - !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass) || !$this->isDateField($property, $resourceClass) ) { diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php b/src/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php index d62677c7da8..206bf888011 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php +++ b/src/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php @@ -35,7 +35,17 @@ */ final class ExistsFilter extends AbstractFilter implements ExistsFilterInterface { - use ExistsFilterTrait; + use ExistsFilterTrait { + getDescription as private getDescriptionLegacy; + } + + /** + * @inheritdoc} + */ + public function getDescription(string $resourceClass, array $context = []): array + { + return $this->getDescriptionLegacy($resourceClass, $context); + } /** * {@inheritdoc} @@ -44,7 +54,7 @@ protected function filterProperty(string $property, $value, Builder $aggregation { if ( !isset($value[self::QUERY_PARAMETER_KEY]) || - !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass, true) || !$this->isNullableField($property, $resourceClass) ) { diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/FilterInterface.php b/src/Bridge/Doctrine/MongoDbOdm/Filter/FilterInterface.php index c5f7546e843..f2c6e5f002c 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/Filter/FilterInterface.php +++ b/src/Bridge/Doctrine/MongoDbOdm/Filter/FilterInterface.php @@ -13,7 +13,7 @@ namespace ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter; -use ApiPlatform\Core\Api\FilterInterface as BaseFilterInterface; +use ApiPlatform\Core\Api\ContextAwareFilterInterface as BaseFilterInterface; use Doctrine\ODM\MongoDB\Aggregation\Builder; /** diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/NumericFilter.php b/src/Bridge/Doctrine/MongoDbOdm/Filter/NumericFilter.php index a8effa1c7de..4a48d84adb0 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/Filter/NumericFilter.php +++ b/src/Bridge/Doctrine/MongoDbOdm/Filter/NumericFilter.php @@ -33,7 +33,9 @@ */ final class NumericFilter extends AbstractFilter { - use NumericFilterTrait; + use NumericFilterTrait { + getDescription as private getDescriptionLegacy; + } /** * Type of numeric in Doctrine. @@ -44,13 +46,21 @@ final class NumericFilter extends AbstractFilter MongoDbType::FLOAT => true, ]; + /** + * @inheritdoc} + */ + public function getDescription(string $resourceClass, array $context = []): array + { + return $this->getDescriptionLegacy($resourceClass, $context); + } + /** * {@inheritdoc} */ protected function filterProperty(string $property, $value, Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) { if ( - !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass) || !$this->isNumericField($property, $resourceClass) ) { diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php b/src/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php index 1d5c5b6c762..3c6f10652f5 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php +++ b/src/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php @@ -15,6 +15,7 @@ use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\OrderFilterInterface; use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\OrderFilterTrait; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\ODM\MongoDB\Aggregation\Builder; use Psr\Log\LoggerInterface; @@ -37,9 +38,11 @@ */ final class OrderFilter extends AbstractFilter implements OrderFilterInterface { - use OrderFilterTrait; + use OrderFilterTrait { + getDescription as private getDescriptionLegacy; + } - public function __construct(ManagerRegistry $managerRegistry, string $orderParameterName = 'order', LoggerInterface $logger = null, array $properties = null) + public function __construct(ManagerRegistry $managerRegistry, PropertyMetadataFactoryInterface $propertyMetadataFactory, string $orderParameterName = 'order', LoggerInterface $logger = null, array $properties = null) { if (null !== $properties) { $properties = array_map(function ($propertyOptions) { @@ -54,11 +57,19 @@ public function __construct(ManagerRegistry $managerRegistry, string $orderParam }, $properties); } - parent::__construct($managerRegistry, $logger, $properties); + parent::__construct($managerRegistry, $propertyMetadataFactory, $logger, $properties); $this->orderParameterName = $orderParameterName; } + /** + * @inheritdoc} + */ + public function getDescription(string $resourceClass, array $context = []): array + { + return $this->getDescriptionLegacy($resourceClass, $context); + } + /** * {@inheritdoc} */ @@ -84,7 +95,7 @@ public function apply(Builder $aggregationBuilder, string $resourceClass, string */ protected function filterProperty(string $property, $direction, Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = []) { - if (!$this->isPropertyEnabled($property, $resourceClass) || !$this->isPropertyMapped($property, $resourceClass)) { + if (!$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass)) { return; } diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php b/src/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php index 132ca27f603..c58a2035e9c 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php +++ b/src/Bridge/Doctrine/MongoDbOdm/Filter/RangeFilter.php @@ -27,7 +27,17 @@ */ final class RangeFilter extends AbstractFilter implements RangeFilterInterface { - use RangeFilterTrait; + use RangeFilterTrait { + getDescription as private getDescriptionLegacy; + } + + /** + * @inheritdoc} + */ + public function getDescription(string $resourceClass, array $context = []): array + { + return $this->getDescriptionLegacy($resourceClass, $context); + } /** * {@inheritdoc} @@ -36,7 +46,7 @@ protected function filterProperty(string $property, $values, Builder $aggregatio { if ( !\is_array($values) || - !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass) ) { return; diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php b/src/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php index b8b658bbc75..25079908201 100644 --- a/src/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php +++ b/src/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php @@ -17,6 +17,7 @@ use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\SearchFilterInterface; use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\SearchFilterTrait; use ApiPlatform\Core\Exception\InvalidArgumentException; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\ODM\MongoDB\Aggregation\Builder; @@ -36,13 +37,23 @@ */ final class SearchFilter extends AbstractFilter implements SearchFilterInterface { - use SearchFilterTrait; + use SearchFilterTrait { + getDescription as private getDescriptionLegacy; + } public const DOCTRINE_INTEGER_TYPE = MongoDbType::INTEGER; - public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null) + /** + * @inheritdoc} + */ + public function getDescription(string $resourceClass, array $context = []): array + { + return $this->getDescriptionLegacy($resourceClass, $context); + } + + public function __construct(ManagerRegistry $managerRegistry, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null) { - parent::__construct($managerRegistry, $logger, $properties); + parent::__construct($managerRegistry, $propertyMetadataFactory, $logger, $properties); $this->iriConverter = $iriConverter; $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); @@ -65,7 +76,7 @@ protected function filterProperty(string $property, $value, Builder $aggregation { if ( null === $value || - !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass, true) ) { return; diff --git a/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php b/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php index 3e3aff8f591..78f267eb059 100644 --- a/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php +++ b/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php @@ -16,6 +16,8 @@ use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait; use ApiPlatform\Core\Bridge\Doctrine\Orm\PropertyHelperTrait as OrmPropertyHelperTrait; use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Core\Exception\InvalidArgumentException; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Util\RequestParser; use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\ORM\QueryBuilder; @@ -42,15 +44,37 @@ abstract class AbstractFilter implements FilterInterface protected $logger; protected $properties; - public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, LoggerInterface $logger = null, array $properties = null) + /** + * @param PropertyMetadataFactoryInterface|null $propertyMetadataFactory + * @param array|null $properties + */ + public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, LoggerInterface $logger = null, /*PropertyMetadataFactoryInterface*/ $propertyMetadataFactory = null, /*array*/ $properties = null) { if (null !== $requestStack) { @trigger_error(sprintf('Passing an instance of "%s" is deprecated since 2.2. Use "filters" context key instead.', RequestStack::class), E_USER_DEPRECATED); } + if (null === $properties && \is_array($propertyMetadataFactory)) { + @trigger_error(sprintf('Passing the "$properties" argument as fourth argument to the %s() method is deprecated since API Platform 2.4 and will not be possible anymore in API Platform 3. Use the fifth argument instead.', __METHOD__), E_USER_DEPRECATED); + + $properties = $propertyMetadataFactory; + $propertyMetadataFactory = null; + } + + if (null !== $properties && !\is_array($properties)) { + throw new InvalidArgumentException('The "$properties" argument is expected to be an array or null.'); + } + + if (null === $propertyMetadataFactory) { + @trigger_error(sprintf('Not injecting an implementation of "%s" as fourth argument to the %s() method is deprecated since API Platform 2.4 and will not be possible anymore in API Platform 3.', PropertyMetadataFactoryInterface::class, __METHOD__), E_USER_DEPRECATED); + } elseif (!$propertyMetadataFactory instanceof PropertyMetadataFactoryInterface) { + throw new InvalidArgumentException(sprintf('The "$propertyMetadataFactory" argument is expected to be an implementation of the "%s" interface.', PropertyMetadataFactoryInterface::class)); + } + $this->managerRegistry = $managerRegistry; $this->requestStack = $requestStack; $this->logger = $logger ?? new NullLogger(); + $this->propertyMetadataFactory = $propertyMetadataFactory; $this->properties = $properties; } @@ -90,31 +114,6 @@ protected function getLogger(): LoggerInterface return $this->logger; } - /** - * Determines whether the given property is enabled. - */ - protected function isPropertyEnabled(string $property/*, string $resourceClass*/): bool - { - if (\func_num_args() > 1) { - $resourceClass = func_get_arg(1); - } else { - if (__CLASS__ !== \get_class($this)) { - $r = new \ReflectionMethod($this, __FUNCTION__); - if (__CLASS__ !== $r->getDeclaringClass()->getName()) { - @trigger_error(sprintf('Method %s() will have a second `$resourceClass` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.1.', __FUNCTION__), E_USER_DEPRECATED); - } - } - $resourceClass = null; - } - - if (null === $this->properties) { - // to ensure sanity, nested properties must still be explicitly enabled - return !$this->isPropertyNested($property, $resourceClass); - } - - return \array_key_exists($property, $this->properties); - } - /** * Extracts properties to filter from the request. */ diff --git a/src/Bridge/Doctrine/Orm/Filter/BooleanFilter.php b/src/Bridge/Doctrine/Orm/Filter/BooleanFilter.php index 3203f18b976..d15c0a6f562 100644 --- a/src/Bridge/Doctrine/Orm/Filter/BooleanFilter.php +++ b/src/Bridge/Doctrine/Orm/Filter/BooleanFilter.php @@ -41,10 +41,16 @@ class BooleanFilter extends AbstractContextAwareFilter /** * {@inheritdoc} */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null/*, array $context = []*/) { + if (\func_num_args() < 7 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a sixth "$context" argument in API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 6 < \func_num_args() ? (array) func_get_arg(6) : []; + if ( - !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass) || !$this->isBooleanField($property, $resourceClass) ) { diff --git a/src/Bridge/Doctrine/Orm/Filter/DateFilter.php b/src/Bridge/Doctrine/Orm/Filter/DateFilter.php index 0b619be243e..8afec648480 100644 --- a/src/Bridge/Doctrine/Orm/Filter/DateFilter.php +++ b/src/Bridge/Doctrine/Orm/Filter/DateFilter.php @@ -44,12 +44,18 @@ class DateFilter extends AbstractContextAwareFilter implements DateFilterInterfa /** * {@inheritdoc} */ - protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) + protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null/*, array $context = []*/) { + if (\func_num_args() < 7 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a sixth "$context" argument in API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 6 < \func_num_args() ? (array) func_get_arg(6) : []; + // Expect $values to be an array having the period as keys and the date value as values if ( !\is_array($values) || - !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass) || !$this->isDateField($property, $resourceClass) ) { diff --git a/src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php b/src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php index ab0bd4662b0..35543e1bcdc 100644 --- a/src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php +++ b/src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php @@ -40,11 +40,17 @@ class ExistsFilter extends AbstractContextAwareFilter implements ExistsFilterInt /** * {@inheritdoc} */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null/*, array $context = []*/) { + if (\func_num_args() < 7 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a sixth "$context" argument in API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 6 < \func_num_args() ? (array) func_get_arg(6) : []; + if ( !isset($value[self::QUERY_PARAMETER_KEY]) || - !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass, true) || !$this->isNullableField($property, $resourceClass) ) { diff --git a/src/Bridge/Doctrine/Orm/Filter/NumericFilter.php b/src/Bridge/Doctrine/Orm/Filter/NumericFilter.php index eb5f0a692e7..662356747d7 100644 --- a/src/Bridge/Doctrine/Orm/Filter/NumericFilter.php +++ b/src/Bridge/Doctrine/Orm/Filter/NumericFilter.php @@ -49,10 +49,16 @@ class NumericFilter extends AbstractContextAwareFilter /** * {@inheritdoc} */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null/*, array $context = []*/) { + if (\func_num_args() < 7 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a sixth "$context" argument in API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 6 < \func_num_args() ? (array) func_get_arg(6) : []; + if ( - !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass) || !$this->isNumericField($property, $resourceClass) ) { diff --git a/src/Bridge/Doctrine/Orm/Filter/OrderFilter.php b/src/Bridge/Doctrine/Orm/Filter/OrderFilter.php index d47895f1bb2..9863a7c03d8 100644 --- a/src/Bridge/Doctrine/Orm/Filter/OrderFilter.php +++ b/src/Bridge/Doctrine/Orm/Filter/OrderFilter.php @@ -40,10 +40,17 @@ class OrderFilter extends AbstractContextAwareFilter implements OrderFilterInter { use OrderFilterTrait; - public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, string $orderParameterName = 'order', LoggerInterface $logger = null, array $properties = null) + /** + * {@inheritdoc} + */ + public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, string $orderParameterName = 'order', LoggerInterface $logger = null, /*PropertyMetadataFactoryInterface*/ $propertyMetadataFactory = null, /*array*/ $properties = null) { - if (null !== $properties) { - $properties = array_map(function ($propertyOptions) { + parent::__construct($managerRegistry, $requestStack, $logger, $propertyMetadataFactory, $properties); + + $this->orderParameterName = $orderParameterName; + + if (null !== $this->properties) { + $this->properties = array_map(function ($propertyOptions) { // shorthand for default direction if (\is_string($propertyOptions)) { $propertyOptions = [ @@ -52,12 +59,8 @@ public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $req } return $propertyOptions; - }, $properties); + }, $this->properties); } - - parent::__construct($managerRegistry, $requestStack, $logger, $properties); - - $this->orderParameterName = $orderParameterName; } /** @@ -83,9 +86,15 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q /** * {@inheritdoc} */ - protected function filterProperty(string $property, $direction, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) + protected function filterProperty(string $property, $direction, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null/*, array $context = []*/) { - if (!$this->isPropertyEnabled($property, $resourceClass) || !$this->isPropertyMapped($property, $resourceClass)) { + if (\func_num_args() < 7 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a sixth "$context" argument in API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 6 < \func_num_args() ? (array) func_get_arg(6) : []; + + if (!$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass)) { return; } diff --git a/src/Bridge/Doctrine/Orm/Filter/RangeFilter.php b/src/Bridge/Doctrine/Orm/Filter/RangeFilter.php index 44e4614a03c..39bda44bee8 100644 --- a/src/Bridge/Doctrine/Orm/Filter/RangeFilter.php +++ b/src/Bridge/Doctrine/Orm/Filter/RangeFilter.php @@ -30,11 +30,17 @@ class RangeFilter extends AbstractContextAwareFilter implements RangeFilterInter /** * {@inheritdoc} */ - protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) + protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null/*, array $context = []*/) { + if (\func_num_args() < 7 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a sixth "$context" argument in API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 6 < \func_num_args() ? (array) func_get_arg(6) : []; + if ( !\is_array($values) || - !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass) ) { return; diff --git a/src/Bridge/Doctrine/Orm/Filter/SearchFilter.php b/src/Bridge/Doctrine/Orm/Filter/SearchFilter.php index c9fc4363efe..e8368aed6a1 100644 --- a/src/Bridge/Doctrine/Orm/Filter/SearchFilter.php +++ b/src/Bridge/Doctrine/Orm/Filter/SearchFilter.php @@ -38,9 +38,12 @@ class SearchFilter extends AbstractContextAwareFilter implements SearchFilterInt public const DOCTRINE_INTEGER_TYPE = DBALType::INTEGER; - public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack, IriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null) + /** + * {@inheritdoc} + */ + public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack, IriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, /*PropertyMetadataFactoryInterface*/ $propertyMetadataFactory = null, /*array*/ $properties = null) { - parent::__construct($managerRegistry, $requestStack, $logger, $properties); + parent::__construct($managerRegistry, $requestStack, $logger, $propertyMetadataFactory, $properties); $this->iriConverter = $iriConverter; $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); @@ -59,11 +62,17 @@ protected function getPropertyAccessor(): PropertyAccessorInterface /** * {@inheritdoc} */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null/*, array $context = []*/) { + if (\func_num_args() < 7 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) { + @trigger_error(sprintf('Method %s() will have a sixth "$context" argument in API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED); + } + + $context = 6 < \func_num_args() ? (array) func_get_arg(6) : []; + if ( null === $value || - !$this->isPropertyEnabled($property, $resourceClass) || + !$this->isPropertyEnabled($property, $resourceClass, $context) || !$this->isPropertyMapped($property, $resourceClass, true) ) { return; diff --git a/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractFilter.php b/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractFilter.php index bdd041936ff..6be24d0ec04 100644 --- a/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractFilter.php +++ b/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractFilter.php @@ -19,6 +19,7 @@ use ApiPlatform\Core\Exception\ResourceClassNotFoundException; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Core\Metadata\Property\PropertyMetadataFactoryOptionsTrait; use Symfony\Component\PropertyInfo\Type; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; @@ -32,6 +33,7 @@ abstract class AbstractFilter implements FilterInterface { use FieldDatatypeTrait { getNestedFieldPath as protected; } + use PropertyMetadataFactoryOptionsTrait; protected $properties; protected $propertyNameCollectionFactory; @@ -49,24 +51,37 @@ public function __construct(PropertyNameCollectionFactoryInterface $propertyName /** * Gets all enabled properties for the given resource class. */ - protected function getProperties(string $resourceClass): \Traversable + protected function getProperties(string $resourceClass, array $context): \Traversable { if (null !== $this->properties) { return yield from array_keys($this->properties); } try { - yield from $this->propertyNameCollectionFactory->create($resourceClass); + $propertyNameCollection = $this->propertyNameCollectionFactory->create($resourceClass); } catch (ResourceClassNotFoundException $e) { + return; + } + + foreach ($propertyNameCollection as $property) { + try { + $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property, $this->getPropertyMetadataFactoryOptions($context)); + } catch (PropertyNotFoundException $e) { + continue; + } + + if ($propertyMetadata->isReadable()) { + yield $property; + } } } /** * Is the given property enabled? */ - protected function hasProperty(string $resourceClass, string $property): bool + protected function hasProperty(string $resourceClass, string $property, array $context): bool { - return \in_array($property, iterator_to_array($this->getProperties($resourceClass)), true); + return \in_array($property, iterator_to_array($this->getProperties($resourceClass, $context)), true); } /** @@ -78,11 +93,11 @@ protected function hasProperty(string $resourceClass, string $property): bool * - the resource class of the decomposed given property * - the property name of the decomposed given property */ - protected function getMetadata(string $resourceClass, string $property): array + protected function getMetadata(string $resourceClass, string $property, array $context): array { $noop = [null, null, null, null]; - if (!$this->hasProperty($resourceClass, $property)) { + if (!$this->hasProperty($resourceClass, $property, $context)) { return $noop; } diff --git a/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php b/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php index e12ee385d76..317bccf63d6 100644 --- a/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php +++ b/src/Bridge/Elasticsearch/DataProvider/Filter/AbstractSearchFilter.php @@ -58,7 +58,7 @@ public function apply(array $clauseBody, string $resourceClass, ?string $operati $searches = []; foreach ($context['filters'] ?? [] as $property => $values) { - [$type, $hasAssociation, $nestedResourceClass, $nestedProperty] = $this->getMetadata($resourceClass, $property); + [$type, $hasAssociation, $nestedResourceClass, $nestedProperty] = $this->getMetadata($resourceClass, $property, $context); if (!$type || !$values = (array) $values) { continue; @@ -93,12 +93,12 @@ public function apply(array $clauseBody, string $resourceClass, ?string $operati /** * {@inheritdoc} */ - public function getDescription(string $resourceClass): array + public function getDescription(string $resourceClass, array $context = []): array { $description = []; - foreach ($this->getProperties($resourceClass) as $property) { - [$type, $hasAssociation] = $this->getMetadata($resourceClass, $property); + foreach ($this->getProperties($resourceClass, $context) as $property) { + [$type, $hasAssociation] = $this->getMetadata($resourceClass, $property, []); if (!$type) { continue; diff --git a/src/Bridge/Elasticsearch/DataProvider/Filter/FilterInterface.php b/src/Bridge/Elasticsearch/DataProvider/Filter/FilterInterface.php index 67b2d40ccf9..61275d545e1 100644 --- a/src/Bridge/Elasticsearch/DataProvider/Filter/FilterInterface.php +++ b/src/Bridge/Elasticsearch/DataProvider/Filter/FilterInterface.php @@ -13,7 +13,7 @@ namespace ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Filter; -use ApiPlatform\Core\Api\FilterInterface as BaseFilterInterface; +use ApiPlatform\Core\Api\ContextAwareFilterInterface as BaseFilterInterface; /** * Elasticsearch filter interface. @@ -24,5 +24,8 @@ */ interface FilterInterface extends BaseFilterInterface { + /** + * Applies the filter. + */ public function apply(array $clauseBody, string $resourceClass, ?string $operationName = null, array $context = []): array; } diff --git a/src/Bridge/Elasticsearch/DataProvider/Filter/OrderFilter.php b/src/Bridge/Elasticsearch/DataProvider/Filter/OrderFilter.php index a1c78f3f06d..d862646769e 100644 --- a/src/Bridge/Elasticsearch/DataProvider/Filter/OrderFilter.php +++ b/src/Bridge/Elasticsearch/DataProvider/Filter/OrderFilter.php @@ -53,7 +53,7 @@ public function apply(array $clauseBody, string $resourceClass, ?string $operati $orders = []; foreach ($properties as $property => $direction) { - [$type] = $this->getMetadata($resourceClass, $property); + [$type] = $this->getMetadata($resourceClass, $property, $context); if (!$type) { continue; @@ -88,12 +88,12 @@ public function apply(array $clauseBody, string $resourceClass, ?string $operati /** * {@inheritdoc} */ - public function getDescription(string $resourceClass): array + public function getDescription(string $resourceClass, array $context = []): array { $description = []; - foreach ($this->getProperties($resourceClass) as $property) { - [$type] = $this->getMetadata($resourceClass, $property); + foreach ($this->getProperties($resourceClass, $context) as $property) { + [$type] = $this->getMetadata($resourceClass, $property, []); if (!$type) { continue; diff --git a/src/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php b/src/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php index 10dacc78afd..77021d2d3fc 100644 --- a/src/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php +++ b/src/Bridge/NelmioApiDoc/Extractor/AnnotationsProvider/ApiPlatformProvider.php @@ -136,7 +136,7 @@ private function getApiDoc(bool $collection, string $resourceClass, ResourceMeta $data['filters'] = []; foreach ($resourceFilters as $filterId) { if ($filter = $this->getFilter($filterId)) { - foreach ($filter->getDescription($resourceClass) as $name => $definition) { + foreach ($filter->getDescription($resourceClass, ['collection_operation_name' => $operationName]) as $name => $definition) { $data['filters'][] = ['name' => $name] + $definition; } } diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index cae08be556c..ee6362acb2e 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -22,6 +22,7 @@ use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface as DoctrineQueryCollectionExtensionInterface; use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface; use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter as DoctrineOrmAbstractContextAwareFilter; +use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter as DoctrineOrmAbstractFilter; use ApiPlatform\Core\Bridge\Elasticsearch\DataProvider\Extension\RequestBodySearchCollectionExtensionInterface; use ApiPlatform\Core\DataPersister\DataPersisterInterface; use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; @@ -480,6 +481,8 @@ private function registerDoctrineConfiguration(ContainerBuilder $container, arra ->addTag('api_platform.doctrine.orm.query_extension.collection'); $container->registerForAutoconfiguration(DoctrineOrmAbstractContextAwareFilter::class) ->setBindings(['$requestStack' => null]); + $container->registerForAutoconfiguration(DoctrineOrmAbstractFilter::class) + ->setBindings(['$propertyMetadataFactory' => new Reference('api_platform.metadata.property.metadata_factory')]); $loader->load('doctrine_orm.xml'); diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml index b8a057d657d..72ec0a8c0f3 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml @@ -63,6 +63,7 @@ + @@ -71,30 +72,35 @@ + + + + + %api_platform.collection.order_parameter_name% @@ -102,6 +108,7 @@ + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml index 0f3a6c62f72..0d07957513d 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml @@ -55,6 +55,7 @@ + @@ -63,6 +64,7 @@ null %api_platform.collection.order_parameter_name% + @@ -70,6 +72,7 @@ null + @@ -77,6 +80,7 @@ null + @@ -84,6 +88,7 @@ null + @@ -91,6 +96,7 @@ null + @@ -98,6 +104,7 @@ null + diff --git a/src/Filter/QueryParameterValidateListener.php b/src/Filter/QueryParameterValidateListener.php index e98c955cb03..759bc93ae14 100644 --- a/src/Filter/QueryParameterValidateListener.php +++ b/src/Filter/QueryParameterValidateListener.php @@ -59,7 +59,7 @@ public function onKernelRequest(GetResponseEvent $event): void continue; } - foreach ($filter->getDescription($attributes['resource_class']) as $name => $data) { + foreach ($filter->getDescription($attributes['resource_class'], ['collection_operation_name' => $operationName]) as $name => $data) { if (!($data['required'] ?? false)) { // property is not required continue; } diff --git a/src/Hydra/Serializer/CollectionFiltersNormalizer.php b/src/Hydra/Serializer/CollectionFiltersNormalizer.php index ff5d750484d..887d722fce1 100644 --- a/src/Hydra/Serializer/CollectionFiltersNormalizer.php +++ b/src/Hydra/Serializer/CollectionFiltersNormalizer.php @@ -110,7 +110,7 @@ public function normalize($object, $format = null, array $context = []) } if ($currentFilters) { - $data['hydra:search'] = $this->getSearch($resourceClass, $requestParts, $currentFilters); + $data['hydra:search'] = $this->getSearch($resourceClass, $requestParts, $currentFilters, $context); } return $data; @@ -131,12 +131,12 @@ public function setNormalizer(NormalizerInterface $normalizer) * * @param FilterInterface[] $filters */ - private function getSearch(string $resourceClass, array $parts, array $filters): array + private function getSearch(string $resourceClass, array $parts, array $filters, array $context): array { $variables = []; $mapping = []; foreach ($filters as $filter) { - foreach ($filter->getDescription($resourceClass) as $variable => $data) { + foreach ($filter->getDescription($resourceClass, $context) as $variable => $data) { $variables[] = $variable; $mapping[] = [ '@type' => 'IriTemplateMapping', diff --git a/src/Metadata/Property/PropertyMetadataFactoryOptionsTrait.php b/src/Metadata/Property/PropertyMetadataFactoryOptionsTrait.php new file mode 100644 index 00000000000..46619a98ccb --- /dev/null +++ b/src/Metadata/Property/PropertyMetadataFactoryOptionsTrait.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Core\Metadata\Property; + +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; + +trait PropertyMetadataFactoryOptionsTrait +{ + /** + * Gets a valid context for property metadata factories. + * + * @see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php + */ + private function getPropertyMetadataFactoryOptions(array $context): array + { + $options = []; + + if (isset($context[AbstractNormalizer::GROUPS])) { + $options['serializer_groups'] = $context[AbstractNormalizer::GROUPS]; + } + + if (isset($context['collection_operation_name'])) { + $options['collection_operation_name'] = $context['collection_operation_name']; + } elseif (isset($context['item_operation_name'])) { + $options['item_operation_name'] = $context['item_operation_name']; + } + + return $options; + } +} diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index a23b95d70c5..c37bfdf5b32 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -23,6 +23,7 @@ use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Metadata\Property\PropertyMetadata; +use ApiPlatform\Core\Metadata\Property\PropertyMetadataFactoryOptionsTrait; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Util\ClassInfoTrait; use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; @@ -52,6 +53,9 @@ abstract class AbstractItemNormalizer extends AbstractObjectNormalizer implement use ClassInfoTrait; use ContextTrait; use InputOutputMetadataTrait; + use PropertyMetadataFactoryOptionsTrait { + getPropertyMetadataFactoryOptions as protected getFactoryOptions; + } protected $propertyNameCollectionFactory; protected $propertyMetadataFactory; @@ -480,30 +484,6 @@ private function setValue($object, string $attributeName, $value) } } - /** - * Gets a valid context for property metadata factories. - * - * @see https://github.com/symfony/symfony/blob/master/src/Symfony/Component/PropertyInfo/Extractor/SerializerExtractor.php - */ - protected function getFactoryOptions(array $context): array - { - $options = []; - - if (isset($context[self::GROUPS])) { - $options['serializer_groups'] = $context[self::GROUPS]; - } - - if (isset($context['collection_operation_name'])) { - $options['collection_operation_name'] = $context['collection_operation_name']; - } - - if (isset($context['item_operation_name'])) { - $options['item_operation_name'] = $context['item_operation_name']; - } - - return $options; - } - /** * Creates the context to use when serializing a relation. * diff --git a/src/Swagger/Serializer/DocumentationNormalizer.php b/src/Swagger/Serializer/DocumentationNormalizer.php index 16a4c6e6dc8..e482ff0741b 100644 --- a/src/Swagger/Serializer/DocumentationNormalizer.php +++ b/src/Swagger/Serializer/DocumentationNormalizer.php @@ -795,7 +795,7 @@ private function getFiltersParameters(bool $v3, string $resourceClass, string $o continue; } - foreach ($filter->getDescription($resourceClass) as $name => $data) { + foreach ($filter->getDescription($resourceClass, ['collection_operation_name' => $operationName]) as $name => $data) { $parameter = [ 'name' => $name, 'in' => 'query',