From 38ee93fec770dd6f64bf81fcaba13829a1841ed9 Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 15 Oct 2026 09:30:33 +0200 Subject: [PATCH 1/3] test(doctrine): put-create under a slug parent link --- .../TestBundle/Entity/CompanyBadge.php | 44 +++++++++++++++++++ .../Parameters/LinkProviderParameterTest.php | 31 ++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 tests/Fixtures/TestBundle/Entity/CompanyBadge.php diff --git a/tests/Fixtures/TestBundle/Entity/CompanyBadge.php b/tests/Fixtures/TestBundle/Entity/CompanyBadge.php new file mode 100644 index 00000000000..3195011803f --- /dev/null +++ b/tests/Fixtures/TestBundle/Entity/CompanyBadge.php @@ -0,0 +1,44 @@ + + * + * 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\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Put; +use Doctrine\ORM\Mapping as ORM; + +#[ApiResource] +#[Get(uriTemplate: '/company_badges/{code}')] +#[Put( + uriTemplate: '/companies-by-name/{name}/badges/{code}', + allowCreate: true, + uriVariables: [ + 'name' => new Link(identifiers: ['name'], fromClass: Company::class, toProperty: 'company'), + 'code' => new Link(fromClass: self::class), + ], +)] +#[ORM\Entity] +class CompanyBadge +{ + #[ORM\Id] + #[ORM\Column] + public string $code; + + #[ORM\ManyToOne(targetEntity: Company::class)] + public ?Company $company = null; + + #[ORM\Column] + public string $label; +} diff --git a/tests/Functional/Parameters/LinkProviderParameterTest.php b/tests/Functional/Parameters/LinkProviderParameterTest.php index cda462cafc6..a25c6243601 100644 --- a/tests/Functional/Parameters/LinkProviderParameterTest.php +++ b/tests/Functional/Parameters/LinkProviderParameterTest.php @@ -21,6 +21,7 @@ use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\LinkParameterProviderResource; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\WithParameter; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Company; +use ApiPlatform\Tests\Fixtures\TestBundle\Entity\CompanyBadge; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Employee; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue7469Dummy; @@ -43,7 +44,7 @@ final class LinkProviderParameterTest extends ApiTestCase */ public static function getResources(): array { - return [WithParameter::class, Dummy::class, Employee::class, Company::class, LinkParameterProviderResource::class, Issue7469TestResource::class, Issue7469Dummy::class, Pairing::class, Plan::class, Issue7939FooResource::class, Issue7939BarResource::class, Issue7939BazResource::class]; + return [WithParameter::class, Dummy::class, Employee::class, Company::class, CompanyBadge::class, LinkParameterProviderResource::class, Issue7469TestResource::class, Issue7469Dummy::class, Pairing::class, Plan::class, Issue7939FooResource::class, Issue7939BarResource::class, Issue7939BazResource::class]; } /** @@ -51,7 +52,7 @@ public static function getResources(): array */ protected function setUp(): void { - $this->recreateSchema([Dummy::class, RelatedOwnedDummy::class, RelatedDummy::class, Employee::class, Company::class, Issue7469Dummy::class, Plan::class, Pairing::class]); + $this->recreateSchema([Dummy::class, RelatedOwnedDummy::class, RelatedDummy::class, Employee::class, Company::class, CompanyBadge::class, Issue7469Dummy::class, Plan::class, Pairing::class]); } public function testReadDummyProviderFromQueryParameter(): void @@ -147,6 +148,32 @@ public function testLinkSecurityWithSlug(): void self::assertResponseStatusCodeSame(200); } + public function testPutCreateUnderSlugParentLink(): void + { + $container = static::getContainer(); + if ('mongodb' === $container->getParameter('kernel.environment')) { + $this->markTestSkipped(); + } + + $manager = $this->getManager(); + $company = new Company(); + $company->setName('Test'); + $manager->persist($company); + $manager->flush(); + $companyId = $company->getId(); + + self::createClient()->request('PUT', '/companies-by-name/Test/badges/gold', [ + 'headers' => ['Content-Type' => 'application/ld+json'], + 'json' => ['label' => 'Gold'], + ]); + self::assertResponseStatusCodeSame(201); + + $manager->clear(); + $badge = $manager->getRepository(CompanyBadge::class)->find('gold'); + self::assertNotNull($badge); + self::assertSame($companyId, $badge->company->getId()); + } + /** * See https://github.com/api-platform/core/issues/7061. */ From 54ba3cf219a47c11596123c2c6819ce7777785a6 Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 15 Oct 2026 09:31:10 +0200 Subject: [PATCH 2/3] fix(doctrine): put-create under a slug parent link --- src/Doctrine/Common/State/PersistProcessor.php | 10 +++++++++- .../Common/Tests/State/PersistProcessorTest.php | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Doctrine/Common/State/PersistProcessor.php b/src/Doctrine/Common/State/PersistProcessor.php index d1ad748c39a..c9530d0e1f7 100644 --- a/src/Doctrine/Common/State/PersistProcessor.php +++ b/src/Doctrine/Common/State/PersistProcessor.php @@ -102,7 +102,15 @@ public function process(mixed $data, Operation $operation, array $uriVariables = $referenceIdentifier = $this->getIdentifierValue($identifiers); } - $reflectionProperties[$toProperty]->setValue($newData, $parentManager->getReference($fromClass, $referenceIdentifier)); + $doctrineIdentifiers = $parentManager->getClassMetadata($fromClass)->getIdentifierFieldNames(); + if (!array_diff($identifierProperties, $doctrineIdentifiers) && !array_diff($doctrineIdentifiers, $identifierProperties)) { + $parent = $parentManager->getReference($fromClass, $referenceIdentifier); + } else { + $criteria = $hasCompositeIdentifiers ? $referenceIdentifier : [$identifierProperties[0] => $referenceIdentifier]; + $parent = $parentManager->getRepository($fromClass)->findOneBy($criteria); + } + + $reflectionProperties[$toProperty]->setValue($newData, $parent); continue; } diff --git a/src/Doctrine/Common/Tests/State/PersistProcessorTest.php b/src/Doctrine/Common/Tests/State/PersistProcessorTest.php index 8f9830967f9..9d0408eb8e5 100644 --- a/src/Doctrine/Common/Tests/State/PersistProcessorTest.php +++ b/src/Doctrine/Common/Tests/State/PersistProcessorTest.php @@ -220,7 +220,11 @@ public function testPersistPutCreateResolvesParentLinkViaToProperty(): void $deviceManagerProphecy->flush()->shouldBeCalled(); $deviceManagerProphecy->refresh($device)->shouldBeCalled(); + $userClassMetadata = new ORMClassMetadata(PersistProcessorTestUserStub::class); + $userClassMetadata->identifier = ['id']; + $userManagerProphecy = $this->prophesize(EntityManagerInterface::class); + $userManagerProphecy->getClassMetadata(PersistProcessorTestUserStub::class)->willReturn($userClassMetadata); $userManagerProphecy->getReference(PersistProcessorTestUserStub::class, 'user-uuid') ->willReturn($userReference) ->shouldBeCalledTimes(1); From 35a7fdaf18a86e276a9b0696a2fb9cfa4ec1b7f0 Mon Sep 17 00:00:00 2001 From: soyuka Date: Fri, 16 Oct 2026 10:49:21 +0200 Subject: [PATCH 3/3] test(doctrine): add the ODM CompanyBadge fixture --- .../TestBundle/Document/CompanyBadge.php | 43 +++++++++++++++++++ .../Parameters/LinkProviderParameterTest.php | 12 +++--- 2 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 tests/Fixtures/TestBundle/Document/CompanyBadge.php diff --git a/tests/Fixtures/TestBundle/Document/CompanyBadge.php b/tests/Fixtures/TestBundle/Document/CompanyBadge.php new file mode 100644 index 00000000000..6a8fb9ce760 --- /dev/null +++ b/tests/Fixtures/TestBundle/Document/CompanyBadge.php @@ -0,0 +1,43 @@ + + * + * 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\ApiResource; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Put; +use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; + +#[ApiResource] +#[Get(uriTemplate: '/company_badges/{code}')] +#[Put( + uriTemplate: '/companies-by-name/{name}/badges/{code}', + allowCreate: true, + uriVariables: [ + 'name' => new Link(identifiers: ['name'], fromClass: Company::class, toProperty: 'company'), + 'code' => new Link(fromClass: self::class), + ], +)] +#[ODM\Document] +class CompanyBadge +{ + #[ODM\Id(type: 'string', strategy: 'NONE')] + public string $code; + + #[ODM\ReferenceOne(targetDocument: Company::class, storeAs: 'id')] + public ?Company $company = null; + + #[ODM\Field] + public string $label; +} diff --git a/tests/Functional/Parameters/LinkProviderParameterTest.php b/tests/Functional/Parameters/LinkProviderParameterTest.php index a25c6243601..5aa78f505f4 100644 --- a/tests/Functional/Parameters/LinkProviderParameterTest.php +++ b/tests/Functional/Parameters/LinkProviderParameterTest.php @@ -20,6 +20,8 @@ use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7939FooResource; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\LinkParameterProviderResource; use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\WithParameter; +use ApiPlatform\Tests\Fixtures\TestBundle\Document\Company as DocumentCompany; +use ApiPlatform\Tests\Fixtures\TestBundle\Document\CompanyBadge as DocumentCompanyBadge; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Company; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\CompanyBadge; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Dummy; @@ -150,13 +152,11 @@ public function testLinkSecurityWithSlug(): void public function testPutCreateUnderSlugParentLink(): void { - $container = static::getContainer(); - if ('mongodb' === $container->getParameter('kernel.environment')) { - $this->markTestSkipped(); - } + $companyClass = $this->isMongoDB() ? DocumentCompany::class : Company::class; + $badgeClass = $this->isMongoDB() ? DocumentCompanyBadge::class : CompanyBadge::class; $manager = $this->getManager(); - $company = new Company(); + $company = new $companyClass(); $company->setName('Test'); $manager->persist($company); $manager->flush(); @@ -169,7 +169,7 @@ public function testPutCreateUnderSlugParentLink(): void self::assertResponseStatusCodeSame(201); $manager->clear(); - $badge = $manager->getRepository(CompanyBadge::class)->find('gold'); + $badge = $manager->getRepository($badgeClass)->find('gold'); self::assertNotNull($badge); self::assertSame($companyId, $badge->company->getId()); }