diff --git a/features/hal/collection_uri_template.feature b/features/hal/collection_uri_template.feature
index 0af300f6c66..a71c2d9329c 100644
--- a/features/hal/collection_uri_template.feature
+++ b/features/hal/collection_uri_template.feature
@@ -36,9 +36,23 @@ Feature: Exposing a property being a collection of resources
"_links": {
"self": {
"href": "/property_collection_iri_only_relations/1"
+ },
+ "children": {
+ "href": "/property_collection_iri_only_relations/1/children"
}
},
- "name": "asb"
+ "name": "asb1"
+ },
+ {
+ "_links": {
+ "self": {
+ "href": "/property_collection_iri_only_relations/2"
+ },
+ "children": {
+ "href": "/property_collection_iri_only_relations/2/children"
+ }
+ },
+ "name": "asb2"
}
],
"iterableIri": [
@@ -46,6 +60,9 @@ Feature: Exposing a property being a collection of resources
"_links": {
"self": {
"href": "/property_collection_iri_only_relations/9999"
+ },
+ "children": {
+ "href": "/property_collection_iri_only_relations/9999/children"
}
},
"name": "Michel"
diff --git a/features/jsonapi/collection_uri_template.feature b/features/jsonapi/collection_uri_template.feature
index 188895d0e7a..6fa13c00732 100644
--- a/features/jsonapi/collection_uri_template.feature
+++ b/features/jsonapi/collection_uri_template.feature
@@ -33,6 +33,10 @@ Feature: Exposing a property being a collection of resources
{
"type": "PropertyCollectionIriOnlyRelation",
"id": "/property_collection_iri_only_relations/1"
+ },
+ {
+ "type": "PropertyCollectionIriOnlyRelation",
+ "id": "/property_collection_iri_only_relations/2"
}
]
},
diff --git a/src/Doctrine/EventListener/PurgeHttpCacheListener.php b/src/Doctrine/EventListener/PurgeHttpCacheListener.php
index 7aba69f65f5..fa7b081e4dc 100644
--- a/src/Doctrine/EventListener/PurgeHttpCacheListener.php
+++ b/src/Doctrine/EventListener/PurgeHttpCacheListener.php
@@ -24,7 +24,6 @@
use ApiPlatform\Metadata\ResourceClassResolverInterface;
use ApiPlatform\Metadata\UrlGeneratorInterface;
use ApiPlatform\Metadata\Util\ClassInfoTrait;
-use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
@@ -60,7 +59,7 @@ public function preUpdate(PreUpdateEventArgs $eventArgs): void
$changeSet = $eventArgs->getEntityChangeSet();
// @phpstan-ignore-next-line
$objectManager = method_exists($eventArgs, 'getObjectManager') ? $eventArgs->getObjectManager() : $eventArgs->getEntityManager();
- $associationMappings = $objectManager->getClassMetadata(ClassUtils::getClass($eventArgs->getObject()))->getAssociationMappings();
+ $associationMappings = $objectManager->getClassMetadata(\get_class($eventArgs->getObject()))->getAssociationMappings();
foreach ($changeSet as $key => $value) {
if (!isset($associationMappings[$key])) {
@@ -127,7 +126,7 @@ private function gatherResourceAndItemTags(object $entity, bool $purgeItem): voi
private function gatherRelationTags(EntityManagerInterface $em, object $entity): void
{
- $associationMappings = $em->getClassMetadata(ClassUtils::getClass($entity))->getAssociationMappings();
+ $associationMappings = $em->getClassMetadata($entity::class)->getAssociationMappings();
/** @var array|AssociationMapping $associationMapping according to the version of doctrine orm */
foreach ($associationMappings as $property => $associationMapping) {
if ($associationMapping instanceof AssociationMapping && ($associationMapping->targetEntity ?? null) && !$this->resourceClassResolver->isResourceClass($associationMapping->targetEntity)) {
diff --git a/src/GraphQl/State/Provider/ResolverProvider.php b/src/GraphQl/State/Provider/ResolverProvider.php
index 05dcb785c3e..1b6f1fe77f3 100644
--- a/src/GraphQl/State/Provider/ResolverProvider.php
+++ b/src/GraphQl/State/Provider/ResolverProvider.php
@@ -69,7 +69,7 @@ private function getResourceClass(?object $item, ?string $resourceClass, string
return $itemClass;
}
- if ($resourceClass !== $itemClass) {
+ if ($resourceClass !== $itemClass && !$item instanceof $resourceClass) {
throw new \UnexpectedValueException(sprintf($errorMessage, (new \ReflectionClass($resourceClass))->getShortName(), (new \ReflectionClass($itemClass))->getShortName()));
}
diff --git a/src/GraphQl/Tests/State/Provider/ResolverProviderTest.php b/src/GraphQl/Tests/State/Provider/ResolverProviderTest.php
index 75d8f698ac9..64d9e9b8220 100644
--- a/src/GraphQl/Tests/State/Provider/ResolverProviderTest.php
+++ b/src/GraphQl/Tests/State/Provider/ResolverProviderTest.php
@@ -15,6 +15,9 @@
use ApiPlatform\GraphQl\Resolver\QueryItemResolverInterface;
use ApiPlatform\GraphQl\State\Provider\ResolverProvider;
+use ApiPlatform\GraphQl\Tests\Fixtures\ApiResource\ChildFoo;
+use ApiPlatform\GraphQl\Tests\Fixtures\ApiResource\ParentFoo;
+use ApiPlatform\Metadata\GraphQl\Query;
use ApiPlatform\Metadata\GraphQl\QueryCollection;
use ApiPlatform\State\ProviderInterface;
use PHPUnit\Framework\TestCase;
@@ -35,4 +38,18 @@ public function testProvide(): void
$provider = new ResolverProvider($decorated, $resolverLocator);
$this->assertEquals($res, $provider->provide($operation, [], $context));
}
+
+ public function testProvideInheritedClass(): void
+ {
+ $res = new ChildFoo();
+ $operation = new Query(class: ParentFoo::class, resolver: 'foo');
+ $context = [];
+ $decorated = $this->createMock(ProviderInterface::class);
+ $resolverMock = $this->createMock(QueryItemResolverInterface::class);
+ $resolverMock->expects($this->once())->method('__invoke')->willReturn($res);
+ $resolverLocator = $this->createMock(ContainerInterface::class);
+ $resolverLocator->expects($this->once())->method('get')->with('foo')->willReturn($resolverMock);
+ $provider = new ResolverProvider($decorated, $resolverLocator);
+ $this->assertEquals($res, $provider->provide($operation, [], $context));
+ }
}
diff --git a/src/Hal/Serializer/ItemNormalizer.php b/src/Hal/Serializer/ItemNormalizer.php
index ee2758f0bb5..549dc146341 100644
--- a/src/Hal/Serializer/ItemNormalizer.php
+++ b/src/Hal/Serializer/ItemNormalizer.php
@@ -186,6 +186,7 @@ private function getComponents(object $object, ?string $format, array $context):
$relation['iri'] = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $operation, $childContext);
$relation['operation'] = $operation;
+ $cacheKey = null;
}
if ($propertyMetadata->isReadableLink()) {
@@ -202,7 +203,7 @@ private function getComponents(object $object, ?string $format, array $context):
}
}
- if (false !== $context['cache_key']) {
+ if ($cacheKey && false !== $context['cache_key']) {
$this->componentsCache[$cacheKey] = $components;
}
diff --git a/src/Metadata/Extractor/XmlResourceExtractor.php b/src/Metadata/Extractor/XmlResourceExtractor.php
index 3211518cc3f..410b59ca445 100644
--- a/src/Metadata/Extractor/XmlResourceExtractor.php
+++ b/src/Metadata/Extractor/XmlResourceExtractor.php
@@ -408,6 +408,7 @@ private function buildOperations(\SimpleXMLElement $resource, array $root): ?arr
'queryParameterValidate' => $this->phpize($operation, 'queryParameterValidate', 'bool'),
'priority' => $this->phpize($operation, 'priority', 'integer'),
'name' => $this->phpize($operation, 'name', 'string'),
+ 'routeName' => $this->phpize($operation, 'routeName', 'string'),
]);
}
diff --git a/src/Metadata/Extractor/schema/resources.xsd b/src/Metadata/Extractor/schema/resources.xsd
index 6121622d758..6c460cf9aa8 100644
--- a/src/Metadata/Extractor/schema/resources.xsd
+++ b/src/Metadata/Extractor/schema/resources.xsd
@@ -48,6 +48,7 @@
+
diff --git a/src/Metadata/Tests/Extractor/XmlExtractorTest.php b/src/Metadata/Tests/Extractor/XmlExtractorTest.php
index be195c2b976..be785206c8e 100644
--- a/src/Metadata/Tests/Extractor/XmlExtractorTest.php
+++ b/src/Metadata/Tests/Extractor/XmlExtractorTest.php
@@ -278,6 +278,7 @@ public function testValidXML(): void
'links' => null,
'headers' => ['hello' => 'world'],
'parameters' => null,
+ 'routeName' => 'custom_route_name',
],
[
'name' => null,
@@ -389,6 +390,7 @@ public function testValidXML(): void
extraProperties: ['foo' => 'bar']
),
],
+ 'routeName' => null,
],
],
'graphQlOperations' => null,
diff --git a/src/Metadata/Tests/Extractor/xml/valid.xml b/src/Metadata/Tests/Extractor/xml/valid.xml
index a725e652894..81606a1f409 100644
--- a/src/Metadata/Tests/Extractor/xml/valid.xml
+++ b/src/Metadata/Tests/Extractor/xml/valid.xml
@@ -99,7 +99,7 @@
-
+
diff --git a/tests/.ignored-deprecations b/tests/.ignored-deprecations
index 6b4de3448a5..2774847dfcd 100644
--- a/tests/.ignored-deprecations
+++ b/tests/.ignored-deprecations
@@ -10,3 +10,5 @@
# Fixed when ApiPlatform\Api\FilterLocatorTrait will we deleted
%ApiPlatform\\Api\\FilterInterface is deprecated in favor of ApiPlatform\\Metadata\\FilterInterface%
+
+%The "Symfony\\Bundle\\MakerBundle\\Maker\\MakeAuthenticator" class is deprecated, use any of the Security\\Make\* commands instead%
diff --git a/tests/.ignored-deprecations-legacy-events b/tests/.ignored-deprecations-legacy-events
index 74dea1488eb..5dcd9cdd7d5 100644
--- a/tests/.ignored-deprecations-legacy-events
+++ b/tests/.ignored-deprecations-legacy-events
@@ -23,3 +23,5 @@
# Fixed when ApiPlatform\Api\FilterLocatorTrait will we deleted
%ApiPlatform\\Api\\FilterInterface is deprecated in favor of ApiPlatform\\Metadata\\FilterInterface%
+
+%The "Symfony\\Bundle\\MakerBundle\\Maker\\MakeAuthenticator" class is deprecated, use any of the Security\\Make\* commands instead%
diff --git a/tests/Behat/DoctrineContext.php b/tests/Behat/DoctrineContext.php
index 8894e737d37..90c9d183dcf 100644
--- a/tests/Behat/DoctrineContext.php
+++ b/tests/Behat/DoctrineContext.php
@@ -2019,18 +2019,23 @@ public function thereAreIriOnlyDummies(int $nb): void
*/
public function thereAreResourcesWithPropertyUriTemplates(): void
{
- $propertyCollectionIriOnlyRelation = $this->isOrm() ? new PropertyCollectionIriOnlyRelation() : new PropertyCollectionIriOnlyRelationDocument();
- $propertyCollectionIriOnlyRelation->name = 'asb';
+ $propertyCollectionIriOnlyRelation1 = $this->isOrm() ? new PropertyCollectionIriOnlyRelation() : new PropertyCollectionIriOnlyRelationDocument();
+ $propertyCollectionIriOnlyRelation1->name = 'asb1';
+
+ $propertyCollectionIriOnlyRelation2 = $this->isOrm() ? new PropertyCollectionIriOnlyRelation() : new PropertyCollectionIriOnlyRelationDocument();
+ $propertyCollectionIriOnlyRelation2->name = 'asb2';
$propertyToOneRelation = $this->isOrm() ? new PropertyUriTemplateOneToOneRelation() : new PropertyUriTemplateOneToOneRelationDocument();
$propertyToOneRelation->name = 'xarguš';
$propertyCollectionIriOnly = $this->isOrm() ? new PropertyCollectionIriOnly() : new PropertyCollectionIriOnlyDocument();
- $propertyCollectionIriOnly->addPropertyCollectionIriOnlyRelation($propertyCollectionIriOnlyRelation);
+ $propertyCollectionIriOnly->addPropertyCollectionIriOnlyRelation($propertyCollectionIriOnlyRelation1);
+ $propertyCollectionIriOnly->addPropertyCollectionIriOnlyRelation($propertyCollectionIriOnlyRelation2);
$propertyCollectionIriOnly->setToOneRelation($propertyToOneRelation);
$this->manager->persist($propertyCollectionIriOnly);
- $this->manager->persist($propertyCollectionIriOnlyRelation);
+ $this->manager->persist($propertyCollectionIriOnlyRelation1);
+ $this->manager->persist($propertyCollectionIriOnlyRelation2);
$this->manager->persist($propertyToOneRelation);
$this->manager->flush();
}
diff --git a/tests/Fixtures/TestBundle/Document/PropertyCollectionIriOnlyRelation.php b/tests/Fixtures/TestBundle/Document/PropertyCollectionIriOnlyRelation.php
index a7fcbd46d8d..af90a375e5e 100644
--- a/tests/Fixtures/TestBundle/Document/PropertyCollectionIriOnlyRelation.php
+++ b/tests/Fixtures/TestBundle/Document/PropertyCollectionIriOnlyRelation.php
@@ -13,9 +13,12 @@
namespace ApiPlatform\Tests\Fixtures\TestBundle\Document;
+use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Post;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Symfony\Component\Serializer\Annotation\Groups;
@@ -42,6 +45,16 @@ class PropertyCollectionIriOnlyRelation
#[ODM\ReferenceOne(targetDocument: PropertyCollectionIriOnly::class)]
private ?PropertyCollectionIriOnly $propertyCollectionIriOnly = null;
+ #[ODM\ReferenceMany(targetDocument: PropertyCollectionIriOnlyRelationSecondLevel::class)]
+ #[ApiProperty(uriTemplate: '/property_collection_iri_only_relations/{parentId}/children')]
+ #[Groups('read')]
+ private Collection $children;
+
+ public function __construct()
+ {
+ $this->children = new ArrayCollection();
+ }
+
public function getId(): ?int
{
return $this->id ?? 9999;
@@ -56,4 +69,34 @@ public function setPropertyCollectionIriOnly(?PropertyCollectionIriOnly $propert
{
$this->propertyCollectionIriOnly = $propertyCollectionIriOnly;
}
+
+ /**
+ * @return Collection
+ */
+ public function getChildren(): Collection
+ {
+ return $this->children;
+ }
+
+ public function addChild(PropertyCollectionIriOnlyRelationSecondLevel $child): self
+ {
+ if (!$this->children->contains($child)) {
+ $this->children->add($child);
+ $child->setParent($this);
+ }
+
+ return $this;
+ }
+
+ public function removeChild(PropertyCollectionIriOnlyRelationSecondLevel $child): self
+ {
+ if ($this->children->removeElement($child)) {
+ // set the owning side to null (unless already changed)
+ if ($child->getParent() === $this) {
+ $child->setParent(null);
+ }
+ }
+
+ return $this;
+ }
}
diff --git a/tests/Fixtures/TestBundle/Document/PropertyCollectionIriOnlyRelationSecondLevel.php b/tests/Fixtures/TestBundle/Document/PropertyCollectionIriOnlyRelationSecondLevel.php
new file mode 100644
index 00000000000..b190aa0a94e
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Document/PropertyCollectionIriOnlyRelationSecondLevel.php
@@ -0,0 +1,54 @@
+
+ *
+ * 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\Tests\Fixtures\TestBundle\Document;
+
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Link;
+use ApiPlatform\Metadata\Post;
+use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
+
+#[
+ Post,
+ GetCollection(uriTemplate: '/property-collection-relation-second-levels'),
+ GetCollection(
+ uriTemplate: '/property_collection_iri_only_relations/{parentId}/children',
+ uriVariables: [
+ 'parentId' => new Link(toProperty: 'parent', fromClass: PropertyCollectionIriOnlyRelation::class),
+ ]
+ )
+]
+#[ODM\Document]
+class PropertyCollectionIriOnlyRelationSecondLevel
+{
+ #[ODM\Id(strategy: 'INCREMENT', type: 'int')]
+ private ?int $id = null;
+
+ #[ODM\ReferenceOne(targetDocument: PropertyCollectionIriOnlyRelation::class)]
+ private ?PropertyCollectionIriOnlyRelation $parent = null;
+
+ public function getId(): ?int
+ {
+ return $this->id ?? 9999;
+ }
+
+ public function getParent(): ?PropertyCollectionIriOnlyRelation
+ {
+ return $this->parent;
+ }
+
+ public function setParent(?PropertyCollectionIriOnlyRelation $parent): void
+ {
+ $this->parent = $parent;
+ }
+}
diff --git a/tests/Fixtures/TestBundle/Entity/PropertyCollectionIriOnlyRelation.php b/tests/Fixtures/TestBundle/Entity/PropertyCollectionIriOnlyRelation.php
index ebc12163c3b..4eae5c6a749 100644
--- a/tests/Fixtures/TestBundle/Entity/PropertyCollectionIriOnlyRelation.php
+++ b/tests/Fixtures/TestBundle/Entity/PropertyCollectionIriOnlyRelation.php
@@ -13,9 +13,12 @@
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
+use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Post;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints\NotBlank;
@@ -49,6 +52,16 @@ class PropertyCollectionIriOnlyRelation
#[ORM\ManyToOne(inversedBy: 'propertyCollectionIriOnlyRelation')]
private ?PropertyCollectionIriOnly $propertyCollectionIriOnly = null;
+ #[ORM\OneToMany(mappedBy: 'parent', targetEntity: PropertyCollectionIriOnlyRelationSecondLevel::class)]
+ #[ApiProperty(uriTemplate: '/property_collection_iri_only_relations/{parentId}/children')]
+ #[Groups('read')]
+ private Collection $children;
+
+ public function __construct()
+ {
+ $this->children = new ArrayCollection();
+ }
+
public function getId(): ?int
{
return $this->id ?? 9999;
@@ -63,4 +76,34 @@ public function setPropertyCollectionIriOnly(?PropertyCollectionIriOnly $propert
{
$this->propertyCollectionIriOnly = $propertyCollectionIriOnly;
}
+
+ /**
+ * @return Collection
+ */
+ public function getChildren(): Collection
+ {
+ return $this->children;
+ }
+
+ public function addChild(PropertyCollectionIriOnlyRelationSecondLevel $child): self
+ {
+ if (!$this->children->contains($child)) {
+ $this->children->add($child);
+ $child->setParent($this);
+ }
+
+ return $this;
+ }
+
+ public function removeChild(PropertyCollectionIriOnlyRelationSecondLevel $child): self
+ {
+ if ($this->children->removeElement($child)) {
+ // set the owning side to null (unless already changed)
+ if ($child->getParent() === $this) {
+ $child->setParent(null);
+ }
+ }
+
+ return $this;
+ }
}
diff --git a/tests/Fixtures/TestBundle/Entity/PropertyCollectionIriOnlyRelationSecondLevel.php b/tests/Fixtures/TestBundle/Entity/PropertyCollectionIriOnlyRelationSecondLevel.php
new file mode 100644
index 00000000000..7522ee8da46
--- /dev/null
+++ b/tests/Fixtures/TestBundle/Entity/PropertyCollectionIriOnlyRelationSecondLevel.php
@@ -0,0 +1,59 @@
+
+ *
+ * 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\Tests\Fixtures\TestBundle\Entity;
+
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Link;
+use ApiPlatform\Metadata\Post;
+use Doctrine\ORM\Mapping as ORM;
+
+#[
+ Post,
+ GetCollection(uriTemplate: '/property-collection-relation-second-levels'),
+ GetCollection(
+ uriTemplate: '/property_collection_iri_only_relations/{parentId}/children',
+ uriVariables: [
+ 'parentId' => new Link(toProperty: 'parent', fromClass: PropertyCollectionIriOnlyRelation::class),
+ ]
+ )
+]
+#[ORM\Entity]
+class PropertyCollectionIriOnlyRelationSecondLevel
+{
+ /**
+ * The entity ID.
+ */
+ #[ORM\Id]
+ #[ORM\Column(type: 'integer')]
+ #[ORM\GeneratedValue]
+ private ?int $id = null;
+
+ #[ORM\ManyToOne(inversedBy: 'children')]
+ private ?PropertyCollectionIriOnlyRelation $parent = null;
+
+ public function getId(): ?int
+ {
+ return $this->id ?? 9999;
+ }
+
+ public function getParent(): ?PropertyCollectionIriOnlyRelation
+ {
+ return $this->parent;
+ }
+
+ public function setParent(?PropertyCollectionIriOnlyRelation $parent): void
+ {
+ $this->parent = $parent;
+ }
+}