Skip to content

Commit fcecfd3

Browse files
authored
NewResourceObject to allow omitting id in resources to-be-created (#108)
1 parent 4eb1497 commit fcecfd3

8 files changed

+302
-30
lines changed

Diff for: CHANGELOG.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
66

77
## [Unreleased]
88

9+
## [2.2.0] - 2020-10-12
10+
### Added
11+
- `NewResourceObject` to allow omitting `id` in resources to-be-created (#108)
12+
913
## [2.1.2] - 2020-03-16
1014
### Fixed
1115
- Related links must be allowed inside relationship documents (#104)
1216

1317
## [2.1.1] - 2019-12-19
1418
### Fixed
15-
- ResourceIdentifier does not allow multiple meta members (#99)
19+
- `ResourceIdentifier` does not allow multiple meta members (#99)
1620

1721
## [2.1.0] - 2019-02-25
1822
### Fixed
@@ -26,7 +30,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2630
### Added
2731
- v2 initial release
2832

29-
[Unreleased]: https://github.com/json-api-php/json-api/compare/2.1.2...HEAD
33+
[Unreleased]: https://github.com/json-api-php/json-api/compare/2.2.0...HEAD
34+
[2.2.0]: https://github.com/json-api-php/json-api/compare/2.1.2...2.2.0
3035
[2.1.2]: https://github.com/json-api-php/json-api/compare/2.1.1...2.1.2
3136
[2.1.1]: https://github.com/json-api-php/json-api/compare/2.1.0...2.1.1
3237
[2.1.0]: https://github.com/json-api-php/json-api/compare/2.0.1...2.1.0

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ The library API and use-cases are expressed in a comprehensive suite of tests.
7070
- [with null data](./test/DataDocument/NullDataTest.php)
7171
- [with multiple Resource Objects](./test/DataDocument/ManyResourceObjectsTest.php)
7272
- [with multiple Resource Identifiers](./test/DataDocument/ManyResourceIdentifiersTest.php)
73+
- [with a new Resource (no id)](./test/NewResourceObjectTest.php)
7374
- [Compound Documents](./test/CompoundDocumentTest.php)
7475
- [Error Documents](./test/ErrorDocumentTest.php)
7576
- [Meta Documents (containing neither data nor errors)](./test/MetaDocumentTest.php)

Diff for: src/Internal/Attachable.php

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
interface Attachable
99
{
1010
/**
11+
* Adds this object's data to $o
1112
* @param object $o
1213
* @internal
1314
*/

Diff for: src/Internal/BaseResource.php

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace JsonApiPhp\JsonApi\Internal;
4+
5+
use function JsonApiPhp\JsonApi\isValidName;
6+
7+
/**
8+
* Class BaseResource
9+
* @internal
10+
*/
11+
class BaseResource implements Attachable
12+
{
13+
/**
14+
* @var string
15+
*/
16+
protected $type;
17+
protected $obj;
18+
protected $registry = [];
19+
20+
public function __construct(string $type, ResourceMember ...$members)
21+
{
22+
if (isValidName($type) === false) {
23+
throw new \DomainException("Invalid type value: $type");
24+
}
25+
$this->obj = (object) ['type' => $type];
26+
$this->type = $type;
27+
28+
$this->addMembers(...$members);
29+
}
30+
31+
/**
32+
* @param ResourceMember ...$members
33+
* @internal
34+
*/
35+
protected function addMembers(ResourceMember ...$members): void
36+
{
37+
$fields = [];
38+
foreach ($members as $member) {
39+
if ($member instanceof Identifier) {
40+
$member->registerIn($this->registry);
41+
}
42+
if ($member instanceof ResourceField) {
43+
$name = $member->name();
44+
if (isset($fields[$name])) {
45+
throw new \LogicException("Field '$name' already exists'");
46+
}
47+
$fields[$name] = true;
48+
}
49+
$member->attachTo($this->obj);
50+
}
51+
}
52+
53+
/**
54+
* @param object $o
55+
* @internal
56+
*/
57+
public function attachTo($o): void
58+
{
59+
$o->data = $this->obj;
60+
}
61+
}

Diff for: src/Internal/PrimaryData.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
/**
66
* @internal
77
*/
8-
interface PrimaryData extends Attachable, Identifier
8+
interface PrimaryData extends Attachable
99
{
1010
}

Diff for: src/NewResourceObject.php

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace JsonApiPhp\JsonApi;
4+
5+
use JsonApiPhp\JsonApi\Internal\BaseResource;
6+
use JsonApiPhp\JsonApi\Internal\PrimaryData;
7+
use JsonApiPhp\JsonApi\Internal\ResourceMember;
8+
9+
/**
10+
* A resource to-be-created on the server. Does not have the `id` yet.
11+
*
12+
* Class NewResourceObject
13+
*/
14+
final class NewResourceObject extends BaseResource implements PrimaryData
15+
{
16+
public function __construct(string $type, ResourceMember ...$members)
17+
{
18+
parent::__construct($type, ...$members);
19+
}
20+
}

Diff for: src/ResourceObject.php

+4-27
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,21 @@
22

33
namespace JsonApiPhp\JsonApi;
44

5-
use JsonApiPhp\JsonApi\Internal\Identifier;
5+
use JsonApiPhp\JsonApi\Internal\BaseResource;
66
use JsonApiPhp\JsonApi\Internal\PrimaryData;
7-
use JsonApiPhp\JsonApi\Internal\ResourceField;
87
use JsonApiPhp\JsonApi\Internal\ResourceMember;
98

10-
final class ResourceObject implements PrimaryData
9+
final class ResourceObject extends BaseResource implements PrimaryData
1110
{
12-
private $obj;
13-
private $registry = [];
14-
/**
15-
* @var string
16-
*/
17-
private $type;
1811
/**
1912
* @var string
2013
*/
2114
private $id;
2215

2316
public function __construct(string $type, string $id, ResourceMember ...$members)
2417
{
25-
if (isValidName($type) === false) {
26-
throw new \DomainException("Invalid type value: $type");
27-
}
28-
$this->obj = (object) ['type' => $type, 'id' => $id];
29-
$fields = [];
30-
foreach ($members as $member) {
31-
if ($member instanceof Identifier) {
32-
$member->registerIn($this->registry);
33-
}
34-
if ($member instanceof ResourceField) {
35-
$name = $member->name();
36-
if (isset($fields[$name])) {
37-
throw new \LogicException("Field '$name' already exists'");
38-
}
39-
$fields[$name] = true;
40-
}
41-
$member->attachTo($this->obj);
42-
}
18+
parent::__construct($type, ...$members);
19+
$this->obj->id = $id;
4320
$this->type = $type;
4421
$this->id = $id;
4522
}

Diff for: test/NewResourceObjectTest.php

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace JsonApiPhp\JsonApi\Test;
4+
5+
use JsonApiPhp\JsonApi\Attribute;
6+
use JsonApiPhp\JsonApi\DataDocument;
7+
use JsonApiPhp\JsonApi\EmptyRelationship;
8+
use JsonApiPhp\JsonApi\Link\RelatedLink;
9+
use JsonApiPhp\JsonApi\Link\SelfLink;
10+
use JsonApiPhp\JsonApi\Meta;
11+
use JsonApiPhp\JsonApi\NewResourceObject;
12+
use JsonApiPhp\JsonApi\ResourceIdentifier;
13+
use JsonApiPhp\JsonApi\ResourceIdentifierCollection;
14+
use JsonApiPhp\JsonApi\ToMany;
15+
use JsonApiPhp\JsonApi\ToNull;
16+
use JsonApiPhp\JsonApi\ToOne;
17+
18+
class NewResourceObjectTest extends BaseTestCase
19+
{
20+
public function testFullFledgedResourceObject()
21+
{
22+
$this->assertEncodesTo(
23+
'
24+
{
25+
"data": {
26+
"type": "apples",
27+
"attributes": {
28+
"title": "Rails is Omakase"
29+
},
30+
"meta": {"foo": "bar"},
31+
"relationships": {
32+
"author": {
33+
"meta": {"foo": "bar"},
34+
"data": null
35+
}
36+
}
37+
}
38+
}
39+
',
40+
new DataDocument(
41+
new NewResourceObject(
42+
'apples',
43+
new Meta('foo', 'bar'),
44+
new Attribute('title', 'Rails is Omakase'),
45+
new ToNull(
46+
'author',
47+
new Meta('foo', 'bar')
48+
)
49+
)
50+
)
51+
);
52+
}
53+
54+
public function testRelationshipWithSingleIdLinkage()
55+
{
56+
$this->assertEncodesTo(
57+
'
58+
{
59+
"data": {
60+
"type": "basket",
61+
"relationships": {
62+
"content": {
63+
"data": {"type": "apples", "id": "1"}
64+
}
65+
}
66+
}
67+
}
68+
',
69+
new DataDocument(
70+
new NewResourceObject(
71+
'basket',
72+
new ToOne('content', new ResourceIdentifier('apples', '1'))
73+
)
74+
)
75+
);
76+
}
77+
78+
public function testRelationshipWithMultiIdLinkage()
79+
{
80+
$this->assertEncodesTo(
81+
'
82+
{
83+
"data": {
84+
"type": "basket",
85+
"relationships": {
86+
"content": {
87+
"data": [{
88+
"type": "apples",
89+
"id": "1"
90+
},{
91+
"type": "pears",
92+
"id": "2"
93+
}]
94+
}
95+
}
96+
}
97+
}
98+
',
99+
new DataDocument(
100+
new NewResourceObject(
101+
'basket',
102+
new ToMany(
103+
'content',
104+
new ResourceIdentifierCollection(
105+
new ResourceIdentifier('apples', '1'),
106+
new ResourceIdentifier('pears', '2')
107+
)
108+
)
109+
)
110+
)
111+
);
112+
}
113+
114+
public function testRelationshipWithEmptyMultiIdLinkage()
115+
{
116+
$this->assertEncodesTo(
117+
'
118+
{
119+
"data": {
120+
"type": "basket",
121+
"relationships": {
122+
"content": {
123+
"data": []
124+
}
125+
}
126+
}
127+
}
128+
',
129+
new DataDocument(
130+
new NewResourceObject(
131+
'basket',
132+
new ToMany('content', new ResourceIdentifierCollection())
133+
)
134+
)
135+
);
136+
}
137+
138+
public function testRelationshipWithNoData()
139+
{
140+
$this->assertEncodesTo(
141+
'
142+
{
143+
"data": {
144+
"type": "basket",
145+
"relationships": {
146+
"empty": {
147+
"links": {
148+
"related": "/foo"
149+
}
150+
}
151+
}
152+
}
153+
}
154+
',
155+
new DataDocument(
156+
new NewResourceObject(
157+
'basket',
158+
new EmptyRelationship('empty', new RelatedLink('/foo'))
159+
)
160+
)
161+
);
162+
163+
$this->assertEncodesTo(
164+
'
165+
{
166+
"data": {
167+
"type": "basket",
168+
"relationships": {
169+
"empty": {
170+
"links": {
171+
"related": "/foo",
172+
"self": "/bar"
173+
},
174+
"meta": {
175+
"foo": "bar"
176+
}
177+
}
178+
}
179+
}
180+
}
181+
',
182+
new DataDocument(
183+
new NewResourceObject(
184+
'basket',
185+
new EmptyRelationship('empty', new RelatedLink('/foo'), new SelfLink('/bar'), new Meta('foo', 'bar'))
186+
)
187+
)
188+
);
189+
}
190+
191+
public function testResourceFieldsMustBeUnique()
192+
{
193+
$this->expectException(\LogicException::class);
194+
$this->expectExceptionMessage("Field 'foo' already exists");
195+
new NewResourceObject(
196+
'apples',
197+
new Attribute('foo', 'bar'),
198+
new ToOne('foo', new ResourceIdentifier('apples', '1'))
199+
);
200+
}
201+
202+
public function testNameValidation()
203+
{
204+
$this->expectException(\DomainException::class);
205+
new NewResourceObject('invalid:id');
206+
}
207+
}

0 commit comments

Comments
 (0)