Skip to content

Commit 060a895

Browse files
authored
Merge pull request #2268 from MoltenCoreIO/fix-oneToOneBidirectional
fix OneToOne bidirectional + unitTest WIP
2 parents dce8367 + fc17efc commit 060a895

File tree

6 files changed

+173
-0
lines changed

6 files changed

+173
-0
lines changed

features/bootstrap/FeatureContext.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Question;
4444
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RamseyUuidDummy;
4545
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
46+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedOwnedDummy;
47+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedOwningDummy;
4648
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedToDummyFriend;
4749
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelationEmbedder;
4850
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\SecuredDummy;
@@ -1007,6 +1009,38 @@ public function thereIsADummyObjectWithAFourthLevelRelation()
10071009
$this->manager->flush();
10081010
}
10091011

1012+
/**
1013+
* @Given there is a RelatedOwnedDummy object with OneToOne relation
1014+
*/
1015+
public function thereIsARelatedOwnedDummy()
1016+
{
1017+
$relatedOwnedDummy = new RelatedOwnedDummy();
1018+
$this->manager->persist($relatedOwnedDummy);
1019+
1020+
$dummy = new Dummy();
1021+
$dummy->setName('plop');
1022+
$dummy->setRelatedOwnedDummy($relatedOwnedDummy);
1023+
$this->manager->persist($dummy);
1024+
1025+
$this->manager->flush();
1026+
}
1027+
1028+
/**
1029+
* @Given there is a RelatedOwningDummy object with OneToOne relation
1030+
*/
1031+
public function thereIsARelatedOwningDummy()
1032+
{
1033+
$dummy = new Dummy();
1034+
$dummy->setName('plop');
1035+
$this->manager->persist($dummy);
1036+
1037+
$relatedOwningDummy = new RelatedOwningDummy();
1038+
$relatedOwningDummy->setOwnedDummy($dummy);
1039+
$this->manager->persist($relatedOwningDummy);
1040+
1041+
$this->manager->flush();
1042+
}
1043+
10101044
/**
10111045
* @Given there is a person named :name greeting with a :message message
10121046
*/

