diff --git a/features/bootstrap/DoctrineContext.php b/features/bootstrap/DoctrineContext.php
index 46e7cb10fd2..a86ef14d69b 100644
--- a/features/bootstrap/DoctrineContext.php
+++ b/features/bootstrap/DoctrineContext.php
@@ -17,6 +17,10 @@
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\CompositeLabel as CompositeLabelDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\CompositePrimitiveItem as CompositePrimitiveItemDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\CompositeRelation as CompositeRelationDocument;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\ConvertedBoolean as ConvertedBoolDocument;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\ConvertedDate as ConvertedDateDocument;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\ConvertedInteger as ConvertedIntegerDocument;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\ConvertedString as ConvertedStringDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\Customer as CustomerDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\Dummy as DummyDocument;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\DummyAggregateOffer as DummyAggregateOfferDocument;
@@ -62,6 +66,10 @@
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositePrimitiveItem;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Container;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ConvertedBoolean;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ConvertedDate;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ConvertedInteger;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ConvertedString;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Customer;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyAggregateOffer;
@@ -164,6 +172,7 @@ public function thereAreDummyObjects(int $nb)
$dummy->setAlias('Alias #'.($nb - $i));
$dummy->setDummy('SomeDummyTest'.$i);
$dummy->setDescription($descriptions[($i - 1) % 2]);
+ $dummy->nameConverted = 'Converted '.$i;
$this->manager->persist($dummy);
}
@@ -257,6 +266,7 @@ public function thereAreDummyPropertyObjects(int $nb)
foreach (['foo', 'bar', 'baz'] as $property) {
$dummyProperty->{$property} = $dummyGroup->{$property} = ucfirst($property).' #'.$i;
}
+ $dummyProperty->nameConverted = "NameConverted #$i";
$dummyProperty->group = $dummyGroup;
@@ -657,6 +667,66 @@ public function thereAreDummyObjectsWithDummyDateAndEmbeddedDummy(int $nb)
$this->manager->flush();
}
+ /**
+ * @Given there are :nb convertedDate objects
+ */
+ public function thereAreconvertedDateObjectsWith(int $nb)
+ {
+ for ($i = 1; $i <= $nb; ++$i) {
+ $convertedDate = $this->buildConvertedDate();
+ $convertedDate->nameConverted = new \DateTime(sprintf('2015-04-%d', $i), new \DateTimeZone('UTC'));
+
+ $this->manager->persist($convertedDate);
+ }
+
+ $this->manager->flush();
+ }
+
+ /**
+ * @Given there are :nb convertedString objects
+ */
+ public function thereAreconvertedStringObjectsWith(int $nb)
+ {
+ for ($i = 1; $i <= $nb; ++$i) {
+ $convertedString = $this->buildConvertedString();
+ $convertedString->nameConverted = ($i % 2) ? "name#$i" : null;
+
+ $this->manager->persist($convertedString);
+ }
+
+ $this->manager->flush();
+ }
+
+ /**
+ * @Given there are :nb convertedBoolean objects
+ */
+ public function thereAreconvertedBooleanObjectsWith(int $nb)
+ {
+ for ($i = 1; $i <= $nb; ++$i) {
+ $convertedBoolean = $this->buildConvertedBoolean();
+ $convertedBoolean->nameConverted = (bool) ($i % 2);
+
+ $this->manager->persist($convertedBoolean);
+ }
+
+ $this->manager->flush();
+ }
+
+ /**
+ * @Given there are :nb convertedInteger objects
+ */
+ public function thereAreconvertedIntegerObjectsWith(int $nb)
+ {
+ for ($i = 1; $i <= $nb; ++$i) {
+ $convertedInteger = $this->buildConvertedInteger();
+ $convertedInteger->nameConverted = $i;
+
+ $this->manager->persist($convertedInteger);
+ }
+
+ $this->manager->flush();
+ }
+
/**
* @Given there are :nb dummy objects with dummyPrice
*/
@@ -1264,6 +1334,35 @@ public function thereAreNbDummyDtoCustom($nb)
$this->manager->clear();
}
+ /**
+ * @Given there is a order with same customer and receiver
+ */
+ public function testEagerLoadingNotDuplicateRelation()
+ {
+ $customer = $this->isOrm() ? new Customer() : new CustomerDocument();
+ $customer->name = 'customer_name';
+
+ $address1 = $this->isOrm() ? new Address() : new AddressDocument();
+ $address1->name = 'foo';
+ $address2 = $this->isOrm() ? new Address() : new AddressDocument();
+ $address2->name = 'bar';
+
+ $order = $this->isOrm() ? new Order() : new OrderDocument();
+ $order->recipient = $customer;
+ $order->customer = $customer;
+
+ $customer->addresses->add($address1);
+ $customer->addresses->add($address2);
+
+ $this->manager->persist($address1);
+ $this->manager->persist($address2);
+ $this->manager->persist($customer);
+ $this->manager->persist($order);
+
+ $this->manager->flush();
+ $this->manager->clear();
+ }
+
private function isOrm(): bool
{
return null !== $this->schemaTool;
@@ -1579,31 +1678,34 @@ private function buildThirdLevel()
}
/**
- * @Given there is a order with same customer and receiver
+ * @return ConvertedDate|ConvertedDateDocument
*/
- public function testEagerLoadingNotDuplicateRelation()
+ private function buildConvertedDate()
{
- $customer = $this->isOrm() ? new Customer() : new CustomerDocument();
- $customer->name = 'customer_name';
-
- $address1 = $this->isOrm() ? new Address() : new AddressDocument();
- $address1->name = 'foo';
- $address2 = $this->isOrm() ? new Address() : new AddressDocument();
- $address2->name = 'bar';
-
- $order = $this->isOrm() ? new Order() : new OrderDocument();
- $order->recipient = $customer;
- $order->customer = $customer;
+ return $this->isOrm() ? new ConvertedDate() : new ConvertedDateDocument();
+ }
- $customer->addresses->add($address1);
- $customer->addresses->add($address2);
+ /**
+ * @return ConvertedBoolean|ConvertedBoolDocument
+ */
+ private function buildConvertedBoolean()
+ {
+ return $this->isOrm() ? new ConvertedBoolean() : new ConvertedBoolDocument();
+ }
- $this->manager->persist($address1);
- $this->manager->persist($address2);
- $this->manager->persist($customer);
- $this->manager->persist($order);
+ /**
+ * @return ConvertedInteger|ConvertedIntegerDocument
+ */
+ private function buildConvertedInteger()
+ {
+ return $this->isOrm() ? new ConvertedInteger() : new ConvertedIntegerDocument();
+ }
- $this->manager->flush();
- $this->manager->clear();
+ /**
+ * @return ConvertedString|ConvertedStringDocument
+ */
+ private function buildConvertedString()
+ {
+ return $this->isOrm() ? new ConvertedString() : new ConvertedStringDocument();
}
}
diff --git a/features/doctrine/boolean_filter.feature b/features/doctrine/boolean_filter.feature
index 5ee947172b0..27b09201d31 100644
--- a/features/doctrine/boolean_filter.feature
+++ b/features/doctrine/boolean_filter.feature
@@ -448,3 +448,118 @@ Feature: Boolean filter on collections
}
"""
And the JSON node "hydra:totalItems" should be equal to 25
+
+ @createSchema
+ Scenario: Get collection filtered using a name converter
+ Given there are 5 convertedBoolean objects
+ When I send a "GET" request to "/converted_booleans?name_converted=false"
+ 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"
+ And the JSON should be equal to:
+ """
+ {
+ "@context": "/contexts/ConvertedBoolean",
+ "@id": "/converted_booleans",
+ "@type": "hydra:Collection",
+ "hydra:member": [
+ {
+ "@id": "/converted_booleans/2",
+ "@type": "ConvertedBoolean",
+ "name_converted": false,
+ "id": 2
+ },
+ {
+ "@id": "/converted_booleans/4",
+ "@type": "ConvertedBoolean",
+ "name_converted": false,
+ "id": 4
+ }
+ ],
+ "hydra:totalItems": 2,
+ "hydra:view": {
+ "@id": "/converted_booleans?name_converted=false",
+ "@type": "hydra:PartialCollectionView"
+ },
+ "hydra:search": {
+ "@type": "hydra:IriTemplate",
+ "hydra:template": "/converted_booleans{?name_converted}",
+ "hydra:variableRepresentation": "BasicRepresentation",
+ "hydra:mapping": [
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted",
+ "property": "name_converted",
+ "required": false
+ }
+ ]
+ }
+ }
+ """
+ And the JSON should be valid according to this schema:
+ """
+ {
+ "type": "object",
+ "properties": {
+ "@context": {"pattern": "^/contexts/ConvertedBoolean"},
+ "@id": {"pattern": "^/converted_booleans"},
+ "@type": {"pattern": "^hydra:Collection$"},
+ "hydra:member": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_booleans/(2|4)$"},
+ "@type": {"pattern": "^ConvertedBoolean"},
+ "name_converted": {"type": "boolean"},
+ "id": {"type": "integer", "minimum":2, "maximum": 4}
+ },
+ "required": ["@id", "@type", "name_converted", "id"],
+ "additionalProperties": false
+ },
+ "minItems": 2,
+ "maxItems": 2,
+ "uniqueItems": true
+ },
+ "hydra:totalItems": {"type": "integer", "minimum": 2, "maximum": 2},
+ "hydra:view": {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_booleans\\?name_converted=false"},
+ "@type": {"pattern": "^hydra:PartialCollectionView$"}
+ },
+ "required": ["@id", "@type"],
+ "additionalProperties": false
+ },
+ "hydra:search": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^hydra:IriTemplate$"},
+ "hydra:template": {"pattern": "^/converted_booleans\\{\\?name_converted\\}$"},
+ "hydra:variableRepresentation": {"pattern": "^BasicRepresentation$"},
+ "hydra:mapping": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^IriTemplateMapping$"},
+ "variable": {"pattern": "^name_converted$"},
+ "property": {"pattern": "^name_converted$"},
+ "required": {"type": "boolean"}
+ },
+ "required": ["@type", "variable", "property", "required"],
+ "additionalProperties": false
+ },
+ "minItems": 1,
+ "maxItems": 1,
+ "uniqueItems": true
+ }
+ },
+ "additionalProperties": false,
+ "required": ["@type", "hydra:template", "hydra:variableRepresentation", "hydra:mapping"]
+ },
+ "additionalProperties": false,
+ "required": ["@context", "@id", "@type", "hydra:member", "hydra:totalItems", "hydra:view", "hydra:search"]
+ }
+ }
+ """
diff --git a/features/doctrine/date_filter.feature b/features/doctrine/date_filter.feature
index f92b240242b..a12c0a5478e 100644
--- a/features/doctrine/date_filter.feature
+++ b/features/doctrine/date_filter.feature
@@ -413,7 +413,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],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: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[],name_converted,properties[]}",
"hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [
{
@@ -740,6 +740,12 @@ Feature: Date filter on collections
"property": "relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level",
"required": false
},
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted",
+ "property": "name_converted",
+ "required": false
+ },
{
"@type": "IriTemplateMapping",
"variable": "properties[]",
@@ -1102,3 +1108,136 @@ Feature: Date filter on collections
}
}
"""
+
+ @createSchema
+ Scenario: Get collection filtered using a name converter
+ Given there are 30 convertedDate objects
+ When I send a "GET" request to "/converted_dates?name_converted[strictly_after]=2015-04-28"
+ 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"
+ And the JSON should be equal to:
+ """
+ {
+ "@context": "/contexts/ConvertedDate",
+ "@id": "/converted_dates",
+ "@type": "hydra:Collection",
+ "hydra:member": [
+ {
+ "@id": "/converted_dates/29",
+ "@type": "ConvertedDate",
+ "name_converted": "2015-04-29T00:00:00+00:00",
+ "id": 29
+ },
+ {
+ "@id": "/converted_dates/30",
+ "@type": "ConvertedDate",
+ "name_converted": "2015-04-30T00:00:00+00:00",
+ "id": 30
+ }
+ ],
+ "hydra:totalItems": 2,
+ "hydra:view": {
+ "@id": "/converted_dates?name_converted%5Bstrictly_after%5D=2015-04-28",
+ "@type": "hydra:PartialCollectionView"
+ },
+ "hydra:search": {
+ "@type": "hydra:IriTemplate",
+ "hydra:template": "/converted_dates{?name_converted[before],name_converted[strictly_before],name_converted[after],name_converted[strictly_after]}",
+ "hydra:variableRepresentation": "BasicRepresentation",
+ "hydra:mapping": [
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[before]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[strictly_before]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[after]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[strictly_after]",
+ "property": "name_converted",
+ "required": false
+ }
+ ]
+ }
+ }
+ """
+ And the JSON should be valid according to this schema:
+ """
+ {
+ "type": "object",
+ "properties": {
+ "@context": {"pattern": "^/contexts/ConvertedDate"},
+ "@id": {"pattern": "^/converted_dates"},
+ "@type": {"pattern": "^hydra:Collection$"},
+ "hydra:member": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_dates/(29|30)$"},
+ "@type": {"pattern": "^ConvertedDate"},
+ "name_converted": {"type": "string"},
+ "id": {"type": "integer", "minimum":29, "maximum": 30}
+ },
+ "required": ["@id", "@type", "name_converted", "id"],
+ "additionalProperties": false
+ },
+ "minItems": 2,
+ "maxItems": 2,
+ "uniqueItems": true
+ },
+ "hydra:totalItems": {"type": "integer", "minimum": 2, "maximum": 2},
+ "hydra:view": {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_dates\\?name_converted%5Bstrictly_after%5D=2015\\-04\\-28$"},
+ "@type": {"pattern": "^hydra:PartialCollectionView$"}
+ },
+ "required": ["@id", "@type"],
+ "additionalProperties": false
+ },
+ "hydra:search": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^hydra:IriTemplate$"},
+ "hydra:template": {"pattern": "^/converted_dates\\{\\?.*name_converted\\[before\\],name_converted\\[strictly_before\\],name_converted\\[after\\],name_converted\\[strictly_after\\].*\\}$"},
+ "hydra:variableRepresentation": {"pattern": "^BasicRepresentation$"},
+ "hydra:mapping": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^IriTemplateMapping$"},
+ "variable": {"pattern": "^name_converted(\\[(strictly_)?(before|after)\\])$"},
+ "property": {"pattern": "^name_converted$"},
+ "required": {"type": "boolean"}
+ },
+ "required": ["@type", "variable", "property", "required"],
+ "additionalProperties": false
+ },
+ "minItems": 4,
+ "maxItems": 4,
+ "uniqueItems": true
+ }
+ },
+ "additionalProperties": false,
+ "required": ["@type", "hydra:template", "hydra:variableRepresentation", "hydra:mapping"]
+ },
+ "additionalProperties": false,
+ "required": ["@context", "@id", "@type", "hydra:member", "hydra:totalItems", "hydra:view", "hydra:search"]
+ }
+ }
+ """
diff --git a/features/doctrine/exists_filter.feature b/features/doctrine/exists_filter.feature
index d6df544d5fd..5616dfda56f 100644
--- a/features/doctrine/exists_filter.feature
+++ b/features/doctrine/exists_filter.feature
@@ -62,3 +62,118 @@ Feature: Exists filter on collections
}
}
"""
+
+ @createSchema
+ Scenario: Get collection filtered using a name converter
+ Given there are 4 convertedString objects
+ When I send a "GET" request to "/converted_strings?exists[name_converted]=true"
+ 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"
+ And the JSON should be equal to:
+ """
+ {
+ "@context": "/contexts/ConvertedString",
+ "@id": "/converted_strings",
+ "@type": "hydra:Collection",
+ "hydra:member": [
+ {
+ "@id": "/converted_strings/1",
+ "@type": "ConvertedString",
+ "name_converted": "name#1",
+ "id": 1
+ },
+ {
+ "@id": "/converted_strings/3",
+ "@type": "ConvertedString",
+ "name_converted": "name#3",
+ "id": 3
+ }
+ ],
+ "hydra:totalItems": 2,
+ "hydra:view": {
+ "@id": "/converted_strings?exists%5Bname_converted%5D=true",
+ "@type": "hydra:PartialCollectionView"
+ },
+ "hydra:search": {
+ "@type": "hydra:IriTemplate",
+ "hydra:template": "/converted_strings{?exists[name_converted]}",
+ "hydra:variableRepresentation": "BasicRepresentation",
+ "hydra:mapping": [
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "exists[name_converted]",
+ "property": "name_converted",
+ "required": false
+ }
+ ]
+ }
+ }
+ """
+ And the JSON should be valid according to this schema:
+ """
+ {
+ "type": "object",
+ "properties": {
+ "@context": {"pattern": "^/contexts/ConvertedString"},
+ "@id": {"pattern": "^/converted_strings"},
+ "@type": {"pattern": "^hydra:Collection$"},
+ "hydra:member": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_strings/(1|3)$"},
+ "@type": {"pattern": "^ConvertedString"},
+ "name_converted": {"pattern": "^name#(1|3)$"},
+ "id": {"type": "integer", "minimum":1, "maximum": 3}
+ },
+ "required": ["@id", "@type", "name_converted", "id"],
+ "additionalProperties": false
+ },
+ "minItems": 2,
+ "maxItems": 2,
+ "uniqueItems": true
+ },
+ "hydra:totalItems": {"type": "integer", "minimum": 2, "maximum": 2},
+ "hydra:view": {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_strings\\?exists%5Bname_converted%5D=true"},
+ "@type": {"pattern": "^hydra:PartialCollectionView$"}
+ },
+ "required": ["@id", "@type"],
+ "additionalProperties": false
+ },
+ "hydra:search": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^hydra:IriTemplate$"},
+ "hydra:template": {"pattern": "^/converted_strings\\{\\?exists\\[name_converted\\]\\}$"},
+ "hydra:variableRepresentation": {"pattern": "^BasicRepresentation$"},
+ "hydra:mapping": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^IriTemplateMapping$"},
+ "variable": {"pattern": "^exists\\[name_converted\\]$"},
+ "property": {"pattern": "^name_converted$"},
+ "required": {"type": "boolean"}
+ },
+ "required": ["@type", "variable", "property", "required"],
+ "additionalProperties": false
+ },
+ "minItems": 1,
+ "maxItems": 1,
+ "uniqueItems": true
+ }
+ },
+ "additionalProperties": false,
+ "required": ["@type", "hydra:template", "hydra:variableRepresentation", "hydra:mapping"]
+ },
+ "additionalProperties": false,
+ "required": ["@context", "@id", "@type", "hydra:member", "hydra:totalItems", "hydra:view", "hydra:search"]
+ }
+ }
+ """
diff --git a/features/doctrine/numeric_filter.feature b/features/doctrine/numeric_filter.feature
index 7670089a4b2..0b6455daeda 100644
--- a/features/doctrine/numeric_filter.feature
+++ b/features/doctrine/numeric_filter.feature
@@ -136,3 +136,166 @@ Feature: Numeric filter on collections
}
}
"""
+
+ @createSchema
+ Scenario: Get collection filtered using a name converter
+ Given there are 5 convertedInteger objects
+ When I send a "GET" request to "/converted_integers?name_converted[]=2&name_converted[]=3"
+ 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"
+ And the JSON should be equal to:
+ """
+ {
+ "@context": "/contexts/ConvertedInteger",
+ "@id": "/converted_integers",
+ "@type": "hydra:Collection",
+ "hydra:member": [
+ {
+ "@id": "/converted_integers/2",
+ "@type": "ConvertedInteger",
+ "name_converted": 2,
+ "id": 2
+ },
+ {
+ "@id": "/converted_integers/3",
+ "@type": "ConvertedInteger",
+ "name_converted": 3,
+ "id": 3
+ }
+ ],
+ "hydra:totalItems": 2,
+ "hydra:view": {
+ "@id": "/converted_integers?name_converted%5B%5D=2&name_converted%5B%5D=3",
+ "@type": "hydra:PartialCollectionView"
+ },
+ "hydra:search": {
+ "@type": "hydra:IriTemplate",
+ "hydra:template": "/converted_integers{?name_converted,name_converted[],name_converted[between],name_converted[gt],name_converted[gte],name_converted[lt],name_converted[lte],order[name_converted]}",
+ "hydra:variableRepresentation": "BasicRepresentation",
+ "hydra:mapping": [
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[between]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[gt]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[gte]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[lt]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[lte]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "order[name_converted]",
+ "property": "name_converted",
+ "required": false
+ }
+ ]
+ }
+ }
+ """
+ And the JSON should be valid according to this schema:
+ """
+ {
+ "type": "object",
+ "properties": {
+ "@context": {"pattern": "^/contexts/ConvertedInteger$"},
+ "@id": {"pattern": "^/converted_integers$"},
+ "@type": {"pattern": "^hydra:Collection$"},
+ "hydra:member": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_integers/(2|3)$"},
+ "@type": {"pattern": "^ConvertedInteger$"},
+ "name_converted": {"type": "integer"},
+ "id": {"type": "integer", "minimum":2, "maximum": 3}
+ },
+ "required": ["@id", "@type", "name_converted", "id"],
+ "additionalProperties": false
+ },
+ "minItems": 2,
+ "maxItems": 2,
+ "uniqueItems": true
+ },
+ "hydra:totalItems": {"type": "integer", "minimum": 2, "maximum": 2},
+ "hydra:view": {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_integers\\?name_converted%5B%5D=2&name_converted%5B%5D=3$"},
+ "@type": {"pattern": "^hydra:PartialCollectionView$"}
+ },
+ "required": ["@id", "@type"],
+ "additionalProperties": false
+ },
+ "hydra:search": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^hydra:IriTemplate$"},
+ "hydra:template": {"pattern": "^/converted_integers\\{\\?.*name_converted,name_converted\\[\\].*\\}$"},
+ "hydra:variableRepresentation": {"pattern": "^BasicRepresentation$"},
+ "hydra:mapping": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^IriTemplateMapping$"},
+ "variable": {
+ "oneOf": [
+ {"pattern": "^name_converted(\\[(between|gt|gte|lt|lte)?\\])?$"},
+ {"pattern": "^order\\[name_converted\\]$"}
+ ]
+ },
+ "property": {"pattern": "^name_converted$"},
+ "required": {"type": "boolean"}
+ },
+ "required": ["@type", "variable", "property", "required"],
+ "additionalProperties": false
+ },
+ "minItems": 8,
+ "maxItems": 8,
+ "uniqueItems": true
+ }
+ },
+ "additionalProperties": false,
+ "required": ["@type", "hydra:template", "hydra:variableRepresentation", "hydra:mapping"]
+ },
+ "additionalProperties": false,
+ "required": ["@context", "@id", "@type", "hydra:member", "hydra:totalItems", "hydra:view", "hydra:search"]
+ }
+ }
+ """
+
diff --git a/features/doctrine/order_filter.feature b/features/doctrine/order_filter.feature
index f235c65f30e..29bb1c45cd5 100644
--- a/features/doctrine/order_filter.feature
+++ b/features/doctrine/order_filter.feature
@@ -882,3 +882,195 @@ Feature: Order filter on collections
}
}
"""
+
+ @createSchema
+ Scenario: Get collection filtered using a name converter
+ Given there are 3 convertedInteger objects
+ When I send a "GET" request to "/converted_integers?order[name_converted]=desc"
+ 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"
+ And the JSON should be equal to:
+ """
+ {
+ "@context": "/contexts/ConvertedInteger",
+ "@id": "/converted_integers",
+ "@type": "hydra:Collection",
+ "hydra:member": [
+ {
+ "@id": "/converted_integers/3",
+ "@type": "ConvertedInteger",
+ "name_converted": 3,
+ "id": 3
+ },
+ {
+ "@id": "/converted_integers/2",
+ "@type": "ConvertedInteger",
+ "name_converted": 2,
+ "id": 2
+ },
+ {
+ "@id": "/converted_integers/1",
+ "@type": "ConvertedInteger",
+ "name_converted": 1,
+ "id": 1
+ }
+ ],
+ "hydra:totalItems": 3,
+ "hydra:view": {
+ "@id": "/converted_integers?order%5Bname_converted%5D=desc",
+ "@type": "hydra:PartialCollectionView"
+ },
+ "hydra:search": {
+ "@type": "hydra:IriTemplate",
+ "hydra:template": "/converted_integers{?name_converted,name_converted[],name_converted[between],name_converted[gt],name_converted[gte],name_converted[lt],name_converted[lte],order[name_converted]}",
+ "hydra:variableRepresentation": "BasicRepresentation",
+ "hydra:mapping": [
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[between]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[gt]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[gte]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[lt]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[lte]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "order[name_converted]",
+ "property": "name_converted",
+ "required": false
+ }
+ ]
+ }
+ }
+ """
+ And the JSON should be valid according to this schema:
+ """
+ {
+ "type": "object",
+ "properties": {
+ "@context": {"pattern": "^/contexts/ConvertedInteger$"},
+ "@id": {"pattern": "^/converted_integers$"},
+ "@type": {"pattern": "^hydra:Collection$"},
+ "hydra:member": {
+ "type": "array",
+ "items": [
+ {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_integers/3$"},
+ "@type": {"pattern": "^ConvertedInteger$"},
+ "name_converted": {"type": "integer"},
+ "id": {"type": "integer", "minimum":3, "maximum": 3}
+ },
+ "required": ["@id", "@type", "name_converted", "id"],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_integers/2$"},
+ "@type": {"pattern": "^ConvertedInteger$"},
+ "name_converted": {"type": "integer"},
+ "id": {"type": "integer", "minimum":2, "maximum": 2}
+ },
+ "required": ["@id", "@type", "name_converted", "id"],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_integers/1$"},
+ "@type": {"pattern": "^ConvertedInteger$"},
+ "name_converted": {"type": "integer"},
+ "id": {"type": "integer", "minimum":1, "maximum": 1}
+ },
+ "required": ["@id", "@type", "name_converted", "id"],
+ "additionalProperties": false
+ }
+ ],
+ "minItems": 3,
+ "maxItems": 3,
+ "uniqueItems": true
+ },
+ "hydra:totalItems": {"type": "integer", "minimum": 3, "maximum": 3},
+ "hydra:view": {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_integers\\?order%5Bname_converted%5D=desc$"},
+ "@type": {"pattern": "^hydra:PartialCollectionView$"}
+ },
+ "required": ["@id", "@type"],
+ "additionalProperties": false
+ },
+ "hydra:search": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^hydra:IriTemplate$"},
+ "hydra:template": {"pattern": "^/converted_integers\\{\\?.*order\\[name_converted\\].*\\}$"},
+ "hydra:variableRepresentation": {"pattern": "^BasicRepresentation$"},
+ "hydra:mapping": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^IriTemplateMapping$"},
+ "variable": {
+ "oneOf": [
+ {"pattern": "^order\\[name_converted\\]$"},
+ {"pattern": "^name_converted(\\[(between|gt|gte|lt|lte)?\\])?$"}
+ ]
+ },
+ "property": {"pattern": "^name_converted$"},
+ "required": {"type": "boolean"}
+ },
+ "required": ["@type", "variable", "property", "required"],
+ "additionalProperties": false
+ },
+ "minItems": 8,
+ "maxItems": 8,
+ "uniqueItems": true
+ }
+ },
+ "additionalProperties": false,
+ "required": ["@type", "hydra:template", "hydra:variableRepresentation", "hydra:mapping"]
+ },
+ "additionalProperties": false,
+ "required": ["@context", "@id", "@type", "hydra:member", "hydra:totalItems", "hydra:view", "hydra:search"]
+ }
+ }
+ """
diff --git a/features/doctrine/range_filter.feature b/features/doctrine/range_filter.feature
index 4069ffc5855..ae53bf99fb5 100644
--- a/features/doctrine/range_filter.feature
+++ b/features/doctrine/range_filter.feature
@@ -379,3 +379,165 @@ Feature: Range filter on collections
}
}
"""
+
+ @createSchema
+ Scenario: Get collection filtered using a name converter
+ Given there are 5 convertedInteger objects
+ When I send a "GET" request to "/converted_integers?name_converted[lte]=2"
+ 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"
+ And the JSON should be equal to:
+ """
+ {
+ "@context": "/contexts/ConvertedInteger",
+ "@id": "/converted_integers",
+ "@type": "hydra:Collection",
+ "hydra:member": [
+ {
+ "@id": "/converted_integers/1",
+ "@type": "ConvertedInteger",
+ "name_converted": 1,
+ "id": 1
+ },
+ {
+ "@id": "/converted_integers/2",
+ "@type": "ConvertedInteger",
+ "name_converted": 2,
+ "id": 2
+ }
+ ],
+ "hydra:totalItems": 2,
+ "hydra:view": {
+ "@id": "/converted_integers?name_converted%5Blte%5D=2",
+ "@type": "hydra:PartialCollectionView"
+ },
+ "hydra:search": {
+ "@type": "hydra:IriTemplate",
+ "hydra:template": "/converted_integers{?name_converted,name_converted[],name_converted[between],name_converted[gt],name_converted[gte],name_converted[lt],name_converted[lte],order[name_converted]}",
+ "hydra:variableRepresentation": "BasicRepresentation",
+ "hydra:mapping": [
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[between]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[gt]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[gte]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[lt]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted[lte]",
+ "property": "name_converted",
+ "required": false
+ },
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "order[name_converted]",
+ "property": "name_converted",
+ "required": false
+ }
+ ]
+ }
+ }
+ """
+ And the JSON should be valid according to this schema:
+ """
+ {
+ "type": "object",
+ "properties": {
+ "@context": {"pattern": "^/contexts/ConvertedInteger$"},
+ "@id": {"pattern": "^/converted_integers$"},
+ "@type": {"pattern": "^hydra:Collection$"},
+ "hydra:member": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_integers/(1|2)$"},
+ "@type": {"pattern": "^ConvertedInteger$"},
+ "name_converted": {"type": "integer"},
+ "id": {"type": "integer", "minimum":1, "maximum": 2}
+ },
+ "required": ["@id", "@type", "name_converted", "id"],
+ "additionalProperties": false
+ },
+ "minItems": 2,
+ "maxItems": 2,
+ "uniqueItems": true
+ },
+ "hydra:totalItems": {"type": "integer", "minimum": 2, "maximum": 2},
+ "hydra:view": {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/converted_integers\\?name_converted%5Blte%5D=2$"},
+ "@type": {"pattern": "^hydra:PartialCollectionView$"}
+ },
+ "required": ["@id", "@type"],
+ "additionalProperties": false
+ },
+ "hydra:search": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^hydra:IriTemplate$"},
+ "hydra:template": {"pattern": "^/converted_integers\\{\\?.*name_converted\\[between\\],name_converted\\[gt\\],name_converted\\[gte\\],name_converted\\[lt\\],name_converted\\[lte\\].*\\}$"},
+ "hydra:variableRepresentation": {"pattern": "^BasicRepresentation$"},
+ "hydra:mapping": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^IriTemplateMapping$"},
+ "variable": {
+ "oneOf": [
+ {"pattern": "^name_converted(\\[(between|gt|gte|lt|lte)?\\])?$"},
+ {"pattern": "^order\\[name_converted\\]$"}
+ ]
+ },
+ "property": {"pattern": "^name_converted$"},
+ "required": {"type": "boolean"}
+ },
+ "required": ["@type", "variable", "property", "required"],
+ "additionalProperties": false
+ },
+ "minItems": 8,
+ "maxItems": 8,
+ "uniqueItems": true
+ }
+ },
+ "additionalProperties": false,
+ "required": ["@type", "hydra:template", "hydra:variableRepresentation", "hydra:mapping"]
+ },
+ "additionalProperties": false,
+ "required": ["@context", "@id", "@type", "hydra:member", "hydra:totalItems", "hydra:view", "hydra:search"]
+ }
+ }
+ """
diff --git a/features/doctrine/search_filter.feature b/features/doctrine/search_filter.feature
index 9b1bc249503..9e966825956 100644
--- a/features/doctrine/search_filter.feature
+++ b/features/doctrine/search_filter.feature
@@ -728,3 +728,72 @@ Feature: Search filter on collections
}
"""
+ @createSchema
+ Scenario: Search collection on a property using a name converted
+ Given there are 30 dummy objects
+ When I send a "GET" request to "/dummies?name_converted=Converted 3"
+ Then the response status code should be 200
+ And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
+ And the JSON should be valid according to this schema:
+ """
+ {
+ "type": "object",
+ "properties": {
+ "@context": {"pattern": "^/contexts/Dummy$"},
+ "@id": {"pattern": "^/dummies$"},
+ "@type": {"pattern": "^hydra:Collection$"},
+ "hydra:member": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@id": {
+ "oneOf": [
+ {"pattern": "^/dummies/3$"},
+ {"pattern": "^/dummies/30$"}
+ ]
+ },
+ "required": ["@id"]
+ }
+ },
+ "minItems": 2,
+ "maxItems": 2
+ },
+ "hydra:view": {
+ "type": "object",
+ "properties": {
+ "@id": {"pattern": "^/dummies\\?name_converted=Converted%203"},
+ "@type": {"pattern": "^hydra:PartialCollectionView$"}
+ }
+ },
+ "hydra:search": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^hydra:IriTemplate$"},
+ "hydra:template": {"pattern": "^/dummies\\{\\?.*name_converted.*}$"},
+ "hydra:variableRepresentation": {"pattern": "^BasicRepresentation$"},
+ "hydra:mapping": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@type": {"pattern": "^IriTemplateMapping$"},
+ "variable": {"pattern": "^name_converted$"},
+ "property": {"pattern": "^name_converted$"},
+ "required": {"type": "boolean"}
+ },
+ "required": ["@type", "variable", "property", "required"],
+ "additionalProperties": false
+ },
+ "additionalItems": true,
+ "uniqueItems": true
+ }
+ },
+ "additionalProperties": false,
+ "required": ["@type", "hydra:template", "hydra:variableRepresentation", "hydra:mapping"]
+ },
+ "additionalProperties": false,
+ "required": ["@context", "@id", "@type", "hydra:member", "hydra:totalItems", "hydra:view", "hydra:search"]
+ }
+ }
+ """
diff --git a/features/filter/property_filter.feature b/features/filter/property_filter.feature
index 722ced764ae..367d1e1689c 100644
--- a/features/filter/property_filter.feature
+++ b/features/filter/property_filter.feature
@@ -6,10 +6,11 @@ Feature: Set properties to include
@createSchema
Scenario: Test properties filter
Given there are 1 dummy objects with relatedDummy and its thirdLevel
- When I send a "GET" request to "/dummies/1?properties[]=name&properties[]=alias&properties[]=relatedDummy"
+ When I send a "GET" request to "/dummies/1?properties[]=name&properties[]=alias&properties[]=relatedDummy&properties[]=name_converted"
And the JSON node "name" should be equal to "Dummy #1"
And the JSON node "alias" should be equal to "Alias #0"
And the JSON node "relatedDummies" should not exist
+ And the JSON node "name_converted" should exist
Scenario: Test relation embedding
When I send a "GET" request to "/dummies/1?properties[]=name&properties[]=alias&properties[relatedDummy][]=name"
diff --git a/features/hal/collection.feature b/features/hal/collection.feature
index 944fb839072..8507c886621 100644
--- a/features/hal/collection.feature
+++ b/features/hal/collection.feature
@@ -78,7 +78,7 @@ Feature: HAL Collections support
"dummyPrice": null,
"jsonData": [],
"arrayData": [],
- "name_converted": null,
+ "name_converted": "Converted 1",
"id": 1,
"name": "Dummy #1",
"alias": "Alias #9",
@@ -98,7 +98,7 @@ Feature: HAL Collections support
"dummyPrice": null,
"jsonData": [],
"arrayData": [],
- "name_converted": null,
+ "name_converted": "Converted 2",
"id": 2,
"name": "Dummy #2",
"alias": "Alias #8",
@@ -118,7 +118,7 @@ Feature: HAL Collections support
"dummyPrice": null,
"jsonData": [],
"arrayData": [],
- "name_converted": null,
+ "name_converted": "Converted 3",
"id": 3,
"name": "Dummy #3",
"alias": "Alias #7",
@@ -185,7 +185,7 @@ Feature: HAL Collections support
"dummyPrice": null,
"jsonData": [],
"arrayData": [],
- "name_converted": null,
+ "name_converted": "Converted 7",
"id": 7,
"name": "Dummy #7",
"alias": "Alias #3",
@@ -205,7 +205,7 @@ Feature: HAL Collections support
"dummyPrice": null,
"jsonData": [],
"arrayData": [],
- "name_converted": null,
+ "name_converted": "Converted 8",
"id": 8,
"name": "Dummy #8",
"alias": "Alias #2",
@@ -225,7 +225,7 @@ Feature: HAL Collections support
"dummyPrice": null,
"jsonData": [],
"arrayData": [],
- "name_converted": null,
+ "name_converted": "Converted 9",
"id": 9,
"name": "Dummy #9",
"alias": "Alias #1",
@@ -283,7 +283,7 @@ Feature: HAL Collections support
"dummyPrice": null,
"jsonData": [],
"arrayData": [],
- "name_converted": null,
+ "name_converted": "Converted 10",
"id": 10,
"name": "Dummy #10",
"alias": "Alias #0",
@@ -344,7 +344,7 @@ Feature: HAL Collections support
"dummyPrice": null,
"jsonData": [],
"arrayData": [],
- "name_converted": null,
+ "name_converted": "Converted 4",
"id": 4,
"name": "Dummy #4",
"alias": "Alias #6",
@@ -364,7 +364,7 @@ Feature: HAL Collections support
"dummyPrice": null,
"jsonData": [],
"arrayData": [],
- "name_converted": null,
+ "name_converted": "Converted 5",
"id": 5,
"name": "Dummy #5",
"alias": "Alias #5",
@@ -384,7 +384,7 @@ Feature: HAL Collections support
"dummyPrice": null,
"jsonData": [],
"arrayData": [],
- "name_converted": null,
+ "name_converted": "Converted 6",
"id": 6,
"name": "Dummy #6",
"alias": "Alias #4",
@@ -506,7 +506,7 @@ Feature: HAL Collections support
"dummyPrice": null,
"jsonData": [],
"arrayData": [],
- "name_converted": null,
+ "name_converted": "Converted 2",
"id": 2,
"name": "Dummy #2",
"alias": "Alias #8",
@@ -555,7 +555,7 @@ Feature: HAL Collections support
"dummyPrice": null,
"jsonData": [],
"arrayData": [],
- "name_converted": null,
+ "name_converted": "Converted 8",
"id": 8,
"name": "Dummy #8",
"alias": "Alias #2",
@@ -604,7 +604,7 @@ Feature: HAL Collections support
"dummyPrice": null,
"jsonData": [],
"arrayData": [],
- "name_converted": null,
+ "name_converted": "Converted 8",
"id": 8,
"name": "Dummy #8",
"alias": "Alias #2",
diff --git a/features/jsonapi/related-resouces-inclusion.feature b/features/jsonapi/related-resouces-inclusion.feature
index a2864df22eb..2c125f6065e 100644
--- a/features/jsonapi/related-resouces-inclusion.feature
+++ b/features/jsonapi/related-resouces-inclusion.feature
@@ -24,7 +24,8 @@ Feature: JSON API Inclusion of Related Resources
"_id": 1,
"foo": "Foo #1",
"bar": "Bar #1",
- "baz": "Baz #1"
+ "baz": "Baz #1",
+ "name_converted": "NameConverted #1"
},
"relationships": {
"group": {
@@ -65,7 +66,8 @@ Feature: JSON API Inclusion of Related Resources
"_id": 1,
"foo": "Foo #1",
"bar": "Bar #1",
- "baz": "Baz #1"
+ "baz": "Baz #1",
+ "name_converted": "NameConverted #1"
},
"relationships": {
"group": {
@@ -166,7 +168,8 @@ Feature: JSON API Inclusion of Related Resources
"_id": 1,
"foo": "Foo #1",
"bar": "Bar #1",
- "baz": "Baz #1"
+ "baz": "Baz #1",
+ "name_converted": null
},
"relationships": {
"group": {
@@ -245,7 +248,8 @@ Feature: JSON API Inclusion of Related Resources
"_id": 1,
"foo": "Foo #1",
"bar": "Bar #1",
- "baz": "Baz #1"
+ "baz": "Baz #1",
+ "name_converted": null
},
"relationships": {
"group": {
@@ -413,7 +417,8 @@ Feature: JSON API Inclusion of Related Resources
"_id": 1,
"foo": "Foo #1",
"bar": "Bar #1",
- "baz": "Baz #1"
+ "baz": "Baz #1",
+ "name_converted": "NameConverted #1"
},
"relationships": {
"group": {
@@ -431,7 +436,8 @@ Feature: JSON API Inclusion of Related Resources
"_id": 2,
"foo": "Foo #2",
"bar": "Bar #2",
- "baz": "Baz #2"
+ "baz": "Baz #2",
+ "name_converted": "NameConverted #2"
},
"relationships": {
"group": {
@@ -449,7 +455,8 @@ Feature: JSON API Inclusion of Related Resources
"_id": 3,
"foo": "Foo #3",
"bar": "Bar #3",
- "baz": "Baz #3"
+ "baz": "Baz #3",
+ "name_converted": "NameConverted #3"
},
"relationships": {
"group": {
@@ -522,7 +529,8 @@ Feature: JSON API Inclusion of Related Resources
"_id": 1,
"foo": "Foo #1",
"bar": "Bar #1",
- "baz": "Baz #1"
+ "baz": "Baz #1",
+ "name_converted": null
},
"relationships": {
"group": {
@@ -540,7 +548,8 @@ Feature: JSON API Inclusion of Related Resources
"_id": 2,
"foo": "Foo #2",
"bar": "Bar #2",
- "baz": "Baz #2"
+ "baz": "Baz #2",
+ "name_converted": null
},
"relationships": {
"group": {
@@ -558,7 +567,8 @@ Feature: JSON API Inclusion of Related Resources
"_id": 3,
"foo": "Foo #3",
"bar": "Bar #3",
- "baz": "Baz #3"
+ "baz": "Baz #3",
+ "name_converted": null
},
"relationships": {
"group": {
@@ -610,7 +620,8 @@ Feature: JSON API Inclusion of Related Resources
"_id": 1,
"foo": "Foo #1",
"bar": "Bar #1",
- "baz": "Baz #1"
+ "baz": "Baz #1",
+ "name_converted": null
},
"relationships": {
"groups": {
@@ -627,7 +638,8 @@ Feature: JSON API Inclusion of Related Resources
"_id": 2,
"foo": "Foo #2",
"bar": "Bar #2",
- "baz": "Baz #2"
+ "baz": "Baz #2",
+ "name_converted": null
},
"relationships": {
"groups": {
diff --git a/features/main/crud.feature b/features/main/crud.feature
index ea6ddd52b3a..2618739c53a 100644
--- a/features/main/crud.feature
+++ b/features/main/crud.feature
@@ -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],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: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[],name_converted,properties[]}",
"hydra:variableRepresentation": "BasicRepresentation",
"hydra:mapping": [
{
@@ -464,6 +464,12 @@ Feature: Create-Retrieve-Update-Delete
"property": "relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level",
"required": false
},
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "name_converted",
+ "property": "name_converted",
+ "required": false
+ },
{
"@type": "IriTemplateMapping",
"variable": "properties[]",
diff --git a/features/serializer/property_filter.feature b/features/serializer/property_filter.feature
index b8b1926e991..932427f255c 100644
--- a/features/serializer/property_filter.feature
+++ b/features/serializer/property_filter.feature
@@ -6,7 +6,7 @@ Feature: Filter with serialization attributes on items and collections
@createSchema
Scenario: Get a collection of resources by attributes id, foo and bar
Given there are 10 dummy property objects
- When I send a "GET" request to "/dummy_properties?properties[]=id&properties[]=foo&properties[]=bar"
+ When I send a "GET" request to "/dummy_properties?properties[]=id&properties[]=foo&properties[]=bar&properties[]=name_converted"
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"
@@ -20,52 +20,27 @@ Feature: Filter with serialization attributes on items and collections
"@type": {"pattern": "^hydra:Collection$"},
"hydra:member": {
"type": "array",
- "items": [
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "id": {},
- "foo": {},
- "bar": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "id", "foo", "bar"]
+ "items": {
+ "type": "object",
+ "properties": {
+ "@id": {},
+ "@type": {},
+ "id": {},
+ "foo": {},
+ "bar": {},
+ "name_converted": {}
},
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "id": {},
- "foo": {},
- "bar": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "id", "foo", "bar"]
- },
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "id": {},
- "foo": {},
- "bar": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "id", "foo", "bar"]
- }
- ],
- "additionalItems": false,
+ "additionalProperties": false,
+ "required": ["@id", "@type", "id", "foo", "bar", "name_converted"]
+ },
+ "uniqueItems": true,
"maxItems": 3,
"minItems": 3
},
"hydra:view": {
"type": "object",
"properties": {
- "@id": {"pattern": "^/dummy_properties\\?properties%5B%5D=id&properties%5B%5D=foo&properties%5B%5D=bar&page=1$"},
+ "@id": {"pattern": "^/dummy_properties\\?properties%5B%5D=id&properties%5B%5D=foo&properties%5B%5D=bar&properties%5B%5D=name_converted&page=1$"},
"@type": {"pattern": "^hydra:PartialCollectionView$"}
}
}
@@ -88,72 +63,27 @@ Feature: Filter with serialization attributes on items and collections
"@type": {"pattern": "^hydra:Collection$"},
"hydra:member": {
"type": "array",
- "items": [
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "foo": {},
- "bar": {},
- "group": {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "baz": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "baz"]
- }
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "foo", "bar"]
- },
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "foo": {},
- "bar": {},
- "group": {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "baz": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "baz"]
- }
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "foo", "bar"]
+ "items": {
+ "type": "object",
+ "properties": {
+ "@id": {},
+ "@type": {},
+ "foo": {},
+ "bar": {},
+ "group": {
+ "type": "object",
+ "properties": {
+ "@id": {},
+ "@type": {},
+ "baz": {}
+ },
+ "additionalProperties": false,
+ "required": ["@id", "@type", "baz"]
+ }
},
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "foo": {},
- "bar": {},
- "group": {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "baz": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "baz"]
- }
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "foo", "bar"]
- }
- ],
- "additionalItems": false,
+ "additionalProperties": false,
+ "required": ["@id", "@type", "foo", "bar"]
+ },
"maxItems": 3,
"minItems": 3
},
@@ -169,7 +99,7 @@ Feature: Filter with serialization attributes on items and collections
"""
Scenario: Get a collection of resources by attributes foo, bar
- When I send a "GET" request to "/dummy_properties?whitelisted_properties[]=foo&whitelisted_properties[]=bar"
+ When I send a "GET" request to "/dummy_properties?whitelisted_properties[]=foo&whitelisted_properties[]=bar&whitelisted_properties[]=name_converted"
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"
@@ -183,46 +113,24 @@ Feature: Filter with serialization attributes on items and collections
"@type": {"pattern": "^hydra:Collection$"},
"hydra:member": {
"type": "array",
- "items": [
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "foo": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "foo"]
- },
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "foo": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "foo"]
+ "items": {
+ "type": "object",
+ "properties": {
+ "@id": {},
+ "@type": {},
+ "foo": {},
+ "name_converted": {}
},
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "foo": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "foo"]
- }
- ],
- "additionalItems": false,
+ "additionalProperties": false,
+ "required": ["@id", "@type", "foo", "name_converted"]
+ },
"maxItems": 3,
"minItems": 3
},
"hydra:view": {
"type": "object",
"properties": {
- "@id": {"pattern": "^/dummy_properties\\?whitelisted_properties%5B%5D=foo&whitelisted_properties%5B%5D=bar&page=1$"},
+ "@id": {"pattern": "^/dummy_properties\\?whitelisted_properties%5B%5D=foo&whitelisted_properties%5B%5D=bar&whitelisted_properties%5B%5D=name_converted&page=1$"},
"@type": {"pattern": "^hydra:PartialCollectionView$"}
}
}
@@ -245,69 +153,26 @@ Feature: Filter with serialization attributes on items and collections
"@type": {"pattern": "^hydra:Collection$"},
"hydra:member": {
"type": "array",
- "items": [
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "foo": {},
- "group": {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "baz": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "baz"]
- }
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "foo"]
+ "items": {
+ "type": "object",
+ "properties": {
+ "@id": {},
+ "@type": {},
+ "foo": {},
+ "group": {
+ "type": "object",
+ "properties": {
+ "@id": {},
+ "@type": {},
+ "baz": {}
+ },
+ "additionalProperties": false,
+ "required": ["@id", "@type", "baz"]
+ }
},
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "foo": {},
- "group": {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "baz": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "baz"]
- }
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "foo"]
- },
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "foo": {},
- "group": {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "baz": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type"]
- }
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "foo"]
- }
- ],
- "additionalItems": false,
+ "additionalProperties": false,
+ "required": ["@id", "@type", "foo"]
+ },
"maxItems": 3,
"minItems": 3
},
@@ -337,36 +202,15 @@ Feature: Filter with serialization attributes on items and collections
"@type": {"pattern": "^hydra:Collection$"},
"hydra:member": {
"type": "array",
- "items": [
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type"]
- },
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type"]
+ "items": {
+ "type": "object",
+ "properties": {
+ "@id": {},
+ "@type": {}
},
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type"]
- }
- ],
- "additionalItems": false,
+ "additionalProperties": false,
+ "required": ["@id", "@type"]
+ },
"maxItems": 3,
"minItems": 3
},
@@ -396,63 +240,24 @@ Feature: Filter with serialization attributes on items and collections
"@type": {"pattern": "^hydra:Collection$"},
"hydra:member": {
"type": "array",
- "items": [
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "group": {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type"]
- }
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "group"]
- },
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "group": {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type"]
- }
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "group"]
+ "items": {
+ "type": "object",
+ "properties": {
+ "@id": {},
+ "@type": {},
+ "group": {
+ "type": "object",
+ "properties": {
+ "@id": {},
+ "@type": {}
+ },
+ "additionalProperties": false,
+ "required": ["@id", "@type"]
+ }
},
- {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {},
- "group": {
- "type": "object",
- "properties": {
- "@id": {},
- "@type": {}
- },
- "additionalProperties": false,
- "required": ["@id", "@type"]
- }
- },
- "additionalProperties": false,
- "required": ["@id", "@type", "group"]
- }
- ],
- "additionalItems": false,
+ "additionalProperties": false,
+ "required": ["@id", "@type", "group"]
+ },
"maxItems": 3,
"minItems": 3
},
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index f7e17f05a21..aba4a36cabb 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -78,7 +78,6 @@ parameters:
path: %currentWorkingDirectory%/src/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php
# Expected, due to deprecations
- - '#Method ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Filter\\ExistsFilter::filterProperty\(\) invoked with 7 parameters, 5-6 required\.#'
- '#Method ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Extension\\QueryResult(Item|Collection)ExtensionInterface::getResult\(\) invoked with 4 parameters, 1 required\.#'
- '#Method ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Extension\\QueryResult(Item|Collection)ExtensionInterface::supportsResult\(\) invoked with 3 parameters, 1-2 required\.#'
- '#Method ApiPlatform\\Core\\Bridge\\Doctrine\\Orm\\Filter\\AbstractFilter::apply\(\) invoked with 5 parameters, 3-4 required\.#'
diff --git a/src/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php
index cf09b77b0d4..df74e3d8e64 100644
--- a/src/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php
+++ b/src/Bridge/Doctrine/Common/Filter/BooleanFilterTrait.php
@@ -50,9 +50,9 @@ public function getDescription(string $resourceClass): array
if (!$this->isPropertyMapped($property, $resourceClass) || !$this->isBooleanField($property, $resourceClass)) {
continue;
}
-
- $description[$property] = [
- 'property' => $property,
+ $propertyName = $this->normalizePropertyName($property);
+ $description[$propertyName] = [
+ 'property' => $propertyName,
'type' => 'bool',
'required' => false,
];
@@ -65,6 +65,8 @@ abstract protected function getProperties(): ?array;
abstract protected function getLogger(): LoggerInterface;
+ abstract protected function normalizePropertyName(string $property): string;
+
/**
* Determines whether the given property refers to a boolean field.
*/
diff --git a/src/Bridge/Doctrine/Common/Filter/DateFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/DateFilterTrait.php
index 8b2492067a3..3b379fa0041 100644
--- a/src/Bridge/Doctrine/Common/Filter/DateFilterTrait.php
+++ b/src/Bridge/Doctrine/Common/Filter/DateFilterTrait.php
@@ -54,6 +54,8 @@ public function getDescription(string $resourceClass): array
abstract protected function getProperties(): ?array;
+ abstract protected function normalizePropertyName(string $property): string;
+
/**
* Determines whether the given property refers to a date field.
*/
@@ -67,9 +69,11 @@ protected function isDateField(string $property, string $resourceClass): bool
*/
protected function getFilterDescription(string $property, string $period): array
{
+ $propertyName = $this->normalizePropertyName($property);
+
return [
- sprintf('%s[%s]', $property, $period) => [
- 'property' => $property,
+ sprintf('%s[%s]', $propertyName, $period) => [
+ 'property' => $propertyName,
'type' => \DateTimeInterface::class,
'required' => false,
],
diff --git a/src/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php
index 8c86c0982bc..327138f8459 100644
--- a/src/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php
+++ b/src/Bridge/Doctrine/Common/Filter/ExistsFilterTrait.php
@@ -48,9 +48,9 @@ public function getDescription(string $resourceClass): array
if (!$this->isPropertyMapped($property, $resourceClass, true) || !$this->isNullableField($property, $resourceClass)) {
continue;
}
-
- $description[sprintf('%s[%s]', $this->existsParameterName, $property)] = [
- 'property' => $property,
+ $propertyName = $this->normalizePropertyName($property);
+ $description[sprintf('%s[%s]', $this->existsParameterName, $propertyName)] = [
+ 'property' => $propertyName,
'type' => 'bool',
'required' => false,
];
@@ -68,6 +68,8 @@ abstract protected function getProperties(): ?array;
abstract protected function getLogger(): LoggerInterface;
+ abstract protected function normalizePropertyName(string $property): string;
+
private function normalizeValue($value, string $property): ?bool
{
if (\is_array($value) && isset($value[self::QUERY_PARAMETER_KEY])) {
diff --git a/src/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php
index bd6f5ee8525..ce41e7b76a6 100644
--- a/src/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php
+++ b/src/Bridge/Doctrine/Common/Filter/NumericFilterTrait.php
@@ -45,11 +45,11 @@ public function getDescription(string $resourceClass): array
continue;
}
- $filterParameterNames = [$property, $property.'[]'];
-
+ $propertyName = $this->normalizePropertyName($property);
+ $filterParameterNames = [$propertyName, $propertyName.'[]'];
foreach ($filterParameterNames as $filterParameterName) {
$description[$filterParameterName] = [
- 'property' => $property,
+ 'property' => $propertyName,
'type' => $this->getType((string) $this->getDoctrineFieldType($property, $resourceClass)),
'required' => false,
'is_collection' => '[]' === substr((string) $filterParameterName, -2),
@@ -69,6 +69,8 @@ abstract protected function getProperties(): ?array;
abstract protected function getLogger(): LoggerInterface;
+ abstract protected function normalizePropertyName(string $property): string;
+
/**
* Determines whether the given property refers to a numeric field.
*/
diff --git a/src/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php
index 53ca563057e..6ebf9e13396 100644
--- a/src/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php
+++ b/src/Bridge/Doctrine/Common/Filter/OrderFilterTrait.php
@@ -47,9 +47,9 @@ public function getDescription(string $resourceClass): array
if (!$this->isPropertyMapped($property, $resourceClass)) {
continue;
}
-
- $description[sprintf('%s[%s]', $this->orderParameterName, $property)] = [
- 'property' => $property,
+ $propertyName = $this->normalizePropertyName($property);
+ $description[sprintf('%s[%s]', $this->orderParameterName, $propertyName)] = [
+ 'property' => $propertyName,
'type' => 'string',
'required' => false,
];
@@ -60,6 +60,8 @@ public function getDescription(string $resourceClass): array
abstract protected function getProperties(): ?array;
+ abstract protected function normalizePropertyName(string $property): string;
+
private function normalizeValue($value, string $property): ?string
{
if (empty($value) && null !== $defaultDirection = $this->getProperties()[$property]['default_direction'] ?? null) {
diff --git a/src/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php
index a44cbdd0d28..1f7c1010f22 100644
--- a/src/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php
+++ b/src/Bridge/Doctrine/Common/Filter/RangeFilterTrait.php
@@ -58,14 +58,18 @@ abstract protected function getProperties(): ?array;
abstract protected function getLogger(): LoggerInterface;
+ abstract protected function normalizePropertyName(string $property): string;
+
/**
* Gets filter description.
*/
protected function getFilterDescription(string $fieldName, string $operator): array
{
+ $propertyName = $this->normalizePropertyName($fieldName);
+
return [
- sprintf('%s[%s]', $fieldName, $operator) => [
- 'property' => $fieldName,
+ sprintf('%s[%s]', $propertyName, $operator) => [
+ 'property' => $propertyName,
'type' => 'string',
'required' => false,
],
diff --git a/src/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php b/src/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php
index 42b3aba9e15..e96fa514c51 100644
--- a/src/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php
+++ b/src/Bridge/Doctrine/Common/Filter/SearchFilterTrait.php
@@ -59,18 +59,19 @@ public function getDescription(string $resourceClass): array
$metadata = $this->getClassMetadata($resourceClass);
}
+ $propertyName = $this->normalizePropertyName($property);
if ($metadata->hasField($field)) {
$typeOfField = $this->getType($metadata->getTypeOfField($field));
$strategy = $this->getProperties()[$property] ?? self::STRATEGY_EXACT;
- $filterParameterNames = [$property];
+ $filterParameterNames = [$propertyName];
if (self::STRATEGY_EXACT === $strategy) {
- $filterParameterNames[] = $property.'[]';
+ $filterParameterNames[] = $propertyName.'[]';
}
foreach ($filterParameterNames as $filterParameterName) {
$description[$filterParameterName] = [
- 'property' => $property,
+ 'property' => $propertyName,
'type' => $typeOfField,
'required' => false,
'strategy' => $strategy,
@@ -79,13 +80,13 @@ public function getDescription(string $resourceClass): array
}
} elseif ($metadata->hasAssociation($field)) {
$filterParameterNames = [
- $property,
- $property.'[]',
+ $propertyName,
+ $propertyName.'[]',
];
foreach ($filterParameterNames as $filterParameterName) {
$description[$filterParameterName] = [
- 'property' => $property,
+ 'property' => $propertyName,
'type' => 'string',
'required' => false,
'strategy' => self::STRATEGY_EXACT,
@@ -111,6 +112,8 @@ abstract protected function getIriConverter(): IriConverterInterface;
abstract protected function getPropertyAccessor(): PropertyAccessorInterface;
+ abstract protected function normalizePropertyName(string $property): string;
+
/**
* Gets the ID from an IRI or a raw ID.
*/
diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php b/src/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php
index a5fe637fdc7..7e75cb7c373 100644
--- a/src/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php
+++ b/src/Bridge/Doctrine/MongoDbOdm/Filter/AbstractFilter.php
@@ -19,6 +19,7 @@
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
+use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
/**
* {@inheritdoc}
@@ -37,12 +38,14 @@ abstract class AbstractFilter implements FilterInterface
protected $managerRegistry;
protected $logger;
protected $properties;
+ protected $nameConverter;
- public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $logger = null, array $properties = null)
+ public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null)
{
$this->managerRegistry = $managerRegistry;
$this->logger = $logger ?? new NullLogger();
$this->properties = $properties;
+ $this->nameConverter = $nameConverter;
}
/**
@@ -51,7 +54,7 @@ public function __construct(ManagerRegistry $managerRegistry, LoggerInterface $l
public function apply(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = [])
{
foreach ($context['filters'] as $property => $value) {
- $this->filterProperty($property, $value, $aggregationBuilder, $resourceClass, $operationName, $context);
+ $this->filterProperty($this->denormalizePropertyName($property), $value, $aggregationBuilder, $resourceClass, $operationName, $context);
}
}
@@ -87,4 +90,14 @@ protected function isPropertyEnabled(string $property, string $resourceClass): b
return \array_key_exists($property, $this->properties);
}
+
+ protected function denormalizePropertyName(string $property): string
+ {
+ return null !== $this->nameConverter ? $this->nameConverter->denormalize($property) : $property;
+ }
+
+ protected function normalizePropertyName(string $property): string
+ {
+ return null !== $this->nameConverter ? $this->nameConverter->normalize($property) : $property;
+ }
}
diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php b/src/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php
index 73eef88c5ad..762f3837192 100644
--- a/src/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php
+++ b/src/Bridge/Doctrine/MongoDbOdm/Filter/ExistsFilter.php
@@ -19,6 +19,7 @@
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Psr\Log\LoggerInterface;
+use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
/**
* Filters the collection by whether a property value exists or not.
@@ -39,9 +40,9 @@ 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)
+ 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);
+ parent::__construct($managerRegistry, $logger, $properties, $nameConverter);
$this->existsParameterName = $existsParameterName;
}
@@ -59,7 +60,7 @@ public function apply(Builder $aggregationBuilder, string $resourceClass, string
}
foreach ($context['filters'][$this->existsParameterName] as $property => $value) {
- $this->filterProperty($property, $value, $aggregationBuilder, $resourceClass, $operationName, $context);
+ $this->filterProperty($this->denormalizePropertyName($property), $value, $aggregationBuilder, $resourceClass, $operationName, $context);
}
}
diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php b/src/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php
index 1d5c5b6c762..bd382e71d18 100644
--- a/src/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php
+++ b/src/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilter.php
@@ -18,6 +18,7 @@
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Psr\Log\LoggerInterface;
+use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
/**
* Order the collection by given properties.
@@ -39,7 +40,7 @@ final class OrderFilter extends AbstractFilter implements OrderFilterInterface
{
use OrderFilterTrait;
- public function __construct(ManagerRegistry $managerRegistry, string $orderParameterName = 'order', LoggerInterface $logger = null, array $properties = null)
+ public function __construct(ManagerRegistry $managerRegistry, string $orderParameterName = 'order', LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null)
{
if (null !== $properties) {
$properties = array_map(function ($propertyOptions) {
@@ -54,7 +55,7 @@ public function __construct(ManagerRegistry $managerRegistry, string $orderParam
}, $properties);
}
- parent::__construct($managerRegistry, $logger, $properties);
+ parent::__construct($managerRegistry, $logger, $properties, $nameConverter);
$this->orderParameterName = $orderParameterName;
}
@@ -75,7 +76,7 @@ public function apply(Builder $aggregationBuilder, string $resourceClass, string
}
foreach ($context['filters'][$this->orderParameterName] as $property => $value) {
- $this->filterProperty($property, $value, $aggregationBuilder, $resourceClass, $operationName, $context);
+ $this->filterProperty($this->denormalizePropertyName($property), $value, $aggregationBuilder, $resourceClass, $operationName, $context);
}
}
diff --git a/src/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php b/src/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php
index e954e3ec786..3597f4297ae 100644
--- a/src/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php
+++ b/src/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilter.php
@@ -27,6 +27,7 @@
use Psr\Log\LoggerInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
+use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
/**
* Filter the collection by given properties.
@@ -42,9 +43,9 @@ final class SearchFilter extends AbstractFilter implements SearchFilterInterface
public const DOCTRINE_INTEGER_TYPE = MongoDbType::INTEGER;
- public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface $iriConverter, IdentifiersExtractorInterface $identifiersExtractor, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null)
+ public function __construct(ManagerRegistry $managerRegistry, IriConverterInterface $iriConverter, IdentifiersExtractorInterface $identifiersExtractor, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null)
{
- parent::__construct($managerRegistry, $logger, $properties);
+ parent::__construct($managerRegistry, $logger, $properties, $nameConverter);
$this->iriConverter = $iriConverter;
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
diff --git a/src/Bridge/Doctrine/Orm/Filter/AbstractContextAwareFilter.php b/src/Bridge/Doctrine/Orm/Filter/AbstractContextAwareFilter.php
index a896f59cc6e..312175040cf 100644
--- a/src/Bridge/Doctrine/Orm/Filter/AbstractContextAwareFilter.php
+++ b/src/Bridge/Doctrine/Orm/Filter/AbstractContextAwareFilter.php
@@ -30,7 +30,7 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q
}
foreach ($context['filters'] as $property => $value) {
- $this->filterProperty($property, $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
+ $this->filterProperty($this->denormalizePropertyName($property), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
}
}
}
diff --git a/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php b/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php
index 3e3aff8f591..daf5f7a4d35 100644
--- a/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php
+++ b/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php
@@ -23,6 +23,7 @@
use Psr\Log\NullLogger;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
/**
* {@inheritdoc}
@@ -41,8 +42,9 @@ abstract class AbstractFilter implements FilterInterface
protected $requestStack;
protected $logger;
protected $properties;
+ protected $nameConverter;
- public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, LoggerInterface $logger = null, array $properties = null)
+ public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null)
{
if (null !== $requestStack) {
@trigger_error(sprintf('Passing an instance of "%s" is deprecated since 2.2. Use "filters" context key instead.', RequestStack::class), E_USER_DEPRECATED);
@@ -52,6 +54,7 @@ public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $req
$this->requestStack = $requestStack;
$this->logger = $logger ?? new NullLogger();
$this->properties = $properties;
+ $this->nameConverter = $nameConverter;
}
/**
@@ -138,4 +141,14 @@ protected function extractProperties(Request $request/*, string $resourceClass*/
return $request->query->all();
}
+
+ protected function denormalizePropertyName(string $property): string
+ {
+ return null !== $this->nameConverter ? $this->nameConverter->denormalize($property) : $property;
+ }
+
+ protected function normalizePropertyName(string $property): string
+ {
+ return null !== $this->nameConverter ? $this->nameConverter->normalize($property) : $property;
+ }
}
diff --git a/src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php b/src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php
index 871a4704489..4dd9f8b7d1f 100644
--- a/src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php
+++ b/src/Bridge/Doctrine/Orm/Filter/ExistsFilter.php
@@ -24,6 +24,7 @@
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
/**
* Filters the collection by whether a property value exists or not.
@@ -41,9 +42,9 @@ class ExistsFilter extends AbstractContextAwareFilter implements ExistsFilterInt
{
use ExistsFilterTrait;
- public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, LoggerInterface $logger = null, array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY)
+ public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, LoggerInterface $logger = null, array $properties = null, string $existsParameterName = self::QUERY_PARAMETER_KEY, NameConverterInterface $nameConverter = null)
{
- parent::__construct($managerRegistry, $requestStack, $logger, $properties);
+ parent::__construct($managerRegistry, $requestStack, $logger, $properties, $nameConverter);
$this->existsParameterName = $existsParameterName;
}
@@ -61,7 +62,7 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q
}
foreach ($context['filters'][$this->existsParameterName] as $property => $value) {
- $this->filterProperty($property, $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
+ $this->filterProperty($this->denormalizePropertyName($property), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
}
}
diff --git a/src/Bridge/Doctrine/Orm/Filter/OrderFilter.php b/src/Bridge/Doctrine/Orm/Filter/OrderFilter.php
index d47895f1bb2..f019c562676 100644
--- a/src/Bridge/Doctrine/Orm/Filter/OrderFilter.php
+++ b/src/Bridge/Doctrine/Orm/Filter/OrderFilter.php
@@ -22,6 +22,7 @@
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
/**
* Order the collection by given properties.
@@ -40,7 +41,7 @@ class OrderFilter extends AbstractContextAwareFilter implements OrderFilterInter
{
use OrderFilterTrait;
- public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, string $orderParameterName = 'order', LoggerInterface $logger = null, array $properties = null)
+ public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack = null, string $orderParameterName = 'order', LoggerInterface $logger = null, array $properties = null, NameConverterInterface $nameConverter = null)
{
if (null !== $properties) {
$properties = array_map(function ($propertyOptions) {
@@ -55,7 +56,7 @@ public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $req
}, $properties);
}
- parent::__construct($managerRegistry, $requestStack, $logger, $properties);
+ parent::__construct($managerRegistry, $requestStack, $logger, $properties, $nameConverter);
$this->orderParameterName = $orderParameterName;
}
@@ -76,7 +77,7 @@ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $q
}
foreach ($context['filters'][$this->orderParameterName] as $property => $value) {
- $this->filterProperty($property, $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
+ $this->filterProperty($this->denormalizePropertyName($property), $value, $queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
}
}
diff --git a/src/Bridge/Doctrine/Orm/Filter/SearchFilter.php b/src/Bridge/Doctrine/Orm/Filter/SearchFilter.php
index c3f24e5cc41..df5f6aeb773 100644
--- a/src/Bridge/Doctrine/Orm/Filter/SearchFilter.php
+++ b/src/Bridge/Doctrine/Orm/Filter/SearchFilter.php
@@ -28,6 +28,7 @@
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
+use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
/**
* Filter the collection by given properties.
@@ -40,9 +41,9 @@ class SearchFilter extends AbstractContextAwareFilter implements SearchFilterInt
public const DOCTRINE_INTEGER_TYPE = DBALType::INTEGER;
- public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack, IriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, IdentifiersExtractorInterface $identifiersExtractor = null)
+ public function __construct(ManagerRegistry $managerRegistry, ?RequestStack $requestStack, IriConverterInterface $iriConverter, PropertyAccessorInterface $propertyAccessor = null, LoggerInterface $logger = null, array $properties = null, IdentifiersExtractorInterface $identifiersExtractor = null, NameConverterInterface $nameConverter = null)
{
- parent::__construct($managerRegistry, $requestStack, $logger, $properties);
+ parent::__construct($managerRegistry, $requestStack, $logger, $properties, $nameConverter);
if (null === $identifiersExtractor) {
@trigger_error('Not injecting ItemIdentifiersExtractor is deprecated since API Platform 2.5 and can lead to unexpected behaviors, it will not be possible anymore in API Platform 3.0.', E_USER_DEPRECATED);
diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml
index 33b5b84e188..f7537107b66 100644
--- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml
+++ b/src/Bridge/Symfony/Bundle/Resources/config/api.xml
@@ -83,7 +83,12 @@
-
+
+ properties
+ false
+ null
+
+
diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml
index f95ffc1964f..fa300e4a700 100644
--- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml
+++ b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml
@@ -67,6 +67,7 @@
+
@@ -74,12 +75,14 @@
+
+
@@ -87,12 +90,14 @@
%api_platform.collection.exists_parameter_name%
+
+
@@ -100,12 +105,14 @@
%api_platform.collection.order_parameter_name%
+
+
diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml
index 51f7dd53978..ad6301b2bdf 100644
--- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml
+++ b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml
@@ -56,6 +56,7 @@
+
@@ -64,6 +65,7 @@
null
%api_platform.collection.order_parameter_name%
+
@@ -71,6 +73,7 @@
null
+
@@ -78,6 +81,7 @@
null
+
@@ -85,6 +89,7 @@
null
+
@@ -92,6 +97,7 @@
null
+
@@ -100,6 +106,7 @@
null
%api_platform.collection.exists_parameter_name%
+
diff --git a/src/Serializer/Filter/PropertyFilter.php b/src/Serializer/Filter/PropertyFilter.php
index c60464e1c62..43e527c8db2 100644
--- a/src/Serializer/Filter/PropertyFilter.php
+++ b/src/Serializer/Filter/PropertyFilter.php
@@ -14,6 +14,7 @@
namespace ApiPlatform\Core\Serializer\Filter;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
/**
@@ -26,12 +27,14 @@ final class PropertyFilter implements FilterInterface
private $overrideDefaultProperties;
private $parameterName;
private $whitelist;
+ private $nameConverter;
- public function __construct(string $parameterName = 'properties', bool $overrideDefaultProperties = false, array $whitelist = null)
+ public function __construct(string $parameterName = 'properties', bool $overrideDefaultProperties = false, array $whitelist = null, NameConverterInterface $nameConverter = null)
{
$this->overrideDefaultProperties = $overrideDefaultProperties;
$this->parameterName = $parameterName;
$this->whitelist = null === $whitelist ? null : $this->formatWhitelist($whitelist);
+ $this->nameConverter = $nameConverter;
}
/**
@@ -51,6 +54,8 @@ public function apply(Request $request, bool $normalization, array $attributes,
return;
}
+ $properties = $this->denormalizeProperties($properties);
+
if (null !== $this->whitelist) {
$properties = $this->getProperties($properties, $this->whitelist);
}
@@ -129,18 +134,42 @@ private function getProperties(array $properties, array $whitelist = null): arra
foreach ($properties as $key => $value) {
if (is_numeric($key)) {
- if (\in_array($value, $whitelist, true)) {
- $result[] = $value;
+ if (\in_array($propertyName = $this->denormalizePropertyName($value), $whitelist, true)) {
+ $result[] = $propertyName;
}
continue;
}
if (\is_array($value) && isset($whitelist[$key]) && $recursiveResult = $this->getProperties($value, $whitelist[$key])) {
- $result[$key] = $recursiveResult;
+ $result[$this->denormalizePropertyName($key)] = $recursiveResult;
+ }
+ }
+
+ return $result;
+ }
+
+ private function denormalizeProperties(array $properties): array
+ {
+ if (null === $this->nameConverter || !$properties) {
+ return $properties;
+ }
+
+ $result = [];
+ foreach ($properties as $key => $value) {
+ if (is_numeric($key)) {
+ $result[] = $this->denormalizePropertyName($value);
+ continue;
}
+
+ $result[$this->denormalizePropertyName($key)] = \is_array($value) ? $this->denormalizeProperties($value) : $value;
}
return $result;
}
+
+ private function denormalizePropertyName(string $property): string
+ {
+ return null !== $this->nameConverter ? $this->nameConverter->denormalize($property) : $property;
+ }
}
diff --git a/tests/Bridge/Doctrine/Common/Filter/SearchFilterTestTrait.php b/tests/Bridge/Doctrine/Common/Filter/SearchFilterTestTrait.php
index b38f528ad16..1cc6e81420e 100644
--- a/tests/Bridge/Doctrine/Common/Filter/SearchFilterTestTrait.php
+++ b/tests/Bridge/Doctrine/Common/Filter/SearchFilterTestTrait.php
@@ -134,15 +134,15 @@ public function testGetDescription()
'strategy' => 'exact',
'is_collection' => true,
],
- 'nameConverted' => [
- 'property' => 'nameConverted',
+ 'name_converted' => [
+ 'property' => 'name_converted',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
'is_collection' => false,
],
- 'nameConverted[]' => [
- 'property' => 'nameConverted',
+ 'name_converted[]' => [
+ 'property' => 'name_converted',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
diff --git a/tests/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilterTest.php b/tests/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilterTest.php
index d11e1fc1f7c..f874fa4f7c2 100644
--- a/tests/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilterTest.php
+++ b/tests/Bridge/Doctrine/MongoDbOdm/Filter/OrderFilterTest.php
@@ -16,6 +16,7 @@
use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\OrderFilter;
use ApiPlatform\Core\Test\DoctrineMongoDbOdmFilterTestCase;
use ApiPlatform\Core\Tests\Bridge\Doctrine\Common\Filter\OrderFilterTestTrait;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Serializer\NameConverter\CustomConverter;
use Doctrine\Common\Persistence\ManagerRegistry;
/**
@@ -82,8 +83,8 @@ public function testGetDescriptionDefaultFields()
'type' => 'string',
'required' => false,
],
- 'order[nameConverted]' => [
- 'property' => 'nameConverted',
+ 'order[name_converted]' => [
+ 'property' => 'name_converted',
'type' => 'string',
'required' => false,
],
@@ -352,6 +353,6 @@ public function provideApplyTestData(): array
protected function buildFilter(?array $properties = null)
{
- return new $this->filterClass($this->managerRegistry, 'order', null, $properties);
+ return new $this->filterClass($this->managerRegistry, 'order', null, $properties, new CustomConverter());
}
}
diff --git a/tests/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilterTest.php b/tests/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilterTest.php
index 0124673dbf2..3b10e839f88 100644
--- a/tests/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilterTest.php
+++ b/tests/Bridge/Doctrine/MongoDbOdm/Filter/SearchFilterTest.php
@@ -20,6 +20,7 @@
use ApiPlatform\Core\Test\DoctrineMongoDbOdmFilterTestCase;
use ApiPlatform\Core\Tests\Bridge\Doctrine\Common\Filter\SearchFilterTestTrait;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Document\RelatedDummy;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Serializer\NameConverter\CustomConverter;
use Doctrine\Common\Persistence\ManagerRegistry;
use MongoDB\BSON\Regex;
use Prophecy\Argument;
@@ -178,15 +179,15 @@ public function testGetDescriptionDefaultFields()
'strategy' => 'exact',
'is_collection' => true,
],
- 'nameConverted' => [
- 'property' => 'nameConverted',
+ 'name_converted' => [
+ 'property' => 'name_converted',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
'is_collection' => false,
],
- 'nameConverted[]' => [
- 'property' => 'nameConverted',
+ 'name_converted[]' => [
+ 'property' => 'name_converted',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
@@ -572,6 +573,6 @@ protected function buildSearchFilter(ManagerRegistry $managerRegistry, ?array $p
$identifierExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class);
$identifierExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']);
- return new SearchFilter($managerRegistry, $iriConverter, $identifierExtractorProphecy->reveal(), $propertyAccessor, null, $properties);
+ return new SearchFilter($managerRegistry, $iriConverter, $identifierExtractorProphecy->reveal(), $propertyAccessor, null, $properties, new CustomConverter());
}
}
diff --git a/tests/Bridge/Doctrine/Orm/Filter/ExistsFilterTest.php b/tests/Bridge/Doctrine/Orm/Filter/ExistsFilterTest.php
index ac8ff833dfa..172544577cd 100644
--- a/tests/Bridge/Doctrine/Orm/Filter/ExistsFilterTest.php
+++ b/tests/Bridge/Doctrine/Orm/Filter/ExistsFilterTest.php
@@ -17,6 +17,7 @@
use ApiPlatform\Core\Test\DoctrineOrmFilterTestCase;
use ApiPlatform\Core\Tests\Bridge\Doctrine\Common\Filter\ExistsFilterTestTrait;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Serializer\NameConverter\CustomConverter;
use Doctrine\Common\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\RequestStack;
@@ -79,8 +80,8 @@ public function testGetDescriptionDefaultFields()
'type' => 'bool',
'required' => false,
],
- 'exists[nameConverted]' => [
- 'property' => 'nameConverted',
+ 'exists[name_converted]' => [
+ 'property' => 'name_converted',
'type' => 'bool',
'required' => false,
],
@@ -273,7 +274,7 @@ public function testLegacyRequest()
sprintf('SELECT o FROM %s o WHERE o.description IS NOT NULL', Dummy::class),
null,
function (ManagerRegistry $managerRegistry, array $properties = null, RequestStack $requestStack = null): ExistsFilter {
- return new ExistsFilter($managerRegistry, $requestStack, null, $properties, 'exists');
+ return new ExistsFilter($managerRegistry, $requestStack, null, $properties, 'exists', new CustomConverter());
},
];
@@ -282,6 +283,6 @@ function (ManagerRegistry $managerRegistry, array $properties = null, RequestSta
protected function buildFilter(?array $properties = null)
{
- return new $this->filterClass($this->managerRegistry, null, null, $properties, 'exists');
+ return new $this->filterClass($this->managerRegistry, null, null, $properties, 'exists', new CustomConverter());
}
}
diff --git a/tests/Bridge/Doctrine/Orm/Filter/OrderFilterTest.php b/tests/Bridge/Doctrine/Orm/Filter/OrderFilterTest.php
index f44b558a13b..0d44a18fc09 100644
--- a/tests/Bridge/Doctrine/Orm/Filter/OrderFilterTest.php
+++ b/tests/Bridge/Doctrine/Orm/Filter/OrderFilterTest.php
@@ -17,6 +17,7 @@
use ApiPlatform\Core\Test\DoctrineOrmFilterTestCase;
use ApiPlatform\Core\Tests\Bridge\Doctrine\Common\Filter\OrderFilterTestTrait;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Serializer\NameConverter\CustomConverter;
use Doctrine\Common\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\RequestStack;
@@ -85,8 +86,8 @@ public function testGetDescriptionDefaultFields()
'type' => 'string',
'required' => false,
],
- 'order[nameConverted]' => [
- 'property' => 'nameConverted',
+ 'order[name_converted]' => [
+ 'property' => 'name_converted',
'type' => 'string',
'required' => false,
],
@@ -196,6 +197,6 @@ public function provideApplyTestData(): array
protected function buildFilter(?array $properties = null)
{
- return new $this->filterClass($this->managerRegistry, null, 'order', null, $properties);
+ return new $this->filterClass($this->managerRegistry, null, 'order', null, $properties, new CustomConverter());
}
}
diff --git a/tests/Bridge/Doctrine/Orm/Filter/SearchFilterTest.php b/tests/Bridge/Doctrine/Orm/Filter/SearchFilterTest.php
index c98b885be46..5b5e6f418e5 100644
--- a/tests/Bridge/Doctrine/Orm/Filter/SearchFilterTest.php
+++ b/tests/Bridge/Doctrine/Orm/Filter/SearchFilterTest.php
@@ -22,6 +22,7 @@
use ApiPlatform\Core\Tests\Bridge\Doctrine\Common\Filter\SearchFilterTestTrait;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Serializer\NameConverter\CustomConverter;
use Doctrine\Common\Persistence\ManagerRegistry;
use Prophecy\Argument;
use Symfony\Component\HttpFoundation\Request;
@@ -183,15 +184,15 @@ public function testGetDescriptionDefaultFields()
'strategy' => 'exact',
'is_collection' => true,
],
- 'nameConverted' => [
- 'property' => 'nameConverted',
+ 'name_converted' => [
+ 'property' => 'name_converted',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
'is_collection' => false,
],
- 'nameConverted[]' => [
- 'property' => 'nameConverted',
+ 'name_converted[]' => [
+ 'property' => 'name_converted',
'type' => 'string',
'required' => false,
'strategy' => 'exact',
@@ -515,6 +516,6 @@ protected function buildSearchFilter(ManagerRegistry $managerRegistry, ?array $p
$identifierExtractorProphecy = $this->prophesize(IdentifiersExtractorInterface::class);
$identifierExtractorProphecy->getIdentifiersFromResourceClass(Argument::type('string'))->willReturn(['id']);
- return new SearchFilter($managerRegistry, $requestStack, $iriConverter, $propertyAccessor, null, $properties, $identifierExtractorProphecy->reveal());
+ return new SearchFilter($managerRegistry, $requestStack, $iriConverter, $propertyAccessor, null, $properties, $identifierExtractorProphecy->reveal(), new CustomConverter());
}
}
diff --git a/tests/Fixtures/TestBundle/Document/ConvertedBoolean.php b/tests/Fixtures/TestBundle/Document/ConvertedBoolean.php
new file mode 100644
index 00000000000..de7f702a873
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Document/ConvertedBoolean.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Document;
+
+use ApiPlatform\Core\Annotation\ApiFilter;
+use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\BooleanFilter;
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * @ApiResource
+ * @ODM\Document
+ * @ApiFilter(BooleanFilter::class)
+ */
+class ConvertedBoolean
+{
+ /**
+ * @var int
+ *
+ * @ODM\Id(strategy="INCREMENT", type="integer")
+ */
+ private $id;
+
+ /**
+ * @var bool
+ *
+ * @ODM\Field(type="boolean")
+ */
+ public $nameConverted;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Document/ConvertedDate.php b/tests/Fixtures/TestBundle/Document/ConvertedDate.php
new file mode 100644
index 00000000000..f16d2813f70
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Document/ConvertedDate.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Document;
+
+use ApiPlatform\Core\Annotation\ApiFilter;
+use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\DateFilter;
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * @ApiResource
+ * @ODM\Document
+ * @ApiFilter(DateFilter::class)
+ */
+class ConvertedDate
+{
+ /**
+ * @var int
+ *
+ * @ODM\Id(strategy="INCREMENT", type="integer")
+ */
+ private $id;
+
+ /**
+ * @var \DateTime
+ *
+ * @ODM\Field(type="date")
+ */
+ public $nameConverted;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Document/ConvertedInteger.php b/tests/Fixtures/TestBundle/Document/ConvertedInteger.php
new file mode 100644
index 00000000000..9fd02e2529e
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Document/ConvertedInteger.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Document;
+
+use ApiPlatform\Core\Annotation\ApiFilter;
+use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\NumericFilter;
+use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\OrderFilter;
+use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\RangeFilter;
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * @ApiResource
+ * @ODM\Document
+ * @ApiFilter(NumericFilter::class, properties={"nameConverted"})
+ * @ApiFilter(RangeFilter::class, properties={"nameConverted"})
+ * @ApiFilter(OrderFilter::class, properties={"nameConverted"})
+ */
+class ConvertedInteger
+{
+ /**
+ * @var int
+ *
+ * @ODM\Id(strategy="INCREMENT", type="integer")
+ */
+ private $id;
+
+ /**
+ * @var int
+ *
+ * @ODM\Field(type="integer")
+ */
+ public $nameConverted;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Document/ConvertedString.php b/tests/Fixtures/TestBundle/Document/ConvertedString.php
new file mode 100644
index 00000000000..057e35d0234
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Document/ConvertedString.php
@@ -0,0 +1,46 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Document;
+
+use ApiPlatform\Core\Annotation\ApiFilter;
+use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\ExistsFilter;
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+/**
+ * @ApiResource
+ * @ODM\Document
+ * @ApiFilter(ExistsFilter::class, properties={"nameConverted"})
+ */
+class ConvertedString
+{
+ /**
+ * @var int
+ *
+ * @ODM\Id(strategy="INCREMENT", type="integer")
+ */
+ private $id;
+
+ /**
+ * @var string|null
+ *
+ * @ODM\Field(type="string", nullable=true)
+ */
+ public $nameConverted;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Document/DummyProperty.php b/tests/Fixtures/TestBundle/Document/DummyProperty.php
index e0dda8378d9..a13540fb41c 100644
--- a/tests/Fixtures/TestBundle/Document/DummyProperty.php
+++ b/tests/Fixtures/TestBundle/Document/DummyProperty.php
@@ -99,6 +99,15 @@ class DummyProperty
*/
public $groups;
+ /**
+ * @var string
+ *
+ * @ODM\Field(nullable=true)
+ *
+ * @Groups({"dummy_read", "dummy_write"})
+ */
+ public $nameConverted;
+
/**
* @return int
*/
diff --git a/tests/Fixtures/TestBundle/Entity/ConvertedBoolean.php b/tests/Fixtures/TestBundle/Entity/ConvertedBoolean.php
new file mode 100644
index 00000000000..52211e28852
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Entity/ConvertedBoolean.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
+
+use ApiPlatform\Core\Annotation\ApiFilter;
+use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\BooleanFilter;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @ApiResource
+ * @ORM\Entity
+ * @ApiFilter(BooleanFilter::class)
+ */
+class ConvertedBoolean
+{
+ /**
+ * @var int
+ *
+ * @ORM\Column(type="integer", nullable=true)
+ * @ORM\Id
+ * @ORM\GeneratedValue(strategy="AUTO")
+ */
+ private $id;
+
+ /**
+ * @var bool
+ *
+ * @ORM\Column(type="boolean")
+ */
+ public $nameConverted;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Entity/ConvertedDate.php b/tests/Fixtures/TestBundle/Entity/ConvertedDate.php
new file mode 100644
index 00000000000..e08357f5159
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Entity/ConvertedDate.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
+
+use ApiPlatform\Core\Annotation\ApiFilter;
+use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @ApiResource
+ * @ORM\Entity
+ * @ApiFilter(DateFilter::class)
+ */
+class ConvertedDate
+{
+ /**
+ * @var int
+ *
+ * @ORM\Column(type="integer", nullable=true)
+ * @ORM\Id
+ * @ORM\GeneratedValue(strategy="AUTO")
+ */
+ private $id;
+
+ /**
+ * @var \DateTime
+ *
+ * @ORM\Column(type="date")
+ */
+ public $nameConverted;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Entity/ConvertedInteger.php b/tests/Fixtures/TestBundle/Entity/ConvertedInteger.php
new file mode 100644
index 00000000000..8db6f254b75
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Entity/ConvertedInteger.php
@@ -0,0 +1,52 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
+
+use ApiPlatform\Core\Annotation\ApiFilter;
+use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\NumericFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\RangeFilter;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @ApiResource
+ * @ORM\Entity
+ * @ApiFilter(NumericFilter::class, properties={"nameConverted"})
+ * @ApiFilter(RangeFilter::class, properties={"nameConverted"})
+ * @ApiFilter(OrderFilter::class, properties={"nameConverted"})
+ */
+class ConvertedInteger
+{
+ /**
+ * @var int
+ *
+ * @ORM\Column(type="integer", nullable=true)
+ * @ORM\Id
+ * @ORM\GeneratedValue(strategy="AUTO")
+ */
+ private $id;
+
+ /**
+ * @var int
+ *
+ * @ORM\Column(type="integer")
+ */
+ public $nameConverted;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Entity/ConvertedString.php b/tests/Fixtures/TestBundle/Entity/ConvertedString.php
new file mode 100644
index 00000000000..cc50a3a393c
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Entity/ConvertedString.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
+
+use ApiPlatform\Core\Annotation\ApiFilter;
+use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\ExistsFilter;
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * @ApiResource
+ * @ORM\Entity
+ * @ApiFilter(ExistsFilter::class, properties={"nameConverted"})
+ */
+class ConvertedString
+{
+ /**
+ * @var int
+ *
+ * @ORM\Column(type="integer", nullable=true)
+ * @ORM\Id
+ * @ORM\GeneratedValue(strategy="AUTO")
+ */
+ private $id;
+
+ /**
+ * @var string|null
+ *
+ * @ORM\Column(type="string", nullable=true)
+ */
+ public $nameConverted;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Entity/DummyProperty.php b/tests/Fixtures/TestBundle/Entity/DummyProperty.php
index b8a11a6e957..4d9e85c9bb5 100644
--- a/tests/Fixtures/TestBundle/Entity/DummyProperty.php
+++ b/tests/Fixtures/TestBundle/Entity/DummyProperty.php
@@ -101,6 +101,15 @@ class DummyProperty
*/
public $groups;
+ /**
+ * @var string
+ *
+ * @ORM\Column(nullable=true)
+ *
+ * @Groups({"dummy_read"})
+ */
+ public $nameConverted;
+
/**
* @return int
*/
diff --git a/tests/Fixtures/app/config/config_common.yml b/tests/Fixtures/app/config/config_common.yml
index 09931dfd7af..bf0e7199c33 100644
--- a/tests/Fixtures/app/config/config_common.yml
+++ b/tests/Fixtures/app/config/config_common.yml
@@ -151,7 +151,7 @@ services:
app.entity.filter.dummy_property.whitelist_property:
parent: 'api_platform.serializer.property_filter'
- arguments: [ 'whitelisted_properties', false, [foo] ]
+ arguments: [ 'whitelisted_properties', false, [foo, nameConverted] ]
tags: [ { name: 'api_platform.filter', id: 'dummy_property.whitelist_property' } ]
app.entity.filter.dummy_property.whitelist_nested_property:
diff --git a/tests/Fixtures/app/config/config_orm.yml b/tests/Fixtures/app/config/config_orm.yml
index bdf30443a30..a98632f722e 100644
--- a/tests/Fixtures/app/config/config_orm.yml
+++ b/tests/Fixtures/app/config/config_orm.yml
@@ -4,7 +4,7 @@ imports:
services:
app.my_dummy_resource.search_filter:
parent: 'api_platform.doctrine.orm.search_filter'
- arguments: [ { 'id': 'exact', 'name': 'partial', 'alias': 'start', 'description': 'word_start', 'relatedDummy.name': 'exact', 'relatedDummies': 'exact', 'dummy': 'ipartial', 'relatedDummies.name': 'start', 'embeddedDummy.dummyName': 'partial', 'relatedDummy.thirdLevel.level': 'exact', 'relatedDummy.thirdLevel.fourthLevel.level': 'exact', 'relatedDummy.thirdLevel.badFourthLevel.level': 'exact', 'relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level': 'exact' } ]
+ arguments: [ { 'id': 'exact', 'name': 'partial', 'alias': 'start', 'description': 'word_start', 'relatedDummy.name': 'exact', 'relatedDummies': 'exact', 'dummy': 'ipartial', 'relatedDummies.name': 'start', 'embeddedDummy.dummyName': 'partial', 'relatedDummy.thirdLevel.level': 'exact', 'relatedDummy.thirdLevel.fourthLevel.level': 'exact', 'relatedDummy.thirdLevel.badFourthLevel.level': 'exact', 'relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level': 'exact', 'nameConverted': 'partial' } ]
tags: [ { name: 'api_platform.filter', id: 'my_dummy.search' } ]
# Tests if the id default to the service name, do not add id attributes here
diff --git a/tests/Fixtures/app/config/config_services_mongodb.yml b/tests/Fixtures/app/config/config_services_mongodb.yml
index 0bfe3e46953..af4098081d9 100644
--- a/tests/Fixtures/app/config/config_services_mongodb.yml
+++ b/tests/Fixtures/app/config/config_services_mongodb.yml
@@ -28,7 +28,7 @@ services:
tags: [ { name: 'api_platform.filter', id: 'my_dummy.mongodb.range' } ]
app.my_dummy_resource.mongodb.search_filter:
parent: 'api_platform.doctrine_mongodb.odm.search_filter'
- arguments: [ { 'id': 'exact', 'name': 'partial', 'alias': 'start', 'description': 'word_start', 'relatedDummy.name': 'exact', 'relatedDummies': 'exact', 'dummy': 'ipartial', 'relatedDummies.name': 'start', 'embeddedDummy.dummyName': 'partial', 'relatedDummy.thirdLevel.level': 'exact', 'relatedDummy.thirdLevel.fourthLevel.level': 'exact', 'relatedDummy.thirdLevel.badFourthLevel.level': 'exact', 'relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level': 'exact' } ]
+ arguments: [ { 'id': 'exact', 'name': 'partial', 'alias': 'start', 'description': 'word_start', 'relatedDummy.name': 'exact', 'relatedDummies': 'exact', 'dummy': 'ipartial', 'relatedDummies.name': 'start', 'embeddedDummy.dummyName': 'partial', 'relatedDummy.thirdLevel.level': 'exact', 'relatedDummy.thirdLevel.fourthLevel.level': 'exact', 'relatedDummy.thirdLevel.badFourthLevel.level': 'exact', 'relatedDummy.thirdLevel.fourthLevel.badThirdLevel.level': 'exact', 'nameConverted': 'partial' } ]
tags: [ { name: 'api_platform.filter', id: 'my_dummy.mongodb.search' } ]
app.related_dummy_resource.mongodb.search_filter:
parent: 'api_platform.doctrine_mongodb.odm.search_filter'
diff --git a/tests/Serializer/Filter/PropertyFilterTest.php b/tests/Serializer/Filter/PropertyFilterTest.php
index 2ee36d3fc59..81ecc75eb5d 100644
--- a/tests/Serializer/Filter/PropertyFilterTest.php
+++ b/tests/Serializer/Filter/PropertyFilterTest.php
@@ -15,6 +15,7 @@
use ApiPlatform\Core\Serializer\Filter\PropertyFilter;
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyProperty;
+use ApiPlatform\Core\Tests\Fixtures\TestBundle\Serializer\NameConverter\CustomConverter;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
@@ -66,7 +67,7 @@ public function testApplyWithPropertiesWhitelist()
$this->assertEquals(['attributes' => ['qux', 'foo', 'bar']], $context);
}
- public function testApplyWithPropertiesWhitelistWithNestedProperty()
+ public function testApplyWithPropertiesWhitelistAndNestedProperty()
{
$request = new Request(['properties' => ['foo', 'bar', 'group' => ['baz' => ['baz', 'qux'], 'qux']]]);
$context = ['attributes' => ['qux']];
@@ -88,7 +89,7 @@ public function testApplyWithPropertiesWhitelistNotMatchingAnyProperty()
$this->assertEquals(['attributes' => ['qux']], $context);
}
- public function testApplyWithoutPropertiesWhitelistWithOverriding()
+ public function testApplyWithPropertiesWhitelistAndOverriding()
{
$request = new Request(['properties' => ['foo', 'bar', 'baz']]);
$context = ['attributes' => ['qux']];
@@ -121,6 +122,93 @@ public function testApplyWithInvalidPropertiesInRequest()
$this->assertEquals(['attributes' => ['foo', 'bar']], $context);
}
+ public function testApplyWithNameConverter()
+ {
+ $request = new Request(['properties' => ['foo', 'bar', 'name_converted']]);
+ $context = ['attributes' => ['foo', 'name_converted']];
+
+ $propertyFilter = new PropertyFilter('properties', false, null, new CustomConverter());
+ $propertyFilter->apply($request, true, [], $context);
+
+ $this->assertEquals(['attributes' => ['foo', 'name_converted', 'foo', 'bar', 'nameConverted']], $context);
+ }
+
+ public function testApplyWithOverridingAndNameConverter()
+ {
+ $request = new Request(['custom_properties' => ['foo', 'bar', 'name_converted']]);
+ $context = ['attributes' => ['foo', 'qux']];
+
+ $propertyFilter = new PropertyFilter('custom_properties', true, null, new CustomConverter());
+ $propertyFilter->apply($request, false, [], $context);
+
+ $this->assertEquals(['attributes' => ['foo', 'bar', 'nameConverted']], $context);
+ }
+
+ public function testApplyWithoutPropertiesInRequestAndNameConverter()
+ {
+ $context = ['attributes' => ['foo', 'name_converted']];
+
+ $propertyFilter = new PropertyFilter('properties', false, null, new CustomConverter());
+ $propertyFilter->apply(new Request(), false, [], $context);
+
+ $this->assertEquals(['attributes' => ['foo', 'name_converted']], $context);
+ }
+
+ public function testApplyWithPropertiesWhitelistAndNameConverter()
+ {
+ $request = new Request(['properties' => ['foo', 'name_converted', 'baz']]);
+ $context = ['attributes' => ['qux']];
+
+ $propertyFilter = new PropertyFilter('properties', false, ['nameConverted', 'fuz', 'foo'], new CustomConverter());
+ $propertyFilter->apply($request, true, [], $context);
+
+ $this->assertEquals(['attributes' => ['qux', 'foo', 'nameConverted']], $context);
+ }
+
+ public function testApplyWithPropertiesWhitelistWithNestedPropertyAndNameConverter()
+ {
+ $request = new Request(['properties' => ['foo', 'bar', 'name_converted' => ['baz' => ['baz', 'name_converted'], 'qux']]]);
+ $context = ['attributes' => ['qux']];
+
+ $propertyFilter = new PropertyFilter('properties', false, ['foo' => null, 'nameConverted' => ['baz' => ['nameConverted']]], new CustomConverter());
+ $propertyFilter->apply($request, true, [], $context);
+
+ $this->assertEquals(['attributes' => ['qux', 'foo', 'nameConverted' => ['baz' => ['nameConverted']]]], $context);
+ }
+
+ public function testApplyWithPropertiesWhitelistNotMatchingAnyPropertyAndNameConverter()
+ {
+ $request = new Request(['properties' => ['foo', 'bar', 'name_converted' => ['baz' => ['baz', 'name_converted'], 'qux']]]);
+ $context = ['attributes' => ['qux']];
+
+ $propertyFilter = new PropertyFilter('properties', false, ['fuz', 'fiz'], new CustomConverter());
+ $propertyFilter->apply($request, true, [], $context);
+
+ $this->assertEquals(['attributes' => ['qux']], $context);
+ }
+
+ public function testApplyWithPropertiesWhitelistAndOverridingAndNameConverter()
+ {
+ $request = new Request(['properties' => ['foo', 'bar', 'name_converted']]);
+ $context = ['attributes' => ['qux']];
+
+ $propertyFilter = new PropertyFilter('properties', true, ['foo', 'nameConverted'], new CustomConverter());
+ $propertyFilter->apply($request, true, [], $context);
+
+ $this->assertEquals(['attributes' => ['foo', 'nameConverted']], $context);
+ }
+
+ public function testApplyWithPropertiesInPropertyFilterAttributeAndNameConverter()
+ {
+ $request = new Request(['properties' => ['foo', 'bar', 'baz']], [], ['_api_filters' => ['properties' => ['name_converted']]]);
+ $context = ['attributes' => ['foo', 'qux']];
+
+ $propertyFilter = new PropertyFilter('properties', false, null, new CustomConverter());
+ $propertyFilter->apply($request, true, [], $context);
+
+ $this->assertEquals(['attributes' => ['foo', 'qux', 'nameConverted']], $context);
+ }
+
public function testGetDescription()
{
$propertyFilter = new PropertyFilter('custom_properties');