diff --git a/src/Doctrine/Common/State/PersistProcessor.php b/src/Doctrine/Common/State/PersistProcessor.php index d1ad748c39..c9530d0e1f 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 8f9830967f..9d0408eb8e 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); diff --git a/tests/Fixtures/TestBundle/Document/CompanyBadge.php b/tests/Fixtures/TestBundle/Document/CompanyBadge.php new file mode 100644 index 0000000000..6a8fb9ce76 --- /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/Fixtures/TestBundle/Entity/CompanyBadge.php b/tests/Fixtures/TestBundle/Entity/CompanyBadge.php new file mode 100644 index 0000000000..3195011803 --- /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 cda462cafc..5aa78f505f 100644 --- a/tests/Functional/Parameters/LinkProviderParameterTest.php +++ b/tests/Functional/Parameters/LinkProviderParameterTest.php @@ -20,7 +20,10 @@ 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; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Employee; use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Issue7469Dummy; @@ -43,7 +46,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 +54,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 +150,30 @@ public function testLinkSecurityWithSlug(): void self::assertResponseStatusCodeSame(200); } + public function testPutCreateUnderSlugParentLink(): void + { + $companyClass = $this->isMongoDB() ? DocumentCompany::class : Company::class; + $badgeClass = $this->isMongoDB() ? DocumentCompanyBadge::class : CompanyBadge::class; + + $manager = $this->getManager(); + $company = new $companyClass(); + $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($badgeClass)->find('gold'); + self::assertNotNull($badge); + self::assertSame($companyId, $badge->company->getId()); + } + /** * See https://github.com/api-platform/core/issues/7061. */