From 4df2c127ed4aaaf2f5037beac4f1d54c3c89d8ac Mon Sep 17 00:00:00 2001 From: Vincent <407859+vincentchalamon@users.noreply.github.com> Date: Fri, 2 Feb 2024 22:45:18 +0100 Subject: [PATCH 01/72] chore: fix CI (#6143) * chore: fix wrong namespace in test document * chore: fix CS nullable_type_declaration_for_default_null_value * chore: update GitHub Actions versions --- Extension/EagerLoadingExtension.php | 6 +++--- Extension/FilterEagerLoadingExtension.php | 2 +- Extension/FilterExtension.php | 2 +- Extension/OrderExtension.php | 2 +- Extension/PaginationExtension.php | 10 +++++----- Extension/QueryCollectionExtensionInterface.php | 2 +- Extension/QueryItemExtensionInterface.php | 2 +- Extension/QueryResultCollectionExtensionInterface.php | 4 ++-- Extension/QueryResultItemExtensionInterface.php | 4 ++-- Filter/AbstractFilter.php | 6 +++--- Filter/BooleanFilter.php | 2 +- Filter/DateFilter.php | 4 ++-- Filter/ExistsFilter.php | 6 +++--- Filter/FilterInterface.php | 2 +- Filter/NumericFilter.php | 4 ++-- Filter/OrderFilter.php | 6 +++--- Filter/RangeFilter.php | 2 +- Filter/SearchFilter.php | 4 ++-- State/CollectionProvider.php | 2 +- State/ItemProvider.php | 2 +- Util/QueryBuilderHelper.php | 4 ++-- 21 files changed, 39 insertions(+), 39 deletions(-) diff --git a/Extension/EagerLoadingExtension.php b/Extension/EagerLoadingExtension.php index dc6de30..49f63b4 100644 --- a/Extension/EagerLoadingExtension.php +++ b/Extension/EagerLoadingExtension.php @@ -48,7 +48,7 @@ public function __construct(private readonly PropertyNameCollectionFactoryInterf /** * {@inheritdoc} */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, Operation $operation = null, array $context = []): void + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): void { $this->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); } @@ -56,7 +56,7 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator /** * The context may contain serialization groups which helps defining joined entities that are readable. */ - public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, Operation $operation = null, array $context = []): void + public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, ?Operation $operation = null, array $context = []): void { $this->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); } @@ -107,7 +107,7 @@ private function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $ * * @throws RuntimeException when the max number of joins has been reached */ - private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, bool $forceEager, bool $fetchPartial, string $parentAlias, array $options = [], array $normalizationContext = [], bool $wasLeftJoin = false, int &$joinCount = 0, int $currentDepth = null, string $parentAssociation = null): void + private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, bool $forceEager, bool $fetchPartial, string $parentAlias, array $options = [], array $normalizationContext = [], bool $wasLeftJoin = false, int &$joinCount = 0, ?int $currentDepth = null, ?string $parentAssociation = null): void { if ($joinCount > $this->maxJoins) { throw new RuntimeException('The total number of joined relations has exceeded the specified maximum. Raise the limit if necessary with the "api_platform.eager_loading.max_joins" configuration key (https://api-platform.com/docs/core/performance/#eager-loading), or limit the maximum serialization depth using the "enable_max_depth" option of the Symfony serializer (https://symfony.com/doc/current/components/serializer.html#handling-serialization-depth).'); diff --git a/Extension/FilterEagerLoadingExtension.php b/Extension/FilterEagerLoadingExtension.php index 1afd8b2..31170db 100644 --- a/Extension/FilterEagerLoadingExtension.php +++ b/Extension/FilterEagerLoadingExtension.php @@ -36,7 +36,7 @@ public function __construct(private readonly bool $forceEager = true, private re /** * {@inheritdoc} */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, Operation $operation = null, array $context = []): void + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): void { if (null === $resourceClass) { throw new InvalidArgumentException('The "$resourceClass" parameter must not be null'); diff --git a/Extension/FilterExtension.php b/Extension/FilterExtension.php index 551a405..298eff2 100644 --- a/Extension/FilterExtension.php +++ b/Extension/FilterExtension.php @@ -36,7 +36,7 @@ public function __construct(private readonly ContainerInterface $filterLocator) /** * {@inheritdoc} */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, Operation $operation = null, array $context = []): void + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): void { if (null === $resourceClass) { throw new InvalidArgumentException('The "$resourceClass" parameter must not be null'); diff --git a/Extension/OrderExtension.php b/Extension/OrderExtension.php index 65c7034..a383383 100644 --- a/Extension/OrderExtension.php +++ b/Extension/OrderExtension.php @@ -35,7 +35,7 @@ public function __construct(private readonly ?string $order = null) /** * {@inheritdoc} */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, Operation $operation = null, array $context = []): void + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): void { if (null === $resourceClass) { throw new InvalidArgumentException('The "$resourceClass" parameter must not be null'); diff --git a/Extension/PaginationExtension.php b/Extension/PaginationExtension.php index b23285f..80513e4 100644 --- a/Extension/PaginationExtension.php +++ b/Extension/PaginationExtension.php @@ -43,7 +43,7 @@ public function __construct(private readonly ManagerRegistry $managerRegistry, p /** * {@inheritdoc} */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if (null === $pagination = $this->getPagination($queryBuilder, $operation, $context)) { return; @@ -59,7 +59,7 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator /** * {@inheritdoc} */ - public function supportsResult(string $resourceClass, Operation $operation = null, array $context = []): bool + public function supportsResult(string $resourceClass, ?Operation $operation = null, array $context = []): bool { if ($context['graphql_operation_name'] ?? false) { return $this->pagination->isGraphQlEnabled($operation, $context); @@ -71,7 +71,7 @@ public function supportsResult(string $resourceClass, Operation $operation = nul /** * {@inheritdoc} */ - public function getResult(QueryBuilder $queryBuilder, string $resourceClass = null, Operation $operation = null, array $context = []): iterable + public function getResult(QueryBuilder $queryBuilder, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): iterable { $query = $queryBuilder->getQuery(); @@ -125,7 +125,7 @@ private function addCountToContext(QueryBuilder $queryBuilder, array $context): /** * Determines the value of the $fetchJoinCollection argument passed to the Doctrine ORM Paginator. */ - private function shouldDoctrinePaginatorFetchJoinCollection(QueryBuilder $queryBuilder, Operation $operation = null, array $context = []): bool + private function shouldDoctrinePaginatorFetchJoinCollection(QueryBuilder $queryBuilder, ?Operation $operation = null, array $context = []): bool { $fetchJoinCollection = $operation?->getPaginationFetchJoinCollection(); @@ -158,7 +158,7 @@ private function shouldDoctrinePaginatorFetchJoinCollection(QueryBuilder $queryB /** * Determines whether the Doctrine ORM Paginator should use output walkers. */ - private function shouldDoctrinePaginatorUseOutputWalkers(QueryBuilder $queryBuilder, Operation $operation = null, array $context = []): bool + private function shouldDoctrinePaginatorUseOutputWalkers(QueryBuilder $queryBuilder, ?Operation $operation = null, array $context = []): bool { $useOutputWalkers = $operation?->getPaginationUseOutputWalkers(); diff --git a/Extension/QueryCollectionExtensionInterface.php b/Extension/QueryCollectionExtensionInterface.php index cb38613..191c2ac 100644 --- a/Extension/QueryCollectionExtensionInterface.php +++ b/Extension/QueryCollectionExtensionInterface.php @@ -25,5 +25,5 @@ */ interface QueryCollectionExtensionInterface { - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void; + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void; } diff --git a/Extension/QueryItemExtensionInterface.php b/Extension/QueryItemExtensionInterface.php index f337ebc..4d7aaed 100644 --- a/Extension/QueryItemExtensionInterface.php +++ b/Extension/QueryItemExtensionInterface.php @@ -25,5 +25,5 @@ */ interface QueryItemExtensionInterface { - public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, Operation $operation = null, array $context = []): void; + public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, ?Operation $operation = null, array $context = []): void; } diff --git a/Extension/QueryResultCollectionExtensionInterface.php b/Extension/QueryResultCollectionExtensionInterface.php index 7f74cac..fe996d2 100644 --- a/Extension/QueryResultCollectionExtensionInterface.php +++ b/Extension/QueryResultCollectionExtensionInterface.php @@ -27,7 +27,7 @@ */ interface QueryResultCollectionExtensionInterface extends QueryCollectionExtensionInterface { - public function supportsResult(string $resourceClass, Operation $operation = null, array $context = []): bool; + public function supportsResult(string $resourceClass, ?Operation $operation = null, array $context = []): bool; - public function getResult(QueryBuilder $queryBuilder, string $resourceClass = null, Operation $operation = null, array $context = []): iterable; + public function getResult(QueryBuilder $queryBuilder, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): iterable; } diff --git a/Extension/QueryResultItemExtensionInterface.php b/Extension/QueryResultItemExtensionInterface.php index 7cc513e..4c23d77 100644 --- a/Extension/QueryResultItemExtensionInterface.php +++ b/Extension/QueryResultItemExtensionInterface.php @@ -26,7 +26,7 @@ */ interface QueryResultItemExtensionInterface extends QueryItemExtensionInterface { - public function supportsResult(string $resourceClass, Operation $operation = null, array $context = []): bool; + public function supportsResult(string $resourceClass, ?Operation $operation = null, array $context = []): bool; - public function getResult(QueryBuilder $queryBuilder, string $resourceClass = null, Operation $operation = null, array $context = []): ?object; + public function getResult(QueryBuilder $queryBuilder, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): ?object; } diff --git a/Filter/AbstractFilter.php b/Filter/AbstractFilter.php index d2b2b5d..c1deb1c 100644 --- a/Filter/AbstractFilter.php +++ b/Filter/AbstractFilter.php @@ -29,7 +29,7 @@ abstract class AbstractFilter implements FilterInterface use PropertyHelperTrait; protected LoggerInterface $logger; - public function __construct(protected ManagerRegistry $managerRegistry, LoggerInterface $logger = null, protected ?array $properties = null, protected ?NameConverterInterface $nameConverter = null) + public function __construct(protected ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, protected ?array $properties = null, protected ?NameConverterInterface $nameConverter = null) { $this->logger = $logger ?? new NullLogger(); } @@ -37,7 +37,7 @@ public function __construct(protected ManagerRegistry $managerRegistry, LoggerIn /** * {@inheritdoc} */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { foreach ($context['filters'] as $property => $value) { $this->filterProperty($this->denormalizePropertyName($property), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); @@ -47,7 +47,7 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q /** * Passes a property through the filter. */ - abstract protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void; + abstract protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void; protected function getManagerRegistry(): ManagerRegistry { diff --git a/Filter/BooleanFilter.php b/Filter/BooleanFilter.php index cb040c4..2f230c4 100644 --- a/Filter/BooleanFilter.php +++ b/Filter/BooleanFilter.php @@ -117,7 +117,7 @@ final class BooleanFilter extends AbstractFilter /** * {@inheritdoc} */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if ( !$this->isPropertyEnabled($property, $resourceClass) diff --git a/Filter/DateFilter.php b/Filter/DateFilter.php index d895cc6..6cbd8d2 100644 --- a/Filter/DateFilter.php +++ b/Filter/DateFilter.php @@ -138,7 +138,7 @@ final class DateFilter extends AbstractFilter implements DateFilterInterface /** * {@inheritdoc} */ - protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { // Expect $values to be an array having the period as keys and the date value as values if ( @@ -220,7 +220,7 @@ protected function filterProperty(string $property, $values, QueryBuilder $query /** * Adds the where clause according to the chosen null management. */ - protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, mixed $value, string $nullManagement = null, DBALType|string $type = null): void + protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, mixed $value, ?string $nullManagement = null, DBALType|string|null $type = null): void { $type = (string) $type; $value = $this->normalizeValue($value, $operator); diff --git a/Filter/ExistsFilter.php b/Filter/ExistsFilter.php index f7f2cba..db0db4b 100644 --- a/Filter/ExistsFilter.php +++ b/Filter/ExistsFilter.php @@ -114,7 +114,7 @@ final class ExistsFilter extends AbstractFilter implements ExistsFilterInterface { use ExistsFilterTrait; - public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $logger = null, array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, NameConverterInterface $nameConverter = null) + public function __construct(ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, ?array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, ?NameConverterInterface $nameConverter = null) { parent::__construct($managerRegistry, $logger, $properties, $nameConverter); @@ -124,7 +124,7 @@ public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $l /** * {@inheritdoc} */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { foreach ($context['filters'][$this->existsParameterName] ?? [] as $property => $value) { $this->filterProperty($this->denormalizePropertyName($property), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); @@ -134,7 +134,7 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q /** * {@inheritdoc} */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if ( !$this->isPropertyEnabled($property, $resourceClass) diff --git a/Filter/FilterInterface.php b/Filter/FilterInterface.php index 50e02b1..4cfa337 100644 --- a/Filter/FilterInterface.php +++ b/Filter/FilterInterface.php @@ -28,5 +28,5 @@ interface FilterInterface extends BaseFilterInterface /** * Applies the filter. */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void; + public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void; } diff --git a/Filter/NumericFilter.php b/Filter/NumericFilter.php index bbcd1d6..c27e3f6 100644 --- a/Filter/NumericFilter.php +++ b/Filter/NumericFilter.php @@ -126,7 +126,7 @@ final class NumericFilter extends AbstractFilter /** * {@inheritdoc} */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if ( !$this->isPropertyEnabled($property, $resourceClass) @@ -164,7 +164,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB /** * {@inheritdoc} */ - protected function getType(string $doctrineType = null): string + protected function getType(?string $doctrineType = null): string { if (null === $doctrineType || Types::DECIMAL === $doctrineType) { return 'string'; diff --git a/Filter/OrderFilter.php b/Filter/OrderFilter.php index abece57..bf40b54 100644 --- a/Filter/OrderFilter.php +++ b/Filter/OrderFilter.php @@ -197,7 +197,7 @@ final class OrderFilter extends AbstractFilter implements OrderFilterInterface { use OrderFilterTrait; - public function __construct(ManagerRegistry $managerRegistry, string $orderParameterName = 'order', LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null, private readonly ?string $orderNullsComparison = null) + public function __construct(ManagerRegistry $managerRegistry, string $orderParameterName = 'order', ?LoggerInterface $logger = null, ?array $properties = null, ?NameConverterInterface $nameConverter = null, private readonly ?string $orderNullsComparison = null) { if (null !== $properties) { $properties = array_map(static function ($propertyOptions) { @@ -220,7 +220,7 @@ public function __construct(ManagerRegistry $managerRegistry, string $orderParam /** * {@inheritdoc} */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if (isset($context['filters']) && !isset($context['filters'][$this->orderParameterName])) { return; @@ -240,7 +240,7 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q /** * {@inheritdoc} */ - protected function filterProperty(string $property, $direction, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $direction, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if (!$this->isPropertyEnabled($property, $resourceClass) || !$this->isPropertyMapped($property, $resourceClass)) { return; diff --git a/Filter/RangeFilter.php b/Filter/RangeFilter.php index c2e0aaf..70c3e68 100644 --- a/Filter/RangeFilter.php +++ b/Filter/RangeFilter.php @@ -112,7 +112,7 @@ final class RangeFilter extends AbstractFilter implements RangeFilterInterface /** * {@inheritdoc} */ - protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if ( !\is_array($values) diff --git a/Filter/SearchFilter.php b/Filter/SearchFilter.php index 0ea0858..7fecabb 100644 --- a/Filter/SearchFilter.php +++ b/Filter/SearchFilter.php @@ -141,7 +141,7 @@ final class SearchFilter extends AbstractFilter implements SearchFilterInterface public const DOCTRINE_INTEGER_TYPE = Types::INTEGER; - public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface|LegacyIriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, IdentifiersExtractorInterface|LegacyIdentifiersExtractorInterface $identifiersExtractor = null, NameConverterInterface $nameConverter = null) + public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface|LegacyIriConverterInterface $iriConverter, ?PropertyAccessorInterface $propertyAccessor = null, ?LoggerInterface $logger = null, ?array $properties = null, IdentifiersExtractorInterface|LegacyIdentifiersExtractorInterface|null $identifiersExtractor = null, ?NameConverterInterface $nameConverter = null) { parent::__construct($managerRegistry, $logger, $properties, $nameConverter); @@ -163,7 +163,7 @@ protected function getPropertyAccessor(): PropertyAccessorInterface /** * {@inheritdoc} */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if ( null === $value diff --git a/State/CollectionProvider.php b/State/CollectionProvider.php index 7534f7e..6078b80 100644 --- a/State/CollectionProvider.php +++ b/State/CollectionProvider.php @@ -37,7 +37,7 @@ final class CollectionProvider implements ProviderInterface /** * @param QueryCollectionExtensionInterface[] $collectionExtensions */ - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ManagerRegistry $managerRegistry, private readonly iterable $collectionExtensions = [], ContainerInterface $handleLinksLocator = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ManagerRegistry $managerRegistry, private readonly iterable $collectionExtensions = [], ?ContainerInterface $handleLinksLocator = null) { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; diff --git a/State/ItemProvider.php b/State/ItemProvider.php index eccc400..7ad57d8 100644 --- a/State/ItemProvider.php +++ b/State/ItemProvider.php @@ -37,7 +37,7 @@ final class ItemProvider implements ProviderInterface /** * @param QueryItemExtensionInterface[] $itemExtensions */ - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ManagerRegistry $managerRegistry, private readonly iterable $itemExtensions = [], ContainerInterface $handleLinksLocator = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, private readonly ManagerRegistry $managerRegistry, private readonly iterable $itemExtensions = [], ?ContainerInterface $handleLinksLocator = null) { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; diff --git a/Util/QueryBuilderHelper.php b/Util/QueryBuilderHelper.php index 38f3386..7814f98 100644 --- a/Util/QueryBuilderHelper.php +++ b/Util/QueryBuilderHelper.php @@ -31,7 +31,7 @@ private function __construct() /** * Adds a join to the QueryBuilder if none exists. */ - public static function addJoinOnce(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $association, string $joinType = null, string $conditionType = null, string $condition = null, string $originAlias = null, string $newAlias = null): string + public static function addJoinOnce(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $association, ?string $joinType = null, ?string $conditionType = null, ?string $condition = null, ?string $originAlias = null, ?string $newAlias = null): string { $join = self::getExistingJoin($queryBuilder, $alias, $association, $originAlias); @@ -160,7 +160,7 @@ public static function traverseJoins(string $alias, QueryBuilder $queryBuilder, /** * Gets the existing join from QueryBuilder DQL parts. */ - public static function getExistingJoin(QueryBuilder $queryBuilder, string $alias, string $association, string $originAlias = null): ?Join + public static function getExistingJoin(QueryBuilder $queryBuilder, string $alias, string $association, ?string $originAlias = null): ?Join { $parts = $queryBuilder->getDQLPart('join'); $rootAlias = $originAlias ?? $queryBuilder->getRootAliases()[0]; From bf7718c18861a5b38c282f82529f544ffb1f2646 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Sat, 3 Feb 2024 13:33:43 +0100 Subject: [PATCH 02/72] Merge 3.2 (#6145) * fix(hydra): move owl:maxCardinality from JsonSchema to Hydra (#6136) * docs: changelog v3.2.13 * cs: missing strict type * docs: guide sf/apip version * test: security configuration * chore: fix CI (#6143) * chore: fix wrong namespace in test document * chore: fix CS nullable_type_declaration_for_default_null_value * chore: update GitHub Actions versions --------- Co-authored-by: Vincent <407859+vincentchalamon@users.noreply.github.com> --- Extension/EagerLoadingExtension.php | 6 +++--- Extension/FilterEagerLoadingExtension.php | 2 +- Extension/FilterExtension.php | 2 +- Extension/OrderExtension.php | 2 +- Extension/PaginationExtension.php | 10 +++++----- Extension/QueryCollectionExtensionInterface.php | 2 +- Extension/QueryItemExtensionInterface.php | 2 +- Extension/QueryResultCollectionExtensionInterface.php | 4 ++-- Extension/QueryResultItemExtensionInterface.php | 4 ++-- Filter/AbstractFilter.php | 6 +++--- Filter/BooleanFilter.php | 2 +- Filter/DateFilter.php | 4 ++-- Filter/ExistsFilter.php | 6 +++--- Filter/FilterInterface.php | 2 +- Filter/NumericFilter.php | 4 ++-- Filter/OrderFilter.php | 6 +++--- Filter/RangeFilter.php | 2 +- Filter/SearchFilter.php | 4 ++-- Tests/DoctrineOrmFilterTestCase.php | 8 ++++---- Tests/Filter/ExistsFilterTest.php | 6 +++--- Tests/Filter/OrderFilterTest.php | 6 +++--- Tests/Filter/SearchFilterTest.php | 2 +- ...octrineOrmResourceCollectionMetadataFactoryTest.php | 2 +- Util/QueryBuilderHelper.php | 4 ++-- 24 files changed, 49 insertions(+), 49 deletions(-) diff --git a/Extension/EagerLoadingExtension.php b/Extension/EagerLoadingExtension.php index 57f5c7b..63773b8 100644 --- a/Extension/EagerLoadingExtension.php +++ b/Extension/EagerLoadingExtension.php @@ -48,7 +48,7 @@ public function __construct(private readonly PropertyNameCollectionFactoryInterf /** * {@inheritdoc} */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, Operation $operation = null, array $context = []): void + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): void { $this->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); } @@ -56,7 +56,7 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator /** * The context may contain serialization groups which helps defining joined entities that are readable. */ - public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, Operation $operation = null, array $context = []): void + public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, ?Operation $operation = null, array $context = []): void { $this->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); } @@ -107,7 +107,7 @@ private function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $ * * @throws RuntimeException when the max number of joins has been reached */ - private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, bool $forceEager, bool $fetchPartial, string $parentAlias, array $options = [], array $normalizationContext = [], bool $wasLeftJoin = false, int &$joinCount = 0, int $currentDepth = null, string $parentAssociation = null): void + private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, bool $forceEager, bool $fetchPartial, string $parentAlias, array $options = [], array $normalizationContext = [], bool $wasLeftJoin = false, int &$joinCount = 0, ?int $currentDepth = null, ?string $parentAssociation = null): void { if ($joinCount > $this->maxJoins) { throw new RuntimeException('The total number of joined relations has exceeded the specified maximum. Raise the limit if necessary with the "api_platform.eager_loading.max_joins" configuration key (https://api-platform.com/docs/core/performance/#eager-loading), or limit the maximum serialization depth using the "enable_max_depth" option of the Symfony serializer (https://symfony.com/doc/current/components/serializer.html#handling-serialization-depth).'); diff --git a/Extension/FilterEagerLoadingExtension.php b/Extension/FilterEagerLoadingExtension.php index 1afd8b2..31170db 100644 --- a/Extension/FilterEagerLoadingExtension.php +++ b/Extension/FilterEagerLoadingExtension.php @@ -36,7 +36,7 @@ public function __construct(private readonly bool $forceEager = true, private re /** * {@inheritdoc} */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, Operation $operation = null, array $context = []): void + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): void { if (null === $resourceClass) { throw new InvalidArgumentException('The "$resourceClass" parameter must not be null'); diff --git a/Extension/FilterExtension.php b/Extension/FilterExtension.php index 551a405..298eff2 100644 --- a/Extension/FilterExtension.php +++ b/Extension/FilterExtension.php @@ -36,7 +36,7 @@ public function __construct(private readonly ContainerInterface $filterLocator) /** * {@inheritdoc} */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, Operation $operation = null, array $context = []): void + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): void { if (null === $resourceClass) { throw new InvalidArgumentException('The "$resourceClass" parameter must not be null'); diff --git a/Extension/OrderExtension.php b/Extension/OrderExtension.php index 65c7034..a383383 100644 --- a/Extension/OrderExtension.php +++ b/Extension/OrderExtension.php @@ -35,7 +35,7 @@ public function __construct(private readonly ?string $order = null) /** * {@inheritdoc} */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass = null, Operation $operation = null, array $context = []): void + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): void { if (null === $resourceClass) { throw new InvalidArgumentException('The "$resourceClass" parameter must not be null'); diff --git a/Extension/PaginationExtension.php b/Extension/PaginationExtension.php index b23285f..80513e4 100644 --- a/Extension/PaginationExtension.php +++ b/Extension/PaginationExtension.php @@ -43,7 +43,7 @@ public function __construct(private readonly ManagerRegistry $managerRegistry, p /** * {@inheritdoc} */ - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if (null === $pagination = $this->getPagination($queryBuilder, $operation, $context)) { return; @@ -59,7 +59,7 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator /** * {@inheritdoc} */ - public function supportsResult(string $resourceClass, Operation $operation = null, array $context = []): bool + public function supportsResult(string $resourceClass, ?Operation $operation = null, array $context = []): bool { if ($context['graphql_operation_name'] ?? false) { return $this->pagination->isGraphQlEnabled($operation, $context); @@ -71,7 +71,7 @@ public function supportsResult(string $resourceClass, Operation $operation = nul /** * {@inheritdoc} */ - public function getResult(QueryBuilder $queryBuilder, string $resourceClass = null, Operation $operation = null, array $context = []): iterable + public function getResult(QueryBuilder $queryBuilder, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): iterable { $query = $queryBuilder->getQuery(); @@ -125,7 +125,7 @@ private function addCountToContext(QueryBuilder $queryBuilder, array $context): /** * Determines the value of the $fetchJoinCollection argument passed to the Doctrine ORM Paginator. */ - private function shouldDoctrinePaginatorFetchJoinCollection(QueryBuilder $queryBuilder, Operation $operation = null, array $context = []): bool + private function shouldDoctrinePaginatorFetchJoinCollection(QueryBuilder $queryBuilder, ?Operation $operation = null, array $context = []): bool { $fetchJoinCollection = $operation?->getPaginationFetchJoinCollection(); @@ -158,7 +158,7 @@ private function shouldDoctrinePaginatorFetchJoinCollection(QueryBuilder $queryB /** * Determines whether the Doctrine ORM Paginator should use output walkers. */ - private function shouldDoctrinePaginatorUseOutputWalkers(QueryBuilder $queryBuilder, Operation $operation = null, array $context = []): bool + private function shouldDoctrinePaginatorUseOutputWalkers(QueryBuilder $queryBuilder, ?Operation $operation = null, array $context = []): bool { $useOutputWalkers = $operation?->getPaginationUseOutputWalkers(); diff --git a/Extension/QueryCollectionExtensionInterface.php b/Extension/QueryCollectionExtensionInterface.php index cb38613..191c2ac 100644 --- a/Extension/QueryCollectionExtensionInterface.php +++ b/Extension/QueryCollectionExtensionInterface.php @@ -25,5 +25,5 @@ */ interface QueryCollectionExtensionInterface { - public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void; + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void; } diff --git a/Extension/QueryItemExtensionInterface.php b/Extension/QueryItemExtensionInterface.php index f337ebc..4d7aaed 100644 --- a/Extension/QueryItemExtensionInterface.php +++ b/Extension/QueryItemExtensionInterface.php @@ -25,5 +25,5 @@ */ interface QueryItemExtensionInterface { - public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, Operation $operation = null, array $context = []): void; + public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, ?Operation $operation = null, array $context = []): void; } diff --git a/Extension/QueryResultCollectionExtensionInterface.php b/Extension/QueryResultCollectionExtensionInterface.php index 7f74cac..fe996d2 100644 --- a/Extension/QueryResultCollectionExtensionInterface.php +++ b/Extension/QueryResultCollectionExtensionInterface.php @@ -27,7 +27,7 @@ */ interface QueryResultCollectionExtensionInterface extends QueryCollectionExtensionInterface { - public function supportsResult(string $resourceClass, Operation $operation = null, array $context = []): bool; + public function supportsResult(string $resourceClass, ?Operation $operation = null, array $context = []): bool; - public function getResult(QueryBuilder $queryBuilder, string $resourceClass = null, Operation $operation = null, array $context = []): iterable; + public function getResult(QueryBuilder $queryBuilder, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): iterable; } diff --git a/Extension/QueryResultItemExtensionInterface.php b/Extension/QueryResultItemExtensionInterface.php index 7cc513e..4c23d77 100644 --- a/Extension/QueryResultItemExtensionInterface.php +++ b/Extension/QueryResultItemExtensionInterface.php @@ -26,7 +26,7 @@ */ interface QueryResultItemExtensionInterface extends QueryItemExtensionInterface { - public function supportsResult(string $resourceClass, Operation $operation = null, array $context = []): bool; + public function supportsResult(string $resourceClass, ?Operation $operation = null, array $context = []): bool; - public function getResult(QueryBuilder $queryBuilder, string $resourceClass = null, Operation $operation = null, array $context = []): ?object; + public function getResult(QueryBuilder $queryBuilder, ?string $resourceClass = null, ?Operation $operation = null, array $context = []): ?object; } diff --git a/Filter/AbstractFilter.php b/Filter/AbstractFilter.php index d2b2b5d..c1deb1c 100644 --- a/Filter/AbstractFilter.php +++ b/Filter/AbstractFilter.php @@ -29,7 +29,7 @@ abstract class AbstractFilter implements FilterInterface use PropertyHelperTrait; protected LoggerInterface $logger; - public function __construct(protected ManagerRegistry $managerRegistry, LoggerInterface $logger = null, protected ?array $properties = null, protected ?NameConverterInterface $nameConverter = null) + public function __construct(protected ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, protected ?array $properties = null, protected ?NameConverterInterface $nameConverter = null) { $this->logger = $logger ?? new NullLogger(); } @@ -37,7 +37,7 @@ public function __construct(protected ManagerRegistry $managerRegistry, LoggerIn /** * {@inheritdoc} */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { foreach ($context['filters'] as $property => $value) { $this->filterProperty($this->denormalizePropertyName($property), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); @@ -47,7 +47,7 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q /** * Passes a property through the filter. */ - abstract protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void; + abstract protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void; protected function getManagerRegistry(): ManagerRegistry { diff --git a/Filter/BooleanFilter.php b/Filter/BooleanFilter.php index cb040c4..2f230c4 100644 --- a/Filter/BooleanFilter.php +++ b/Filter/BooleanFilter.php @@ -117,7 +117,7 @@ final class BooleanFilter extends AbstractFilter /** * {@inheritdoc} */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if ( !$this->isPropertyEnabled($property, $resourceClass) diff --git a/Filter/DateFilter.php b/Filter/DateFilter.php index dd42e8a..066606e 100644 --- a/Filter/DateFilter.php +++ b/Filter/DateFilter.php @@ -138,7 +138,7 @@ final class DateFilter extends AbstractFilter implements DateFilterInterface /** * {@inheritdoc} */ - protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { // Expect $values to be an array having the period as keys and the date value as values if ( @@ -220,7 +220,7 @@ protected function filterProperty(string $property, $values, QueryBuilder $query /** * Adds the where clause according to the chosen null management. */ - protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, mixed $value, string $nullManagement = null, DBALType|string $type = null): void + protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, mixed $value, ?string $nullManagement = null, DBALType|string|null $type = null): void { $type = (string) $type; $value = $this->normalizeValue($value, $operator); diff --git a/Filter/ExistsFilter.php b/Filter/ExistsFilter.php index f7f2cba..db0db4b 100644 --- a/Filter/ExistsFilter.php +++ b/Filter/ExistsFilter.php @@ -114,7 +114,7 @@ final class ExistsFilter extends AbstractFilter implements ExistsFilterInterface { use ExistsFilterTrait; - public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $logger = null, array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, NameConverterInterface $nameConverter = null) + public function __construct(ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, ?array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, ?NameConverterInterface $nameConverter = null) { parent::__construct($managerRegistry, $logger, $properties, $nameConverter); @@ -124,7 +124,7 @@ public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $l /** * {@inheritdoc} */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { foreach ($context['filters'][$this->existsParameterName] ?? [] as $property => $value) { $this->filterProperty($this->denormalizePropertyName($property), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); @@ -134,7 +134,7 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q /** * {@inheritdoc} */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if ( !$this->isPropertyEnabled($property, $resourceClass) diff --git a/Filter/FilterInterface.php b/Filter/FilterInterface.php index 50e02b1..4cfa337 100644 --- a/Filter/FilterInterface.php +++ b/Filter/FilterInterface.php @@ -28,5 +28,5 @@ interface FilterInterface extends BaseFilterInterface /** * Applies the filter. */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void; + public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void; } diff --git a/Filter/NumericFilter.php b/Filter/NumericFilter.php index bbcd1d6..c27e3f6 100644 --- a/Filter/NumericFilter.php +++ b/Filter/NumericFilter.php @@ -126,7 +126,7 @@ final class NumericFilter extends AbstractFilter /** * {@inheritdoc} */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if ( !$this->isPropertyEnabled($property, $resourceClass) @@ -164,7 +164,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB /** * {@inheritdoc} */ - protected function getType(string $doctrineType = null): string + protected function getType(?string $doctrineType = null): string { if (null === $doctrineType || Types::DECIMAL === $doctrineType) { return 'string'; diff --git a/Filter/OrderFilter.php b/Filter/OrderFilter.php index abece57..bf40b54 100644 --- a/Filter/OrderFilter.php +++ b/Filter/OrderFilter.php @@ -197,7 +197,7 @@ final class OrderFilter extends AbstractFilter implements OrderFilterInterface { use OrderFilterTrait; - public function __construct(ManagerRegistry $managerRegistry, string $orderParameterName = 'order', LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null, private readonly ?string $orderNullsComparison = null) + public function __construct(ManagerRegistry $managerRegistry, string $orderParameterName = 'order', ?LoggerInterface $logger = null, ?array $properties = null, ?NameConverterInterface $nameConverter = null, private readonly ?string $orderNullsComparison = null) { if (null !== $properties) { $properties = array_map(static function ($propertyOptions) { @@ -220,7 +220,7 @@ public function __construct(ManagerRegistry $managerRegistry, string $orderParam /** * {@inheritdoc} */ - public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if (isset($context['filters']) && !isset($context['filters'][$this->orderParameterName])) { return; @@ -240,7 +240,7 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q /** * {@inheritdoc} */ - protected function filterProperty(string $property, $direction, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $direction, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if (!$this->isPropertyEnabled($property, $resourceClass) || !$this->isPropertyMapped($property, $resourceClass)) { return; diff --git a/Filter/RangeFilter.php b/Filter/RangeFilter.php index c2e0aaf..70c3e68 100644 --- a/Filter/RangeFilter.php +++ b/Filter/RangeFilter.php @@ -112,7 +112,7 @@ final class RangeFilter extends AbstractFilter implements RangeFilterInterface /** * {@inheritdoc} */ - protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if ( !\is_array($values) diff --git a/Filter/SearchFilter.php b/Filter/SearchFilter.php index 4133f33..1a46caf 100644 --- a/Filter/SearchFilter.php +++ b/Filter/SearchFilter.php @@ -141,7 +141,7 @@ final class SearchFilter extends AbstractFilter implements SearchFilterInterface public const DOCTRINE_INTEGER_TYPE = Types::INTEGER; - public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface|LegacyIriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, IdentifiersExtractorInterface|LegacyIdentifiersExtractorInterface $identifiersExtractor = null, NameConverterInterface $nameConverter = null) + public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface|LegacyIriConverterInterface $iriConverter, ?PropertyAccessorInterface $propertyAccessor = null, ?LoggerInterface $logger = null, ?array $properties = null, IdentifiersExtractorInterface|LegacyIdentifiersExtractorInterface|null $identifiersExtractor = null, ?NameConverterInterface $nameConverter = null) { parent::__construct($managerRegistry, $logger, $properties, $nameConverter); @@ -163,7 +163,7 @@ protected function getPropertyAccessor(): PropertyAccessorInterface /** * {@inheritdoc} */ - protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if ( null === $value diff --git a/Tests/DoctrineOrmFilterTestCase.php b/Tests/DoctrineOrmFilterTestCase.php index f901340..77b851e 100644 --- a/Tests/DoctrineOrmFilterTestCase.php +++ b/Tests/DoctrineOrmFilterTestCase.php @@ -48,15 +48,15 @@ protected function setUp(): void /** * @dataProvider provideApplyTestData */ - public function testApply(?array $properties, array $filterParameters, string $expectedDql, array $expectedParameters = null, callable $factory = null, string $resourceClass = null): void + public function testApply(?array $properties, array $filterParameters, string $expectedDql, ?array $expectedParameters = null, ?callable $factory = null, ?string $resourceClass = null): void { $this->doTestApply($properties, $filterParameters, $expectedDql, $expectedParameters, $factory, $resourceClass); } - protected function doTestApply(?array $properties, array $filterParameters, string $expectedDql, array $expectedParameters = null, callable $filterFactory = null, string $resourceClass = null): void + protected function doTestApply(?array $properties, array $filterParameters, string $expectedDql, ?array $expectedParameters = null, ?callable $filterFactory = null, ?string $resourceClass = null): void { if (null === $filterFactory) { - $filterFactory = fn (self $that, ManagerRegistry $managerRegistry, array $properties = null): FilterInterface => new ($this->filterClass)($managerRegistry, null, $properties); + $filterFactory = fn (self $that, ManagerRegistry $managerRegistry, ?array $properties = null): FilterInterface => new ($this->filterClass)($managerRegistry, null, $properties); } $repository = $this->repository; @@ -83,7 +83,7 @@ protected function doTestApply(?array $properties, array $filterParameters, stri } } - protected function buildFilter(array $properties = null) + protected function buildFilter(?array $properties = null) { return new $this->filterClass($this->managerRegistry, null, $properties); } diff --git a/Tests/Filter/ExistsFilterTest.php b/Tests/Filter/ExistsFilterTest.php index cb0782e..eb2e30a 100644 --- a/Tests/Filter/ExistsFilterTest.php +++ b/Tests/Filter/ExistsFilterTest.php @@ -94,8 +94,8 @@ public function testGetDescriptionDefaultFields(): void public static function provideApplyTestData(): array { - $existsFilterFactory = fn (self $that, ManagerRegistry $managerRegistry, array $properties = null): ExistsFilter => new ExistsFilter($managerRegistry, null, $properties, 'exists'); - $customExistsFilterFactory = fn (self $that, ManagerRegistry $managerRegistry, array $properties = null): ExistsFilter => new ExistsFilter($managerRegistry, null, $properties, 'customExists'); + $existsFilterFactory = fn (self $that, ManagerRegistry $managerRegistry, ?array $properties = null): ExistsFilter => new ExistsFilter($managerRegistry, null, $properties, 'exists'); + $customExistsFilterFactory = fn (self $that, ManagerRegistry $managerRegistry, ?array $properties = null): ExistsFilter => new ExistsFilter($managerRegistry, null, $properties, 'customExists'); return array_merge_recursive( self::provideApplyTestArguments(), @@ -223,7 +223,7 @@ public static function provideApplyTestData(): array ); } - protected function buildFilter(array $properties = null) + protected function buildFilter(?array $properties = null) { return new $this->filterClass($this->managerRegistry, null, $properties, 'exists', new CustomConverter()); } diff --git a/Tests/Filter/OrderFilterTest.php b/Tests/Filter/OrderFilterTest.php index 2e1fa99..c83142c 100644 --- a/Tests/Filter/OrderFilterTest.php +++ b/Tests/Filter/OrderFilterTest.php @@ -296,8 +296,8 @@ public function testGetDescriptionDefaultFields(): void public static function provideApplyTestData(): array { - $orderFilterFactory = fn (self $that, ManagerRegistry $managerRegistry, array $properties = null): OrderFilter => new OrderFilter($managerRegistry, 'order', null, $properties); - $customOrderFilterFactory = fn (self $that, ManagerRegistry $managerRegistry, array $properties = null): OrderFilter => new OrderFilter($managerRegistry, 'customOrder', null, $properties); + $orderFilterFactory = fn (self $that, ManagerRegistry $managerRegistry, ?array $properties = null): OrderFilter => new OrderFilter($managerRegistry, 'order', null, $properties); + $customOrderFilterFactory = fn (self $that, ManagerRegistry $managerRegistry, ?array $properties = null): OrderFilter => new OrderFilter($managerRegistry, 'customOrder', null, $properties); return array_merge_recursive( self::provideApplyTestArguments(), @@ -423,7 +423,7 @@ public static function provideApplyTestData(): array ); } - protected function buildFilter(array $properties = null) + protected function buildFilter(?array $properties = null) { return new $this->filterClass($this->managerRegistry, 'order', null, $properties, new CustomConverter()); } diff --git a/Tests/Filter/SearchFilterTest.php b/Tests/Filter/SearchFilterTest.php index aa1f8e4..7b81fdc 100644 --- a/Tests/Filter/SearchFilterTest.php +++ b/Tests/Filter/SearchFilterTest.php @@ -504,7 +504,7 @@ public static function provideApplyTestData(): array ); } - protected static function buildSearchFilter(self $that, ManagerRegistry $managerRegistry, array $properties = null): SearchFilter + protected static function buildSearchFilter(self $that, ManagerRegistry $managerRegistry, ?array $properties = null): SearchFilter { $relatedDummyProphecy = $that->prophesize(RelatedDummy::class); $iriConverterProphecy = $that->prophesize(IriConverterInterface::class); diff --git a/Tests/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php b/Tests/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php index 34433a5..9904186 100644 --- a/Tests/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php +++ b/Tests/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php @@ -65,7 +65,7 @@ public function testWithoutManager(): void /** * @dataProvider operationProvider */ - public function testWithProvider(Operation $operation, string $expectedProvider = null, string $expectedProcessor = null): void + public function testWithProvider(Operation $operation, ?string $expectedProvider = null, ?string $expectedProcessor = null): void { $objectManager = $this->prophesize(EntityManagerInterface::class); $managerRegistry = $this->prophesize(ManagerRegistry::class); diff --git a/Util/QueryBuilderHelper.php b/Util/QueryBuilderHelper.php index 38f3386..7814f98 100644 --- a/Util/QueryBuilderHelper.php +++ b/Util/QueryBuilderHelper.php @@ -31,7 +31,7 @@ private function __construct() /** * Adds a join to the QueryBuilder if none exists. */ - public static function addJoinOnce(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $association, string $joinType = null, string $conditionType = null, string $condition = null, string $originAlias = null, string $newAlias = null): string + public static function addJoinOnce(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $association, ?string $joinType = null, ?string $conditionType = null, ?string $condition = null, ?string $originAlias = null, ?string $newAlias = null): string { $join = self::getExistingJoin($queryBuilder, $alias, $association, $originAlias); @@ -160,7 +160,7 @@ public static function traverseJoins(string $alias, QueryBuilder $queryBuilder, /** * Gets the existing join from QueryBuilder DQL parts. */ - public static function getExistingJoin(QueryBuilder $queryBuilder, string $alias, string $association, string $originAlias = null): ?Join + public static function getExistingJoin(QueryBuilder $queryBuilder, string $alias, string $association, ?string $originAlias = null): ?Join { $parts = $queryBuilder->getDQLPart('join'); $rootAlias = $originAlias ?? $queryBuilder->getRootAliases()[0]; From 1ac56b19b75b36cc04c9b158279410e1d4f921fc Mon Sep 17 00:00:00 2001 From: Vincent <407859+vincentchalamon@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:20:47 +0100 Subject: [PATCH 03/72] chore: fix CI (#6144) * chore: disable keep_legacy_inflector in Doctrine tests * chore: fix Doctrine ORM enable_lazy_ghost_objects deprecation in tests configuration * chore: fix null value for CS * chore: fix CS post rebase from main * chore: remove useless legacy test services * chore: fix YAML with Symfony dev dependencies --- State/CollectionProvider.php | 2 +- State/ItemProvider.php | 2 +- Tests/Fixtures/Entity/CompositeLabel.php | 2 +- Tests/Fixtures/Entity/Dummy.php | 4 ++-- Tests/Fixtures/Entity/RelatedDummy.php | 2 +- Tests/Fixtures/Entity/ThirdLevel.php | 2 +- Tests/config.yml | 2 ++ 7 files changed, 9 insertions(+), 7 deletions(-) diff --git a/State/CollectionProvider.php b/State/CollectionProvider.php index 0c0248e..ab2cb5a 100644 --- a/State/CollectionProvider.php +++ b/State/CollectionProvider.php @@ -39,7 +39,7 @@ final class CollectionProvider implements ProviderInterface /** * @param QueryCollectionExtensionInterface[] $collectionExtensions */ - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, private readonly iterable $collectionExtensions = [], ContainerInterface $handleLinksLocator = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, private readonly iterable $collectionExtensions = [], ?ContainerInterface $handleLinksLocator = null) { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; diff --git a/State/ItemProvider.php b/State/ItemProvider.php index 91896e7..0a7f486 100644 --- a/State/ItemProvider.php +++ b/State/ItemProvider.php @@ -39,7 +39,7 @@ final class ItemProvider implements ProviderInterface /** * @param QueryItemExtensionInterface[] $itemExtensions */ - public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, private readonly iterable $itemExtensions = [], ContainerInterface $handleLinksLocator = null) + public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ManagerRegistry $managerRegistry, private readonly iterable $itemExtensions = [], ?ContainerInterface $handleLinksLocator = null) { $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory; $this->handleLinksLocator = $handleLinksLocator; diff --git a/Tests/Fixtures/Entity/CompositeLabel.php b/Tests/Fixtures/Entity/CompositeLabel.php index a884b4e..59ec038 100644 --- a/Tests/Fixtures/Entity/CompositeLabel.php +++ b/Tests/Fixtures/Entity/CompositeLabel.php @@ -53,7 +53,7 @@ public function getValue(): ?string * * @param string|null $value the value to set */ - public function setValue(string $value = null): void + public function setValue(?string $value = null): void { $this->value = $value; } diff --git a/Tests/Fixtures/Entity/Dummy.php b/Tests/Fixtures/Entity/Dummy.php index 46d4bd4..00df229 100644 --- a/Tests/Fixtures/Entity/Dummy.php +++ b/Tests/Fixtures/Entity/Dummy.php @@ -194,12 +194,12 @@ public function getFoo(): ?array return $this->foo; } - public function setFoo(array $foo = null): void + public function setFoo(?array $foo = null): void { $this->foo = $foo; } - public function setDummyDate(\DateTime $dummyDate = null): void + public function setDummyDate(?\DateTime $dummyDate = null): void { $this->dummyDate = $dummyDate; } diff --git a/Tests/Fixtures/Entity/RelatedDummy.php b/Tests/Fixtures/Entity/RelatedDummy.php index 9c15807..9fbe053 100644 --- a/Tests/Fixtures/Entity/RelatedDummy.php +++ b/Tests/Fixtures/Entity/RelatedDummy.php @@ -169,7 +169,7 @@ public function getThirdLevel(): ?ThirdLevel return $this->thirdLevel; } - public function setThirdLevel(ThirdLevel $thirdLevel = null): void + public function setThirdLevel(?ThirdLevel $thirdLevel = null): void { $this->thirdLevel = $thirdLevel; } diff --git a/Tests/Fixtures/Entity/ThirdLevel.php b/Tests/Fixtures/Entity/ThirdLevel.php index 3203c97..e3105c7 100644 --- a/Tests/Fixtures/Entity/ThirdLevel.php +++ b/Tests/Fixtures/Entity/ThirdLevel.php @@ -97,7 +97,7 @@ public function getFourthLevel(): ?FourthLevel return $this->fourthLevel; } - public function setFourthLevel(FourthLevel $fourthLevel = null): void + public function setFourthLevel(?FourthLevel $fourthLevel = null): void { $this->fourthLevel = $fourthLevel; } diff --git a/Tests/config.yml b/Tests/config.yml index 42d86cc..120b389 100644 --- a/Tests/config.yml +++ b/Tests/config.yml @@ -8,6 +8,7 @@ doctrine: orm: auto_generate_proxy_classes: '%kernel.debug%' + enable_lazy_ghost_objects: true mappings: ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity: type: attribute @@ -20,6 +21,7 @@ api_platform: formats: json: ['application/json'] doctrine: true + keep_legacy_inflector: false mapping: paths: - '%kernel.project_dir%/Fixtures/Entity' From a3e45fd944245ba68342709cf682f1899b108198 Mon Sep 17 00:00:00 2001 From: Emmanuel Averty Date: Fri, 16 Feb 2024 09:15:32 +0100 Subject: [PATCH 04/72] fix(doctrine): test array type existence before using it (#6161) --- Filter/SearchFilter.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Filter/SearchFilter.php b/Filter/SearchFilter.php index 7fecabb..bdb60be 100644 --- a/Filter/SearchFilter.php +++ b/Filter/SearchFilter.php @@ -369,8 +369,12 @@ protected function createWrapCase(bool $caseSensitive): \Closure */ protected function getType(string $doctrineType): string { + // Remove this test when doctrine/dbal:3 support is removed + if (\defined(Types::class.'::ARRAY') && Types::ARRAY === $doctrineType) { + return 'array'; + } + return match ($doctrineType) { - Types::ARRAY => 'array', Types::BIGINT, Types::INTEGER, Types::SMALLINT => 'int', Types::BOOLEAN => 'bool', Types::DATE_MUTABLE, Types::TIME_MUTABLE, Types::DATETIME_MUTABLE, Types::DATETIMETZ_MUTABLE, Types::DATE_IMMUTABLE, Types::TIME_IMMUTABLE, Types::DATETIME_IMMUTABLE, Types::DATETIMETZ_IMMUTABLE => \DateTimeInterface::class, From b65414fee24019fff61b37a13f9251d21c5673ed Mon Sep 17 00:00:00 2001 From: Xavier Leune Date: Tue, 20 Feb 2024 09:17:55 +0100 Subject: [PATCH 05/72] feat(graphql): partial pagination for page based pagination (#6120) Co-authored-by: Xavier Leune --- Extension/DoctrinePaginatorFactory.php | 24 +++++++++++ Paginator.php | 42 +++++++++++++++++- Tests/Fixtures/Query.php | 41 ++++++++++++++++++ Tests/PaginatorTest.php | 59 ++++++++++++++++++++++++-- 4 files changed, 162 insertions(+), 4 deletions(-) create mode 100644 Extension/DoctrinePaginatorFactory.php diff --git a/Extension/DoctrinePaginatorFactory.php b/Extension/DoctrinePaginatorFactory.php new file mode 100644 index 0000000..f7daadb --- /dev/null +++ b/Extension/DoctrinePaginatorFactory.php @@ -0,0 +1,24 @@ + + * + * 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\Doctrine\Orm\Extension; + +use Doctrine\ORM\Tools\Pagination\Paginator; + +class DoctrinePaginatorFactory +{ + public function getPaginator($query, $fetchJoinCollection): Paginator + { + return new Paginator($query, $fetchJoinCollection); + } +} diff --git a/Paginator.php b/Paginator.php index 4e1dff6..2256a62 100644 --- a/Paginator.php +++ b/Paginator.php @@ -13,17 +13,21 @@ namespace ApiPlatform\Doctrine\Orm; +use ApiPlatform\Doctrine\Orm\Extension\DoctrinePaginatorFactory; +use ApiPlatform\State\Pagination\HasNextPagePaginatorInterface; use ApiPlatform\State\Pagination\PaginatorInterface; use Doctrine\ORM\Query; +use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator; /** * Decorates the Doctrine ORM paginator. * * @author Kévin Dunglas */ -final class Paginator extends AbstractPaginator implements PaginatorInterface, QueryAwareInterface +final class Paginator extends AbstractPaginator implements PaginatorInterface, QueryAwareInterface, HasNextPagePaginatorInterface { private ?int $totalItems = null; + private ?DoctrinePaginatorFactory $doctrinePaginatorFactory = null; /** * {@inheritdoc} @@ -52,4 +56,40 @@ public function getQuery(): Query { return $this->paginator->getQuery(); } + + /** + * {@inheritdoc} + */ + public function hasNextPage(): bool + { + if (isset($this->totalItems)) { + return $this->totalItems > ($this->firstResult + $this->maxResults); + } + + $cloneQuery = clone $this->paginator->getQuery(); + + $cloneQuery->setParameters(clone $this->paginator->getQuery()->getParameters()); + $cloneQuery->setCacheable(false); + + foreach ($this->paginator->getQuery()->getHints() as $name => $value) { + $cloneQuery->setHint($name, $value); + } + + $cloneQuery + ->setFirstResult($this->paginator->getQuery()->getFirstResult() + $this->paginator->getQuery()->getMaxResults()) + ->setMaxResults(1); + + if (null !== $this->doctrinePaginatorFactory) { + $fakePaginator = $this->doctrinePaginatorFactory->getPaginator($cloneQuery, $this->paginator->getFetchJoinCollection()); + } else { + $fakePaginator = new DoctrinePaginator($cloneQuery, $this->paginator->getFetchJoinCollection()); + } + + return iterator_count($fakePaginator->getIterator()) > 0; + } + + public function setDoctrinePaginatorFactory(?DoctrinePaginatorFactory $doctrinePaginatorFactory = null): void + { + $this->doctrinePaginatorFactory = $doctrinePaginatorFactory; + } } diff --git a/Tests/Fixtures/Query.php b/Tests/Fixtures/Query.php index b0003a8..f6536af 100644 --- a/Tests/Fixtures/Query.php +++ b/Tests/Fixtures/Query.php @@ -13,6 +13,8 @@ namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures; +use Doctrine\Common\Collections\ArrayCollection; + /** * Replace Doctrine\ORM\Query in tests because it cannot be mocked. */ @@ -27,4 +29,43 @@ public function getMaxResults(): ?int { return null; } + + public function setFirstResult($firstResult): self + { + return $this; + } + + public function setMaxResults($maxResults): self + { + return $this; + } + + public function setParameters($parameters): self + { + return $this; + } + + public function getParameters() + { + return new ArrayCollection(); + } + + public function setCacheable($cacheable): self + { + return $this; + } + + public function getHints() + { + return []; + } + + public function getFetchJoinCollection() + { + return false; + } + + public function getResult(): void + { + } } diff --git a/Tests/PaginatorTest.php b/Tests/PaginatorTest.php index 87f7324..db44032 100644 --- a/Tests/PaginatorTest.php +++ b/Tests/PaginatorTest.php @@ -13,11 +13,14 @@ namespace ApiPlatform\Doctrine\Orm\Tests; +use ApiPlatform\Doctrine\Orm\Extension\DoctrinePaginatorFactory; use ApiPlatform\Doctrine\Orm\Paginator; use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Query; use ApiPlatform\Metadata\Exception\InvalidArgumentException; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator; use PHPUnit\Framework\TestCase; +use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; class PaginatorTest extends TestCase @@ -27,13 +30,14 @@ class PaginatorTest extends TestCase /** * @dataProvider initializeProvider */ - public function testInitialize(int $firstResult, int $maxResults, int $totalItems, int $currentPage, int $lastPage): void + public function testInitialize(int $firstResult, int $maxResults, int $totalItems, int $currentPage, int $lastPage, bool $hasNextPage): void { $paginator = $this->getPaginator($firstResult, $maxResults, $totalItems); $this->assertSame((float) $currentPage, $paginator->getCurrentPage()); $this->assertSame((float) $lastPage, $paginator->getLastPage()); $this->assertSame((float) $maxResults, $paginator->getItemsPerPage()); + $this->assertSame($hasNextPage, $paginator->hasNextPage()); } public function testInitializeWithQueryFirstResultNotApplied(): void @@ -90,11 +94,60 @@ private function getPaginatorWithMalformedQuery(bool $maxResults = false): void new Paginator($doctrinePaginator->reveal()); } + public function testHasNextPageShouldNotMakeQueryIfTotalPagesHasBeenCalled(): void + { + $query = $this->prophesize(Query::class); + $query->getFirstResult()->willReturn(1)->shouldBeCalled(); + $query->getMaxResults()->willReturn(15)->shouldBeCalled(); + $query->setMaxResults(Argument::any())->shouldNotBeCalled(); + + $doctrinePaginator = $this->prophesize(DoctrinePaginator::class); + + $doctrinePaginator->getQuery()->willReturn($query->reveal())->shouldBeCalled(); + $doctrinePaginator->count()->willReturn(42); + + $doctrinePaginator->getIterator()->will(fn (): \ArrayIterator => new \ArrayIterator()); + + $paginator = new Paginator($doctrinePaginator->reveal()); + $paginator->getTotalItems(); + $this->assertTrue($paginator->hasNextPage()); + } + + public function testHasNextPageShouldMakeQueryIfTotalPagesHasNotBeenCalled(): void + { + $query = $this->prophesize(Query::class); + $query->getFirstResult()->willReturn(1)->shouldBeCalled(); + $query->getMaxResults()->willReturn(15)->shouldBeCalled(); + $query->getParameters()->willReturn(new ArrayCollection())->shouldBeCalled(); + $query->setParameters(Argument::any())->willReturn($query->reveal())->shouldBeCalled(); + $query->setCacheable(false)->willReturn($query->reveal())->shouldBeCalled(); + $query->setMaxResults(1)->shouldBeCalled(); + $query->getHints()->willReturn([])->shouldBeCalled(); + $query->setFirstResult(Argument::any())->willReturn($query->reveal())->shouldBeCalled(); + + $doctrinePaginator = $this->prophesize(DoctrinePaginator::class); + + $doctrinePaginator->getQuery()->willReturn($query->reveal())->shouldBeCalled(); + $doctrinePaginator->count()->willReturn(42); + $doctrinePaginator->getFetchJoinCollection()->willReturn(false); + + $doctrinePaginator->getIterator()->will(fn (): \ArrayIterator => new \ArrayIterator()); + + $secondDoctrinePaginator = $this->prophesize(DoctrinePaginator::class); + $secondDoctrinePaginator->getIterator()->will(fn (): \ArrayIterator => new \ArrayIterator()); + $doctrinePaginatorFactory = $this->prophesize(DoctrinePaginatorFactory::class); + $doctrinePaginatorFactory->getPaginator(Argument::any(), Argument::any())->willReturn($secondDoctrinePaginator->reveal()); + + $paginator = new Paginator($doctrinePaginator->reveal()); + $paginator->setDoctrinePaginatorFactory($doctrinePaginatorFactory->reveal()); + $this->assertFalse($paginator->hasNextPage()); + } + public static function initializeProvider(): array { return [ - 'First of three pages of 15 items each' => [0, 15, 42, 1, 3], - 'Second of two pages of 10 items each' => [10, 10, 20, 2, 2], + 'First of three pages of 15 items each' => [0, 15, 42, 1, 3, true], + 'Second of two pages of 10 items each' => [10, 10, 20, 2, 2, false], ]; } } From e7f098c075afd0aac94d6f785116646c0566d202 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Thu, 29 Feb 2024 18:53:32 +0100 Subject: [PATCH 06/72] fix: components split dependencies (#6186) --- Tests/AppKernel.php | 2 - Tests/Filter/BooleanFilterTest.php | 1 - Tests/Filter/BooleanFilterTestTrait.php | 170 ++++++++ Tests/Filter/DateFilterTest.php | 1 - Tests/Filter/DateFilterTestTrait.php | 229 +++++++++++ Tests/Filter/ExistsFilterTest.php | 1 - Tests/Filter/ExistsFilterTestTrait.php | 280 +++++++++++++ Tests/Filter/NumericFilterTest.php | 1 - Tests/Filter/NumericFilterTestTrait.php | 206 ++++++++++ Tests/Filter/OrderFilterTest.php | 1 - Tests/Filter/OrderFilterTestTrait.php | 338 ++++++++++++++++ Tests/Filter/RangeFilterTest.php | 1 - Tests/Filter/RangeFilterTestTrait.php | 139 +++++++ Tests/Filter/SearchFilterTest.php | 1 - Tests/Filter/SearchFilterTestTrait.php | 517 ++++++++++++++++++++++++ Tests/config.yml | 9 - composer.json | 147 +++---- 17 files changed, 1944 insertions(+), 100 deletions(-) create mode 100644 Tests/Filter/BooleanFilterTestTrait.php create mode 100644 Tests/Filter/DateFilterTestTrait.php create mode 100644 Tests/Filter/ExistsFilterTestTrait.php create mode 100644 Tests/Filter/NumericFilterTestTrait.php create mode 100644 Tests/Filter/OrderFilterTestTrait.php create mode 100644 Tests/Filter/RangeFilterTestTrait.php create mode 100644 Tests/Filter/SearchFilterTestTrait.php diff --git a/Tests/AppKernel.php b/Tests/AppKernel.php index 22409c7..29229e2 100644 --- a/Tests/AppKernel.php +++ b/Tests/AppKernel.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Doctrine\Orm\Tests; -use ApiPlatform\Symfony\Bundle\ApiPlatformBundle; use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; @@ -41,7 +40,6 @@ public function __construct(string $environment, bool $debug) public function registerBundles(): array { return [ - new ApiPlatformBundle(), new FrameworkBundle(), new DoctrineBundle(), new TestBundle(), diff --git a/Tests/Filter/BooleanFilterTest.php b/Tests/Filter/BooleanFilterTest.php index fa9425b..1fdd6aa 100644 --- a/Tests/Filter/BooleanFilterTest.php +++ b/Tests/Filter/BooleanFilterTest.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Doctrine\Orm\Tests\Filter; -use ApiPlatform\Doctrine\Common\Tests\Filter\BooleanFilterTestTrait; use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter; use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase; use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy; diff --git a/Tests/Filter/BooleanFilterTestTrait.php b/Tests/Filter/BooleanFilterTestTrait.php new file mode 100644 index 0000000..26c4ef2 --- /dev/null +++ b/Tests/Filter/BooleanFilterTestTrait.php @@ -0,0 +1,170 @@ + + * + * 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\Doctrine\Orm\Tests\Filter; + +/** + * @author Amrouche Hamza + */ +trait BooleanFilterTestTrait +{ + public function testGetDescription(): void + { + $filter = $this->buildFilter([ + 'id' => null, + 'name' => null, + 'foo' => null, + 'dummyBoolean' => null, + ]); + + $this->assertEquals([ + 'dummyBoolean' => [ + 'property' => 'dummyBoolean', + 'type' => 'bool', + 'required' => false, + ], + ], $filter->getDescription($this->resourceClass)); + } + + public function testGetDescriptionDefaultFields(): void + { + $filter = $this->buildFilter(); + + $this->assertEquals([ + 'dummyBoolean' => [ + 'property' => 'dummyBoolean', + 'type' => 'bool', + 'required' => false, + ], + ], $filter->getDescription($this->resourceClass)); + } + + private static function provideApplyTestArguments(): array + { + return [ + 'string ("true")' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBoolean' => null, + ], + [ + 'dummyBoolean' => 'true', + ], + ], + 'string ("false")' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBoolean' => null, + ], + [ + 'dummyBoolean' => 'false', + ], + ], + 'non-boolean' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBoolean' => null, + ], + [ + 'dummyBoolean' => 'toto', + ], + ], + 'numeric string ("0")' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBoolean' => null, + ], + [ + 'dummyBoolean' => '0', + ], + ], + 'numeric string ("1")' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBoolean' => null, + ], + [ + 'dummyBoolean' => '1', + ], + ], + 'nested properties' => [ + [ + 'id' => null, + 'name' => null, + 'relatedDummy.dummyBoolean' => null, + ], + [ + 'relatedDummy.dummyBoolean' => '1', + ], + ], + 'numeric string ("1") on non-boolean property' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBoolean' => null, + ], + [ + 'name' => '1', + ], + ], + 'numeric string ("0") on non-boolean property' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBoolean' => null, + ], + [ + 'name' => '0', + ], + ], + 'string ("true") on non-boolean property' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBoolean' => null, + ], + [ + 'name' => 'true', + ], + ], + 'string ("false") on non-boolean property' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBoolean' => null, + ], + [ + 'name' => 'false', + ], + ], + 'mixed boolean, non-boolean and invalid property' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBoolean' => null, + ], + [ + 'dummyBoolean' => 'false', + 'toto' => 'toto', + 'name' => 'true', + 'id' => '0', + ], + ], + ]; + } +} diff --git a/Tests/Filter/DateFilterTest.php b/Tests/Filter/DateFilterTest.php index 91e6aa4..01d5e72 100644 --- a/Tests/Filter/DateFilterTest.php +++ b/Tests/Filter/DateFilterTest.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Doctrine\Orm\Tests\Filter; -use ApiPlatform\Doctrine\Common\Tests\Filter\DateFilterTestTrait; use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase; use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy; diff --git a/Tests/Filter/DateFilterTestTrait.php b/Tests/Filter/DateFilterTestTrait.php new file mode 100644 index 0000000..24c00e4 --- /dev/null +++ b/Tests/Filter/DateFilterTestTrait.php @@ -0,0 +1,229 @@ + + * + * 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\Doctrine\Orm\Tests\Filter; + +/** + * @author Théo FIDRY + * @author Vincent CHALAMON + */ +trait DateFilterTestTrait +{ + public function testGetDescription(): void + { + $filter = $this->buildFilter(); + + $this->assertEquals([ + 'dummyDate[before]' => [ + 'property' => 'dummyDate', + 'type' => \DateTimeInterface::class, + 'required' => false, + ], + 'dummyDate[strictly_before]' => [ + 'property' => 'dummyDate', + 'type' => \DateTimeInterface::class, + 'required' => false, + ], + 'dummyDate[after]' => [ + 'property' => 'dummyDate', + 'type' => \DateTimeInterface::class, + 'required' => false, + ], + 'dummyDate[strictly_after]' => [ + 'property' => 'dummyDate', + 'type' => \DateTimeInterface::class, + 'required' => false, + ], + ], $filter->getDescription($this->resourceClass)); + } + + private static function provideApplyTestArguments(): array + { + return [ + 'after (all properties enabled)' => [ + null, + [ + 'dummyDate' => [ + 'after' => '2015-04-05', + ], + ], + ], + 'after but not equals (all properties enabled)' => [ + null, + [ + 'dummyDate' => [ + 'strictly_after' => '2015-04-05', + ], + ], + ], + 'after' => [ + [ + 'dummyDate' => null, + ], + [ + 'dummyDate' => [ + 'after' => '2015-04-05', + ], + ], + ], + 'after but not equals' => [ + [ + 'dummyDate' => null, + ], + [ + 'dummyDate' => [ + 'strictly_after' => '2015-04-05', + ], + ], + ], + 'before (all properties enabled)' => [ + null, + [ + 'dummyDate' => [ + 'before' => '2015-04-05', + ], + ], + ], + 'before but not equals (all properties enabled)' => [ + null, + [ + 'dummyDate' => [ + 'strictly_before' => '2015-04-05', + ], + ], + ], + 'before' => [ + [ + 'dummyDate' => null, + ], + [ + 'dummyDate' => [ + 'before' => '2015-04-05', + ], + ], + ], + 'before but not equals' => [ + [ + 'dummyDate' => null, + ], + [ + 'dummyDate' => [ + 'strictly_before' => '2015-04-05', + ], + ], + ], + 'before + after (all properties enabled)' => [ + null, + [ + 'dummyDate' => [ + 'after' => '2015-04-05', + 'before' => '2015-04-05', + ], + ], + ], + 'before but not equals + after but not equals (all properties enabled)' => [ + null, + [ + 'dummyDate' => [ + 'strictly_after' => '2015-04-05', + 'strictly_before' => '2015-04-05', + ], + ], + ], + 'before + after' => [ + [ + 'dummyDate' => null, + ], + [ + 'dummyDate' => [ + 'after' => '2015-04-05', + 'before' => '2015-04-05', + ], + ], + ], + 'before but not equals + after but not equals' => [ + [ + 'dummyDate' => null, + ], + [ + 'dummyDate' => [ + 'strictly_after' => '2015-04-05', + 'strictly_before' => '2015-04-05', + ], + ], + ], + 'property not enabled' => [ + [ + 'unknown' => null, + ], + [ + 'dummyDate' => [ + 'after' => '2015-04-05', + 'before' => '2015-04-05', + ], + ], + ], + 'nested property' => [ + [ + 'relatedDummy.dummyDate' => null, + ], + [ + 'relatedDummy.dummyDate' => [ + 'after' => '2015-04-05', + ], + ], + ], + 'after (exclude_null)' => [ + [ + 'dummyDate' => 'exclude_null', + ], + [ + 'dummyDate' => [ + 'after' => '2015-04-05', + ], + ], + ], + 'after (include_null_after)' => [ + [ + 'dummyDate' => 'include_null_after', + ], + [ + 'dummyDate' => [ + 'after' => '2015-04-05', + ], + ], + ], + 'include null before and after (include_null_before_and_after)' => [ + [ + 'dummyDate' => 'include_null_before_and_after', + ], + [ + 'dummyDate' => [ + 'after' => '2015-04-05', + ], + ], + ], + 'bad date format' => [ + [ + 'dummyDate' => null, + ], + [ + 'dummyDate' => [ + 'after' => '1932iur123ufqe', + 'before' => [], + ], + ], + ], + ]; + } +} diff --git a/Tests/Filter/ExistsFilterTest.php b/Tests/Filter/ExistsFilterTest.php index eb2e30a..9f3f345 100644 --- a/Tests/Filter/ExistsFilterTest.php +++ b/Tests/Filter/ExistsFilterTest.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Doctrine\Orm\Tests\Filter; -use ApiPlatform\Doctrine\Common\Tests\Filter\ExistsFilterTestTrait; use ApiPlatform\Doctrine\Orm\Filter\ExistsFilter; use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase; use ApiPlatform\Doctrine\Orm\Tests\Fixtures\CustomConverter; diff --git a/Tests/Filter/ExistsFilterTestTrait.php b/Tests/Filter/ExistsFilterTestTrait.php new file mode 100644 index 0000000..1fcf2d5 --- /dev/null +++ b/Tests/Filter/ExistsFilterTestTrait.php @@ -0,0 +1,280 @@ + + * + * 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\Doctrine\Orm\Tests\Filter; + +/** + * @author Antoine Bluchet + */ +trait ExistsFilterTestTrait +{ + public function testGetDescription(): void + { + $filter = $this->buildFilter(['name' => null, 'description' => null]); + + $this->assertEquals([ + 'exists[description]' => [ + 'property' => 'description', + 'type' => 'bool', + 'required' => false, + ], + ], $filter->getDescription($this->resourceClass)); + } + + private static function provideApplyTestArguments(): array + { + return [ + 'valid values' => [ + [ + 'description' => null, + ], + [ + 'exists' => [ + 'description' => 'true', + ], + ], + ], + + 'valid values (empty for true)' => [ + [ + 'description' => null, + ], + [ + 'exists' => [ + 'description' => '', + ], + ], + ], + + 'valid values (1 for true)' => [ + [ + 'description' => null, + ], + [ + 'exists' => [ + 'description' => '1', + ], + ], + ], + + 'invalid values' => [ + [ + 'description' => null, + ], + [ + 'exists' => [ + 'description' => 'invalid', + ], + ], + ], + + 'negative values' => [ + [ + 'description' => null, + ], + [ + 'exists' => [ + 'description' => 'false', + ], + ], + ], + + 'negative values (0)' => [ + [ + 'description' => null, + ], + [ + 'exists' => [ + 'description' => '0', + ], + ], + ], + + 'multiple values (true and true)' => [ + [ + 'alias' => null, + 'description' => null, + ], + [ + 'exists' => [ + 'alias' => 'true', + 'description' => 'true', + ], + ], + ], + + 'multiple values (1 and 0)' => [ + [ + 'alias' => null, + 'description' => null, + ], + [ + 'exists' => [ + 'alias' => '1', + 'description' => '0', + ], + ], + ], + + 'multiple values (false and 0)' => [ + [ + 'alias' => null, + 'description' => null, + ], + [ + 'exists' => [ + 'alias' => 'false', + 'description' => '0', + ], + ], + ], + + 'custom exists parameter name' => [ + [ + 'alias' => null, + 'description' => null, + ], + [ + 'exists' => [ + 'alias' => 'true', + ], + 'customExists' => [ + 'description' => 'true', + ], + ], + ], + + 'related values' => [ + [ + 'description' => null, + 'relatedDummy.name' => null, + ], + [ + 'exists' => [ + 'description' => '1', + 'relatedDummy.name' => '1', + ], + ], + ], + + 'not nullable values' => [ + [ + 'description' => null, + 'name' => null, + ], + [ + 'exists' => [ + 'description' => '1', + 'name' => '0', + ], + ], + ], + + 'related collection not empty' => [ + [ + 'description' => null, + 'relatedDummies' => null, + ], + [ + 'exists' => [ + 'description' => '1', + 'relatedDummies' => '1', + ], + ], + ], + + 'related collection empty' => [ + [ + 'description' => null, + 'relatedDummies' => null, + ], + [ + 'exists' => [ + 'description' => '1', + 'relatedDummies' => '0', + ], + ], + ], + + 'related association exists' => [ + [ + 'description' => null, + 'relatedDummy' => null, + ], + [ + 'exists' => [ + 'description' => '1', + 'relatedDummy' => '1', + ], + ], + ], + + 'related association does not exist' => [ + [ + 'description' => null, + 'relatedDummy' => null, + ], + [ + 'exists' => [ + 'description' => '1', + 'relatedDummy' => '0', + ], + ], + ], + + 'related owned association does not exist' => [ + [ + 'relatedOwnedDummy' => null, + ], + [ + 'exists' => [ + 'relatedOwnedDummy' => '0', + ], + ], + ], + + 'related owned association exists' => [ + [ + 'relatedOwnedDummy' => null, + ], + [ + 'exists' => [ + 'relatedOwnedDummy' => '1', + ], + ], + ], + + 'related owning association does not exist' => [ + [ + 'relatedOwningDummy' => null, + ], + [ + 'exists' => [ + 'relatedOwningDummy' => '0', + ], + ], + ], + + 'related owning association exists' => [ + [ + 'relatedOwningDummy' => null, + ], + [ + 'exists' => [ + 'relatedOwningDummy' => '1', + ], + ], + ], + ]; + } +} diff --git a/Tests/Filter/NumericFilterTest.php b/Tests/Filter/NumericFilterTest.php index d3322ce..d5a4eda 100644 --- a/Tests/Filter/NumericFilterTest.php +++ b/Tests/Filter/NumericFilterTest.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Doctrine\Orm\Tests\Filter; -use ApiPlatform\Doctrine\Common\Tests\Filter\NumericFilterTestTrait; use ApiPlatform\Doctrine\Orm\Filter\NumericFilter; use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase; use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy; diff --git a/Tests/Filter/NumericFilterTestTrait.php b/Tests/Filter/NumericFilterTestTrait.php new file mode 100644 index 0000000..3941852 --- /dev/null +++ b/Tests/Filter/NumericFilterTestTrait.php @@ -0,0 +1,206 @@ + + * + * 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\Doctrine\Orm\Tests\Filter; + +/** + * @author Amrouche Hamza + */ +trait NumericFilterTestTrait +{ + public function testGetDescription(): void + { + $filter = $this->buildFilter([ + 'id' => null, + 'name' => null, + 'foo' => null, + 'dummyBoolean' => null, + ]); + + $this->assertEquals([ + 'id' => [ + 'property' => 'id', + 'type' => 'int', + 'required' => false, + 'is_collection' => false, + ], + 'id[]' => [ + 'property' => 'id', + 'type' => 'int', + 'required' => false, + 'is_collection' => true, + ], + ], $filter->getDescription($this->resourceClass)); + } + + public function testGetDescriptionDefaultFields(): void + { + $filter = $this->buildFilter(); + + $this->assertEquals([ + 'id' => [ + 'property' => 'id', + 'type' => 'int', + 'required' => false, + 'is_collection' => false, + ], + 'id[]' => [ + 'property' => 'id', + 'type' => 'int', + 'required' => false, + 'is_collection' => true, + ], + 'dummyFloat' => [ + 'property' => 'dummyFloat', + 'type' => 'float', + 'required' => false, + 'is_collection' => false, + ], + 'dummyFloat[]' => [ + 'property' => 'dummyFloat', + 'type' => 'float', + 'required' => false, + 'is_collection' => true, + ], + 'dummyPrice' => [ + 'property' => 'dummyPrice', + 'type' => 'string', + 'required' => false, + 'is_collection' => false, + ], + 'dummyPrice[]' => [ + 'property' => 'dummyPrice', + 'type' => 'string', + 'required' => false, + 'is_collection' => true, + ], + ], $filter->getDescription($this->resourceClass)); + } + + private static function provideApplyTestArguments(): array + { + return [ + 'numeric string (positive integer)' => [ + [ + 'id' => null, + 'name' => null, + 'dummyPrice' => null, + ], + [ + 'dummyPrice' => '21', + ], + ], + 'multiple numeric string (positive integer)' => [ + [ + 'id' => null, + 'name' => null, + 'dummyPrice' => null, + ], + [ + 'dummyPrice' => ['21', '22'], + ], + ], + 'multiple numeric string with one invalid property key' => [ + [ + 'id' => null, + 'name' => null, + 'dummyPrice' => null, + ], + [ + 'dummyPrice' => ['invalid' => '21', '22'], + ], + ], + 'multiple numeric string with invalid value keys' => [ + [ + 'id' => null, + 'name' => null, + 'dummyPrice' => null, + ], + [ + 'dummyPrice' => ['invalid' => '21', 'invalid2' => '22'], + ], + ], + 'multiple non-numeric' => [ + [ + 'id' => null, + 'name' => null, + 'dummyPrice' => null, + ], + [ + 'dummyPrice' => ['test', 'invalid'], + ], + ], + 'numeric string (negative integer)' => [ + [ + 'id' => null, + 'name' => null, + 'dummyPrice' => null, + ], + [ + 'dummyPrice' => '-21', + ], + ], + 'non-numeric' => [ + [ + 'id' => null, + ], + [ + 'id' => 'toto', + ], + ], + 'numeric string ("0")' => [ + [ + 'id' => null, + 'name' => null, + 'dummyPrice' => null, + ], + [ + 'dummyPrice' => 0, + ], + ], + 'nested property' => [ + [ + 'id' => null, + 'name' => null, + 'relatedDummy.id' => null, + ], + [ + 'relatedDummy.id' => 0, + ], + ], + 'mixed numeric and non-numeric' => [ + [ + 'id' => null, + 'name' => null, + 'dummyPrice' => null, + ], + [ + 'dummyPrice' => 10, + 'name' => '15toto', + ], + ], + 'mixed numeric, non-numeric and invalid property' => [ + [ + 'id' => null, + 'name' => null, + 'dummyPrice' => null, + ], + [ + 'toto' => 'toto', + 'name' => 'gerard', + 'dummyPrice' => '0', + ], + ], + ]; + } +} diff --git a/Tests/Filter/OrderFilterTest.php b/Tests/Filter/OrderFilterTest.php index c83142c..3838290 100644 --- a/Tests/Filter/OrderFilterTest.php +++ b/Tests/Filter/OrderFilterTest.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Doctrine\Orm\Tests\Filter; -use ApiPlatform\Doctrine\Common\Tests\Filter\OrderFilterTestTrait; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase; use ApiPlatform\Doctrine\Orm\Tests\Fixtures\CustomConverter; diff --git a/Tests/Filter/OrderFilterTestTrait.php b/Tests/Filter/OrderFilterTestTrait.php new file mode 100644 index 0000000..2409ae8 --- /dev/null +++ b/Tests/Filter/OrderFilterTestTrait.php @@ -0,0 +1,338 @@ + + * + * 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\Doctrine\Orm\Tests\Filter; + +/** + * @author Théo FIDRY + * @author Vincent CHALAMON + */ +trait OrderFilterTestTrait +{ + public function testGetDescription(): void + { + $filter = $this->buildFilter(['id' => null, 'name' => null, 'foo' => null]); + + $this->assertEquals([ + 'order[id]' => [ + 'property' => 'id', + 'type' => 'string', + 'required' => false, + 'schema' => [ + 'type' => 'string', + 'enum' => [ + 'asc', + 'desc', + ], + ], + ], + 'order[name]' => [ + 'property' => 'name', + 'type' => 'string', + 'required' => false, + 'schema' => [ + 'type' => 'string', + 'enum' => [ + 'asc', + 'desc', + ], + ], + ], + ], $filter->getDescription($this->resourceClass)); + } + + private static function provideApplyTestArguments(): array + { + return [ + 'valid values' => [ + [ + 'id' => null, + 'name' => null, + ], + [ + 'order' => [ + 'id' => 'asc', + 'name' => 'desc', + ], + ], + ], + 'invalid values' => [ + [ + 'id' => null, + 'name' => null, + ], + [ + 'order' => [ + 'id' => 'asc', + 'name' => 'invalid', + ], + ], + ], + 'valid values (properties not enabled)' => [ + [ + 'id' => null, + 'name' => null, + ], + [ + 'order' => [ + 'id' => 'asc', + 'alias' => 'asc', + ], + ], + ], + 'invalid values (properties not enabled)' => [ + [ + 'id' => null, + 'name' => null, + ], + [ + 'order' => [ + 'id' => 'invalid', + 'name' => 'asc', + 'alias' => 'invalid', + ], + ], + ], + 'invalid property (property not enabled)' => [ + [ + 'id' => null, + 'name' => null, + ], + [ + 'order' => [ + 'unknown' => 'asc', + ], + ], + ], + 'invalid property (property enabled)' => [ + [ + 'id' => null, + 'name' => null, + 'unknown' => null, + ], + [ + 'order' => [ + 'unknown' => 'asc', + ], + ], + ], + 'custom order parameter name' => [ + [ + 'id' => null, + 'name' => null, + ], + [ + 'order' => [ + 'id' => 'asc', + 'name' => 'asc', + ], + 'customOrder' => [ + 'name' => 'desc', + ], + ], + ], + 'valid values (all properties enabled)' => [ + null, + [ + 'order' => [ + 'id' => 'asc', + 'name' => 'asc', + ], + ], + ], + 'nested property' => [ + [ + 'id' => null, + 'name' => null, + 'relatedDummy.symfony' => null, + ], + [ + 'order' => [ + 'id' => 'asc', + 'name' => 'desc', + 'relatedDummy.symfony' => 'desc', + ], + ], + ], + 'empty values with default sort direction' => [ + [ + 'id' => 'asc', + 'name' => 'desc', + ], + [ + 'order' => [ + 'id' => null, + 'name' => null, + ], + ], + ], + 'nulls_smallest (asc)' => [ + [ + 'dummyDate' => [ + 'nulls_comparison' => 'nulls_smallest', + ], + 'name' => null, + ], + [ + 'order' => [ + 'dummyDate' => 'asc', + 'name' => 'desc', + ], + ], + ], + 'nulls_smallest (desc)' => [ + [ + 'dummyDate' => [ + 'nulls_comparison' => 'nulls_smallest', + ], + 'name' => null, + ], + [ + 'order' => [ + 'dummyDate' => 'desc', + 'name' => 'desc', + ], + ], + ], + 'nulls_largest (asc)' => [ + [ + 'dummyDate' => [ + 'nulls_comparison' => 'nulls_largest', + ], + 'name' => null, + ], + [ + 'order' => [ + 'dummyDate' => 'asc', + 'name' => 'desc', + ], + ], + ], + 'nulls_largest (desc)' => [ + [ + 'dummyDate' => [ + 'nulls_comparison' => 'nulls_largest', + ], + 'name' => null, + ], + [ + 'order' => [ + 'dummyDate' => 'desc', + 'name' => 'desc', + ], + ], + ], + 'nulls_always_first (asc)' => [ + [ + 'dummyDate' => [ + 'nulls_comparison' => 'nulls_always_first', + ], + 'name' => null, + ], + [ + 'order' => [ + 'dummyDate' => 'asc', + 'name' => 'desc', + ], + ], + ], + 'nulls_always_first (desc)' => [ + [ + 'dummyDate' => [ + 'nulls_comparison' => 'nulls_always_first', + ], + 'name' => null, + ], + [ + 'order' => [ + 'dummyDate' => 'desc', + 'name' => 'desc', + ], + ], + ], + 'nulls_always_last (asc)' => [ + [ + 'dummyDate' => [ + 'nulls_comparison' => 'nulls_always_last', + ], + 'name' => null, + ], + [ + 'order' => [ + 'dummyDate' => 'asc', + 'name' => 'desc', + ], + ], + ], + 'nulls_always_last (desc)' => [ + [ + 'dummyDate' => [ + 'nulls_comparison' => 'nulls_always_last', + ], + 'name' => null, + ], + [ + 'order' => [ + 'dummyDate' => 'desc', + 'name' => 'desc', + ], + ], + ], + 'not having order should not throw a deprecation (select unchanged)' => [ + [ + 'id' => null, + 'name' => null, + ], + [ + 'name' => 'q', + ], + ], + 'not nullable relation will be a LEFT JOIN' => [ + [ + 'relatedDummy.name' => 'ASC', + ], + [ + 'order' => ['relatedDummy.name' => 'ASC'], + ], + ], + 'embedded' => [ + [ + 'embeddedDummy.dummyName' => 'ASC', + ], + [ + 'order' => ['embeddedDummy.dummyName' => 'ASC'], + ], + ], + 'embedded with nulls_comparison' => [ + [ + 'embeddedDummy.dummyName' => [ + 'nulls_comparison' => 'nulls_largest', + ], + ], + [ + 'order' => [ + 'embeddedDummy.dummyName' => 'ASC', + ], + ], + ], + 'nullable field in relation will be a LEFT JOIN' => [ + [ + 'relatedDummy.name' => null, + ], + [ + 'order' => ['relatedDummy.name' => 'ASC'], + ], + ], + ]; + } +} diff --git a/Tests/Filter/RangeFilterTest.php b/Tests/Filter/RangeFilterTest.php index 8230580..ac31b95 100644 --- a/Tests/Filter/RangeFilterTest.php +++ b/Tests/Filter/RangeFilterTest.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Doctrine\Orm\Tests\Filter; -use ApiPlatform\Doctrine\Common\Tests\Filter\RangeFilterTestTrait; use ApiPlatform\Doctrine\Orm\Filter\RangeFilter; use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase; use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy; diff --git a/Tests/Filter/RangeFilterTestTrait.php b/Tests/Filter/RangeFilterTestTrait.php new file mode 100644 index 0000000..1d2646c --- /dev/null +++ b/Tests/Filter/RangeFilterTestTrait.php @@ -0,0 +1,139 @@ + + * + * 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\Doctrine\Orm\Tests\Filter; + +/** + * @author Lee Siong Chan + */ +trait RangeFilterTestTrait +{ + private static function provideApplyTestArguments(): array + { + return [ + 'between' => [ + null, + [ + 'dummyPrice' => [ + 'between' => '9.99..15.99', + ], + ], + ], + 'between (same values)' => [ + null, + [ + 'dummyPrice' => [ + 'between' => '9.99..9.99', + ], + ], + ], + 'between (too many operands)' => [ + null, + [ + 'dummyPrice' => [ + 'between' => '9.99..15.99..99.99', + ], + ], + ], + 'between (too few operands)' => [ + null, + [ + 'dummyPrice' => [ + 'between' => '15.99', + ], + ], + ], + 'between (non-numeric operands)' => [ + null, + [ + 'dummyPrice' => [ + 'between' => 'abc..def', + ], + ], + ], + 'lt' => [ + null, + [ + 'dummyPrice' => [ + 'lt' => '9.99', + ], + ], + ], + 'lt (non-numeric)' => [ + null, + [ + 'dummyPrice' => [ + 'lt' => '127.0.0.1', + ], + ], + ], + 'lte' => [ + null, + [ + 'dummyPrice' => [ + 'lte' => '9.99', + ], + ], + ], + 'lte (non-numeric)' => [ + null, + [ + 'dummyPrice' => [ + 'lte' => '127.0.0.1', + ], + ], + ], + 'gt' => [ + null, + [ + 'dummyPrice' => [ + 'gt' => '9.99', + ], + ], + ], + 'gt (non-numeric)' => [ + null, + [ + 'dummyPrice' => [ + 'gt' => '127.0.0.1', + ], + ], + ], + 'gte' => [ + null, + [ + 'dummyPrice' => [ + 'gte' => '9.99', + ], + ], + ], + 'gte (non-numeric)' => [ + null, + [ + 'dummyPrice' => [ + 'gte' => '127.0.0.1', + ], + ], + ], + 'lte + gte' => [ + null, + [ + 'dummyPrice' => [ + 'gte' => '9.99', + 'lte' => '19.99', + ], + ], + ], + ]; + } +} diff --git a/Tests/Filter/SearchFilterTest.php b/Tests/Filter/SearchFilterTest.php index 7b81fdc..89f2e3f 100644 --- a/Tests/Filter/SearchFilterTest.php +++ b/Tests/Filter/SearchFilterTest.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Doctrine\Orm\Tests\Filter; -use ApiPlatform\Doctrine\Common\Tests\Filter\SearchFilterTestTrait; use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase; use ApiPlatform\Doctrine\Orm\Tests\Fixtures\CustomConverter; diff --git a/Tests/Filter/SearchFilterTestTrait.php b/Tests/Filter/SearchFilterTestTrait.php new file mode 100644 index 0000000..a538ddb --- /dev/null +++ b/Tests/Filter/SearchFilterTestTrait.php @@ -0,0 +1,517 @@ + + * + * 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\Doctrine\Orm\Tests\Filter; + +/** + * @author Julien Deniau + * @author Vincent CHALAMON + */ +trait SearchFilterTestTrait +{ + public function testGetDescription(): void + { + $filter = $this->buildSearchFilter($this, $this->managerRegistry, [ + 'id' => null, + 'name' => null, + 'alias' => null, + 'dummy' => null, + 'dummyDate' => null, + 'jsonData' => null, + 'arrayData' => null, + 'nameConverted' => null, + 'foo' => null, + 'relatedDummies.dummyDate' => null, + 'relatedDummy' => null, + ]); + + $this->assertEquals([ + 'id' => [ + 'property' => 'id', + 'type' => 'int', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => false, + ], + 'id[]' => [ + 'property' => 'id', + 'type' => 'int', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => true, + ], + 'name' => [ + 'property' => 'name', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => false, + ], + 'name[]' => [ + 'property' => 'name', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => true, + ], + 'alias' => [ + 'property' => 'alias', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => false, + ], + 'alias[]' => [ + 'property' => 'alias', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => true, + ], + 'dummy' => [ + 'property' => 'dummy', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => false, + ], + 'dummy[]' => [ + 'property' => 'dummy', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => true, + ], + 'dummyDate' => [ + 'property' => 'dummyDate', + 'type' => \DateTimeInterface::class, + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => false, + ], + 'dummyDate[]' => [ + 'property' => 'dummyDate', + 'type' => \DateTimeInterface::class, + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => true, + ], + 'jsonData' => [ + 'property' => 'jsonData', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => false, + ], + 'jsonData[]' => [ + 'property' => 'jsonData', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => true, + ], + 'arrayData' => [ + 'property' => 'arrayData', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => false, + ], + 'arrayData[]' => [ + 'property' => 'arrayData', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => true, + ], + 'name_converted' => [ + 'property' => 'name_converted', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => false, + ], + 'name_converted[]' => [ + 'property' => 'name_converted', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => true, + ], + 'relatedDummies.dummyDate' => [ + 'property' => 'relatedDummies.dummyDate', + 'type' => \DateTimeInterface::class, + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => false, + ], + 'relatedDummies.dummyDate[]' => [ + 'property' => 'relatedDummies.dummyDate', + 'type' => \DateTimeInterface::class, + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => true, + ], + 'relatedDummy' => [ + 'property' => 'relatedDummy', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => false, + ], + 'relatedDummy[]' => [ + 'property' => 'relatedDummy', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => true, + ], + ], $filter->getDescription($this->resourceClass)); + } + + private static function provideApplyTestArguments(): array + { + return [ + 'exact' => [ + [ + 'id' => null, + 'name' => null, + ], + [ + 'name' => 'exact', + ], + ], + 'exact (case insensitive)' => [ + [ + 'id' => null, + 'name' => 'iexact', + ], + [ + 'name' => 'exact', + ], + ], + 'exact (case insensitive, with special characters)' => [ + [ + 'id' => null, + 'name' => 'iexact', + ], + [ + 'name' => 'exact (special)', + ], + ], + 'exact (multiple values)' => [ + [ + 'id' => null, + 'name' => 'exact', + ], + [ + 'name' => [ + 'CaSE', + 'SENSitive', + ], + ], + ], + 'exact (multiple values; case insensitive)' => [ + [ + 'id' => null, + 'name' => 'iexact', + ], + [ + 'name' => [ + 'CaSE', + 'inSENSitive', + ], + ], + ], + 'invalid property' => [ + [ + 'id' => null, + 'name' => null, + ], + [ + 'foo' => 'exact', + ], + ], + 'invalid values for relations' => [ + [ + 'id' => null, + 'name' => null, + 'relatedDummy' => null, + 'relatedDummies' => null, + ], + [ + 'name' => ['foo'], + 'relatedDummy' => ['foo'], + 'relatedDummies' => [['foo']], + ], + ], + 'partial' => [ + [ + 'id' => null, + 'name' => 'partial', + ], + [ + 'name' => 'partial', + ], + ], + 'partial (case insensitive)' => [ + [ + 'id' => null, + 'name' => 'ipartial', + ], + [ + 'name' => 'partial', + ], + ], + 'partial (multiple values)' => [ + [ + 'id' => null, + 'name' => 'partial', + ], + [ + 'name' => [ + 'CaSE', + 'SENSitive', + ], + ], + ], + 'partial (multiple values; case insensitive)' => [ + [ + 'id' => null, + 'name' => 'ipartial', + ], + [ + 'name' => [ + 'CaSE', + 'inSENSitive', + ], + ], + ], + 'partial (multiple almost same values; case insensitive)' => [ + [ + 'id' => null, + 'name' => 'ipartial', + ], + [ + 'name' => [ + 'blue car', + 'Blue Car', + ], + ], + ], + 'start' => [ + [ + 'id' => null, + 'name' => 'start', + ], + [ + 'name' => 'partial', + ], + ], + 'start (case insensitive)' => [ + [ + 'id' => null, + 'name' => 'istart', + ], + [ + 'name' => 'partial', + ], + ], + 'start (multiple values)' => [ + [ + 'id' => null, + 'name' => 'start', + ], + [ + 'name' => [ + 'CaSE', + 'SENSitive', + ], + ], + ], + 'start (multiple values; case insensitive)' => [ + [ + 'id' => null, + 'name' => 'istart', + ], + [ + 'name' => [ + 'CaSE', + 'inSENSitive', + ], + ], + ], + 'end' => [ + [ + 'id' => null, + 'name' => 'end', + ], + [ + 'name' => 'partial', + ], + ], + 'end (case insensitive)' => [ + [ + 'id' => null, + 'name' => 'iend', + ], + [ + 'name' => 'partial', + ], + ], + 'end (multiple values)' => [ + [ + 'id' => null, + 'name' => 'end', + ], + [ + 'name' => [ + 'CaSE', + 'SENSitive', + ], + ], + ], + 'end (multiple values; case insensitive)' => [ + [ + 'id' => null, + 'name' => 'iend', + ], + [ + 'name' => [ + 'CaSE', + 'inSENSitive', + ], + ], + ], + 'word_start' => [ + [ + 'id' => null, + 'name' => 'word_start', + ], + [ + 'name' => 'partial', + ], + ], + 'word_start (case insensitive)' => [ + [ + 'id' => null, + 'name' => 'iword_start', + ], + [ + 'name' => 'partial', + ], + ], + 'word_start (multiple values)' => [ + [ + 'id' => null, + 'name' => 'word_start', + ], + [ + 'name' => [ + 'CaSE', + 'SENSitive', + ], + ], + ], + 'word_start (multiple values; case insensitive)' => [ + [ + 'id' => null, + 'name' => 'iword_start', + ], + [ + 'name' => [ + 'CaSE', + 'inSENSitive', + ], + ], + ], + 'invalid value for relation' => [ + [ + 'id' => null, + 'name' => null, + 'relatedDummy' => null, + ], + [ + 'relatedDummy' => 'exact', + ], + ], + 'IRI value for relation' => [ + [ + 'id' => null, + 'name' => null, + 'relatedDummy.id' => null, + ], + [ + 'relatedDummy.id' => '/related_dummies/1', + ], + ], + 'mixed IRI and entity ID values for relations' => [ + [ + 'id' => null, + 'name' => null, + 'relatedDummy' => null, + 'relatedDummies' => null, + ], + [ + 'relatedDummy' => ['/related_dummies/1', '2'], + 'relatedDummies' => '1', + ], + ], + 'nested property' => [ + [ + 'id' => null, + 'name' => null, + 'relatedDummy.symfony' => null, + ], + [ + 'name' => 'exact', + 'relatedDummy.symfony' => 'exact', + ], + ], + 'empty nested property' => [ + [ + 'relatedDummy.symfony' => null, + ], + [ + 'relatedDummy.symfony' => [], + ], + ], + 'integer value' => [ + [ + 'age' => 'exact', + ], + [ + 'age' => 46, + ], + ], + 'related owned one-to-one association' => [ + [ + 'id' => null, + 'relatedOwnedDummy' => null, + ], + [ + 'relatedOwnedDummy' => 1, + ], + ], + 'related owning one-to-one association' => [ + [ + 'id' => null, + 'relatedOwningDummy' => null, + ], + [ + 'relatedOwningDummy' => 1, + ], + ], + ]; + } +} diff --git a/Tests/config.yml b/Tests/config.yml index 120b389..700332d 100644 --- a/Tests/config.yml +++ b/Tests/config.yml @@ -17,15 +17,6 @@ doctrine: prefix: ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity alias: App -api_platform: - formats: - json: ['application/json'] - doctrine: true - keep_legacy_inflector: false - mapping: - paths: - - '%kernel.project_dir%/Fixtures/Entity' - services: test.property_accessor: alias: property_accessor diff --git a/composer.json b/composer.json index 982e56f..58a490d 100644 --- a/composer.json +++ b/composer.json @@ -1,88 +1,71 @@ { - "name": "api-platform/doctrine-orm", - "description": "Doctrine ORM bridge", - "type": "library", - "keywords": [ - "Doctrine", - "ORM" - ], - "homepage": "https://api-platform.com", - "license": "MIT", - "authors": [ - { - "name": "Kévin Dunglas", - "email": "kevin@dunglas.fr", - "homepage": "https://dunglas.fr" + "name": "api-platform/doctrine-orm", + "description": "Doctrine ORM bridge", + "type": "library", + "keywords": [ + "Doctrine", + "ORM" + ], + "homepage": "https://api-platform.com", + "license": "MIT", + "authors": [ + { + "name": "Kévin Dunglas", + "email": "kevin@dunglas.fr", + "homepage": "https://dunglas.fr" + }, + { + "name": "API Platform Community", + "homepage": "https://api-platform.com/community/contributors" + } + ], + "require": { + "php": ">=8.1", + "api-platform/doctrine-common": "*@dev || ^3.1", + "api-platform/metadata": "*@dev || ^3.1", + "api-platform/state": "*@dev || ^3.1", + "doctrine/doctrine-bundle": "^2.11", + "doctrine/orm": "^2.17", + "symfony/property-info": "^6.4 || ^7.0" }, - { - "name": "API Platform Community", - "homepage": "https://api-platform.com/community/contributors" - } - ], - "require": { - "php": ">=8.1", - "api-platform/doctrine-common": "*@dev || ^3.1", - "api-platform/metadata": "*@dev || ^3.1", - "api-platform/state": "*@dev || ^3.1", - "doctrine/doctrine-bundle": "^2.11", - "doctrine/orm": "^2.17", - "symfony/property-info": "^6.4 || ^7.0" - }, - "require-dev": { - "api-platform/parameter-validator": "*@dev || ^3.2", - "api-platform/symfony": "*@dev || ^3.2", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^10.0", - "ramsey/uuid": "^4.7", - "ramsey/uuid-doctrine": "^2.0", - "symfony/cache": "^6.4 || ^7.0", - "symfony/framework-bundle": "^6.4 || ^7.0", - "symfony/phpunit-bridge": "^6.4 || ^7.0", - "symfony/serializer": "^6.4 || ^7.0", - "symfony/yaml": "^6.4 || ^7.0" - }, - "autoload": { - "psr-4": { - "ApiPlatform\\Doctrine\\Orm\\": "" - } - }, - "config": { - "preferred-install": { - "*": "dist" + "require-dev": { + "api-platform/parameter-validator": "*@dev || ^3.2", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^10.0", + "ramsey/uuid": "^4.7", + "ramsey/uuid-doctrine": "^2.0", + "symfony/cache": "^6.4 || ^7.0", + "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/phpunit-bridge": "^6.4 || ^7.0", + "symfony/property-access": "^6.4 || ^7.0", + "symfony/serializer": "^6.4 || ^7.0", + "symfony/uid": "^6.4 || ^7.0", + "symfony/validator": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0" }, - "sort-packages": true, - "allow-plugins": { - "php-http/discovery": false - } - }, - "extra": { - "branch-alias": { - "dev-main": "3.2.x-dev" + "autoload": { + "psr-4": { + "ApiPlatform\\Doctrine\\Orm\\": "" + } + }, + "config": { + "preferred-install": { + "*": "dist" + }, + "sort-packages": true, + "allow-plugins": { + "php-http/discovery": false + } + }, + "extra": { + "branch-alias": { + "dev-main": "3.3.x-dev" + }, + "symfony": { + "require": "^6.4" + } }, - "symfony": { - "require": "^6.4" + "scripts": { + "test": "./vendor/bin/phpunit" } - }, - "repositories": [ - { - "type": "path", - "url": "../../Metadata" - }, - { - "type": "path", - "url": "../../State" - }, - { - "type": "path", - "url": "../Common" - }, - { - "type": "path", - "url": "../../Symfony" - }, - { - "type": "path", - "url": "../../ParameterValidator" - } - ] } From 8ade0bcfe64deb13b2dea6e94098fb319c22d117 Mon Sep 17 00:00:00 2001 From: Vincent <407859+vincentchalamon@users.noreply.github.com> Date: Mon, 4 Mar 2024 17:46:10 +0100 Subject: [PATCH 07/72] feat: improve ApiProperty::security using property name (#5853) * feat: improve ApiProperty::security using property name * docs: update guide * fix: code-selector --- Filter/OrderFilter.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Filter/OrderFilter.php b/Filter/OrderFilter.php index bf40b54..1987ee2 100644 --- a/Filter/OrderFilter.php +++ b/Filter/OrderFilter.php @@ -110,7 +110,8 @@ * * By default, whenever the query does not specify the direction explicitly (e.g.: `/books?order[title]&order[id]`), filters will not be applied unless you configure a default order direction to use: * - * [codeSelector] + *
+ * * ```php * * * ``` - * [/codeSelector] + * + *
* * When the property used for ordering can contain `null` values, you may want to specify how `null` values are treated in the comparison: * - Use the default behavior of the DBMS: use `null` strategy From a91ae37433ca955a218c663065d56bf6291c83f4 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Tue, 5 Mar 2024 09:48:15 +0100 Subject: [PATCH 08/72] fix(doctrine): isAssociationInverseSide before getMappedBy (#6197) --- Metadata/Resource/DoctrineOrmLinkFactory.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Metadata/Resource/DoctrineOrmLinkFactory.php b/Metadata/Resource/DoctrineOrmLinkFactory.php index ba3dcc3..1922ccd 100644 --- a/Metadata/Resource/DoctrineOrmLinkFactory.php +++ b/Metadata/Resource/DoctrineOrmLinkFactory.php @@ -66,8 +66,16 @@ public function createLinksFromRelations(Metadata $operation): array continue; } + if (!$doctrineMetadata->isAssociationInverseSide($property)) { + continue; + } + + if (!($mappedBy = $doctrineMetadata->getAssociationMappedByTargetField($property))) { + continue; + } + $relationClass = $doctrineMetadata->getAssociationTargetClass($property); - if (!($mappedBy = $doctrineMetadata->getAssociationMappedByTargetField($property)) || !$this->resourceClassResolver->isResourceClass($relationClass)) { + if (!$this->resourceClassResolver->isResourceClass($relationClass)) { continue; } From 3077731073638fb5b4773f9ebddc13a1f9eb7747 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Tue, 5 Mar 2024 11:16:34 +0100 Subject: [PATCH 09/72] fix(doctrine): doctrine/orm:^3.0 support (#6193) * feat: add compatibility with doctrine/orm 3.0 * attempt to fix lowest --------- Co-authored-by: David de Boer --- AbstractPaginator.php | 2 +- Extension/EagerLoadingExtension.php | 5 ++-- Extension/FilterEagerLoadingExtension.php | 6 ++-- Filter/ExistsFilter.php | 29 ++++++++++++++++--- .../DoctrineOrmPropertyMetadataFactory.php | 14 +++++---- State/LinksHandlerTrait.php | 6 ++-- 6 files changed, 43 insertions(+), 19 deletions(-) diff --git a/AbstractPaginator.php b/AbstractPaginator.php index 3c3fa42..5324bf1 100644 --- a/AbstractPaginator.php +++ b/AbstractPaginator.php @@ -32,7 +32,7 @@ public function __construct(DoctrinePaginator $paginator) { $query = $paginator->getQuery(); - if (null === ($firstResult = $query->getFirstResult()) || null === $maxResults = $query->getMaxResults()) { // @phpstan-ignore-line + if (null === ($firstResult = $query->getFirstResult()) || $firstResult < 0 || null === $maxResults = $query->getMaxResults()) { // @phpstan-ignore-line throw new InvalidArgumentException(sprintf('"%1$s::setFirstResult()" or/and "%1$s::setMaxResults()" was/were not applied to the query.', Query::class)); } diff --git a/Extension/EagerLoadingExtension.php b/Extension/EagerLoadingExtension.php index 49f63b4..e04bf2e 100644 --- a/Extension/EagerLoadingExtension.php +++ b/Extension/EagerLoadingExtension.php @@ -23,7 +23,6 @@ use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\Query\Expr\Select; use Doctrine\ORM\QueryBuilder; @@ -137,9 +136,9 @@ private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInt if ( // Always skip extra lazy associations - ClassMetadataInfo::FETCH_EXTRA_LAZY === $mapping['fetch'] + ClassMetadata::FETCH_EXTRA_LAZY === $mapping['fetch'] // We don't want to interfere with doctrine on this association - || (false === $forceEager && ClassMetadataInfo::FETCH_EAGER !== $mapping['fetch']) + || (false === $forceEager && ClassMetadata::FETCH_EAGER !== $mapping['fetch']) ) { continue; } diff --git a/Extension/FilterEagerLoadingExtension.php b/Extension/FilterEagerLoadingExtension.php index 31170db..daba5d9 100644 --- a/Extension/FilterEagerLoadingExtension.php +++ b/Extension/FilterEagerLoadingExtension.php @@ -19,7 +19,7 @@ use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\ResourceClassResolverInterface; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; @@ -111,12 +111,12 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator * * @param array $checked array cache of tested metadata classes */ - private function hasFetchEagerAssociation(EntityManagerInterface $em, ClassMetadataInfo $classMetadata, array &$checked = []): bool + private function hasFetchEagerAssociation(EntityManagerInterface $em, ClassMetadata $classMetadata, array &$checked = []): bool { $checked[] = $classMetadata->name; foreach ($classMetadata->getAssociationMappings() as $mapping) { - if (ClassMetadataInfo::FETCH_EAGER === $mapping['fetch']) { + if (ClassMetadata::FETCH_EAGER === $mapping['fetch']) { return true; } diff --git a/Filter/ExistsFilter.php b/Filter/ExistsFilter.php index db0db4b..5f516c4 100644 --- a/Filter/ExistsFilter.php +++ b/Filter/ExistsFilter.php @@ -18,7 +18,10 @@ use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Metadata\Operation; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\AssociationMapping; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ManyToManyOwningSideMapping; +use Doctrine\ORM\Mapping\ToOneOwningSideMapping; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; @@ -199,7 +202,7 @@ protected function isNullableField(string $property, string $resourceClass): boo if ($metadata->hasAssociation($field)) { if ($metadata->isSingleValuedAssociation($field)) { - if (!($metadata instanceof ClassMetadataInfo)) { + if (!($metadata instanceof ClassMetadata)) { return false; } @@ -211,7 +214,7 @@ protected function isNullableField(string $property, string $resourceClass): boo return true; } - if ($metadata instanceof ClassMetadataInfo && $metadata->hasField($field)) { + if ($metadata instanceof ClassMetadata && $metadata->hasField($field)) { return $metadata->isNullable($field); } @@ -223,8 +226,26 @@ protected function isNullableField(string $property, string $resourceClass): boo * * @see https://github.com/doctrine/doctrine2/blob/v2.5.4/lib/Doctrine/ORM/Tools/EntityGenerator.php#L1221-L1246 */ - private function isAssociationNullable(array $associationMapping): bool + private function isAssociationNullable(AssociationMapping|array $associationMapping): bool { + if ($associationMapping instanceof AssociationMapping) { + if (!empty($associationMapping->id)) { + return false; + } + + if ($associationMapping instanceof ToOneOwningSideMapping || $associationMapping instanceof ManyToManyOwningSideMapping) { + foreach ($associationMapping->joinColumns as $joinColumn) { + if (false === $joinColumn->nullable) { + return false; + } + } + + return true; + } + + return true; + } + if (!empty($associationMapping['id'])) { return false; } diff --git a/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php b/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php index d3e4816..c450e85 100644 --- a/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php +++ b/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php @@ -15,7 +15,8 @@ use ApiPlatform\Metadata\ApiProperty; use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\FieldMapping; use Doctrine\Persistence\ManagerRegistry; /** @@ -55,7 +56,7 @@ public function create(string $resourceClass, string $property, array $options = break; } - if ($doctrineClassMetadata instanceof ClassMetadataInfo) { + if ($doctrineClassMetadata instanceof ClassMetadata) { $writable = $doctrineClassMetadata->isIdentifierNatural(); } else { $writable = false; @@ -67,10 +68,13 @@ public function create(string $resourceClass, string $property, array $options = } } - if ($doctrineClassMetadata instanceof ClassMetadataInfo && \in_array($property, $doctrineClassMetadata->getFieldNames(), true)) { - /** @var mixed[] */ + if ($doctrineClassMetadata instanceof ClassMetadata && \in_array($property, $doctrineClassMetadata->getFieldNames(), true)) { $fieldMapping = $doctrineClassMetadata->getFieldMapping($property); - $propertyMetadata = $propertyMetadata->withDefault($fieldMapping['options']['default'] ?? $propertyMetadata->getDefault()); + if (class_exists(FieldMapping::class) && $fieldMapping instanceof FieldMapping) { + $propertyMetadata = $propertyMetadata->withDefault($fieldMapping->default ?? $propertyMetadata->getDefault()); + } else { + $propertyMetadata = $propertyMetadata->withDefault($fieldMapping['options']['default'] ?? $propertyMetadata->getDefault()); + } } return $propertyMetadata; diff --git a/State/LinksHandlerTrait.php b/State/LinksHandlerTrait.php index 585195d..9c9f268 100644 --- a/State/LinksHandlerTrait.php +++ b/State/LinksHandlerTrait.php @@ -17,7 +17,7 @@ use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Operation; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\QueryBuilder; /** @@ -83,7 +83,7 @@ private function handleLinks(QueryBuilder $queryBuilder, array $identifiers, Que $associationMapping = $fromClassMetadata->getAssociationMapping($link->getFromProperty()); // @phpstan-ignore-line $relationType = $associationMapping['type']; - if ($relationType & ClassMetadataInfo::TO_MANY) { + if ($relationType & ClassMetadata::TO_MANY) { $nextAlias = $queryNameGenerator->generateJoinAlias($alias); $whereClause = []; foreach ($identifierProperties as $identifierProperty) { @@ -100,7 +100,7 @@ private function handleLinks(QueryBuilder $queryBuilder, array $identifiers, Que } // A single-valued association path expression to an inverse side is not supported in DQL queries. - if ($relationType & ClassMetadataInfo::TO_ONE && !($associationMapping['isOwningSide'] ?? true)) { + if ($relationType & ClassMetadata::TO_ONE && !($associationMapping['isOwningSide'] ?? true)) { $queryBuilder->innerJoin("$previousAlias.".$associationMapping['mappedBy'], $joinAlias); } else { $queryBuilder->join( From 8cb2d64e0d06e7a79efa8ba809442007ac223301 Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 21 Mar 2024 09:55:29 +0100 Subject: [PATCH 10/72] chore: doctrine deprecations --- Tests/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/config.yml b/Tests/config.yml index 700332d..e3e948b 100644 --- a/Tests/config.yml +++ b/Tests/config.yml @@ -8,6 +8,8 @@ doctrine: orm: auto_generate_proxy_classes: '%kernel.debug%' + controller_resolver: + auto_mapping: true enable_lazy_ghost_objects: true mappings: ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity: From 2097de40be5c81bf5e26112b1266a8f5fe0b403b Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 21 Mar 2024 11:24:31 +0100 Subject: [PATCH 11/72] test: decorate name converter as symfony interface changed --- Tests/Fixtures/CustomConverter.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Tests/Fixtures/CustomConverter.php b/Tests/Fixtures/CustomConverter.php index af1617c..956eca7 100644 --- a/Tests/Fixtures/CustomConverter.php +++ b/Tests/Fixtures/CustomConverter.php @@ -14,20 +14,28 @@ namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; /** * Custom converter that will only convert a property named "nameConverted" * with the same logic as Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter. */ -class CustomConverter extends CamelCaseToSnakeCaseNameConverter +class CustomConverter implements NameConverterInterface { + private NameConverterInterface $nameConverter; + + public function __construct() + { + $this->nameConverter = new CamelCaseToSnakeCaseNameConverter(); + } + public function normalize(string $propertyName): string { - return 'nameConverted' === $propertyName ? parent::normalize($propertyName) : $propertyName; + return 'nameConverted' === $propertyName ? $this->nameConverter->normalize($propertyName) : $propertyName; } public function denormalize(string $propertyName): string { - return 'name_converted' === $propertyName ? parent::denormalize($propertyName) : $propertyName; + return 'name_converted' === $propertyName ? $this->nameConverter->denormalize($propertyName) : $propertyName; } } From a12063a5ac5b8683fcdfab3e92db665739523cad Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 29 Mar 2024 15:02:44 +0100 Subject: [PATCH 12/72] fix(doctrine): wrong return type without name converter #6079 (#6254) --- Filter/AbstractFilter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Filter/AbstractFilter.php b/Filter/AbstractFilter.php index c1deb1c..0ad569e 100644 --- a/Filter/AbstractFilter.php +++ b/Filter/AbstractFilter.php @@ -80,7 +80,7 @@ protected function isPropertyEnabled(string $property, string $resourceClass): b protected function denormalizePropertyName(string|int $property): string { if (!$this->nameConverter instanceof NameConverterInterface) { - return $property; + return (string) $property; } return implode('.', array_map($this->nameConverter->denormalize(...), explode('.', (string) $property))); From c1a7a24ea957b5498d6edf6ab90f5e0da0d53662 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 29 Mar 2024 17:11:24 +0100 Subject: [PATCH 13/72] feat(doctrine): parameter filter extension (#6248) * feat(doctrine): parameter filtering * feat(graphql): parameter graphql arguments --- Extension/ParameterExtension.php | 70 ++++++++++++++++++++++++++++++++ Filter/AbstractFilter.php | 11 ++++- 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 Extension/ParameterExtension.php diff --git a/Extension/ParameterExtension.php b/Extension/ParameterExtension.php new file mode 100644 index 0000000..792f311 --- /dev/null +++ b/Extension/ParameterExtension.php @@ -0,0 +1,70 @@ + + * + * 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\Doctrine\Orm\Extension; + +use ApiPlatform\Doctrine\Orm\Filter\FilterInterface; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Operation; +use Doctrine\ORM\QueryBuilder; +use Psr\Container\ContainerInterface; + +/** + * Reads operation parameters and execute its filter. + * + * @author Antoine Bluchet + */ +final class ParameterExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface +{ + public function __construct(private readonly ContainerInterface $filterLocator) + { + } + + /** + * @param array $context + */ + private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void + { + foreach ($operation->getParameters() ?? [] as $parameter) { + $values = $parameter->getExtraProperties()['_api_values'] ?? []; + if (!$values) { + continue; + } + + if (null === ($filterId = $parameter->getFilter())) { + continue; + } + + $filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null; + if ($filter instanceof FilterInterface) { + $filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $values] + $context); + } + } + } + + /** + * {@inheritdoc} + */ + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void + { + $this->applyFilter($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); + } + + /** + * {@inheritdoc} + */ + public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, ?Operation $operation = null, array $context = []): void + { + $this->applyFilter($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); + } +} diff --git a/Filter/AbstractFilter.php b/Filter/AbstractFilter.php index 0ad569e..2e258af 100644 --- a/Filter/AbstractFilter.php +++ b/Filter/AbstractFilter.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Orm\Filter; +use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface; use ApiPlatform\Doctrine\Common\PropertyHelperTrait; use ApiPlatform\Doctrine\Orm\PropertyHelperTrait as OrmPropertyHelperTrait; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; @@ -23,7 +24,7 @@ use Psr\Log\NullLogger; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -abstract class AbstractFilter implements FilterInterface +abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface { use OrmPropertyHelperTrait; use PropertyHelperTrait; @@ -64,6 +65,14 @@ protected function getLogger(): LoggerInterface return $this->logger; } + /** + * @param string[] $properties + */ + public function setProperties(array $properties): void + { + $this->properties = $properties; + } + /** * Determines whether the given property is enabled. */ From 7fc1f6edc5b72c32e83d4d481795f423a14e2ee9 Mon Sep 17 00:00:00 2001 From: Stephan Vierkant Date: Fri, 31 May 2024 11:04:16 +0200 Subject: [PATCH 14/72] fix(metadata): add some phpdoc annotations to ORM (#6387) --- .../QueryCollectionExtensionInterface.php | 4 +++ Extension/QueryItemExtensionInterface.php | 5 ++++ Filter/AbstractFilter.php | 3 ++ Tests/Extension/PaginationExtensionTest.php | 28 +++++++++---------- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/Extension/QueryCollectionExtensionInterface.php b/Extension/QueryCollectionExtensionInterface.php index 191c2ac..d58c08d 100644 --- a/Extension/QueryCollectionExtensionInterface.php +++ b/Extension/QueryCollectionExtensionInterface.php @@ -25,5 +25,9 @@ */ interface QueryCollectionExtensionInterface { + /** + * @param class-string $resourceClass + * @param array $context + */ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void; } diff --git a/Extension/QueryItemExtensionInterface.php b/Extension/QueryItemExtensionInterface.php index 4d7aaed..ddbbb04 100644 --- a/Extension/QueryItemExtensionInterface.php +++ b/Extension/QueryItemExtensionInterface.php @@ -25,5 +25,10 @@ */ interface QueryItemExtensionInterface { + /** + * @param class-string $resourceClass + * @param array $identifiers + * @param array $context + */ public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, ?Operation $operation = null, array $context = []): void; } diff --git a/Filter/AbstractFilter.php b/Filter/AbstractFilter.php index 2e258af..4ec7046 100644 --- a/Filter/AbstractFilter.php +++ b/Filter/AbstractFilter.php @@ -47,6 +47,9 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q /** * Passes a property through the filter. + * + * @param class-string $resourceClass + * @param array $context */ abstract protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void; diff --git a/Tests/Extension/PaginationExtensionTest.php b/Tests/Extension/PaginationExtensionTest.php index 5c6b3e7..64e505c 100644 --- a/Tests/Extension/PaginationExtensionTest.php +++ b/Tests/Extension/PaginationExtensionTest.php @@ -67,7 +67,7 @@ public function testApplyToCollection(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), 'Foo', new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationItemsPerPage: 40), ['filters' => ['pagination' => true, 'itemsPerPage' => 20, '_page' => 2]]); + $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationItemsPerPage: 40), ['filters' => ['pagination' => true, 'itemsPerPage' => 20, '_page' => 2]]); } public function testApplyToCollectionWithItemPerPageZero(): void @@ -86,7 +86,7 @@ public function testApplyToCollectionWithItemPerPageZero(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), 'Foo', new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationItemsPerPage: 0), ['filters' => ['pagination' => true, 'itemsPerPage' => 0, '_page' => 1]]); + $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationItemsPerPage: 0), ['filters' => ['pagination' => true, 'itemsPerPage' => 0, '_page' => 1]]); } public function testApplyToCollectionWithItemPerPageZeroAndPage2(): void @@ -108,7 +108,7 @@ public function testApplyToCollectionWithItemPerPageZeroAndPage2(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), 'Foo', new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationItemsPerPage: 0), ['filters' => ['pagination' => true, 'itemsPerPage' => 0, '_page' => 2]]); + $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationItemsPerPage: 0), ['filters' => ['pagination' => true, 'itemsPerPage' => 0, '_page' => 2]]); } public function testApplyToCollectionWithItemPerPageLessThan0(): void @@ -130,7 +130,7 @@ public function testApplyToCollectionWithItemPerPageLessThan0(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), 'Foo', new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationItemsPerPage: -20), ['filters' => ['pagination' => true, 'itemsPerPage' => -20, '_page' => 2]]); + $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationItemsPerPage: -20), ['filters' => ['pagination' => true, 'itemsPerPage' => -20, '_page' => 2]]); } public function testApplyToCollectionWithItemPerPageTooHigh(): void @@ -149,7 +149,7 @@ public function testApplyToCollectionWithItemPerPageTooHigh(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), 'Foo', new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationClientItemsPerPage: true), ['filters' => ['pagination' => true, 'itemsPerPage' => 301, '_page' => 2]]); + $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationClientItemsPerPage: true), ['filters' => ['pagination' => true, 'itemsPerPage' => 301, '_page' => 2]]); } public function testApplyToCollectionWithGraphql(): void @@ -165,7 +165,7 @@ public function testApplyToCollectionWithGraphql(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), 'Foo', new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationItemsPerPage: 20), ['filters' => ['pagination' => true, 'first' => 5, 'after' => 'OQ=='], 'graphql_operation_name' => 'query']); + $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationItemsPerPage: 20), ['filters' => ['pagination' => true, 'first' => 5, 'after' => 'OQ=='], 'graphql_operation_name' => 'query']); } public function testApplyToCollectionNofilters(): void @@ -181,7 +181,7 @@ public function testApplyToCollectionNofilters(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), 'Foo', new GetCollection()); + $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection()); } public function testApplyToCollectionPaginationDisabled(): void @@ -199,7 +199,7 @@ public function testApplyToCollectionPaginationDisabled(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), 'Foo', new GetCollection()); + $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection()); } public function testApplyToCollectionGraphQlPaginationDisabled(): void @@ -217,7 +217,7 @@ public function testApplyToCollectionGraphQlPaginationDisabled(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), 'Foo', new GetCollection(), ['graphql_operation_name' => 'op']); + $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(), ['graphql_operation_name' => 'op']); } public function testApplyToCollectionWithMaximumItemsPerPage(): void @@ -237,7 +237,7 @@ public function testApplyToCollectionWithMaximumItemsPerPage(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), 'Foo', new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationMaximumItemsPerPage: 80), ['filters' => ['pagination' => true, 'itemsPerPage' => 80, 'page' => 1]]); + $extension->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(paginationEnabled: true, paginationClientEnabled: true, paginationMaximumItemsPerPage: 80), ['filters' => ['pagination' => true, 'itemsPerPage' => 80, 'page' => 1]]); } public function testSupportsResult(): void @@ -248,7 +248,7 @@ public function testSupportsResult(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $this->assertTrue($extension->supportsResult('Foo', new GetCollection())); + $this->assertTrue($extension->supportsResult(Dummy::class, new GetCollection())); } public function testSupportsResultClientNotAllowedToPaginate(): void @@ -262,7 +262,7 @@ public function testSupportsResultClientNotAllowedToPaginate(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $this->assertFalse($extension->supportsResult('Foo', new GetCollection(), ['filters' => ['pagination' => true]])); + $this->assertFalse($extension->supportsResult(Dummy::class, new GetCollection(), ['filters' => ['pagination' => true]])); } public function testSupportsResultPaginationDisabled(): void @@ -275,7 +275,7 @@ public function testSupportsResultPaginationDisabled(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $this->assertFalse($extension->supportsResult('Foo', new GetCollection())); + $this->assertFalse($extension->supportsResult(Dummy::class, new GetCollection())); } public function testSupportsResultGraphQlPaginationDisabled(): void @@ -288,7 +288,7 @@ public function testSupportsResultGraphQlPaginationDisabled(): void $this->prophesize(ManagerRegistry::class)->reveal(), $pagination ); - $this->assertFalse($extension->supportsResult('Foo', new GetCollection(), ['graphql_operation_name' => 'op'])); + $this->assertFalse($extension->supportsResult(Dummy::class, new GetCollection(), ['graphql_operation_name' => 'op'])); } public function testGetResult(): void From cd46f611c32026a587af191ddca2696c3f2e0d8c Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Sat, 1 Jun 2024 14:18:37 +0200 Subject: [PATCH 15/72] chore(symfony): deprecations (#6401) --- Tests/Fixtures/CustomConverter.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Tests/Fixtures/CustomConverter.php b/Tests/Fixtures/CustomConverter.php index 956eca7..e0a06d8 100644 --- a/Tests/Fixtures/CustomConverter.php +++ b/Tests/Fixtures/CustomConverter.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures; +use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; @@ -20,7 +21,7 @@ * Custom converter that will only convert a property named "nameConverted" * with the same logic as Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter. */ -class CustomConverter implements NameConverterInterface +class CustomConverter implements AdvancedNameConverterInterface { private NameConverterInterface $nameConverter; @@ -29,12 +30,12 @@ public function __construct() $this->nameConverter = new CamelCaseToSnakeCaseNameConverter(); } - public function normalize(string $propertyName): string + public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { return 'nameConverted' === $propertyName ? $this->nameConverter->normalize($propertyName) : $propertyName; } - public function denormalize(string $propertyName): string + public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { return 'name_converted' === $propertyName ? $this->nameConverter->denormalize($propertyName) : $propertyName; } From c9843ee9fd5cdfc4419c1b2bc26e66029817bff6 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Thu, 13 Jun 2024 08:27:02 +0200 Subject: [PATCH 16/72] fix(doctrine): use null-safe operator when retrieving parameters (#6423) Co-authored-by: Sebastiaan <45851377+Kyzegs@users.noreply.github.com> --- Extension/ParameterExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extension/ParameterExtension.php b/Extension/ParameterExtension.php index 792f311..334c1b9 100644 --- a/Extension/ParameterExtension.php +++ b/Extension/ParameterExtension.php @@ -35,7 +35,7 @@ public function __construct(private readonly ContainerInterface $filterLocator) */ private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { - foreach ($operation->getParameters() ?? [] as $parameter) { + foreach ($operation?->getParameters() ?? [] as $parameter) { $values = $parameter->getExtraProperties()['_api_values'] ?? []; if (!$values) { continue; From 8e5977b957558b12e06a3e87494a144bc8646ff8 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Tue, 25 Jun 2024 16:01:23 +0200 Subject: [PATCH 17/72] feat(laravel): laravel component (#5882) * feat(laravel): laravel component * try to skip laravel * feat(jsonapi): component * feat(laravel): json api support (needs review) * work on relations * relations (needs toMany) + skolem + IRI to resource * links handler * ulid * validation * slug post * remove deprecations * move classes * fix tests * fix tests metadata * phpstan * missing class * fix laravel tests * fix stan --- Extension/FilterEagerLoadingExtension.php | 2 +- Extension/FilterExtension.php | 2 +- Extension/OrderExtension.php | 2 +- Extension/PaginationExtension.php | 2 +- Filter/SearchFilter.php | 6 ++---- Metadata/Resource/DoctrineOrmLinkFactory.php | 3 +-- PropertyHelperTrait.php | 2 +- State/Options.php | 13 ------------- .../Resource/DoctrineOrmLinkFactoryTest.php | 2 +- 9 files changed, 9 insertions(+), 25 deletions(-) diff --git a/Extension/FilterEagerLoadingExtension.php b/Extension/FilterEagerLoadingExtension.php index daba5d9..8145fa6 100644 --- a/Extension/FilterEagerLoadingExtension.php +++ b/Extension/FilterEagerLoadingExtension.php @@ -15,7 +15,7 @@ use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\ResourceClassResolverInterface; use Doctrine\ORM\EntityManagerInterface; diff --git a/Extension/FilterExtension.php b/Extension/FilterExtension.php index 298eff2..801e9c4 100644 --- a/Extension/FilterExtension.php +++ b/Extension/FilterExtension.php @@ -16,7 +16,7 @@ use ApiPlatform\Doctrine\Orm\Filter\FilterInterface; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; use Doctrine\ORM\QueryBuilder; use Psr\Container\ContainerInterface; diff --git a/Extension/OrderExtension.php b/Extension/OrderExtension.php index a383383..3989072 100644 --- a/Extension/OrderExtension.php +++ b/Extension/OrderExtension.php @@ -15,7 +15,7 @@ use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; use Doctrine\ORM\QueryBuilder; diff --git a/Extension/PaginationExtension.php b/Extension/PaginationExtension.php index 80513e4..9b4ab0d 100644 --- a/Extension/PaginationExtension.php +++ b/Extension/PaginationExtension.php @@ -17,7 +17,7 @@ use ApiPlatform\Doctrine\Orm\Paginator; use ApiPlatform\Doctrine\Orm\Util\QueryChecker; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\Pagination\Pagination; use Doctrine\ORM\QueryBuilder; diff --git a/Filter/SearchFilter.php b/Filter/SearchFilter.php index 3b09533..61b35b7 100644 --- a/Filter/SearchFilter.php +++ b/Filter/SearchFilter.php @@ -13,8 +13,6 @@ namespace ApiPlatform\Doctrine\Orm\Filter; -use ApiPlatform\Api\IdentifiersExtractorInterface as LegacyIdentifiersExtractorInterface; -use ApiPlatform\Api\IriConverterInterface as LegacyIriConverterInterface; use ApiPlatform\Doctrine\Common\Filter\SearchFilterInterface; use ApiPlatform\Doctrine\Common\Filter\SearchFilterTrait; use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper; @@ -141,7 +139,7 @@ final class SearchFilter extends AbstractFilter implements SearchFilterInterface public const DOCTRINE_INTEGER_TYPE = Types::INTEGER; - public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface|LegacyIriConverterInterface $iriConverter, ?PropertyAccessorInterface $propertyAccessor = null, ?LoggerInterface $logger = null, ?array $properties = null, IdentifiersExtractorInterface|LegacyIdentifiersExtractorInterface|null $identifiersExtractor = null, ?NameConverterInterface $nameConverter = null) + public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface $iriConverter, ?PropertyAccessorInterface $propertyAccessor = null, ?LoggerInterface $logger = null, ?array $properties = null, ?IdentifiersExtractorInterface $identifiersExtractor = null, ?NameConverterInterface $nameConverter = null) { parent::__construct($managerRegistry, $logger, $properties, $nameConverter); @@ -150,7 +148,7 @@ public function __construct(ManagerRegistry $managerRegistry, IriConverterInterf $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); } - protected function getIriConverter(): IriConverterInterface|LegacyIriConverterInterface + protected function getIriConverter(): IriConverterInterface { return $this->iriConverter; } diff --git a/Metadata/Resource/DoctrineOrmLinkFactory.php b/Metadata/Resource/DoctrineOrmLinkFactory.php index 1922ccd..bf958b8 100644 --- a/Metadata/Resource/DoctrineOrmLinkFactory.php +++ b/Metadata/Resource/DoctrineOrmLinkFactory.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Doctrine\Orm\Metadata\Resource; -use ApiPlatform\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Metadata; use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; @@ -28,7 +27,7 @@ */ final class DoctrineOrmLinkFactory implements LinkFactoryInterface, PropertyLinkFactoryInterface { - public function __construct(private readonly ManagerRegistry $managerRegistry, private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly ResourceClassResolverInterface|LegacyResourceClassResolverInterface $resourceClassResolver, private readonly LinkFactoryInterface&PropertyLinkFactoryInterface $linkFactory) + public function __construct(private readonly ManagerRegistry $managerRegistry, private readonly PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, private readonly ResourceClassResolverInterface $resourceClassResolver, private readonly LinkFactoryInterface&PropertyLinkFactoryInterface $linkFactory) { } diff --git a/PropertyHelperTrait.php b/PropertyHelperTrait.php index e0b9a14..a8086ca 100644 --- a/PropertyHelperTrait.php +++ b/PropertyHelperTrait.php @@ -15,7 +15,7 @@ use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; use Doctrine\ORM\Mapping\ClassMetadata as ClassMetadataInfo; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; diff --git a/State/Options.php b/State/Options.php index 397d2da..3a9a46c 100644 --- a/State/Options.php +++ b/State/Options.php @@ -42,17 +42,4 @@ public function withEntityClass(?string $entityClass): self return $self; } - - public function getHandleLinks(): mixed - { - return $this->handleLinks; - } - - public function withHandleLinks(mixed $handleLinks): self - { - $self = clone $this; - $self->handleLinks = $handleLinks; - - return $self; - } } diff --git a/Tests/Metadata/Resource/DoctrineOrmLinkFactoryTest.php b/Tests/Metadata/Resource/DoctrineOrmLinkFactoryTest.php index 3c16c0f..65b5eee 100644 --- a/Tests/Metadata/Resource/DoctrineOrmLinkFactoryTest.php +++ b/Tests/Metadata/Resource/DoctrineOrmLinkFactoryTest.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Doctrine\Orm\Tests\Metadata\Resource; -use ApiPlatform\Api\ResourceClassResolverInterface; use ApiPlatform\Doctrine\Orm\Metadata\Resource\DoctrineOrmLinkFactory; use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy; use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\RelatedDummy; @@ -25,6 +24,7 @@ use ApiPlatform\Metadata\Property\PropertyNameCollection; use ApiPlatform\Metadata\Resource\Factory\LinkFactoryInterface; use ApiPlatform\Metadata\Resource\Factory\PropertyLinkFactoryInterface; +use ApiPlatform\Metadata\ResourceClassResolverInterface; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\Persistence\ManagerRegistry; From bd8b844d4d15e41f72e628b482bd7bc0e6da8135 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 5 Jul 2024 17:48:09 +0200 Subject: [PATCH 18/72] fix(state): query and header parameter with the same name (#6453) --- Extension/ParameterExtension.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Extension/ParameterExtension.php b/Extension/ParameterExtension.php index 334c1b9..46c8664 100644 --- a/Extension/ParameterExtension.php +++ b/Extension/ParameterExtension.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Doctrine\Orm\Extension; +use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait; use ApiPlatform\Doctrine\Orm\Filter\FilterInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Metadata\Operation; @@ -26,6 +27,8 @@ */ final class ParameterExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface { + use ParameterValueExtractorTrait; + public function __construct(private readonly ContainerInterface $filterLocator) { } @@ -36,7 +39,7 @@ public function __construct(private readonly ContainerInterface $filterLocator) private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { foreach ($operation?->getParameters() ?? [] as $parameter) { - $values = $parameter->getExtraProperties()['_api_values'] ?? []; + $values = $this->extractParameterValue($parameter->getValue() ?? []); if (!$values) { continue; } From 15e4671cc23f8167ff175aadfb731a19e970c37b Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Mon, 8 Jul 2024 09:41:59 +0200 Subject: [PATCH 19/72] fix(state): store parameter value without its key (#6456) --- Extension/ParameterExtension.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Extension/ParameterExtension.php b/Extension/ParameterExtension.php index 46c8664..dce2c62 100644 --- a/Extension/ParameterExtension.php +++ b/Extension/ParameterExtension.php @@ -17,6 +17,7 @@ use ApiPlatform\Doctrine\Orm\Filter\FilterInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Metadata\Operation; +use ApiPlatform\State\ParameterNotFound; use Doctrine\ORM\QueryBuilder; use Psr\Container\ContainerInterface; @@ -39,11 +40,11 @@ public function __construct(private readonly ContainerInterface $filterLocator) private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { foreach ($operation?->getParameters() ?? [] as $parameter) { - $values = $this->extractParameterValue($parameter->getValue() ?? []); - if (!$values) { + if (!($v = $parameter->getValue()) || $v instanceof ParameterNotFound) { continue; } + $values = $this->extractParameterValue($parameter, $v); if (null === ($filterId = $parameter->getFilter())) { continue; } From 6e8f967cd07850ba7c93a3fbbd615b82b54a68ea Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Mon, 8 Jul 2024 13:06:27 +0200 Subject: [PATCH 20/72] feat: deprecate query parameter validator (#6454) --- Tests/Filter/OrderFilterTest.php | 21 +++++++++++++++++++++ Tests/Filter/OrderFilterTestTrait.php | 2 ++ 2 files changed, 23 insertions(+) diff --git a/Tests/Filter/OrderFilterTest.php b/Tests/Filter/OrderFilterTest.php index 3838290..8864928 100644 --- a/Tests/Filter/OrderFilterTest.php +++ b/Tests/Filter/OrderFilterTest.php @@ -40,6 +40,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -52,6 +53,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -64,6 +66,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -76,6 +79,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -88,6 +92,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -100,6 +105,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -112,6 +118,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -124,6 +131,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -136,6 +144,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -148,6 +157,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -160,6 +170,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -172,6 +183,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -187,6 +199,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -199,6 +212,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -211,6 +225,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -223,6 +238,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -235,6 +251,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -247,6 +264,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -259,6 +277,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -271,6 +290,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -283,6 +303,7 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', diff --git a/Tests/Filter/OrderFilterTestTrait.php b/Tests/Filter/OrderFilterTestTrait.php index 2409ae8..b125895 100644 --- a/Tests/Filter/OrderFilterTestTrait.php +++ b/Tests/Filter/OrderFilterTestTrait.php @@ -29,6 +29,7 @@ public function testGetDescription(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', @@ -41,6 +42,7 @@ public function testGetDescription(): void 'type' => 'string', 'required' => false, 'schema' => [ + 'default' => 'asc', 'type' => 'string', 'enum' => [ 'asc', From 62c4f71be5a3bf35afccecb0e520172b5e10bc54 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Mon, 15 Jul 2024 11:45:37 +0200 Subject: [PATCH 21/72] chore: symfony 7.1 dependency and branch alias (#6468) * chore: symfony 7.1 dependency and branch alias * chore: main_request --- composer.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index e02d885..40bebfc 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "api-platform/state": "*@dev || ^3.1", "doctrine/doctrine-bundle": "^2.11", "doctrine/orm": "^2.17 || ^3.0", - "symfony/property-info": "^6.4 || ^7.0" + "symfony/property-info": "^6.4 || ^7.1" }, "require-dev": { "api-platform/parameter-validator": "*@dev || ^3.2", @@ -59,13 +59,14 @@ }, "extra": { "branch-alias": { - "dev-main": "3.3.x-dev" + "dev-main": "4.0.x-dev", + "dev-3.4": "3.4.x-dev" }, "symfony": { - "require": "^6.4" + "require": "^6.4 || ^7.1" } }, "scripts": { "test": "./vendor/bin/phpunit" } -} +} \ No newline at end of file From cd35e81df468bba602a94dd7202a71e121af2a5c Mon Sep 17 00:00:00 2001 From: soyuka Date: Tue, 25 Jun 2024 22:24:35 +0200 Subject: [PATCH 22/72] chore: remove deprecations --- Tests/AppKernel.php | 7 +++++++ Tests/DoctrineOrmFilterTestCase.php | 4 +--- Tests/Extension/EagerLoadingExtensionTest.php | 4 +--- Tests/Extension/PaginationExtensionTest.php | 8 ++------ Tests/Fixtures/Entity/Dummy.php | 2 +- Tests/Fixtures/Entity/RelatedToDummyFriend.php | 2 +- .../DoctrineOrmResourceCollectionMetadataFactoryTest.php | 4 +--- Tests/PaginatorTest.php | 4 +--- Tests/Util/QueryBuilderHelperTest.php | 8 ++------ composer.json | 4 ++-- 10 files changed, 19 insertions(+), 28 deletions(-) diff --git a/Tests/AppKernel.php b/Tests/AppKernel.php index 29229e2..3f1efec 100644 --- a/Tests/AppKernel.php +++ b/Tests/AppKernel.php @@ -18,6 +18,7 @@ use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Kernel; /** @@ -43,6 +44,12 @@ public function registerBundles(): array new FrameworkBundle(), new DoctrineBundle(), new TestBundle(), + new class() extends Bundle { + public function shutdown(): void + { + restore_exception_handler(); + } + }, ]; } diff --git a/Tests/DoctrineOrmFilterTestCase.php b/Tests/DoctrineOrmFilterTestCase.php index 77b851e..9a2c0e9 100644 --- a/Tests/DoctrineOrmFilterTestCase.php +++ b/Tests/DoctrineOrmFilterTestCase.php @@ -45,9 +45,7 @@ protected function setUp(): void $this->repository = $this->managerRegistry->getManagerForClass(Dummy::class)->getRepository(Dummy::class); } - /** - * @dataProvider provideApplyTestData - */ + #[\PHPUnit\Framework\Attributes\DataProvider('provideApplyTestData')] public function testApply(?array $properties, array $filterParameters, string $expectedDql, ?array $expectedParameters = null, ?callable $factory = null, ?string $resourceClass = null): void { $this->doTestApply($properties, $filterParameters, $expectedDql, $expectedParameters, $factory, $resourceClass); diff --git a/Tests/Extension/EagerLoadingExtensionTest.php b/Tests/Extension/EagerLoadingExtensionTest.php index 2bd173d..5dec2ce 100644 --- a/Tests/Extension/EagerLoadingExtensionTest.php +++ b/Tests/Extension/EagerLoadingExtensionTest.php @@ -821,9 +821,7 @@ public function testApplyToCollectionWithANonReadableButFetchEagerProperty(): vo $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class, new GetCollection(normalizationContext: [AbstractNormalizer::GROUPS => 'foo'])); } - /** - * @dataProvider provideExistingJoinCases - */ + #[\PHPUnit\Framework\Attributes\DataProvider('provideExistingJoinCases')] public function testApplyToCollectionWithExistingJoin(string $joinType): void { $context = ['groups' => ['foo']]; diff --git a/Tests/Extension/PaginationExtensionTest.php b/Tests/Extension/PaginationExtensionTest.php index 64e505c..2b8bcc5 100644 --- a/Tests/Extension/PaginationExtensionTest.php +++ b/Tests/Extension/PaginationExtensionTest.php @@ -355,9 +355,7 @@ public function testGetResultWithoutDistinct(): void $this->assertFalse($query->getHint(CountWalker::HINT_DISTINCT)); } - /** - * @dataProvider fetchJoinCollectionProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('fetchJoinCollectionProvider')] public function testGetResultWithFetchJoinCollection(bool $paginationFetchJoinCollection, array $context, bool $expected): void { $dummyMetadata = new ClassMetadata(Dummy::class); @@ -405,9 +403,7 @@ public static function fetchJoinCollectionProvider(): array ]; } - /** - * @dataProvider fetchUseOutputWalkersProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('fetchUseOutputWalkersProvider')] public function testGetResultWithUseOutputWalkers(bool $paginationUseOutputWalkers, array $context, bool $expected): void { $dummyMetadata = new ClassMetadata(Dummy::class); diff --git a/Tests/Fixtures/Entity/Dummy.php b/Tests/Fixtures/Entity/Dummy.php index 00df229..544d01c 100644 --- a/Tests/Fixtures/Entity/Dummy.php +++ b/Tests/Fixtures/Entity/Dummy.php @@ -27,7 +27,7 @@ * * @author Kévin Dunglas */ -#[ApiResource(filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], extraProperties: ['standard_put' => false, 'rfc_7807_compliant_errors' => false])] +#[ApiResource(filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], extraProperties: ['standard_put' => false])] #[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy')], status: 200, filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], operations: [new Get()])] #[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy')], status: 200, filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], operations: [new Get()])] #[ORM\Entity] diff --git a/Tests/Fixtures/Entity/RelatedToDummyFriend.php b/Tests/Fixtures/Entity/RelatedToDummyFriend.php index 3e95aa9..e0482b1 100644 --- a/Tests/Fixtures/Entity/RelatedToDummyFriend.php +++ b/Tests/Fixtures/Entity/RelatedToDummyFriend.php @@ -24,7 +24,7 @@ /** * Related To Dummy Friend represent an association table for a manytomany relation. */ -#[ApiResource(normalizationContext: ['groups' => ['fakemanytomany']], filters: ['related_to_dummy_friend.name'], extraProperties: ['rfc_7807_compliant_errors' => false])] +#[ApiResource(normalizationContext: ['groups' => ['fakemanytomany']], filters: ['related_to_dummy_friend.name'])] #[ApiResource(uriTemplate: '/dummies/{id}/related_dummies/{relatedDummies}/related_to_dummy_friends{._format}', uriVariables: ['id' => new Link(fromClass: Dummy::class, identifiers: ['id'], fromProperty: 'relatedDummies'), 'relatedDummies' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])] #[ApiResource(uriTemplate: '/related_dummies/{id}/id/related_to_dummy_friends{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])] #[ApiResource(uriTemplate: '/related_dummies/{id}/related_to_dummy_friends{._format}', uriVariables: ['id' => new Link(fromClass: RelatedDummy::class, identifiers: ['id'], toProperty: 'relatedDummy')], status: 200, filters: ['related_to_dummy_friend.name'], normalizationContext: ['groups' => ['fakemanytomany']], operations: [new GetCollection()])] diff --git a/Tests/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php b/Tests/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php index 9904186..3e371af 100644 --- a/Tests/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php +++ b/Tests/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactoryTest.php @@ -62,9 +62,7 @@ public function testWithoutManager(): void $this->assertNull($resourceMetadataCollection->getOperation('graphql_get')->getProvider()); } - /** - * @dataProvider operationProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('operationProvider')] public function testWithProvider(Operation $operation, ?string $expectedProvider = null, ?string $expectedProcessor = null): void { $objectManager = $this->prophesize(EntityManagerInterface::class); diff --git a/Tests/PaginatorTest.php b/Tests/PaginatorTest.php index b5a347a..6201003 100644 --- a/Tests/PaginatorTest.php +++ b/Tests/PaginatorTest.php @@ -30,9 +30,7 @@ class PaginatorTest extends TestCase { use ProphecyTrait; - /** - * @dataProvider initializeProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('initializeProvider')] public function testInitialize(int $firstResult, int $maxResults, int $totalItems, int $currentPage, int $lastPage, bool $hasNextPage): void { $paginator = $this->getPaginator($firstResult, $maxResults, $totalItems); diff --git a/Tests/Util/QueryBuilderHelperTest.php b/Tests/Util/QueryBuilderHelperTest.php index 7ce2b44..488d217 100644 --- a/Tests/Util/QueryBuilderHelperTest.php +++ b/Tests/Util/QueryBuilderHelperTest.php @@ -29,9 +29,7 @@ class QueryBuilderHelperTest extends TestCase { use ProphecyTrait; - /** - * @dataProvider provideAddJoinOnce - */ + #[\PHPUnit\Framework\Attributes\DataProvider('provideAddJoinOnce')] public function testAddJoinOnce(?string $originAliasForJoinOnce, string $expectedAlias): void { $queryBuilder = new QueryBuilder($this->prophesize(EntityManagerInterface::class)->reveal()); @@ -57,9 +55,7 @@ public function testAddJoinOnce(?string $originAliasForJoinOnce, string $expecte $queryBuilder->getDQLPart('join')[$originAliasForJoinOnce ?? 'f'][0]->getAlias()); } - /** - * @dataProvider provideAddJoinOnce - */ + #[\PHPUnit\Framework\Attributes\DataProvider('provideAddJoinOnce')] public function testAddJoinOnceWithSpecifiedNewAlias(): void { $queryBuilder = new QueryBuilder($this->prophesize(EntityManagerInterface::class)->reveal()); diff --git a/composer.json b/composer.json index 40bebfc..4cfb545 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "require-dev": { "api-platform/parameter-validator": "*@dev || ^3.2", "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^10.0", + "phpunit/phpunit": "^11.2", "ramsey/uuid": "^4.7", "ramsey/uuid-doctrine": "^2.0", "symfony/cache": "^6.4 || ^7.0", @@ -69,4 +69,4 @@ "scripts": { "test": "./vendor/bin/phpunit" } -} \ No newline at end of file +} From 7209acf2e5f468bef1a1f4c94777e56385688633 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 9 Aug 2024 09:21:07 +0200 Subject: [PATCH 23/72] style: various cs fixes (#6504) * cs: fixes * chore: phpstan fixes --- AbstractPaginator.php | 2 +- Extension/EagerLoadingExtension.php | 4 +- Extension/OrderExtension.php | 2 +- Filter/BooleanFilter.php | 2 +- Filter/DateFilter.php | 10 ++-- Filter/ExistsFilter.php | 8 +-- Filter/NumericFilter.php | 4 +- Filter/OrderFilter.php | 6 +-- Filter/RangeFilter.php | 16 +++--- Filter/SearchFilter.php | 14 ++--- PropertyHelperTrait.php | 2 +- Tests/DoctrineOrmFilterTestCase.php | 4 +- Tests/Filter/BooleanFilterTest.php | 22 ++++---- Tests/Filter/DateFilterTest.php | 36 ++++++------- Tests/Filter/ExistsFilterTest.php | 40 +++++++-------- Tests/Filter/NumericFilterTest.php | 22 ++++---- Tests/Filter/OrderFilterTest.php | 46 ++++++++--------- Tests/Filter/RangeFilterTest.php | 28 +++++----- Tests/Filter/SearchFilterTest.php | 80 ++++++++++++++--------------- Util/QueryBuilderHelper.php | 10 ++-- Util/QueryNameGenerator.php | 4 +- 21 files changed, 181 insertions(+), 181 deletions(-) diff --git a/AbstractPaginator.php b/AbstractPaginator.php index a3c2a08..59595c7 100644 --- a/AbstractPaginator.php +++ b/AbstractPaginator.php @@ -33,7 +33,7 @@ public function __construct(DoctrinePaginator $paginator) $query = $paginator->getQuery(); if (null === ($firstResult = $query->getFirstResult()) || $firstResult < 0 || null === $maxResults = $query->getMaxResults()) { // @phpstan-ignore-line - throw new InvalidArgumentException(sprintf('"%1$s::setFirstResult()" or/and "%1$s::setMaxResults()" was/were not applied to the query.', Query::class)); + throw new InvalidArgumentException(\sprintf('"%1$s::setFirstResult()" or/and "%1$s::setMaxResults()" was/were not applied to the query.', Query::class)); } $this->paginator = $paginator; diff --git a/Extension/EagerLoadingExtension.php b/Extension/EagerLoadingExtension.php index bd0935d..aa1e08a 100644 --- a/Extension/EagerLoadingExtension.php +++ b/Extension/EagerLoadingExtension.php @@ -186,7 +186,7 @@ private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInt $method = $isLeftJoin ? 'leftJoin' : 'innerJoin'; $associationAlias = $queryNameGenerator->generateJoinAlias($association); - $queryBuilder->{$method}(sprintf('%s.%s', $parentAlias, $association), $associationAlias); + $queryBuilder->{$method}(\sprintf('%s.%s', $parentAlias, $association), $associationAlias); ++$joinCount; } @@ -264,7 +264,7 @@ private function addSelect(QueryBuilder $queryBuilder, string $entity, string $a } } - $queryBuilder->addSelect(sprintf('partial %s.{%s}', $associationAlias, implode(',', $select))); + $queryBuilder->addSelect(\sprintf('partial %s.{%s}', $associationAlias, implode(',', $select))); } private function addSelectOnce(QueryBuilder $queryBuilder, string $alias): void diff --git a/Extension/OrderExtension.php b/Extension/OrderExtension.php index a383383..00140fd 100644 --- a/Extension/OrderExtension.php +++ b/Extension/OrderExtension.php @@ -67,7 +67,7 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator $field = "{$rootAlias}.{$field}"; } else { $alias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $rootAlias, substr($field, 0, $pos)); - $field = sprintf('%s.%s', $alias, substr($field, $pos + 1)); + $field = \sprintf('%s.%s', $alias, substr($field, $pos + 1)); } $queryBuilder->addOrderBy($field, $order); } diff --git a/Filter/BooleanFilter.php b/Filter/BooleanFilter.php index 2f230c4..e9f0a83 100644 --- a/Filter/BooleanFilter.php +++ b/Filter/BooleanFilter.php @@ -142,7 +142,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB $valueParameter = $queryNameGenerator->generateParameterName($field); $queryBuilder - ->andWhere(sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $value); } } diff --git a/Filter/DateFilter.php b/Filter/DateFilter.php index 066606e..8533ee3 100644 --- a/Filter/DateFilter.php +++ b/Filter/DateFilter.php @@ -161,7 +161,7 @@ protected function filterProperty(string $property, $values, QueryBuilder $query $type = (string) $this->getDoctrineFieldType($property, $resourceClass); if (self::EXCLUDE_NULL === $nullManagement) { - $queryBuilder->andWhere($queryBuilder->expr()->isNotNull(sprintf('%s.%s', $alias, $field))); + $queryBuilder->andWhere($queryBuilder->expr()->isNotNull(\sprintf('%s.%s', $alias, $field))); } if (isset($values[self::PARAMETER_BEFORE])) { @@ -234,7 +234,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf } catch (\Exception) { // Silently ignore this filter if it can not be transformed to a \DateTime $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('The field "%s" has a wrong date format. Use one accepted by the \DateTime constructor', $field)), + 'exception' => new InvalidArgumentException(\sprintf('The field "%s" has a wrong date format. Use one accepted by the \DateTime constructor', $field)), ]); return; @@ -247,7 +247,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf self::PARAMETER_AFTER => '>=', self::PARAMETER_STRICTLY_AFTER => '>', ]; - $baseWhere = sprintf('%s.%s %s :%s', $alias, $field, $operatorValue[$operator], $valueParameter); + $baseWhere = \sprintf('%s.%s %s :%s', $alias, $field, $operatorValue[$operator], $valueParameter); if (null === $nullManagement || self::EXCLUDE_NULL === $nullManagement) { $queryBuilder->andWhere($baseWhere); @@ -258,12 +258,12 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf ) { $queryBuilder->andWhere($queryBuilder->expr()->orX( $baseWhere, - $queryBuilder->expr()->isNull(sprintf('%s.%s', $alias, $field)) + $queryBuilder->expr()->isNull(\sprintf('%s.%s', $alias, $field)) )); } else { $queryBuilder->andWhere($queryBuilder->expr()->andX( $baseWhere, - $queryBuilder->expr()->isNotNull(sprintf('%s.%s', $alias, $field)) + $queryBuilder->expr()->isNotNull(\sprintf('%s.%s', $alias, $field)) )); } diff --git a/Filter/ExistsFilter.php b/Filter/ExistsFilter.php index 5f516c4..0dde78e 100644 --- a/Filter/ExistsFilter.php +++ b/Filter/ExistsFilter.php @@ -164,7 +164,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB if ($metadata->hasAssociation($field)) { if ($metadata->isCollectionValuedAssociation($field)) { $queryBuilder - ->andWhere(sprintf('%s.%s %s EMPTY', $alias, $field, $value ? 'IS NOT' : 'IS')); + ->andWhere(\sprintf('%s.%s %s EMPTY', $alias, $field, $value ? 'IS NOT' : 'IS')); return; } @@ -173,20 +173,20 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB $alias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $alias, $field, Join::LEFT_JOIN); $queryBuilder - ->andWhere(sprintf('%s %s NULL', $alias, $value ? 'IS NOT' : 'IS')); + ->andWhere(\sprintf('%s %s NULL', $alias, $value ? 'IS NOT' : 'IS')); return; } $queryBuilder - ->andWhere(sprintf('%s.%s %s NULL', $alias, $field, $value ? 'IS NOT' : 'IS')); + ->andWhere(\sprintf('%s.%s %s NULL', $alias, $field, $value ? 'IS NOT' : 'IS')); return; } if ($metadata->hasField($field)) { $queryBuilder - ->andWhere(sprintf('%s.%s %s NULL', $alias, $field, $value ? 'IS NOT' : 'IS')); + ->andWhere(\sprintf('%s.%s %s NULL', $alias, $field, $value ? 'IS NOT' : 'IS')); } } diff --git a/Filter/NumericFilter.php b/Filter/NumericFilter.php index c27e3f6..545b552 100644 --- a/Filter/NumericFilter.php +++ b/Filter/NumericFilter.php @@ -152,11 +152,11 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB if (1 === \count($values)) { $queryBuilder - ->andWhere(sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $values[0], (string) $this->getDoctrineFieldType($property, $resourceClass)); } else { $queryBuilder - ->andWhere(sprintf('%s.%s IN (:%s)', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s IN (:%s)', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $values); } } diff --git a/Filter/OrderFilter.php b/Filter/OrderFilter.php index 1987ee2..b2abdab 100644 --- a/Filter/OrderFilter.php +++ b/Filter/OrderFilter.php @@ -263,12 +263,12 @@ protected function filterProperty(string $property, $direction, QueryBuilder $qu if (null !== $nullsComparison = $this->properties[$property]['nulls_comparison'] ?? $this->orderNullsComparison) { $nullsDirection = self::NULLS_DIRECTION_MAP[$nullsComparison][$direction]; - $nullRankHiddenField = sprintf('_%s_%s_null_rank', $alias, str_replace('.', '_', $field)); + $nullRankHiddenField = \sprintf('_%s_%s_null_rank', $alias, str_replace('.', '_', $field)); - $queryBuilder->addSelect(sprintf('CASE WHEN %s.%s IS NULL THEN 0 ELSE 1 END AS HIDDEN %s', $alias, $field, $nullRankHiddenField)); + $queryBuilder->addSelect(\sprintf('CASE WHEN %s.%s IS NULL THEN 0 ELSE 1 END AS HIDDEN %s', $alias, $field, $nullRankHiddenField)); $queryBuilder->addOrderBy($nullRankHiddenField, $nullsDirection); } - $queryBuilder->addOrderBy(sprintf('%s.%s', $alias, $field), $direction); + $queryBuilder->addOrderBy(\sprintf('%s.%s', $alias, $field), $direction); } } diff --git a/Filter/RangeFilter.php b/Filter/RangeFilter.php index 70c3e68..85de711 100644 --- a/Filter/RangeFilter.php +++ b/Filter/RangeFilter.php @@ -164,16 +164,16 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf if ($rangeValue[0] === $rangeValue[1]) { $queryBuilder - ->andWhere(sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $rangeValue[0]); return; } $queryBuilder - ->andWhere(sprintf('%1$s.%2$s BETWEEN :%3$s_1 AND :%3$s_2', $alias, $field, $valueParameter)) - ->setParameter(sprintf('%s_1', $valueParameter), $rangeValue[0]) - ->setParameter(sprintf('%s_2', $valueParameter), $rangeValue[1]); + ->andWhere(\sprintf('%1$s.%2$s BETWEEN :%3$s_1 AND :%3$s_2', $alias, $field, $valueParameter)) + ->setParameter(\sprintf('%s_1', $valueParameter), $rangeValue[0]) + ->setParameter(\sprintf('%s_2', $valueParameter), $rangeValue[1]); break; case self::PARAMETER_GREATER_THAN: @@ -183,7 +183,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf } $queryBuilder - ->andWhere(sprintf('%s.%s > :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s > :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $value); break; @@ -194,7 +194,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf } $queryBuilder - ->andWhere(sprintf('%s.%s >= :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s >= :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $value); break; @@ -205,7 +205,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf } $queryBuilder - ->andWhere(sprintf('%s.%s < :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s < :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $value); break; @@ -216,7 +216,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf } $queryBuilder - ->andWhere(sprintf('%s.%s <= :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s <= :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $value); break; diff --git a/Filter/SearchFilter.php b/Filter/SearchFilter.php index 3b09533..a94c862 100644 --- a/Filter/SearchFilter.php +++ b/Filter/SearchFilter.php @@ -205,7 +205,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB if (!$this->hasValidValues($values, $this->getDoctrineFieldType($property, $resourceClass))) { $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Values for field "%s" are not valid according to the doctrine type.', $field)), + 'exception' => new InvalidArgumentException(\sprintf('Values for field "%s" are not valid according to the doctrine type.', $field)), ]); return; @@ -243,7 +243,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB */ if (!$this->hasValidValues([$value], $doctrineTypeField)) { $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Values for field "%s" are not valid according to the doctrine type.', $associationFieldIdentifier)), + 'exception' => new InvalidArgumentException(\sprintf('Values for field "%s" are not valid according to the doctrine type.', $associationFieldIdentifier)), ]); return null; @@ -260,7 +260,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB * Shouldn't this actually fail harder? */ $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Values for field "%s" are not valid according to the doctrine type.', $field)), + 'exception' => new InvalidArgumentException(\sprintf('Values for field "%s" are not valid according to the doctrine type.', $field)), ]); return; @@ -289,7 +289,7 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild $wrapCase = $this->createWrapCase($caseSensitive); $valueParameter = ':'.$queryNameGenerator->generateParameterName($field); - $aliasedField = sprintf('%s.%s', $alias, $field); + $aliasedField = \sprintf('%s.%s', $alias, $field); if (!$strategy || self::STRATEGY_EXACT === $strategy) { if (1 === \count($values)) { @@ -310,7 +310,7 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild $ors = []; $parameters = []; foreach ($values as $key => $value) { - $keyValueParameter = sprintf('%s_%s', $valueParameter, $key); + $keyValueParameter = \sprintf('%s_%s', $valueParameter, $key); $parameters[] = [$caseSensitive ? $value : strtolower($value), $keyValueParameter]; $ors[] = match ($strategy) { @@ -336,7 +336,7 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild $wrapCase((string) $queryBuilder->expr()->concat("'% '", $keyValueParameter, "'%'")) ) ), - default => throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy)), + default => throw new InvalidArgumentException(\sprintf('strategy %s does not exist.', $strategy)), }; } @@ -360,7 +360,7 @@ protected function createWrapCase(bool $caseSensitive): \Closure return $expr; } - return sprintf('LOWER(%s)', $expr); + return \sprintf('LOWER(%s)', $expr); }; } diff --git a/PropertyHelperTrait.php b/PropertyHelperTrait.php index e0b9a14..8ed61ca 100644 --- a/PropertyHelperTrait.php +++ b/PropertyHelperTrait.php @@ -73,7 +73,7 @@ protected function addJoinsForNestedProperty(string $property, string $rootAlias } if (null === $alias) { - throw new InvalidArgumentException(sprintf('Cannot add joins for property "%s" - property is not nested.', $property)); + throw new InvalidArgumentException(\sprintf('Cannot add joins for property "%s" - property is not nested.', $property)); } return [$alias, $propertyParts['field'], $propertyParts['associations']]; diff --git a/Tests/DoctrineOrmFilterTestCase.php b/Tests/DoctrineOrmFilterTestCase.php index 77b851e..f091da0 100644 --- a/Tests/DoctrineOrmFilterTestCase.php +++ b/Tests/DoctrineOrmFilterTestCase.php @@ -78,8 +78,8 @@ protected function doTestApply(?array $properties, array $filterParameters, stri foreach ($expectedParameters as $parameterName => $expectedParameterValue) { $queryParameter = $queryBuilder->getQuery()->getParameter($parameterName); - $this->assertNotNull($queryParameter, sprintf('Expected query parameter "%s" to be set', $parameterName)); - $this->assertEquals($expectedParameterValue, $queryParameter->getValue(), sprintf('Expected query parameter "%s" to be "%s"', $parameterName, var_export($expectedParameterValue, true))); + $this->assertNotNull($queryParameter, \sprintf('Expected query parameter "%s" to be set', $parameterName)); + $this->assertEquals($expectedParameterValue, $queryParameter->getValue(), \sprintf('Expected query parameter "%s" to be "%s"', $parameterName, var_export($expectedParameterValue, true))); } } diff --git a/Tests/Filter/BooleanFilterTest.php b/Tests/Filter/BooleanFilterTest.php index 1fdd6aa..0be3965 100644 --- a/Tests/Filter/BooleanFilterTest.php +++ b/Tests/Filter/BooleanFilterTest.php @@ -32,37 +32,37 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'string ("true")' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), ], 'string ("false")' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), ], 'non-boolean' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'numeric string ("0")' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), ], 'numeric string ("1")' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), ], 'nested properties' => [ - sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.dummyBoolean = :dummyBoolean_p1', Dummy::class), + \sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.dummyBoolean = :dummyBoolean_p1', Dummy::class), ], 'numeric string ("1") on non-boolean property' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'numeric string ("0") on non-boolean property' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'string ("true") on non-boolean property' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'string ("false") on non-boolean property' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'mixed boolean, non-boolean and invalid property' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), ], ] ); diff --git a/Tests/Filter/DateFilterTest.php b/Tests/Filter/DateFilterTest.php index 01d5e72..cd66dd1 100644 --- a/Tests/Filter/DateFilterTest.php +++ b/Tests/Filter/DateFilterTest.php @@ -62,58 +62,58 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'after (all properties enabled)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1', Dummy::class), ], 'after but not equals (all properties enabled)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate > :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate > :dummyDate_p1', Dummy::class), ], 'after' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1', Dummy::class), ], 'after but not equals' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate > :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate > :dummyDate_p1', Dummy::class), ], 'before (all properties enabled)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1', Dummy::class), ], 'before but not equals (all properties enabled)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1', Dummy::class), ], 'before' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1', Dummy::class), ], 'before but not equals' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1', Dummy::class), ], 'before + after (all properties enabled)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1 AND o.dummyDate >= :dummyDate_p2', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1 AND o.dummyDate >= :dummyDate_p2', Dummy::class), ], 'before but not equals + after but not equals (all properties enabled)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1 AND o.dummyDate > :dummyDate_p2', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1 AND o.dummyDate > :dummyDate_p2', Dummy::class), ], 'before + after' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1 AND o.dummyDate >= :dummyDate_p2', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1 AND o.dummyDate >= :dummyDate_p2', Dummy::class), ], 'before but not equals + after but not equals' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1 AND o.dummyDate > :dummyDate_p2', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1 AND o.dummyDate > :dummyDate_p2', Dummy::class), ], 'property not enabled' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'nested property' => [ - sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.dummyDate >= :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.dummyDate >= :dummyDate_p1', Dummy::class), ], 'after (exclude_null)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate IS NOT NULL AND o.dummyDate >= :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate IS NOT NULL AND o.dummyDate >= :dummyDate_p1', Dummy::class), ], 'after (include_null_after)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1 OR o.dummyDate IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1 OR o.dummyDate IS NULL', Dummy::class), ], 'include null before and after (include_null_before_and_after)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1 OR o.dummyDate IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1 OR o.dummyDate IS NULL', Dummy::class), ], 'bad date format' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], ] ); diff --git a/Tests/Filter/ExistsFilterTest.php b/Tests/Filter/ExistsFilterTest.php index 9f3f345..fa9f0a7 100644 --- a/Tests/Filter/ExistsFilterTest.php +++ b/Tests/Filter/ExistsFilterTest.php @@ -100,121 +100,121 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'valid values' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'valid values (empty for true)' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'valid values (1 for true)' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'invalid values' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), null, $existsFilterFactory, ], 'negative values' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NULL', Dummy::class), null, $existsFilterFactory, ], 'negative values (0)' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NULL', Dummy::class), null, $existsFilterFactory, ], 'multiple values (true and true)' => [ - sprintf('SELECT o FROM %s o WHERE o.alias IS NOT NULL AND o.description IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.alias IS NOT NULL AND o.description IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'multiple values (1 and 0)' => [ - sprintf('SELECT o FROM %s o WHERE o.alias IS NOT NULL AND o.description IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.alias IS NOT NULL AND o.description IS NULL', Dummy::class), null, $existsFilterFactory, ], 'multiple values (false and 0)' => [ - sprintf('SELECT o FROM %s o WHERE o.alias IS NULL AND o.description IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.alias IS NULL AND o.description IS NULL', Dummy::class), null, $existsFilterFactory, ], 'custom exists parameter name' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), null, $customExistsFilterFactory, ], 'related values' => [ - sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE o.description IS NOT NULL AND relatedDummy_a1.name IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE o.description IS NOT NULL AND relatedDummy_a1.name IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'not nullable values' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'related collection not empty' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummies IS NOT EMPTY', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummies IS NOT EMPTY', Dummy::class), null, $existsFilterFactory, ], 'related collection empty' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummies IS EMPTY', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummies IS EMPTY', Dummy::class), null, $existsFilterFactory, ], 'related association exists' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummy IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummy IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'related association does not exist' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummy IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummy IS NULL', Dummy::class), null, $existsFilterFactory, ], 'related owned association does not exist' => [ - sprintf('SELECT o FROM %s o LEFT JOIN o.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1 IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o LEFT JOIN o.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1 IS NULL', Dummy::class), null, $existsFilterFactory, ], 'related owned association exists' => [ - sprintf('SELECT o FROM %s o LEFT JOIN o.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1 IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o LEFT JOIN o.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1 IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'related owning association does not exist' => [ - sprintf('SELECT o FROM %s o WHERE o.relatedOwningDummy IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.relatedOwningDummy IS NULL', Dummy::class), null, $existsFilterFactory, ], 'related owning association exists' => [ - sprintf('SELECT o FROM %s o WHERE o.relatedOwningDummy IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.relatedOwningDummy IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], diff --git a/Tests/Filter/NumericFilterTest.php b/Tests/Filter/NumericFilterTest.php index d5a4eda..2ef8494 100644 --- a/Tests/Filter/NumericFilterTest.php +++ b/Tests/Filter/NumericFilterTest.php @@ -76,37 +76,37 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'numeric string (positive integer)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], 'multiple numeric string (positive integer)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice IN (:dummyPrice_p1)', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice IN (:dummyPrice_p1)', Dummy::class), ], 'multiple numeric string with one invalid property key' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], 'multiple numeric string with invalid value keys' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'multiple non-numeric' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'numeric string (negative integer)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], 'non-numeric' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'numeric string ("0")' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], 'nested property' => [ - sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.id = :id_p1', Dummy::class), + \sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.id = :id_p1', Dummy::class), ], 'mixed numeric and non-numeric' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], 'mixed numeric, non-numeric and invalid property' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], ] ); diff --git a/Tests/Filter/OrderFilterTest.php b/Tests/Filter/OrderFilterTest.php index 3838290..d3e4c77 100644 --- a/Tests/Filter/OrderFilterTest.php +++ b/Tests/Filter/OrderFilterTest.php @@ -302,119 +302,119 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'valid values' => [ - sprintf('SELECT o FROM %s o ORDER BY o.id ASC, o.name DESC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.id ASC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'invalid values' => [ - sprintf('SELECT o FROM %s o ORDER BY o.id ASC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.id ASC', Dummy::class), null, $orderFilterFactory, ], 'valid values (properties not enabled)' => [ - sprintf('SELECT o FROM %s o ORDER BY o.id ASC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.id ASC', Dummy::class), null, $orderFilterFactory, ], 'invalid values (properties not enabled)' => [ - sprintf('SELECT o FROM %s o ORDER BY o.name ASC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.name ASC', Dummy::class), null, $orderFilterFactory, ], 'invalid property (property not enabled)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), null, $orderFilterFactory, ], 'invalid property (property enabled)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), null, $orderFilterFactory, ], 'custom order parameter name' => [ - sprintf('SELECT o FROM %s o ORDER BY o.name DESC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.name DESC', Dummy::class), null, $customOrderFilterFactory, ], 'valid values (all properties enabled)' => [ - sprintf('SELECT o FROM %s o ORDER BY o.id ASC, o.name ASC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.id ASC, o.name ASC', Dummy::class), null, $orderFilterFactory, ], 'nested property' => [ - sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY o.id ASC, o.name DESC, relatedDummy_a1.symfony DESC', Dummy::class), + \sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY o.id ASC, o.name DESC, relatedDummy_a1.symfony DESC', Dummy::class), null, $orderFilterFactory, ], 'empty values with default sort direction' => [ - sprintf('SELECT o FROM %s o ORDER BY o.id ASC, o.name DESC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.id ASC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_smallest (asc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate ASC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate ASC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_smallest (desc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate DESC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate DESC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_largest (asc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate ASC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate ASC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_largest (desc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate DESC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate DESC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_always_first (asc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate ASC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate ASC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_always_first (desc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate DESC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate DESC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_always_last (asc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate ASC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate ASC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_always_last (desc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate DESC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate DESC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'not having order should not throw a deprecation (select unchanged)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), null, $orderFilterFactory, ], 'not nullable relation will be a LEFT JOIN' => [ - sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY relatedDummy_a1.name ASC', Dummy::class), + \sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY relatedDummy_a1.name ASC', Dummy::class), null, $orderFilterFactory, ], 'embedded' => [ - sprintf('SELECT o FROM %s o ORDER BY o.embeddedDummy.dummyName ASC', EmbeddedDummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.embeddedDummy.dummyName ASC', EmbeddedDummy::class), null, $orderFilterFactory, EmbeddedDummy::class, ], 'embedded with nulls_comparison' => [ - sprintf('SELECT o, CASE WHEN o.embeddedDummy.dummyName IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_embeddedDummy_dummyName_null_rank FROM %s o ORDER BY _o_embeddedDummy_dummyName_null_rank DESC, o.embeddedDummy.dummyName ASC', EmbeddedDummy::class), + \sprintf('SELECT o, CASE WHEN o.embeddedDummy.dummyName IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_embeddedDummy_dummyName_null_rank FROM %s o ORDER BY _o_embeddedDummy_dummyName_null_rank DESC, o.embeddedDummy.dummyName ASC', EmbeddedDummy::class), null, $orderFilterFactory, EmbeddedDummy::class, ], 'nullable field in relation will be a LEFT JOIN' => [ - sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY relatedDummy_a1.name ASC', Dummy::class), + \sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY relatedDummy_a1.name ASC', Dummy::class), null, $orderFilterFactory, ], diff --git a/Tests/Filter/RangeFilterTest.php b/Tests/Filter/RangeFilterTest.php index ac31b95..3fbdb9f 100644 --- a/Tests/Filter/RangeFilterTest.php +++ b/Tests/Filter/RangeFilterTest.php @@ -340,46 +340,46 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'between' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice BETWEEN :dummyPrice_p1_1 AND :dummyPrice_p1_2', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice BETWEEN :dummyPrice_p1_1 AND :dummyPrice_p1_2', Dummy::class), ], 'between (same values)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], 'between (too many operands)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'between (too few operands)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'between (non-numeric operands)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'lt' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice < :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice < :dummyPrice_p1', Dummy::class), ], 'lt (non-numeric)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'lte' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice <= :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice <= :dummyPrice_p1', Dummy::class), ], 'lte (non-numeric)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'gt' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice > :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice > :dummyPrice_p1', Dummy::class), ], 'gt (non-numeric)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'gte' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice >= :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice >= :dummyPrice_p1', Dummy::class), ], 'gte (non-numeric)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'lte + gte' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice >= :dummyPrice_p1 AND o.dummyPrice <= :dummyPrice_p2', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice >= :dummyPrice_p1 AND o.dummyPrice <= :dummyPrice_p2', Dummy::class), ], ] ); diff --git a/Tests/Filter/SearchFilterTest.php b/Tests/Filter/SearchFilterTest.php index 89f2e3f..0b11fb1 100644 --- a/Tests/Filter/SearchFilterTest.php +++ b/Tests/Filter/SearchFilterTest.php @@ -221,11 +221,11 @@ public function testDoubleJoin(): void $queryBuilder = $this->repository->createQueryBuilder(static::ALIAS); $filter = self::buildSearchFilter($this, $this->managerRegistry, ['relatedDummy.symfony' => null]); - $queryBuilder->innerJoin(sprintf('%s.relatedDummy', static::ALIAS), 'relateddummy_a1'); + $queryBuilder->innerJoin(\sprintf('%s.relatedDummy', static::ALIAS), 'relateddummy_a1'); $filter->apply($queryBuilder, new QueryNameGenerator(), $this->resourceClass, new Get(), ['filters' => $filters]); $actual = strtolower($queryBuilder->getQuery()->getDQL()); - $expected = strtolower(sprintf('SELECT %s FROM %s %1$s inner join %1$s.relatedDummy relateddummy_a1 WHERE relateddummy_a1.symfony = :symfony_p1', static::ALIAS, Dummy::class)); + $expected = strtolower(\sprintf('SELECT %s FROM %s %1$s inner join %1$s.relatedDummy relateddummy_a1 WHERE relateddummy_a1.symfony = :symfony_p1', static::ALIAS, Dummy::class)); $this->assertSame($actual, $expected); } @@ -236,12 +236,12 @@ public function testTripleJoin(): void $queryBuilder = $this->repository->createQueryBuilder(static::ALIAS); $filter = self::buildSearchFilter($this, $this->managerRegistry, ['relatedDummy.symfony' => null, 'relatedDummy.thirdLevel.level' => null]); - $queryBuilder->innerJoin(sprintf('%s.relatedDummy', static::ALIAS), 'relateddummy_a1'); + $queryBuilder->innerJoin(\sprintf('%s.relatedDummy', static::ALIAS), 'relateddummy_a1'); $queryBuilder->innerJoin('relateddummy_a1.thirdLevel', 'thirdLevel_a1'); $filter->apply($queryBuilder, new QueryNameGenerator(), $this->resourceClass, new Get(), ['filters' => $filters]); $actual = strtolower($queryBuilder->getQuery()->getDQL()); - $expected = strtolower(sprintf('SELECT %s FROM %s %1$s inner join %1$s.relatedDummy relateddummy_a1 inner join relateddummy_a1.thirdLevel thirdLevel_a1 WHERE relateddummy_a1.symfony = :symfony_p1 and thirdLevel_a1.level = :level_p2', static::ALIAS, Dummy::class)); + $expected = strtolower(\sprintf('SELECT %s FROM %s %1$s inner join %1$s.relatedDummy relateddummy_a1 inner join relateddummy_a1.thirdLevel thirdLevel_a1 WHERE relateddummy_a1.symfony = :symfony_p1 and thirdLevel_a1.level = :level_p2', static::ALIAS, Dummy::class)); $this->assertSame($actual, $expected); } @@ -250,13 +250,13 @@ public function testJoinLeft(): void $filters = ['relatedDummy.symfony' => 'foo', 'relatedDummy.thirdLevel.level' => '3']; $queryBuilder = $this->repository->createQueryBuilder(static::ALIAS); - $queryBuilder->leftJoin(sprintf('%s.relatedDummy', static::ALIAS), 'relateddummy_a1'); + $queryBuilder->leftJoin(\sprintf('%s.relatedDummy', static::ALIAS), 'relateddummy_a1'); $filter = self::buildSearchFilter($this, $this->managerRegistry, ['relatedDummy.symfony' => null, 'relatedDummy.thirdLevel.level' => null]); $filter->apply($queryBuilder, new QueryNameGenerator(), $this->resourceClass, new Get(), ['filters' => $filters]); $actual = strtolower($queryBuilder->getQuery()->getDQL()); - $expected = strtolower(sprintf('SELECT %s FROM %s %1$s left join %1$s.relatedDummy relateddummy_a1 left join relateddummy_a1.thirdLevel thirdLevel_a1 WHERE relateddummy_a1.symfony = :symfony_p1 and thirdLevel_a1.level = :level_p2', static::ALIAS, Dummy::class)); + $expected = strtolower(\sprintf('SELECT %s FROM %s %1$s left join %1$s.relatedDummy relateddummy_a1 left join relateddummy_a1.thirdLevel thirdLevel_a1 WHERE relateddummy_a1.symfony = :symfony_p1 and thirdLevel_a1.level = :level_p2', static::ALIAS, Dummy::class)); $this->assertSame($actual, $expected); } @@ -269,7 +269,7 @@ public function testApplyWithAnotherAlias(): void $filter = self::buildSearchFilter($this, $this->managerRegistry, ['id' => null, 'name' => null]); $filter->apply($queryBuilder, new QueryNameGenerator(), $this->resourceClass, new Get(), ['filters' => $filters]); - $expectedDql = sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name = :name_p1', 'somealias', Dummy::class); + $expectedDql = \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name = :name_p1', 'somealias', Dummy::class); $this->assertSame($expectedDql, $queryBuilder->getQuery()->getDQL()); } @@ -281,22 +281,22 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'exact' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name = :name_p1', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name = :name_p1', static::ALIAS, Dummy::class), ['name_p1' => 'exact'], $filterFactory, ], 'exact (case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) = LOWER(:name_p1)', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) = LOWER(:name_p1)', static::ALIAS, Dummy::class), ['name_p1' => 'exact'], $filterFactory, ], 'exact (case insensitive, with special characters)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) = LOWER(:name_p1)', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) = LOWER(:name_p1)', static::ALIAS, Dummy::class), ['name_p1' => 'exact (special)'], $filterFactory, ], 'exact (multiple values)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name IN(:name_p1)', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name IN(:name_p1)', static::ALIAS, Dummy::class), [ 'name_p1' => [ 'CaSE', @@ -306,7 +306,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'exact (multiple values; case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) IN(:name_p1)', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) IN(:name_p1)', static::ALIAS, Dummy::class), [ 'name_p1' => [ 'case', @@ -316,27 +316,27 @@ public static function provideApplyTestData(): array $filterFactory, ], 'invalid property' => [ - sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), [], $filterFactory, ], 'invalid values for relations' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name = :name_p1', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name = :name_p1', static::ALIAS, Dummy::class), [], $filterFactory, ], 'partial' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0, \'%%\')', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0, \'%%\')', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'partial (case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0, \'%%\'))', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'partial (multiple values)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(\'%%\', :name_p1_1, \'%%\')', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(\'%%\', :name_p1_1, \'%%\')', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'CaSE', 'name_p1_1' => 'SENSitive', @@ -344,7 +344,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'partial (multiple values; case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'case', 'name_p1_1' => 'insensitive', @@ -352,7 +352,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'partial (multiple almost same values; case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'blue car', 'name_p1_1' => 'blue car', @@ -360,17 +360,17 @@ public static function provideApplyTestData(): array $filterFactory, ], 'start' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(:name_p1_0, \'%%\')', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(:name_p1_0, \'%%\')', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'start (case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\'))', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'start (multiple values)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(:name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(:name_p1_1, \'%%\')', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(:name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(:name_p1_1, \'%%\')', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'CaSE', 'name_p1_1' => 'SENSitive', @@ -378,7 +378,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'start (multiple values; case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'case', 'name_p1_1' => 'insensitive', @@ -386,17 +386,17 @@ public static function provideApplyTestData(): array $filterFactory, ], 'end' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0)', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0)', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'end (case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0))', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'end (multiple values)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0) OR %1$s.name LIKE CONCAT(\'%%\', :name_p1_1)', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0) OR %1$s.name LIKE CONCAT(\'%%\', :name_p1_1)', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'CaSE', 'name_p1_1' => 'SENSitive', @@ -404,7 +404,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'end (multiple values; case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0)) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_1))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0)) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_1))', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'case', 'name_p1_1' => 'insensitive', @@ -412,17 +412,17 @@ public static function provideApplyTestData(): array $filterFactory, ], 'word_start' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(:name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(\'%% \', :name_p1_0, \'%%\')', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(:name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(\'%% \', :name_p1_0, \'%%\')', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'word_start (case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%% \', :name_p1_0, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%% \', :name_p1_0, \'%%\'))', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'word_start (multiple values)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE (%1$s.name LIKE CONCAT(:name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(\'%% \', :name_p1_0, \'%%\')) OR (%1$s.name LIKE CONCAT(:name_p1_1, \'%%\') OR %1$s.name LIKE CONCAT(\'%% \', :name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE (%1$s.name LIKE CONCAT(:name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(\'%% \', :name_p1_0, \'%%\')) OR (%1$s.name LIKE CONCAT(:name_p1_1, \'%%\') OR %1$s.name LIKE CONCAT(\'%% \', :name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'CaSE', 'name_p1_1' => 'SENSitive', @@ -430,7 +430,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'word_start (multiple values; case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE (LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%% \', :name_p1_0, \'%%\'))) OR (LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_1, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%% \', :name_p1_1, \'%%\')))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE (LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%% \', :name_p1_0, \'%%\'))) OR (LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_1, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%% \', :name_p1_1, \'%%\')))', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'case', 'name_p1_1' => 'insensitive', @@ -438,7 +438,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'invalid value for relation' => [ - sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), [], $filterFactory, ], @@ -451,17 +451,17 @@ public static function provideApplyTestData(): array [ 'relatedDummy' => '/related_dummie/1', ], - sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), [], $filterFactory, ], 'IRI value for relation' => [ - sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.id = :id_p1', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.id = :id_p1', static::ALIAS, Dummy::class), ['id_p1' => 1], $filterFactory, ], 'mixed IRI and entity ID values for relations' => [ - sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummies relatedDummies_a1 WHERE %1$s.relatedDummy IN(:relatedDummy_p1) AND relatedDummies_a1.id = :id_p2', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummies relatedDummies_a1 WHERE %1$s.relatedDummy IN(:relatedDummy_p1) AND relatedDummies_a1.id = :id_p2', static::ALIAS, Dummy::class), [ 'relatedDummy_p1' => [1, 2], 'id_p2' => 1, @@ -469,7 +469,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'nested property' => [ - sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummy relatedDummy_a1 WHERE %1$s.name = :name_p1 AND relatedDummy_a1.symfony = :symfony_p2', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummy relatedDummy_a1 WHERE %1$s.name = :name_p1 AND relatedDummy_a1.symfony = :symfony_p2', static::ALIAS, Dummy::class), [ 'name_p1' => 'exact', 'symfony_p2' => 'exact', @@ -477,24 +477,24 @@ public static function provideApplyTestData(): array $filterFactory, ], 'empty nested property' => [ - sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), [], $filterFactory, ], 'integer value' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.age = :age_p1', static::ALIAS, RelatedDummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.age = :age_p1', static::ALIAS, RelatedDummy::class), ['age_p1' => 46], $filterFactory, RelatedDummy::class, ], 'related owned one-to-one association' => [ - sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1.id = :id_p1', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1.id = :id_p1', static::ALIAS, Dummy::class), ['id_p1' => 1], $filterFactory, Dummy::class, ], 'related owning one-to-one association' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.relatedOwningDummy = :relatedOwningDummy_p1', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.relatedOwningDummy = :relatedOwningDummy_p1', static::ALIAS, Dummy::class), ['relatedOwningDummy_p1' => 1], $filterFactory, Dummy::class, diff --git a/Util/QueryBuilderHelper.php b/Util/QueryBuilderHelper.php index 7814f98..54956fc 100644 --- a/Util/QueryBuilderHelper.php +++ b/Util/QueryBuilderHelper.php @@ -57,7 +57,7 @@ public static function addJoinOnce(QueryBuilder $queryBuilder, QueryNameGenerato public static function getEntityClassByAlias(string $alias, QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): string { if (!\in_array($alias, $queryBuilder->getAllAliases(), true)) { - throw new \LogicException(sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias)); + throw new \LogicException(\sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias)); } $rootAliasMap = self::mapRootAliases($queryBuilder->getRootAliases(), $queryBuilder->getRootEntities()); @@ -73,7 +73,7 @@ public static function getEntityClassByAlias(string $alias, QueryBuilder $queryB } if (null === $metadata) { - throw new \LogicException(sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias)); + throw new \LogicException(\sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias)); } return $metadata->getName(); @@ -96,7 +96,7 @@ public static function findRootAlias(string $alias, QueryBuilder $queryBuilder): } } - throw new \LogicException(sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias)); + throw new \LogicException(\sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias)); } /** @@ -122,7 +122,7 @@ public static function traverseJoins(string $alias, QueryBuilder $queryBuilder, while (null === $apexEntityClass) { if (!isset($aliasMap[$currentAlias])) { - throw new \LogicException(sprintf('Unknown alias "%s".', $currentAlias)); + throw new \LogicException(\sprintf('Unknown alias "%s".', $currentAlias)); } if (\is_string($aliasMap[$currentAlias])) { @@ -171,7 +171,7 @@ public static function getExistingJoin(QueryBuilder $queryBuilder, string $alias foreach ($parts[$rootAlias] as $join) { /** @var Join $join */ - if (sprintf('%s.%s', $alias, $association) === $join->getJoin()) { + if (\sprintf('%s.%s', $alias, $association) === $join->getJoin()) { return $join; } } diff --git a/Util/QueryNameGenerator.php b/Util/QueryNameGenerator.php index 810a1e0..f6a47d2 100644 --- a/Util/QueryNameGenerator.php +++ b/Util/QueryNameGenerator.php @@ -30,7 +30,7 @@ final class QueryNameGenerator implements QueryNameGeneratorInterface */ public function generateJoinAlias(string $association): string { - return sprintf('%s_a%d', $association, $this->incrementedAssociation++); + return \sprintf('%s_a%d', $association, $this->incrementedAssociation++); } /** @@ -38,6 +38,6 @@ public function generateJoinAlias(string $association): string */ public function generateParameterName(string $name): string { - return sprintf('%s_p%d', str_replace('.', '_', $name), $this->incrementedName++); + return \sprintf('%s_p%d', str_replace('.', '_', $name), $this->incrementedName++); } } From e4212ba427fda19a7dc3dc027a1e6b74806a9bd7 Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 9 Aug 2024 09:49:50 +0200 Subject: [PATCH 24/72] style: cs fixes --- AbstractPaginator.php | 2 +- Extension/EagerLoadingExtension.php | 4 +- Extension/OrderExtension.php | 2 +- Filter/BooleanFilter.php | 2 +- Filter/DateFilter.php | 10 ++-- Filter/ExistsFilter.php | 8 +-- Filter/NumericFilter.php | 4 +- Filter/OrderFilter.php | 6 +-- Filter/RangeFilter.php | 16 +++--- Filter/SearchFilter.php | 14 ++--- PropertyHelperTrait.php | 2 +- Tests/AppKernel.php | 2 +- Tests/DoctrineOrmFilterTestCase.php | 4 +- Tests/Filter/BooleanFilterTest.php | 22 ++++---- Tests/Filter/DateFilterTest.php | 36 ++++++------- Tests/Filter/ExistsFilterTest.php | 40 +++++++-------- Tests/Filter/NumericFilterTest.php | 22 ++++---- Tests/Filter/OrderFilterTest.php | 46 ++++++++--------- Tests/Filter/RangeFilterTest.php | 28 +++++----- Tests/Filter/SearchFilterTest.php | 80 ++++++++++++++--------------- Util/QueryBuilderHelper.php | 10 ++-- Util/QueryNameGenerator.php | 4 +- 22 files changed, 182 insertions(+), 182 deletions(-) diff --git a/AbstractPaginator.php b/AbstractPaginator.php index a3c2a08..59595c7 100644 --- a/AbstractPaginator.php +++ b/AbstractPaginator.php @@ -33,7 +33,7 @@ public function __construct(DoctrinePaginator $paginator) $query = $paginator->getQuery(); if (null === ($firstResult = $query->getFirstResult()) || $firstResult < 0 || null === $maxResults = $query->getMaxResults()) { // @phpstan-ignore-line - throw new InvalidArgumentException(sprintf('"%1$s::setFirstResult()" or/and "%1$s::setMaxResults()" was/were not applied to the query.', Query::class)); + throw new InvalidArgumentException(\sprintf('"%1$s::setFirstResult()" or/and "%1$s::setMaxResults()" was/were not applied to the query.', Query::class)); } $this->paginator = $paginator; diff --git a/Extension/EagerLoadingExtension.php b/Extension/EagerLoadingExtension.php index bd0935d..aa1e08a 100644 --- a/Extension/EagerLoadingExtension.php +++ b/Extension/EagerLoadingExtension.php @@ -186,7 +186,7 @@ private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInt $method = $isLeftJoin ? 'leftJoin' : 'innerJoin'; $associationAlias = $queryNameGenerator->generateJoinAlias($association); - $queryBuilder->{$method}(sprintf('%s.%s', $parentAlias, $association), $associationAlias); + $queryBuilder->{$method}(\sprintf('%s.%s', $parentAlias, $association), $associationAlias); ++$joinCount; } @@ -264,7 +264,7 @@ private function addSelect(QueryBuilder $queryBuilder, string $entity, string $a } } - $queryBuilder->addSelect(sprintf('partial %s.{%s}', $associationAlias, implode(',', $select))); + $queryBuilder->addSelect(\sprintf('partial %s.{%s}', $associationAlias, implode(',', $select))); } private function addSelectOnce(QueryBuilder $queryBuilder, string $alias): void diff --git a/Extension/OrderExtension.php b/Extension/OrderExtension.php index 3989072..1d3deaf 100644 --- a/Extension/OrderExtension.php +++ b/Extension/OrderExtension.php @@ -67,7 +67,7 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator $field = "{$rootAlias}.{$field}"; } else { $alias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $rootAlias, substr($field, 0, $pos)); - $field = sprintf('%s.%s', $alias, substr($field, $pos + 1)); + $field = \sprintf('%s.%s', $alias, substr($field, $pos + 1)); } $queryBuilder->addOrderBy($field, $order); } diff --git a/Filter/BooleanFilter.php b/Filter/BooleanFilter.php index 2f230c4..e9f0a83 100644 --- a/Filter/BooleanFilter.php +++ b/Filter/BooleanFilter.php @@ -142,7 +142,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB $valueParameter = $queryNameGenerator->generateParameterName($field); $queryBuilder - ->andWhere(sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $value); } } diff --git a/Filter/DateFilter.php b/Filter/DateFilter.php index 066606e..8533ee3 100644 --- a/Filter/DateFilter.php +++ b/Filter/DateFilter.php @@ -161,7 +161,7 @@ protected function filterProperty(string $property, $values, QueryBuilder $query $type = (string) $this->getDoctrineFieldType($property, $resourceClass); if (self::EXCLUDE_NULL === $nullManagement) { - $queryBuilder->andWhere($queryBuilder->expr()->isNotNull(sprintf('%s.%s', $alias, $field))); + $queryBuilder->andWhere($queryBuilder->expr()->isNotNull(\sprintf('%s.%s', $alias, $field))); } if (isset($values[self::PARAMETER_BEFORE])) { @@ -234,7 +234,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf } catch (\Exception) { // Silently ignore this filter if it can not be transformed to a \DateTime $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('The field "%s" has a wrong date format. Use one accepted by the \DateTime constructor', $field)), + 'exception' => new InvalidArgumentException(\sprintf('The field "%s" has a wrong date format. Use one accepted by the \DateTime constructor', $field)), ]); return; @@ -247,7 +247,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf self::PARAMETER_AFTER => '>=', self::PARAMETER_STRICTLY_AFTER => '>', ]; - $baseWhere = sprintf('%s.%s %s :%s', $alias, $field, $operatorValue[$operator], $valueParameter); + $baseWhere = \sprintf('%s.%s %s :%s', $alias, $field, $operatorValue[$operator], $valueParameter); if (null === $nullManagement || self::EXCLUDE_NULL === $nullManagement) { $queryBuilder->andWhere($baseWhere); @@ -258,12 +258,12 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf ) { $queryBuilder->andWhere($queryBuilder->expr()->orX( $baseWhere, - $queryBuilder->expr()->isNull(sprintf('%s.%s', $alias, $field)) + $queryBuilder->expr()->isNull(\sprintf('%s.%s', $alias, $field)) )); } else { $queryBuilder->andWhere($queryBuilder->expr()->andX( $baseWhere, - $queryBuilder->expr()->isNotNull(sprintf('%s.%s', $alias, $field)) + $queryBuilder->expr()->isNotNull(\sprintf('%s.%s', $alias, $field)) )); } diff --git a/Filter/ExistsFilter.php b/Filter/ExistsFilter.php index 5f516c4..0dde78e 100644 --- a/Filter/ExistsFilter.php +++ b/Filter/ExistsFilter.php @@ -164,7 +164,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB if ($metadata->hasAssociation($field)) { if ($metadata->isCollectionValuedAssociation($field)) { $queryBuilder - ->andWhere(sprintf('%s.%s %s EMPTY', $alias, $field, $value ? 'IS NOT' : 'IS')); + ->andWhere(\sprintf('%s.%s %s EMPTY', $alias, $field, $value ? 'IS NOT' : 'IS')); return; } @@ -173,20 +173,20 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB $alias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $alias, $field, Join::LEFT_JOIN); $queryBuilder - ->andWhere(sprintf('%s %s NULL', $alias, $value ? 'IS NOT' : 'IS')); + ->andWhere(\sprintf('%s %s NULL', $alias, $value ? 'IS NOT' : 'IS')); return; } $queryBuilder - ->andWhere(sprintf('%s.%s %s NULL', $alias, $field, $value ? 'IS NOT' : 'IS')); + ->andWhere(\sprintf('%s.%s %s NULL', $alias, $field, $value ? 'IS NOT' : 'IS')); return; } if ($metadata->hasField($field)) { $queryBuilder - ->andWhere(sprintf('%s.%s %s NULL', $alias, $field, $value ? 'IS NOT' : 'IS')); + ->andWhere(\sprintf('%s.%s %s NULL', $alias, $field, $value ? 'IS NOT' : 'IS')); } } diff --git a/Filter/NumericFilter.php b/Filter/NumericFilter.php index c27e3f6..545b552 100644 --- a/Filter/NumericFilter.php +++ b/Filter/NumericFilter.php @@ -152,11 +152,11 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB if (1 === \count($values)) { $queryBuilder - ->andWhere(sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $values[0], (string) $this->getDoctrineFieldType($property, $resourceClass)); } else { $queryBuilder - ->andWhere(sprintf('%s.%s IN (:%s)', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s IN (:%s)', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $values); } } diff --git a/Filter/OrderFilter.php b/Filter/OrderFilter.php index 1987ee2..b2abdab 100644 --- a/Filter/OrderFilter.php +++ b/Filter/OrderFilter.php @@ -263,12 +263,12 @@ protected function filterProperty(string $property, $direction, QueryBuilder $qu if (null !== $nullsComparison = $this->properties[$property]['nulls_comparison'] ?? $this->orderNullsComparison) { $nullsDirection = self::NULLS_DIRECTION_MAP[$nullsComparison][$direction]; - $nullRankHiddenField = sprintf('_%s_%s_null_rank', $alias, str_replace('.', '_', $field)); + $nullRankHiddenField = \sprintf('_%s_%s_null_rank', $alias, str_replace('.', '_', $field)); - $queryBuilder->addSelect(sprintf('CASE WHEN %s.%s IS NULL THEN 0 ELSE 1 END AS HIDDEN %s', $alias, $field, $nullRankHiddenField)); + $queryBuilder->addSelect(\sprintf('CASE WHEN %s.%s IS NULL THEN 0 ELSE 1 END AS HIDDEN %s', $alias, $field, $nullRankHiddenField)); $queryBuilder->addOrderBy($nullRankHiddenField, $nullsDirection); } - $queryBuilder->addOrderBy(sprintf('%s.%s', $alias, $field), $direction); + $queryBuilder->addOrderBy(\sprintf('%s.%s', $alias, $field), $direction); } } diff --git a/Filter/RangeFilter.php b/Filter/RangeFilter.php index 70c3e68..85de711 100644 --- a/Filter/RangeFilter.php +++ b/Filter/RangeFilter.php @@ -164,16 +164,16 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf if ($rangeValue[0] === $rangeValue[1]) { $queryBuilder - ->andWhere(sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $rangeValue[0]); return; } $queryBuilder - ->andWhere(sprintf('%1$s.%2$s BETWEEN :%3$s_1 AND :%3$s_2', $alias, $field, $valueParameter)) - ->setParameter(sprintf('%s_1', $valueParameter), $rangeValue[0]) - ->setParameter(sprintf('%s_2', $valueParameter), $rangeValue[1]); + ->andWhere(\sprintf('%1$s.%2$s BETWEEN :%3$s_1 AND :%3$s_2', $alias, $field, $valueParameter)) + ->setParameter(\sprintf('%s_1', $valueParameter), $rangeValue[0]) + ->setParameter(\sprintf('%s_2', $valueParameter), $rangeValue[1]); break; case self::PARAMETER_GREATER_THAN: @@ -183,7 +183,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf } $queryBuilder - ->andWhere(sprintf('%s.%s > :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s > :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $value); break; @@ -194,7 +194,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf } $queryBuilder - ->andWhere(sprintf('%s.%s >= :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s >= :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $value); break; @@ -205,7 +205,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf } $queryBuilder - ->andWhere(sprintf('%s.%s < :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s < :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $value); break; @@ -216,7 +216,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf } $queryBuilder - ->andWhere(sprintf('%s.%s <= :%s', $alias, $field, $valueParameter)) + ->andWhere(\sprintf('%s.%s <= :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $value); break; diff --git a/Filter/SearchFilter.php b/Filter/SearchFilter.php index 61b35b7..d0caccb 100644 --- a/Filter/SearchFilter.php +++ b/Filter/SearchFilter.php @@ -203,7 +203,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB if (!$this->hasValidValues($values, $this->getDoctrineFieldType($property, $resourceClass))) { $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Values for field "%s" are not valid according to the doctrine type.', $field)), + 'exception' => new InvalidArgumentException(\sprintf('Values for field "%s" are not valid according to the doctrine type.', $field)), ]); return; @@ -241,7 +241,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB */ if (!$this->hasValidValues([$value], $doctrineTypeField)) { $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Values for field "%s" are not valid according to the doctrine type.', $associationFieldIdentifier)), + 'exception' => new InvalidArgumentException(\sprintf('Values for field "%s" are not valid according to the doctrine type.', $associationFieldIdentifier)), ]); return null; @@ -258,7 +258,7 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB * Shouldn't this actually fail harder? */ $this->logger->notice('Invalid filter ignored', [ - 'exception' => new InvalidArgumentException(sprintf('Values for field "%s" are not valid according to the doctrine type.', $field)), + 'exception' => new InvalidArgumentException(\sprintf('Values for field "%s" are not valid according to the doctrine type.', $field)), ]); return; @@ -287,7 +287,7 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild $wrapCase = $this->createWrapCase($caseSensitive); $valueParameter = ':'.$queryNameGenerator->generateParameterName($field); - $aliasedField = sprintf('%s.%s', $alias, $field); + $aliasedField = \sprintf('%s.%s', $alias, $field); if (!$strategy || self::STRATEGY_EXACT === $strategy) { if (1 === \count($values)) { @@ -308,7 +308,7 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild $ors = []; $parameters = []; foreach ($values as $key => $value) { - $keyValueParameter = sprintf('%s_%s', $valueParameter, $key); + $keyValueParameter = \sprintf('%s_%s', $valueParameter, $key); $parameters[] = [$caseSensitive ? $value : strtolower($value), $keyValueParameter]; $ors[] = match ($strategy) { @@ -334,7 +334,7 @@ protected function addWhereByStrategy(string $strategy, QueryBuilder $queryBuild $wrapCase((string) $queryBuilder->expr()->concat("'% '", $keyValueParameter, "'%'")) ) ), - default => throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy)), + default => throw new InvalidArgumentException(\sprintf('strategy %s does not exist.', $strategy)), }; } @@ -358,7 +358,7 @@ protected function createWrapCase(bool $caseSensitive): \Closure return $expr; } - return sprintf('LOWER(%s)', $expr); + return \sprintf('LOWER(%s)', $expr); }; } diff --git a/PropertyHelperTrait.php b/PropertyHelperTrait.php index a8086ca..8431e3e 100644 --- a/PropertyHelperTrait.php +++ b/PropertyHelperTrait.php @@ -73,7 +73,7 @@ protected function addJoinsForNestedProperty(string $property, string $rootAlias } if (null === $alias) { - throw new InvalidArgumentException(sprintf('Cannot add joins for property "%s" - property is not nested.', $property)); + throw new InvalidArgumentException(\sprintf('Cannot add joins for property "%s" - property is not nested.', $property)); } return [$alias, $propertyParts['field'], $propertyParts['associations']]; diff --git a/Tests/AppKernel.php b/Tests/AppKernel.php index 3f1efec..94b796b 100644 --- a/Tests/AppKernel.php +++ b/Tests/AppKernel.php @@ -44,7 +44,7 @@ public function registerBundles(): array new FrameworkBundle(), new DoctrineBundle(), new TestBundle(), - new class() extends Bundle { + new class extends Bundle { public function shutdown(): void { restore_exception_handler(); diff --git a/Tests/DoctrineOrmFilterTestCase.php b/Tests/DoctrineOrmFilterTestCase.php index 9a2c0e9..1b8b25c 100644 --- a/Tests/DoctrineOrmFilterTestCase.php +++ b/Tests/DoctrineOrmFilterTestCase.php @@ -76,8 +76,8 @@ protected function doTestApply(?array $properties, array $filterParameters, stri foreach ($expectedParameters as $parameterName => $expectedParameterValue) { $queryParameter = $queryBuilder->getQuery()->getParameter($parameterName); - $this->assertNotNull($queryParameter, sprintf('Expected query parameter "%s" to be set', $parameterName)); - $this->assertEquals($expectedParameterValue, $queryParameter->getValue(), sprintf('Expected query parameter "%s" to be "%s"', $parameterName, var_export($expectedParameterValue, true))); + $this->assertNotNull($queryParameter, \sprintf('Expected query parameter "%s" to be set', $parameterName)); + $this->assertEquals($expectedParameterValue, $queryParameter->getValue(), \sprintf('Expected query parameter "%s" to be "%s"', $parameterName, var_export($expectedParameterValue, true))); } } diff --git a/Tests/Filter/BooleanFilterTest.php b/Tests/Filter/BooleanFilterTest.php index 1fdd6aa..0be3965 100644 --- a/Tests/Filter/BooleanFilterTest.php +++ b/Tests/Filter/BooleanFilterTest.php @@ -32,37 +32,37 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'string ("true")' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), ], 'string ("false")' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), ], 'non-boolean' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'numeric string ("0")' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), ], 'numeric string ("1")' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), ], 'nested properties' => [ - sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.dummyBoolean = :dummyBoolean_p1', Dummy::class), + \sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.dummyBoolean = :dummyBoolean_p1', Dummy::class), ], 'numeric string ("1") on non-boolean property' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'numeric string ("0") on non-boolean property' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'string ("true") on non-boolean property' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'string ("false") on non-boolean property' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'mixed boolean, non-boolean and invalid property' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyBoolean = :dummyBoolean_p1', Dummy::class), ], ] ); diff --git a/Tests/Filter/DateFilterTest.php b/Tests/Filter/DateFilterTest.php index 01d5e72..cd66dd1 100644 --- a/Tests/Filter/DateFilterTest.php +++ b/Tests/Filter/DateFilterTest.php @@ -62,58 +62,58 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'after (all properties enabled)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1', Dummy::class), ], 'after but not equals (all properties enabled)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate > :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate > :dummyDate_p1', Dummy::class), ], 'after' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1', Dummy::class), ], 'after but not equals' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate > :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate > :dummyDate_p1', Dummy::class), ], 'before (all properties enabled)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1', Dummy::class), ], 'before but not equals (all properties enabled)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1', Dummy::class), ], 'before' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1', Dummy::class), ], 'before but not equals' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1', Dummy::class), ], 'before + after (all properties enabled)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1 AND o.dummyDate >= :dummyDate_p2', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1 AND o.dummyDate >= :dummyDate_p2', Dummy::class), ], 'before but not equals + after but not equals (all properties enabled)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1 AND o.dummyDate > :dummyDate_p2', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1 AND o.dummyDate > :dummyDate_p2', Dummy::class), ], 'before + after' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1 AND o.dummyDate >= :dummyDate_p2', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate <= :dummyDate_p1 AND o.dummyDate >= :dummyDate_p2', Dummy::class), ], 'before but not equals + after but not equals' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1 AND o.dummyDate > :dummyDate_p2', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate < :dummyDate_p1 AND o.dummyDate > :dummyDate_p2', Dummy::class), ], 'property not enabled' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'nested property' => [ - sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.dummyDate >= :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.dummyDate >= :dummyDate_p1', Dummy::class), ], 'after (exclude_null)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate IS NOT NULL AND o.dummyDate >= :dummyDate_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate IS NOT NULL AND o.dummyDate >= :dummyDate_p1', Dummy::class), ], 'after (include_null_after)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1 OR o.dummyDate IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1 OR o.dummyDate IS NULL', Dummy::class), ], 'include null before and after (include_null_before_and_after)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1 OR o.dummyDate IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyDate >= :dummyDate_p1 OR o.dummyDate IS NULL', Dummy::class), ], 'bad date format' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], ] ); diff --git a/Tests/Filter/ExistsFilterTest.php b/Tests/Filter/ExistsFilterTest.php index 9f3f345..fa9f0a7 100644 --- a/Tests/Filter/ExistsFilterTest.php +++ b/Tests/Filter/ExistsFilterTest.php @@ -100,121 +100,121 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'valid values' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'valid values (empty for true)' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'valid values (1 for true)' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'invalid values' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), null, $existsFilterFactory, ], 'negative values' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NULL', Dummy::class), null, $existsFilterFactory, ], 'negative values (0)' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NULL', Dummy::class), null, $existsFilterFactory, ], 'multiple values (true and true)' => [ - sprintf('SELECT o FROM %s o WHERE o.alias IS NOT NULL AND o.description IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.alias IS NOT NULL AND o.description IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'multiple values (1 and 0)' => [ - sprintf('SELECT o FROM %s o WHERE o.alias IS NOT NULL AND o.description IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.alias IS NOT NULL AND o.description IS NULL', Dummy::class), null, $existsFilterFactory, ], 'multiple values (false and 0)' => [ - sprintf('SELECT o FROM %s o WHERE o.alias IS NULL AND o.description IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.alias IS NULL AND o.description IS NULL', Dummy::class), null, $existsFilterFactory, ], 'custom exists parameter name' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), null, $customExistsFilterFactory, ], 'related values' => [ - sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE o.description IS NOT NULL AND relatedDummy_a1.name IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE o.description IS NOT NULL AND relatedDummy_a1.name IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'not nullable values' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'related collection not empty' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummies IS NOT EMPTY', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummies IS NOT EMPTY', Dummy::class), null, $existsFilterFactory, ], 'related collection empty' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummies IS EMPTY', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummies IS EMPTY', Dummy::class), null, $existsFilterFactory, ], 'related association exists' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummy IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummy IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'related association does not exist' => [ - sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummy IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL AND o.relatedDummy IS NULL', Dummy::class), null, $existsFilterFactory, ], 'related owned association does not exist' => [ - sprintf('SELECT o FROM %s o LEFT JOIN o.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1 IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o LEFT JOIN o.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1 IS NULL', Dummy::class), null, $existsFilterFactory, ], 'related owned association exists' => [ - sprintf('SELECT o FROM %s o LEFT JOIN o.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1 IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o LEFT JOIN o.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1 IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], 'related owning association does not exist' => [ - sprintf('SELECT o FROM %s o WHERE o.relatedOwningDummy IS NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.relatedOwningDummy IS NULL', Dummy::class), null, $existsFilterFactory, ], 'related owning association exists' => [ - sprintf('SELECT o FROM %s o WHERE o.relatedOwningDummy IS NOT NULL', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.relatedOwningDummy IS NOT NULL', Dummy::class), null, $existsFilterFactory, ], diff --git a/Tests/Filter/NumericFilterTest.php b/Tests/Filter/NumericFilterTest.php index d5a4eda..2ef8494 100644 --- a/Tests/Filter/NumericFilterTest.php +++ b/Tests/Filter/NumericFilterTest.php @@ -76,37 +76,37 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'numeric string (positive integer)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], 'multiple numeric string (positive integer)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice IN (:dummyPrice_p1)', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice IN (:dummyPrice_p1)', Dummy::class), ], 'multiple numeric string with one invalid property key' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], 'multiple numeric string with invalid value keys' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'multiple non-numeric' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'numeric string (negative integer)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], 'non-numeric' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'numeric string ("0")' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], 'nested property' => [ - sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.id = :id_p1', Dummy::class), + \sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.id = :id_p1', Dummy::class), ], 'mixed numeric and non-numeric' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], 'mixed numeric, non-numeric and invalid property' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], ] ); diff --git a/Tests/Filter/OrderFilterTest.php b/Tests/Filter/OrderFilterTest.php index 8864928..f75fb5a 100644 --- a/Tests/Filter/OrderFilterTest.php +++ b/Tests/Filter/OrderFilterTest.php @@ -323,119 +323,119 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'valid values' => [ - sprintf('SELECT o FROM %s o ORDER BY o.id ASC, o.name DESC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.id ASC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'invalid values' => [ - sprintf('SELECT o FROM %s o ORDER BY o.id ASC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.id ASC', Dummy::class), null, $orderFilterFactory, ], 'valid values (properties not enabled)' => [ - sprintf('SELECT o FROM %s o ORDER BY o.id ASC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.id ASC', Dummy::class), null, $orderFilterFactory, ], 'invalid values (properties not enabled)' => [ - sprintf('SELECT o FROM %s o ORDER BY o.name ASC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.name ASC', Dummy::class), null, $orderFilterFactory, ], 'invalid property (property not enabled)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), null, $orderFilterFactory, ], 'invalid property (property enabled)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), null, $orderFilterFactory, ], 'custom order parameter name' => [ - sprintf('SELECT o FROM %s o ORDER BY o.name DESC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.name DESC', Dummy::class), null, $customOrderFilterFactory, ], 'valid values (all properties enabled)' => [ - sprintf('SELECT o FROM %s o ORDER BY o.id ASC, o.name ASC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.id ASC, o.name ASC', Dummy::class), null, $orderFilterFactory, ], 'nested property' => [ - sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY o.id ASC, o.name DESC, relatedDummy_a1.symfony DESC', Dummy::class), + \sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY o.id ASC, o.name DESC, relatedDummy_a1.symfony DESC', Dummy::class), null, $orderFilterFactory, ], 'empty values with default sort direction' => [ - sprintf('SELECT o FROM %s o ORDER BY o.id ASC, o.name DESC', Dummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.id ASC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_smallest (asc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate ASC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate ASC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_smallest (desc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate DESC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate DESC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_largest (asc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate ASC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate ASC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_largest (desc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate DESC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate DESC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_always_first (asc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate ASC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate ASC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_always_first (desc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate DESC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank ASC, o.dummyDate DESC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_always_last (asc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate ASC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate ASC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'nulls_always_last (desc)' => [ - sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate DESC, o.name DESC', Dummy::class), + \sprintf('SELECT o, CASE WHEN o.dummyDate IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dummyDate_null_rank FROM %s o ORDER BY _o_dummyDate_null_rank DESC, o.dummyDate DESC, o.name DESC', Dummy::class), null, $orderFilterFactory, ], 'not having order should not throw a deprecation (select unchanged)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), null, $orderFilterFactory, ], 'not nullable relation will be a LEFT JOIN' => [ - sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY relatedDummy_a1.name ASC', Dummy::class), + \sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY relatedDummy_a1.name ASC', Dummy::class), null, $orderFilterFactory, ], 'embedded' => [ - sprintf('SELECT o FROM %s o ORDER BY o.embeddedDummy.dummyName ASC', EmbeddedDummy::class), + \sprintf('SELECT o FROM %s o ORDER BY o.embeddedDummy.dummyName ASC', EmbeddedDummy::class), null, $orderFilterFactory, EmbeddedDummy::class, ], 'embedded with nulls_comparison' => [ - sprintf('SELECT o, CASE WHEN o.embeddedDummy.dummyName IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_embeddedDummy_dummyName_null_rank FROM %s o ORDER BY _o_embeddedDummy_dummyName_null_rank DESC, o.embeddedDummy.dummyName ASC', EmbeddedDummy::class), + \sprintf('SELECT o, CASE WHEN o.embeddedDummy.dummyName IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_embeddedDummy_dummyName_null_rank FROM %s o ORDER BY _o_embeddedDummy_dummyName_null_rank DESC, o.embeddedDummy.dummyName ASC', EmbeddedDummy::class), null, $orderFilterFactory, EmbeddedDummy::class, ], 'nullable field in relation will be a LEFT JOIN' => [ - sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY relatedDummy_a1.name ASC', Dummy::class), + \sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY relatedDummy_a1.name ASC', Dummy::class), null, $orderFilterFactory, ], diff --git a/Tests/Filter/RangeFilterTest.php b/Tests/Filter/RangeFilterTest.php index ac31b95..3fbdb9f 100644 --- a/Tests/Filter/RangeFilterTest.php +++ b/Tests/Filter/RangeFilterTest.php @@ -340,46 +340,46 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'between' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice BETWEEN :dummyPrice_p1_1 AND :dummyPrice_p1_2', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice BETWEEN :dummyPrice_p1_1 AND :dummyPrice_p1_2', Dummy::class), ], 'between (same values)' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice = :dummyPrice_p1', Dummy::class), ], 'between (too many operands)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'between (too few operands)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'between (non-numeric operands)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'lt' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice < :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice < :dummyPrice_p1', Dummy::class), ], 'lt (non-numeric)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'lte' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice <= :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice <= :dummyPrice_p1', Dummy::class), ], 'lte (non-numeric)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'gt' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice > :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice > :dummyPrice_p1', Dummy::class), ], 'gt (non-numeric)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'gte' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice >= :dummyPrice_p1', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice >= :dummyPrice_p1', Dummy::class), ], 'gte (non-numeric)' => [ - sprintf('SELECT o FROM %s o', Dummy::class), + \sprintf('SELECT o FROM %s o', Dummy::class), ], 'lte + gte' => [ - sprintf('SELECT o FROM %s o WHERE o.dummyPrice >= :dummyPrice_p1 AND o.dummyPrice <= :dummyPrice_p2', Dummy::class), + \sprintf('SELECT o FROM %s o WHERE o.dummyPrice >= :dummyPrice_p1 AND o.dummyPrice <= :dummyPrice_p2', Dummy::class), ], ] ); diff --git a/Tests/Filter/SearchFilterTest.php b/Tests/Filter/SearchFilterTest.php index 89f2e3f..0b11fb1 100644 --- a/Tests/Filter/SearchFilterTest.php +++ b/Tests/Filter/SearchFilterTest.php @@ -221,11 +221,11 @@ public function testDoubleJoin(): void $queryBuilder = $this->repository->createQueryBuilder(static::ALIAS); $filter = self::buildSearchFilter($this, $this->managerRegistry, ['relatedDummy.symfony' => null]); - $queryBuilder->innerJoin(sprintf('%s.relatedDummy', static::ALIAS), 'relateddummy_a1'); + $queryBuilder->innerJoin(\sprintf('%s.relatedDummy', static::ALIAS), 'relateddummy_a1'); $filter->apply($queryBuilder, new QueryNameGenerator(), $this->resourceClass, new Get(), ['filters' => $filters]); $actual = strtolower($queryBuilder->getQuery()->getDQL()); - $expected = strtolower(sprintf('SELECT %s FROM %s %1$s inner join %1$s.relatedDummy relateddummy_a1 WHERE relateddummy_a1.symfony = :symfony_p1', static::ALIAS, Dummy::class)); + $expected = strtolower(\sprintf('SELECT %s FROM %s %1$s inner join %1$s.relatedDummy relateddummy_a1 WHERE relateddummy_a1.symfony = :symfony_p1', static::ALIAS, Dummy::class)); $this->assertSame($actual, $expected); } @@ -236,12 +236,12 @@ public function testTripleJoin(): void $queryBuilder = $this->repository->createQueryBuilder(static::ALIAS); $filter = self::buildSearchFilter($this, $this->managerRegistry, ['relatedDummy.symfony' => null, 'relatedDummy.thirdLevel.level' => null]); - $queryBuilder->innerJoin(sprintf('%s.relatedDummy', static::ALIAS), 'relateddummy_a1'); + $queryBuilder->innerJoin(\sprintf('%s.relatedDummy', static::ALIAS), 'relateddummy_a1'); $queryBuilder->innerJoin('relateddummy_a1.thirdLevel', 'thirdLevel_a1'); $filter->apply($queryBuilder, new QueryNameGenerator(), $this->resourceClass, new Get(), ['filters' => $filters]); $actual = strtolower($queryBuilder->getQuery()->getDQL()); - $expected = strtolower(sprintf('SELECT %s FROM %s %1$s inner join %1$s.relatedDummy relateddummy_a1 inner join relateddummy_a1.thirdLevel thirdLevel_a1 WHERE relateddummy_a1.symfony = :symfony_p1 and thirdLevel_a1.level = :level_p2', static::ALIAS, Dummy::class)); + $expected = strtolower(\sprintf('SELECT %s FROM %s %1$s inner join %1$s.relatedDummy relateddummy_a1 inner join relateddummy_a1.thirdLevel thirdLevel_a1 WHERE relateddummy_a1.symfony = :symfony_p1 and thirdLevel_a1.level = :level_p2', static::ALIAS, Dummy::class)); $this->assertSame($actual, $expected); } @@ -250,13 +250,13 @@ public function testJoinLeft(): void $filters = ['relatedDummy.symfony' => 'foo', 'relatedDummy.thirdLevel.level' => '3']; $queryBuilder = $this->repository->createQueryBuilder(static::ALIAS); - $queryBuilder->leftJoin(sprintf('%s.relatedDummy', static::ALIAS), 'relateddummy_a1'); + $queryBuilder->leftJoin(\sprintf('%s.relatedDummy', static::ALIAS), 'relateddummy_a1'); $filter = self::buildSearchFilter($this, $this->managerRegistry, ['relatedDummy.symfony' => null, 'relatedDummy.thirdLevel.level' => null]); $filter->apply($queryBuilder, new QueryNameGenerator(), $this->resourceClass, new Get(), ['filters' => $filters]); $actual = strtolower($queryBuilder->getQuery()->getDQL()); - $expected = strtolower(sprintf('SELECT %s FROM %s %1$s left join %1$s.relatedDummy relateddummy_a1 left join relateddummy_a1.thirdLevel thirdLevel_a1 WHERE relateddummy_a1.symfony = :symfony_p1 and thirdLevel_a1.level = :level_p2', static::ALIAS, Dummy::class)); + $expected = strtolower(\sprintf('SELECT %s FROM %s %1$s left join %1$s.relatedDummy relateddummy_a1 left join relateddummy_a1.thirdLevel thirdLevel_a1 WHERE relateddummy_a1.symfony = :symfony_p1 and thirdLevel_a1.level = :level_p2', static::ALIAS, Dummy::class)); $this->assertSame($actual, $expected); } @@ -269,7 +269,7 @@ public function testApplyWithAnotherAlias(): void $filter = self::buildSearchFilter($this, $this->managerRegistry, ['id' => null, 'name' => null]); $filter->apply($queryBuilder, new QueryNameGenerator(), $this->resourceClass, new Get(), ['filters' => $filters]); - $expectedDql = sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name = :name_p1', 'somealias', Dummy::class); + $expectedDql = \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name = :name_p1', 'somealias', Dummy::class); $this->assertSame($expectedDql, $queryBuilder->getQuery()->getDQL()); } @@ -281,22 +281,22 @@ public static function provideApplyTestData(): array self::provideApplyTestArguments(), [ 'exact' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name = :name_p1', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name = :name_p1', static::ALIAS, Dummy::class), ['name_p1' => 'exact'], $filterFactory, ], 'exact (case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) = LOWER(:name_p1)', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) = LOWER(:name_p1)', static::ALIAS, Dummy::class), ['name_p1' => 'exact'], $filterFactory, ], 'exact (case insensitive, with special characters)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) = LOWER(:name_p1)', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) = LOWER(:name_p1)', static::ALIAS, Dummy::class), ['name_p1' => 'exact (special)'], $filterFactory, ], 'exact (multiple values)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name IN(:name_p1)', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name IN(:name_p1)', static::ALIAS, Dummy::class), [ 'name_p1' => [ 'CaSE', @@ -306,7 +306,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'exact (multiple values; case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) IN(:name_p1)', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) IN(:name_p1)', static::ALIAS, Dummy::class), [ 'name_p1' => [ 'case', @@ -316,27 +316,27 @@ public static function provideApplyTestData(): array $filterFactory, ], 'invalid property' => [ - sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), [], $filterFactory, ], 'invalid values for relations' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name = :name_p1', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name = :name_p1', static::ALIAS, Dummy::class), [], $filterFactory, ], 'partial' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0, \'%%\')', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0, \'%%\')', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'partial (case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0, \'%%\'))', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'partial (multiple values)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(\'%%\', :name_p1_1, \'%%\')', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(\'%%\', :name_p1_1, \'%%\')', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'CaSE', 'name_p1_1' => 'SENSitive', @@ -344,7 +344,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'partial (multiple values; case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'case', 'name_p1_1' => 'insensitive', @@ -352,7 +352,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'partial (multiple almost same values; case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'blue car', 'name_p1_1' => 'blue car', @@ -360,17 +360,17 @@ public static function provideApplyTestData(): array $filterFactory, ], 'start' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(:name_p1_0, \'%%\')', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(:name_p1_0, \'%%\')', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'start (case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\'))', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'start (multiple values)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(:name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(:name_p1_1, \'%%\')', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(:name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(:name_p1_1, \'%%\')', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'CaSE', 'name_p1_1' => 'SENSitive', @@ -378,7 +378,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'start (multiple values; case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'case', 'name_p1_1' => 'insensitive', @@ -386,17 +386,17 @@ public static function provideApplyTestData(): array $filterFactory, ], 'end' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0)', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0)', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'end (case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0))', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'end (multiple values)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0) OR %1$s.name LIKE CONCAT(\'%%\', :name_p1_1)', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(\'%%\', :name_p1_0) OR %1$s.name LIKE CONCAT(\'%%\', :name_p1_1)', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'CaSE', 'name_p1_1' => 'SENSitive', @@ -404,7 +404,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'end (multiple values; case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0)) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_1))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_0)) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%%\', :name_p1_1))', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'case', 'name_p1_1' => 'insensitive', @@ -412,17 +412,17 @@ public static function provideApplyTestData(): array $filterFactory, ], 'word_start' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(:name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(\'%% \', :name_p1_0, \'%%\')', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.name LIKE CONCAT(:name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(\'%% \', :name_p1_0, \'%%\')', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'word_start (case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%% \', :name_p1_0, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%% \', :name_p1_0, \'%%\'))', static::ALIAS, Dummy::class), ['name_p1_0' => 'partial'], $filterFactory, ], 'word_start (multiple values)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE (%1$s.name LIKE CONCAT(:name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(\'%% \', :name_p1_0, \'%%\')) OR (%1$s.name LIKE CONCAT(:name_p1_1, \'%%\') OR %1$s.name LIKE CONCAT(\'%% \', :name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE (%1$s.name LIKE CONCAT(:name_p1_0, \'%%\') OR %1$s.name LIKE CONCAT(\'%% \', :name_p1_0, \'%%\')) OR (%1$s.name LIKE CONCAT(:name_p1_1, \'%%\') OR %1$s.name LIKE CONCAT(\'%% \', :name_p1_1, \'%%\'))', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'CaSE', 'name_p1_1' => 'SENSitive', @@ -430,7 +430,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'word_start (multiple values; case insensitive)' => [ - sprintf('SELECT %s FROM %s %1$s WHERE (LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%% \', :name_p1_0, \'%%\'))) OR (LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_1, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%% \', :name_p1_1, \'%%\')))', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE (LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_0, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%% \', :name_p1_0, \'%%\'))) OR (LOWER(%1$s.name) LIKE LOWER(CONCAT(:name_p1_1, \'%%\')) OR LOWER(%1$s.name) LIKE LOWER(CONCAT(\'%% \', :name_p1_1, \'%%\')))', static::ALIAS, Dummy::class), [ 'name_p1_0' => 'case', 'name_p1_1' => 'insensitive', @@ -438,7 +438,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'invalid value for relation' => [ - sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), [], $filterFactory, ], @@ -451,17 +451,17 @@ public static function provideApplyTestData(): array [ 'relatedDummy' => '/related_dummie/1', ], - sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), [], $filterFactory, ], 'IRI value for relation' => [ - sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.id = :id_p1', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.id = :id_p1', static::ALIAS, Dummy::class), ['id_p1' => 1], $filterFactory, ], 'mixed IRI and entity ID values for relations' => [ - sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummies relatedDummies_a1 WHERE %1$s.relatedDummy IN(:relatedDummy_p1) AND relatedDummies_a1.id = :id_p2', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummies relatedDummies_a1 WHERE %1$s.relatedDummy IN(:relatedDummy_p1) AND relatedDummies_a1.id = :id_p2', static::ALIAS, Dummy::class), [ 'relatedDummy_p1' => [1, 2], 'id_p2' => 1, @@ -469,7 +469,7 @@ public static function provideApplyTestData(): array $filterFactory, ], 'nested property' => [ - sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummy relatedDummy_a1 WHERE %1$s.name = :name_p1 AND relatedDummy_a1.symfony = :symfony_p2', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedDummy relatedDummy_a1 WHERE %1$s.name = :name_p1 AND relatedDummy_a1.symfony = :symfony_p2', static::ALIAS, Dummy::class), [ 'name_p1' => 'exact', 'symfony_p2' => 'exact', @@ -477,24 +477,24 @@ public static function provideApplyTestData(): array $filterFactory, ], 'empty nested property' => [ - sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s', static::ALIAS, Dummy::class), [], $filterFactory, ], 'integer value' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.age = :age_p1', static::ALIAS, RelatedDummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.age = :age_p1', static::ALIAS, RelatedDummy::class), ['age_p1' => 46], $filterFactory, RelatedDummy::class, ], 'related owned one-to-one association' => [ - sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1.id = :id_p1', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s INNER JOIN %1$s.relatedOwnedDummy relatedOwnedDummy_a1 WHERE relatedOwnedDummy_a1.id = :id_p1', static::ALIAS, Dummy::class), ['id_p1' => 1], $filterFactory, Dummy::class, ], 'related owning one-to-one association' => [ - sprintf('SELECT %s FROM %s %1$s WHERE %1$s.relatedOwningDummy = :relatedOwningDummy_p1', static::ALIAS, Dummy::class), + \sprintf('SELECT %s FROM %s %1$s WHERE %1$s.relatedOwningDummy = :relatedOwningDummy_p1', static::ALIAS, Dummy::class), ['relatedOwningDummy_p1' => 1], $filterFactory, Dummy::class, diff --git a/Util/QueryBuilderHelper.php b/Util/QueryBuilderHelper.php index 7814f98..54956fc 100644 --- a/Util/QueryBuilderHelper.php +++ b/Util/QueryBuilderHelper.php @@ -57,7 +57,7 @@ public static function addJoinOnce(QueryBuilder $queryBuilder, QueryNameGenerato public static function getEntityClassByAlias(string $alias, QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): string { if (!\in_array($alias, $queryBuilder->getAllAliases(), true)) { - throw new \LogicException(sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias)); + throw new \LogicException(\sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias)); } $rootAliasMap = self::mapRootAliases($queryBuilder->getRootAliases(), $queryBuilder->getRootEntities()); @@ -73,7 +73,7 @@ public static function getEntityClassByAlias(string $alias, QueryBuilder $queryB } if (null === $metadata) { - throw new \LogicException(sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias)); + throw new \LogicException(\sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias)); } return $metadata->getName(); @@ -96,7 +96,7 @@ public static function findRootAlias(string $alias, QueryBuilder $queryBuilder): } } - throw new \LogicException(sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias)); + throw new \LogicException(\sprintf('The alias "%s" does not exist in the QueryBuilder.', $alias)); } /** @@ -122,7 +122,7 @@ public static function traverseJoins(string $alias, QueryBuilder $queryBuilder, while (null === $apexEntityClass) { if (!isset($aliasMap[$currentAlias])) { - throw new \LogicException(sprintf('Unknown alias "%s".', $currentAlias)); + throw new \LogicException(\sprintf('Unknown alias "%s".', $currentAlias)); } if (\is_string($aliasMap[$currentAlias])) { @@ -171,7 +171,7 @@ public static function getExistingJoin(QueryBuilder $queryBuilder, string $alias foreach ($parts[$rootAlias] as $join) { /** @var Join $join */ - if (sprintf('%s.%s', $alias, $association) === $join->getJoin()) { + if (\sprintf('%s.%s', $alias, $association) === $join->getJoin()) { return $join; } } diff --git a/Util/QueryNameGenerator.php b/Util/QueryNameGenerator.php index 810a1e0..f6a47d2 100644 --- a/Util/QueryNameGenerator.php +++ b/Util/QueryNameGenerator.php @@ -30,7 +30,7 @@ final class QueryNameGenerator implements QueryNameGeneratorInterface */ public function generateJoinAlias(string $association): string { - return sprintf('%s_a%d', $association, $this->incrementedAssociation++); + return \sprintf('%s_a%d', $association, $this->incrementedAssociation++); } /** @@ -38,6 +38,6 @@ public function generateJoinAlias(string $association): string */ public function generateParameterName(string $name): string { - return sprintf('%s_p%d', str_replace('.', '_', $name), $this->incrementedName++); + return \sprintf('%s_p%d', str_replace('.', '_', $name), $this->incrementedName++); } } From a48e476ec3f023e68f03eac63ffb12a5dbf5dae8 Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 12 Aug 2024 09:26:44 +0200 Subject: [PATCH 25/72] chore: bump api-platform dependencies --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 4cfb545..4528fc5 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,9 @@ ], "require": { "php": ">=8.1", - "api-platform/doctrine-common": "*@dev || ^3.1", - "api-platform/metadata": "*@dev || ^3.1", - "api-platform/state": "*@dev || ^3.1", + "api-platform/doctrine-common": "*@dev || ^3.2 || ^4.0", + "api-platform/metadata": "*@dev || ^3.2 || ^4.0", + "api-platform/state": "*@dev || ^3.2 || ^4.0", "doctrine/doctrine-bundle": "^2.11", "doctrine/orm": "^2.17 || ^3.0", "symfony/property-info": "^6.4 || ^7.1" From 10b842076f19c7b108894f2d290c42a4c94fb1d6 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Mon, 12 Aug 2024 12:17:47 +0200 Subject: [PATCH 26/72] chore: update branch aliases with 3.4 and 4.0 (#6509) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4528fc5..e737937 100644 --- a/composer.json +++ b/composer.json @@ -69,4 +69,4 @@ "scripts": { "test": "./vendor/bin/phpunit" } -} +} \ No newline at end of file From 5ca9343c18ad2de2333d7b42dd84d49704a3d170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 12 Aug 2024 12:19:38 +0200 Subject: [PATCH 27/72] fix: add missing v4 version constraints (#6510) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e737937..accf60e 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "symfony/property-info": "^6.4 || ^7.1" }, "require-dev": { - "api-platform/parameter-validator": "*@dev || ^3.2", + "api-platform/parameter-validator": "*@dev || ^3.2 || ^4.0", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^11.2", "ramsey/uuid": "^4.7", From c123c1b4056c1d52855611922b0103f9e8765f99 Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 12 Aug 2024 16:36:55 +0200 Subject: [PATCH 28/72] cs: newline ending --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index accf60e..fbe9ee4 100644 --- a/composer.json +++ b/composer.json @@ -69,4 +69,4 @@ "scripts": { "test": "./vendor/bin/phpunit" } -} \ No newline at end of file +} From eba49930890b12d60804b351d3fd99026ab842f2 Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 12 Aug 2024 16:44:43 +0200 Subject: [PATCH 29/72] chore: align dependencies across components --- composer.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index fbe9ee4..0f0f4f2 100644 --- a/composer.json +++ b/composer.json @@ -21,27 +21,27 @@ ], "require": { "php": ">=8.1", - "api-platform/doctrine-common": "*@dev || ^3.2 || ^4.0", - "api-platform/metadata": "*@dev || ^3.2 || ^4.0", - "api-platform/state": "*@dev || ^3.2 || ^4.0", + "api-platform/doctrine-common": "@dev || ^3.2 || ^4.0", + "api-platform/metadata": "@dev || ^3.2 || ^4.0", + "api-platform/state": "@dev || ^3.2 || ^4.0", "doctrine/doctrine-bundle": "^2.11", "doctrine/orm": "^2.17 || ^3.0", "symfony/property-info": "^6.4 || ^7.1" }, "require-dev": { - "api-platform/parameter-validator": "*@dev || ^3.2 || ^4.0", - "phpspec/prophecy-phpunit": "^2.0", + "api-platform/parameter-validator": "@dev || ^3.2 || ^4.0", + "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "^11.2", - "ramsey/uuid": "^4.7", + "ramsey/uuid": "^4.0", "ramsey/uuid-doctrine": "^2.0", - "symfony/cache": "^6.4 || ^7.0", - "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/cache": "^6.4 || ^7.1", + "symfony/framework-bundle": "^6.4 || ^7.1", "symfony/phpunit-bridge": "^6.4 || ^7.0", "symfony/property-access": "^6.4 || ^7.0", "symfony/serializer": "^6.4 || ^7.0", - "symfony/uid": "^6.4 || ^7.0", - "symfony/validator": "^6.4 || ^7.0", - "symfony/yaml": "^6.4 || ^7.0" + "symfony/uid": "^6.4 || ^7.1", + "symfony/validator": "^6.4 || ^7.1", + "symfony/yaml": "^6.4 || ^7.1" }, "autoload": { "psr-4": { From 25589a934effeeb3899ee5d3dd21d6d44a3cb91f Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Mon, 19 Aug 2024 14:51:02 +0200 Subject: [PATCH 30/72] chore: remove @dev constraint (#6513) --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 0f0f4f2..7df105b 100644 --- a/composer.json +++ b/composer.json @@ -21,15 +21,15 @@ ], "require": { "php": ">=8.1", - "api-platform/doctrine-common": "@dev || ^3.2 || ^4.0", - "api-platform/metadata": "@dev || ^3.2 || ^4.0", - "api-platform/state": "@dev || ^3.2 || ^4.0", + "api-platform/doctrine-common": "^3.2 || ^4.0", + "api-platform/metadata": "^3.2 || ^4.0", + "api-platform/state": "^3.2 || ^4.0", "doctrine/doctrine-bundle": "^2.11", "doctrine/orm": "^2.17 || ^3.0", "symfony/property-info": "^6.4 || ^7.1" }, "require-dev": { - "api-platform/parameter-validator": "@dev || ^3.2 || ^4.0", + "api-platform/parameter-validator": "^3.2 || ^4.0", "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "^11.2", "ramsey/uuid": "^4.0", From c48b76e96fd1cb9fed7fce5e0ba24dbdc31d58bd Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Thu, 22 Aug 2024 14:54:29 +0200 Subject: [PATCH 31/72] fix: parameter context for filters (#6535) --- Extension/ParameterExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extension/ParameterExtension.php b/Extension/ParameterExtension.php index dce2c62..aa65efb 100644 --- a/Extension/ParameterExtension.php +++ b/Extension/ParameterExtension.php @@ -51,7 +51,7 @@ private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInter $filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null; if ($filter instanceof FilterInterface) { - $filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $values] + $context); + $filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $values, 'parameter' => $parameter] + $context); } } } From 30619c597ddaca6d87018ed75f25ebbf58879f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Marseille?= Date: Fri, 30 Aug 2024 10:38:25 +0200 Subject: [PATCH 32/72] feat(doctrine): add new filter for filtering an entity using PHP backed enum, resolves #6506 (#6547) --- Filter/BackedEnumFilter.php | 178 +++++++++++++++++++++ Tests/Filter/BackedEnumFilterTest.php | 49 ++++++ Tests/Filter/BackedEnumFilterTestTrait.php | 105 ++++++++++++ Tests/Filter/ExistsFilterTest.php | 5 + Tests/Filter/OrderFilterTest.php | 13 ++ Tests/Filter/RangeFilterTest.php | 25 +++ Tests/Filter/SearchFilterTest.php | 14 ++ Tests/Fixtures/Entity/Dummy.php | 5 +- Tests/Fixtures/Entity/DummyBackedEnum.php | 20 +++ Tests/Fixtures/Entity/RelatedDummy.php | 3 + 10 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 Filter/BackedEnumFilter.php create mode 100644 Tests/Filter/BackedEnumFilterTest.php create mode 100644 Tests/Filter/BackedEnumFilterTestTrait.php create mode 100644 Tests/Fixtures/Entity/DummyBackedEnum.php diff --git a/Filter/BackedEnumFilter.php b/Filter/BackedEnumFilter.php new file mode 100644 index 0000000..fffcb13 --- /dev/null +++ b/Filter/BackedEnumFilter.php @@ -0,0 +1,178 @@ + + * + * 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\Doctrine\Orm\Filter; + +use ApiPlatform\Doctrine\Common\Filter\BackedEnumFilterTrait; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Operation; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\FieldMapping; +use Doctrine\ORM\Query\Expr\Join; +use Doctrine\ORM\QueryBuilder; + +/** + * The backed enum filter allows you to search on backed enum fields and values. + * + * Note: it is possible to filter on properties and relations too. + * + * Syntax: `?property=foo`. + * + *
+ * + * ```php + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * book.backed_enum_filter + * + * + * + * + * + * ``` + * + *
+ * + * Given that the collection endpoint is `/books`, you can filter books with the following query: `/books?status=published`. + * + * @author Rémi Marseille + */ +final class BackedEnumFilter extends AbstractFilter +{ + use BackedEnumFilterTrait; + + /** + * {@inheritdoc} + */ + protected function filterProperty(string $property, mixed $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void + { + if ( + !$this->isPropertyEnabled($property, $resourceClass) + || !$this->isPropertyMapped($property, $resourceClass) + || !$this->isBackedEnumField($property, $resourceClass) + ) { + return; + } + + $value = $this->normalizeValue($value, $property); + if (null === $value) { + return; + } + + $alias = $queryBuilder->getRootAliases()[0]; + $field = $property; + + if ($this->isPropertyNested($property, $resourceClass)) { + [$alias, $field] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass, Join::INNER_JOIN); + } + + $valueParameter = $queryNameGenerator->generateParameterName($field); + + $queryBuilder + ->andWhere(\sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) + ->setParameter($valueParameter, $value); + } + + /** + * {@inheritdoc} + */ + protected function isBackedEnumField(string $property, string $resourceClass): bool + { + $propertyParts = $this->splitPropertyParts($property, $resourceClass); + $metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']); + + if (!$metadata instanceof ClassMetadata) { + return false; + } + + $fieldMapping = $metadata->fieldMappings[$propertyParts['field']]; + + // Doctrine ORM 2.x returns an array and Doctrine ORM 3.x returns a FieldMapping object + if ($fieldMapping instanceof FieldMapping) { + $fieldMapping = (array) $fieldMapping; + } + + if (!$enumType = $fieldMapping['enumType']) { + return false; + } + + if (!($enumType::cases()[0] ?? null) instanceof \BackedEnum) { + return false; + } + + $this->enumTypes[$property] = $enumType; + + return true; + } +} diff --git a/Tests/Filter/BackedEnumFilterTest.php b/Tests/Filter/BackedEnumFilterTest.php new file mode 100644 index 0000000..4522e0b --- /dev/null +++ b/Tests/Filter/BackedEnumFilterTest.php @@ -0,0 +1,49 @@ + + * + * 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\Doctrine\Orm\Tests\Filter; + +use ApiPlatform\Doctrine\Orm\Filter\BackedEnumFilter; +use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase; +use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy; + +/** + * @author Rémi Marseille + */ +final class BackedEnumFilterTest extends DoctrineOrmFilterTestCase +{ + use BackedEnumFilterTestTrait; + + protected string $filterClass = BackedEnumFilter::class; + + public static function provideApplyTestData(): array + { + return array_merge_recursive( + self::provideApplyTestArguments(), + [ + 'valid case' => [ + \sprintf('SELECT o FROM %s o WHERE o.dummyBackedEnum = :dummyBackedEnum_p1', Dummy::class), + ], + 'invalid case' => [ + \sprintf('SELECT o FROM %s o', Dummy::class), + ], + 'valid case for nested property' => [ + \sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.dummyBackedEnum = :dummyBackedEnum_p1', Dummy::class), + ], + 'invalid case for nested property' => [ + \sprintf('SELECT o FROM %s o', Dummy::class), + ], + ] + ); + } +} diff --git a/Tests/Filter/BackedEnumFilterTestTrait.php b/Tests/Filter/BackedEnumFilterTestTrait.php new file mode 100644 index 0000000..ebfb2a7 --- /dev/null +++ b/Tests/Filter/BackedEnumFilterTestTrait.php @@ -0,0 +1,105 @@ + + * + * 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\Doctrine\Orm\Tests\Filter; + +/** + * @author Rémi Marseille + */ +trait BackedEnumFilterTestTrait +{ + public function testGetDescription(): void + { + $filter = $this->buildFilter([ + 'id' => null, + 'name' => null, + 'foo' => null, + 'dummyBackedEnum' => null, + ]); + + $this->assertEquals([ + 'dummyBackedEnum' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + 'schema' => [ + 'type' => 'string', + 'enum' => ['one', 'two'], + ], + ], + ], $filter->getDescription($this->resourceClass)); + } + + public function testGetDescriptionDefaultFields(): void + { + $filter = $this->buildFilter(); + + $this->assertEquals([ + 'dummyBackedEnum' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + 'schema' => [ + 'type' => 'string', + 'enum' => ['one', 'two'], + ], + ], + ], $filter->getDescription($this->resourceClass)); + } + + private static function provideApplyTestArguments(): array + { + return [ + 'valid case' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBackedEnum' => null, + ], + [ + 'dummyBackedEnum' => 'one', + ], + ], + 'invalid case' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBackedEnum' => null, + ], + [ + 'dummyBackedEnum' => 'zero', + ], + ], + 'valid case for nested property' => [ + [ + 'id' => null, + 'name' => null, + 'relatedDummy.dummyBackedEnum' => null, + ], + [ + 'relatedDummy.dummyBackedEnum' => 'two', + ], + ], + 'invalid case for nested property' => [ + [ + 'id' => null, + 'name' => null, + 'relatedDummy.dummyBackedEnum' => null, + ], + [ + 'relatedDummy.dummyBackedEnum' => 'foo', + ], + ], + ]; + } +} diff --git a/Tests/Filter/ExistsFilterTest.php b/Tests/Filter/ExistsFilterTest.php index fa9f0a7..168714e 100644 --- a/Tests/Filter/ExistsFilterTest.php +++ b/Tests/Filter/ExistsFilterTest.php @@ -88,6 +88,11 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'bool', 'required' => false, ], + 'exists[dummyBackedEnum]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'bool', + 'required' => false, + ], ], $filter->getDescription($this->resourceClass)); } diff --git a/Tests/Filter/OrderFilterTest.php b/Tests/Filter/OrderFilterTest.php index f75fb5a..20e3293 100644 --- a/Tests/Filter/OrderFilterTest.php +++ b/Tests/Filter/OrderFilterTest.php @@ -191,6 +191,19 @@ public function testGetDescriptionDefaultFields(): void ], ], ], + 'order[dummyBackedEnum]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + 'schema' => [ + 'default' => 'asc', + 'type' => 'string', + 'enum' => [ + 'asc', + 'desc', + ], + ], + ], ], $filter->getDescription($this->resourceClass)); $this->assertEquals([ diff --git a/Tests/Filter/RangeFilterTest.php b/Tests/Filter/RangeFilterTest.php index 3fbdb9f..4c0370b 100644 --- a/Tests/Filter/RangeFilterTest.php +++ b/Tests/Filter/RangeFilterTest.php @@ -331,6 +331,31 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, ], + 'dummyBackedEnum[between]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + ], + 'dummyBackedEnum[gt]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + ], + 'dummyBackedEnum[gte]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + ], + 'dummyBackedEnum[lt]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + ], + 'dummyBackedEnum[lte]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + ], ], $filter->getDescription($this->resourceClass)); } diff --git a/Tests/Filter/SearchFilterTest.php b/Tests/Filter/SearchFilterTest.php index 0b11fb1..5bc48cc 100644 --- a/Tests/Filter/SearchFilterTest.php +++ b/Tests/Filter/SearchFilterTest.php @@ -211,6 +211,20 @@ public function testGetDescriptionDefaultFields(): void 'strategy' => 'exact', 'is_collection' => true, ], + 'dummyBackedEnum' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => false, + ], + 'dummyBackedEnum[]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => true, + ], ], $filter->getDescription($this->resourceClass)); } diff --git a/Tests/Fixtures/Entity/Dummy.php b/Tests/Fixtures/Entity/Dummy.php index 544d01c..f5a29cc 100644 --- a/Tests/Fixtures/Entity/Dummy.php +++ b/Tests/Fixtures/Entity/Dummy.php @@ -27,7 +27,7 @@ * * @author Kévin Dunglas */ -#[ApiResource(filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], extraProperties: ['standard_put' => false])] +#[ApiResource(filters: ['my_dummy.backed_enum', 'my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], extraProperties: ['standard_put' => false])] #[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy')], status: 200, filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], operations: [new Get()])] #[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy')], status: 200, filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], operations: [new Get()])] #[ORM\Entity] @@ -74,6 +74,9 @@ class Dummy #[ORM\Column(nullable: true)] public $dummy; + #[ORM\Column(enumType: DummyBackedEnum::class, nullable: true)] + public DummyBackedEnum $dummyBackedEnum; + /** * @var bool|null A dummy boolean */ diff --git a/Tests/Fixtures/Entity/DummyBackedEnum.php b/Tests/Fixtures/Entity/DummyBackedEnum.php new file mode 100644 index 0000000..b5d76c0 --- /dev/null +++ b/Tests/Fixtures/Entity/DummyBackedEnum.php @@ -0,0 +1,20 @@ + + * + * 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\Doctrine\Orm\Tests\Fixtures\Entity; + +enum DummyBackedEnum: string +{ + case One = 'one'; + case Two = 'two'; +} diff --git a/Tests/Fixtures/Entity/RelatedDummy.php b/Tests/Fixtures/Entity/RelatedDummy.php index 9fbe053..4cf6f92 100644 --- a/Tests/Fixtures/Entity/RelatedDummy.php +++ b/Tests/Fixtures/Entity/RelatedDummy.php @@ -94,6 +94,9 @@ class RelatedDummy extends ParentDummy implements \Stringable #[Groups(['fakemanytomany', 'friends'])] public Collection|iterable $relatedToDummyFriend; + #[ORM\Column(enumType: DummyBackedEnum::class, nullable: true)] + public DummyBackedEnum $dummyBackedEnum; + /** * @var bool|null A dummy bool */ From 858b5e9918b85b1c73e25772b8e3c68362d56346 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 30 Aug 2024 11:29:28 +0200 Subject: [PATCH 33/72] feat(doctrine): add new filter for filtering an entity using PHP backed enum, resolves #6506 (#6547) (#6560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rémi Marseille --- Filter/BackedEnumFilter.php | 178 +++++++++++++++++++++ Tests/Filter/BackedEnumFilterTest.php | 49 ++++++ Tests/Filter/BackedEnumFilterTestTrait.php | 105 ++++++++++++ Tests/Filter/ExistsFilterTest.php | 5 + Tests/Filter/OrderFilterTest.php | 13 ++ Tests/Filter/RangeFilterTest.php | 25 +++ Tests/Filter/SearchFilterTest.php | 14 ++ Tests/Fixtures/Entity/Dummy.php | 5 +- Tests/Fixtures/Entity/DummyBackedEnum.php | 20 +++ Tests/Fixtures/Entity/RelatedDummy.php | 3 + 10 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 Filter/BackedEnumFilter.php create mode 100644 Tests/Filter/BackedEnumFilterTest.php create mode 100644 Tests/Filter/BackedEnumFilterTestTrait.php create mode 100644 Tests/Fixtures/Entity/DummyBackedEnum.php diff --git a/Filter/BackedEnumFilter.php b/Filter/BackedEnumFilter.php new file mode 100644 index 0000000..fffcb13 --- /dev/null +++ b/Filter/BackedEnumFilter.php @@ -0,0 +1,178 @@ + + * + * 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\Doctrine\Orm\Filter; + +use ApiPlatform\Doctrine\Common\Filter\BackedEnumFilterTrait; +use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Operation; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\FieldMapping; +use Doctrine\ORM\Query\Expr\Join; +use Doctrine\ORM\QueryBuilder; + +/** + * The backed enum filter allows you to search on backed enum fields and values. + * + * Note: it is possible to filter on properties and relations too. + * + * Syntax: `?property=foo`. + * + *
+ * + * ```php + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * book.backed_enum_filter + * + * + * + * + * + * ``` + * + *
+ * + * Given that the collection endpoint is `/books`, you can filter books with the following query: `/books?status=published`. + * + * @author Rémi Marseille + */ +final class BackedEnumFilter extends AbstractFilter +{ + use BackedEnumFilterTrait; + + /** + * {@inheritdoc} + */ + protected function filterProperty(string $property, mixed $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void + { + if ( + !$this->isPropertyEnabled($property, $resourceClass) + || !$this->isPropertyMapped($property, $resourceClass) + || !$this->isBackedEnumField($property, $resourceClass) + ) { + return; + } + + $value = $this->normalizeValue($value, $property); + if (null === $value) { + return; + } + + $alias = $queryBuilder->getRootAliases()[0]; + $field = $property; + + if ($this->isPropertyNested($property, $resourceClass)) { + [$alias, $field] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass, Join::INNER_JOIN); + } + + $valueParameter = $queryNameGenerator->generateParameterName($field); + + $queryBuilder + ->andWhere(\sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) + ->setParameter($valueParameter, $value); + } + + /** + * {@inheritdoc} + */ + protected function isBackedEnumField(string $property, string $resourceClass): bool + { + $propertyParts = $this->splitPropertyParts($property, $resourceClass); + $metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']); + + if (!$metadata instanceof ClassMetadata) { + return false; + } + + $fieldMapping = $metadata->fieldMappings[$propertyParts['field']]; + + // Doctrine ORM 2.x returns an array and Doctrine ORM 3.x returns a FieldMapping object + if ($fieldMapping instanceof FieldMapping) { + $fieldMapping = (array) $fieldMapping; + } + + if (!$enumType = $fieldMapping['enumType']) { + return false; + } + + if (!($enumType::cases()[0] ?? null) instanceof \BackedEnum) { + return false; + } + + $this->enumTypes[$property] = $enumType; + + return true; + } +} diff --git a/Tests/Filter/BackedEnumFilterTest.php b/Tests/Filter/BackedEnumFilterTest.php new file mode 100644 index 0000000..4522e0b --- /dev/null +++ b/Tests/Filter/BackedEnumFilterTest.php @@ -0,0 +1,49 @@ + + * + * 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\Doctrine\Orm\Tests\Filter; + +use ApiPlatform\Doctrine\Orm\Filter\BackedEnumFilter; +use ApiPlatform\Doctrine\Orm\Tests\DoctrineOrmFilterTestCase; +use ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity\Dummy; + +/** + * @author Rémi Marseille + */ +final class BackedEnumFilterTest extends DoctrineOrmFilterTestCase +{ + use BackedEnumFilterTestTrait; + + protected string $filterClass = BackedEnumFilter::class; + + public static function provideApplyTestData(): array + { + return array_merge_recursive( + self::provideApplyTestArguments(), + [ + 'valid case' => [ + \sprintf('SELECT o FROM %s o WHERE o.dummyBackedEnum = :dummyBackedEnum_p1', Dummy::class), + ], + 'invalid case' => [ + \sprintf('SELECT o FROM %s o', Dummy::class), + ], + 'valid case for nested property' => [ + \sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 WHERE relatedDummy_a1.dummyBackedEnum = :dummyBackedEnum_p1', Dummy::class), + ], + 'invalid case for nested property' => [ + \sprintf('SELECT o FROM %s o', Dummy::class), + ], + ] + ); + } +} diff --git a/Tests/Filter/BackedEnumFilterTestTrait.php b/Tests/Filter/BackedEnumFilterTestTrait.php new file mode 100644 index 0000000..ebfb2a7 --- /dev/null +++ b/Tests/Filter/BackedEnumFilterTestTrait.php @@ -0,0 +1,105 @@ + + * + * 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\Doctrine\Orm\Tests\Filter; + +/** + * @author Rémi Marseille + */ +trait BackedEnumFilterTestTrait +{ + public function testGetDescription(): void + { + $filter = $this->buildFilter([ + 'id' => null, + 'name' => null, + 'foo' => null, + 'dummyBackedEnum' => null, + ]); + + $this->assertEquals([ + 'dummyBackedEnum' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + 'schema' => [ + 'type' => 'string', + 'enum' => ['one', 'two'], + ], + ], + ], $filter->getDescription($this->resourceClass)); + } + + public function testGetDescriptionDefaultFields(): void + { + $filter = $this->buildFilter(); + + $this->assertEquals([ + 'dummyBackedEnum' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + 'schema' => [ + 'type' => 'string', + 'enum' => ['one', 'two'], + ], + ], + ], $filter->getDescription($this->resourceClass)); + } + + private static function provideApplyTestArguments(): array + { + return [ + 'valid case' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBackedEnum' => null, + ], + [ + 'dummyBackedEnum' => 'one', + ], + ], + 'invalid case' => [ + [ + 'id' => null, + 'name' => null, + 'dummyBackedEnum' => null, + ], + [ + 'dummyBackedEnum' => 'zero', + ], + ], + 'valid case for nested property' => [ + [ + 'id' => null, + 'name' => null, + 'relatedDummy.dummyBackedEnum' => null, + ], + [ + 'relatedDummy.dummyBackedEnum' => 'two', + ], + ], + 'invalid case for nested property' => [ + [ + 'id' => null, + 'name' => null, + 'relatedDummy.dummyBackedEnum' => null, + ], + [ + 'relatedDummy.dummyBackedEnum' => 'foo', + ], + ], + ]; + } +} diff --git a/Tests/Filter/ExistsFilterTest.php b/Tests/Filter/ExistsFilterTest.php index fa9f0a7..168714e 100644 --- a/Tests/Filter/ExistsFilterTest.php +++ b/Tests/Filter/ExistsFilterTest.php @@ -88,6 +88,11 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'bool', 'required' => false, ], + 'exists[dummyBackedEnum]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'bool', + 'required' => false, + ], ], $filter->getDescription($this->resourceClass)); } diff --git a/Tests/Filter/OrderFilterTest.php b/Tests/Filter/OrderFilterTest.php index f75fb5a..20e3293 100644 --- a/Tests/Filter/OrderFilterTest.php +++ b/Tests/Filter/OrderFilterTest.php @@ -191,6 +191,19 @@ public function testGetDescriptionDefaultFields(): void ], ], ], + 'order[dummyBackedEnum]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + 'schema' => [ + 'default' => 'asc', + 'type' => 'string', + 'enum' => [ + 'asc', + 'desc', + ], + ], + ], ], $filter->getDescription($this->resourceClass)); $this->assertEquals([ diff --git a/Tests/Filter/RangeFilterTest.php b/Tests/Filter/RangeFilterTest.php index 3fbdb9f..4c0370b 100644 --- a/Tests/Filter/RangeFilterTest.php +++ b/Tests/Filter/RangeFilterTest.php @@ -331,6 +331,31 @@ public function testGetDescriptionDefaultFields(): void 'type' => 'string', 'required' => false, ], + 'dummyBackedEnum[between]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + ], + 'dummyBackedEnum[gt]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + ], + 'dummyBackedEnum[gte]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + ], + 'dummyBackedEnum[lt]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + ], + 'dummyBackedEnum[lte]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + ], ], $filter->getDescription($this->resourceClass)); } diff --git a/Tests/Filter/SearchFilterTest.php b/Tests/Filter/SearchFilterTest.php index 0b11fb1..5bc48cc 100644 --- a/Tests/Filter/SearchFilterTest.php +++ b/Tests/Filter/SearchFilterTest.php @@ -211,6 +211,20 @@ public function testGetDescriptionDefaultFields(): void 'strategy' => 'exact', 'is_collection' => true, ], + 'dummyBackedEnum' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => false, + ], + 'dummyBackedEnum[]' => [ + 'property' => 'dummyBackedEnum', + 'type' => 'string', + 'required' => false, + 'strategy' => 'exact', + 'is_collection' => true, + ], ], $filter->getDescription($this->resourceClass)); } diff --git a/Tests/Fixtures/Entity/Dummy.php b/Tests/Fixtures/Entity/Dummy.php index 00df229..9c1530c 100644 --- a/Tests/Fixtures/Entity/Dummy.php +++ b/Tests/Fixtures/Entity/Dummy.php @@ -27,7 +27,7 @@ * * @author Kévin Dunglas */ -#[ApiResource(filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], extraProperties: ['standard_put' => false, 'rfc_7807_compliant_errors' => false])] +#[ApiResource(filters: ['my_dummy.backed_enum', 'my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], extraProperties: ['standard_put' => false, 'rfc_7807_compliant_errors' => false])] #[ApiResource(uriTemplate: '/related_owned_dummies/{id}/owning_dummy{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwnedDummy::class, identifiers: ['id'], fromProperty: 'owningDummy')], status: 200, filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], operations: [new Get()])] #[ApiResource(uriTemplate: '/related_owning_dummies/{id}/owned_dummy{._format}', uriVariables: ['id' => new Link(fromClass: RelatedOwningDummy::class, identifiers: ['id'], fromProperty: 'ownedDummy')], status: 200, filters: ['my_dummy.boolean', 'my_dummy.date', 'my_dummy.exists', 'my_dummy.numeric', 'my_dummy.order', 'my_dummy.range', 'my_dummy.search', 'my_dummy.property'], operations: [new Get()])] #[ORM\Entity] @@ -74,6 +74,9 @@ class Dummy #[ORM\Column(nullable: true)] public $dummy; + #[ORM\Column(enumType: DummyBackedEnum::class, nullable: true)] + public DummyBackedEnum $dummyBackedEnum; + /** * @var bool|null A dummy boolean */ diff --git a/Tests/Fixtures/Entity/DummyBackedEnum.php b/Tests/Fixtures/Entity/DummyBackedEnum.php new file mode 100644 index 0000000..b5d76c0 --- /dev/null +++ b/Tests/Fixtures/Entity/DummyBackedEnum.php @@ -0,0 +1,20 @@ + + * + * 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\Doctrine\Orm\Tests\Fixtures\Entity; + +enum DummyBackedEnum: string +{ + case One = 'one'; + case Two = 'two'; +} diff --git a/Tests/Fixtures/Entity/RelatedDummy.php b/Tests/Fixtures/Entity/RelatedDummy.php index 9fbe053..4cf6f92 100644 --- a/Tests/Fixtures/Entity/RelatedDummy.php +++ b/Tests/Fixtures/Entity/RelatedDummy.php @@ -94,6 +94,9 @@ class RelatedDummy extends ParentDummy implements \Stringable #[Groups(['fakemanytomany', 'friends'])] public Collection|iterable $relatedToDummyFriend; + #[ORM\Column(enumType: DummyBackedEnum::class, nullable: true)] + public DummyBackedEnum $dummyBackedEnum; + /** * @var bool|null A dummy bool */ From 1517e0a8d227b385df1b6b2a7f83f8a90f6638f4 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 30 Aug 2024 22:19:12 +0200 Subject: [PATCH 34/72] chore: deprecations (#6563) * chore: deprecations * chore: deprecations --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 40bebfc..0928b49 100644 --- a/composer.json +++ b/composer.json @@ -21,15 +21,15 @@ ], "require": { "php": ">=8.1", - "api-platform/doctrine-common": "*@dev || ^3.1", - "api-platform/metadata": "*@dev || ^3.1", - "api-platform/state": "*@dev || ^3.1", + "api-platform/doctrine-common": "^3.1", + "api-platform/metadata": "^3.1", + "api-platform/state": "^3.1", "doctrine/doctrine-bundle": "^2.11", "doctrine/orm": "^2.17 || ^3.0", "symfony/property-info": "^6.4 || ^7.1" }, "require-dev": { - "api-platform/parameter-validator": "*@dev || ^3.2", + "api-platform/parameter-validator": "^3.2", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^10.0", "ramsey/uuid": "^4.7", @@ -69,4 +69,4 @@ "scripts": { "test": "./vendor/bin/phpunit" } -} \ No newline at end of file +} From b2be6f342c971aa8e3d419d5d551d44c77687fa8 Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 2 Sep 2024 18:34:47 +0200 Subject: [PATCH 35/72] test: doctrine auto mapping controller is deprecated --- Tests/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/config.yml b/Tests/config.yml index e3e948b..f89f33e 100644 --- a/Tests/config.yml +++ b/Tests/config.yml @@ -9,7 +9,8 @@ doctrine: orm: auto_generate_proxy_classes: '%kernel.debug%' controller_resolver: - auto_mapping: true + enabled: false + auto_mapping: false enable_lazy_ghost_objects: true mappings: ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity: From 5bf3775059c1ce7717490da2b32f4b9589178324 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Tue, 3 Sep 2024 09:00:23 +0200 Subject: [PATCH 36/72] chore: bump dependencies to ^3.4 || ^4.0 (#6576) * chore: bump dependencies to ^3.4 || ^4.0 * fix: use baseline for serializer context builder * ci: use pmu 0.12 --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 0928b49..85a0ed3 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,9 @@ ], "require": { "php": ">=8.1", - "api-platform/doctrine-common": "^3.1", - "api-platform/metadata": "^3.1", - "api-platform/state": "^3.1", + "api-platform/doctrine-common": "^3.4 || ^4.0", + "api-platform/metadata": "^3.4 || ^4.0", + "api-platform/state": "^3.4 || ^4.0", "doctrine/doctrine-bundle": "^2.11", "doctrine/orm": "^2.17 || ^3.0", "symfony/property-info": "^6.4 || ^7.1" From e21f9b5cac18c86fbd6f2fa7fd8bb9a2ab8f6da6 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 6 Sep 2024 08:35:15 +0200 Subject: [PATCH 37/72] chore: remove phpunit-bridge (#6589) --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index cb7d3cd..f9b9622 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,6 @@ "ramsey/uuid-doctrine": "^2.0", "symfony/cache": "^6.4 || ^7.1", "symfony/framework-bundle": "^6.4 || ^7.1", - "symfony/phpunit-bridge": "^6.4 || ^7.0", "symfony/property-access": "^6.4 || ^7.0", "symfony/serializer": "^6.4 || ^7.0", "symfony/uid": "^6.4 || ^7.1", From 464b06dd919408e6a5a88914965c6d4aafbc2fbe Mon Sep 17 00:00:00 2001 From: Vincent <407859+vincentchalamon@users.noreply.github.com> Date: Sat, 7 Sep 2024 08:31:56 +0200 Subject: [PATCH 38/72] fix: replace ApiPlatform\Exception use by ApiPlatform\Metadata\Exception (#6597) --- Extension/FilterEagerLoadingExtension.php | 2 +- Extension/FilterExtension.php | 2 +- Extension/OrderExtension.php | 2 +- Extension/PaginationExtension.php | 2 +- PropertyHelperTrait.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Extension/FilterEagerLoadingExtension.php b/Extension/FilterEagerLoadingExtension.php index daba5d9..8145fa6 100644 --- a/Extension/FilterEagerLoadingExtension.php +++ b/Extension/FilterEagerLoadingExtension.php @@ -15,7 +15,7 @@ use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\ResourceClassResolverInterface; use Doctrine\ORM\EntityManagerInterface; diff --git a/Extension/FilterExtension.php b/Extension/FilterExtension.php index 298eff2..801e9c4 100644 --- a/Extension/FilterExtension.php +++ b/Extension/FilterExtension.php @@ -16,7 +16,7 @@ use ApiPlatform\Doctrine\Orm\Filter\FilterInterface; use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; use Doctrine\ORM\QueryBuilder; use Psr\Container\ContainerInterface; diff --git a/Extension/OrderExtension.php b/Extension/OrderExtension.php index 00140fd..1d3deaf 100644 --- a/Extension/OrderExtension.php +++ b/Extension/OrderExtension.php @@ -15,7 +15,7 @@ use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; use Doctrine\ORM\QueryBuilder; diff --git a/Extension/PaginationExtension.php b/Extension/PaginationExtension.php index 80513e4..9b4ab0d 100644 --- a/Extension/PaginationExtension.php +++ b/Extension/PaginationExtension.php @@ -17,7 +17,7 @@ use ApiPlatform\Doctrine\Orm\Paginator; use ApiPlatform\Doctrine\Orm\Util\QueryChecker; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\Pagination\Pagination; use Doctrine\ORM\QueryBuilder; diff --git a/PropertyHelperTrait.php b/PropertyHelperTrait.php index 8ed61ca..8431e3e 100644 --- a/PropertyHelperTrait.php +++ b/PropertyHelperTrait.php @@ -15,7 +15,7 @@ use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; use Doctrine\ORM\Mapping\ClassMetadata as ClassMetadataInfo; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; From a892e528b3e715a42fa858a5521e57c4177bf7f8 Mon Sep 17 00:00:00 2001 From: soyuka Date: Mon, 2 Sep 2024 18:34:47 +0200 Subject: [PATCH 39/72] test: doctrine auto mapping controller is deprecated --- Tests/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/config.yml b/Tests/config.yml index e3e948b..f89f33e 100644 --- a/Tests/config.yml +++ b/Tests/config.yml @@ -9,7 +9,8 @@ doctrine: orm: auto_generate_proxy_classes: '%kernel.debug%' controller_resolver: - auto_mapping: true + enabled: false + auto_mapping: false enable_lazy_ghost_objects: true mappings: ApiPlatform\Doctrine\Orm\Tests\Fixtures\Entity: From 7ee600c1b46f32eaded7a885169bdcc2c105c999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 19 Sep 2024 17:56:07 +0200 Subject: [PATCH 40/72] chore: setup star forwarding (#6630) --- composer.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 85a0ed3..5fda971 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,10 @@ "type": "library", "keywords": [ "Doctrine", - "ORM" + "ORM", + "API", + "REST", + "GraphQL" ], "homepage": "https://api-platform.com", "license": "MIT", @@ -64,6 +67,10 @@ }, "symfony": { "require": "^6.4 || ^7.1" + }, + "thanks": { + "name": "api-platform/api-platform", + "url": "https://github.com/api-platform/api-platform" } }, "scripts": { From 9195c38abb3a36b9d526d51c0959235b5604adaf Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Thu, 19 Sep 2024 17:56:20 +0200 Subject: [PATCH 41/72] chore: php version >= 8.2 (#6628) * chore: php version >= 8.2 * down to sf 7.0 * symfony extra require * missing hal constraint --- composer.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index f9b9622..e734865 100644 --- a/composer.json +++ b/composer.json @@ -20,26 +20,26 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "api-platform/doctrine-common": "^3.4 || ^4.0", "api-platform/metadata": "^3.4 || ^4.0", "api-platform/state": "^3.4 || ^4.0", "doctrine/doctrine-bundle": "^2.11", "doctrine/orm": "^2.17 || ^3.0", - "symfony/property-info": "^6.4 || ^7.1" + "symfony/property-info": "^6.4 || ^7.0" }, "require-dev": { "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "^11.2", "ramsey/uuid": "^4.0", "ramsey/uuid-doctrine": "^2.0", - "symfony/cache": "^6.4 || ^7.1", - "symfony/framework-bundle": "^6.4 || ^7.1", + "symfony/cache": "^6.4 || ^7.0", + "symfony/framework-bundle": "^6.4 || ^7.0", "symfony/property-access": "^6.4 || ^7.0", "symfony/serializer": "^6.4 || ^7.0", - "symfony/uid": "^6.4 || ^7.1", - "symfony/validator": "^6.4 || ^7.1", - "symfony/yaml": "^6.4 || ^7.1" + "symfony/uid": "^6.4 || ^7.0", + "symfony/validator": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0" }, "autoload": { "psr-4": { @@ -61,7 +61,7 @@ "dev-3.4": "3.4.x-dev" }, "symfony": { - "require": "^6.4 || ^7.1" + "require": "^6.4 || ^7.0" } }, "scripts": { From e34c197af383e7692fd30c45f276745b1b3bde9f Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 19 Sep 2024 17:58:51 +0200 Subject: [PATCH 42/72] chore: fix thanks url --- composer.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/composer.json b/composer.json index c16aae7..ac24c64 100644 --- a/composer.json +++ b/composer.json @@ -65,6 +65,10 @@ }, "symfony": { "require": "^6.4 || ^7.0" + }, + "thanks": { + "name": "api-platform/api-platform", + "url": "https://github.com/api-platform/api-platform" } }, "scripts": { From fd6fb1c11d55bf69930762c08941acbb5726bb60 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 20 Sep 2024 11:32:54 +0200 Subject: [PATCH 43/72] chore: move dependency to dev requirements (#6634) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ac24c64..9bc1aa9 100644 --- a/composer.json +++ b/composer.json @@ -27,11 +27,11 @@ "api-platform/doctrine-common": "^3.4 || ^4.0", "api-platform/metadata": "^3.4 || ^4.0", "api-platform/state": "^3.4 || ^4.0", - "doctrine/doctrine-bundle": "^2.11", "doctrine/orm": "^2.17 || ^3.0", "symfony/property-info": "^6.4 || ^7.0" }, "require-dev": { + "doctrine/doctrine-bundle": "^2.11", "phpspec/prophecy-phpunit": "^2.2", "phpunit/phpunit": "^11.2", "ramsey/uuid": "^4.0", From 74aec72e14c0f1971078ede17bb710117fb50436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 20 Sep 2024 11:46:41 +0200 Subject: [PATCH 44/72] fix: composer.json config --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5fda971..2ec46bc 100644 --- a/composer.json +++ b/composer.json @@ -27,12 +27,12 @@ "api-platform/doctrine-common": "^3.4 || ^4.0", "api-platform/metadata": "^3.4 || ^4.0", "api-platform/state": "^3.4 || ^4.0", - "doctrine/doctrine-bundle": "^2.11", "doctrine/orm": "^2.17 || ^3.0", "symfony/property-info": "^6.4 || ^7.1" }, "require-dev": { "api-platform/parameter-validator": "^3.2", + "doctrine/doctrine-bundle": "^2.11", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^10.0", "ramsey/uuid": "^4.7", From b6b9eabf268a3e8b8f707db7e558f86d23022616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Fri, 20 Sep 2024 15:47:23 +0200 Subject: [PATCH 45/72] chore: add GitHub Action to automatically close PRs on subtree splits (#6648) --- .gitattributes | 3 +++ .github/workflows/close_pr.yml | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 .github/workflows/close_pr.yml diff --git a/.gitattributes b/.gitattributes index ae3c2e1..801f208 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,5 @@ +/.github export-ignore +/.gitattributes export-ignore /.gitignore export-ignore /Tests export-ignore +/phpunit.xml.dist export-ignore diff --git a/.github/workflows/close_pr.yml b/.github/workflows/close_pr.yml new file mode 100644 index 0000000..72a8ab4 --- /dev/null +++ b/.github/workflows/close_pr.yml @@ -0,0 +1,13 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [opened] + +jobs: + run: + runs-on: ubuntu-latest + steps: + - uses: superbrothers/close-pull-request@v3 + with: + comment: "Thank you for your pull request. However, you have submitted this PR on a read-only sub split of `api-platform/core`. Please submit your PR on the https://github.com/api-platform/core repository.

Thanks!" From 2375ab718eee1bfef00301e5dcd50cf712f11f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 21 Sep 2024 12:54:56 +0200 Subject: [PATCH 46/72] doc: add README files for components (#6653) * docs: add README for components * Update README.md Co-authored-by: Antoine Bluchet * Update README.md Co-authored-by: Antoine Bluchet * Update README.md Co-authored-by: Antoine Bluchet * remove trailing spaces * typo * better title --------- Co-authored-by: Antoine Bluchet --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ca9c9ab --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# API Platform - Doctrine ORM Support + +Integration for [Doctrine ORM](https://www.doctrine-project.org/projects/doctrine-orm/en/current/index.html) with the [API Platform](https://api-platform.com) framework. + +[Documentation](https://api-platform.com/docs/core/getting-started/) + +> [!CAUTION] +> +> This is a read-only sub split of `api-platform/core`, please +> [report issues](https://github.com/api-platform/core/issues) and +> [send Pull Requests](https://github.com/api-platform/core/pulls) +> in the [core API Platform repository](https://github.com/api-platform/core). From 46dd020708dd128c564688a4fbfc877903747300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Mon, 4 Nov 2024 15:19:45 +0100 Subject: [PATCH 47/72] fix(doctrine): throw an exception when a filter is not found in a parameter (#6767) --- Extension/ParameterExtension.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Extension/ParameterExtension.php b/Extension/ParameterExtension.php index aa65efb..6ae3a00 100644 --- a/Extension/ParameterExtension.php +++ b/Extension/ParameterExtension.php @@ -16,6 +16,7 @@ use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait; use ApiPlatform\Doctrine\Orm\Filter\FilterInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ParameterNotFound; use Doctrine\ORM\QueryBuilder; @@ -50,9 +51,11 @@ private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInter } $filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null; - if ($filter instanceof FilterInterface) { - $filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $values, 'parameter' => $parameter] + $context); + if (!$filter instanceof FilterInterface) { + throw new InvalidArgumentException(\sprintf('Could not find filter "%s" for parameter "%s" in operation "%s" for resource "%s".', $filterId, $parameter->getKey(), $operation?->getShortName(), $resourceClass)); } + + $filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $values, 'parameter' => $parameter] + $context); } } From 66da16453f6236f455fcc4d10380a717d277b9db Mon Sep 17 00:00:00 2001 From: Arvids Godjuks Date: Fri, 6 Dec 2024 12:53:33 +0200 Subject: [PATCH 48/72] fix(doctrine): fixed orm datefilter applying inner join when no filtering values have been provided (#6849) --- Filter/DateFilter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Filter/DateFilter.php b/Filter/DateFilter.php index 8533ee3..268b115 100644 --- a/Filter/DateFilter.php +++ b/Filter/DateFilter.php @@ -153,7 +153,7 @@ protected function filterProperty(string $property, $values, QueryBuilder $query $alias = $queryBuilder->getRootAliases()[0]; $field = $property; - if ($this->isPropertyNested($property, $resourceClass)) { + if ($this->isPropertyNested($property, $resourceClass) && \count($values) > 0) { [$alias, $field] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass, Join::INNER_JOIN); } From 01d49d6f89060eaf4804b02bc0f8d04f59107999 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Mon, 9 Dec 2024 11:30:13 +0100 Subject: [PATCH 49/72] fix: test empty parameter against null (#6852) fixes #6844 --- Extension/ParameterExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extension/ParameterExtension.php b/Extension/ParameterExtension.php index 6ae3a00..acd6aac 100644 --- a/Extension/ParameterExtension.php +++ b/Extension/ParameterExtension.php @@ -41,7 +41,7 @@ public function __construct(private readonly ContainerInterface $filterLocator) private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { foreach ($operation?->getParameters() ?? [] as $parameter) { - if (!($v = $parameter->getValue()) || $v instanceof ParameterNotFound) { + if (null === ($v = $parameter->getValue()) || $v instanceof ParameterNotFound) { continue; } From b4f6e500a8e5d8daae322e82acc35b643060ac88 Mon Sep 17 00:00:00 2001 From: Vincent Amstoutz Date: Tue, 10 Dec 2024 13:46:08 +0100 Subject: [PATCH 50/72] feat(doctrine): doctrine filters like laravel eloquent filters (#6775) * feat(doctrine): doctrine filters like laravel eloquent filters * fix: allow multiple validation with :property placeholder * fix: correct escape filter condition * fix: remove duplicated block --------- Co-authored-by: soyuka --- Extension/ParameterExtension.php | 42 +++++++++++++++++++++++--- Filter/AbstractFilter.php | 38 +++++++++++++++++------ Filter/BooleanFilter.php | 12 +++++++- Filter/DateFilter.php | 52 ++++++++++++++++++++++++-------- Filter/ExistsFilter.php | 25 +++++++++++++-- Filter/NumericFilter.php | 9 +++++- Filter/OrderFilter.php | 32 +++++++++++++++----- Filter/RangeFilter.php | 19 +++++++++++- PropertyHelperTrait.php | 2 +- 9 files changed, 191 insertions(+), 40 deletions(-) diff --git a/Extension/ParameterExtension.php b/Extension/ParameterExtension.php index 6ae3a00..893305e 100644 --- a/Extension/ParameterExtension.php +++ b/Extension/ParameterExtension.php @@ -13,7 +13,9 @@ namespace ApiPlatform\Doctrine\Orm\Extension; +use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface; use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait; +use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; use ApiPlatform\Doctrine\Orm\Filter\FilterInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Metadata\Exception\InvalidArgumentException; @@ -21,6 +23,7 @@ use ApiPlatform\State\ParameterNotFound; use Doctrine\ORM\QueryBuilder; use Psr\Container\ContainerInterface; +use Symfony\Bridge\Doctrine\ManagerRegistry; /** * Reads operation parameters and execute its filter. @@ -31,8 +34,10 @@ final class ParameterExtension implements QueryCollectionExtensionInterface, Que { use ParameterValueExtractorTrait; - public function __construct(private readonly ContainerInterface $filterLocator) - { + public function __construct( + private readonly ContainerInterface $filterLocator, + private readonly ?ManagerRegistry $managerRegistry = null, + ) { } /** @@ -41,7 +46,8 @@ public function __construct(private readonly ContainerInterface $filterLocator) private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { foreach ($operation?->getParameters() ?? [] as $parameter) { - if (!($v = $parameter->getValue()) || $v instanceof ParameterNotFound) { + // TODO: remove the null equality as a parameter can have a null value + if (null === ($v = $parameter->getValue()) || $v instanceof ParameterNotFound) { continue; } @@ -50,12 +56,38 @@ private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInter continue; } - $filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null; + $filter = match (true) { + $filterId instanceof FilterInterface => $filterId, + \is_string($filterId) && $this->filterLocator->has($filterId) => $this->filterLocator->get($filterId), + default => null, + }; + if (!$filter instanceof FilterInterface) { throw new InvalidArgumentException(\sprintf('Could not find filter "%s" for parameter "%s" in operation "%s" for resource "%s".', $filterId, $parameter->getKey(), $operation?->getShortName(), $resourceClass)); } - $filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $values, 'parameter' => $parameter] + $context); + if ($this->managerRegistry && $filter instanceof ManagerRegistryAwareInterface && !$filter->hasManagerRegistry()) { + $filter->setManagerRegistry($this->managerRegistry); + } + + if ($filter instanceof AbstractFilter && !$filter->getProperties()) { + $propertyKey = $parameter->getProperty() ?? $parameter->getKey(); + + if (str_contains($propertyKey, ':property')) { + $extraProperties = $parameter->getExtraProperties()['_properties'] ?? []; + foreach (array_keys($extraProperties) as $property) { + $properties[$property] = $parameter->getFilterContext(); + } + } else { + $properties = [$propertyKey => $parameter->getFilterContext()]; + } + + $filter->setProperties($properties ?? []); + } + + $filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, + ['filters' => $values, 'parameter' => $parameter] + $context + ); } } diff --git a/Filter/AbstractFilter.php b/Filter/AbstractFilter.php index 4ec7046..18b2928 100644 --- a/Filter/AbstractFilter.php +++ b/Filter/AbstractFilter.php @@ -13,10 +13,12 @@ namespace ApiPlatform\Doctrine\Orm\Filter; +use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface; use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface; use ApiPlatform\Doctrine\Common\PropertyHelperTrait; use ApiPlatform\Doctrine\Orm\PropertyHelperTrait as OrmPropertyHelperTrait; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\Exception\RuntimeException; use ApiPlatform\Metadata\Operation; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; @@ -24,14 +26,18 @@ use Psr\Log\NullLogger; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface +abstract class AbstractFilter implements FilterInterface, PropertyAwareFilterInterface, ManagerRegistryAwareInterface { use OrmPropertyHelperTrait; use PropertyHelperTrait; protected LoggerInterface $logger; - public function __construct(protected ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, protected ?array $properties = null, protected ?NameConverterInterface $nameConverter = null) - { + public function __construct( + protected ?ManagerRegistry $managerRegistry = null, + ?LoggerInterface $logger = null, + protected ?array $properties = null, + protected ?NameConverterInterface $nameConverter = null, + ) { $this->logger = $logger ?? new NullLogger(); } @@ -53,29 +59,43 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q */ abstract protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void; - protected function getManagerRegistry(): ManagerRegistry + public function hasManagerRegistry(): bool + { + return $this->managerRegistry instanceof ManagerRegistry; + } + + public function getManagerRegistry(): ManagerRegistry { + if (!$this->hasManagerRegistry()) { + throw new RuntimeException('ManagerRegistry must be initialized before accessing it.'); + } + return $this->managerRegistry; } - protected function getProperties(): ?array + public function setManagerRegistry(ManagerRegistry $managerRegistry): void { - return $this->properties; + $this->managerRegistry = $managerRegistry; } - protected function getLogger(): LoggerInterface + public function getProperties(): ?array { - return $this->logger; + return $this->properties; } /** - * @param string[] $properties + * @param array $properties */ public function setProperties(array $properties): void { $this->properties = $properties; } + protected function getLogger(): LoggerInterface + { + return $this->logger; + } + /** * Determines whether the given property is enabled. */ diff --git a/Filter/BooleanFilter.php b/Filter/BooleanFilter.php index e9f0a83..6cc7dfd 100644 --- a/Filter/BooleanFilter.php +++ b/Filter/BooleanFilter.php @@ -15,7 +15,9 @@ use ApiPlatform\Doctrine\Common\Filter\BooleanFilterTrait; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\JsonSchemaFilterInterface; use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Parameter; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; @@ -106,7 +108,7 @@ * @author Amrouche Hamza * @author Teoh Han Hui */ -final class BooleanFilter extends AbstractFilter +final class BooleanFilter extends AbstractFilter implements JsonSchemaFilterInterface { use BooleanFilterTrait; @@ -145,4 +147,12 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB ->andWhere(\sprintf('%s.%s = :%s', $alias, $field, $valueParameter)) ->setParameter($valueParameter, $value); } + + /** + * @return array + */ + public function getSchema(Parameter $parameter): array + { + return ['type' => 'boolean']; + } } diff --git a/Filter/DateFilter.php b/Filter/DateFilter.php index 268b115..b1eb525 100644 --- a/Filter/DateFilter.php +++ b/Filter/DateFilter.php @@ -17,7 +17,12 @@ use ApiPlatform\Doctrine\Common\Filter\DateFilterTrait; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Metadata\Exception\InvalidArgumentException; +use ApiPlatform\Metadata\JsonSchemaFilterInterface; +use ApiPlatform\Metadata\OpenApiParameterFilterInterface; use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Parameter; +use ApiPlatform\Metadata\QueryParameter; +use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter; use Doctrine\DBAL\Types\Type as DBALType; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Query\Expr\Join; @@ -120,7 +125,7 @@ * @author Kévin Dunglas * @author Théo FIDRY */ -final class DateFilter extends AbstractFilter implements DateFilterInterface +final class DateFilter extends AbstractFilter implements DateFilterInterface, JsonSchemaFilterInterface, OpenApiParameterFilterInterface { use DateFilterTrait; @@ -138,11 +143,11 @@ final class DateFilter extends AbstractFilter implements DateFilterInterface /** * {@inheritdoc} */ - protected function filterProperty(string $property, $values, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { - // Expect $values to be an array having the period as keys and the date value as values + // Expect $value to be an array having the period as keys and the date value as values if ( - !\is_array($values) + !\is_array($value) || !$this->isPropertyEnabled($property, $resourceClass) || !$this->isPropertyMapped($property, $resourceClass) || !$this->isDateField($property, $resourceClass) @@ -153,7 +158,7 @@ protected function filterProperty(string $property, $values, QueryBuilder $query $alias = $queryBuilder->getRootAliases()[0]; $field = $property; - if ($this->isPropertyNested($property, $resourceClass) && \count($values) > 0) { + if ($this->isPropertyNested($property, $resourceClass) && \count($value) > 0) { [$alias, $field] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass, Join::INNER_JOIN); } @@ -164,53 +169,53 @@ protected function filterProperty(string $property, $values, QueryBuilder $query $queryBuilder->andWhere($queryBuilder->expr()->isNotNull(\sprintf('%s.%s', $alias, $field))); } - if (isset($values[self::PARAMETER_BEFORE])) { + if (isset($value[self::PARAMETER_BEFORE])) { $this->addWhere( $queryBuilder, $queryNameGenerator, $alias, $field, self::PARAMETER_BEFORE, - $values[self::PARAMETER_BEFORE], + $value[self::PARAMETER_BEFORE], $nullManagement, $type ); } - if (isset($values[self::PARAMETER_STRICTLY_BEFORE])) { + if (isset($value[self::PARAMETER_STRICTLY_BEFORE])) { $this->addWhere( $queryBuilder, $queryNameGenerator, $alias, $field, self::PARAMETER_STRICTLY_BEFORE, - $values[self::PARAMETER_STRICTLY_BEFORE], + $value[self::PARAMETER_STRICTLY_BEFORE], $nullManagement, $type ); } - if (isset($values[self::PARAMETER_AFTER])) { + if (isset($value[self::PARAMETER_AFTER])) { $this->addWhere( $queryBuilder, $queryNameGenerator, $alias, $field, self::PARAMETER_AFTER, - $values[self::PARAMETER_AFTER], + $value[self::PARAMETER_AFTER], $nullManagement, $type ); } - if (isset($values[self::PARAMETER_STRICTLY_AFTER])) { + if (isset($value[self::PARAMETER_STRICTLY_AFTER])) { $this->addWhere( $queryBuilder, $queryNameGenerator, $alias, $field, self::PARAMETER_STRICTLY_AFTER, - $values[self::PARAMETER_STRICTLY_AFTER], + $value[self::PARAMETER_STRICTLY_AFTER], $nullManagement, $type ); @@ -269,4 +274,25 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf $queryBuilder->setParameter($valueParameter, $value, $type); } + + /** + * @return array + */ + public function getSchema(Parameter $parameter): array + { + return ['type' => 'date']; + } + + public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null + { + $in = $parameter instanceof QueryParameter ? 'query' : 'header'; + $key = $parameter->getKey(); + + return [ + new OpenApiParameter(name: $key.'[after]', in: $in), + new OpenApiParameter(name: $key.'[before]', in: $in), + new OpenApiParameter(name: $key.'[strictly_after]', in: $in), + new OpenApiParameter(name: $key.'[strictly_before]', in: $in), + ]; + } } diff --git a/Filter/ExistsFilter.php b/Filter/ExistsFilter.php index 0dde78e..4824057 100644 --- a/Filter/ExistsFilter.php +++ b/Filter/ExistsFilter.php @@ -15,9 +15,13 @@ use ApiPlatform\Doctrine\Common\Filter\ExistsFilterInterface; use ApiPlatform\Doctrine\Common\Filter\ExistsFilterTrait; +use ApiPlatform\Doctrine\Common\Filter\PropertyPlaceholderOpenApiParameterTrait; use ApiPlatform\Doctrine\Orm\Util\QueryBuilderHelper; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\JsonSchemaFilterInterface; +use ApiPlatform\Metadata\OpenApiParameterFilterInterface; use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Parameter; use Doctrine\ORM\Mapping\AssociationMapping; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ManyToManyOwningSideMapping; @@ -113,11 +117,12 @@ * * @author Teoh Han Hui */ -final class ExistsFilter extends AbstractFilter implements ExistsFilterInterface +final class ExistsFilter extends AbstractFilter implements ExistsFilterInterface, JsonSchemaFilterInterface, OpenApiParameterFilterInterface { use ExistsFilterTrait; + use PropertyPlaceholderOpenApiParameterTrait; - public function __construct(ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, ?array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, ?NameConverterInterface $nameConverter = null) + public function __construct(?ManagerRegistry $managerRegistry = null, ?LoggerInterface $logger = null, ?array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, ?NameConverterInterface $nameConverter = null) { parent::__construct($managerRegistry, $logger, $properties, $nameConverter); @@ -129,6 +134,14 @@ public function __construct(ManagerRegistry $managerRegistry, ?LoggerInterface $ */ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { + $parameter = $context['parameter'] ?? null; + + if (null !== ($value = $context['filters'][$parameter?->getProperty()] ?? null)) { + $this->filterProperty($this->denormalizePropertyName($parameter->getProperty()), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); + + return; + } + foreach ($context['filters'][$this->existsParameterName] ?? [] as $property => $value) { $this->filterProperty($this->denormalizePropertyName($property), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); } @@ -263,4 +276,12 @@ private function isAssociationNullable(AssociationMapping|array $associationMapp return true; } + + /** + * @return array + */ + public function getSchema(Parameter $parameter): array + { + return ['type' => 'boolean']; + } } diff --git a/Filter/NumericFilter.php b/Filter/NumericFilter.php index 545b552..c81a118 100644 --- a/Filter/NumericFilter.php +++ b/Filter/NumericFilter.php @@ -15,7 +15,9 @@ use ApiPlatform\Doctrine\Common\Filter\NumericFilterTrait; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\JsonSchemaFilterInterface; use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Parameter; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; @@ -106,7 +108,7 @@ * @author Amrouche Hamza * @author Teoh Han Hui */ -final class NumericFilter extends AbstractFilter +final class NumericFilter extends AbstractFilter implements JsonSchemaFilterInterface { use NumericFilterTrait; @@ -176,4 +178,9 @@ protected function getType(?string $doctrineType = null): string return 'int'; } + + public function getSchema(Parameter $parameter): array + { + return ['type' => 'numeric']; + } } diff --git a/Filter/OrderFilter.php b/Filter/OrderFilter.php index b2abdab..61b3a65 100644 --- a/Filter/OrderFilter.php +++ b/Filter/OrderFilter.php @@ -15,8 +15,12 @@ use ApiPlatform\Doctrine\Common\Filter\OrderFilterInterface; use ApiPlatform\Doctrine\Common\Filter\OrderFilterTrait; +use ApiPlatform\Doctrine\Common\Filter\PropertyPlaceholderOpenApiParameterTrait; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\JsonSchemaFilterInterface; +use ApiPlatform\Metadata\OpenApiParameterFilterInterface; use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Parameter; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; @@ -195,11 +199,12 @@ * @author Kévin Dunglas * @author Théo FIDRY */ -final class OrderFilter extends AbstractFilter implements OrderFilterInterface +final class OrderFilter extends AbstractFilter implements OrderFilterInterface, JsonSchemaFilterInterface, OpenApiParameterFilterInterface { use OrderFilterTrait; + use PropertyPlaceholderOpenApiParameterTrait; - public function __construct(ManagerRegistry $managerRegistry, string $orderParameterName = 'order', ?LoggerInterface $logger = null, ?array $properties = null, ?NameConverterInterface $nameConverter = null, private readonly ?string $orderNullsComparison = null) + public function __construct(?ManagerRegistry $managerRegistry = null, string $orderParameterName = 'order', ?LoggerInterface $logger = null, ?array $properties = null, ?NameConverterInterface $nameConverter = null, private readonly ?string $orderNullsComparison = null) { if (null !== $properties) { $properties = array_map(static function ($propertyOptions) { @@ -224,12 +229,17 @@ public function __construct(ManagerRegistry $managerRegistry, string $orderParam */ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { - if (isset($context['filters']) && !isset($context['filters'][$this->orderParameterName])) { + if ( + isset($context['filters']) + && (!isset($context['filters'][$this->orderParameterName]) || !\is_array($context['filters'][$this->orderParameterName])) + && !isset($context['parameter']) + ) { return; } - if (!isset($context['filters'][$this->orderParameterName]) || !\is_array($context['filters'][$this->orderParameterName])) { - parent::apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); + $parameter = $context['parameter'] ?? null; + if (null !== ($value = $context['filters'][$parameter?->getProperty()] ?? null)) { + $this->filterProperty($this->denormalizePropertyName($parameter->getProperty()), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context); return; } @@ -242,13 +252,13 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q /** * {@inheritdoc} */ - protected function filterProperty(string $property, $direction, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void + protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void { if (!$this->isPropertyEnabled($property, $resourceClass) || !$this->isPropertyMapped($property, $resourceClass)) { return; } - $direction = $this->normalizeValue($direction, $property); + $direction = $this->normalizeValue($value, $property); if (null === $direction) { return; } @@ -271,4 +281,12 @@ protected function filterProperty(string $property, $direction, QueryBuilder $qu $queryBuilder->addOrderBy(\sprintf('%s.%s', $alias, $field), $direction); } + + /** + * @return array + */ + public function getSchema(Parameter $parameter): array + { + return ['type' => 'string', 'enum' => ['asc', 'desc']]; + } } diff --git a/Filter/RangeFilter.php b/Filter/RangeFilter.php index 85de711..ae052fa 100644 --- a/Filter/RangeFilter.php +++ b/Filter/RangeFilter.php @@ -16,7 +16,11 @@ use ApiPlatform\Doctrine\Common\Filter\RangeFilterInterface; use ApiPlatform\Doctrine\Common\Filter\RangeFilterTrait; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Metadata\OpenApiParameterFilterInterface; use ApiPlatform\Metadata\Operation; +use ApiPlatform\Metadata\Parameter; +use ApiPlatform\Metadata\QueryParameter; +use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; @@ -105,7 +109,7 @@ * * @author Lee Siong Chan */ -final class RangeFilter extends AbstractFilter implements RangeFilterInterface +final class RangeFilter extends AbstractFilter implements RangeFilterInterface, OpenApiParameterFilterInterface { use RangeFilterTrait; @@ -222,4 +226,17 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf break; } } + + public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null + { + $in = $parameter instanceof QueryParameter ? 'query' : 'header'; + $key = $parameter->getKey(); + + return [ + new OpenApiParameter(name: $key.'[gt]', in: $in), + new OpenApiParameter(name: $key.'[lt]', in: $in), + new OpenApiParameter(name: $key.'[gte]', in: $in), + new OpenApiParameter(name: $key.'[lte]', in: $in), + ]; + } } diff --git a/PropertyHelperTrait.php b/PropertyHelperTrait.php index 8431e3e..d9376bc 100644 --- a/PropertyHelperTrait.php +++ b/PropertyHelperTrait.php @@ -29,7 +29,7 @@ */ trait PropertyHelperTrait { - abstract protected function getManagerRegistry(): ManagerRegistry; + abstract protected function getManagerRegistry(): ?ManagerRegistry; /** * Splits the given property into parts. From 70f7c7bbcfe8728d4a548b7ba97aff1cb885a355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 31 Jan 2025 14:52:20 +0100 Subject: [PATCH 51/72] chore: cross-package version dependencies (#6935) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ff2b513..8757f18 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ ], "require": { "php": ">=8.2", - "api-platform/doctrine-common": "^3.4 || ^4.0", + "api-platform/doctrine-common": "^4.1", "api-platform/metadata": "^3.4 || ^4.0", "api-platform/state": "^3.4 || ^4.0", "doctrine/orm": "^2.17 || ^3.0", From 5f0f749ac53019a730debd7ac95549c473f557fd Mon Sep 17 00:00:00 2001 From: Tac Tacelosky Date: Mon, 10 Feb 2025 04:23:32 -0500 Subject: [PATCH 52/72] chore: remove AdvancedNameConverterInterface usage (#6956) --- Tests/Fixtures/CustomConverter.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/Fixtures/CustomConverter.php b/Tests/Fixtures/CustomConverter.php index e0a06d8..da479c7 100644 --- a/Tests/Fixtures/CustomConverter.php +++ b/Tests/Fixtures/CustomConverter.php @@ -13,7 +13,6 @@ namespace ApiPlatform\Doctrine\Orm\Tests\Fixtures; -use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; @@ -21,7 +20,7 @@ * Custom converter that will only convert a property named "nameConverted" * with the same logic as Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter. */ -class CustomConverter implements AdvancedNameConverterInterface +class CustomConverter implements NameConverterInterface { private NameConverterInterface $nameConverter; From 5aa5047c514de3b147b4909bcabf0848a8ddd0c8 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 28 Feb 2025 11:08:08 +0100 Subject: [PATCH 53/72] chore: dependency constraints (#6988) --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 8757f18..1470080 100644 --- a/composer.json +++ b/composer.json @@ -25,15 +25,15 @@ "require": { "php": ">=8.2", "api-platform/doctrine-common": "^4.1", - "api-platform/metadata": "^3.4 || ^4.0", - "api-platform/state": "^3.4 || ^4.0", + "api-platform/metadata": "^4.1", + "api-platform/state": "^4.1", "doctrine/orm": "^2.17 || ^3.0", - "symfony/property-info": "^6.4 || ^7.0" + "symfony/property-info": "^6.4 || ^7.1" }, "require-dev": { "doctrine/doctrine-bundle": "^2.11", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^10.0", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "^11.2", "ramsey/uuid": "^4.7", "ramsey/uuid-doctrine": "^2.0", "symfony/cache": "^6.4 || ^7.0", @@ -60,7 +60,7 @@ }, "extra": { "branch-alias": { - "dev-main": "4.0.x-dev", + "dev-main": "4.2.x-dev", "dev-3.4": "3.4.x-dev" }, "symfony": { From ffae2b442a13436d9048359ccd3d8d9024a67850 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 28 Feb 2025 11:39:00 +0100 Subject: [PATCH 54/72] chore: dependency constraints for laravel 12 (#6989) --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index ff2b513..752ee62 100644 --- a/composer.json +++ b/composer.json @@ -28,12 +28,12 @@ "api-platform/metadata": "^3.4 || ^4.0", "api-platform/state": "^3.4 || ^4.0", "doctrine/orm": "^2.17 || ^3.0", - "symfony/property-info": "^6.4 || ^7.0" + "symfony/property-info": "^6.4 || ^7.1" }, "require-dev": { "doctrine/doctrine-bundle": "^2.11", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^10.0", + "phpspec/prophecy-phpunit": "^2.2", + "phpunit/phpunit": "^11.2", "ramsey/uuid": "^4.7", "ramsey/uuid-doctrine": "^2.0", "symfony/cache": "^6.4 || ^7.0", From fbaff08933ef9db56470e07ef68b73fb62235203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 7 Mar 2025 11:15:36 +0100 Subject: [PATCH 55/72] fix(doctrine): Add a proper exception when a doctrine manager could not be found for a resource class (#6995) * fix(doctrine): Add a proper exception when a doctrine manager could not be found for a resource class * Update ItemProvider.php --------- Co-authored-by: Antoine Bluchet --- State/ItemProvider.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/State/ItemProvider.php b/State/ItemProvider.php index 0a7f486..8f32acb 100644 --- a/State/ItemProvider.php +++ b/State/ItemProvider.php @@ -53,8 +53,11 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $entityClass = $options->getEntityClass(); } - /** @var EntityManagerInterface $manager */ + /** @var EntityManagerInterface|null $manager */ $manager = $this->managerRegistry->getManagerForClass($entityClass); + if (null === $manager) { + throw new RuntimeException(\sprintf('No manager found for class "%s". Are you sure it\'s an entity?', $entityClass)); + } $fetchData = $context['fetch_data'] ?? true; if (!$fetchData && \array_key_exists('id', $uriVariables)) { From 59a5cb85571dc75867854b0ec01369a9d805d822 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 7 Mar 2025 17:11:19 +0100 Subject: [PATCH 56/72] fix(doctrine): correct the use statement for ManagerRegistry (#7004) --- Extension/ParameterExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extension/ParameterExtension.php b/Extension/ParameterExtension.php index cc99611..838ee7b 100644 --- a/Extension/ParameterExtension.php +++ b/Extension/ParameterExtension.php @@ -22,8 +22,8 @@ use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ParameterNotFound; use Doctrine\ORM\QueryBuilder; +use Doctrine\Persistence\ManagerRegistry; use Psr\Container\ContainerInterface; -use Symfony\Bridge\Doctrine\ManagerRegistry; /** * Reads operation parameters and execute its filter. From 89a4bac54df434f39d796ec9cefb03ba74db0a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 8 Mar 2025 08:53:11 +0100 Subject: [PATCH 57/72] fix(doctrine): mapping ArrayAccess deprecation (#6982) * fix: Doctrine mapping ArrayAccess deprecation * fix tests --------- Co-authored-by: soyuka --- Extension/EagerLoadingExtension.php | 2 +- Tests/Extension/EagerLoadingExtensionTest.php | 33 ++++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Extension/EagerLoadingExtension.php b/Extension/EagerLoadingExtension.php index aa1e08a..240c1ff 100644 --- a/Extension/EagerLoadingExtension.php +++ b/Extension/EagerLoadingExtension.php @@ -181,7 +181,7 @@ private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInt $associationAlias = $existingJoin->getAlias(); $isLeftJoin = Join::LEFT_JOIN === $existingJoin->getJoinType(); } else { - $isNullable = $mapping['joinColumns'][0]['nullable'] ?? true; + $isNullable = $mapping['joinColumns'][0]?->nullable ?? true; $isLeftJoin = false !== $wasLeftJoin || true === $isNullable; $method = $isLeftJoin ? 'leftJoin' : 'innerJoin'; diff --git a/Tests/Extension/EagerLoadingExtensionTest.php b/Tests/Extension/EagerLoadingExtensionTest.php index 5dec2ce..2ad7a74 100644 --- a/Tests/Extension/EagerLoadingExtensionTest.php +++ b/Tests/Extension/EagerLoadingExtensionTest.php @@ -35,6 +35,7 @@ use ApiPlatform\Metadata\Property\PropertyNameCollection; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\JoinColumn; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use PHPUnit\Framework\TestCase; @@ -94,8 +95,8 @@ public function testApplyToCollection(): void $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->associationMappings = [ - 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class], - 'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => RelatedDummy::class], + 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: true)], 'targetEntity' => RelatedDummy::class], + 'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => RelatedDummy::class], ]; $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class); @@ -181,9 +182,9 @@ public function testApplyToItem(): void $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->associationMappings = [ - 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class], - 'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => UnknownDummy::class], - 'relatedDummy3' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinTable' => ['joinColumns' => [['nullable' => false]]], 'targetEntity' => UnknownDummy::class], + 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: true)], 'targetEntity' => RelatedDummy::class], + 'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => UnknownDummy::class], + 'relatedDummy3' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinTable' => ['joinColumns' => [new JoinColumn(nullable: false)]], 'targetEntity' => UnknownDummy::class], 'relatedDummy4' => ['fetch' => ClassMetadata::FETCH_EAGER, 'targetEntity' => UnknownDummy::class], 'relatedDummy5' => ['fetch' => ClassMetadata::FETCH_LAZY, 'targetEntity' => UnknownDummy::class], 'singleInheritanceRelation' => ['fetch' => ClassMetadata::FETCH_EAGER, 'targetEntity' => AbstractDummy::class], @@ -200,7 +201,7 @@ public function testApplyToItem(): void $relatedClassMetadataProphecy->hasField('embeddedDummy.name')->willReturn(true)->shouldBeCalled(); $relatedClassMetadataProphecy->associationMappings = [ - 'relation' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => UnknownDummy::class], + 'relation' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => UnknownDummy::class], 'thirdLevel' => ['fetch' => ClassMetadata::FETCH_EAGER, 'targetEntity' => ThirdLevel::class, 'sourceEntity' => RelatedDummy::class, 'inversedBy' => 'relatedDummies', 'type' => ClassMetadata::TO_ONE], ]; @@ -361,13 +362,13 @@ public function testMaxJoinsReached(): void $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->associationMappings = [ - 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => RelatedDummy::class], + 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => RelatedDummy::class], ]; $classMetadataProphecy->hasField('relatedDummy')->willReturn(true); $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class); $relatedClassMetadataProphecy->associationMappings = [ - 'dummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => Dummy::class], + 'dummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => Dummy::class], ]; $relatedClassMetadataProphecy->hasField('dummy')->willReturn(true); @@ -410,13 +411,13 @@ public function testMaxDepth(): void $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->associationMappings = [ - 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => RelatedDummy::class], + 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => RelatedDummy::class], ]; $classMetadataProphecy->hasField('relatedDummy')->willReturn(true); $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class); $relatedClassMetadataProphecy->associationMappings = [ - 'dummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => Dummy::class], + 'dummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => Dummy::class], ]; $relatedClassMetadataProphecy->hasField('dummy')->willReturn(true); @@ -471,7 +472,7 @@ public function testForceEager(): void $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->associationMappings = [ - 'relation' => ['fetch' => ClassMetadata::FETCH_LAZY, 'targetEntity' => UnknownDummy::class, 'joinColumns' => [['nullable' => false]]], + 'relation' => ['fetch' => ClassMetadata::FETCH_LAZY, 'targetEntity' => UnknownDummy::class, 'joinColumns' => [new JoinColumn(nullable: false)]], ]; $unknownClassMetadataProphecy = $this->prophesize(ClassMetadata::class); @@ -577,7 +578,7 @@ public function testResourceClassNotFoundExceptionPropertyNameCollection(): void $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->associationMappings = [ - 'relation' => ['fetch' => ClassMetadata::FETCH_LAZY, 'targetEntity' => UnknownDummy::class, 'joinColumns' => [['nullable' => false]]], + 'relation' => ['fetch' => ClassMetadata::FETCH_LAZY, 'targetEntity' => UnknownDummy::class, 'joinColumns' => [new JoinColumn(nullable: false)]], ]; $emProphecy = $this->prophesize(EntityManager::class); $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); @@ -751,8 +752,8 @@ public function testApplyToCollectionNoPartial(): void $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->associationMappings = [ - 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class], - 'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => RelatedDummy::class], + 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: true)], 'targetEntity' => RelatedDummy::class], + 'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => RelatedDummy::class], ]; $emProphecy = $this->prophesize(EntityManager::class); @@ -796,8 +797,8 @@ public function testApplyToCollectionWithANonReadableButFetchEagerProperty(): vo $classMetadataProphecy = $this->prophesize(ClassMetadata::class); $classMetadataProphecy->associationMappings = [ - 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => true]], 'targetEntity' => RelatedDummy::class], - 'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [['nullable' => false]], 'targetEntity' => RelatedDummy::class], + 'relatedDummy' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: true)], 'targetEntity' => RelatedDummy::class], + 'relatedDummy2' => ['fetch' => ClassMetadata::FETCH_EAGER, 'joinColumns' => [new JoinColumn(nullable: false)], 'targetEntity' => RelatedDummy::class], ]; $emProphecy = $this->prophesize(EntityManager::class); From 68430e6fac333e0350bf64f4e07446a2f3bbcd10 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Mon, 24 Mar 2025 14:07:46 +0100 Subject: [PATCH 58/72] =?UTF-8?q?Revert=20"fix(doctrine):=20throw=20an=20e?= =?UTF-8?q?xception=20when=20a=20filter=20is=20not=20found=20in=20a=20par?= =?UTF-8?q?=E2=80=A6"=20(#7046)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 2f967d9345004779f409b9ce1b5d0cbba84c7132. --- Extension/ParameterExtension.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Extension/ParameterExtension.php b/Extension/ParameterExtension.php index acd6aac..36ebc2b 100644 --- a/Extension/ParameterExtension.php +++ b/Extension/ParameterExtension.php @@ -16,7 +16,6 @@ use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait; use ApiPlatform\Doctrine\Orm\Filter\FilterInterface; use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; -use ApiPlatform\Metadata\Exception\InvalidArgumentException; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ParameterNotFound; use Doctrine\ORM\QueryBuilder; @@ -51,11 +50,9 @@ private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInter } $filter = $this->filterLocator->has($filterId) ? $this->filterLocator->get($filterId) : null; - if (!$filter instanceof FilterInterface) { - throw new InvalidArgumentException(\sprintf('Could not find filter "%s" for parameter "%s" in operation "%s" for resource "%s".', $filterId, $parameter->getKey(), $operation?->getShortName(), $resourceClass)); + if ($filter instanceof FilterInterface) { + $filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $values, 'parameter' => $parameter] + $context); } - - $filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $values, 'parameter' => $parameter] + $context); } } From b2b5f4bb1498aed276cfe28f434dff674bb9b26d Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 28 Mar 2025 16:01:40 +0100 Subject: [PATCH 59/72] fix(doctrine): joinColumn might be an array (#7060) fixes #7052 --- Extension/EagerLoadingExtension.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Extension/EagerLoadingExtension.php b/Extension/EagerLoadingExtension.php index 240c1ff..bcff245 100644 --- a/Extension/EagerLoadingExtension.php +++ b/Extension/EagerLoadingExtension.php @@ -181,7 +181,13 @@ private function joinRelations(QueryBuilder $queryBuilder, QueryNameGeneratorInt $associationAlias = $existingJoin->getAlias(); $isLeftJoin = Join::LEFT_JOIN === $existingJoin->getJoinType(); } else { - $isNullable = $mapping['joinColumns'][0]?->nullable ?? true; + $joinColumn = $mapping['joinColumns'][0] ?? ['nullable' => true]; + if (\is_array($joinColumn)) { + $isNullable = $joinColumn['nullable'] ?? true; + } else { + $isNullable = $joinColumn->nullable ?? true; + } + $isLeftJoin = false !== $wasLeftJoin || true === $isNullable; $method = $isLeftJoin ? 'leftJoin' : 'innerJoin'; From 4cfd9b739e82c6fb2c6f9e9c7ac2c42a1bc589d2 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 11 Apr 2025 11:32:56 +0200 Subject: [PATCH 60/72] chore: phpunit missing deprecation triggers (#7059) --- phpunit.xml.dist | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 913b48c..12b5bda 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,6 +11,11 @@ + + trigger_deprecation + Doctrine\Deprecations\Deprecation::trigger + Doctrine\Deprecations\Deprecation::delegateTriggerToBackend + ./ From 7f0c61ec30546593ebe0aa69652d471ac0e82898 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 18 Apr 2025 10:39:51 +0200 Subject: [PATCH 61/72] ci: patch phpunit deprecations inside component (#7103) --- composer.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 1470080..6b7c9bd 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,7 @@ "require-dev": { "doctrine/doctrine-bundle": "^2.11", "phpspec/prophecy-phpunit": "^2.2", - "phpunit/phpunit": "^11.2", + "phpunit/phpunit": "11.5.x-dev", "ramsey/uuid": "^4.7", "ramsey/uuid-doctrine": "^2.0", "symfony/cache": "^6.4 || ^7.0", @@ -73,5 +73,11 @@ }, "scripts": { "test": "./vendor/bin/phpunit" - } + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/soyuka/phpunit" + } + ] } From 9f6640daf280469b88d8afb4507b20a8dd460e6e Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Wed, 23 Apr 2025 10:40:08 +0200 Subject: [PATCH 62/72] refactor(state): state options code duplication (#7109) --- ...neOrmResourceCollectionMetadataFactory.php | 13 +++++-------- State/CollectionProvider.php | 7 +++---- State/ItemProvider.php | 7 +++---- State/LinksHandlerTrait.php | 19 ++++--------------- 4 files changed, 15 insertions(+), 31 deletions(-) diff --git a/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php b/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php index c102182..77b7737 100644 --- a/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php +++ b/Metadata/Resource/DoctrineOrmResourceCollectionMetadataFactory.php @@ -22,11 +22,14 @@ use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\Metadata\Resource\ResourceMetadataCollection; +use ApiPlatform\State\Util\StateOptionsTrait; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ManagerRegistry; final class DoctrineOrmResourceCollectionMetadataFactory implements ResourceMetadataCollectionFactoryInterface { + use StateOptionsTrait; + public function __construct(private readonly ManagerRegistry $managerRegistry, private readonly ResourceMetadataCollectionFactoryInterface $decorated) { } @@ -45,10 +48,7 @@ public function create(string $resourceClass): ResourceMetadataCollection if ($operations) { /** @var Operation $operation */ foreach ($resourceMetadata->getOperations() as $operationName => $operation) { - $entityClass = $operation->getClass(); - if (($options = $operation->getStateOptions()) && $options instanceof Options && $options->getEntityClass()) { - $entityClass = $options->getEntityClass(); - } + $entityClass = $this->getStateOptionsClass($operation, $operation->getClass(), Options::class); if (!$this->managerRegistry->getManagerForClass($entityClass) instanceof EntityManagerInterface) { continue; @@ -64,10 +64,7 @@ public function create(string $resourceClass): ResourceMetadataCollection if ($graphQlOperations) { foreach ($graphQlOperations as $operationName => $graphQlOperation) { - $entityClass = $graphQlOperation->getClass(); - if (($options = $graphQlOperation->getStateOptions()) && $options instanceof Options && $options->getEntityClass()) { - $entityClass = $options->getEntityClass(); - } + $entityClass = $this->getStateOptionsClass($graphQlOperation, $graphQlOperation->getClass(), Options::class); if (!$this->managerRegistry->getManagerForClass($entityClass) instanceof EntityManagerInterface) { continue; diff --git a/State/CollectionProvider.php b/State/CollectionProvider.php index ab2cb5a..3815447 100644 --- a/State/CollectionProvider.php +++ b/State/CollectionProvider.php @@ -21,6 +21,7 @@ use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\State\ProviderInterface; +use ApiPlatform\State\Util\StateOptionsTrait; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ManagerRegistry; use Psr\Container\ContainerInterface; @@ -35,6 +36,7 @@ final class CollectionProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; + use StateOptionsTrait; /** * @param QueryCollectionExtensionInterface[] $collectionExtensions @@ -48,10 +50,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource public function provide(Operation $operation, array $uriVariables = [], array $context = []): iterable { - $entityClass = $operation->getClass(); - if (($options = $operation->getStateOptions()) && $options instanceof Options && $options->getEntityClass()) { - $entityClass = $options->getEntityClass(); - } + $entityClass = $this->getStateOptionsClass($operation, $operation->getClass(), Options::class); /** @var EntityManagerInterface $manager */ $manager = $this->managerRegistry->getManagerForClass($entityClass); diff --git a/State/ItemProvider.php b/State/ItemProvider.php index 8f32acb..b201d03 100644 --- a/State/ItemProvider.php +++ b/State/ItemProvider.php @@ -21,6 +21,7 @@ use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; use ApiPlatform\State\ProviderInterface; +use ApiPlatform\State\Util\StateOptionsTrait; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ManagerRegistry; use Psr\Container\ContainerInterface; @@ -35,6 +36,7 @@ final class ItemProvider implements ProviderInterface { use LinksHandlerLocatorTrait; use LinksHandlerTrait; + use StateOptionsTrait; /** * @param QueryItemExtensionInterface[] $itemExtensions @@ -48,10 +50,7 @@ public function __construct(ResourceMetadataCollectionFactoryInterface $resource public function provide(Operation $operation, array $uriVariables = [], array $context = []): ?object { - $entityClass = $operation->getClass(); - if (($options = $operation->getStateOptions()) && $options instanceof Options && $options->getEntityClass()) { - $entityClass = $options->getEntityClass(); - } + $entityClass = $this->getStateOptionsClass($operation, $operation->getClass(), Options::class); /** @var EntityManagerInterface|null $manager */ $manager = $this->managerRegistry->getManagerForClass($entityClass); diff --git a/State/LinksHandlerTrait.php b/State/LinksHandlerTrait.php index 9a088a4..d44805a 100644 --- a/State/LinksHandlerTrait.php +++ b/State/LinksHandlerTrait.php @@ -17,6 +17,7 @@ use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Operation; +use ApiPlatform\State\Util\StateOptionsTrait; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; @@ -27,6 +28,7 @@ trait LinksHandlerTrait { use CommonLinksHandlerTrait; + use StateOptionsTrait; private ManagerRegistry $managerRegistry; @@ -159,25 +161,12 @@ private function handleLinks(QueryBuilder $queryBuilder, array $identifiers, Que private function getLinkFromClass(Link $link, Operation $operation): string { $fromClass = $link->getFromClass(); - if ($fromClass === $operation->getClass() && $entityClass = $this->getStateOptionsEntityClass($operation)) { + if ($fromClass === $operation->getClass() && $entityClass = $this->getStateOptionsClass($operation, $operation->getClass(), Options::class)) { return $entityClass; } $operation = $this->resourceMetadataCollectionFactory->create($fromClass)->getOperation(); - if ($entityClass = $this->getStateOptionsEntityClass($operation)) { - return $entityClass; - } - - throw new \Exception('Can not found a doctrine class for this link.'); - } - - private function getStateOptionsEntityClass(Operation $operation): ?string - { - if (($options = $operation->getStateOptions()) && $options instanceof Options && $entityClass = $options->getEntityClass()) { - return $entityClass; - } - - return null; + return $this->getStateOptionsClass($operation, $operation->getClass(), Options::class); } } From 2f64d82c068e545539649995538593a70656d7c4 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Wed, 7 May 2025 12:11:37 +0200 Subject: [PATCH 63/72] fix(doctrine): filters schema for dates and numbers (#7131) --- Filter/DateFilter.php | 2 +- Filter/NumericFilter.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Filter/DateFilter.php b/Filter/DateFilter.php index b1eb525..6559aba 100644 --- a/Filter/DateFilter.php +++ b/Filter/DateFilter.php @@ -280,7 +280,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf */ public function getSchema(Parameter $parameter): array { - return ['type' => 'date']; + return ['type' => 'string', 'format' => 'date']; } public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|array|null diff --git a/Filter/NumericFilter.php b/Filter/NumericFilter.php index c81a118..71e5841 100644 --- a/Filter/NumericFilter.php +++ b/Filter/NumericFilter.php @@ -181,6 +181,6 @@ protected function getType(?string $doctrineType = null): string public function getSchema(Parameter $parameter): array { - return ['type' => 'numeric']; + return ['type' => 'number']; } } From 6380a9518e64eb495fb5e8972902fb78e3be752e Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Wed, 14 May 2025 06:54:42 +0200 Subject: [PATCH 64/72] chore(doctrine): constraint api-platform/state v4.1.8 (#7142) fixes #7138 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6b7c9bd..6972438 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "php": ">=8.2", "api-platform/doctrine-common": "^4.1", "api-platform/metadata": "^4.1", - "api-platform/state": "^4.1", + "api-platform/state": "^4.1.8", "doctrine/orm": "^2.17 || ^3.0", "symfony/property-info": "^6.4 || ^7.1" }, From 5e0cd55b2bf0dbdaf512fbdbbae71f1d79827980 Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 22 May 2025 15:13:30 +0200 Subject: [PATCH 65/72] chore: bump patch dependencies --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 6972438..7ab5506 100644 --- a/composer.json +++ b/composer.json @@ -24,9 +24,9 @@ ], "require": { "php": ">=8.2", - "api-platform/doctrine-common": "^4.1", - "api-platform/metadata": "^4.1", - "api-platform/state": "^4.1.8", + "api-platform/doctrine-common": "^4.1.11", + "api-platform/metadata": "^4.1.11", + "api-platform/state": "^4.1.11", "doctrine/orm": "^2.17 || ^3.0", "symfony/property-info": "^6.4 || ^7.1" }, From b85107d6c7f287bdb265e626550449a399b4c031 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Wed, 28 May 2025 10:03:08 +0200 Subject: [PATCH 66/72] ci: prefer-lowest to avoid bumping inter components dependencies (#7169) --- Filter/BackedEnumFilter.php | 2 +- composer.json | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Filter/BackedEnumFilter.php b/Filter/BackedEnumFilter.php index fffcb13..de0d15b 100644 --- a/Filter/BackedEnumFilter.php +++ b/Filter/BackedEnumFilter.php @@ -163,7 +163,7 @@ protected function isBackedEnumField(string $property, string $resourceClass): b $fieldMapping = (array) $fieldMapping; } - if (!$enumType = $fieldMapping['enumType']) { + if (!($enumType = $fieldMapping['enumType'] ?? null)) { return false; } diff --git a/composer.json b/composer.json index 7ab5506..bccdaef 100644 --- a/composer.json +++ b/composer.json @@ -61,7 +61,8 @@ "extra": { "branch-alias": { "dev-main": "4.2.x-dev", - "dev-3.4": "3.4.x-dev" + "dev-3.4": "3.4.x-dev", + "dev-4.1": "4.1.x-dev" }, "symfony": { "require": "^6.4 || ^7.0" @@ -79,5 +80,6 @@ "type": "vcs", "url": "https://github.com/soyuka/phpunit" } - ] + ], + "version": "4.1.12" } From 31a689120418309db34d882a2ac0c5584c419eb5 Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 6 Jun 2025 16:02:15 +0200 Subject: [PATCH 67/72] fix: bump composer.json version nodes --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index bccdaef..d6c844c 100644 --- a/composer.json +++ b/composer.json @@ -81,5 +81,5 @@ "url": "https://github.com/soyuka/phpunit" } ], - "version": "4.1.12" + "version": "4.1.14" } From 5f497f6b4c217cd03cc804f860d63f623d9db1ee Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 6 Jun 2025 16:18:03 +0200 Subject: [PATCH 68/72] chore: missing "v" prefix in composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d6c844c..7b99c4c 100644 --- a/composer.json +++ b/composer.json @@ -81,5 +81,5 @@ "url": "https://github.com/soyuka/phpunit" } ], - "version": "4.1.14" + "version": "v4.1.15" } From 61a199da6f6014dba2da43ea1a66b2c9dda27263 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Fri, 6 Jun 2025 16:56:47 +0200 Subject: [PATCH 69/72] ci: remove version from composer to avoid release side effects (#7196) --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 7b99c4c..a32498d 100644 --- a/composer.json +++ b/composer.json @@ -80,6 +80,5 @@ "type": "vcs", "url": "https://github.com/soyuka/phpunit" } - ], - "version": "v4.1.15" + ] } From 9989d0fd4badfe77fa71fed9ae734d146bc0495d Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Mon, 15 Sep 2025 11:57:10 +0200 Subject: [PATCH 70/72] fix(metadata): compute isWritable during updates (#7383) fixes #7382 --- Metadata/Property/DoctrineOrmPropertyMetadataFactory.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php b/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php index c450e85..7706c3f 100644 --- a/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php +++ b/Metadata/Property/DoctrineOrmPropertyMetadataFactory.php @@ -56,6 +56,10 @@ public function create(string $resourceClass, string $property, array $options = break; } + if ($options['api_allow_update'] ?? false) { + break; + } + if ($doctrineClassMetadata instanceof ClassMetadata) { $writable = $doctrineClassMetadata->isIdentifierNatural(); } else { From 686d508cdda2e2a53d093d10a26de70d88c00314 Mon Sep 17 00:00:00 2001 From: Nicolas LAURENT Date: Fri, 31 Oct 2025 09:52:41 +0100 Subject: [PATCH 71/72] fix(doctrine): properly set properties according to interface (#7487) --- Extension/ParameterExtension.php | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Extension/ParameterExtension.php b/Extension/ParameterExtension.php index 17d0235..8d20754 100644 --- a/Extension/ParameterExtension.php +++ b/Extension/ParameterExtension.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Doctrine\Orm\Extension; use ApiPlatform\Doctrine\Common\Filter\ManagerRegistryAwareInterface; +use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface; use ApiPlatform\Doctrine\Common\ParameterValueExtractorTrait; use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; use ApiPlatform\Doctrine\Orm\Filter\FilterInterface; @@ -68,19 +69,21 @@ private function applyFilter(QueryBuilder $queryBuilder, QueryNameGeneratorInter $filter->setManagerRegistry($this->managerRegistry); } - if ($filter instanceof AbstractFilter && !$filter->getProperties()) { + if ($filter instanceof PropertyAwareFilterInterface) { + $properties = []; $propertyKey = $parameter->getProperty() ?? $parameter->getKey(); - - if (str_contains($propertyKey, ':property')) { - $extraProperties = $parameter->getExtraProperties()['_properties'] ?? []; - foreach (array_keys($extraProperties) as $property) { - $properties[$property] = $parameter->getFilterContext(); + if ($filter instanceof AbstractFilter) { + $properties = $filter->getProperties() ?? []; + + if (str_contains($propertyKey, ':property')) { + $extraProperties = $parameter->getExtraProperties()['_properties'] ?? []; + foreach (array_keys($extraProperties) as $property) { + $properties[$property] = $parameter->getFilterContext(); + } } - } else { - $properties = [$propertyKey => $parameter->getFilterContext()]; } - $filter->setProperties($properties ?? []); + $filter->setProperties($properties + [$propertyKey => $parameter->getFilterContext()]); } $filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, From f796384bf4208c1a3c9132b8f79fd2475c119aec Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 13 Nov 2025 16:57:28 +0100 Subject: [PATCH 72/72] chore: symfony/validator:^6.4.11 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a32498d..d911cbb 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ "symfony/property-access": "^6.4 || ^7.0", "symfony/serializer": "^6.4 || ^7.0", "symfony/uid": "^6.4 || ^7.0", - "symfony/validator": "^6.4 || ^7.0", + "symfony/validator": "^6.4.11 || ^7.0", "symfony/yaml": "^6.4 || ^7.0" }, "autoload": {