features/main/subresource.feature

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,71 @@ Feature: Subresource support
342342
}
343343
"""
344344

345+
346+
Scenario: The OneToOne subresource should be accessible from owned side
347+
Given there is a RelatedOwnedDummy object with OneToOne relation
348+
When I send a "GET" request to "/related_owned_dummies/1/owning_dummy"
349+
Then the response status code should be 200
350+
And the response should be in JSON
351+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
352+
And the JSON should be equal to:
353+
"""
354+
{
355+
"@context": "/contexts/Dummy",
356+
"@id": "/dummies/3",
357+
"@type": "Dummy",
358+
"description": null,
359+
"dummy": null,
360+
"dummyBoolean": null,
361+
"dummyDate": null,
362+
"dummyFloat": null,
363+
"dummyPrice": null,
364+
"relatedDummy": null,
365+
"relatedDummies": [],
366+
"jsonData": [],
367+
"arrayData": [],
368+
"name_converted": null,
369+
"relatedOwnedDummy": "/related_owned_dummies/1",
370+
"relatedOwningDummy": null,
371+
"id": 3,
372+
"name": "plop",
373+
"alias": null,
374+
"foo": null
375+
}
376+
"""
377+
378+
Scenario: The OneToOne subresource should be accessible from owning side
379+
Given there is a RelatedOwningDummy object with OneToOne relation
380+
When I send a "GET" request to "/related_owning_dummies/1/owned_dummy"
381+
Then the response status code should be 200
382+
And the response should be in JSON
383+
And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8"
384+
And the JSON should be equal to:
385+
"""
386+
{
387+
"@context": "/contexts/Dummy",
388+
"@id": "/dummies/4",
389+
"@type": "Dummy",
390+
"description": null,
391+
"dummy": null,
392+
"dummyBoolean": null,
393+
"dummyDate": null,
394+
"dummyFloat": null,
395+
"dummyPrice": null,
396+
"relatedDummy": null,
397+
"relatedDummies": [],
398+
"jsonData": [],
399+
"arrayData": [],
400+
"name_converted": null,
401+
"relatedOwnedDummy": null,
402+
"relatedOwningDummy": "/related_owning_dummies/1",
403+
"id": 4,
404+
"name": "plop",
405+
"alias": null,
406+
"foo": null
407+
}
408+
"""
409+
345410
@dropSchema
346411
Scenario: Recursive resource
347412
When I send a "GET" request to "/dummy_products/2"

src/Bridge/Doctrine/Orm/SubresourceDataProvider.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,19 @@ private function buildQuery(array $identifiers, array $context, QueryNameGenerat
175175
$mappedBy = $classMetadata->getAssociationMapping($previousAssociationProperty)['mappedBy'];
176176
$previousAlias = "$previousAlias.$mappedBy";
177177

178+
$qb->select($alias)
179+
->from($identifierResourceClass, $alias);
180+
break;
181+
case ClassMetadataInfo::ONE_TO_ONE:
182+
$association = $classMetadata->getAssociationMapping($previousAssociationProperty);
183+
if (!isset($association['mappedBy'])) {
184+
$qb->select("IDENTITY($alias.$previousAssociationProperty)")
185+
->from($identifierResourceClass, $alias);
186+
break;
187+
}
188+
$mappedBy = $association['mappedBy'];
189+
$previousAlias = "$previousAlias.$mappedBy";
190+
178191
$qb->select($alias)
179192
->from($identifierResourceClass, $alias);
180193
break;

tests/Bridge/Doctrine/Orm/SubresourceDataProviderTest.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use ApiPlatform\Core\Metadata\Property\PropertyNameCollection;
2626
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy;
2727
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy;
28+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedOwningDummy;
2829
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\ThirdLevel;
2930
use Doctrine\Common\Persistence\ManagerRegistry;
3031
use Doctrine\Common\Persistence\ObjectManager;
@@ -267,6 +268,62 @@ public function testGetSubSubresourceItem()
267268
$this->assertEquals($result, $dataProvider->getSubresource(ThirdLevel::class, ['id' => ['id' => 1], 'relatedDummies' => ['id' => 1]], $context));
268269
}
269270

271+
public function testGetSubresourceOneToOneOwningRelation()
272+
{
273+
// RelatedOwningDummy OneToOne Dummy
274+
$dql = 'SELECT ownedDummy_a2 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy id_a1 INNER JOIN id_a1.ownedDummy ownedDummy_a2 WHERE id_a1.id = :id_p1';
275+
276+
$queryProphecy = $this->prophesize(AbstractQuery::class);
277+
$queryProphecy->getOneOrNullResult()->shouldBeCalled()->willReturn([]);
278+
279+
$identifiers = ['id'];
280+
$queryBuilder = $this->prophesize(QueryBuilder::class);
281+
$queryBuilder->setParameter('id_p1', 1, DBALType::INTEGER)->shouldBeCalled()->willReturn($queryBuilder);
282+
$funcProphecy = $this->prophesize(Func::class);
283+
$func = $funcProphecy->reveal();
284+
$queryBuilder->andWhere($func)->shouldBeCalled()->willReturn($queryBuilder);
285+
$queryBuilder->getQuery()->shouldBeCalled()->willReturn($queryProphecy->reveal());
286+
287+
$repositoryProphecy = $this->prophesize(EntityRepository::class);
288+
$repositoryProphecy->createQueryBuilder('o')->shouldBeCalled()->willReturn($queryBuilder->reveal());
289+
290+
$managerProphecy = $this->prophesize(EntityManager::class);
291+
$managerProphecy->getRepository(RelatedOwningDummy::class)->shouldBeCalled()->willReturn($repositoryProphecy->reveal());
292+
$this->assertIdentifierManagerMethodCalls($managerProphecy);
293+
294+
$classMetadataProphecy = $this->prophesize(ClassMetadata::class);
295+
$classMetadataProphecy->hasAssociation('ownedDummy')->willReturn(true)->shouldBeCalled();
296+
$classMetadataProphecy->getAssociationMapping('ownedDummy')->shouldBeCalled()->willReturn(['type' => ClassMetadata::ONE_TO_ONE]);
297+
$classMetadataProphecy->getTypeOfField('id')->willReturn(DBALType::INTEGER)->shouldBeCalled();
298+
299+
$managerProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal());
300+
301+
$qb = $this->prophesize(QueryBuilder::class);
302+
$qb->select('IDENTITY(id_a1.ownedDummy)')->shouldBeCalled()->willReturn($qb);
303+
$qb->from(Dummy::class, 'id_a1')->shouldBeCalled()->willReturn($qb);
304+
$qb->andWhere('id_a1.id = :id_p1')->shouldBeCalled()->willReturn($qb);
305+
$qb->getDQL()->shouldBeCalled()->willReturn($dql);
306+
307+
$exprProphecy = $this->prophesize(Expr::class);
308+
$exprProphecy->in('o', $dql)->willReturn($func)->shouldBeCalled();
309+
310+
$qb->expr()->shouldBeCalled()->willReturn($exprProphecy->reveal());
311+
312+
$managerProphecy->createQueryBuilder()->shouldBeCalled()->willReturn($qb->reveal());
313+
314+
$managerRegistryProphecy = $this->prophesize(ManagerRegistry::class);
315+
$managerRegistryProphecy->getManagerForClass(RelatedOwningDummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal());
316+
$managerRegistryProphecy->getManagerForClass(Dummy::class)->shouldBeCalled()->willReturn($managerProphecy->reveal());
317+
318+
list($propertyNameCollectionFactory, $propertyMetadataFactory) = $this->getMetadataProphecies([Dummy::class => $identifiers]);
319+
320+
$dataProvider = new SubresourceDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactory, $propertyMetadataFactory);
321+
322+
$context = ['property' => 'ownedDummy', 'identifiers' => [['id', Dummy::class]], 'collection' => false, IdentifierConverterInterface::HAS_IDENTIFIER_CONVERTER => true];
323+
324+
$this->assertEquals([], $dataProvider->getSubresource(RelatedOwningDummy::class, ['id' => ['id' => 1]], $context));
325+
}
326+
270327
public function testQueryResultExtension()
271328
{
272329
$dql = 'SELECT relatedDummies_a2 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy id_a1 INNER JOIN id_a1.relatedDummies relatedDummies_a2 WHERE id_a1.id = :id_p1';

tests/Fixtures/TestBundle/Entity/RelatedOwnedDummy.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
1515

1616
use ApiPlatform\Core\Annotation\ApiResource;
17+
use ApiPlatform\Core\Annotation\ApiSubresource;
1718
use Doctrine\ORM\Mapping as ORM;
1819

1920
/**
@@ -45,6 +46,7 @@ class RelatedOwnedDummy
4546
*
4647
* @ORM\OneToOne(targetEntity="Dummy", cascade={"persist"}, inversedBy="relatedOwnedDummy")
4748
* @ORM\JoinColumn(nullable=false)
49+
* @ApiSubresource
4850
*/
4951
public $owningDummy;
5052

tests/Fixtures/TestBundle/Entity/RelatedOwningDummy.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
1515

1616
use ApiPlatform\Core\Annotation\ApiResource;
17+
use ApiPlatform\Core\Annotation\ApiSubresource;
1718
use Doctrine\ORM\Mapping as ORM;
1819

1920
/**
@@ -44,6 +45,7 @@ class RelatedOwningDummy
4445
* @var Dummy
4546
*
4647
* @ORM\OneToOne(targetEntity="Dummy", cascade={"persist"}, mappedBy="relatedOwningDummy")
48+
* @ApiSubresource
4749
*/
4850
public $ownedDummy;
4951

0 commit comments

Comments
 (0)