Skip to content

Commit

Permalink
Update the ExistsFilter behavior (#2243)
Browse files Browse the repository at this point in the history
  • Loading branch information
epourail authored and alanpoulain committed Mar 6, 2019
1 parent b3bd652 commit 19c08e0
Show file tree
Hide file tree
Showing 18 changed files with 508 additions and 109 deletions.
26 changes: 16 additions & 10 deletions features/doctrine/date_filter.feature
Expand Up @@ -404,7 +404,7 @@ Feature: Date filter on collections
},
"hydra:search": {
"@type": "hydra:IriTemplate",
"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[]}",
"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[]}",
"hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [
{
Expand Down Expand Up @@ -469,25 +469,31 @@ Feature: Date filter on collections
},
{
"@type": "IriTemplateMapping",
"variable": "description[exists]",
"variable": "exists[alias]",
"property": "alias",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "exists[description]",
"property": "description",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "relatedDummy.name[exists]",
"variable": "exists[relatedDummy.name]",
"property": "relatedDummy.name",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "dummyBoolean[exists]",
"variable": "exists[dummyBoolean]",
"property": "dummyBoolean",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "relatedDummy[exists]",
"variable": "exists[relatedDummy]",
"property": "relatedDummy",
"required": false
},
Expand Down Expand Up @@ -831,7 +837,7 @@ Feature: Date filter on collections
},
"hydra:search": {
"@type": "hydra:IriTemplate",
"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}",
"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}",
"hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [
{
Expand Down Expand Up @@ -890,25 +896,25 @@ Feature: Date filter on collections
},
{
"@type": "IriTemplateMapping",
"variable": "description[exists]",
"variable": "exists[description]",
"property": "description",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "relatedDummy.name[exists]",
"variable": "exists[relatedDummy.name]",
"property": "relatedDummy.name",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "relatedDummy[exists]",
"variable": "exists[relatedDummy]",
"property": "relatedDummy",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "dummyBoolean[exists]",
"variable": "exists[dummyBoolean]",
"property": "dummyBoolean",
"required": false
},
Expand Down
8 changes: 4 additions & 4 deletions features/doctrine/exists_filter.feature
Expand Up @@ -6,7 +6,7 @@ Feature: Exists filter on collections
@createSchema
Scenario: Get collection where exists does not exist
Given there are 15 dummy objects with dummyBoolean true
When I send a "GET" request to "/dummies?dummyBoolean[exists]=0"
When I send a "GET" request to "/dummies?exists[dummyBoolean]=0"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
Expand All @@ -26,7 +26,7 @@ Feature: Exists filter on collections
"hydra:view": {
"type": "object",
"properties": {
"@id": {"pattern": "^/dummies\\?dummyBoolean%5Bexists%5D=0$"},
"@id": {"pattern": "^/dummies\\?exists%5BdummyBoolean%5D=0$"},
"@type": {"pattern": "^hydra:PartialCollectionView$"}
}
}
Expand All @@ -35,7 +35,7 @@ Feature: Exists filter on collections
"""

Scenario: Get collection where exists does exist
When I send a "GET" request to "/dummies?dummyBoolean[exists]=1"
When I send a "GET" request to "/dummies?exists[dummyBoolean]=1"
Then the response status code should be 200
And the response should be in JSON
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
Expand All @@ -55,7 +55,7 @@ Feature: Exists filter on collections
"hydra:view": {
"type": "object",
"properties": {
"@id": {"pattern": "^/dummies\\?dummyBoolean%5Bexists%5D=1&page=1$"},
"@id": {"pattern": "^/dummies\\?exists%5BdummyBoolean%5D=1&page=1$"},
"@type": {"pattern": "^hydra:PartialCollectionView$"}
}
}
Expand Down
2 changes: 1 addition & 1 deletion features/graphql/filters.feature
Expand Up @@ -30,7 +30,7 @@ Feature: Collections filtering
When I send the following GraphQL request:
"""
{
dummies(relatedDummy: {exists: true}) {
dummies(exists: {relatedDummy: true}) {
edges {
node {
id
Expand Down
16 changes: 11 additions & 5 deletions features/main/crud.feature
Expand Up @@ -137,7 +137,7 @@ Feature: Create-Retrieve-Update-Delete
"hydra:totalItems": 1,
"hydra:search": {
"@type": "hydra:IriTemplate",
"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[]}",
"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[]}",
"hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [
{
Expand Down Expand Up @@ -202,25 +202,31 @@ Feature: Create-Retrieve-Update-Delete
},
{
"@type": "IriTemplateMapping",
"variable": "description[exists]",
"variable": "exists[alias]",
"property": "alias",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "exists[description]",
"property": "description",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "relatedDummy.name[exists]",
"variable": "exists[relatedDummy.name]",
"property": "relatedDummy.name",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "dummyBoolean[exists]",
"variable": "exists[dummyBoolean]",
"property": "dummyBoolean",
"required": false
},
{
"@type": "IriTemplateMapping",
"variable": "relatedDummy[exists]",
"variable": "exists[relatedDummy]",
"property": "relatedDummy",
"required": false
},
Expand Down
21 changes: 17 additions & 4 deletions src/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php
Expand Up @@ -27,6 +27,11 @@ trait ExistsFilterTrait
{
use PropertyHelperTrait;

/**
* @var string Keyword used to retrieve the value
*/
private $existsParameterName;

/**
* {@inheritdoc}
*/
Expand All @@ -44,7 +49,7 @@ public function getDescription(string $resourceClass): array
continue;
}

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

private function normalizeValue($value, string $property): ?bool
{
if (\in_array($value[self::QUERY_PARAMETER_KEY], [true, 'true', '1', '', null], true)) {
if (\is_array($value) && isset($value[self::QUERY_PARAMETER_KEY])) {
@trigger_error(
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),
E_USER_DEPRECATED
);
$value = $value[self::QUERY_PARAMETER_KEY];
}

if (\in_array($value, [true, 'true', '1', '', null], true)) {
return true;
}

if (\in_array($value[self::QUERY_PARAMETER_KEY], [false, 'false', '0'], true)) {
if (\in_array($value, [false, 'false', '0'], true)) {
return false;
}

$this->getLogger()->notice('Invalid filter ignored', [
'exception' => new InvalidArgumentException(sprintf('Invalid value for "%s[%s]", expected one of ( "%s" )', $property, self::QUERY_PARAMETER_KEY, implode('" | "', [
'exception' => new InvalidArgumentException(sprintf('Invalid value for "%s[%s]", expected one of ( "%s" )', $this->existsParameterName, $property, implode('" | "', [
'true',
'false',
'1',
Expand Down
30 changes: 28 additions & 2 deletions src/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php
Expand Up @@ -15,8 +15,10 @@

use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\ExistsFilterInterface;
use ApiPlatform\Core\Bridge\Doctrine\Common\Filter\ExistsFilterTrait;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Psr\Log\LoggerInterface;

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

public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $logger = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, array $properties = null)
{
parent::__construct($managerRegistry, $logger, $properties);

$this->existsParameterName = $existsParameterName;
}

/**
* {@inheritdoc}
*/
public function apply(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = [])
{
if (!\is_array($context['filters'][$this->existsParameterName] ?? null)) {
$context['exists_deprecated_syntax'] = true;
parent::apply($aggregationBuilder, $resourceClass, $operationName, $context);

return;
}

foreach ($context['filters'][$this->existsParameterName] as $property => $value) {
$this->filterProperty($property, $value, $aggregationBuilder, $resourceClass, $operationName, $context);
}
}

/**
* {@inheritdoc}
*/
protected function filterProperty(string $property, $value, Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = [])
{
if (
!isset($value[self::QUERY_PARAMETER_KEY]) ||
(($context['exists_deprecated_syntax'] ?? false) && !isset($value[self::QUERY_PARAMETER_KEY])) ||
!$this->isPropertyEnabled($property, $resourceClass) ||
!$this->isPropertyMapped($property, $resourceClass, true) ||
!$this->isNullableField($property, $resourceClass)
Expand Down

0 comments on commit 19c08e0

Please sign in to comment.