From 28c9717195c2cb71736d295e504c3f51bd86b4f8 Mon Sep 17 00:00:00 2001 From: Divine <48183131+divine@users.noreply.github.com> Date: Mon, 10 Jul 2023 23:53:32 +0300 Subject: [PATCH 1/4] fix: odm subresources relation link --- src/Doctrine/Odm/State/LinksHandlerTrait.php | 49 +++++++++- .../Fixtures/TestBundle/Document/Analysis.php | 97 +++++++++++++++++++ tests/Fixtures/TestBundle/Document/Study.php | 81 ++++++++++++++++ 3 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 tests/Fixtures/TestBundle/Document/Analysis.php create mode 100644 tests/Fixtures/TestBundle/Document/Study.php diff --git a/src/Doctrine/Odm/State/LinksHandlerTrait.php b/src/Doctrine/Odm/State/LinksHandlerTrait.php index 287ba13dac8..97dd871f9b1 100644 --- a/src/Doctrine/Odm/State/LinksHandlerTrait.php +++ b/src/Doctrine/Odm/State/LinksHandlerTrait.php @@ -20,6 +20,7 @@ use Doctrine\ODM\MongoDB\Aggregation\Builder; use Doctrine\ODM\MongoDB\DocumentManager; use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\ODM\MongoDB\Types\Type; trait LinksHandlerTrait { @@ -66,6 +67,8 @@ private function buildAggregation(string $toClass, array $links, array $identifi $identifierProperties = $link->getIdentifiers(); $hasCompositeIdentifiers = 1 < \count($identifierProperties); + $hasAssociation = false; + $aggregationClass = $fromClass; if ($toProperty) { $aggregationClass = $toClass; @@ -91,15 +94,30 @@ private function buildAggregation(string $toClass, array $links, array $identifi if ($lookupProperty && $classMetadata->hasAssociation($lookupProperty)) { $aggregation->lookup($lookupProperty)->alias($lookupPropertyAlias); + $hasAssociation = true; } if ($toProperty) { foreach ($identifierProperties as $identifierProperty) { - $aggregation->match()->field(sprintf('%s.%s', $lookupPropertyAlias, 'id' === $identifierProperty ? '_id' : $identifierProperty))->equals($this->getIdentifierValue($identifiers, $hasCompositeIdentifiers ? $identifierProperty : null)); + $aggregation->match()->field(sprintf('%s.%s', $lookupPropertyAlias, 'id' === $identifierProperty ? '_id' : $identifierProperty))->equals( + $this->getFieldValue( + $hasAssociation, + $classMetadata, + $identifierProperty, + $this->getIdentifierValue($identifiers, $hasCompositeIdentifiers ? $identifierProperty : null) + ) + ); } } else { foreach ($identifierProperties as $identifierProperty) { - $aggregation->match()->field($identifierProperty)->equals($this->getIdentifierValue($identifiers, $hasCompositeIdentifiers ? $identifierProperty : null)); + $aggregation->match()->field($identifierProperty)->equals( + $this->getFieldValue( + $hasAssociation, + $classMetadata, + $identifierProperty, + $this->getIdentifierValue($identifiers, $hasCompositeIdentifiers ? $identifierProperty : null) + ) + ); } } @@ -121,4 +139,31 @@ private function buildAggregation(string $toClass, array $links, array $identifi return $previousAggregationBuilder; } + + private function getFieldValue($hasAssociation, $classMetadata, $property, $value) + { + if ($hasAssociation) { + return $this->getFieldType( + $classMetadata->getTypeOfField($property), + $value + ); + } + + return $value; + } + + private function getFieldType($type, $value) + { + if (null === $type) { + return $value; + } + if (!Type::hasType($type)) { + return $value; + } + if (Type::STRING !== $type) { + return Type::getType($type)->convertToDatabaseValue($value); + } + + return $value; + } } diff --git a/tests/Fixtures/TestBundle/Document/Analysis.php b/tests/Fixtures/TestBundle/Document/Analysis.php new file mode 100644 index 00000000000..bc7a554fe53 --- /dev/null +++ b/tests/Fixtures/TestBundle/Document/Analysis.php @@ -0,0 +1,97 @@ + + * + * 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\Delete; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Put; +use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; + + +/** + * Analysis. + */ +#[ApiResource(uriTemplate: '/studies/{studyId}/analyses', + operations: [ + new GetCollection(), + new Put(), + new Patch(), + new Delete(), + ], + uriVariables: [ + 'studyId' => new Link(toProperty: 'study', fromClass: Study::class, identifiers: ['id']), + ] +)] +#[ODM\Document] +class Analysis +{ + #[ODM\Id] + #[Groups(['analysis:read', 'analysis:write'])] + private $id; + + #[ODM\Field(nullable: false)] + #[Groups(['analysis:read', 'analysis:write'])] + private ?string $content = null; + + #[ODM\ReferenceOne(storeAs: 'id', targetDocument: Study::class, inversedBy: 'analyses')] + #[Groups(['analysis:read', 'analysis:write'])] + private ?Study $study = null; + + /** + * Get id. + */ + public function getId(): ?string + { + return $this->id; + } + + /** + * Set content. + */ + public function setContent(string $content): self + { + $this->content = $content; + + return $this; + } + + /** + * Get content. + */ + public function getContent(): ?string + { + return $this->content; + } + + /** + * Set study. + */ + public function setStudy(Study $study = null): self + { + $this->study = $study; + + return $this; + } + + /** + * Get study. + */ + public function getStudy(): ?Study + { + return $this->study; + } +} diff --git a/tests/Fixtures/TestBundle/Document/Study.php b/tests/Fixtures/TestBundle/Document/Study.php new file mode 100644 index 00000000000..9e455addc6a --- /dev/null +++ b/tests/Fixtures/TestBundle/Document/Study.php @@ -0,0 +1,81 @@ + + * + * 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 Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; +use Symfony\Component\Serializer\Annotation\Groups; + +/** + * Study. + */ +#[ApiResource( + normalizationContext: ['groups' => ['study:read']], + denormalizationContext: ['groups' => ['study:write']], +)] +#[ODM\Document] +class Study +{ + #[ODM\Id] + #[Groups(['study:read', 'study:write'])] + private $id; + + #[ODM\Field(nullable: true)] + #[Groups(['study:read', 'study:write'])] + private string $content; + + #[ODM\ReferenceMany(storeAs: 'id', targetDocument: Analysis::class, mappedBy: 'study')] + public Collection|iterable $analyses; + + public function __construct() + { + $this->analyses = new ArrayCollection(); + } + + /** + * Set content. + */ + public function setContent(string $content): self + { + $this->content = $content; + + return $this; + } + + /** + * Get content. + */ + public function getContent(): string + { + return $this->content; + } + + /** + * Get id. + */ + public function getId(): ?string + { + return $this->id; + } + + /** + * Get analyses. + */ + public function getAnalyses(): Collection|iterable + { + return $this->analyses; + } +} From 636221e8c72277fc7a6766da66ed7884e4870439 Mon Sep 17 00:00:00 2001 From: Divine <48183131+divine@users.noreply.github.com> Date: Tue, 11 Jul 2023 00:03:09 +0300 Subject: [PATCH 2/4] fix: tests --- tests/Fixtures/TestBundle/Document/Analysis.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/Fixtures/TestBundle/Document/Analysis.php b/tests/Fixtures/TestBundle/Document/Analysis.php index bc7a554fe53..4883ec01422 100644 --- a/tests/Fixtures/TestBundle/Document/Analysis.php +++ b/tests/Fixtures/TestBundle/Document/Analysis.php @@ -18,23 +18,28 @@ use ApiPlatform\Metadata\GetCollection; use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM; - +use Symfony\Component\Serializer\Annotation\Groups; /** * Analysis. */ -#[ApiResource(uriTemplate: '/studies/{studyId}/analyses', +#[ApiResource( + uriTemplate: '/studies/{studyId}/analyses', operations: [ new GetCollection(), + new Post(), new Put(), new Patch(), new Delete(), ], uriVariables: [ 'studyId' => new Link(toProperty: 'study', fromClass: Study::class, identifiers: ['id']), - ] + ], + normalizationContext: ['groups' => ['analysis:read']], + denormalizationContext: ['groups' => ['analysis:write']], )] #[ODM\Document] class Analysis From 84160c72da0cd5750e1e869831864ff3f191deb0 Mon Sep 17 00:00:00 2001 From: Divine <48183131+divine@users.noreply.github.com> Date: Wed, 19 Jul 2023 00:56:08 +0300 Subject: [PATCH 3/4] chore: add tests --- ...custom_identifier_with_subresource.feature | 66 +++++++++++++++++++ .../Fixtures/TestBundle/Document/Analysis.php | 12 +++- tests/Fixtures/TestBundle/Document/Study.php | 10 ++- 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/features/main/custom_identifier_with_subresource.feature b/features/main/custom_identifier_with_subresource.feature index 74d9c65a4cd..ba8ea8fde5a 100644 --- a/features/main/custom_identifier_with_subresource.feature +++ b/features/main/custom_identifier_with_subresource.feature @@ -93,3 +93,69 @@ Feature: Using custom parent identifier for resources ] } """ + + Scenario: Create a new study and analysis, and query analyses + When I add "Accept" header equal to "application/ld+json" + And I add "Content-Type" header equal to "application/ld+json" + And I send a "POST" request to "/studies" with body: + """ + { + "id": "64b703fc1d65f957cce5eb33", + "content": "study for the app" + } + """ + Then the response status code should be 201 + And the JSON should be equal to: + """ + { + "@context": "/contexts/Study", + "@id": "/studies/64b703fc1d65f957cce5eb33", + "@type": "Study", + "id": "64b703fc1d65f957cce5eb33", + "content": "study for the app" + } + """ + When I add "Accept" header equal to "application/ld+json" + And I add "Content-Type" header equal to "application/ld+json" + And I send a "POST" request to "/studies/64b703fc1d65f957cce5eb33/analyses" with body: + """ + { + "id": "64b70696f2d88fe04a86f905", + "content": "a", + "study": "/studies/64b703fc1d65f957cce5eb33" + } + """ + Then the response status code should be 201 + And the response should be in JSON + And the JSON should be equal to: + """ + { + "@context": "/contexts/Analysis", + "@id": "/analyses/64b70696f2d88fe04a86f905", + "@type": "Analysis", + "id": "64b70696f2d88fe04a86f905", + "content": "a", + "study": "/studies/64b703fc1d65f957cce5eb33" + } + """ + When I send a "GET" request to "/studies/64b703fc1d65f957cce5eb33/analyses" + Then the response status code should be 200 + And the response should be in JSON + And the JSON should be equal to: + """ + { + "@context": "/contexts/Analysis", + "@id": "/studies/64b703fc1d65f957cce5eb33/analyses", + "@type": "hydra:Collection", + "hydra:member": [ + { + "@id": "/analyses/64b70696f2d88fe04a86f905", + "@type": "Analysis", + "id": "64b70696f2d88fe04a86f905", + "content": "a", + "study": "/studies/64b703fc1d65f957cce5eb33" + } + ], + "hydra:totalItems": 1 + } + """ diff --git a/tests/Fixtures/TestBundle/Document/Analysis.php b/tests/Fixtures/TestBundle/Document/Analysis.php index 4883ec01422..57f3e92df1f 100644 --- a/tests/Fixtures/TestBundle/Document/Analysis.php +++ b/tests/Fixtures/TestBundle/Document/Analysis.php @@ -44,7 +44,7 @@ #[ODM\Document] class Analysis { - #[ODM\Id] + #[ODM\Id(type: 'object_id', strategy: 'NONE')] #[Groups(['analysis:read', 'analysis:write'])] private $id; @@ -52,7 +52,7 @@ class Analysis #[Groups(['analysis:read', 'analysis:write'])] private ?string $content = null; - #[ODM\ReferenceOne(storeAs: 'id', targetDocument: Study::class, inversedBy: 'analyses')] + #[ODM\ReferenceOne(storeAs: 'id', targetDocument: Study::class)] #[Groups(['analysis:read', 'analysis:write'])] private ?Study $study = null; @@ -64,6 +64,14 @@ public function getId(): ?string return $this->id; } + /** + * Set id. + */ + public function setId(?string $id): void + { + $this->id = $id; + } + /** * Set content. */ diff --git a/tests/Fixtures/TestBundle/Document/Study.php b/tests/Fixtures/TestBundle/Document/Study.php index 9e455addc6a..d7a02535551 100644 --- a/tests/Fixtures/TestBundle/Document/Study.php +++ b/tests/Fixtures/TestBundle/Document/Study.php @@ -29,7 +29,7 @@ #[ODM\Document] class Study { - #[ODM\Id] + #[ODM\Id(type: 'object_id', strategy: 'NONE')] #[Groups(['study:read', 'study:write'])] private $id; @@ -71,6 +71,14 @@ public function getId(): ?string return $this->id; } + /** + * Set id. + */ + public function setId(?string $id): void + { + $this->id = $id; + } + /** * Get analyses. */ From 1b56f982e4ffcb424712097dfa769b0b6cb9c110 Mon Sep 17 00:00:00 2001 From: Divine <48183131+divine@users.noreply.github.com> Date: Wed, 19 Jul 2023 14:01:38 +0300 Subject: [PATCH 4/4] fix: test only on mongodb --- features/main/custom_identifier_with_subresource.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/main/custom_identifier_with_subresource.feature b/features/main/custom_identifier_with_subresource.feature index ba8ea8fde5a..b407d41a06e 100644 --- a/features/main/custom_identifier_with_subresource.feature +++ b/features/main/custom_identifier_with_subresource.feature @@ -93,7 +93,7 @@ Feature: Using custom parent identifier for resources ] } """ - + @mongodb Scenario: Create a new study and analysis, and query analyses When I add "Accept" header equal to "application/ld+json" And I add "Content-Type" header equal to "application/ld+json"