Skip to content

Commit b7eca2d

Browse files
committed
fix iri identifier on doctrine proxy
1 parent 3f6ca0e commit b7eca2d

File tree

5 files changed

+336
-12
lines changed

5 files changed

+336
-12
lines changed

features/relation.feature

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,24 @@ Feature: Relations support
2323
}
2424
"""
2525

26+
Scenario: Create a dummy friend
27+
When I send a "POST" request to "/dummy_friends" with body:
28+
"""
29+
{"name": "Zoidberg"}
30+
"""
31+
Then the response status code should be 201
32+
And the response should be in JSON
33+
And the header "Content-Type" should be equal to "application/ld+json"
34+
And the JSON should be equal to:
35+
"""
36+
{
37+
"@context": "/contexts/DummyFriend",
38+
"@id": "/dummy_friends/1",
39+
"@type": "DummyFriend",
40+
"name": "Zoidberg"
41+
}
42+
"""
43+
2644
Scenario: Create a related dummy
2745
When I send a "POST" request to "/related_dummies" with body:
2846
"""
@@ -42,12 +60,51 @@ Feature: Relations support
4260
"name": null,
4361
"dummyDate": null,
4462
"thirdLevel": "/third_levels/1",
63+
"relatedToDummyFriend": null,
4564
"dummyBoolean": null,
4665
"symfony": "symfony",
4766
"age": null
4867
}
4968
"""
5069

70+
Scenario: Create a friend relationship
71+
When I send a "POST" request to "/related_to_dummy_friends" with body:
72+
"""
73+
{
74+
"name": "Friends relation",
75+
"dummyFriend": "/dummy_friends/1",
76+
"relatedDummy": "/related_dummies/1"
77+
}
78+
"""
79+
Then the response status code should be 201
80+
And the response should be in JSON
81+
And the header "Content-Type" should be equal to "application/ld+json"
82+
And the JSON should be equal to:
83+
"""
84+
{
85+
"@context": "/contexts/RelatedToDummyFriend",
86+
"@id": "/related_to_dummy_friends/dummyFriend=1;relatedDummy=1",
87+
"@type": "RelatedToDummyFriend",
88+
"name": "Friends relation"
89+
}
90+
"""
91+
92+
Scenario: Get the relationship
93+
When I send a "GET" request to "/related_to_dummy_friends/dummyFriend=1;relatedDummy=1"
94+
And the response status code should be 200
95+
And the response should be in JSON
96+
And the header "Content-Type" should be equal to "application/ld+json"
97+
And the JSON should be equal to:
98+
"""
99+
{
100+
"@context": "/contexts/RelatedToDummyFriend",
101+
"@id": "/related_to_dummy_friends/dummyFriend=1;relatedDummy=1",
102+
"@type": "RelatedToDummyFriend",
103+
"name": "Friends relation"
104+
}
105+
"""
106+
107+
51108
Scenario: Create a dummy with relations
52109
When I send a "POST" request to "/dummies" with body:
53110
"""

src/Bridge/Symfony/Routing/IriConverter.php

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,25 +77,75 @@ public function getIriFromItem($item, int $referenceType = UrlGeneratorInterface
7777
$resourceClass = $this->getObjectClass($item);
7878
$routeName = $this->getRouteName($resourceClass, false);
7979

80+
$identifiers = $this->generateIdentifiersUrl($this->getIdentifiersFromItem($item));
81+
82+
return $this->router->generate($routeName, ['id' => implode(';', $identifiers)], $referenceType);
83+
}
84+
85+
/**
86+
* Generate the identifier url.
87+
*
88+
* @param array $identifiers
89+
*
90+
* @return array
91+
*/
92+
public function generateIdentifiersUrl(array $identifiers) : array
93+
{
94+
if (1 === count($identifiers)) {
95+
return [rawurlencode(array_values($identifiers)[0])];
96+
}
97+
98+
foreach ($identifiers as $name => $value) {
99+
$identifiers[$name] = sprintf('%s=%s', $name, $value);
100+
}
101+
102+
return $identifiers;
103+
}
104+
105+
/**
106+
* Find identifiers from an Item (Object).
107+
*
108+
* @param object $item
109+
*
110+
* @throws RuntimeException
111+
*
112+
* @return array
113+
*/
114+
private function getIdentifiersFromItem($item) : array
115+
{
116+
$identifiers = [];
117+
$resourceClass = $this->getObjectClass($item);
118+
80119
foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) {
81120
$propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName);
82121

