Skip to content

Commit

Permalink
NewResourceObject to allow omitting id in resources to-be-created (#108)
Browse files Browse the repository at this point in the history
  • Loading branch information
f3ath authored Oct 13, 2020
1 parent 4eb1497 commit fcecfd3
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 30 deletions.
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

## [2.2.0] - 2020-10-12
### Added
- `NewResourceObject` to allow omitting `id` in resources to-be-created (#108)

## [2.1.2] - 2020-03-16
### Fixed
- Related links must be allowed inside relationship documents (#104)

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

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

[Unreleased]: https://github.com/json-api-php/json-api/compare/2.1.2...HEAD
[Unreleased]: https://github.com/json-api-php/json-api/compare/2.2.0...HEAD
[2.2.0]: https://github.com/json-api-php/json-api/compare/2.1.2...2.2.0
[2.1.2]: https://github.com/json-api-php/json-api/compare/2.1.1...2.1.2
[2.1.1]: https://github.com/json-api-php/json-api/compare/2.1.0...2.1.1
[2.1.0]: https://github.com/json-api-php/json-api/compare/2.0.1...2.1.0
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ The library API and use-cases are expressed in a comprehensive suite of tests.
- [with null data](./test/DataDocument/NullDataTest.php)
- [with multiple Resource Objects](./test/DataDocument/ManyResourceObjectsTest.php)
- [with multiple Resource Identifiers](./test/DataDocument/ManyResourceIdentifiersTest.php)
- [with a new Resource (no id)](./test/NewResourceObjectTest.php)
- [Compound Documents](./test/CompoundDocumentTest.php)
- [Error Documents](./test/ErrorDocumentTest.php)
- [Meta Documents (containing neither data nor errors)](./test/MetaDocumentTest.php)
Expand Down
1 change: 1 addition & 0 deletions src/Internal/Attachable.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
interface Attachable
{
/**
* Adds this object's data to $o
* @param object $o
* @internal
*/
Expand Down
61 changes: 61 additions & 0 deletions src/Internal/BaseResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php declare(strict_types=1);

namespace JsonApiPhp\JsonApi\Internal;

use function JsonApiPhp\JsonApi\isValidName;

/**
* Class BaseResource
* @internal
*/
class BaseResource implements Attachable
{
/**
* @var string
*/
protected $type;
protected $obj;
protected $registry = [];

public function __construct(string $type, ResourceMember ...$members)
{
if (isValidName($type) === false) {
throw new \DomainException("Invalid type value: $type");
}
$this->obj = (object) ['type' => $type];
$this->type = $type;

$this->addMembers(...$members);
}

/**
* @param ResourceMember ...$members
* @internal
*/
protected function addMembers(ResourceMember ...$members): void
{
$fields = [];
foreach ($members as $member) {
if ($member instanceof Identifier) {
$member->registerIn($this->registry);
}
if ($member instanceof ResourceField) {
$name = $member->name();
if (isset($fields[$name])) {
throw new \LogicException("Field '$name' already exists'");
}
$fields[$name] = true;
}
$member->attachTo($this->obj);
}
}

/**
* @param object $o
* @internal
*/
public function attachTo($o): void
{
$o->data = $this->obj;
}
}
2 changes: 1 addition & 1 deletion src/Internal/PrimaryData.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
/**
* @internal
*/
interface PrimaryData extends Attachable, Identifier
interface PrimaryData extends Attachable
{
}
20 changes: 20 additions & 0 deletions src/NewResourceObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php declare(strict_types=1);

namespace JsonApiPhp\JsonApi;

use JsonApiPhp\JsonApi\Internal\BaseResource;
use JsonApiPhp\JsonApi\Internal\PrimaryData;
use JsonApiPhp\JsonApi\Internal\ResourceMember;

/**
* A resource to-be-created on the server. Does not have the `id` yet.
*
* Class NewResourceObject
*/
final class NewResourceObject extends BaseResource implements PrimaryData
{
public function __construct(string $type, ResourceMember ...$members)
{
parent::__construct($type, ...$members);
}
}
31 changes: 4 additions & 27 deletions src/ResourceObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,21 @@

namespace JsonApiPhp\JsonApi;

use JsonApiPhp\JsonApi\Internal\Identifier;
use JsonApiPhp\JsonApi\Internal\BaseResource;
use JsonApiPhp\JsonApi\Internal\PrimaryData;
use JsonApiPhp\JsonApi\Internal\ResourceField;
use JsonApiPhp\JsonApi\Internal\ResourceMember;

final class ResourceObject implements PrimaryData
final class ResourceObject extends BaseResource implements PrimaryData
{
private $obj;
private $registry = [];
/**
* @var string
*/
private $type;
/**
* @var string
*/
private $id;

public function __construct(string $type, string $id, ResourceMember ...$members)
{
if (isValidName($type) === false) {
throw new \DomainException("Invalid type value: $type");
}
$this->obj = (object) ['type' => $type, 'id' => $id];
$fields = [];
foreach ($members as $member) {
if ($member instanceof Identifier) {
$member->registerIn($this->registry);
}
if ($member instanceof ResourceField) {
$name = $member->name();
if (isset($fields[$name])) {
throw new \LogicException("Field '$name' already exists'");
}
$fields[$name] = true;
}
$member->attachTo($this->obj);
}
parent::__construct($type, ...$members);
$this->obj->id = $id;
$this->type = $type;
$this->id = $id;
}
Expand Down
207 changes: 207 additions & 0 deletions test/NewResourceObjectTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<?php declare(strict_types=1);

namespace JsonApiPhp\JsonApi\Test;

use JsonApiPhp\JsonApi\Attribute;
use JsonApiPhp\JsonApi\DataDocument;
use JsonApiPhp\JsonApi\EmptyRelationship;
use JsonApiPhp\JsonApi\Link\RelatedLink;
use JsonApiPhp\JsonApi\Link\SelfLink;
use JsonApiPhp\JsonApi\Meta;
use JsonApiPhp\JsonApi\NewResourceObject;
use JsonApiPhp\JsonApi\ResourceIdentifier;
use JsonApiPhp\JsonApi\ResourceIdentifierCollection;
use JsonApiPhp\JsonApi\ToMany;
use JsonApiPhp\JsonApi\ToNull;
use JsonApiPhp\JsonApi\ToOne;

class NewResourceObjectTest extends BaseTestCase
{
public function testFullFledgedResourceObject()
{
$this->assertEncodesTo(
'
{
"data": {
"type": "apples",
"attributes": {
"title": "Rails is Omakase"
},
"meta": {"foo": "bar"},
"relationships": {
"author": {
"meta": {"foo": "bar"},
"data": null
}
}
}
}
',
new DataDocument(
new NewResourceObject(
'apples',
new Meta('foo', 'bar'),
new Attribute('title', 'Rails is Omakase'),
new ToNull(
'author',
new Meta('foo', 'bar')
)
)
)
);
}

public function testRelationshipWithSingleIdLinkage()
{
$this->assertEncodesTo(
'
{
"data": {
"type": "basket",
"relationships": {
"content": {
"data": {"type": "apples", "id": "1"}
}
}
}
}
',
new DataDocument(
new NewResourceObject(
'basket',
new ToOne('content', new ResourceIdentifier('apples', '1'))
)
)
);
}

public function testRelationshipWithMultiIdLinkage()
{
$this->assertEncodesTo(
'
{
"data": {
"type": "basket",
"relationships": {
"content": {
"data": [{
"type": "apples",
"id": "1"
},{
"type": "pears",
"id": "2"
}]
}
}
}
}
',
new DataDocument(
new NewResourceObject(
'basket',
new ToMany(
'content',
new ResourceIdentifierCollection(
new ResourceIdentifier('apples', '1'),
new ResourceIdentifier('pears', '2')
)
)
)
)
);
}

public function testRelationshipWithEmptyMultiIdLinkage()
{
$this->assertEncodesTo(
'
{
"data": {
"type": "basket",
"relationships": {
"content": {
"data": []
}
}
}
}
',
new DataDocument(
new NewResourceObject(
'basket',
new ToMany('content', new ResourceIdentifierCollection())
)
)
);
}

public function testRelationshipWithNoData()
{
$this->assertEncodesTo(
'
{
"data": {
"type": "basket",
"relationships": {
"empty": {
"links": {
"related": "/foo"
}
}
}
}
}
',
new DataDocument(
new NewResourceObject(
'basket',
new EmptyRelationship('empty', new RelatedLink('/foo'))
)
)
);

$this->assertEncodesTo(
'
{
"data": {
"type": "basket",
"relationships": {
"empty": {
"links": {
"related": "/foo",
"self": "/bar"
},
"meta": {
"foo": "bar"
}
}
}
}
}
',
new DataDocument(
new NewResourceObject(
'basket',
new EmptyRelationship('empty', new RelatedLink('/foo'), new SelfLink('/bar'), new Meta('foo', 'bar'))
)
)
);
}

public function testResourceFieldsMustBeUnique()
{
$this->expectException(\LogicException::class);
$this->expectExceptionMessage("Field 'foo' already exists");
new NewResourceObject(
'apples',
new Attribute('foo', 'bar'),
new ToOne('foo', new ResourceIdentifier('apples', '1'))
);
}

public function testNameValidation()
{
$this->expectException(\DomainException::class);
new NewResourceObject('invalid:id');
}
}

0 comments on commit fcecfd3

Please sign in to comment.