diff --git a/src/Doctrine/Common/Filter/DateFilterInterface.php b/src/Doctrine/Common/Filter/DateFilterInterface.php index cc748e701f..9058c7acd3 100644 --- a/src/Doctrine/Common/Filter/DateFilterInterface.php +++ b/src/Doctrine/Common/Filter/DateFilterInterface.php @@ -24,6 +24,7 @@ interface DateFilterInterface { public const PARAMETER_BEFORE = 'before'; public const PARAMETER_STRICTLY_BEFORE = 'strictly_before'; + public const PARAMETER_EXACTLY = 'exactly'; public const PARAMETER_AFTER = 'after'; public const PARAMETER_STRICTLY_AFTER = 'strictly_after'; public const EXCLUDE_NULL = 'exclude_null'; diff --git a/src/Doctrine/Common/Filter/DateFilterTrait.php b/src/Doctrine/Common/Filter/DateFilterTrait.php index 33f2466445..77227460e8 100644 --- a/src/Doctrine/Common/Filter/DateFilterTrait.php +++ b/src/Doctrine/Common/Filter/DateFilterTrait.php @@ -44,8 +44,9 @@ public function getDescription(string $resourceClass): array continue; } - $description += $this->getFilterDescription($property, self::PARAMETER_BEFORE); $description += $this->getFilterDescription($property, self::PARAMETER_STRICTLY_BEFORE); + $description += $this->getFilterDescription($property, self::PARAMETER_BEFORE); + $description += $this->getFilterDescription($property, self::PARAMETER_EXACTLY); $description += $this->getFilterDescription($property, self::PARAMETER_AFTER); $description += $this->getFilterDescription($property, self::PARAMETER_STRICTLY_AFTER); } diff --git a/src/Doctrine/Odm/Filter/DateFilter.php b/src/Doctrine/Odm/Filter/DateFilter.php index 86a0958aad..a800ddd0fa 100644 --- a/src/Doctrine/Odm/Filter/DateFilter.php +++ b/src/Doctrine/Odm/Filter/DateFilter.php @@ -28,7 +28,7 @@ /** * The date filter allows to filter a collection by date intervals. * - * Syntax: `?property[]=value`. + * Syntax: `?property[]=value`. * * The value can take any date format supported by the [`\DateTime` constructor](https://www.php.net/manual/en/datetime.construct.php). * @@ -158,6 +158,16 @@ protected function filterProperty(string $property, $value, Builder $aggregation $aggregationBuilder->match()->field($matchField)->notEqual(null); } + if (isset($value[self::PARAMETER_STRICTLY_BEFORE])) { + $this->addMatch( + $aggregationBuilder, + $matchField, + self::PARAMETER_STRICTLY_BEFORE, + $value[self::PARAMETER_STRICTLY_BEFORE], + $nullManagement + ); + } + if (isset($value[self::PARAMETER_BEFORE])) { $this->addMatch( $aggregationBuilder, @@ -168,12 +178,12 @@ protected function filterProperty(string $property, $value, Builder $aggregation ); } - if (isset($value[self::PARAMETER_STRICTLY_BEFORE])) { + if (isset($value[self::PARAMETER_EXACTLY])) { $this->addMatch( $aggregationBuilder, $matchField, - self::PARAMETER_STRICTLY_BEFORE, - $value[self::PARAMETER_STRICTLY_BEFORE], + self::PARAMETER_EXACTLY, + $value[self::PARAMETER_EXACTLY], $nullManagement ); } @@ -222,8 +232,9 @@ private function addMatch(Builder $aggregationBuilder, string $field, string $op } $operatorValue = [ - self::PARAMETER_BEFORE => '$lte', self::PARAMETER_STRICTLY_BEFORE => '$lt', + self::PARAMETER_BEFORE => '$lte', + self::PARAMETER_EXACTLY => '$eq', self::PARAMETER_AFTER => '$gte', self::PARAMETER_STRICTLY_AFTER => '$gt', ]; @@ -257,10 +268,11 @@ public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|arr $key = $parameter->getKey(); return [ - new OpenApiParameter(name: $key.'[after]', in: $in), + new OpenApiParameter(name: $key.'[strictly_before]', in: $in), new OpenApiParameter(name: $key.'[before]', in: $in), + new OpenApiParameter(name: $key.'[exactly]', in: $in), + new OpenApiParameter(name: $key.'[after]', in: $in), new OpenApiParameter(name: $key.'[strictly_after]', in: $in), - new OpenApiParameter(name: $key.'[strictly_before]', in: $in), ]; } } diff --git a/src/Doctrine/Orm/Filter/DateFilter.php b/src/Doctrine/Orm/Filter/DateFilter.php index b1eb525008..448f92e8e3 100644 --- a/src/Doctrine/Orm/Filter/DateFilter.php +++ b/src/Doctrine/Orm/Filter/DateFilter.php @@ -31,7 +31,7 @@ /** * The date filter allows to filter a collection by date intervals. * - * Syntax: `?property[]=value`. + * Syntax: `?property[]=value`. * * The value can take any date format supported by the [`\DateTime` constructor](https://www.php.net/manual/en/datetime.construct.php). * @@ -169,6 +169,19 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB $queryBuilder->andWhere($queryBuilder->expr()->isNotNull(\sprintf('%s.%s', $alias, $field))); } + if (isset($value[self::PARAMETER_STRICTLY_BEFORE])) { + $this->addWhere( + $queryBuilder, + $queryNameGenerator, + $alias, + $field, + self::PARAMETER_STRICTLY_BEFORE, + $value[self::PARAMETER_STRICTLY_BEFORE], + $nullManagement, + $type + ); + } + if (isset($value[self::PARAMETER_BEFORE])) { $this->addWhere( $queryBuilder, @@ -182,14 +195,14 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB ); } - if (isset($value[self::PARAMETER_STRICTLY_BEFORE])) { + if (isset($value[self::PARAMETER_EXACTLY])) { $this->addWhere( $queryBuilder, $queryNameGenerator, $alias, $field, - self::PARAMETER_STRICTLY_BEFORE, - $value[self::PARAMETER_STRICTLY_BEFORE], + self::PARAMETER_EXACTLY, + $value[self::PARAMETER_EXACTLY], $nullManagement, $type ); @@ -247,8 +260,9 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf $valueParameter = $queryNameGenerator->generateParameterName($field); $operatorValue = [ - self::PARAMETER_BEFORE => '<=', self::PARAMETER_STRICTLY_BEFORE => '<', + self::PARAMETER_BEFORE => '<=', + self::PARAMETER_EXACTLY => '=', self::PARAMETER_AFTER => '>=', self::PARAMETER_STRICTLY_AFTER => '>', ]; @@ -289,10 +303,11 @@ public function getOpenApiParameters(Parameter $parameter): OpenApiParameter|arr $key = $parameter->getKey(); return [ - new OpenApiParameter(name: $key.'[after]', in: $in), + new OpenApiParameter(name: $key.'[strictly_before]', in: $in), new OpenApiParameter(name: $key.'[before]', in: $in), + new OpenApiParameter(name: $key.'[exactly]', in: $in), + new OpenApiParameter(name: $key.'[after]', in: $in), new OpenApiParameter(name: $key.'[strictly_after]', in: $in), - new OpenApiParameter(name: $key.'[strictly_before]', in: $in), ]; } } diff --git a/tests/Functional/Parameters/DoctrineTest.php b/tests/Functional/Parameters/DoctrineTest.php index c0ba47b3fd..6c1c4641e7 100644 --- a/tests/Functional/Parameters/DoctrineTest.php +++ b/tests/Functional/Parameters/DoctrineTest.php @@ -47,7 +47,7 @@ public function testDoctrineEntitySearchFilter(): void $this->assertEquals('bar', $a['hydra:member'][1]['foo']); $this->assertArraySubset(['hydra:search' => [ - 'hydra:template' => \sprintf('/%s{?foo,fooAlias,order[order[id]],order[order[foo]],searchPartial[foo],searchExact[foo],searchOnTextAndDate[foo],searchOnTextAndDate[createdAt][before],searchOnTextAndDate[createdAt][strictly_before],searchOnTextAndDate[createdAt][after],searchOnTextAndDate[createdAt][strictly_after],q,id,createdAt}', $route), + 'hydra:template' => \sprintf('/%s{?foo,fooAlias,order[order[id]],order[order[foo]],searchPartial[foo],searchExact[foo],searchOnTextAndDate[foo],searchOnTextAndDate[createdAt][strictly_before],searchOnTextAndDate[createdAt][before],searchOnTextAndDate[createdAt][exactly],searchOnTextAndDate[createdAt][after],searchOnTextAndDate[createdAt][strictly_after],q,id,createdAt}', $route), ]], $a); $this->assertArraySubset(['@type' => 'IriTemplateMapping', 'variable' => 'fooAlias', 'property' => 'foo'], $a['hydra:search']['hydra:mapping'][1]); @@ -71,6 +71,11 @@ public function testDoctrineEntitySearchFilter(): void $members = $response->toArray()['hydra:member']; $this->assertCount(1, $members); $this->assertArraySubset(['foo' => 'bar', 'createdAt' => '2024-01-21T00:00:00+00:00'], $members[0]); + + $response = self::createClient()->request('GET', $route.'?searchOnTextAndDate[createdAt][exactly]=2024-01-22'); + $members = $response->toArray()['hydra:member']; + $this->assertCount(1, $members); + $this->assertArraySubset(['foo' => 'bar', 'createdAt' => '2024-01-22T00:00:00+00:00'], $members[0]); } public function testGraphQl(): void @@ -134,7 +139,7 @@ public function testStateOptions(): void $manager->flush(); $response = self::createClient()->request('GET', 'filter_with_state_options?date[before]='.$d->format('Y-m-d')); $a = $response->toArray(); - $this->assertEquals('/filter_with_state_options{?date[before],date[strictly_before],date[after],date[strictly_after]}', $a['hydra:search']['hydra:template']); + $this->assertEquals('/filter_with_state_options{?date[strictly_before],date[before],date[exactly],date[after],date[strictly_after]}', $a['hydra:search']['hydra:template']); $this->assertCount(1, $a['hydra:member']); $this->assertEquals('current', $a['hydra:member'][0]['name']); $response = self::createClient()->request('GET', 'filter_with_state_options?date[strictly_after]='.$d->format('Y-m-d'));