83-
if ($propertyMetadata->isIdentifier()) {
84-
$identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);
122+
if (!$propertyMetadata->isIdentifier()) {
123+
continue;
85124
}
86-
}
87125

88-
if (1 === count($identifiers)) {
89-
$identifiers = array_map(function ($identifierValue) {
90-
return rawurlencode($identifierValue);
91-
}, $identifiers);
92-
} else {
93-
$identifiers = array_map(function ($identifierName, $identifierValue) {
94-
return sprintf('%s=%s', $identifierName, rawurlencode($identifierValue));
95-
}, array_keys($identifiers), $identifiers);
126+
$identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName);
127+
128+
if (!is_object($identifiers[$propertyName])) {
129+
continue;
130+
}
131+
132+
$relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]);
133+
$relatedItem = $identifiers[$propertyName];
134+
135+
foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) {
136+
$propertyMetadata = $this->propertyMetadataFactory->create($relatedResourceClass, $relatedPropertyName);
137+
138+
if ($propertyMetadata->isIdentifier()) {
139+
$identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedPropertyName);
140+
}
141+
}
142+
143+
if (empty($identifiers[$propertyName])) {
144+
throw new \RuntimeException(sprintf('%s identifiers can not be found', $resourceClass));
145+
}
96146
}
97147

98-
return $this->router->generate($routeName, ['id' => implode(';', $identifiers)], $referenceType);
148+
return $identifiers;
99149
}
100150

101151
/**
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
13+
14+
use ApiPlatform\Core\Annotation\ApiProperty;
15+
use ApiPlatform\Core\Annotation\ApiResource;
16+
use Doctrine\ORM\Mapping as ORM;
17+
use Symfony\Component\Serializer\Annotation\Groups;
18+
use Symfony\Component\Validator\Constraints as Assert;
19+
20+
/**
21+
* DummyFriend.
22+
*
23+
* @author Kévin Dunglas <[email protected]>
24+
*
25+
* @ApiResource()
26+
* @ORM\Entity
27+
*/
28+
class DummyFriend
29+
{
30+
/**
31+
* @var int The id.
32+
*
33+
* @ORM\Column(type="integer")
34+
* @ORM\Id
35+
* @ORM\GeneratedValue(strategy="AUTO")
36+
* @Groups({"fakemanytomany"})
37+
*/
38+
private $id;
39+
40+
/**
41+
* @var string The dummy name.
42+
*
43+
* @ORM\Column
44+
* @Assert\NotBlank
45+
* @ApiProperty(iri="http://schema.org/name")
46+
* @Groups({"fakemanytomany"})
47+
*/
48+
private $name;
49+
50+
/**
51+
* Get id.
52+
*
53+
* @return id.
54+
*/
55+
public function getId()
56+
{
57+
return $this->id;
58+
}
59+
60+
/**
61+
* Set id.
62+
*
63+
* @param id the value to set.
64+
*/
65+
public function setId($id)
66+
{
67+
$this->id = $id;
68+
}
69+
70+
/**
71+
* Get name.
72+
*
73+
* @return name.
74+
*/
75+
public function getName()
76+
{
77+
return $this->name;
78+
}
79+
80+
/**
81+
* Set name.
82+
*
83+
* @param name the value to set.
84+
*/
85+
public function setName($name)
86+
{
87+
$this->name = $name;
88+
}
89+
}

