Skip to content

Commit 7ff218f

Browse files
authored
Merge pull request #78 from swisnl/bugfix/issue-77
Properly handle relations without data
2 parents a3b3fc9 + 89c59fa commit 7ff218f

13 files changed

+245
-73
lines changed

src/Concerns/HasRelations.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,14 @@ public function getRelationValue(string $name): ?DataInterface
141141
/**
142142
* Set the specific relationship on the model.
143143
*
144-
* @param string $name
145-
* @param \Swis\JsonApi\Client\Interfaces\DataInterface|null $data
146-
* @param \Swis\JsonApi\Client\Links|null $links
147-
* @param \Swis\JsonApi\Client\Meta|null $meta
144+
* @param string $name
145+
* @param \Swis\JsonApi\Client\Interfaces\DataInterface|false|null $data
146+
* @param \Swis\JsonApi\Client\Links|null $links
147+
* @param \Swis\JsonApi\Client\Meta|null $meta
148148
*
149149
* @return static
150150
*/
151-
public function setRelation(string $name, DataInterface $data = null, Links $links = null, Meta $meta = null)
151+
public function setRelation(string $name, $data = false, Links $links = null, Meta $meta = null)
152152
{
153153
$method = Str::camel($name);
154154
if (method_exists($this, $method)) {
@@ -160,8 +160,11 @@ public function setRelation(string $name, DataInterface $data = null, Links $lin
160160
$relationObject = $this->morphTo($name);
161161
}
162162

163-
if ($data !== null) {
164-
$relationObject->associate($data);
163+
if ($data !== false) {
164+
$relationObject->dissociate();
165+
if ($data !== null) {
166+
$relationObject->associate($data);
167+
}
165168
}
166169
$relationObject->setLinks($links);
167170
$relationObject->setMeta($meta);

src/Interfaces/ItemInterface.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,14 @@ public function getAvailableRelations(): array;
127127
/**
128128
* Set the specific relationship in the model.
129129
*
130-
* @param string $relation
131-
* @param \Swis\JsonApi\Client\Interfaces\DataInterface|null $value
132-
* @param \Swis\JsonApi\Client\Links|null $links
133-
* @param \Swis\JsonApi\Client\Meta|null $meta
130+
* @param string $relation
131+
* @param \Swis\JsonApi\Client\Interfaces\DataInterface|false|null $value
132+
* @param \Swis\JsonApi\Client\Links|null $links
133+
* @param \Swis\JsonApi\Client\Meta|null $meta
134134
*
135135
* @return static
136136
*/
137-
public function setRelation(string $relation, DataInterface $value = null, Links $links = null, Meta $meta = null);
137+
public function setRelation(string $relation, $value = false, Links $links = null, Meta $meta = null);
138138

139139
/**
140140
* @return \Swis\JsonApi\Client\Interfaces\OneRelationInterface[]|\Swis\JsonApi\Client\Interfaces\ManyRelationInterface[]

src/Item.php

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -130,28 +130,37 @@ public function getRelationships(): array
130130
$relationships = [];
131131

132132
foreach ($this->getRelations() as $name => $relation) {
133-
if ($relation instanceof OneRelationInterface) {
134-
$relationships[$name] = ['data' => null];
133+
if ($relation->hasIncluded()) {
134+
if ($relation instanceof OneRelationInterface) {
135+
$relationships[$name]['data'] = null;
135136

136-
if ($relation->getIncluded() !== null) {
137-
$relationships[$name] = [
138-
'data' => [
137+
if ($relation->getIncluded() !== null) {
138+
$relationships[$name]['data'] = [
139139
'type' => $relation->getIncluded()->getType(),
140140
'id' => $relation->getIncluded()->getId(),
141-
],
142-
];
143-
}
144-
} elseif ($relation instanceof ManyRelationInterface) {
145-
$relationships[$name]['data'] = [];
141+
];
142+
}
143+
} elseif ($relation instanceof ManyRelationInterface) {
144+
$relationships[$name]['data'] = [];
146145

147-
foreach ($relation->getIncluded() as $item) {
148-
$relationships[$name]['data'][] =
149-
[
146+
foreach ($relation->getIncluded() as $item) {
147+
$relationships[$name]['data'][] = [
150148
'type' => $item->getType(),
151149
'id' => $item->getId(),
152150
];
151+
}
153152
}
154153
}
154+
155+
$links = $relation->getLinks();
156+
if ($links !== null) {
157+
$relationships[$name]['links'] = $links->toArray();
158+
}
159+
160+
$meta = $relation->getMeta();
161+
if ($meta !== null) {
162+
$relationships[$name]['meta'] = $meta->toArray();
163+
}
155164
}
156165

157166
return $relationships;

src/Parsers/ItemParser.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,12 @@ private function setRelations(ItemInterface $item, $data): void
135135
throw new ValidationException('Relationship object MUST contain at least one of the following properties: `links`, `data`, `meta`.');
136136
}
137137

138-
$value = null;
139-
if (property_exists($relationship, 'data') && $relationship->data !== null) {
140-
$value = $this->parseRelationshipData($relationship->data);
138+
$value = false;
139+
if (property_exists($relationship, 'data')) {
140+
$value = null;
141+
if ($relationship->data !== null) {
142+
$value = $this->parseRelationshipData($relationship->data);
143+
}
141144
}
142145

143146
$links = null;

src/Relations/AbstractManyRelation.php

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use Swis\JsonApi\Client\Interfaces\ManyRelationInterface;
77

88
/**
9-
* @property \Swis\JsonApi\Client\Collection|null $included
9+
* @property \Swis\JsonApi\Client\Collection|false|null $included
1010
*/
1111
abstract class AbstractManyRelation extends AbstractRelation implements ManyRelationInterface
1212
{
@@ -22,14 +22,6 @@ public function associate(Collection $included)
2222
return $this;
2323
}
2424

25-
/**
26-
* @return bool
27-
*/
28-
public function hasIncluded(): bool
29-
{
30-
return $this->getIncluded()->isNotEmpty();
31-
}
32-
3325
/**
3426
* @return \Swis\JsonApi\Client\Collection
3527
*/

src/Relations/AbstractOneRelation.php

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use Swis\JsonApi\Client\Interfaces\OneRelationInterface;
77

88
/**
9-
* @property \Swis\JsonApi\Client\Interfaces\ItemInterface|null $included
9+
* @property \Swis\JsonApi\Client\Interfaces\ItemInterface|false|null $included
1010
*/
1111
abstract class AbstractOneRelation extends AbstractRelation implements OneRelationInterface
1212
{
@@ -27,14 +27,6 @@ public function associate(ItemInterface $included)
2727
*/
2828
public function getIncluded(): ? ItemInterface
2929
{
30-
return $this->included;
31-
}
32-
33-
/**
34-
* @return bool
35-
*/
36-
public function hasIncluded(): bool
37-
{
38-
return null !== $this->getIncluded();
30+
return $this->included ?: null;
3931
}
4032
}

src/Relations/AbstractRelation.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ abstract class AbstractRelation
1111
use HasMeta;
1212

1313
/**
14-
* @var \Swis\JsonApi\Client\Interfaces\DataInterface|null
14+
* @var \Swis\JsonApi\Client\Interfaces\DataInterface|false|null
1515
*/
16-
protected $included;
16+
protected $included = false;
1717

1818
/**
1919
* @var bool
@@ -30,6 +30,14 @@ public function dissociate()
3030
return $this;
3131
}
3232

33+
/**
34+
* @return bool
35+
*/
36+
public function hasIncluded(): bool
37+
{
38+
return $this->included !== false;
39+
}
40+
3341
/**
3442
* @param bool $omitIncluded
3543
*

tests/Concerns/HasRelationsTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public function it_can_get_and_set_an_item_as_relation()
2929

3030
$relation = $mock->getRelation('foo');
3131
$this->assertInstanceOf(MorphToRelation::class, $relation);
32+
$this->assertTrue($relation->hasIncluded());
3233
$this->assertSame($data, $relation->getIncluded());
3334
}
3435

@@ -45,9 +46,41 @@ public function it_can_get_and_set_a_collection_as_relation()
4546

4647
$relation = $mock->getRelation('foo');
4748
$this->assertInstanceOf(MorphToManyRelation::class, $relation);
49+
$this->assertTrue($relation->hasIncluded());
4850
$this->assertSame($data, $relation->getIncluded());
4951
}
5052

53+
/**
54+
* @test
55+
*/
56+
public function it_can_get_and_set_null_as_relation()
57+
{
58+
/** @var \PHPUnit\Framework\MockObject\MockObject&\Swis\JsonApi\Client\Concerns\HasRelations $mock */
59+
$mock = $this->getMockForTrait(HasRelations::class);
60+
61+
$mock->setRelation('foo', null);
62+
63+
$relation = $mock->getRelation('foo');
64+
$this->assertInstanceOf(MorphToRelation::class, $relation);
65+
$this->assertTrue($relation->hasIncluded());
66+
$this->assertNull($relation->getIncluded());
67+
}
68+
69+
/**
70+
* @test
71+
*/
72+
public function it_does_not_set_false_as_relation()
73+
{
74+
/** @var \PHPUnit\Framework\MockObject\MockObject&\Swis\JsonApi\Client\Concerns\HasRelations $mock */
75+
$mock = $this->getMockForTrait(HasRelations::class);
76+
77+
$mock->setRelation('foo', false);
78+
79+
$relation = $mock->getRelation('foo');
80+
$this->assertInstanceOf(MorphToRelation::class, $relation);
81+
$this->assertFalse($relation->hasIncluded());
82+
}
83+
5184
/**
5285
* @test
5386
*/

tests/DocumentClientTest.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class DocumentClientTest extends AbstractTest
1818
public function the_base_url_can_be_changed_after_instantiation()
1919
{
2020
/** @var \PHPUnit\Framework\MockObject\MockObject|\Swis\JsonApi\Client\Interfaces\ClientInterface $client */
21-
$client = $this->getMockBuilder(ClientInterface::class)->getMock();
21+
$client = $this->createMock(ClientInterface::class);
2222

2323
$client->expects($this->once())
2424
->method('getBaseUri')
@@ -29,7 +29,7 @@ public function the_base_url_can_be_changed_after_instantiation()
2929
->with('http://www.test-changed.com');
3030

3131
/** @var \PHPUnit\Framework\MockObject\MockObject|\Swis\JsonApi\Client\Interfaces\ResponseParserInterface $parser */
32-
$parser = $this->getMockBuilder(ResponseParserInterface::class)->getMock();
32+
$parser = $this->createMock(ResponseParserInterface::class);
3333

3434
$documentClient = new DocumentClient($client, $parser);
3535

@@ -46,15 +46,15 @@ public function it_builds_a_get_request()
4646
$document = new Document();
4747

4848
/** @var \PHPUnit\Framework\MockObject\MockObject|\Swis\JsonApi\Client\Interfaces\ClientInterface $client */
49-
$client = $this->getMockBuilder(ClientInterface::class)->getMock();
49+
$client = $this->createMock(ClientInterface::class);
5050

5151
$client->expects($this->once())
5252
->method('get')
5353
->with('/test/1', ['X-Foo' => 'bar'])
5454
->willReturn($response);
5555

5656
/** @var \PHPUnit\Framework\MockObject\MockObject|\Swis\JsonApi\Client\Interfaces\ResponseParserInterface $parser */
57-
$parser = $this->getMockBuilder(ResponseParserInterface::class)->getMock();
57+
$parser = $this->createMock(ResponseParserInterface::class);
5858

5959
$parser->expects($this->once())
6060
->method('parse')
@@ -77,15 +77,15 @@ public function it_builds_a_delete_request()
7777
$document = new Document();
7878

7979
/** @var \PHPUnit\Framework\MockObject\MockObject|\Swis\JsonApi\Client\Interfaces\ClientInterface $client */
80-
$client = $this->getMockBuilder(ClientInterface::class)->getMock();
80+
$client = $this->createMock(ClientInterface::class);
8181

8282
$client->expects($this->once())
8383
->method('delete')
8484
->with('/test/1', ['X-Foo' => 'bar'])
8585
->willReturn($response);
8686

8787
/** @var \PHPUnit\Framework\MockObject\MockObject|\Swis\JsonApi\Client\Interfaces\ResponseParserInterface $parser */
88-
$parser = $this->getMockBuilder(ResponseParserInterface::class)->getMock();
88+
$parser = $this->createMock(ResponseParserInterface::class);
8989

9090
$parser->expects($this->once())
9191
->method('parse')
@@ -110,15 +110,15 @@ public function it_builds_a_patch_request()
110110
$itemDocument->setData((new Item())->setType('test')->setId('1'));
111111

112112
/** @var \PHPUnit\Framework\MockObject\MockObject|\Swis\JsonApi\Client\Interfaces\ClientInterface $client */
113-
$client = $this->getMockBuilder(ClientInterface::class)->getMock();
113+
$client = $this->createMock(ClientInterface::class);
114114

115115
$client->expects($this->once())
116116
->method('patch')
117117
->with('/test/1', '{"data":{"type":"test","id":"1"}}', ['X-Foo' => 'bar'])
118118
->willReturn($response);
119119

120120
/** @var \PHPUnit\Framework\MockObject\MockObject|\Swis\JsonApi\Client\Interfaces\ResponseParserInterface $parser */
121-
$parser = $this->getMockBuilder(ResponseParserInterface::class)->getMock();
121+
$parser = $this->createMock(ResponseParserInterface::class);
122122

123123
$parser->expects($this->once())
124124
->method('parse')
@@ -143,15 +143,15 @@ public function it_builds_a_post_request()
143143
$itemDocument->setData((new Item())->setType('test'));
144144

145145
/** @var \PHPUnit\Framework\MockObject\MockObject|\Swis\JsonApi\Client\Interfaces\ClientInterface $client */
146-
$client = $this->getMockBuilder(ClientInterface::class)->getMock();
146+
$client = $this->createMock(ClientInterface::class);
147147

148148
$client->expects($this->once())
149149
->method('post')
150150
->with('/test/1', '{"data":{"type":"test"}}', ['X-Foo' => 'bar'])
151151
->willReturn($response);
152152

153153
/** @var \PHPUnit\Framework\MockObject\MockObject|\Swis\JsonApi\Client\Interfaces\ResponseParserInterface $parser */
154-
$parser = $this->getMockBuilder(ResponseParserInterface::class)->getMock();
154+
$parser = $this->createMock(ResponseParserInterface::class);
155155

156156
$parser->expects($this->once())
157157
->method('parse')

0 commit comments

Comments
 (0)