From 2374ebcb0062e5705f47423771f3e62df75856d5 Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 12 Jun 2026 14:23:47 +0200 Subject: [PATCH] fix(doctrine): filter parent link from uri variables in fetch_data=false reference getReference threw UnrecognizedIdentifierFields on subresource IRIs because the parent link (e.g. companyId) was passed as an entity identifier. Only short-circuit to getReference when all own identifiers are present; otherwise fall through to a query that resolves the link. Fixes #8124 --- src/Doctrine/Orm/State/ItemProvider.php | 14 +++++++-- .../Orm/Tests/State/ItemProviderTest.php | 30 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/Doctrine/Orm/State/ItemProvider.php b/src/Doctrine/Orm/State/ItemProvider.php index b201d03b7d0..ae92525d588 100644 --- a/src/Doctrine/Orm/State/ItemProvider.php +++ b/src/Doctrine/Orm/State/ItemProvider.php @@ -59,9 +59,17 @@ public function provide(Operation $operation, array $uriVariables = [], array $c } $fetchData = $context['fetch_data'] ?? true; - if (!$fetchData && \array_key_exists('id', $uriVariables)) { - // todo : if uriVariables don't contain the id, this fails. This should behave like it does in the following code - return $manager->getReference($entityClass, $uriVariables); + if (!$fetchData) { + $identifierFields = $manager->getClassMetadata($entityClass)->getIdentifierFieldNames(); + $identifiers = array_intersect_key($uriVariables, array_flip($identifierFields)); + + // Subresources also carry the parent link (e.g. "companyId"), which is not an identifier of + // the entity and would make getReference() throw UnrecognizedIdentifierFields. Only build a + // reference when every own identifier is present; otherwise fall through to a query that + // resolves the link. + if (\count($identifiers) === \count($identifierFields)) { + return $manager->getReference($entityClass, $identifiers); + } } $repository = $manager->getRepository($entityClass); diff --git a/src/Doctrine/Orm/Tests/State/ItemProviderTest.php b/src/Doctrine/Orm/Tests/State/ItemProviderTest.php index 168939cc047..3a6f140342a 100644 --- a/src/Doctrine/Orm/Tests/State/ItemProviderTest.php +++ b/src/Doctrine/Orm/Tests/State/ItemProviderTest.php @@ -138,6 +138,36 @@ public function testGetItemDoubleIdentifier(): void $this->assertEquals($returnObject, $dataProvider->provide($operation, ['ida' => 1, 'idb' => 2], $context)); } + public function testGetItemWithFetchDataFalseOnSubresourceFiltersParentLink(): void + { + $reference = new Employee(); + + $classMetadataMock = $this->createMock(ClassMetadata::class); + $classMetadataMock->method('getIdentifierFieldNames')->willReturn(['id']); + + $managerMock = $this->createMock(EntityManagerInterface::class); + $managerMock->method('getClassMetadata')->with(Employee::class)->willReturn($classMetadataMock); + $managerMock->expects($this->once()) + ->method('getReference') + ->with(Employee::class, ['id' => 2]) + ->willReturn($reference); + + $managerRegistryMock = $this->createMock(ManagerRegistry::class); + $managerRegistryMock->method('getManagerForClass')->with(Employee::class)->willReturn($managerMock); + + $operation = (new Get())->withUriVariables([ + 'companyId' => (new Link())->withFromClass(Company::class)->withToProperty('company'), + 'id' => (new Link())->withFromClass(Employee::class)->withIdentifiers(['id']), + ])->withName('get')->withClass(Employee::class); + + $dataProvider = new ItemProvider( + $this->createStub(ResourceMetadataCollectionFactoryInterface::class), + $managerRegistryMock, + ); + + $this->assertSame($reference, $dataProvider->provide($operation, ['companyId' => 1, 'id' => 2], ['fetch_data' => false])); + } + public function testQueryResultExtension(): void { $returnObject = new \stdClass();