Skip to content

Commit 3110c9b

Browse files
jpdz8005alanpoulain
authored andcommitted
Update the ExistsFilter behavior
1 parent b3bd652 commit 3110c9b

File tree

18 files changed

+508
-109
lines changed

18 files changed

+508
-109
lines changed

features/doctrine/date_filter.feature

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ Feature: Date filter on collections
404404
},
405405
"hydra:search": {
406406
"@type": "hydra:IriTemplate",
407-
"hydra:template": "/dummies{?dummyBoolean,relatedDummy.embeddedDummy.dummyBoolean,dummyDate[before],dummyDate[strictly_before],dummyDate[after],dummyDate[strictly_after],relatedDummy.dummyDate[before],relatedDummy.dummyDate[strictly_before],relatedDummy.dummyDate[after],relatedDummy.dummyDate[strictly_after],description[exists],relatedDummy.name[exists],dummyBoolean[exists],relatedDummy[exists],dummyFloat,dummyFloat[],dummyPrice,dummyPrice[],order[id],order[name],order[description],order[relatedDummy.name],order[relatedDummy.symfony],order[dummyDate],dummyFloat[between],dummyFloat[gt],dummyFloat[gte],dummyFloat[lt],dummyFloat[lte],dummyPrice[between],dummyPrice[gt],dummyPrice[gte],dummyPrice[lt],dummyPrice[lte],id,id[],name,alias,description,relatedDummy.name,relatedDummy.name[],relatedDummies,relatedDummies[],dummy,relatedDummies.name,relatedDummy.thirdLevel.level,relatedDummy.thirdLevel.level[],relatedDummy.thirdLevel.fourthLevel.level,relatedDummy.thirdLevel.fourthLevel.level[],relatedDummy.thirdLevel.badFourthLevel.level,relatedDummy.thirdLevel.badFourthLevel.level[],relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level,relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level[],properties[]}",
407+
"hydra:template": "/dummies{?dummyBoolean,relatedDummy.embeddedDummy.dummyBoolean,dummyDate[before],dummyDate[strictly_before],dummyDate[after],dummyDate[strictly_after],relatedDummy.dummyDate[before],relatedDummy.dummyDate[strictly_before],relatedDummy.dummyDate[after],relatedDummy.dummyDate[strictly_after],exists[alias],exists[description],exists[relatedDummy.name],exists[dummyBoolean],exists[relatedDummy],dummyFloat,dummyFloat[],dummyPrice,dummyPrice[],order[id],order[name],order[description],order[relatedDummy.name],order[relatedDummy.symfony],order[dummyDate],dummyFloat[between],dummyFloat[gt],dummyFloat[gte],dummyFloat[lt],dummyFloat[lte],dummyPrice[between],dummyPrice[gt],dummyPrice[gte],dummyPrice[lt],dummyPrice[lte],id,id[],name,alias,description,relatedDummy.name,relatedDummy.name[],relatedDummies,relatedDummies[],dummy,relatedDummies.name,relatedDummy.thirdLevel.level,relatedDummy.thirdLevel.level[],relatedDummy.thirdLevel.fourthLevel.level,relatedDummy.thirdLevel.fourthLevel.level[],relatedDummy.thirdLevel.badFourthLevel.level,relatedDummy.thirdLevel.badFourthLevel.level[],relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level,relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level[],properties[]}",
408408
"hydra:variableRepresentation": "BasicRepresentation",
409409
"hydra:mapping": [
410410
{
@@ -469,25 +469,31 @@ Feature: Date filter on collections
469469
},
470470
{
471471
"@type": "IriTemplateMapping",
472-
"variable": "description[exists]",
472+
"variable": "exists[alias]",
473+
"property": "alias",
474+
"required": false
475+
},
476+
{
477+
"@type": "IriTemplateMapping",
478+
"variable": "exists[description]",
473479
"property": "description",
474480
"required": false
475481
},
476482
{
477483
"@type": "IriTemplateMapping",
478-
"variable": "relatedDummy.name[exists]",
484+
"variable": "exists[relatedDummy.name]",
479485
"property": "relatedDummy.name",
480486
"required": false
481487
},
482488
{
483489
"@type": "IriTemplateMapping",
484-
"variable": "dummyBoolean[exists]",
490+
"variable": "exists[dummyBoolean]",
485491
"property": "dummyBoolean",
486492
"required": false
487493
},
488494
{
489495
"@type": "IriTemplateMapping",
490-
"variable": "relatedDummy[exists]",
496+
"variable": "exists[relatedDummy]",
491497
"property": "relatedDummy",
492498
"required": false
493499
},
@@ -831,7 +837,7 @@ Feature: Date filter on collections
831837
},
832838
"hydra:search": {
833839
"@type": "hydra:IriTemplate",
834-
"hydra:template": "/dummies{?dummyBoolean,dummyDate[before],dummyDate[after],relatedDummy.dummyDate[before],relatedDummy.dummyDate[strictly_before],relatedDummy.dummyDate[after],relatedDummy.dummyDate[strictly_after],description[exists],relatedDummy.name[exists],dummyBoolean[exists],relatedDummy[exists],dummyFloat,dummyPrice,order[id],order[name],order[relatedDummy.symfony],dummyFloat[between],dummyFloat[gt],dummyFloat[gte],dummyFloat[lt],dummyFloat[lte],dummyPrice[between],dummyPrice[gt],dummyPrice[gte],dummyPrice[lt],dummyPrice[lte],id,id[],name,alias,description,relatedDummy.name,relatedDummy.name[],relatedDummies,relatedDummies[],dummy,relatedDummies.name}",
840+
"hydra:template": "/dummies{?dummyBoolean,dummyDate[before],dummyDate[after],relatedDummy.dummyDate[before],relatedDummy.dummyDate[strictly_before],relatedDummy.dummyDate[after],relatedDummy.dummyDate[strictly_after],exists[description],exists[relatedDummy.name],exists[dummyBoolean],exists[relatedDummy],dummyFloat,dummyPrice,order[id],order[name],order[relatedDummy.symfony],dummyFloat[between],dummyFloat[gt],dummyFloat[gte],dummyFloat[lt],dummyFloat[lte],dummyPrice[between],dummyPrice[gt],dummyPrice[gte],dummyPrice[lt],dummyPrice[lte],id,id[],name,alias,description,relatedDummy.name,relatedDummy.name[],relatedDummies,relatedDummies[],dummy,relatedDummies.name}",
835841
"hydra:variableRepresentation": "BasicRepresentation",
836842
"hydra:mapping": [
837843
{
@@ -890,25 +896,25 @@ Feature: Date filter on collections
890896
},
891897
{
892898
"@type": "IriTemplateMapping",
893-
"variable": "description[exists]",
899+
"variable": "exists[description]",
894900
"property": "description",
895901
"required": false
896902
},
897903
{
898904
"@type": "IriTemplateMapping",
899-
"variable": "relatedDummy.name[exists]",
905+
"variable": "exists[relatedDummy.name]",
900906
"property": "relatedDummy.name",
901907
"required": false
902908
},
903909
{
904910
"@type": "IriTemplateMapping",
905-
"variable": "relatedDummy[exists]",
911+
"variable": "exists[relatedDummy]",
906912
"property": "relatedDummy",
907913
"required": false
908914
},
909915
{
910916
"@type": "IriTemplateMapping",
911-
"variable": "dummyBoolean[exists]",
917+
"variable": "exists[dummyBoolean]",
912918
"property": "dummyBoolean",
913919
"required": false
914920
},

features/doctrine/exists_filter.feature

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Feature: Exists filter on collections
66
@createSchema
77
Scenario: Get collection where exists does not exist
88
Given there are 15 dummy objects with dummyBoolean true
9-
When I send a "GET" request to "/dummies?dummyBoolean[exists]=0"
9+
When I send a "GET" request to "/dummies?exists[dummyBoolean]=0"
1010
Then the response status code should be 200
1111
And the response should be in JSON
1212
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
@@ -26,7 +26,7 @@ Feature: Exists filter on collections
2626
"hydra:view": {
2727
"type": "object",
2828
"properties": {
29-
"@id": {"pattern": "^/dummies\\?dummyBoolean%5Bexists%5D=0$"},
29+
"@id": {"pattern": "^/dummies\\?exists%5BdummyBoolean%5D=0$"},
3030
"@type": {"pattern": "^hydra:PartialCollectionView$"}
3131
}
3232
}
@@ -35,7 +35,7 @@ Feature: Exists filter on collections
3535
"""
3636

3737
Scenario: Get collection where exists does exist
38-
When I send a "GET" request to "/dummies?dummyBoolean[exists]=1"
38+
When I send a "GET" request to "/dummies?exists[dummyBoolean]=1"
3939
Then the response status code should be 200
4040
And the response should be in JSON
4141
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
@@ -55,7 +55,7 @@ Feature: Exists filter on collections
5555
"hydra:view": {
5656
"type": "object",
5757
"properties": {
58-
"@id": {"pattern": "^/dummies\\?dummyBoolean%5Bexists%5D=1&page=1$"},
58+
"@id": {"pattern": "^/dummies\\?exists%5BdummyBoolean%5D=1&page=1$"},
5959
"@type": {"pattern": "^hydra:PartialCollectionView$"}
6060
}
6161
}

features/graphql/filters.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Feature: Collections filtering
3030
When I send the following GraphQL request:
3131
"""
3232
{
33-
dummies(relatedDummy: {exists: true}) {
33+
dummies(exists: {relatedDummy: true}) {
3434
edges {
3535
node {
3636
id

features/main/crud.feature

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ Feature: Create-Retrieve-Update-Delete
137137
"hydra:totalItems": 1,
138138
"hydra:search": {
139139
"@type": "hydra:IriTemplate",
140-
"hydra:template": "/dummies{?dummyBoolean,relatedDummy.embeddedDummy.dummyBoolean,dummyDate[before],dummyDate[strictly_before],dummyDate[after],dummyDate[strictly_after],relatedDummy.dummyDate[before],relatedDummy.dummyDate[strictly_before],relatedDummy.dummyDate[after],relatedDummy.dummyDate[strictly_after],description[exists],relatedDummy.name[exists],dummyBoolean[exists],relatedDummy[exists],dummyFloat,dummyFloat[],dummyPrice,dummyPrice[],order[id],order[name],order[description],order[relatedDummy.name],order[relatedDummy.symfony],order[dummyDate],dummyFloat[between],dummyFloat[gt],dummyFloat[gte],dummyFloat[lt],dummyFloat[lte],dummyPrice[between],dummyPrice[gt],dummyPrice[gte],dummyPrice[lt],dummyPrice[lte],id,id[],name,alias,description,relatedDummy.name,relatedDummy.name[],relatedDummies,relatedDummies[],dummy,relatedDummies.name,relatedDummy.thirdLevel.level,relatedDummy.thirdLevel.level[],relatedDummy.thirdLevel.fourthLevel.level,relatedDummy.thirdLevel.fourthLevel.level[],relatedDummy.thirdLevel.badFourthLevel.level,relatedDummy.thirdLevel.badFourthLevel.level[],relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level,relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level[],properties[]}",
140+
"hydra:template": "/dummies{?dummyBoolean,relatedDummy.embeddedDummy.dummyBoolean,dummyDate[before],dummyDate[strictly_before],dummyDate[after],dummyDate[strictly_after],relatedDummy.dummyDate[before],relatedDummy.dummyDate[strictly_before],relatedDummy.dummyDate[after],relatedDummy.dummyDate[strictly_after],exists[alias],exists[description],exists[relatedDummy.name],exists[dummyBoolean],exists[relatedDummy],dummyFloat,dummyFloat[],dummyPrice,dummyPrice[],order[id],order[name],order[description],order[relatedDummy.name],order[relatedDummy.symfony],order[dummyDate],dummyFloat[between],dummyFloat[gt],dummyFloat[gte],dummyFloat[lt],dummyFloat[lte],dummyPrice[between],dummyPrice[gt],dummyPrice[gte],dummyPrice[lt],dummyPrice[lte],id,id[],name,alias,description,relatedDummy.name,relatedDummy.name[],relatedDummies,relatedDummies[],dummy,relatedDummies.name,relatedDummy.thirdLevel.level,relatedDummy.thirdLevel.level[],relatedDummy.thirdLevel.fourthLevel.level,relatedDummy.thirdLevel.fourthLevel.level[],relatedDummy.thirdLevel.badFourthLevel.level,relatedDummy.thirdLevel.badFourthLevel.level[],relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level,relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level[],properties[]}",
141141
"hydra:variableRepresentation": "BasicRepresentation",
142142
"hydra:mapping": [
143143
{
@@ -202,25 +202,31 @@ Feature: Create-Retrieve-Update-Delete
202202
},
203203
{
204204
"@type": "IriTemplateMapping",
205-
"variable": "description[exists]",
205+
"variable": "exists[alias]",
206+
"property": "alias",
207+
"required": false
208+
},
209+
{
210+
"@type": "IriTemplateMapping",
211+
"variable": "exists[description]",
206212
"property": "description",
207213
"required": false
208214
},
209215
{
210216
"@type": "IriTemplateMapping",
211-
"variable": "relatedDummy.name[exists]",
217+
"variable": "exists[relatedDummy.name]",
212218
"property": "relatedDummy.name",
213219
"required": false
214220
},
215221
{
216222
"@type": "IriTemplateMapping",
217-
"variable": "dummyBoolean[exists]",
223+
"variable": "exists[dummyBoolean]",
218224
"property": "dummyBoolean",
219225
"required": false
220226
},
221227
{
222228
"@type": "IriTemplateMapping",
223-
"variable": "relatedDummy[exists]",
229+
"variable": "exists[relatedDummy]",
224230
"property": "relatedDummy",
225231
"required": false
226232
},

src/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ trait ExistsFilterTrait
2727
{
2828
use PropertyHelperTrait;
2929

30+
/**
31+
* @var string Keyword used to retrieve the value
32+
*/
33+
private $existsParameterName;
34+
3035
/**
3136
* {@inheritdoc}
3237
*/
@@ -44,7 +49,7 @@ public function getDescription(string $resourceClass): array
4449
continue;
4550
}
4651

47-
$description[sprintf('%s[%s]', $property, self::QUERY_PARAMETER_KEY)] = [
52+
$description[sprintf('%s[%s]', $this->existsParameterName, $property)] = [
4853
'property' => $property,
4954
'type' => 'bool',
5055
'required' => false,
@@ -65,16 +70,24 @@ abstract protected function getLogger(): LoggerInterface;
6570

6671
private function normalizeValue($value, string $property): ?bool
6772
{
68-
if (\in_array($value[self::QUERY_PARAMETER_KEY], [true, 'true', '1', '', null], true)) {
73+
if (\is_array($value) && isset($value[self::QUERY_PARAMETER_KEY])) {
74+
@trigger_error(
75+
sprintf('The ExistsFilter syntax "%s[exists]=true/false" is deprecated since 2.5. Use the syntax "%s[%s]=true/false" instead.', $property, $this->existsParameterName, $property),
76+
E_USER_DEPRECATED
77+
);
78+
$value = $value[self::QUERY_PARAMETER_KEY];
79+
}
80+
81+
if (\in_array($value, [true, 'true', '1', '', null], true)) {
6982
return true;
7083
}
7184

72-
if (\in_array($value[self::QUERY_PARAMETER_KEY], [false, 'false', '0'], true)) {
85+
if (\in_array($value, [false, 'false', '0'], true)) {
7386
return false;
7487
}
7588

7689
$this->getLogger()->notice('Invalid filter ignored', [
77-
'exception' => new InvalidArgumentException(sprintf('Invalid value for "%s[%s]", expected one of ( "%s" )', $property, self::QUERY_PARAMETER_KEY, implode('" | "', [
90+
'exception' => new InvalidArgumentException(sprintf('Invalid value for "%s[%s]", expected one of ( "%s" )', $this->existsParameterName, $property, implode('" | "', [
7891
'true',
7992
'false',
8093
'1',

src/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515

1616
use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\ExistsFilterInterface;
1717
use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\ExistsFilterTrait;
18+
use Doctrine\Common\Persistence\ManagerRegistry;
1819
use Doctrine\ODM\MongoDB\Aggregation\Builder;
1920
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
21+
use Psr\Log\LoggerInterface;
2022

2123
/**
2224
* Filters the collection by whether a property value exists or not.
@@ -25,7 +27,7 @@
2527
* the value is not one of ( "true" | "false" | "1" | "0" ) the property is ignored.
2628
*
2729
* A query parameter with key but no value is treated as `true`, e.g.:
28-
* Request: GET /products?brand[exists]
30+
* Request: GET /products?exists[brand]
2931
* Interpretation: filter products which have a brand
3032
*
3133
* @experimental
@@ -37,13 +39,37 @@ final class ExistsFilter extends AbstractFilter implements ExistsFilterInterface
3739
{
3840
use ExistsFilterTrait;
3941

42+
public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $logger = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, array $properties = null)
43+
{
44+
parent::__construct($managerRegistry, $logger, $properties);
45+
46+
$this->existsParameterName = $existsParameterName;
47+
}
48+
49+
/**
50+
* {@inheritdoc}
51+
*/
52+
public function apply(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = [])
53+
{
54+
if (!\is_array($context['filters'][$this->existsParameterName] ?? null)) {
55+
$context['exists_deprecated_syntax'] = true;
56+
parent::apply($aggregationBuilder, $resourceClass, $operationName, $context);
57+
58+
return;
59+
}
60+
61+
foreach ($context['filters'][$this->existsParameterName] as $property => $value) {
62+
$this->filterProperty($property, $value, $aggregationBuilder, $resourceClass, $operationName, $context);
63+
}
64+
}
65+
4066
/**
4167
* {@inheritdoc}
4268
*/
4369
protected function filterProperty(string $property, $value, Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = [])
4470
{
4571
if (
46-
!isset($value[self::QUERY_PARAMETER_KEY]) ||
72+
(($context['exists_deprecated_syntax'] ?? false) && !isset($value[self::QUERY_PARAMETER_KEY])) ||
4773
!$this->isPropertyEnabled($property, $resourceClass) ||
4874
!$this->isPropertyMapped($property, $resourceClass, true) ||
4975
!$this->isNullableField($property, $resourceClass)

0 commit comments

Comments
 (0)