tests/Fixtures/TestBundle/Entity/RelatedDummy.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ class RelatedDummy extends ParentDummy
6060
*/
6161
public $thirdLevel;
6262

63+
/**
64+
* @ORM\OneToMany(targetEntity="RelatedToDummyFriend", cascade={"persist"}, fetch="EAGER", mappedBy="relatedDummy")
65+
* @Groups({"fakemanytomany"})
66+
*/
67+
public $relatedToDummyFriend;
68+
6369
/**
6470
* @var bool A dummy bool.
6571
*
@@ -117,4 +123,24 @@ public function setDummyBoolean($dummyBoolean)
117123
{
118124
$this->dummyBoolean = $dummyBoolean;
119125
}
126+
127+
/**
128+
* Get relatedToDummyFriend.
129+
*
130+
* @return relatedToDummyFriend.
131+
*/
132+
public function getRelatedToDummyFriend()
133+
{
134+
return $this->relatedToDummyFriend;
135+
}
136+
137+
/**
138+
* Set relatedToDummyFriend.
139+
*
140+
* @param relatedToDummyFriend the value to set.
141+
*/
142+
public function setRelatedToDummyFriend(RelatedToDummyFriend $relatedToDummyFriend)
143+
{
144+
$this->relatedToDummyFriend = $relatedToDummyFriend;
145+
}
120146
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
13+
14+
use ApiPlatform\Core\Annotation\ApiProperty;
15+
use ApiPlatform\Core\Annotation\ApiResource;
16+
use Doctrine\ORM\Mapping as ORM;
17+
use Symfony\Component\Serializer\Annotation\Groups;
18+
use Symfony\Component\Validator\Constraints as Assert;
19+
20+
/**
21+
* Related To Dummy Friend represent an association table for a manytomany relation.
22+
*
23+
* @ApiResource()
24+
* @ORM\Entity
25+
*/
26+
class RelatedToDummyFriend
27+
{
28+
/**
29+
* @var string The dummy name.
30+
*
31+
* @ORM\Column
32+
* @Assert\NotBlank
33+
* @ApiProperty(iri="http://schema.org/name")
34+
*/
35+
private $name;
36+
37+
/**
38+
* @ORM\Id
39+
* @ORM\ManyToOne(targetEntity="DummyFriend", fetch="EAGER")
40+
* @ORM\JoinColumn(name="dummyfriend_id", referencedColumnName="id", nullable=false)
41+
* @Groups({"fakemanytomany"})
42+
*/
43+
private $dummyFriend;
44+
45+
/**
46+
* @ORM\Id
47+
* @ORM\ManyToOne(targetEntity="RelatedDummy", inversedBy="relatedToDummyFriend")
48+
* @ORM\JoinColumn(name="relateddummy_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
49+
* @Groups({"fakemanytomany"})
50+
*/
51+
private $relatedDummy;
52+
53+
public function setName($name)
54+
{
55+
$this->name = $name;
56+
}
57+
58+
public function getName()
59+
{
60+
return $this->name;
61+
}
62+
63+
/**
64+
* Get dummyFriend.
65+
*
66+
* @return dummyFriend.
67+
*/
68+
public function getDummyFriend()
69+
{
70+
return $this->dummyFriend;
71+
}
72+
73+
/**
74+
* Set dummyFriend.
75+
*
76+
* @param dummyFriend the value to set.
77+
*/
78+
public function setDummyFriend($dummyFriend)
79+
{
80+
$this->dummyFriend = $dummyFriend;
81+
}
82+
83+
/**
84+
* Get relatedDummy.
85+
*
86+
* @return relatedDummy.
87+
*/
88+
public function getRelatedDummy()
89+
{
90+
return $this->relatedDummy;
91+
}
92+
93+
/**
94+
* Set relatedDummy.
95+
*
96+
* @param relatedDummy the value to set.
97+
*/
98+
public function setRelatedDummy($relatedDummy)
99+
{
100+
$this->relatedDummy = $relatedDummy;
101+
}
102+
}

0 commit comments

Comments
 (0)