From 8b98f7d47f0a2c981a694be1ca6e71a9ff07eca7 Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 30 Aug 2017 17:24:13 -0700 Subject: [PATCH 01/45] Deprecated ArrayMeta (#48) --- README.md | 9 +++++---- src/Document/ArrayMeta.php | 3 +++ src/Document/Meta.php | 5 +++++ test/Document/DocumentTest.php | 4 ++-- test/Document/ErrorTest.php | 2 +- test/Document/MetaTest.php | 4 ++-- test/Document/Resource/Relationship/RelationshipTest.php | 3 +-- test/Document/Resource/ResourceFieldsTest.php | 8 ++++---- test/Document/Resource/ResourceTest.php | 8 ++++---- test/IntegrationTest.php | 6 +++--- 10 files changed, 30 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 4e43a2e..d38123c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Implementation of [JSON API](http://jsonapi.org) in PHP 7 This library is an attempt to express business rules of JSON API specification in a set of PHP 7 classes. -A simple example to illustrate the general idea. This (slightly modified) JSON representation from +A simple example to illustrate the general idea. This JSON representation from [the documentation](http://jsonapi.org/format/#document-resource-objects) ```json @@ -19,8 +19,8 @@ A simple example to illustrate the general idea. This (slightly modified) JSON r "id": "9" }, "links": { - "self": "\/articles\/1\/relationships\/author", - "related": "\/articles\/1\/author" + "self": "/articles/1/relationships/author", + "related": "/articles/1/author" } } } @@ -29,6 +29,7 @@ A simple example to illustrate the general idea. This (slightly modified) JSON r ``` can be built with the following php code: ```php +setLink('related', '/articles/1/author'); $articles->setRelationship('author', $author); $articles->setAttribute('title', 'Rails is Omakase'); $doc = Document::fromResource($articles); -echo json_encode($doc, JSON_PRETTY_PRINT); +echo json_encode($doc, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); ``` Please refer to [the tests](./test) for the full API documentation: diff --git a/src/Document/ArrayMeta.php b/src/Document/ArrayMeta.php index 91473a4..b41444d 100644 --- a/src/Document/ArrayMeta.php +++ b/src/Document/ArrayMeta.php @@ -11,6 +11,9 @@ namespace JsonApiPhp\JsonApi\Document; +/** + * @deprecated to be removed in 1.0, use Meta::fromArray() + */ class ArrayMeta extends Meta { public function __construct(array $array) diff --git a/src/Document/Meta.php b/src/Document/Meta.php index f4d060a..dd45f88 100644 --- a/src/Document/Meta.php +++ b/src/Document/Meta.php @@ -20,6 +20,11 @@ public function __construct(\stdClass $data) $this->data = $data; } + public static function fromArray(array $array): self + { + return new self((object) $array); + } + public function jsonSerialize() { return $this->data; diff --git a/test/Document/DocumentTest.php b/test/Document/DocumentTest.php index d8b63e9..000456f 100644 --- a/test/Document/DocumentTest.php +++ b/test/Document/DocumentTest.php @@ -12,8 +12,8 @@ namespace JsonApiPhp\JsonApi\Test\Document; use JsonApiPhp\JsonApi\Document; -use JsonApiPhp\JsonApi\Document\ArrayMeta; use JsonApiPhp\JsonApi\Document\Error; +use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\NullResource; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Test\BaseTestCase; @@ -24,7 +24,7 @@ public function testCanCreateFromMeta() { $this->assertEqualsAsJson( ['meta' => ['foo' => 'bar']], - Document::fromMeta(new ArrayMeta(['foo' => 'bar'])) + Document::fromMeta(Meta::fromArray(['foo' => 'bar'])) ); } diff --git a/test/Document/ErrorTest.php b/test/Document/ErrorTest.php index 0f76612..dc65371 100644 --- a/test/Document/ErrorTest.php +++ b/test/Document/ErrorTest.php @@ -31,7 +31,7 @@ public function testErrorWithFullSetOfProperties() $e->setDetail('Nothing is found'); $e->setSourcePointer('/data'); $e->setSourceParameter('test_param'); - $e->setMeta(new ArrayMeta(['foo' => 'bar'])); + $e->setMeta(Meta::fromArray(['foo' => 'bar'])); $this->assertEqualsAsJson( [ diff --git a/test/Document/MetaTest.php b/test/Document/MetaTest.php index 928dba0..46eb96a 100644 --- a/test/Document/MetaTest.php +++ b/test/Document/MetaTest.php @@ -11,13 +11,13 @@ namespace JsonApiPhp\JsonApi\Test\Document; -use JsonApiPhp\JsonApi\Document\ArrayMeta; +use JsonApiPhp\JsonApi\Document\Meta; use PHPUnit\Framework\TestCase; class MetaTest extends TestCase { public function testPhpArraysAreConvertedToObjects() { - $this->assertEquals('{"0":"foo"}', json_encode(new ArrayMeta(['foo']))); + $this->assertEquals('{"0":"foo"}', json_encode(Meta::fromArray(['foo']))); } } diff --git a/test/Document/Resource/Relationship/RelationshipTest.php b/test/Document/Resource/Relationship/RelationshipTest.php index c98948d..383bd6b 100644 --- a/test/Document/Resource/Relationship/RelationshipTest.php +++ b/test/Document/Resource/Relationship/RelationshipTest.php @@ -11,7 +11,6 @@ namespace JsonApiPhp\JsonApi\Test\Document\Resource\Relationship; -use JsonApiPhp\JsonApi\Document\ArrayMeta; use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Relationship\Linkage; use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; @@ -91,7 +90,7 @@ public function testCanCreateFromMeta() 'a' => 'b', ], ], - Relationship::fromMeta(new ArrayMeta(['a' => 'b'])) + Relationship::fromMeta(Meta::fromArray(['a' => 'b'])) ); } } diff --git a/test/Document/Resource/ResourceFieldsTest.php b/test/Document/Resource/ResourceFieldsTest.php index e14e0b2..ffe323e 100644 --- a/test/Document/Resource/ResourceFieldsTest.php +++ b/test/Document/Resource/ResourceFieldsTest.php @@ -11,7 +11,7 @@ namespace JsonApiPhp\JsonApi\Test\Document\Resource; -use JsonApiPhp\JsonApi\Document\ArrayMeta; +use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use PHPUnit\Framework\TestCase; @@ -37,7 +37,7 @@ public function testCanNotSetRelationshipIfAttributeExists() { $res = new ResourceObject('books', '1'); $res->setAttribute('foo', 'bar'); - $res->setRelationship('foo', Relationship::fromMeta(new ArrayMeta(['a' => 'b']))); + $res->setRelationship('foo', Relationship::fromMeta(Meta::fromArray(['a' => 'b']))); } /** @@ -47,7 +47,7 @@ public function testCanNotSetRelationshipIfAttributeExists() public function testCanNotSetAttributeIfRelationshipExists() { $res = new ResourceObject('books', '1'); - $res->setRelationship('foo', Relationship::fromMeta(new ArrayMeta(['a' => 'b']))); + $res->setRelationship('foo', Relationship::fromMeta(Meta::fromArray(['a' => 'b']))); $res->setAttribute('foo', 'bar'); } @@ -72,7 +72,7 @@ public function testAttributeCanNotHaveReservedNames(string $name) public function testRelationshipCanNotHaveReservedNames(string $name) { $res = new ResourceObject('books', 'abc'); - $res->setRelationship($name, Relationship::fromMeta(new ArrayMeta(['a' => 'b']))); + $res->setRelationship($name, Relationship::fromMeta(Meta::fromArray(['a' => 'b']))); } public function invalidAttributeNames(): array diff --git a/test/Document/Resource/ResourceTest.php b/test/Document/Resource/ResourceTest.php index 48957ca..0179836 100644 --- a/test/Document/Resource/ResourceTest.php +++ b/test/Document/Resource/ResourceTest.php @@ -11,7 +11,7 @@ namespace JsonApiPhp\JsonApi\Test\Document\Resource; -use JsonApiPhp\JsonApi\Document\ArrayMeta; +use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; @@ -58,7 +58,7 @@ public function resourceProvider() 'foo' => 'bar', ], ], - new ResourceIdentifier('books', '42abc', new ArrayMeta(['foo' => 'bar'])), + new ResourceIdentifier('books', '42abc', Meta::fromArray(['foo' => 'bar'])), ], [ [ @@ -83,10 +83,10 @@ public function resourceProvider() ], (function () { $resource = new ResourceObject('books', '42abc'); - $resource->setMeta(new ArrayMeta(['foo' => 'bar'])); + $resource->setMeta(Meta::fromArray(['foo' => 'bar'])); $resource->setAttribute('attr', 'val'); $resource->setLink('self', 'http://localhost'); - $resource->setRelationship('author', Relationship::fromMeta(new ArrayMeta(['a' => 'b']))); + $resource->setRelationship('author', Relationship::fromMeta(Meta::fromArray(['a' => 'b']))); return $resource; })(), ], diff --git a/test/IntegrationTest.php b/test/IntegrationTest.php index d81c007..211ed25 100644 --- a/test/IntegrationTest.php +++ b/test/IntegrationTest.php @@ -37,8 +37,8 @@ public function testFromTheReadmeFile() "id": "9" }, "links": { - "self": "\/articles\/1\/relationships\/author", - "related": "\/articles\/1\/author" + "self": "/articles/1/relationships/author", + "related": "/articles/1/author" } } } @@ -60,7 +60,7 @@ public function testFromTheReadmeFile() $this->assertEquals( $json, - json_encode($doc, JSON_PRETTY_PRINT) + json_encode($doc, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) ); } } From caa16e48a2ad433a1cec12040f86b3fb0d3ef841 Mon Sep 17 00:00:00 2001 From: Alexey Date: Fri, 1 Sep 2017 16:39:21 -0700 Subject: [PATCH 02/45] improved docs (#49) --- LICENSE | 2 +- README.md | 5 +- src/Document/Error.php | 8 + test/BaseTestCase.php | 4 +- test/Document/CompoundDocumentTest.php | 102 +++++--- test/Document/DocumentTest.php | 240 +++++++++++------- test/Document/ErrorTest.php | 40 +-- test/Document/MetaTest.php | 6 +- .../Resource/Relationship/LinkageTest.php | 49 ++-- .../Relationship/RelationshipTest.php | 52 ++-- test/Document/Resource/ResourceTest.php | 75 +++--- 11 files changed, 346 insertions(+), 237 deletions(-) diff --git a/LICENSE b/LICENSE index c10e57a..efe2a4d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Alexey +Copyright (c) 2017 Alexey Karapetov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index d38123c..7dcb01c 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,9 @@ echo json_encode($doc, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); ``` Please refer to [the tests](./test) for the full API documentation: -* [Documents](./test/Document/DocumentTest.php) - * [Compound Documents](./test/Document/CompoundDocumentTest.php) +* [Documents](./test/Document/DocumentTest.php). Creating documents with primary data, errors, and meta. +Adding links and API version to a document. + * [Compound Documents](./test/Document/CompoundDocumentTest.php). Resource linkage. * [Errors](./test/Document/ErrorTest.php) * [Resources](./test/Document/Resource/ResourceTest.php) * [Relationships](./test/Document/Resource/Relationship/RelationshipTest.php) diff --git a/src/Document/Error.php b/src/Document/Error.php index 8537ee7..2a7d491 100644 --- a/src/Document/Error.php +++ b/src/Document/Error.php @@ -23,6 +23,14 @@ final class Error implements \JsonSerializable private $detail; private $source; + /** + * @param string $id + */ + public function __construct(string $id = null) + { + $this->id = $id; + } + public function setId(string $id) { $this->id = $id; diff --git a/test/BaseTestCase.php b/test/BaseTestCase.php index ce01e3d..cc7a5a4 100644 --- a/test/BaseTestCase.php +++ b/test/BaseTestCase.php @@ -15,8 +15,8 @@ abstract class BaseTestCase extends TestCase { - public static function assertEqualsAsJson($expected, $actual, string $message = '') + public static function assertEncodesTo(string $expected, $obj, string $message = '') { - self::assertEquals(json_encode($expected), json_encode($actual), $message); + self::assertEquals(json_encode(json_decode($expected)), json_encode($obj), $message); } } diff --git a/test/Document/CompoundDocumentTest.php b/test/Document/CompoundDocumentTest.php index 6255a32..2b3ef27 100644 --- a/test/Document/CompoundDocumentTest.php +++ b/test/Document/CompoundDocumentTest.php @@ -40,13 +40,17 @@ */ class CompoundDocumentTest extends BaseTestCase { + /** + * In a compound document, all included resources MUST be represented as an array of resource objects + * in a top-level included member. + */ public function testIncludedResourcesRepresentedAsArray() { $apple = new ResourceObject('apples', '1'); $apple->setAttribute('color', 'red'); $orange = new ResourceObject('oranges', '1'); $orange->setAttribute('color', 'orange'); - $basket = new ResourceObject('basket', '1'); + $basket = new ResourceObject('baskets', '1'); $basket->setRelationship( 'fruits', Relationship::fromLinkage( @@ -56,26 +60,48 @@ public function testIncludedResourcesRepresentedAsArray() ) ) ); - $doc = \JsonApiPhp\JsonApi\Document::fromResource($basket); + $doc = Document::fromResource($basket); $doc->setIncluded($apple, $orange); - $this->assertEquals( - [ - [ - 'type' => 'apples', - 'id' => '1', - 'attributes' => [ - 'color' => 'red', - ], - ], - [ - 'type' => 'oranges', - 'id' => '1', - 'attributes' => [ - 'color' => 'orange', - ], - ], - ], - $this->convertToArray($doc)['included'] + $this->assertEncodesTo( + ' + { + "data": { + "type": "baskets", + "id": "1", + "relationships": { + "fruits": { + "data": [ + { + "type": "apples", + "id": "1" + }, + { + "type": "oranges", + "id": "1" + } + ] + } + } + }, + "included": [ + { + "type": "apples", + "id": "1", + "attributes": { + "color": "red" + } + }, + { + "type": "oranges", + "id": "1", + "attributes": { + "color": "orange" + } + } + ] + } + ', + $doc ); } @@ -90,12 +116,28 @@ public function testFullLinkageIsRequired() json_encode($doc); } + /** + * A compound document must be explicitly marked as sparse. In this case full linkage is not required. + */ public function testFullLinkageIsNotRequiredIfSparse() { - $doc = \JsonApiPhp\JsonApi\Document::fromResource(new NullResource); + $doc = Document::fromResource(new NullResource); $doc->markSparse(); $doc->setIncluded(new ResourceObject('apples', '1')); - $this->assertCanBeBuilt($doc); + $this->assertEncodesTo( + ' + { + "data": null, + "included": [ + { + "type": "apples", + "id": "1" + } + ] + } + ', + $doc + ); } public function testIncludedResourceMayBeIdentifiedByPrimaryData() @@ -104,7 +146,7 @@ public function testIncludedResourceMayBeIdentifiedByPrimaryData() $apple->setAttribute('color', 'red'); $doc = Document::fromResource($apple->toId()); $doc->setIncluded($apple); - $this->assertCanBeBuilt($doc); + $this->assertJson(json_encode($doc)); } public function testIncludedResourceMayBeIdentifiedByAnotherIncludedResource() @@ -124,18 +166,8 @@ public function testIncludedResourceMayBeIdentifiedByAnotherIncludedResource() ) ) ); - $doc = \JsonApiPhp\JsonApi\Document::fromResource($basket->toId()); + $doc = Document::fromResource($basket->toId()); $doc->setIncluded($apple, $basket); - $this->assertCanBeBuilt($doc); - } - - private function convertToArray($object): array - { - return json_decode(json_encode($object), true); - } - - private function assertCanBeBuilt($doc) - { - $this->assertInternalType('string', json_encode($doc)); + $this->assertJson(json_encode($doc)); } } diff --git a/test/Document/DocumentTest.php b/test/Document/DocumentTest.php index 000456f..e7d5255 100644 --- a/test/Document/DocumentTest.php +++ b/test/Document/DocumentTest.php @@ -16,131 +16,183 @@ use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\NullResource; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; +use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use JsonApiPhp\JsonApi\Test\BaseTestCase; +use JsonApiPhp\JsonApi\Test\Document\Resource\Relationship\LinkageTest; +use JsonApiPhp\JsonApi\Test\Document\Resource\ResourceTest; +/** + * This is the JSON document's top level object + */ class DocumentTest extends BaseTestCase { - public function testCanCreateFromMeta() + /** + * A valid document may contain just a meta object. + */ + public function testDocumentMayContainJustMeta() { - $this->assertEqualsAsJson( - ['meta' => ['foo' => 'bar']], - Document::fromMeta(Meta::fromArray(['foo' => 'bar'])) + $this->assertEncodesTo( + ' + { + "meta": { + "foo": "bar" + } + } + ', + Document::fromMeta( + Meta::fromArray(['foo' => 'bar']) + ) ); } - public function testCanCreateFromErrors() + /** + * A valid document may contain just an array of errors. + * The array of errors may even be empty, the documentation does not explicitly restrict it. + */ + public function testDocumentMayContainJustErrors() { - $this->assertEqualsAsJson( - ['errors' => []], - Document::fromErrors() + $this->assertEncodesTo( + ' + { + "errors": [ + { + "id": "first" + } + ] + } + ', + Document::fromErrors(new Error('first')) ); - $this->assertEqualsAsJson( - [ - 'errors' => [ - ['id' => 'first'], - ], - ], - $this->createErrorDoc() + $this->assertEncodesTo( + ' + { + "errors": [] + } + ', + Document::fromErrors() ); } - public function testCanCreateNullDocument() + /** + * A valid document may contain just a primary data object. + * The primary data object is represented by ResourceInterface (@see ResourceTest for details). + * Here is how a document can be created from different kinds of resources: + * - null resource + * - resource identifier + * - full-fledged resource object + * - an array of resource objects/identifiers + */ + public function testDocumentMayContainJustData() { - $this->assertEqualsAsJson( - ['data' => null], - $this->createNullDoc() + $this->assertEncodesTo( + ' + { + "data": null + } + ', + Document::fromResource(new NullResource), + 'The simplest document possible contains null' ); - } - public function testCanCreateFromResourceId() - { - $this->assertEqualsAsJson( - [ - 'data' => [ - 'type' => 'books', - 'id' => 'abc123', - ], - ], - Document::fromResource(new ResourceIdentifier('books', 'abc123')) + $this->assertEncodesTo( + ' + { + "data": { + "type": "books", + "id": "abc123" + } + } + ', + Document::fromResource(new ResourceIdentifier('books', 'abc123')), + 'Resource identifier can be used as primary data' ); - } - public function testCanCreateFromMultipleItems() - { - $this->assertEqualsAsJson( - [ - 'data' => [], - ], - Document::fromResources() + $apple = new ResourceObject('apples', '007'); + $apple->setAttribute('color', 'red'); + $this->assertEncodesTo( + ' + { + "data": { + "type": "apples", + "id": "007", + "attributes": { + "color": "red" + } + } + } + ', + Document::fromResource($apple), + 'Full-fledged resource object' ); - $this->assertEqualsAsJson( - [ - 'data' => [ - [ - 'type' => 'books', - 'id' => '12', - ], - [ - 'type' => 'carrots', - 'id' => '42', - ], - ], - ], + $this->assertEncodesTo( + ' + { + "data": [ + { + "type": "books", + "id": "12" + }, + { + "type": "carrots", + "id": "42" + } + ] + } + ', Document::fromResources( new ResourceIdentifier('books', '12'), new ResourceIdentifier('carrots', '42') - ) + ), + 'An array of resource identifiers' ); } - public function testDocumentMayContainVersion() + /** + * When a document is created, it is possible to add more stuff to it: + * - API details + * - meta + * - links (@see LinkageTest for details) + */ + public function testDocumentCanHaveExtraProperties() { - $doc = $this->createNullDoc(); - $doc->setApiVersion('1.2.3'); - $doc->setApiMeta(['a' => 'b']); - $this->assertEqualsAsJson( - [ - 'data' => null, - 'jsonapi' => [ - 'version' => '1.2.3', - 'meta' => ['a' => 'b'], - ], - ], - $doc + $doc = Document::fromResource( + new ResourceIdentifier('apples', '42') ); - } - - public function testDocumentMayContainLinks() - { - $doc = $this->createNullDoc(); + $doc->setApiVersion('1.0'); + $doc->setApiMeta(['a' => 'b']); + $doc->setMeta(Meta::fromArray(['test' => 'test'])); $doc->setLink('self', 'http://example.com/self'); - $doc->setLink('related', 'http://example.com/rel', ['a' => 'b']); - $this->assertEqualsAsJson( - [ - 'data' => null, - 'links' => [ - 'self' => 'http://example.com/self', - 'related' => [ - 'href' => 'http://example.com/rel', - 'meta' => ['a' => 'b'], - ], - ], - ], + $doc->setLink('related', 'http://example.com/rel', ['foo' => 'bar']); + $this->assertEncodesTo( + ' + { + "data": { + "type": "apples", + "id": "42" + }, + "meta": { + "test": "test" + }, + "jsonapi": { + "version": "1.0", + "meta": { + "a": "b" + } + }, + "links": { + "self": "http://example.com/self", + "related": { + "href": "http://example.com/rel", + "meta": { + "foo": "bar" + } + } + } + } + ', $doc ); } - - private function createErrorDoc(): Document - { - $e = new Error(); - $e->setId('first'); - return Document::fromErrors($e); - } - - private function createNullDoc(): Document - { - return Document::fromResource(new NullResource); - } } diff --git a/test/Document/ErrorTest.php b/test/Document/ErrorTest.php index dc65371..ce036e0 100644 --- a/test/Document/ErrorTest.php +++ b/test/Document/ErrorTest.php @@ -17,7 +17,7 @@ class ErrorTest extends BaseTestCase { public function testEmptyErrorIsEmptyObject() { - $this->assertEquals('{}', json_encode(new Error())); + $this->assertEncodesTo('{}', new Error()); } public function testErrorWithFullSetOfProperties() @@ -33,24 +33,26 @@ public function testErrorWithFullSetOfProperties() $e->setSourceParameter('test_param'); $e->setMeta(Meta::fromArray(['foo' => 'bar'])); - $this->assertEqualsAsJson( - [ - 'id' => 'test_id', - 'links' => [ - 'about' => 'http://localhost', - ], - 'status' => '404', - 'code' => 'OMG', - 'title' => 'Error', - 'detail' => 'Nothing is found', - 'source' => [ - 'pointer' => '/data', - 'parameter' => 'test_param', - ], - 'meta' => [ - 'foo' => 'bar', - ], - ], + $this->assertEncodesTo( + ' + { + "id": "test_id", + "links": { + "about":"http://localhost" + }, + "status": "404", + "code": "OMG", + "title": "Error", + "detail": "Nothing is found", + "source": { + "pointer": "/data", + "parameter": "test_param" + }, + "meta": { + "foo":"bar" + } + } + ', $e ); } diff --git a/test/Document/MetaTest.php b/test/Document/MetaTest.php index 46eb96a..fd4ab9c 100644 --- a/test/Document/MetaTest.php +++ b/test/Document/MetaTest.php @@ -12,12 +12,12 @@ namespace JsonApiPhp\JsonApi\Test\Document; use JsonApiPhp\JsonApi\Document\Meta; -use PHPUnit\Framework\TestCase; +use JsonApiPhp\JsonApi\Test\BaseTestCase; -class MetaTest extends TestCase +class MetaTest extends BaseTestCase { public function testPhpArraysAreConvertedToObjects() { - $this->assertEquals('{"0":"foo"}', json_encode(Meta::fromArray(['foo']))); + $this->assertEncodesTo('{"0":"foo"}', Meta::fromArray(['foo'])); } } diff --git a/test/Document/Resource/Relationship/LinkageTest.php b/test/Document/Resource/Relationship/LinkageTest.php index b71222e..e71cb9e 100644 --- a/test/Document/Resource/Relationship/LinkageTest.php +++ b/test/Document/Resource/Relationship/LinkageTest.php @@ -23,7 +23,7 @@ * all of the included resource objects without having to GET any URLs via links. * * Resource linkage MUST be represented as one of the following: - * - null for empty to-one relationships. + * - null for empty-to-one relationships. * - an empty array ([]) for empty to-many relationships. * - a single resource identifier object for non-empty to-one relationships. * - an array of resource identifier objects for non-empty to-many relationships. @@ -38,45 +38,52 @@ class LinkageTest extends BaseTestCase { public function testCanCreateNullLinkage() { - $this->assertEqualsAsJson( - null, + $this->assertEncodesTo( + 'null', Linkage::nullLinkage() ); } public function testCanCreateEmptyArrayLinkage() { - $this->assertEqualsAsJson( - [], + $this->assertEncodesTo( + '[]', Linkage::emptyArrayLinkage() ); } public function testCanCreateFromSingleResourceId() { - $this->assertEqualsAsJson( - [ - 'type' => 'books', - 'id' => 'abc', - ], + $this->assertEncodesTo( + ' + { + "type": "books", + "id": "abc" + } + ', Linkage::fromSingleIdentifier(new ResourceIdentifier('books', 'abc')) ); } public function testCanCreateFromArrayOfResourceIds() { - $this->assertEqualsAsJson( + $this->assertEncodesTo( + ' [ - [ - 'type' => 'books', - 'id' => 'abc', - ], - [ - 'type' => 'squirrels', - 'id' => '123', - ], - ], - Linkage::fromManyIdentifiers(new ResourceIdentifier('books', 'abc'), new ResourceIdentifier('squirrels', '123')) + { + "type": "books", + "id": "abc" + }, + { + "type": "squirrels", + "id": "123" + } + ] + ', + Linkage::fromManyIdentifiers( + new ResourceIdentifier('books', 'abc'), + new ResourceIdentifier('squirrels', '123') + ) ); } diff --git a/test/Document/Resource/Relationship/RelationshipTest.php b/test/Document/Resource/Relationship/RelationshipTest.php index 383bd6b..45ad99e 100644 --- a/test/Document/Resource/Relationship/RelationshipTest.php +++ b/test/Document/Resource/Relationship/RelationshipTest.php @@ -50,46 +50,54 @@ class RelationshipTest extends BaseTestCase { public function testCanCreateFromSelfLink() { - $this->assertEqualsAsJson( - [ - 'links' => [ - 'self' => 'http://localhost', - ], - ], + $this->assertEncodesTo( + ' + { + "links": { + "self": "http://localhost" + } + } + ', Relationship::fromSelfLink('http://localhost') ); } public function testCanCreateFromRelatedLink() { - $this->assertEqualsAsJson( - [ - 'links' => [ - 'related' => 'http://localhost', - ], - ], + $this->assertEncodesTo( + ' + { + "links": { + "related": "http://localhost" + } + } + ', Relationship::fromRelatedLink('http://localhost') ); } public function testCanCreateFromLinkage() { - $this->assertEqualsAsJson( - [ - 'data' => null, - ], + $this->assertEncodesTo( + ' + { + "data": null + } + ', Relationship::fromLinkage(Linkage::nullLinkage()) ); } public function testCanCreateFromMeta() { - $this->assertEqualsAsJson( - [ - 'meta' => [ - 'a' => 'b', - ], - ], + $this->assertEncodesTo( + ' + { + "meta": { + "a": "b" + } + } + ', Relationship::fromMeta(Meta::fromArray(['a' => 'b'])) ); } diff --git a/test/Document/Resource/ResourceTest.php b/test/Document/Resource/ResourceTest.php index 0179836..97c0779 100644 --- a/test/Document/Resource/ResourceTest.php +++ b/test/Document/Resource/ResourceTest.php @@ -25,62 +25,61 @@ class ResourceTest extends BaseTestCase { /** - * @param array $expected - * @param mixed $data + * @param string $expected + * @param mixed $data * @dataProvider resourceProvider */ - public function testSerialization(array $expected, $data) + public function testSerialization(string $expected, $data) { - $this->assertEqualsAsJson($expected, $data); + $this->assertEncodesTo($expected, $data); } public function resourceProvider() { return [ [ - [ - 'type' => 'books', - ], + '{"type": "books"}', new ResourceIdentifier('books'), ], [ - [ - 'type' => 'books', - 'id' => '42abc', - ], + '{"type":"books","id":"42abc"}', new ResourceIdentifier('books', '42abc'), ], [ - [ - 'type' => 'books', - 'id' => '42abc', - 'meta' => [ - 'foo' => 'bar', - ], - ], + ' + { + "type": "books", + "id": "42abc", + "meta": { + "foo":"bar" + } + } + ', new ResourceIdentifier('books', '42abc', Meta::fromArray(['foo' => 'bar'])), ], [ - [ - 'type' => 'books', - 'id' => '42abc', - 'attributes' => [ - 'attr' => 'val', - ], - 'relationships' => [ - 'author' => [ - 'meta' => [ - 'a' => 'b', - ], - ], - ], - 'links' => [ - 'self' => 'http://localhost', - ], - 'meta' => [ - 'foo' => 'bar', - ], - ], + ' + { + "type": "books", + "id": "42abc", + "attributes": { + "attr": "val" + }, + "relationships": { + "author": { + "meta": { + "a": "b" + } + } + }, + "links": { + "self": "http://localhost" + }, + "meta": { + "foo": "bar" + } + } + ', (function () { $resource = new ResourceObject('books', '42abc'); $resource->setMeta(Meta::fromArray(['foo' => 'bar'])); From c43c2d03cec634e683e7167859b143ddf49a5932 Mon Sep 17 00:00:00 2001 From: Alexey Date: Fri, 1 Sep 2017 22:09:44 -0700 Subject: [PATCH 03/45] __toString has been removed from the interface --- src/Document/Resource/NullResource.php | 3 +++ src/Document/Resource/ResourceIdentifier.php | 3 +++ src/Document/Resource/ResourceInterface.php | 2 -- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Document/Resource/NullResource.php b/src/Document/Resource/NullResource.php index 8a92014..f30976d 100644 --- a/src/Document/Resource/NullResource.php +++ b/src/Document/Resource/NullResource.php @@ -23,6 +23,9 @@ public function identifies(ResourceInterface $resource): bool return false; } + /** + * @deprecated to be removed in 1.0 + */ public function __toString(): string { return 'null'; diff --git a/src/Document/Resource/ResourceIdentifier.php b/src/Document/Resource/ResourceIdentifier.php index 7153a11..9d28075 100644 --- a/src/Document/Resource/ResourceIdentifier.php +++ b/src/Document/Resource/ResourceIdentifier.php @@ -44,6 +44,9 @@ function ($v) { ); } + /** + * @deprecated to be removed in 1.0 + */ public function __toString(): string { return sprintf("%s:%s", $this->type, $this->id ?: 'null'); diff --git a/src/Document/Resource/ResourceInterface.php b/src/Document/Resource/ResourceInterface.php index aadbdf8..d8b558d 100644 --- a/src/Document/Resource/ResourceInterface.php +++ b/src/Document/Resource/ResourceInterface.php @@ -14,6 +14,4 @@ interface ResourceInterface extends \JsonSerializable { public function identifies(ResourceInterface $resource): bool; - - public function __toString(): string; } From 9475a1aebd991e68698d55a966805f896c88100e Mon Sep 17 00:00:00 2001 From: Alexey Date: Fri, 8 Sep 2017 23:35:44 -0700 Subject: [PATCH 04/45] Version 1.0 (#53) --- src/Document.php | 113 ++++---- .../PrimaryData/MultiIdentifierData.php | 40 +++ .../PrimaryData/MultiResourceData.php | 39 +++ src/Document/PrimaryData/NullData.php | 27 ++ .../PrimaryData/PrimaryDataInterface.php | 21 ++ .../PrimaryData/SingleIdentifierData.php | 35 +++ .../PrimaryData/SingleResourceData.php | 34 +++ .../Resource/Linkage/LinkageInterface.php | 20 ++ .../Resource/Linkage/MultiLinkage.php | 40 +++ .../NullLinkage.php} | 20 +- .../Resource/Linkage/SingleLinkage.php | 35 +++ .../Resource/Relationship/Linkage.php | 76 ------ .../Resource/Relationship/Relationship.php | 9 +- src/Document/Resource/ResourceIdentifier.php | 33 +-- src/Document/Resource/ResourceInterface.php | 17 -- src/Document/Resource/ResourceObject.php | 30 ++- test/BaseTestCase.php | 6 +- test/Document/CompoundDocumentTest.php | 254 +++++++++++++----- test/Document/DocumentTest.php | 11 +- .../Resource/Relationship/LinkageTest.php | 37 +-- .../Relationship/RelationshipTest.php | 3 +- test/Document/Resource/ResourceTest.php | 2 +- test/IntegrationTest.php | 4 +- 23 files changed, 621 insertions(+), 285 deletions(-) create mode 100644 src/Document/PrimaryData/MultiIdentifierData.php create mode 100644 src/Document/PrimaryData/MultiResourceData.php create mode 100644 src/Document/PrimaryData/NullData.php create mode 100644 src/Document/PrimaryData/PrimaryDataInterface.php create mode 100644 src/Document/PrimaryData/SingleIdentifierData.php create mode 100644 src/Document/PrimaryData/SingleResourceData.php create mode 100644 src/Document/Resource/Linkage/LinkageInterface.php create mode 100644 src/Document/Resource/Linkage/MultiLinkage.php rename src/Document/Resource/{NullResource.php => Linkage/NullLinkage.php} (56%) create mode 100644 src/Document/Resource/Linkage/SingleLinkage.php delete mode 100644 src/Document/Resource/Relationship/Linkage.php delete mode 100644 src/Document/Resource/ResourceInterface.php diff --git a/src/Document.php b/src/Document.php index fdec9ce..a102132 100644 --- a/src/Document.php +++ b/src/Document.php @@ -15,7 +15,13 @@ use JsonApiPhp\JsonApi\Document\LinksTrait; use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\MetaTrait; -use JsonApiPhp\JsonApi\Document\Resource\ResourceInterface; +use JsonApiPhp\JsonApi\Document\PrimaryData\MultiIdentifierData; +use JsonApiPhp\JsonApi\Document\PrimaryData\MultiResourceData; +use JsonApiPhp\JsonApi\Document\PrimaryData\NullData; +use JsonApiPhp\JsonApi\Document\PrimaryData\PrimaryDataInterface; +use JsonApiPhp\JsonApi\Document\PrimaryData\SingleIdentifierData; +use JsonApiPhp\JsonApi\Document\PrimaryData\SingleResourceData; +use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; class Document implements \JsonSerializable @@ -26,11 +32,23 @@ class Document implements \JsonSerializable use LinksTrait; use MetaTrait; + /** + * @var PrimaryDataInterface + */ private $data; + + /** + * @var Error[] + */ private $errors; + private $api; + + /** + * @var ResourceObject[] + */ private $included; - private $is_sparse = false; + private $sparse = false; private function __construct() { @@ -50,17 +68,38 @@ public static function fromErrors(Error ...$errors): self return $doc; } - public static function fromResource(ResourceInterface $data): self + public static function fromResource(ResourceObject $resource): self + { + $doc = new self; + $doc->data = new SingleResourceData($resource); + return $doc; + } + + public static function fromResources(ResourceObject ...$resources): self + { + $doc = new self; + $doc->data = new MultiResourceData(...$resources); + return $doc; + } + + public static function fromIdentifier(ResourceIdentifier $identifier) + { + $doc = new self; + $doc->data = new SingleIdentifierData($identifier); + return $doc; + } + + public static function fromIdentifiers(ResourceIdentifier... $identifiers) { $doc = new self; - $doc->data = $data; + $doc->data = new MultiIdentifierData(...$identifiers); return $doc; } - public static function fromResources(ResourceInterface ...$data): self + public static function nullDocument() { $doc = new self; - $doc->data = $data; + $doc->data = new NullData(); return $doc; } @@ -69,19 +108,27 @@ public function setApiVersion(string $version = self::DEFAULT_API_VERSION) $this->api['version'] = $version; } - public function setApiMeta(array $meta) + public function setApiMeta(Meta $meta) { $this->api['meta'] = $meta; } - public function setIncluded(ResourceObject ...$included) + public function setIncluded(ResourceObject ...$resources) { - $this->included = $included; + if (null === $this->data) { + throw new \LogicException('Document with no data cannot contain included resources'); + } + foreach ($resources as $resource) { + if (isset($this->included[(string) $resource->toIdentifier()])) { + throw new \LogicException("Resource {$resource->toIdentifier()} is already included"); + } + $this->included[(string) $resource->toIdentifier()] = $resource; + } } public function markSparse() { - $this->is_sparse = true; + $this->sparse = true; } public function jsonSerialize() @@ -94,7 +141,7 @@ public function jsonSerialize() 'meta' => $this->meta, 'jsonapi' => $this->api, 'links' => $this->links, - 'included' => $this->included, + 'included' => $this->included ? array_values($this->included) : null, ], function ($v) { return null !== $v; @@ -104,47 +151,19 @@ function ($v) { private function enforceFullLinkage() { - if ($this->is_sparse || empty($this->included)) { + if ($this->sparse || empty($this->included)) { return; } - foreach ($this->included as $included_resource) { - if ($this->hasLinkTo($included_resource) || $this->anotherIncludedResourceIdentifies($included_resource)) { + foreach ($this->included as $included) { + if ($this->data->hasLinkTo($included)) { continue; } - throw new \LogicException("Full linkage is required for $included_resource"); - } - } - - private function anotherIncludedResourceIdentifies(ResourceObject $resource): bool - { - /** @var ResourceObject $included_resource */ - foreach ($this->included as $included_resource) { - if ($included_resource !== $resource && $included_resource->identifies($resource)) { - return true; - } - } - return false; - } - - private function hasLinkTo(ResourceObject $resource): bool - { - /** @var ResourceInterface $my_resource */ - foreach ($this->toResources() as $my_resource) { - if ($my_resource->identifies($resource)) { - return true; - } - } - return false; - } - - private function toResources(): \Iterator - { - if ($this->data instanceof ResourceInterface) { - yield $this->data; - } elseif (is_array($this->data)) { - foreach ($this->data as $datum) { - yield $datum; + foreach ($this->included as $anotherIncluded) { + if ($anotherIncluded->identifies($included)) { + continue 2; + } } + throw new \LogicException("Full linkage is required for {$included->toIdentifier()}"); } } } diff --git a/src/Document/PrimaryData/MultiIdentifierData.php b/src/Document/PrimaryData/MultiIdentifierData.php new file mode 100644 index 0000000..93d7c1a --- /dev/null +++ b/src/Document/PrimaryData/MultiIdentifierData.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace JsonApiPhp\JsonApi\Document\PrimaryData; + +use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; +use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; + +final class MultiIdentifierData implements PrimaryDataInterface +{ + private $identifiers; + + public function __construct(ResourceIdentifier ...$identifiers) + { + $this->identifiers = $identifiers; + } + + public function hasLinkTo(ResourceObject $resource): bool + { + foreach ($this->identifiers as $identifier) { + if ($identifier->identifies($resource)) { + return true; + } + } + return false; + } + + public function jsonSerialize() + { + return $this->identifiers; + } +} diff --git a/src/Document/PrimaryData/MultiResourceData.php b/src/Document/PrimaryData/MultiResourceData.php new file mode 100644 index 0000000..0e63fd8 --- /dev/null +++ b/src/Document/PrimaryData/MultiResourceData.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace JsonApiPhp\JsonApi\Document\PrimaryData; + +use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; + +final class MultiResourceData implements PrimaryDataInterface +{ + private $resources; + + public function __construct(ResourceObject ...$resources) + { + $this->resources = $resources; + } + + public function hasLinkTo(ResourceObject $resource): bool + { + foreach ($this->resources as $myResource) { + if ($myResource->identifies($resource)) { + return true; + } + } + return false; + } + + public function jsonSerialize() + { + return $this->resources; + } +} diff --git a/src/Document/PrimaryData/NullData.php b/src/Document/PrimaryData/NullData.php new file mode 100644 index 0000000..68882da --- /dev/null +++ b/src/Document/PrimaryData/NullData.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace JsonApiPhp\JsonApi\Document\PrimaryData; + +use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; + +final class NullData implements PrimaryDataInterface +{ + public function hasLinkTo(ResourceObject $resource): bool + { + return false; + } + + public function jsonSerialize() + { + return null; + } +} diff --git a/src/Document/PrimaryData/PrimaryDataInterface.php b/src/Document/PrimaryData/PrimaryDataInterface.php new file mode 100644 index 0000000..89967c5 --- /dev/null +++ b/src/Document/PrimaryData/PrimaryDataInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace JsonApiPhp\JsonApi\Document\PrimaryData; + +use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; + +interface PrimaryDataInterface extends \JsonSerializable +{ + public function hasLinkTo(ResourceObject $resource): bool; + + public function jsonSerialize(); +} diff --git a/src/Document/PrimaryData/SingleIdentifierData.php b/src/Document/PrimaryData/SingleIdentifierData.php new file mode 100644 index 0000000..9f8959b --- /dev/null +++ b/src/Document/PrimaryData/SingleIdentifierData.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace JsonApiPhp\JsonApi\Document\PrimaryData; + +use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; +use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; + +final class SingleIdentifierData implements PrimaryDataInterface +{ + private $identifier; + + public function __construct(ResourceIdentifier $identifier) + { + $this->identifier = $identifier; + } + + public function hasLinkTo(ResourceObject $resource): bool + { + return $this->identifier->identifies($resource); + } + + public function jsonSerialize() + { + return $this->identifier; + } +} diff --git a/src/Document/PrimaryData/SingleResourceData.php b/src/Document/PrimaryData/SingleResourceData.php new file mode 100644 index 0000000..1ff8446 --- /dev/null +++ b/src/Document/PrimaryData/SingleResourceData.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace JsonApiPhp\JsonApi\Document\PrimaryData; + +use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; + +final class SingleResourceData implements PrimaryDataInterface +{ + private $resource; + + public function __construct(ResourceObject $resource) + { + $this->resource = $resource; + } + + public function hasLinkTo(ResourceObject $resource): bool + { + return $this->resource->identifies($resource); + } + + public function jsonSerialize() + { + return $this->resource; + } +} diff --git a/src/Document/Resource/Linkage/LinkageInterface.php b/src/Document/Resource/Linkage/LinkageInterface.php new file mode 100644 index 0000000..7340c67 --- /dev/null +++ b/src/Document/Resource/Linkage/LinkageInterface.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace JsonApiPhp\JsonApi\Document\Resource\Linkage; + +use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; + +interface LinkageInterface extends \JsonSerializable +{ + public function isLinkedTo(ResourceObject $resource): bool; + + public function jsonSerialize(); +} diff --git a/src/Document/Resource/Linkage/MultiLinkage.php b/src/Document/Resource/Linkage/MultiLinkage.php new file mode 100644 index 0000000..b732335 --- /dev/null +++ b/src/Document/Resource/Linkage/MultiLinkage.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace JsonApiPhp\JsonApi\Document\Resource\Linkage; + +use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; +use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; + +final class MultiLinkage implements LinkageInterface +{ + private $identifiers; + + public function __construct(ResourceIdentifier ...$identifiers) + { + $this->identifiers = $identifiers; + } + + public function isLinkedTo(ResourceObject $resource): bool + { + foreach ($this->identifiers as $identifier) { + if ($identifier->identifies($resource)) { + return true; + } + } + return false; + } + + public function jsonSerialize() + { + return $this->identifiers; + } +} diff --git a/src/Document/Resource/NullResource.php b/src/Document/Resource/Linkage/NullLinkage.php similarity index 56% rename from src/Document/Resource/NullResource.php rename to src/Document/Resource/Linkage/NullLinkage.php index f30976d..e6fde43 100644 --- a/src/Document/Resource/NullResource.php +++ b/src/Document/Resource/Linkage/NullLinkage.php @@ -9,25 +9,19 @@ */ declare(strict_types=1); -namespace JsonApiPhp\JsonApi\Document\Resource; +namespace JsonApiPhp\JsonApi\Document\Resource\Linkage; -final class NullResource implements ResourceInterface -{ - public function jsonSerialize() - { - return null; - } +use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; - public function identifies(ResourceInterface $resource): bool +final class NullLinkage implements LinkageInterface +{ + public function isLinkedTo(ResourceObject $resource): bool { return false; } - /** - * @deprecated to be removed in 1.0 - */ - public function __toString(): string + public function jsonSerialize() { - return 'null'; + return null; } } diff --git a/src/Document/Resource/Linkage/SingleLinkage.php b/src/Document/Resource/Linkage/SingleLinkage.php new file mode 100644 index 0000000..1ed15ec --- /dev/null +++ b/src/Document/Resource/Linkage/SingleLinkage.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace JsonApiPhp\JsonApi\Document\Resource\Linkage; + +use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; +use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; + +final class SingleLinkage implements LinkageInterface +{ + private $identifier; + + public function __construct(ResourceIdentifier $identifier) + { + $this->identifier = $identifier; + } + + public function isLinkedTo(ResourceObject $resource): bool + { + return $this->identifier->identifies($resource); + } + + public function jsonSerialize() + { + return $this->identifier; + } +} diff --git a/src/Document/Resource/Relationship/Linkage.php b/src/Document/Resource/Relationship/Linkage.php deleted file mode 100644 index 54c5b76..0000000 --- a/src/Document/Resource/Relationship/Linkage.php +++ /dev/null @@ -1,76 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -declare(strict_types=1); - -namespace JsonApiPhp\JsonApi\Document\Resource\Relationship; - -use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; -use JsonApiPhp\JsonApi\Document\Resource\ResourceInterface; - -final class Linkage implements \JsonSerializable -{ - private $data; - - private function __construct() - { - } - - public static function nullLinkage(): self - { - return new self; - } - - public static function emptyArrayLinkage(): self - { - $linkage = new self; - $linkage->data = []; - return $linkage; - } - - public static function fromSingleIdentifier(ResourceIdentifier $data): self - { - $linkage = new self; - $linkage->data = $data; - return $linkage; - } - - public static function fromManyIdentifiers(ResourceIdentifier ...$data): self - { - $linkage = new self; - $linkage->data = $data; - return $linkage; - } - - public function isLinkedTo(ResourceInterface $resource): bool - { - foreach ($this->toLinkages() as $linkage) { - if ($linkage->identifies($resource)) { - return true; - } - } - return false; - } - - private function toLinkages(): \Generator - { - if ($this->data instanceof ResourceIdentifier) { - yield $this->data; - } elseif (is_array($this->data)) { - foreach ($this->data as $resource) { - yield $resource; - } - } - } - - public function jsonSerialize() - { - return $this->data; - } -} diff --git a/src/Document/Resource/Relationship/Relationship.php b/src/Document/Resource/Relationship/Relationship.php index 84b47e6..6c151db 100644 --- a/src/Document/Resource/Relationship/Relationship.php +++ b/src/Document/Resource/Relationship/Relationship.php @@ -14,7 +14,8 @@ use JsonApiPhp\JsonApi\Document\LinksTrait; use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\MetaTrait; -use JsonApiPhp\JsonApi\Document\Resource\ResourceInterface; +use JsonApiPhp\JsonApi\Document\Resource\Linkage\LinkageInterface; +use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; final class Relationship implements \JsonSerializable { @@ -22,7 +23,7 @@ final class Relationship implements \JsonSerializable use MetaTrait; /** - * @var Linkage + * @var LinkageInterface */ private $linkage = null; @@ -51,14 +52,14 @@ public static function fromRelatedLink(string $link, array $meta = null): self return $r; } - public static function fromLinkage(Linkage $linkage): self + public static function fromLinkage(LinkageInterface $linkage): self { $r = new self; $r->linkage = $linkage; return $r; } - public function hasLinkageTo(ResourceInterface $resource): bool + public function hasLinkageTo(ResourceObject $resource): bool { return ($this->linkage && $this->linkage->isLinkedTo($resource)); } diff --git a/src/Document/Resource/ResourceIdentifier.php b/src/Document/Resource/ResourceIdentifier.php index 9d28075..3b67bd8 100644 --- a/src/Document/Resource/ResourceIdentifier.php +++ b/src/Document/Resource/ResourceIdentifier.php @@ -12,22 +12,18 @@ namespace JsonApiPhp\JsonApi\Document\Resource; use JsonApiPhp\JsonApi\Document\Meta; -use JsonApiPhp\JsonApi\Document\MetaTrait; -class ResourceIdentifier implements ResourceInterface +class ResourceIdentifier implements \JsonSerializable { - use MetaTrait; + private $type; + private $id; + private $meta; - protected $type; - protected $id; - - public function __construct(string $type, string $id = null, Meta $meta = null) + public function __construct(string $type, string $id, Meta $meta = null) { $this->type = $type; $this->id = $id; - if ($meta) { - $this->setMeta($meta); - } + $this->meta = $meta; } public function jsonSerialize() @@ -44,19 +40,18 @@ function ($v) { ); } - /** - * @deprecated to be removed in 1.0 - */ + public function identifies(ResourceObject $resource): bool + { + return $resource->toIdentifier()->equals($this); + } + public function __toString(): string { - return sprintf("%s:%s", $this->type, $this->id ?: 'null'); + return "$this->type:$this->id"; } - public function identifies(ResourceInterface $resource): bool + private function equals(ResourceIdentifier $that) { - return $resource instanceof self - && $this->type === $resource->type - && $this->id !== null - && $this->id === $resource->id; + return $this->type === $that->type && $this->id === $that->id; } } diff --git a/src/Document/Resource/ResourceInterface.php b/src/Document/Resource/ResourceInterface.php deleted file mode 100644 index d8b558d..0000000 --- a/src/Document/Resource/ResourceInterface.php +++ /dev/null @@ -1,17 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -declare(strict_types=1); - -namespace JsonApiPhp\JsonApi\Document\Resource; - -interface ResourceInterface extends \JsonSerializable -{ - public function identifies(ResourceInterface $resource): bool; -} diff --git a/src/Document/Resource/ResourceObject.php b/src/Document/Resource/ResourceObject.php index ad21ba0..9276a84 100644 --- a/src/Document/Resource/ResourceObject.php +++ b/src/Document/Resource/ResourceObject.php @@ -12,15 +12,34 @@ namespace JsonApiPhp\JsonApi\Document\Resource; use JsonApiPhp\JsonApi\Document\LinksTrait; +use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; -class ResourceObject extends ResourceIdentifier +class ResourceObject implements \JsonSerializable { use LinksTrait; + private $type; + private $id; + private $meta; private $attributes; + + /** + * @var Relationship[] + */ private $relationships; + public function __construct(string $type, string $id = null) + { + $this->type = $type; + $this->id = $id; + } + + public function setMeta(Meta $meta) + { + $this->meta = $meta; + } + public function setAttribute(string $name, $value) { if ($this->isReservedName($name)) { @@ -43,7 +62,7 @@ public function setRelationship(string $name, Relationship $relationship) $this->relationships[$name] = $relationship; } - public function toId(): ResourceIdentifier + public function toIdentifier(): ResourceIdentifier { return new ResourceIdentifier($this->type, $this->id); } @@ -65,10 +84,9 @@ function ($v) { ); } - public function identifies(ResourceInterface $resource): bool + public function identifies(ResourceObject $resource): bool { if ($this->relationships) { - /** @var Relationship $relationship */ foreach ($this->relationships as $relationship) { if ($relationship->hasLinkageTo($resource)) { return true; @@ -78,10 +96,6 @@ public function identifies(ResourceInterface $resource): bool return false; } - /** - * @param string $name - * @return bool - */ private function isReservedName(string $name): bool { return in_array($name, ['id', 'type']); diff --git a/test/BaseTestCase.php b/test/BaseTestCase.php index cc7a5a4..3c30197 100644 --- a/test/BaseTestCase.php +++ b/test/BaseTestCase.php @@ -17,6 +17,10 @@ abstract class BaseTestCase extends TestCase { public static function assertEncodesTo(string $expected, $obj, string $message = '') { - self::assertEquals(json_encode(json_decode($expected)), json_encode($obj), $message); + self::assertEquals( + json_decode($expected), + json_decode(json_encode($obj, JSON_UNESCAPED_SLASHES)), + $message + ); } } diff --git a/test/Document/CompoundDocumentTest.php b/test/Document/CompoundDocumentTest.php index 2b3ef27..1c6794a 100644 --- a/test/Document/CompoundDocumentTest.php +++ b/test/Document/CompoundDocumentTest.php @@ -12,9 +12,11 @@ namespace JsonApiPhp\JsonApi\Test\Document; use JsonApiPhp\JsonApi\Document; -use JsonApiPhp\JsonApi\Document\Resource\NullResource; -use JsonApiPhp\JsonApi\Document\Resource\Relationship\Linkage; +use JsonApiPhp\JsonApi\Document\Meta; +use JsonApiPhp\JsonApi\Document\Resource\Linkage\MultiLinkage; +use JsonApiPhp\JsonApi\Document\Resource\Linkage\SingleLinkage; use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; +use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use JsonApiPhp\JsonApi\Test\BaseTestCase; @@ -41,65 +43,122 @@ class CompoundDocumentTest extends BaseTestCase { /** - * In a compound document, all included resources MUST be represented as an array of resource objects - * in a top-level included member. + * Let's begin with the example from the official docs @link http://jsonapi.org/format/#document-compound-documents */ - public function testIncludedResourcesRepresentedAsArray() + public function testOfficialDocsExample() { - $apple = new ResourceObject('apples', '1'); - $apple->setAttribute('color', 'red'); - $orange = new ResourceObject('oranges', '1'); - $orange->setAttribute('color', 'orange'); - $basket = new ResourceObject('baskets', '1'); - $basket->setRelationship( - 'fruits', - Relationship::fromLinkage( - Linkage::fromManyIdentifiers( - $apple->toId(), - $orange->toId() - ) - ) + $dan = new ResourceObject('people', '9'); + $dan->setAttribute('first-name', 'Dan'); + $dan->setAttribute('last-name', 'Gebhardt'); + $dan->setAttribute('twitter', 'dgeb'); + $dan->setLink('self', 'http://example.com/people/9'); + + $comment05 = new ResourceObject('comments', '5'); + $comment05->setAttribute('body', 'First!'); + $comment05->setLink('self', 'http://example.com/comments/5'); + $comment05->setRelationship( + 'author', + Relationship::fromLinkage(new SingleLinkage(new ResourceIdentifier('people', '2'))) ); - $doc = Document::fromResource($basket); - $doc->setIncluded($apple, $orange); + + $comment12 = new ResourceObject('comments', '12'); + $comment12->setAttribute('body', 'I like XML better'); + $comment12->setLink('self', 'http://example.com/comments/12'); + $comment12->setRelationship( + 'author', + Relationship::fromLinkage(new SingleLinkage($dan->toIdentifier())) + ); + + $author = Relationship::fromLinkage(new SingleLinkage($dan->toIdentifier())); + $author->setLink('self', 'http://example.com/articles/1/relationships/author'); + $author->setLink('related', 'http://example.com/articles/1/author'); + + $comments = Relationship::fromLinkage(new MultiLinkage($comment05->toIdentifier(), $comment12->toIdentifier())); + $comments->setLink('self', 'http://example.com/articles/1/relationships/comments'); + $comments->setLink('related', 'http://example.com/articles/1/comments'); + + $article = new ResourceObject('articles', '1'); + $article->setAttribute('title', 'JSON API paints my bikeshed!'); + $article->setLink('self', 'http://example.com/articles/1'); + $article->setRelationship('author', $author); + $article->setRelationship('comments', $comments); + + $doc = Document::fromResources($article); + $doc->setIncluded($dan, $comment05, $comment12); + $this->assertEncodesTo( ' { - "data": { - "type": "baskets", - "id": "1", - "relationships": { - "fruits": { - "data": [ - { - "type": "apples", - "id": "1" - }, - { - "type": "oranges", - "id": "1" - } - ] - } - } + "data": [{ + "type": "articles", + "id": "1", + "attributes": { + "title": "JSON API paints my bikeshed!" }, - "included": [ - { - "type": "apples", - "id": "1", - "attributes": { - "color": "red" - } + "links": { + "self": "http://example.com/articles/1" + }, + "relationships": { + "author": { + "links": { + "self": "http://example.com/articles/1/relationships/author", + "related": "http://example.com/articles/1/author" }, - { - "type": "oranges", - "id": "1", - "attributes": { - "color": "orange" - } - } - ] - } + "data": { "type": "people", "id": "9" } + }, + "comments": { + "links": { + "self": "http://example.com/articles/1/relationships/comments", + "related": "http://example.com/articles/1/comments" + }, + "data": [ + { "type": "comments", "id": "5" }, + { "type": "comments", "id": "12" } + ] + } + } + }], + "included": [{ + "type": "people", + "id": "9", + "attributes": { + "first-name": "Dan", + "last-name": "Gebhardt", + "twitter": "dgeb" + }, + "links": { + "self": "http://example.com/people/9" + } + }, { + "type": "comments", + "id": "5", + "attributes": { + "body": "First!" + }, + "relationships": { + "author": { + "data": { "type": "people", "id": "2" } + } + }, + "links": { + "self": "http://example.com/comments/5" + } + }, { + "type": "comments", + "id": "12", + "attributes": { + "body": "I like XML better" + }, + "relationships": { + "author": { + "data": { "type": "people", "id": "9" } + } + }, + "links": { + "self": "http://example.com/comments/12" + } + }] + } ', $doc ); @@ -111,7 +170,7 @@ public function testIncludedResourcesRepresentedAsArray() */ public function testFullLinkageIsRequired() { - $doc = Document::fromResource(new NullResource); + $doc = Document::nullDocument(); $doc->setIncluded(new ResourceObject('apples', '1')); json_encode($doc); } @@ -121,7 +180,7 @@ public function testFullLinkageIsRequired() */ public function testFullLinkageIsNotRequiredIfSparse() { - $doc = Document::fromResource(new NullResource); + $doc = Document::nullDocument(); $doc->markSparse(); $doc->setIncluded(new ResourceObject('apples', '1')); $this->assertEncodesTo( @@ -140,34 +199,85 @@ public function testFullLinkageIsNotRequiredIfSparse() ); } + /** + * Compound documents require “full linkage”, meaning that every included resource MUST be identified + * by at least one resource identifier object in the same document. + * These resource identifier objects could either be primary data or represent resource linkage + * contained within primary or included resources. + */ public function testIncludedResourceMayBeIdentifiedByPrimaryData() { $apple = new ResourceObject('apples', '1'); $apple->setAttribute('color', 'red'); - $doc = Document::fromResource($apple->toId()); + $doc = Document::fromIdentifier($apple->toIdentifier()); $doc->setIncluded($apple); $this->assertJson(json_encode($doc)); } - public function testIncludedResourceMayBeIdentifiedByAnotherIncludedResource() + public function testIncludedResourceMayBeIdentifiedByLinkageInPrimaryData() + { + $author = new ResourceObject('people', '9'); + $author->setAttribute('first-name', 'Dan'); + + $article = new ResourceObject('articles', '1'); + $article->setAttribute('title', 'JSON API paints my bikeshed!'); + $article->setRelationship( + 'author', + Relationship::fromLinkage(new SingleLinkage($author->toIdentifier())) + ); + + $doc = Document::fromResource($article); + $doc->setIncluded($author); + $this->assertJson(json_encode($doc)); + } + + public function testIncludedResourceMayBeIdentifiedByAnotherLinkedResource() + { + $writer = new ResourceObject('writers', '3'); + $writer->setAttribute('name', 'Eric Evans'); + + $book = new ResourceObject('books', '2'); + $book->setAttribute('name', 'Domain Driven Design'); + $book->setRelationship( + 'author', + Relationship::fromLinkage(new SingleLinkage($writer->toIdentifier())) + ); + + $cart = new ResourceObject('shopping-carts', '1'); + $cart->setRelationship( + 'contents', + Relationship::fromLinkage(new MultiLinkage($book->toIdentifier())) + ); + + $this->assertTrue($book->identifies($writer)); + + $doc = Document::fromResource($cart); + $doc->setIncluded($book, $writer); + $this->assertJson(json_encode($doc)); + } + + /** + * A compound document MUST NOT include more than one resource object for each type and id pair. + * @expectedException \LogicException + * @expectedExceptionMessage Resource apples:1 is already included + */ + public function testCanNotBeManyIncludedResourcesWithEqualIdentifiers() { - /** - * BasketID identifies included BasketObject - * BasketObject identifies included AppleObject - */ $apple = new ResourceObject('apples', '1'); $apple->setAttribute('color', 'red'); - $basket = new ResourceObject('basket', '1'); - $basket->setRelationship( - 'fruits', - Relationship::fromLinkage( - Linkage::fromManyIdentifiers( - $apple->toId() - ) - ) - ); - $doc = Document::fromResource($basket->toId()); - $doc->setIncluded($apple, $basket); + $doc = Document::fromIdentifier($apple->toIdentifier()); + $doc->setIncluded($apple, $apple); $this->assertJson(json_encode($doc)); } + + /** + * If a document does not contain a top-level data key, the included member MUST NOT be present either. + * @expectedException \LogicException + * @expectedExceptionMessage Document with no data cannot contain included resources + */ + public function testIncludedMustOnlyBePresentWithData() + { + $doc = Document::fromMeta(Meta::fromArray(['foo' => 'bar'])); + $doc->setIncluded(new ResourceObject('apples', '1')); + } } diff --git a/test/Document/DocumentTest.php b/test/Document/DocumentTest.php index e7d5255..e1cc757 100644 --- a/test/Document/DocumentTest.php +++ b/test/Document/DocumentTest.php @@ -14,7 +14,6 @@ use JsonApiPhp\JsonApi\Document; use JsonApiPhp\JsonApi\Document\Error; use JsonApiPhp\JsonApi\Document\Meta; -use JsonApiPhp\JsonApi\Document\Resource\NullResource; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use JsonApiPhp\JsonApi\Test\BaseTestCase; @@ -91,7 +90,7 @@ public function testDocumentMayContainJustData() "data": null } ', - Document::fromResource(new NullResource), + Document::nullDocument(), 'The simplest document possible contains null' ); @@ -104,7 +103,7 @@ public function testDocumentMayContainJustData() } } ', - Document::fromResource(new ResourceIdentifier('books', 'abc123')), + Document::fromIdentifier(new ResourceIdentifier('books', 'abc123')), 'Resource identifier can be used as primary data' ); @@ -141,7 +140,7 @@ public function testDocumentMayContainJustData() ] } ', - Document::fromResources( + Document::fromIdentifiers( new ResourceIdentifier('books', '12'), new ResourceIdentifier('carrots', '42') ), @@ -157,11 +156,11 @@ public function testDocumentMayContainJustData() */ public function testDocumentCanHaveExtraProperties() { - $doc = Document::fromResource( + $doc = Document::fromIdentifier( new ResourceIdentifier('apples', '42') ); $doc->setApiVersion('1.0'); - $doc->setApiMeta(['a' => 'b']); + $doc->setApiMeta(Meta::fromArray(['a' => 'b'])); $doc->setMeta(Meta::fromArray(['test' => 'test'])); $doc->setLink('self', 'http://example.com/self'); $doc->setLink('related', 'http://example.com/rel', ['foo' => 'bar']); diff --git a/test/Document/Resource/Relationship/LinkageTest.php b/test/Document/Resource/Relationship/LinkageTest.php index e71cb9e..0c7b8d9 100644 --- a/test/Document/Resource/Relationship/LinkageTest.php +++ b/test/Document/Resource/Relationship/LinkageTest.php @@ -11,9 +11,12 @@ namespace JsonApiPhp\JsonApi\Test\Document\Resource\Relationship; -use JsonApiPhp\JsonApi\Document\Resource\NullResource; +use JsonApiPhp\JsonApi\Document\Resource\Linkage\MultiLinkage; +use JsonApiPhp\JsonApi\Document\Resource\Linkage\NullLinkage; +use JsonApiPhp\JsonApi\Document\Resource\Linkage\SingleLinkage; use JsonApiPhp\JsonApi\Document\Resource\Relationship\Linkage; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; +use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use JsonApiPhp\JsonApi\Test\BaseTestCase; /** @@ -40,7 +43,7 @@ public function testCanCreateNullLinkage() { $this->assertEncodesTo( 'null', - Linkage::nullLinkage() + new NullLinkage() ); } @@ -48,7 +51,7 @@ public function testCanCreateEmptyArrayLinkage() { $this->assertEncodesTo( '[]', - Linkage::emptyArrayLinkage() + new MultiLinkage() ); } @@ -61,7 +64,7 @@ public function testCanCreateFromSingleResourceId() "id": "abc" } ', - Linkage::fromSingleIdentifier(new ResourceIdentifier('books', 'abc')) + new SingleLinkage(new ResourceIdentifier('books', 'abc')) ); } @@ -80,7 +83,7 @@ public function testCanCreateFromArrayOfResourceIds() } ] ', - Linkage::fromManyIdentifiers( + new MultiLinkage( new ResourceIdentifier('books', 'abc'), new ResourceIdentifier('squirrels', '123') ) @@ -89,24 +92,22 @@ public function testCanCreateFromArrayOfResourceIds() public function testNullLinkageIsLinkedToNothing() { - $apple = new ResourceIdentifier('apples', '1'); - $this->assertFalse(Linkage::nullLinkage()->isLinkedTo($apple)); - $this->assertFalse(Linkage::nullLinkage()->isLinkedTo(new NullResource)); + $apple = new ResourceObject('apples', '1'); + $this->assertFalse((new NullLinkage())->isLinkedTo($apple)); } public function testEmptyArrayLinkageIsLinkedToNothing() { - $apple = new ResourceIdentifier('apples', '1'); - $this->assertFalse(Linkage::emptyArrayLinkage()->isLinkedTo($apple)); - $this->assertFalse(Linkage::emptyArrayLinkage()->isLinkedTo(new NullResource)); + $apple = new ResourceObject('apples', '1'); + $this->assertFalse((new MultiLinkage())->isLinkedTo($apple)); } public function testSingleLinkageIsLinkedOnlyToItself() { - $apple = new ResourceIdentifier('apples', '1'); - $orange = new ResourceIdentifier('oranges', '1'); + $apple = new ResourceObject('apples', '1'); + $orange = new ResourceObject('oranges', '1'); - $linkage = Linkage::fromSingleIdentifier($apple); + $linkage = new SingleLinkage($apple->toIdentifier()); $this->assertTrue($linkage->isLinkedTo($apple)); $this->assertFalse($linkage->isLinkedTo($orange)); @@ -114,11 +115,11 @@ public function testSingleLinkageIsLinkedOnlyToItself() public function testMultiLinkageIsLinkedOnlyToItsMembers() { - $apple = new ResourceIdentifier('apples', '1'); - $orange = new ResourceIdentifier('oranges', '1'); - $banana = new ResourceIdentifier('bananas', '1'); + $apple = new ResourceObject('apples', '1'); + $orange = new ResourceObject('oranges', '1'); + $banana = new ResourceObject('bananas', '1'); - $linkage = Linkage::fromManyIdentifiers($apple, $orange); + $linkage = new MultiLinkage($apple->toIdentifier(), $orange->toIdentifier()); $this->assertTrue($linkage->isLinkedTo($apple)); $this->assertTrue($linkage->isLinkedTo($orange)); diff --git a/test/Document/Resource/Relationship/RelationshipTest.php b/test/Document/Resource/Relationship/RelationshipTest.php index 45ad99e..436384a 100644 --- a/test/Document/Resource/Relationship/RelationshipTest.php +++ b/test/Document/Resource/Relationship/RelationshipTest.php @@ -12,6 +12,7 @@ namespace JsonApiPhp\JsonApi\Test\Document\Resource\Relationship; use JsonApiPhp\JsonApi\Document\Meta; +use JsonApiPhp\JsonApi\Document\Resource\Linkage\NullLinkage; use JsonApiPhp\JsonApi\Document\Resource\Relationship\Linkage; use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; use JsonApiPhp\JsonApi\Test\BaseTestCase; @@ -84,7 +85,7 @@ public function testCanCreateFromLinkage() "data": null } ', - Relationship::fromLinkage(Linkage::nullLinkage()) + Relationship::fromLinkage(new NullLinkage()) ); } diff --git a/test/Document/Resource/ResourceTest.php b/test/Document/Resource/ResourceTest.php index 97c0779..c1b238d 100644 --- a/test/Document/Resource/ResourceTest.php +++ b/test/Document/Resource/ResourceTest.php @@ -39,7 +39,7 @@ public function resourceProvider() return [ [ '{"type": "books"}', - new ResourceIdentifier('books'), + new ResourceObject('books'), ], [ '{"type":"books","id":"42abc"}', diff --git a/test/IntegrationTest.php b/test/IntegrationTest.php index 211ed25..6eee1e5 100644 --- a/test/IntegrationTest.php +++ b/test/IntegrationTest.php @@ -12,7 +12,7 @@ namespace JsonApiPhp\JsonApi\Test; use JsonApiPhp\JsonApi\Document; -use JsonApiPhp\JsonApi\Document\Resource\Relationship\Linkage; +use JsonApiPhp\JsonApi\Document\Resource\Linkage\SingleLinkage; use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; @@ -48,7 +48,7 @@ public function testFromTheReadmeFile() $articles = new ResourceObject('articles', '1'); $author = Relationship::fromLinkage( - Linkage::fromSingleIdentifier( + new SingleLinkage( new ResourceIdentifier('people', '9') ) ); From f5dec3fa7b5fdebf01f4298fab8d51974b170bd2 Mon Sep 17 00:00:00 2001 From: Jahak Date: Sat, 9 Sep 2017 14:11:09 +0500 Subject: [PATCH 05/45] Remove useless declaration (#55) --- src/Document/PrimaryData/PrimaryDataInterface.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Document/PrimaryData/PrimaryDataInterface.php b/src/Document/PrimaryData/PrimaryDataInterface.php index 89967c5..1477328 100644 --- a/src/Document/PrimaryData/PrimaryDataInterface.php +++ b/src/Document/PrimaryData/PrimaryDataInterface.php @@ -16,6 +16,4 @@ interface PrimaryDataInterface extends \JsonSerializable { public function hasLinkTo(ResourceObject $resource): bool; - - public function jsonSerialize(); } From 6b54910fb0979b569385b699c9b939e99abdb1b0 Mon Sep 17 00:00:00 2001 From: Jahak Date: Sat, 9 Sep 2017 14:19:03 +0500 Subject: [PATCH 06/45] Delete ArrayMeta.php (#57) --- src/Document/ArrayMeta.php | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 src/Document/ArrayMeta.php diff --git a/src/Document/ArrayMeta.php b/src/Document/ArrayMeta.php deleted file mode 100644 index b41444d..0000000 --- a/src/Document/ArrayMeta.php +++ /dev/null @@ -1,23 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -declare(strict_types=1); - -namespace JsonApiPhp\JsonApi\Document; - -/** - * @deprecated to be removed in 1.0, use Meta::fromArray() - */ -class ArrayMeta extends Meta -{ - public function __construct(array $array) - { - parent::__construct((object) $array); - } -} From 36b0d295117b69c4589f65688c59b20c825008b9 Mon Sep 17 00:00:00 2001 From: Alexey Date: Sun, 10 Sep 2017 23:15:40 -0700 Subject: [PATCH 07/45] Link model fixes (#58) --- .php_cs.dist | 1 - README.md | 6 ++-- src/Document.php | 1 + src/Document/Error.php | 1 + src/Document/Link/Link.php | 28 ++++++++++++++++ src/Document/Link/LinkInterface.php | 18 ++++++++++ src/Document/Link/LinkObject.php | 33 +++++++++++++++++++ src/Document/LinksTrait.php | 16 +++++++-- src/Document/Meta.php | 1 + src/Document/MetaTrait.php | 1 + .../PrimaryData/MultiIdentifierData.php | 1 + .../PrimaryData/MultiResourceData.php | 1 + src/Document/PrimaryData/NullData.php | 1 + .../PrimaryData/PrimaryDataInterface.php | 1 + .../PrimaryData/SingleIdentifierData.php | 1 + .../PrimaryData/SingleResourceData.php | 1 + .../Resource/Linkage/LinkageInterface.php | 18 +++++----- .../Resource/Linkage/MultiLinkage.php | 1 + src/Document/Resource/Linkage/NullLinkage.php | 1 + .../Resource/Linkage/SingleLinkage.php | 1 + .../Resource/Relationship/Relationship.php | 10 +++--- src/Document/Resource/ResourceIdentifier.php | 1 + src/Document/Resource/ResourceObject.php | 1 + test/BaseTestCase.php | 1 + test/Document/CompoundDocumentTest.php | 2 ++ test/Document/DocumentTest.php | 4 ++- test/Document/ErrorTest.php | 1 + test/Document/MetaTest.php | 1 + .../Resource/Relationship/LinkageTest.php | 2 +- .../Relationship/RelationshipTest.php | 6 ++-- test/Document/Resource/ResourceFieldsTest.php | 1 + test/Document/Resource/ResourceTest.php | 1 + test/IntegrationTest.php | 1 + 33 files changed, 143 insertions(+), 22 deletions(-) create mode 100644 src/Document/Link/Link.php create mode 100644 src/Document/Link/LinkInterface.php create mode 100644 src/Document/Link/LinkObject.php diff --git a/.php_cs.dist b/.php_cs.dist index 9c1b5d4..e8482d9 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -21,7 +21,6 @@ return PhpCsFixer\Config::create() 'method_separation' => true, 'native_function_casing' => true, 'no_blank_lines_after_class_opening' => true, - 'no_blank_lines_after_phpdoc' => true, 'no_empty_statement' => true, 'no_extra_consecutive_blank_lines' => true, 'no_leading_import_slash' => true, diff --git a/README.md b/README.md index 7dcb01c..36b1821 100644 --- a/README.md +++ b/README.md @@ -27,17 +27,17 @@ A simple example to illustrate the general idea. This JSON representation from } } ``` -can be built with the following php code: +can be built with the following php code (less imports): ```php setLink('self', '/articles/1/relationships/author'); -$author->setLink('related', '/articles/1/author'); +$author->setLink('related','/articles/1/author'); $articles->setRelationship('author', $author); $articles->setAttribute('title', 'Rails is Omakase'); $doc = Document::fromResource($articles); diff --git a/src/Document.php b/src/Document.php index a102132..cbec4c0 100644 --- a/src/Document.php +++ b/src/Document.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi; diff --git a/src/Document/Error.php b/src/Document/Error.php index 2a7d491..ac3668f 100644 --- a/src/Document/Error.php +++ b/src/Document/Error.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document; diff --git a/src/Document/Link/Link.php b/src/Document/Link/Link.php new file mode 100644 index 0000000..6c3cdc0 --- /dev/null +++ b/src/Document/Link/Link.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace JsonApiPhp\JsonApi\Document\Link; + +final class Link implements LinkInterface +{ + private $url; + + public function __construct(string $url) + { + $this->url = $url; + } + + public function jsonSerialize() + { + return $this->url; + } +} diff --git a/src/Document/Link/LinkInterface.php b/src/Document/Link/LinkInterface.php new file mode 100644 index 0000000..4baeaea --- /dev/null +++ b/src/Document/Link/LinkInterface.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace JsonApiPhp\JsonApi\Document\Link; + +interface LinkInterface extends \JsonSerializable +{ + public function jsonSerialize(); +} diff --git a/src/Document/Link/LinkObject.php b/src/Document/Link/LinkObject.php new file mode 100644 index 0000000..d0dcb49 --- /dev/null +++ b/src/Document/Link/LinkObject.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace JsonApiPhp\JsonApi\Document\Link; + +use JsonApiPhp\JsonApi\Document\Meta; + +final class LinkObject implements LinkInterface +{ + private $link; + + public function __construct(string $href, Meta $meta = null) + { + $this->link['href'] = $href; + if ($meta) { + $this->link['meta'] = $meta; + } + } + + public function jsonSerialize() + { + return $this->link; + } +} diff --git a/src/Document/LinksTrait.php b/src/Document/LinksTrait.php index de2bf3a..68d1947 100644 --- a/src/Document/LinksTrait.php +++ b/src/Document/LinksTrait.php @@ -7,16 +7,28 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document; +use JsonApiPhp\JsonApi\Document\Link\Link; +use JsonApiPhp\JsonApi\Document\Link\LinkInterface; + trait LinksTrait { + /** + * @var LinkInterface[] + */ protected $links; - public function setLink(string $name, string $value, array $meta = null) + public function setLink(string $name, string $url) + { + $this->links[$name] = new Link($url); + } + + public function setLinkObject(string $name, LinkInterface $link) { - $this->links[$name] = $meta ? ['href' => $value, 'meta' => $meta] : $value; + $this->links[$name] = $link; } } diff --git a/src/Document/Meta.php b/src/Document/Meta.php index dd45f88..ea5c659 100644 --- a/src/Document/Meta.php +++ b/src/Document/Meta.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document; diff --git a/src/Document/MetaTrait.php b/src/Document/MetaTrait.php index 9379a12..7ed3f44 100644 --- a/src/Document/MetaTrait.php +++ b/src/Document/MetaTrait.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document; diff --git a/src/Document/PrimaryData/MultiIdentifierData.php b/src/Document/PrimaryData/MultiIdentifierData.php index 93d7c1a..d4cb242 100644 --- a/src/Document/PrimaryData/MultiIdentifierData.php +++ b/src/Document/PrimaryData/MultiIdentifierData.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\PrimaryData; diff --git a/src/Document/PrimaryData/MultiResourceData.php b/src/Document/PrimaryData/MultiResourceData.php index 0e63fd8..e85cbad 100644 --- a/src/Document/PrimaryData/MultiResourceData.php +++ b/src/Document/PrimaryData/MultiResourceData.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\PrimaryData; diff --git a/src/Document/PrimaryData/NullData.php b/src/Document/PrimaryData/NullData.php index 68882da..d583f53 100644 --- a/src/Document/PrimaryData/NullData.php +++ b/src/Document/PrimaryData/NullData.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\PrimaryData; diff --git a/src/Document/PrimaryData/PrimaryDataInterface.php b/src/Document/PrimaryData/PrimaryDataInterface.php index 1477328..d2e6c1a 100644 --- a/src/Document/PrimaryData/PrimaryDataInterface.php +++ b/src/Document/PrimaryData/PrimaryDataInterface.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\PrimaryData; diff --git a/src/Document/PrimaryData/SingleIdentifierData.php b/src/Document/PrimaryData/SingleIdentifierData.php index 9f8959b..8497cb8 100644 --- a/src/Document/PrimaryData/SingleIdentifierData.php +++ b/src/Document/PrimaryData/SingleIdentifierData.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\PrimaryData; diff --git a/src/Document/PrimaryData/SingleResourceData.php b/src/Document/PrimaryData/SingleResourceData.php index 1ff8446..0060960 100644 --- a/src/Document/PrimaryData/SingleResourceData.php +++ b/src/Document/PrimaryData/SingleResourceData.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\PrimaryData; diff --git a/src/Document/Resource/Linkage/LinkageInterface.php b/src/Document/Resource/Linkage/LinkageInterface.php index 7340c67..6a0c582 100644 --- a/src/Document/Resource/Linkage/LinkageInterface.php +++ b/src/Document/Resource/Linkage/LinkageInterface.php @@ -1,12 +1,14 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ +* This file is part of JSON:API implementation for PHP. +* +* (c) Alexey Karapetov +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource\Linkage; diff --git a/src/Document/Resource/Linkage/MultiLinkage.php b/src/Document/Resource/Linkage/MultiLinkage.php index b732335..5d79bcb 100644 --- a/src/Document/Resource/Linkage/MultiLinkage.php +++ b/src/Document/Resource/Linkage/MultiLinkage.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource\Linkage; diff --git a/src/Document/Resource/Linkage/NullLinkage.php b/src/Document/Resource/Linkage/NullLinkage.php index e6fde43..5c76d64 100644 --- a/src/Document/Resource/Linkage/NullLinkage.php +++ b/src/Document/Resource/Linkage/NullLinkage.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource\Linkage; diff --git a/src/Document/Resource/Linkage/SingleLinkage.php b/src/Document/Resource/Linkage/SingleLinkage.php index 1ed15ec..5084a8b 100644 --- a/src/Document/Resource/Linkage/SingleLinkage.php +++ b/src/Document/Resource/Linkage/SingleLinkage.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource\Linkage; diff --git a/src/Document/Resource/Relationship/Relationship.php b/src/Document/Resource/Relationship/Relationship.php index 6c151db..61025cc 100644 --- a/src/Document/Resource/Relationship/Relationship.php +++ b/src/Document/Resource/Relationship/Relationship.php @@ -7,10 +7,12 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource\Relationship; +use JsonApiPhp\JsonApi\Document\Link\LinkInterface; use JsonApiPhp\JsonApi\Document\LinksTrait; use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\MetaTrait; @@ -38,17 +40,17 @@ public static function fromMeta(Meta $meta): self return $r; } - public static function fromSelfLink(string $link, array $meta = null): self + public static function fromSelfLink(LinkInterface $link): self { $r = new self; - $r->setLink('self', $link, $meta); + $r->setLinkObject('self', $link); return $r; } - public static function fromRelatedLink(string $link, array $meta = null): self + public static function fromRelatedLink(LinkInterface $link): self { $r = new self; - $r->setLink('related', $link, $meta); + $r->setLinkObject('related', $link); return $r; } diff --git a/src/Document/Resource/ResourceIdentifier.php b/src/Document/Resource/ResourceIdentifier.php index 3b67bd8..6e841cb 100644 --- a/src/Document/Resource/ResourceIdentifier.php +++ b/src/Document/Resource/ResourceIdentifier.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource; diff --git a/src/Document/Resource/ResourceObject.php b/src/Document/Resource/ResourceObject.php index 9276a84..06eaf7f 100644 --- a/src/Document/Resource/ResourceObject.php +++ b/src/Document/Resource/ResourceObject.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource; diff --git a/test/BaseTestCase.php b/test/BaseTestCase.php index 3c30197..9090980 100644 --- a/test/BaseTestCase.php +++ b/test/BaseTestCase.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test; diff --git a/test/Document/CompoundDocumentTest.php b/test/Document/CompoundDocumentTest.php index 1c6794a..c186190 100644 --- a/test/Document/CompoundDocumentTest.php +++ b/test/Document/CompoundDocumentTest.php @@ -7,11 +7,13 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document; use JsonApiPhp\JsonApi\Document; +use JsonApiPhp\JsonApi\Document\Link\Link; use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Linkage\MultiLinkage; use JsonApiPhp\JsonApi\Document\Resource\Linkage\SingleLinkage; diff --git a/test/Document/DocumentTest.php b/test/Document/DocumentTest.php index e1cc757..9e54c5f 100644 --- a/test/Document/DocumentTest.php +++ b/test/Document/DocumentTest.php @@ -7,12 +7,14 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document; use JsonApiPhp\JsonApi\Document; use JsonApiPhp\JsonApi\Document\Error; +use JsonApiPhp\JsonApi\Document\Link\LinkObject; use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; @@ -163,7 +165,7 @@ public function testDocumentCanHaveExtraProperties() $doc->setApiMeta(Meta::fromArray(['a' => 'b'])); $doc->setMeta(Meta::fromArray(['test' => 'test'])); $doc->setLink('self', 'http://example.com/self'); - $doc->setLink('related', 'http://example.com/rel', ['foo' => 'bar']); + $doc->setLinkObject('related', new LinkObject('http://example.com/rel', Meta::fromArray(['foo' => 'bar']))); $this->assertEncodesTo( ' { diff --git a/test/Document/ErrorTest.php b/test/Document/ErrorTest.php index ce036e0..f1f00da 100644 --- a/test/Document/ErrorTest.php +++ b/test/Document/ErrorTest.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document; diff --git a/test/Document/MetaTest.php b/test/Document/MetaTest.php index fd4ab9c..1f1da8c 100644 --- a/test/Document/MetaTest.php +++ b/test/Document/MetaTest.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document; diff --git a/test/Document/Resource/Relationship/LinkageTest.php b/test/Document/Resource/Relationship/LinkageTest.php index 0c7b8d9..93f57d3 100644 --- a/test/Document/Resource/Relationship/LinkageTest.php +++ b/test/Document/Resource/Relationship/LinkageTest.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document\Resource\Relationship; @@ -14,7 +15,6 @@ use JsonApiPhp\JsonApi\Document\Resource\Linkage\MultiLinkage; use JsonApiPhp\JsonApi\Document\Resource\Linkage\NullLinkage; use JsonApiPhp\JsonApi\Document\Resource\Linkage\SingleLinkage; -use JsonApiPhp\JsonApi\Document\Resource\Relationship\Linkage; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use JsonApiPhp\JsonApi\Test\BaseTestCase; diff --git a/test/Document/Resource/Relationship/RelationshipTest.php b/test/Document/Resource/Relationship/RelationshipTest.php index 436384a..9222748 100644 --- a/test/Document/Resource/Relationship/RelationshipTest.php +++ b/test/Document/Resource/Relationship/RelationshipTest.php @@ -7,10 +7,12 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document\Resource\Relationship; +use JsonApiPhp\JsonApi\Document\Link\Link; use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Linkage\NullLinkage; use JsonApiPhp\JsonApi\Document\Resource\Relationship\Linkage; @@ -59,7 +61,7 @@ public function testCanCreateFromSelfLink() } } ', - Relationship::fromSelfLink('http://localhost') + Relationship::fromSelfLink(new Link('http://localhost')) ); } @@ -73,7 +75,7 @@ public function testCanCreateFromRelatedLink() } } ', - Relationship::fromRelatedLink('http://localhost') + Relationship::fromRelatedLink(new Link('http://localhost')) ); } diff --git a/test/Document/Resource/ResourceFieldsTest.php b/test/Document/Resource/ResourceFieldsTest.php index ffe323e..a9c0856 100644 --- a/test/Document/Resource/ResourceFieldsTest.php +++ b/test/Document/Resource/ResourceFieldsTest.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document\Resource; diff --git a/test/Document/Resource/ResourceTest.php b/test/Document/Resource/ResourceTest.php index c1b238d..f051ffa 100644 --- a/test/Document/Resource/ResourceTest.php +++ b/test/Document/Resource/ResourceTest.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document\Resource; diff --git a/test/IntegrationTest.php b/test/IntegrationTest.php index 6eee1e5..79e98df 100644 --- a/test/IntegrationTest.php +++ b/test/IntegrationTest.php @@ -7,6 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ + declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test; From 2dee705210546c7b68ccade4e27e36630c04de8e Mon Sep 17 00:00:00 2001 From: Alexey Date: Tue, 12 Sep 2017 21:57:53 -0700 Subject: [PATCH 08/45] Removed license banner (#59) --- .php_cs.dist | 1 + LICENSE | 2 +- src/Document.php | 9 --------- src/Document/Error.php | 9 --------- src/Document/Link/Link.php | 9 --------- src/Document/Link/LinkInterface.php | 9 --------- src/Document/Link/LinkObject.php | 9 --------- src/Document/LinksTrait.php | 9 --------- src/Document/Meta.php | 9 --------- src/Document/MetaTrait.php | 9 --------- src/Document/PrimaryData/MultiIdentifierData.php | 9 --------- src/Document/PrimaryData/MultiResourceData.php | 9 --------- src/Document/PrimaryData/NullData.php | 9 --------- src/Document/PrimaryData/PrimaryDataInterface.php | 9 --------- src/Document/PrimaryData/SingleIdentifierData.php | 9 --------- src/Document/PrimaryData/SingleResourceData.php | 9 --------- src/Document/Resource/Linkage/LinkageInterface.php | 9 --------- src/Document/Resource/Linkage/MultiLinkage.php | 9 --------- src/Document/Resource/Linkage/NullLinkage.php | 9 --------- src/Document/Resource/Linkage/SingleLinkage.php | 9 --------- src/Document/Resource/Relationship/Relationship.php | 9 --------- src/Document/Resource/ResourceIdentifier.php | 9 --------- src/Document/Resource/ResourceObject.php | 9 --------- test/BaseTestCase.php | 9 --------- test/Document/CompoundDocumentTest.php | 9 --------- test/Document/DocumentTest.php | 9 --------- test/Document/ErrorTest.php | 9 --------- test/Document/MetaTest.php | 9 --------- test/Document/Resource/Relationship/LinkageTest.php | 9 --------- test/Document/Resource/Relationship/RelationshipTest.php | 9 --------- test/Document/Resource/ResourceFieldsTest.php | 9 --------- test/Document/Resource/ResourceTest.php | 9 --------- test/IntegrationTest.php | 9 --------- 33 files changed, 2 insertions(+), 280 deletions(-) diff --git a/.php_cs.dist b/.php_cs.dist index e8482d9..9c1b5d4 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -21,6 +21,7 @@ return PhpCsFixer\Config::create() 'method_separation' => true, 'native_function_casing' => true, 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, 'no_empty_statement' => true, 'no_extra_consecutive_blank_lines' => true, 'no_leading_import_slash' => true, diff --git a/LICENSE b/LICENSE index efe2a4d..c581e9c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Alexey Karapetov +Copyright (c) Alexey Karapetov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Document.php b/src/Document.php index cbec4c0..fb832e4 100644 --- a/src/Document.php +++ b/src/Document.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi; diff --git a/src/Document/Error.php b/src/Document/Error.php index ac3668f..03e23b7 100644 --- a/src/Document/Error.php +++ b/src/Document/Error.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document; diff --git a/src/Document/Link/Link.php b/src/Document/Link/Link.php index 6c3cdc0..27070c3 100644 --- a/src/Document/Link/Link.php +++ b/src/Document/Link/Link.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Link; diff --git a/src/Document/Link/LinkInterface.php b/src/Document/Link/LinkInterface.php index 4baeaea..b5dc9d9 100644 --- a/src/Document/Link/LinkInterface.php +++ b/src/Document/Link/LinkInterface.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Link; diff --git a/src/Document/Link/LinkObject.php b/src/Document/Link/LinkObject.php index d0dcb49..4cf2a3b 100644 --- a/src/Document/Link/LinkObject.php +++ b/src/Document/Link/LinkObject.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Link; diff --git a/src/Document/LinksTrait.php b/src/Document/LinksTrait.php index 68d1947..a246991 100644 --- a/src/Document/LinksTrait.php +++ b/src/Document/LinksTrait.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document; diff --git a/src/Document/Meta.php b/src/Document/Meta.php index ea5c659..5220baf 100644 --- a/src/Document/Meta.php +++ b/src/Document/Meta.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document; diff --git a/src/Document/MetaTrait.php b/src/Document/MetaTrait.php index 7ed3f44..91ccc38 100644 --- a/src/Document/MetaTrait.php +++ b/src/Document/MetaTrait.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document; diff --git a/src/Document/PrimaryData/MultiIdentifierData.php b/src/Document/PrimaryData/MultiIdentifierData.php index d4cb242..008fee2 100644 --- a/src/Document/PrimaryData/MultiIdentifierData.php +++ b/src/Document/PrimaryData/MultiIdentifierData.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\PrimaryData; diff --git a/src/Document/PrimaryData/MultiResourceData.php b/src/Document/PrimaryData/MultiResourceData.php index e85cbad..a607ee9 100644 --- a/src/Document/PrimaryData/MultiResourceData.php +++ b/src/Document/PrimaryData/MultiResourceData.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\PrimaryData; diff --git a/src/Document/PrimaryData/NullData.php b/src/Document/PrimaryData/NullData.php index d583f53..9f577cd 100644 --- a/src/Document/PrimaryData/NullData.php +++ b/src/Document/PrimaryData/NullData.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\PrimaryData; diff --git a/src/Document/PrimaryData/PrimaryDataInterface.php b/src/Document/PrimaryData/PrimaryDataInterface.php index d2e6c1a..a32b6ae 100644 --- a/src/Document/PrimaryData/PrimaryDataInterface.php +++ b/src/Document/PrimaryData/PrimaryDataInterface.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\PrimaryData; diff --git a/src/Document/PrimaryData/SingleIdentifierData.php b/src/Document/PrimaryData/SingleIdentifierData.php index 8497cb8..0019e07 100644 --- a/src/Document/PrimaryData/SingleIdentifierData.php +++ b/src/Document/PrimaryData/SingleIdentifierData.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\PrimaryData; diff --git a/src/Document/PrimaryData/SingleResourceData.php b/src/Document/PrimaryData/SingleResourceData.php index 0060960..b81279b 100644 --- a/src/Document/PrimaryData/SingleResourceData.php +++ b/src/Document/PrimaryData/SingleResourceData.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\PrimaryData; diff --git a/src/Document/Resource/Linkage/LinkageInterface.php b/src/Document/Resource/Linkage/LinkageInterface.php index 6a0c582..466a762 100644 --- a/src/Document/Resource/Linkage/LinkageInterface.php +++ b/src/Document/Resource/Linkage/LinkageInterface.php @@ -1,13 +1,4 @@ -* -* For the full copyright and license information, please view the LICENSE -* file that was distributed with this source code. -*/ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource\Linkage; diff --git a/src/Document/Resource/Linkage/MultiLinkage.php b/src/Document/Resource/Linkage/MultiLinkage.php index 5d79bcb..95d4157 100644 --- a/src/Document/Resource/Linkage/MultiLinkage.php +++ b/src/Document/Resource/Linkage/MultiLinkage.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource\Linkage; diff --git a/src/Document/Resource/Linkage/NullLinkage.php b/src/Document/Resource/Linkage/NullLinkage.php index 5c76d64..a44c2ba 100644 --- a/src/Document/Resource/Linkage/NullLinkage.php +++ b/src/Document/Resource/Linkage/NullLinkage.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource\Linkage; diff --git a/src/Document/Resource/Linkage/SingleLinkage.php b/src/Document/Resource/Linkage/SingleLinkage.php index 5084a8b..ce9a53d 100644 --- a/src/Document/Resource/Linkage/SingleLinkage.php +++ b/src/Document/Resource/Linkage/SingleLinkage.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource\Linkage; diff --git a/src/Document/Resource/Relationship/Relationship.php b/src/Document/Resource/Relationship/Relationship.php index 61025cc..05b4226 100644 --- a/src/Document/Resource/Relationship/Relationship.php +++ b/src/Document/Resource/Relationship/Relationship.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource\Relationship; diff --git a/src/Document/Resource/ResourceIdentifier.php b/src/Document/Resource/ResourceIdentifier.php index 6e841cb..39e354b 100644 --- a/src/Document/Resource/ResourceIdentifier.php +++ b/src/Document/Resource/ResourceIdentifier.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource; diff --git a/src/Document/Resource/ResourceObject.php b/src/Document/Resource/ResourceObject.php index 06eaf7f..f15b027 100644 --- a/src/Document/Resource/ResourceObject.php +++ b/src/Document/Resource/ResourceObject.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document\Resource; diff --git a/test/BaseTestCase.php b/test/BaseTestCase.php index 9090980..c9841b4 100644 --- a/test/BaseTestCase.php +++ b/test/BaseTestCase.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test; diff --git a/test/Document/CompoundDocumentTest.php b/test/Document/CompoundDocumentTest.php index c186190..a402b0a 100644 --- a/test/Document/CompoundDocumentTest.php +++ b/test/Document/CompoundDocumentTest.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document; diff --git a/test/Document/DocumentTest.php b/test/Document/DocumentTest.php index 9e54c5f..2d255ee 100644 --- a/test/Document/DocumentTest.php +++ b/test/Document/DocumentTest.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document; diff --git a/test/Document/ErrorTest.php b/test/Document/ErrorTest.php index f1f00da..b4935c0 100644 --- a/test/Document/ErrorTest.php +++ b/test/Document/ErrorTest.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Document; diff --git a/test/Document/MetaTest.php b/test/Document/MetaTest.php index 1f1da8c..f9e665e 100644 --- a/test/Document/MetaTest.php +++ b/test/Document/MetaTest.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document; diff --git a/test/Document/Resource/Relationship/LinkageTest.php b/test/Document/Resource/Relationship/LinkageTest.php index 93f57d3..21671c6 100644 --- a/test/Document/Resource/Relationship/LinkageTest.php +++ b/test/Document/Resource/Relationship/LinkageTest.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document\Resource\Relationship; diff --git a/test/Document/Resource/Relationship/RelationshipTest.php b/test/Document/Resource/Relationship/RelationshipTest.php index 9222748..8b98734 100644 --- a/test/Document/Resource/Relationship/RelationshipTest.php +++ b/test/Document/Resource/Relationship/RelationshipTest.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document\Resource\Relationship; diff --git a/test/Document/Resource/ResourceFieldsTest.php b/test/Document/Resource/ResourceFieldsTest.php index a9c0856..f76da46 100644 --- a/test/Document/Resource/ResourceFieldsTest.php +++ b/test/Document/Resource/ResourceFieldsTest.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document\Resource; diff --git a/test/Document/Resource/ResourceTest.php b/test/Document/Resource/ResourceTest.php index f051ffa..eeb3e7e 100644 --- a/test/Document/Resource/ResourceTest.php +++ b/test/Document/Resource/ResourceTest.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test\Document\Resource; diff --git a/test/IntegrationTest.php b/test/IntegrationTest.php index 79e98df..12b189d 100644 --- a/test/IntegrationTest.php +++ b/test/IntegrationTest.php @@ -1,13 +1,4 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - declare(strict_types=1); namespace JsonApiPhp\JsonApi\Test; From 82f61772e5e396defba17d0b27d196a2604ab2a2 Mon Sep 17 00:00:00 2001 From: kefzce Date: Fri, 6 Oct 2017 21:04:41 +0300 Subject: [PATCH 09/45] Remove unused import && add return value (#60) --- src/Document.php | 6 +++--- test/Document/CompoundDocumentTest.php | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Document.php b/src/Document.php index fb832e4..bdb77cc 100644 --- a/src/Document.php +++ b/src/Document.php @@ -74,21 +74,21 @@ public static function fromResources(ResourceObject ...$resources): self return $doc; } - public static function fromIdentifier(ResourceIdentifier $identifier) + public static function fromIdentifier(ResourceIdentifier $identifier): self { $doc = new self; $doc->data = new SingleIdentifierData($identifier); return $doc; } - public static function fromIdentifiers(ResourceIdentifier... $identifiers) + public static function fromIdentifiers(ResourceIdentifier... $identifiers): self { $doc = new self; $doc->data = new MultiIdentifierData(...$identifiers); return $doc; } - public static function nullDocument() + public static function nullDocument(): self { $doc = new self; $doc->data = new NullData(); diff --git a/test/Document/CompoundDocumentTest.php b/test/Document/CompoundDocumentTest.php index a402b0a..b22ef66 100644 --- a/test/Document/CompoundDocumentTest.php +++ b/test/Document/CompoundDocumentTest.php @@ -4,7 +4,6 @@ namespace JsonApiPhp\JsonApi\Test\Document; use JsonApiPhp\JsonApi\Document; -use JsonApiPhp\JsonApi\Document\Link\Link; use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Linkage\MultiLinkage; use JsonApiPhp\JsonApi\Document\Resource\Linkage\SingleLinkage; From bb65a432ea242b5ba867c88b5dd0c6606a5c1bc1 Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Sat, 7 Oct 2017 23:54:19 -0400 Subject: [PATCH 10/45] [Fix #3] Implement Attribute Name Validation (#61) * Implement attribute name validation * Add tests for attribute name validation * Fix type * Add assertion * Remove whitespace on blank line * Remove whitespace on blank lines --- src/Document/Resource/ResourceObject.php | 8 +++ test/Document/Resource/ResourceFieldsTest.php | 60 ++++++++++++++++++- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/Document/Resource/ResourceObject.php b/src/Document/Resource/ResourceObject.php index f15b027..aa54892 100644 --- a/src/Document/Resource/ResourceObject.php +++ b/src/Document/Resource/ResourceObject.php @@ -37,6 +37,9 @@ public function setAttribute(string $name, $value) if ($this->isReservedName($name)) { throw new \InvalidArgumentException('Can not use a reserved name'); } + if (!$this->isValidMemberName($name)) { + throw new \OutOfBoundsException('Not a valid attribute name'); + } if (isset($this->relationships[$name])) { throw new \LogicException("Field $name already exists in relationships"); } @@ -92,4 +95,9 @@ private function isReservedName(string $name): bool { return in_array($name, ['id', 'type']); } + + private function isValidMemberName(string $name): bool + { + return preg_match('/^(?=[^-_ ])[a-zA-Z0-9\x{0080}-\x{FFFF}-_ ]*(?<=[^-_ ])$/u', $name) === 1; + } } diff --git a/test/Document/Resource/ResourceFieldsTest.php b/test/Document/Resource/ResourceFieldsTest.php index f76da46..c301fb3 100644 --- a/test/Document/Resource/ResourceFieldsTest.php +++ b/test/Document/Resource/ResourceFieldsTest.php @@ -47,7 +47,7 @@ public function testCanNotSetAttributeIfRelationshipExists() * @param string $name * @expectedException \InvalidArgumentException * @expectedExceptionMessage Can not use a reserved name - * @dataProvider invalidAttributeNames + * @dataProvider reservedAttributeNames */ public function testAttributeCanNotHaveReservedNames(string $name) { @@ -59,7 +59,7 @@ public function testAttributeCanNotHaveReservedNames(string $name) * @param string $name * @expectedException \InvalidArgumentException * @expectedExceptionMessage Can not use a reserved name - * @dataProvider invalidAttributeNames + * @dataProvider reservedAttributeNames */ public function testRelationshipCanNotHaveReservedNames(string $name) { @@ -67,11 +67,65 @@ public function testRelationshipCanNotHaveReservedNames(string $name) $res->setRelationship($name, Relationship::fromMeta(Meta::fromArray(['a' => 'b']))); } - public function invalidAttributeNames(): array + /** + * @param string $name + * @expectedException \OutOfBoundsException + * @expectedExceptionMessage Not a valid attribute name + * @dataProvider invalidAttributeNames + */ + public function testAttributeNameIsNotValid(string $name) + { + $res = new ResourceObject('books', 'abc'); + $res->setAttribute($name, 1); + } + + /** + * @param string $name + * @dataProvider validAttributeNames + */ + public function testAttributeNameIsValid(string $name) + { + $res = new ResourceObject('books', 'abc'); + $res->setAttribute($name, 1); + $this->assertTrue(true); + } + + public function reservedAttributeNames(): array { return [ ['id'], ['type'], ]; } + + public function invalidAttributeNames(): array + { + return [ + ['_abcde'], + ['abcd_'], + ['abc$EDS'], + ['#abcde'], + ['abcde('], + ['b_'], + ['_a'], + ['$ab_c-d'], + ['-abc'], + ]; + } + + public function validAttributeNames(): array + { + return [ + ['abcd'], + ['abcA4C'], + ['abc_d3f45'], + ['abd_eca'], + ['a'], + ['b'], + ['ab'], + ['a-bc_de'], + ['abcéêçèÇ_n'], + ['abc 汉字 abc'], + ]; + } } From aff1169a61087f8b236ef9526c7ece2000af0749 Mon Sep 17 00:00:00 2001 From: Alexey Date: Sun, 15 Oct 2017 00:34:17 -0700 Subject: [PATCH 11/45] Test refactoring (#62) --- composer.json | 2 +- .../{Relationship => }/Relationship.php | 3 +- src/Document/Resource/ResourceIdentifier.php | 11 ++ src/Document/Resource/ResourceObject.php | 14 ++- test/Document/CompoundDocumentTest.php | 18 ++- test/Document/DocumentTest.php | 4 +- .../Relationship/RelationshipTest.php | 8 +- test/Document/Resource/ResourceFieldsTest.php | 60 +--------- .../Resource/ResourceIdentifierTest.php | 45 +++++++ test/Document/Resource/ResourceObjectTest.php | 111 ++++++++++++++++++ test/Document/Resource/ResourceTest.php | 86 -------------- test/IntegrationTest.php | 2 +- test/MemberNamesTest.php | 89 ++++++++++++++ 13 files changed, 291 insertions(+), 162 deletions(-) rename src/Document/Resource/{Relationship => }/Relationship.php (93%) create mode 100644 test/Document/Resource/ResourceIdentifierTest.php create mode 100644 test/Document/Resource/ResourceObjectTest.php delete mode 100644 test/Document/Resource/ResourceTest.php create mode 100644 test/MemberNamesTest.php diff --git a/composer.json b/composer.json index 8ce2d74..0decca3 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,6 @@ } }, "scripts": { - "test": "php-cs-fixer fix -v --dry-run --ansi && phpunit --colors=always" + "test": "php-cs-fixer fix -v --dry-run --ansi && phpunit --colors=always --coverage-text" } } diff --git a/src/Document/Resource/Relationship/Relationship.php b/src/Document/Resource/Relationship.php similarity index 93% rename from src/Document/Resource/Relationship/Relationship.php rename to src/Document/Resource/Relationship.php index 05b4226..49cefcb 100644 --- a/src/Document/Resource/Relationship/Relationship.php +++ b/src/Document/Resource/Relationship.php @@ -1,14 +1,13 @@ isReservedName($name)) { - throw new \InvalidArgumentException('Can not use a reserved name'); + throw new \InvalidArgumentException("Can not use a reserved name '$name'"); } if (!$this->isValidMemberName($name)) { - throw new \OutOfBoundsException('Not a valid attribute name'); + throw new \OutOfBoundsException("Not a valid attribute name '$name'"); } if (isset($this->relationships[$name])) { - throw new \LogicException("Field $name already exists in relationships"); + throw new \LogicException("Field '$name' already exists in relationships"); } $this->attributes[$name] = $value; } @@ -49,10 +48,13 @@ public function setAttribute(string $name, $value) public function setRelationship(string $name, Relationship $relationship) { if ($this->isReservedName($name)) { - throw new \InvalidArgumentException('Can not use a reserved name'); + throw new \InvalidArgumentException("Can not use a reserved name '$name'"); + } + if (!$this->isValidMemberName($name)) { + throw new \OutOfBoundsException("Not a valid attribute name '$name'"); } if (isset($this->attributes[$name])) { - throw new \LogicException("Field $name already exists in attributes"); + throw new \LogicException("Field '$name' already exists in attributes"); } $this->relationships[$name] = $relationship; } diff --git a/test/Document/CompoundDocumentTest.php b/test/Document/CompoundDocumentTest.php index b22ef66..c796508 100644 --- a/test/Document/CompoundDocumentTest.php +++ b/test/Document/CompoundDocumentTest.php @@ -7,7 +7,7 @@ use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Linkage\MultiLinkage; use JsonApiPhp\JsonApi\Document\Resource\Linkage\SingleLinkage; -use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; +use JsonApiPhp\JsonApi\Document\Resource\Relationship; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use JsonApiPhp\JsonApi\Test\BaseTestCase; @@ -159,14 +159,26 @@ public function testOfficialDocsExample() /** * @expectedException \LogicException * @expectedExceptionMessage Full linkage is required for apples:1 + * @dataProvider documentsWithoutFullLinkage + * @param Document $doc */ - public function testFullLinkageIsRequired() + public function testFullLinkageIsRequired(Document $doc) { - $doc = Document::nullDocument(); $doc->setIncluded(new ResourceObject('apples', '1')); json_encode($doc); } + public function documentsWithoutFullLinkage(): array + { + return [ + [Document::nullDocument()], + [Document::fromIdentifier(new ResourceIdentifier('oranges', '1'))], + [Document::fromIdentifiers(new ResourceIdentifier('oranges', '1'), new ResourceIdentifier('oranges', '2'))], + [Document::fromResource(new ResourceObject('oranges', '1'))], + [Document::fromResources(new ResourceObject('oranges', '1'), new ResourceObject('oranges', '1'))], + ]; + } + /** * A compound document must be explicitly marked as sparse. In this case full linkage is not required. */ diff --git a/test/Document/DocumentTest.php b/test/Document/DocumentTest.php index 2d255ee..72e6336 100644 --- a/test/Document/DocumentTest.php +++ b/test/Document/DocumentTest.php @@ -11,7 +11,7 @@ use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use JsonApiPhp\JsonApi\Test\BaseTestCase; use JsonApiPhp\JsonApi\Test\Document\Resource\Relationship\LinkageTest; -use JsonApiPhp\JsonApi\Test\Document\Resource\ResourceTest; +use JsonApiPhp\JsonApi\Test\Document\Resource\ResourceObjectTest; /** * This is the JSON document's top level object @@ -68,7 +68,7 @@ public function testDocumentMayContainJustErrors() /** * A valid document may contain just a primary data object. - * The primary data object is represented by ResourceInterface (@see ResourceTest for details). + * The primary data object is represented by ResourceInterface (@see ResourceObjectTest for details). * Here is how a document can be created from different kinds of resources: * - null resource * - resource identifier diff --git a/test/Document/Resource/Relationship/RelationshipTest.php b/test/Document/Resource/Relationship/RelationshipTest.php index 8b98734..6fdfe10 100644 --- a/test/Document/Resource/Relationship/RelationshipTest.php +++ b/test/Document/Resource/Relationship/RelationshipTest.php @@ -6,8 +6,8 @@ use JsonApiPhp\JsonApi\Document\Link\Link; use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Linkage\NullLinkage; +use JsonApiPhp\JsonApi\Document\Resource\Relationship; use JsonApiPhp\JsonApi\Document\Resource\Relationship\Linkage; -use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; use JsonApiPhp\JsonApi\Test\BaseTestCase; /** @@ -52,7 +52,7 @@ public function testCanCreateFromSelfLink() } } ', - Relationship::fromSelfLink(new Link('http://localhost')) + \JsonApiPhp\JsonApi\Document\Resource\Relationship::fromSelfLink(new Link('http://localhost')) ); } @@ -66,7 +66,7 @@ public function testCanCreateFromRelatedLink() } } ', - Relationship::fromRelatedLink(new Link('http://localhost')) + \JsonApiPhp\JsonApi\Document\Resource\Relationship::fromRelatedLink(new Link('http://localhost')) ); } @@ -78,7 +78,7 @@ public function testCanCreateFromLinkage() "data": null } ', - Relationship::fromLinkage(new NullLinkage()) + \JsonApiPhp\JsonApi\Document\Resource\Relationship::fromLinkage(new NullLinkage()) ); } diff --git a/test/Document/Resource/ResourceFieldsTest.php b/test/Document/Resource/ResourceFieldsTest.php index c301fb3..f8aede2 100644 --- a/test/Document/Resource/ResourceFieldsTest.php +++ b/test/Document/Resource/ResourceFieldsTest.php @@ -4,7 +4,7 @@ namespace JsonApiPhp\JsonApi\Test\Document\Resource; use JsonApiPhp\JsonApi\Document\Meta; -use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; +use JsonApiPhp\JsonApi\Document\Resource\Relationship; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use PHPUnit\Framework\TestCase; @@ -23,7 +23,7 @@ class ResourceFieldsTest extends TestCase { /** * @expectedException \LogicException - * @expectedExceptionMessage Field foo already exists in attributes + * @expectedExceptionMessage Field 'foo' already exists in attributes */ public function testCanNotSetRelationshipIfAttributeExists() { @@ -34,7 +34,7 @@ public function testCanNotSetRelationshipIfAttributeExists() /** * @expectedException \LogicException - * @expectedExceptionMessage Field foo already exists in relationships + * @expectedExceptionMessage Field 'foo' already exists in relationships */ public function testCanNotSetAttributeIfRelationshipExists() { @@ -67,29 +67,6 @@ public function testRelationshipCanNotHaveReservedNames(string $name) $res->setRelationship($name, Relationship::fromMeta(Meta::fromArray(['a' => 'b']))); } - /** - * @param string $name - * @expectedException \OutOfBoundsException - * @expectedExceptionMessage Not a valid attribute name - * @dataProvider invalidAttributeNames - */ - public function testAttributeNameIsNotValid(string $name) - { - $res = new ResourceObject('books', 'abc'); - $res->setAttribute($name, 1); - } - - /** - * @param string $name - * @dataProvider validAttributeNames - */ - public function testAttributeNameIsValid(string $name) - { - $res = new ResourceObject('books', 'abc'); - $res->setAttribute($name, 1); - $this->assertTrue(true); - } - public function reservedAttributeNames(): array { return [ @@ -97,35 +74,4 @@ public function reservedAttributeNames(): array ['type'], ]; } - - public function invalidAttributeNames(): array - { - return [ - ['_abcde'], - ['abcd_'], - ['abc$EDS'], - ['#abcde'], - ['abcde('], - ['b_'], - ['_a'], - ['$ab_c-d'], - ['-abc'], - ]; - } - - public function validAttributeNames(): array - { - return [ - ['abcd'], - ['abcA4C'], - ['abc_d3f45'], - ['abd_eca'], - ['a'], - ['b'], - ['ab'], - ['a-bc_de'], - ['abcéêçèÇ_n'], - ['abc 汉字 abc'], - ]; - } } diff --git a/test/Document/Resource/ResourceIdentifierTest.php b/test/Document/Resource/ResourceIdentifierTest.php new file mode 100644 index 0000000..bc1c7f0 --- /dev/null +++ b/test/Document/Resource/ResourceIdentifierTest.php @@ -0,0 +1,45 @@ +assertEncodesTo( + ' + { + "type": "books", + "id": "1" + } + ', + new ResourceIdentifier('books', '1') + ); + } + + public function testResourceIdentifierMayContainMeta() + { + $this->assertEncodesTo( + ' + { + "type": "books", + "id": "1", + "meta": { + "foo":"bar" + } + } + ', + new ResourceIdentifier('books', '1', Meta::fromArray(['foo' => 'bar'])) + ); + } +} diff --git a/test/Document/Resource/ResourceObjectTest.php b/test/Document/Resource/ResourceObjectTest.php new file mode 100644 index 0000000..8dfdb37 --- /dev/null +++ b/test/Document/Resource/ResourceObjectTest.php @@ -0,0 +1,111 @@ +assertEncodesTo('{"type": "books"}', new ResourceObject('books')); + } + + /** + * In addition, a resource object MAY contain any of these top-level members: + * + * - attributes: an attributes object representing some of the resource’s data. + * + * - relationships: a relationships object describing relationships + * between the resource and other JSON API resources. + * + * - links: a links object containing links related to the resource. + * + * - meta: a meta object containing non-standard meta-information about a resource + * that can not be represented as an attribute or relationship. + */ + public function testResourceObjectMayContainAttributes() + { + $apple = new ResourceObject('apples', '1'); + $apple->setAttribute('color', 'red'); + $this->assertEncodesTo('{"type":"apples", "id":"1", "attributes":{"color":"red"}}', $apple); + } + + public function testResourceObjectMayContainRelationships() + { + $article = new ResourceObject('articles', '1'); + $user = new ResourceIdentifier('users', '42'); + $article->setRelationship('author', Relationship::fromLinkage(new SingleLinkage($user))); + $this->assertEncodesTo( + ' + { + "type":"articles", + "id":"1", + "relationships":{ + "author":{ + "data":{ + "type":"users", + "id":"42" + } + } + } + } + ', + $article + ); + } + + public function testResourceObjectMayContainLinks() + { + $article = new ResourceObject('articles', '1'); + $article->setLink('self', 'https://example.com'); + $this->assertEncodesTo( + ' + { + "type":"articles", + "id":"1", + "links":{ + "self":"https://example.com" + } + } + ', + $article + ); + } + + public function testResourceObjectMayContainMeta() + { + $article = new ResourceObject('articles', '1'); + $article->setMeta(Meta::fromArray(['tags' => ['cool', 'new']])); + $this->assertEncodesTo( + ' + { + "type":"articles", + "id":"1", + "meta":{ + "tags":[ + "cool", + "new" + ] + } + } + ', + $article + ); + } +} diff --git a/test/Document/Resource/ResourceTest.php b/test/Document/Resource/ResourceTest.php deleted file mode 100644 index eeb3e7e..0000000 --- a/test/Document/Resource/ResourceTest.php +++ /dev/null @@ -1,86 +0,0 @@ -assertEncodesTo($expected, $data); - } - - public function resourceProvider() - { - return [ - [ - '{"type": "books"}', - new ResourceObject('books'), - ], - [ - '{"type":"books","id":"42abc"}', - new ResourceIdentifier('books', '42abc'), - ], - [ - ' - { - "type": "books", - "id": "42abc", - "meta": { - "foo":"bar" - } - } - ', - new ResourceIdentifier('books', '42abc', Meta::fromArray(['foo' => 'bar'])), - ], - [ - ' - { - "type": "books", - "id": "42abc", - "attributes": { - "attr": "val" - }, - "relationships": { - "author": { - "meta": { - "a": "b" - } - } - }, - "links": { - "self": "http://localhost" - }, - "meta": { - "foo": "bar" - } - } - ', - (function () { - $resource = new ResourceObject('books', '42abc'); - $resource->setMeta(Meta::fromArray(['foo' => 'bar'])); - $resource->setAttribute('attr', 'val'); - $resource->setLink('self', 'http://localhost'); - $resource->setRelationship('author', Relationship::fromMeta(Meta::fromArray(['a' => 'b']))); - return $resource; - })(), - ], - ]; - } -} diff --git a/test/IntegrationTest.php b/test/IntegrationTest.php index 12b189d..a884d86 100644 --- a/test/IntegrationTest.php +++ b/test/IntegrationTest.php @@ -5,7 +5,7 @@ use JsonApiPhp\JsonApi\Document; use JsonApiPhp\JsonApi\Document\Resource\Linkage\SingleLinkage; -use JsonApiPhp\JsonApi\Document\Resource\Relationship\Relationship; +use JsonApiPhp\JsonApi\Document\Resource\Relationship; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use PHPUnit\Framework\TestCase; diff --git a/test/MemberNamesTest.php b/test/MemberNamesTest.php new file mode 100644 index 0000000..d7c405f --- /dev/null +++ b/test/MemberNamesTest.php @@ -0,0 +1,89 @@ +setAttribute($name, 1); + } + + /** + * @param string $name + * @dataProvider validAttributeNames + */ + public function testValidAttributeNamesCanBeSet(string $name) + { + $res = new ResourceObject('books', 'abc'); + $res->setAttribute($name, 1); + $this->assertInternalType('string', json_encode($res)); + } + + /** + * @param string $name + * @expectedException \OutOfBoundsException + * @expectedExceptionMessage Not a valid attribute name + * @dataProvider invalidAttributeNames + */ + public function testInvalidRelationshipNamesAreNotAllowed(string $name) + { + $res = new ResourceObject('books', 'abc'); + $res->setRelationship($name, Relationship::fromSelfLink(new Link('https://example.com'))); + } + + /** + * @param string $name + * @dataProvider validAttributeNames + */ + public function testValidRelationshipNamesCanBeSet(string $name) + { + $res = new ResourceObject('books', 'abc'); + $res->setRelationship($name, Relationship::fromSelfLink(new Link('https://example.com'))); + $this->assertInternalType('string', json_encode($res)); + } + + public function invalidAttributeNames(): array + { + return [ + ['_abcde'], + ['abcd_'], + ['abc$EDS'], + ['#abcde'], + ['abcde('], + ['b_'], + ['_a'], + ['$ab_c-d'], + ['-abc'], + ]; + } + + public function validAttributeNames(): array + { + return [ + ['abcd'], + ['abcA4C'], + ['abc_d3f45'], + ['abd_eca'], + ['a'], + ['b'], + ['ab'], + ['a-bc_de'], + ['abcéêçèÇ_n'], + ['abc 汉字 abc'], + ]; + } +} From 814505e1fc467b727d50adf9c856c09129c0c752 Mon Sep 17 00:00:00 2001 From: Ananya Singh <10459084+anniesingh@users.noreply.github.com> Date: Sun, 15 Oct 2017 13:38:44 +0530 Subject: [PATCH 12/45] Add PHP 7.2 to CI builds (#65) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 8fa35d0..b6598c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: php php: - '7.0' - '7.1' + - '7.2' before_script: - composer install From d5b5dffc2f5d106d346b8d432ef7b0f462900ca3 Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 18 Oct 2017 00:21:36 -0700 Subject: [PATCH 13/45] Doc2test (#68) --- .codeclimate.yml | 4 +--- .doc2test.ini | 2 ++ .gitignore | 1 + .travis.yml | 6 ++++++ README.md | 13 +++++++------ composer.json | 5 +++-- phpunit.xml.dist | 9 --------- test/Document/CompoundDocumentTest.php | 8 ++++++++ 8 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 .doc2test.ini diff --git a/.codeclimate.yml b/.codeclimate.yml index 7205c36..00cf4c1 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -2,9 +2,7 @@ engines: phpmd: enabled: true checks: - Controversial/CamelCasePropertyName: - enabled: false Naming/ShortVariable: enabled: false CleanCode/StaticAccess: - enabled: false \ No newline at end of file + enabled: false diff --git a/.doc2test.ini b/.doc2test.ini new file mode 100644 index 0000000..550103c --- /dev/null +++ b/.doc2test.ini @@ -0,0 +1,2 @@ +source = ./README.md +destination = ./doc-test diff --git a/.gitignore b/.gitignore index d136198..f22a32d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ composer.phar +/doc-test/ /vendor/ /composer.lock /.php_cs.cache diff --git a/.travis.yml b/.travis.yml index b6598c8..06332bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,3 +10,9 @@ before_script: script: - vendor/bin/php-cs-fixer fix -v --dry-run - phpunit --coverage-clover build/logs/clover.xml + - vendor/bin/doc2test && vendor/bin/phpunit -c doc-test/phpunit.xml + +matrix: + exclude: + - php: '7.0' + script: vendor/bin/doc2test && vendor/bin/phpunit -c doc-test/phpunit.xml diff --git a/README.md b/README.md index 36b1821..fdc1cc9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This library is an attempt to express business rules of JSON API specification i A simple example to illustrate the general idea. This JSON representation from [the documentation](http://jsonapi.org/format/#document-resource-objects) - + ```json { "data": { @@ -28,19 +28,20 @@ A simple example to illustrate the general idea. This JSON representation from } ``` can be built with the following php code (less imports): + ```php setLink('self', '/articles/1/relationships/author'); $author->setLink('related','/articles/1/author'); $articles->setRelationship('author', $author); $articles->setAttribute('title', 'Rails is Omakase'); -$doc = Document::fromResource($articles); +$doc = \JsonApiPhp\JsonApi\Document::fromResource($articles); echo json_encode($doc, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); ``` diff --git a/composer.json b/composer.json index 0decca3..2f3dc68 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "json-api-php/json-api", "description": "An attempt to express JSON API specs (jsonapi.org) in object-oriented way as a set of PHP 7 classes", "type": "library", - + "prefer-stable": true, "license": "MIT", "authors": [ { @@ -15,7 +15,8 @@ }, "require-dev": { "phpunit/phpunit": "^6.0", - "friendsofphp/php-cs-fixer": "^2.2" + "friendsofphp/php-cs-fixer": "^2.2", + "doc2test/doc2test": "dev-master" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1b86d03..48bfa68 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,4 @@ - - setIncluded($dan, $comment05, $comment12); + $doc->setLink('self', 'http://example.com/articles'); + $doc->setLink('next', 'http://example.com/articles?page[offset]=2'); + $doc->setLink('last', 'http://example.com/articles?page[offset]=10'); $this->assertEncodesTo( ' { + "links": { + "self": "http://example.com/articles", + "next": "http://example.com/articles?page[offset]=2", + "last": "http://example.com/articles?page[offset]=10" + }, "data": [{ "type": "articles", "id": "1", From 825b828fbb542d5512fb1c80f96cc67b57880595 Mon Sep 17 00:00:00 2001 From: Volodymyr Tsykun Date: Thu, 19 Oct 2017 08:30:12 +0300 Subject: [PATCH 14/45] Added name validation in meta (#67) --- src/Document/Meta.php | 20 +++++++++++++++++++ test/MemberNamesTest.php | 43 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/src/Document/Meta.php b/src/Document/Meta.php index 5220baf..50786c0 100644 --- a/src/Document/Meta.php +++ b/src/Document/Meta.php @@ -9,6 +9,8 @@ class Meta implements \JsonSerializable public function __construct(\stdClass $data) { + $this->validateObject($data); + $this->data = $data; } @@ -21,4 +23,22 @@ public function jsonSerialize() { return $this->data; } + + private function validateObject($object) + { + foreach ($object as $name => $value) { + if (is_string($name) && !$this->isValidMemberName($name)) { + throw new \OutOfBoundsException("Not a valid attribute name '$name'"); + } + + if (is_array($value) || $value instanceof \stdClass) { + $this->validateObject($value); + } + } + } + + private function isValidMemberName(string $name): bool + { + return preg_match('/^(?=[^-_ ])[a-zA-Z0-9\x{0080}-\x{FFFF}-_ ]*(?<=[^-_ ])$/u', $name) === 1; + } } diff --git a/test/MemberNamesTest.php b/test/MemberNamesTest.php index d7c405f..c2eb41e 100644 --- a/test/MemberNamesTest.php +++ b/test/MemberNamesTest.php @@ -4,6 +4,7 @@ namespace JsonApiPhp\JsonApi\Test; use JsonApiPhp\JsonApi\Document\Link\Link; +use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Relationship; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use PHPUnit\Framework\TestCase; @@ -45,6 +46,48 @@ public function testInvalidRelationshipNamesAreNotAllowed(string $name) $res->setRelationship($name, Relationship::fromSelfLink(new Link('https://example.com'))); } + /** + * @param string $name + * @expectedException \OutOfBoundsException + * @expectedExceptionMessage Not a valid attribute name + * @dataProvider invalidAttributeNames + */ + public function testInvalidMetaNames(string $name) + { + Meta::fromArray( + [ + 'copyright' => 'Copyright 2015 Example Corp.', + 'authors' => [ + [ + 'firstname' => 'Yehuda', + $name => 'Katz', + ], + ], + ] + ); + } + + /** + * @param string $name + * @dataProvider validAttributeNames + */ + public function testValidMetaNames(string $name) + { + $meta = Meta::fromArray( + [ + 'copyright' => 'Copyright 2015 Example Corp.', + 'authors' => [ + [ + 'firstname' => 'Yehuda', + $name => 'Katz', + ], + ], + ] + ); + + $this->assertInternalType('string', json_encode($meta)); + } + /** * @param string $name * @dataProvider validAttributeNames From 62e564fd0bcaa79b60472435b554541498c12b4a Mon Sep 17 00:00:00 2001 From: Alexey Date: Fri, 20 Oct 2017 01:16:22 -0700 Subject: [PATCH 15/45] Member names validation (#69) --- .travis.yml | 5 -- composer.json | 2 +- src/Document/Container.php | 27 ++++++++++ src/Document/LinksTrait.php | 15 ++++-- src/Document/MemberName.php | 12 +++++ src/Document/Meta.php | 43 +++++++++++---- src/Document/ReservedName.php | 20 +++++++ src/Document/Resource/ResourceObject.php | 29 ++--------- src/Document/Resource/ResourceType.php | 14 +++++ src/Document/SpecialValue.php | 32 ++++++++++++ test/MemberNamesTest.php | 66 ++++-------------------- 11 files changed, 166 insertions(+), 99 deletions(-) create mode 100644 src/Document/Container.php create mode 100644 src/Document/MemberName.php create mode 100644 src/Document/ReservedName.php create mode 100644 src/Document/Resource/ResourceType.php create mode 100644 src/Document/SpecialValue.php diff --git a/.travis.yml b/.travis.yml index 06332bf..641d25d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,3 @@ script: - vendor/bin/php-cs-fixer fix -v --dry-run - phpunit --coverage-clover build/logs/clover.xml - vendor/bin/doc2test && vendor/bin/phpunit -c doc-test/phpunit.xml - -matrix: - exclude: - - php: '7.0' - script: vendor/bin/doc2test && vendor/bin/phpunit -c doc-test/phpunit.xml diff --git a/composer.json b/composer.json index 2f3dc68..c6dbdc6 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,6 @@ } }, "scripts": { - "test": "php-cs-fixer fix -v --dry-run --ansi && phpunit --colors=always --coverage-text" + "test": "php-cs-fixer fix -v --dry-run --ansi && phpunit --colors=always --coverage-text && vendor/bin/doc2test && vendor/bin/phpunit -c doc-test/phpunit.xml --coverage-text" } } diff --git a/src/Document/Container.php b/src/Document/Container.php new file mode 100644 index 0000000..e7f57ec --- /dev/null +++ b/src/Document/Container.php @@ -0,0 +1,27 @@ +data) { + $this->data = (object) []; + } + $this->data->$name = $value; + } + + public function getIterator(): \Traversable + { + return $this->data; + } + + public function jsonSerialize() + { + return $this->data; + } +} diff --git a/src/Document/LinksTrait.php b/src/Document/LinksTrait.php index a246991..700872a 100644 --- a/src/Document/LinksTrait.php +++ b/src/Document/LinksTrait.php @@ -9,17 +9,26 @@ trait LinksTrait { /** - * @var LinkInterface[] + * @var Container|null */ protected $links; public function setLink(string $name, string $url) { - $this->links[$name] = new Link($url); + $this->init(); + $this->links->set(new MemberName($name), new Link($url)); } public function setLinkObject(string $name, LinkInterface $link) { - $this->links[$name] = $link; + $this->init(); + $this->links->set(new MemberName($name), $link); + } + + private function init() + { + if (! $this->links) { + $this->links = new Container(); + } } } diff --git a/src/Document/MemberName.php b/src/Document/MemberName.php new file mode 100644 index 0000000..fab7026 --- /dev/null +++ b/src/Document/MemberName.php @@ -0,0 +1,12 @@ +validateObject($data); - - $this->data = $data; + $this->data = $this->toContainer($data); } public static function fromArray(array $array): self @@ -24,21 +25,43 @@ public function jsonSerialize() return $this->data; } - private function validateObject($object) + private function toContainer($object): Container { + $c = new Container(); foreach ($object as $name => $value) { - if (is_string($name) && !$this->isValidMemberName($name)) { - throw new \OutOfBoundsException("Not a valid attribute name '$name'"); + if (is_object($object)) { + $name = (string) $name; + } + if ($this->canConvert($value)) { + $value = $this->toContainer($value); + } else { + $value = $this->traverse($value); } + $c->set(new MemberName($name), $value); + } + return $c; + } - if (is_array($value) || $value instanceof \stdClass) { - $this->validateObject($value); + private function traverse($value) + { + if ($this->canConvert($value)) { + return $this->toContainer($value); + } + if (is_array($value)) { + foreach ($value as $k => $v) { + $value[$k] = $this->traverse($v); } } + return $value; } - private function isValidMemberName(string $name): bool + private function canConvert($v): bool { - return preg_match('/^(?=[^-_ ])[a-zA-Z0-9\x{0080}-\x{FFFF}-_ ]*(?<=[^-_ ])$/u', $name) === 1; + return is_object($v) + || ( + is_array($v) + && $v !== [] + && array_keys($v) !== range(0, count($v) - 1) + ); } } diff --git a/src/Document/ReservedName.php b/src/Document/ReservedName.php new file mode 100644 index 0000000..50771ea --- /dev/null +++ b/src/Document/ReservedName.php @@ -0,0 +1,20 @@ +isReservedName($name)) { + throw new \InvalidArgumentException("Can not use a reserved name '$name'"); + } + } + + private function isReservedName(string $name): bool + { + return in_array($name, ['id', 'type']); + } +} diff --git a/src/Document/Resource/ResourceObject.php b/src/Document/Resource/ResourceObject.php index 337d65d..e9b68b7 100644 --- a/src/Document/Resource/ResourceObject.php +++ b/src/Document/Resource/ResourceObject.php @@ -5,6 +5,7 @@ use JsonApiPhp\JsonApi\Document\LinksTrait; use JsonApiPhp\JsonApi\Document\Meta; +use JsonApiPhp\JsonApi\Document\ReservedName; class ResourceObject implements \JsonSerializable { @@ -22,7 +23,7 @@ class ResourceObject implements \JsonSerializable public function __construct(string $type, string $id = null) { - $this->type = $type; + $this->type = new ResourceType($type); $this->id = $id; } @@ -33,12 +34,7 @@ public function setMeta(Meta $meta) public function setAttribute(string $name, $value) { - if ($this->isReservedName($name)) { - throw new \InvalidArgumentException("Can not use a reserved name '$name'"); - } - if (!$this->isValidMemberName($name)) { - throw new \OutOfBoundsException("Not a valid attribute name '$name'"); - } + $name = (string) new ReservedName($name); if (isset($this->relationships[$name])) { throw new \LogicException("Field '$name' already exists in relationships"); } @@ -47,12 +43,7 @@ public function setAttribute(string $name, $value) public function setRelationship(string $name, Relationship $relationship) { - if ($this->isReservedName($name)) { - throw new \InvalidArgumentException("Can not use a reserved name '$name'"); - } - if (!$this->isValidMemberName($name)) { - throw new \OutOfBoundsException("Not a valid attribute name '$name'"); - } + $name = (string) new ReservedName($name); if (isset($this->attributes[$name])) { throw new \LogicException("Field '$name' already exists in attributes"); } @@ -61,7 +52,7 @@ public function setRelationship(string $name, Relationship $relationship) public function toIdentifier(): ResourceIdentifier { - return new ResourceIdentifier($this->type, $this->id); + return new ResourceIdentifier((string) $this->type, $this->id); } public function jsonSerialize() @@ -92,14 +83,4 @@ public function identifies(ResourceObject $resource): bool } return false; } - - private function isReservedName(string $name): bool - { - return in_array($name, ['id', 'type']); - } - - private function isValidMemberName(string $name): bool - { - return preg_match('/^(?=[^-_ ])[a-zA-Z0-9\x{0080}-\x{FFFF}-_ ]*(?<=[^-_ ])$/u', $name) === 1; - } } diff --git a/src/Document/Resource/ResourceType.php b/src/Document/Resource/ResourceType.php new file mode 100644 index 0000000..a3b9362 --- /dev/null +++ b/src/Document/Resource/ResourceType.php @@ -0,0 +1,14 @@ +isValidMemberNameOrTypeValue($val)) { + throw new \OutOfBoundsException(sprintf($errorMessage, $val)); + } + $this->val = $val; + } + + public function jsonSerialize() + { + return $this->val; + } + + public function __toString() + { + return $this->val; + } + + protected function isValidMemberNameOrTypeValue(string $name): bool + { + return preg_match('/^(?=[^-_ ])[a-zA-Z0-9\x{0080}-\x{FFFF}-_ ]*(?<=[^-_ ])$/u', $name) === 1; + } +} diff --git a/test/MemberNamesTest.php b/test/MemberNamesTest.php index c2eb41e..219c6e1 100644 --- a/test/MemberNamesTest.php +++ b/test/MemberNamesTest.php @@ -14,7 +14,7 @@ class MemberNamesTest extends TestCase /** * @param string $name * @expectedException \OutOfBoundsException - * @expectedExceptionMessage Not a valid attribute name + * @expectedExceptionMessage Invalid member name * @dataProvider invalidAttributeNames */ public function testInvalidAttributeNamesAreNotAllowed(string $name) @@ -24,20 +24,22 @@ public function testInvalidAttributeNamesAreNotAllowed(string $name) } /** + * The values of type members MUST adhere to the same constraints as member names. + * * @param string $name - * @dataProvider validAttributeNames + * @expectedException \OutOfBoundsException + * @expectedExceptionMessage Invalid resource type + * @dataProvider invalidAttributeNames */ - public function testValidAttributeNamesCanBeSet(string $name) + public function testInvalidTypesAreNotAllowed(string $name) { - $res = new ResourceObject('books', 'abc'); - $res->setAttribute($name, 1); - $this->assertInternalType('string', json_encode($res)); + new ResourceObject($name, 'abc'); } /** * @param string $name * @expectedException \OutOfBoundsException - * @expectedExceptionMessage Not a valid attribute name + * @expectedExceptionMessage Invalid member name * @dataProvider invalidAttributeNames */ public function testInvalidRelationshipNamesAreNotAllowed(string $name) @@ -49,7 +51,7 @@ public function testInvalidRelationshipNamesAreNotAllowed(string $name) /** * @param string $name * @expectedException \OutOfBoundsException - * @expectedExceptionMessage Not a valid attribute name + * @expectedExceptionMessage Invalid member name * @dataProvider invalidAttributeNames */ public function testInvalidMetaNames(string $name) @@ -67,38 +69,6 @@ public function testInvalidMetaNames(string $name) ); } - /** - * @param string $name - * @dataProvider validAttributeNames - */ - public function testValidMetaNames(string $name) - { - $meta = Meta::fromArray( - [ - 'copyright' => 'Copyright 2015 Example Corp.', - 'authors' => [ - [ - 'firstname' => 'Yehuda', - $name => 'Katz', - ], - ], - ] - ); - - $this->assertInternalType('string', json_encode($meta)); - } - - /** - * @param string $name - * @dataProvider validAttributeNames - */ - public function testValidRelationshipNamesCanBeSet(string $name) - { - $res = new ResourceObject('books', 'abc'); - $res->setRelationship($name, Relationship::fromSelfLink(new Link('https://example.com'))); - $this->assertInternalType('string', json_encode($res)); - } - public function invalidAttributeNames(): array { return [ @@ -113,20 +83,4 @@ public function invalidAttributeNames(): array ['-abc'], ]; } - - public function validAttributeNames(): array - { - return [ - ['abcd'], - ['abcA4C'], - ['abc_d3f45'], - ['abd_eca'], - ['a'], - ['b'], - ['ab'], - ['a-bc_de'], - ['abcéêçèÇ_n'], - ['abc 汉字 abc'], - ]; - } } From 266d153c994d54e09aa5240cbd0fac01942a7041 Mon Sep 17 00:00:00 2001 From: Alexey Date: Tue, 24 Oct 2017 23:10:53 -0700 Subject: [PATCH 16/45] Improve member names validation (#70) --- .travis.yml | 1 - composer.json | 7 +- src/Document.php | 37 +++-- src/Document/Container.php | 22 +-- src/Document/Error.php | 27 ++-- src/Document/Link/Link.php | 19 --- src/Document/Link/LinkInterface.php | 9 -- src/Document/Link/LinkObject.php | 24 ---- src/Document/LinksTrait.php | 24 +--- src/Document/MemberName.php | 12 -- src/Document/Meta.php | 67 --------- src/Document/MetaTrait.php | 4 +- src/Document/ReservedName.php | 20 --- src/Document/Resource/Relationship.php | 28 ++-- src/Document/Resource/ResourceIdentifier.php | 41 +++--- src/Document/Resource/ResourceObject.php | 59 +++++--- src/Document/Resource/ResourceType.php | 14 -- src/Document/SpecialValue.php | 32 ----- src/functions.php | 25 ++++ test/Document/CompoundDocumentTest.php | 3 +- test/Document/DocumentTest.php | 12 +- test/Document/ErrorTest.php | 2 +- test/Document/MetaTest.php | 15 -- .../Relationship/RelationshipTest.php | 19 ++- test/Document/Resource/ResourceFieldsTest.php | 7 +- .../Resource/ResourceIdentifierTest.php | 3 +- test/Document/Resource/ResourceObjectTest.php | 3 +- test/MemberNamesTest.php | 131 +++++++++++------- 28 files changed, 246 insertions(+), 421 deletions(-) delete mode 100644 src/Document/Link/Link.php delete mode 100644 src/Document/Link/LinkInterface.php delete mode 100644 src/Document/Link/LinkObject.php delete mode 100644 src/Document/MemberName.php delete mode 100644 src/Document/Meta.php delete mode 100644 src/Document/ReservedName.php delete mode 100644 src/Document/Resource/ResourceType.php delete mode 100644 src/Document/SpecialValue.php create mode 100644 src/functions.php delete mode 100644 test/Document/MetaTest.php diff --git a/.travis.yml b/.travis.yml index 641d25d..55b45fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: php php: - - '7.0' - '7.1' - '7.2' diff --git a/composer.json b/composer.json index c6dbdc6..9982e20 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ } ], "require": { - "php": ">=7.0" + "php": ">=7.1" }, "require-dev": { "phpunit/phpunit": "^6.0", @@ -21,7 +21,10 @@ "autoload": { "psr-4": { "JsonApiPhp\\JsonApi\\": "src/" - } + }, + "files": [ + "src/functions.php" + ] }, "autoload-dev": { "psr-4": { diff --git a/src/Document.php b/src/Document.php index bdb77cc..32fdccb 100644 --- a/src/Document.php +++ b/src/Document.php @@ -3,9 +3,9 @@ namespace JsonApiPhp\JsonApi; +use JsonApiPhp\JsonApi\Document\Container; use JsonApiPhp\JsonApi\Document\Error; use JsonApiPhp\JsonApi\Document\LinksTrait; -use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\MetaTrait; use JsonApiPhp\JsonApi\Document\PrimaryData\MultiIdentifierData; use JsonApiPhp\JsonApi\Document\PrimaryData\MultiResourceData; @@ -16,7 +16,7 @@ use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; -class Document implements \JsonSerializable +final class Document implements \JsonSerializable { const MEDIA_TYPE = 'application/vnd.api+json'; const DEFAULT_API_VERSION = '1.0'; @@ -46,7 +46,7 @@ private function __construct() { } - public static function fromMeta(Meta $meta): self + public static function fromMeta(iterable $meta): self { $doc = new self; $doc->setMeta($meta); @@ -100,12 +100,12 @@ public function setApiVersion(string $version = self::DEFAULT_API_VERSION) $this->api['version'] = $version; } - public function setApiMeta(Meta $meta) + public function setApiMeta(iterable $meta): void { - $this->api['meta'] = $meta; + $this->api['meta'] = new Container($meta); } - public function setIncluded(ResourceObject ...$resources) + public function setIncluded(ResourceObject ...$resources): void { if (null === $this->data) { throw new \LogicException('Document with no data cannot contain included resources'); @@ -118,7 +118,7 @@ public function setIncluded(ResourceObject ...$resources) } } - public function markSparse() + public function markSparse(): void { $this->sparse = true; } @@ -126,22 +126,17 @@ public function markSparse() public function jsonSerialize() { $this->enforceFullLinkage(); - return array_filter( - [ - 'data' => $this->data, - 'errors' => $this->errors, - 'meta' => $this->meta, - 'jsonapi' => $this->api, - 'links' => $this->links, - 'included' => $this->included ? array_values($this->included) : null, - ], - function ($v) { - return null !== $v; - } - ); + return filterNulls([ + 'data' => $this->data, + 'errors' => $this->errors, + 'meta' => $this->meta, + 'jsonapi' => $this->api, + 'links' => $this->links, + 'included' => $this->included ? array_values($this->included) : null, + ]); } - private function enforceFullLinkage() + private function enforceFullLinkage(): void { if ($this->sparse || empty($this->included)) { return; diff --git a/src/Document/Container.php b/src/Document/Container.php index e7f57ec..694f504 100644 --- a/src/Document/Container.php +++ b/src/Document/Container.php @@ -3,25 +3,31 @@ namespace JsonApiPhp\JsonApi\Document; -class Container implements \JsonSerializable, \IteratorAggregate +use function JsonApiPhp\JsonApi\isValidMemberName; + +final class Container implements \JsonSerializable { private $data; - public function set(MemberName $name, $value) + public function __construct(iterable $data = null) { - if (! $this->data) { - $this->data = (object) []; + if ($data) { + foreach ($data as $k => $v) { + $this->set((string) $k, $v); + } } - $this->data->$name = $value; } - public function getIterator(): \Traversable + public function set(string $name, $value) { - return $this->data; + if (! isValidMemberName($name)) { + throw new \OutOfBoundsException("Invalid member name '$name'"); + } + $this->data[$name] = $value; } public function jsonSerialize() { - return $this->data; + return (object) $this->data; } } diff --git a/src/Document/Error.php b/src/Document/Error.php index 03e23b7..b8ed3a8 100644 --- a/src/Document/Error.php +++ b/src/Document/Error.php @@ -3,6 +3,8 @@ namespace JsonApiPhp\JsonApi\Document; +use function JsonApiPhp\JsonApi\filterNulls; + final class Error implements \JsonSerializable { use MetaTrait; @@ -65,20 +67,15 @@ public function setSourceParameter(string $parameter) public function jsonSerialize() { - return array_filter( - [ - 'id' => $this->id, - 'links' => $this->links, - 'status' => $this->status, - 'code' => $this->code, - 'title' => $this->title, - 'detail' => $this->detail, - 'source' => $this->source, - 'meta' => $this->meta, - ], - function ($v) { - return null !== $v; - } - ) ?: (object) []; + return filterNulls([ + 'id' => $this->id, + 'links' => $this->links, + 'status' => $this->status, + 'code' => $this->code, + 'title' => $this->title, + 'detail' => $this->detail, + 'source' => $this->source, + 'meta' => $this->meta, + ]) ?: (object) []; } } diff --git a/src/Document/Link/Link.php b/src/Document/Link/Link.php deleted file mode 100644 index 27070c3..0000000 --- a/src/Document/Link/Link.php +++ /dev/null @@ -1,19 +0,0 @@ -url = $url; - } - - public function jsonSerialize() - { - return $this->url; - } -} diff --git a/src/Document/Link/LinkInterface.php b/src/Document/Link/LinkInterface.php deleted file mode 100644 index b5dc9d9..0000000 --- a/src/Document/Link/LinkInterface.php +++ /dev/null @@ -1,9 +0,0 @@ -link['href'] = $href; - if ($meta) { - $this->link['meta'] = $meta; - } - } - - public function jsonSerialize() - { - return $this->link; - } -} diff --git a/src/Document/LinksTrait.php b/src/Document/LinksTrait.php index 700872a..8f84849 100644 --- a/src/Document/LinksTrait.php +++ b/src/Document/LinksTrait.php @@ -3,32 +3,16 @@ namespace JsonApiPhp\JsonApi\Document; -use JsonApiPhp\JsonApi\Document\Link\Link; -use JsonApiPhp\JsonApi\Document\Link\LinkInterface; - trait LinksTrait { /** - * @var Container|null + * @var Container */ protected $links; - public function setLink(string $name, string $url) - { - $this->init(); - $this->links->set(new MemberName($name), new Link($url)); - } - - public function setLinkObject(string $name, LinkInterface $link) - { - $this->init(); - $this->links->set(new MemberName($name), $link); - } - - private function init() + public function setLink(string $name, string $url, iterable $meta = null) { - if (! $this->links) { - $this->links = new Container(); - } + $this->links = $this->links ?: new Container(); + $this->links->set($name, $meta ? ['meta' => new Container($meta), 'href' => $url] : $url); } } diff --git a/src/Document/MemberName.php b/src/Document/MemberName.php deleted file mode 100644 index fab7026..0000000 --- a/src/Document/MemberName.php +++ /dev/null @@ -1,12 +0,0 @@ -data = $this->toContainer($data); - } - - public static function fromArray(array $array): self - { - return new self((object) $array); - } - - public function jsonSerialize() - { - return $this->data; - } - - private function toContainer($object): Container - { - $c = new Container(); - foreach ($object as $name => $value) { - if (is_object($object)) { - $name = (string) $name; - } - if ($this->canConvert($value)) { - $value = $this->toContainer($value); - } else { - $value = $this->traverse($value); - } - $c->set(new MemberName($name), $value); - } - return $c; - } - - private function traverse($value) - { - if ($this->canConvert($value)) { - return $this->toContainer($value); - } - if (is_array($value)) { - foreach ($value as $k => $v) { - $value[$k] = $this->traverse($v); - } - } - return $value; - } - - private function canConvert($v): bool - { - return is_object($v) - || ( - is_array($v) - && $v !== [] - && array_keys($v) !== range(0, count($v) - 1) - ); - } -} diff --git a/src/Document/MetaTrait.php b/src/Document/MetaTrait.php index 91ccc38..562402d 100644 --- a/src/Document/MetaTrait.php +++ b/src/Document/MetaTrait.php @@ -7,8 +7,8 @@ trait MetaTrait { protected $meta; - public function setMeta(Meta $meta) + public function setMeta(iterable $meta): void { - $this->meta = $meta; + $this->meta = new Container($meta); } } diff --git a/src/Document/ReservedName.php b/src/Document/ReservedName.php deleted file mode 100644 index 50771ea..0000000 --- a/src/Document/ReservedName.php +++ /dev/null @@ -1,20 +0,0 @@ -isReservedName($name)) { - throw new \InvalidArgumentException("Can not use a reserved name '$name'"); - } - } - - private function isReservedName(string $name): bool - { - return in_array($name, ['id', 'type']); - } -} diff --git a/src/Document/Resource/Relationship.php b/src/Document/Resource/Relationship.php index 49cefcb..da2bdd3 100644 --- a/src/Document/Resource/Relationship.php +++ b/src/Document/Resource/Relationship.php @@ -3,11 +3,10 @@ namespace JsonApiPhp\JsonApi\Document\Resource; -use JsonApiPhp\JsonApi\Document\Link\LinkInterface; use JsonApiPhp\JsonApi\Document\LinksTrait; -use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\MetaTrait; use JsonApiPhp\JsonApi\Document\Resource\Linkage\LinkageInterface; +use function JsonApiPhp\JsonApi\filterNulls; final class Relationship implements \JsonSerializable { @@ -23,24 +22,24 @@ private function __construct() { } - public static function fromMeta(Meta $meta): self + public static function fromMeta(iterable $meta): self { $r = new self; $r->setMeta($meta); return $r; } - public static function fromSelfLink(LinkInterface $link): self + public static function fromSelfLink(string $url, iterable $meta = null): self { $r = new self; - $r->setLinkObject('self', $link); + $r->setLink('self', $url, $meta); return $r; } - public static function fromRelatedLink(LinkInterface $link): self + public static function fromRelatedLink(string $url, iterable $meta = null): self { $r = new self; - $r->setLinkObject('related', $link); + $r->setLink('related', $url, $meta); return $r; } @@ -58,15 +57,10 @@ public function hasLinkageTo(ResourceObject $resource): bool public function jsonSerialize() { - return array_filter( - [ - 'data' => $this->linkage, - 'links' => $this->links, - 'meta' => $this->meta, - ], - function ($v) { - return null !== $v; - } - ); + return filterNulls([ + 'data' => $this->linkage, + 'links' => $this->links, + 'meta' => $this->meta, + ]); } } diff --git a/src/Document/Resource/ResourceIdentifier.php b/src/Document/Resource/ResourceIdentifier.php index 67eb922..77e5cba 100644 --- a/src/Document/Resource/ResourceIdentifier.php +++ b/src/Document/Resource/ResourceIdentifier.php @@ -3,9 +3,11 @@ namespace JsonApiPhp\JsonApi\Document\Resource; -use JsonApiPhp\JsonApi\Document\Meta; +use JsonApiPhp\JsonApi\Document\Container; +use function JsonApiPhp\JsonApi\filterNulls; +use function JsonApiPhp\JsonApi\isValidResourceType; -class ResourceIdentifier implements \JsonSerializable +final class ResourceIdentifier implements \JsonSerializable { /** * @var string @@ -17,30 +19,27 @@ class ResourceIdentifier implements \JsonSerializable */ private $id; - /** - * @var Meta - */ private $meta; - public function __construct(string $type, string $id, Meta $meta = null) + public function __construct(string $type, string $id, iterable $meta = null) { + if (! isValidResourceType($type)) { + throw new \OutOfBoundsException("Invalid resource type '$type'"); + } $this->type = $type; $this->id = $id; - $this->meta = $meta; + if ($meta) { + $this->meta = new Container($meta); + } } public function jsonSerialize() { - return array_filter( - [ - 'type' => $this->type, - 'id' => $this->id, - 'meta' => $this->meta, - ], - function ($v) { - return null !== $v; - } - ); + return filterNulls([ + 'type' => $this->type, + 'id' => $this->id, + 'meta' => $this->meta, + ]); } public function identifies(ResourceObject $resource): bool @@ -48,13 +47,13 @@ public function identifies(ResourceObject $resource): bool return $resource->toIdentifier()->equals($this); } - public function __toString(): string + private function equals(ResourceIdentifier $that) { - return "$this->type:$this->id"; + return $this->type === $that->type && $this->id === $that->id; } - private function equals(ResourceIdentifier $that) + public function __toString(): string { - return $this->type === $that->type && $this->id === $that->id; + return "$this->type:$this->id"; } } diff --git a/src/Document/Resource/ResourceObject.php b/src/Document/Resource/ResourceObject.php index e9b68b7..bb631dc 100644 --- a/src/Document/Resource/ResourceObject.php +++ b/src/Document/Resource/ResourceObject.php @@ -3,11 +3,13 @@ namespace JsonApiPhp\JsonApi\Document\Resource; +use JsonApiPhp\JsonApi\Document\Container; use JsonApiPhp\JsonApi\Document\LinksTrait; -use JsonApiPhp\JsonApi\Document\Meta; -use JsonApiPhp\JsonApi\Document\ReservedName; +use function JsonApiPhp\JsonApi\filterNulls; +use function JsonApiPhp\JsonApi\isValidMemberName; +use function JsonApiPhp\JsonApi\isValidResourceType; -class ResourceObject implements \JsonSerializable +final class ResourceObject implements \JsonSerializable { use LinksTrait; @@ -23,18 +25,26 @@ class ResourceObject implements \JsonSerializable public function __construct(string $type, string $id = null) { - $this->type = new ResourceType($type); + if (! isValidResourceType($type)) { + throw new \OutOfBoundsException("Invalid resource type '$type'"); + } + $this->type = $type; $this->id = $id; } - public function setMeta(Meta $meta) + public function setMeta(iterable $meta) { - $this->meta = $meta; + $this->meta = new Container($meta); } public function setAttribute(string $name, $value) { - $name = (string) new ReservedName($name); + if ($this->isReservedName($name)) { + throw new \InvalidArgumentException("Can not use a reserved name '$name'"); + } + if (! isValidMemberName($name)) { + throw new \OutOfBoundsException("Invalid member name '$name'"); + } if (isset($this->relationships[$name])) { throw new \LogicException("Field '$name' already exists in relationships"); } @@ -43,7 +53,12 @@ public function setAttribute(string $name, $value) public function setRelationship(string $name, Relationship $relationship) { - $name = (string) new ReservedName($name); + if ($this->isReservedName($name)) { + throw new \InvalidArgumentException("Can not use a reserved name '$name'"); + } + if (! isValidMemberName($name)) { + throw new \OutOfBoundsException("Invalid member name '$name'"); + } if (isset($this->attributes[$name])) { throw new \LogicException("Field '$name' already exists in attributes"); } @@ -52,24 +67,19 @@ public function setRelationship(string $name, Relationship $relationship) public function toIdentifier(): ResourceIdentifier { - return new ResourceIdentifier((string) $this->type, $this->id); + return new ResourceIdentifier($this->type, $this->id); } public function jsonSerialize() { - return array_filter( - [ - 'type' => $this->type, - 'id' => $this->id, - 'attributes' => $this->attributes, - 'relationships' => $this->relationships, - 'links' => $this->links, - 'meta' => $this->meta, - ], - function ($v) { - return null !== $v; - } - ); + return filterNulls([ + 'type' => $this->type, + 'id' => $this->id, + 'attributes' => $this->attributes, + 'relationships' => $this->relationships, + 'links' => $this->links, + 'meta' => $this->meta, + ]); } public function identifies(ResourceObject $resource): bool @@ -83,4 +93,9 @@ public function identifies(ResourceObject $resource): bool } return false; } + + private function isReservedName(string $name): bool + { + return in_array($name, ['id', 'type']); + } } diff --git a/src/Document/Resource/ResourceType.php b/src/Document/Resource/ResourceType.php deleted file mode 100644 index a3b9362..0000000 --- a/src/Document/Resource/ResourceType.php +++ /dev/null @@ -1,14 +0,0 @@ -isValidMemberNameOrTypeValue($val)) { - throw new \OutOfBoundsException(sprintf($errorMessage, $val)); - } - $this->val = $val; - } - - public function jsonSerialize() - { - return $this->val; - } - - public function __toString() - { - return $this->val; - } - - protected function isValidMemberNameOrTypeValue(string $name): bool - { - return preg_match('/^(?=[^-_ ])[a-zA-Z0-9\x{0080}-\x{FFFF}-_ ]*(?<=[^-_ ])$/u', $name) === 1; - } -} diff --git a/src/functions.php b/src/functions.php new file mode 100644 index 0000000..6bf26f3 --- /dev/null +++ b/src/functions.php @@ -0,0 +1,25 @@ + 'bar'])); + $doc = Document::fromMeta(['foo' => 'bar']); $doc->setIncluded(new ResourceObject('apples', '1')); } } diff --git a/test/Document/DocumentTest.php b/test/Document/DocumentTest.php index 72e6336..705f769 100644 --- a/test/Document/DocumentTest.php +++ b/test/Document/DocumentTest.php @@ -5,8 +5,6 @@ use JsonApiPhp\JsonApi\Document; use JsonApiPhp\JsonApi\Document\Error; -use JsonApiPhp\JsonApi\Document\Link\LinkObject; -use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use JsonApiPhp\JsonApi\Test\BaseTestCase; @@ -31,9 +29,7 @@ public function testDocumentMayContainJustMeta() } } ', - Document::fromMeta( - Meta::fromArray(['foo' => 'bar']) - ) + Document::fromMeta(['foo' => 'bar']) ); } @@ -153,10 +149,10 @@ public function testDocumentCanHaveExtraProperties() new ResourceIdentifier('apples', '42') ); $doc->setApiVersion('1.0'); - $doc->setApiMeta(Meta::fromArray(['a' => 'b'])); - $doc->setMeta(Meta::fromArray(['test' => 'test'])); + $doc->setApiMeta(['a' => 'b']); + $doc->setMeta(['test' => 'test']); $doc->setLink('self', 'http://example.com/self'); - $doc->setLinkObject('related', new LinkObject('http://example.com/rel', Meta::fromArray(['foo' => 'bar']))); + $doc->setLink('related', 'http://example.com/rel', ['foo' => 'bar']); $this->assertEncodesTo( ' { diff --git a/test/Document/ErrorTest.php b/test/Document/ErrorTest.php index b4935c0..995a8e9 100644 --- a/test/Document/ErrorTest.php +++ b/test/Document/ErrorTest.php @@ -23,7 +23,7 @@ public function testErrorWithFullSetOfProperties() $e->setDetail('Nothing is found'); $e->setSourcePointer('/data'); $e->setSourceParameter('test_param'); - $e->setMeta(Meta::fromArray(['foo' => 'bar'])); + $e->setMeta(['foo' => 'bar']); $this->assertEncodesTo( ' diff --git a/test/Document/MetaTest.php b/test/Document/MetaTest.php deleted file mode 100644 index f9e665e..0000000 --- a/test/Document/MetaTest.php +++ /dev/null @@ -1,15 +0,0 @@ -assertEncodesTo('{"0":"foo"}', Meta::fromArray(['foo'])); - } -} diff --git a/test/Document/Resource/Relationship/RelationshipTest.php b/test/Document/Resource/Relationship/RelationshipTest.php index 6fdfe10..fc406af 100644 --- a/test/Document/Resource/Relationship/RelationshipTest.php +++ b/test/Document/Resource/Relationship/RelationshipTest.php @@ -3,11 +3,8 @@ namespace JsonApiPhp\JsonApi\Test\Document\Resource\Relationship; -use JsonApiPhp\JsonApi\Document\Link\Link; -use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Linkage\NullLinkage; use JsonApiPhp\JsonApi\Document\Resource\Relationship; -use JsonApiPhp\JsonApi\Document\Resource\Relationship\Linkage; use JsonApiPhp\JsonApi\Test\BaseTestCase; /** @@ -35,10 +32,10 @@ * pagination links under the links member, as described below. * * @see http://jsonapi.org/format/#document-resource-object-relationships - * @see RelationshipTest::testCanCreateFromSelfLink() - * @see RelationshipTest::testCanCreateFromRelatedLink()) - * @see RelationshipTest::testCanCreateFromLinkage()) - * @see RelationshipTest::testCanCreateFromMeta()) + * @see RelationshipTest::testCanCreateFromSelfLink + * @see RelationshipTest::testCanCreateFromRelatedLink + * @see RelationshipTest::testCanCreateFromLinkage + * @see RelationshipTest::testCanCreateFromMeta */ class RelationshipTest extends BaseTestCase { @@ -52,7 +49,7 @@ public function testCanCreateFromSelfLink() } } ', - \JsonApiPhp\JsonApi\Document\Resource\Relationship::fromSelfLink(new Link('http://localhost')) + Relationship::fromSelfLink('http://localhost') ); } @@ -66,7 +63,7 @@ public function testCanCreateFromRelatedLink() } } ', - \JsonApiPhp\JsonApi\Document\Resource\Relationship::fromRelatedLink(new Link('http://localhost')) + Relationship::fromRelatedLink('http://localhost') ); } @@ -78,7 +75,7 @@ public function testCanCreateFromLinkage() "data": null } ', - \JsonApiPhp\JsonApi\Document\Resource\Relationship::fromLinkage(new NullLinkage()) + Relationship::fromLinkage(new NullLinkage()) ); } @@ -92,7 +89,7 @@ public function testCanCreateFromMeta() } } ', - Relationship::fromMeta(Meta::fromArray(['a' => 'b'])) + Relationship::fromMeta(['a' => 'b']) ); } } diff --git a/test/Document/Resource/ResourceFieldsTest.php b/test/Document/Resource/ResourceFieldsTest.php index f8aede2..fc06e8b 100644 --- a/test/Document/Resource/ResourceFieldsTest.php +++ b/test/Document/Resource/ResourceFieldsTest.php @@ -3,7 +3,6 @@ namespace JsonApiPhp\JsonApi\Test\Document\Resource; -use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Relationship; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use PHPUnit\Framework\TestCase; @@ -29,7 +28,7 @@ public function testCanNotSetRelationshipIfAttributeExists() { $res = new ResourceObject('books', '1'); $res->setAttribute('foo', 'bar'); - $res->setRelationship('foo', Relationship::fromMeta(Meta::fromArray(['a' => 'b']))); + $res->setRelationship('foo', Relationship::fromMeta(['a' => 'b'])); } /** @@ -39,7 +38,7 @@ public function testCanNotSetRelationshipIfAttributeExists() public function testCanNotSetAttributeIfRelationshipExists() { $res = new ResourceObject('books', '1'); - $res->setRelationship('foo', Relationship::fromMeta(Meta::fromArray(['a' => 'b']))); + $res->setRelationship('foo', Relationship::fromMeta(['a' => 'b'])); $res->setAttribute('foo', 'bar'); } @@ -64,7 +63,7 @@ public function testAttributeCanNotHaveReservedNames(string $name) public function testRelationshipCanNotHaveReservedNames(string $name) { $res = new ResourceObject('books', 'abc'); - $res->setRelationship($name, Relationship::fromMeta(Meta::fromArray(['a' => 'b']))); + $res->setRelationship($name, Relationship::fromMeta(['a' => 'b'])); } public function reservedAttributeNames(): array diff --git a/test/Document/Resource/ResourceIdentifierTest.php b/test/Document/Resource/ResourceIdentifierTest.php index bc1c7f0..9911355 100644 --- a/test/Document/Resource/ResourceIdentifierTest.php +++ b/test/Document/Resource/ResourceIdentifierTest.php @@ -3,7 +3,6 @@ namespace JsonApiPhp\JsonApi\Test\Document\Resource; -use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Test\BaseTestCase; @@ -39,7 +38,7 @@ public function testResourceIdentifierMayContainMeta() } } ', - new ResourceIdentifier('books', '1', Meta::fromArray(['foo' => 'bar'])) + new ResourceIdentifier('books', '1', ['foo' => 'bar']) ); } } diff --git a/test/Document/Resource/ResourceObjectTest.php b/test/Document/Resource/ResourceObjectTest.php index 8dfdb37..978a77b 100644 --- a/test/Document/Resource/ResourceObjectTest.php +++ b/test/Document/Resource/ResourceObjectTest.php @@ -3,7 +3,6 @@ namespace JsonApiPhp\JsonApi\Test\Document\Resource; -use JsonApiPhp\JsonApi\Document\Meta; use JsonApiPhp\JsonApi\Document\Resource\Linkage\SingleLinkage; use JsonApiPhp\JsonApi\Document\Resource\Relationship; use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; @@ -91,7 +90,7 @@ public function testResourceObjectMayContainLinks() public function testResourceObjectMayContainMeta() { $article = new ResourceObject('articles', '1'); - $article->setMeta(Meta::fromArray(['tags' => ['cool', 'new']])); + $article->setMeta(['tags' => ['cool', 'new']]); $this->assertEncodesTo( ' { diff --git a/test/MemberNamesTest.php b/test/MemberNamesTest.php index 219c6e1..196c356 100644 --- a/test/MemberNamesTest.php +++ b/test/MemberNamesTest.php @@ -3,84 +3,115 @@ namespace JsonApiPhp\JsonApi\Test; -use JsonApiPhp\JsonApi\Document\Link\Link; -use JsonApiPhp\JsonApi\Document\Meta; +use JsonApiPhp\JsonApi\Document; use JsonApiPhp\JsonApi\Document\Resource\Relationship; +use JsonApiPhp\JsonApi\Document\Resource\ResourceIdentifier; use JsonApiPhp\JsonApi\Document\Resource\ResourceObject; use PHPUnit\Framework\TestCase; class MemberNamesTest extends TestCase { - /** - * @param string $name - * @expectedException \OutOfBoundsException - * @expectedExceptionMessage Invalid member name - * @dataProvider invalidAttributeNames - */ - public function testInvalidAttributeNamesAreNotAllowed(string $name) - { - $res = new ResourceObject('books', 'abc'); - $res->setAttribute($name, 1); - } - /** * The values of type members MUST adhere to the same constraints as member names. * - * @param string $name + * @param string $name + * @param callable $callback * @expectedException \OutOfBoundsException * @expectedExceptionMessage Invalid resource type - * @dataProvider invalidAttributeNames + * @dataProvider invalidAttributesAndResourceTypeCallbacks */ - public function testInvalidTypesAreNotAllowed(string $name) + public function testInvalidTypesAreNotAllowed(string $name, callable $callback) { - new ResourceObject($name, 'abc'); + $callback($name); } /** - * @param string $name * @expectedException \OutOfBoundsException * @expectedExceptionMessage Invalid member name - * @dataProvider invalidAttributeNames + * @dataProvider invalidAttributesAndMemberNameCallbacks + * @param string $name + * @param callable $callback */ - public function testInvalidRelationshipNamesAreNotAllowed(string $name) + public function testInvalidAttributeNamesAreNotAllowed(string $name, callable $callback) + { + $callback($name); + } + + public function invalidAttributesAndMemberNameCallbacks() + { + foreach ($this->invalidAttributeNames() as $attributeName) { + foreach ($this->memberNameCallbacks() as $memberNameCallback) { + yield [$attributeName, $memberNameCallback]; + } + foreach ($this->linksCallbacks() as $linksCallback) { + yield [$attributeName, $linksCallback]; + } + } + } + + public function invalidAttributesAndResourceTypeCallbacks() { - $res = new ResourceObject('books', 'abc'); - $res->setRelationship($name, Relationship::fromSelfLink(new Link('https://example.com'))); + foreach ($this->invalidAttributeNames() as $attributeName) { + foreach ($this->resourceTypeCallbacks() as $resourceTypeCallback) { + yield [$attributeName, $resourceTypeCallback]; + } + } + } + + private function invalidAttributeNames(): array + { + return [ + '_abcde', + 'abcd_', + 'abc$EDS', + '#abcde', + 'abcde(', + 'b_', + '_a', + '$ab_c-d', + '-abc', + ]; } /** - * @param string $name - * @expectedException \OutOfBoundsException - * @expectedExceptionMessage Invalid member name - * @dataProvider invalidAttributeNames + * @return callable[] */ - public function testInvalidMetaNames(string $name) + private function memberNameCallbacks() { - Meta::fromArray( - [ - 'copyright' => 'Copyright 2015 Example Corp.', - 'authors' => [ - [ - 'firstname' => 'Yehuda', - $name => 'Katz', - ], - ], - ] - ); + return [ + function ($name) { + (new ResourceObject('apples', '0'))->setRelationship( + $name, + Relationship::fromSelfLink('https://example.com') + ); + }, + function ($name) { + (new ResourceObject('apples', '0'))->setAttribute($name, 'foo'); + }, + ]; } - public function invalidAttributeNames(): array + private function resourceTypeCallbacks() { - return [ - ['_abcde'], - ['abcd_'], - ['abc$EDS'], - ['#abcde'], - ['abcde('], - ['b_'], - ['_a'], - ['$ab_c-d'], - ['-abc'], + yield function ($type) { + new ResourceIdentifier($type, 'foo'); + }; + yield function ($type) { + new ResourceObject($type, 'foo'); + }; + } + + private function linksCallbacks() + { + $objects = [ + Document::nullDocument(), + Relationship::fromSelfLink('https://example.com'), + new ResourceObject('apples', '0'), ]; + foreach ($objects as $object) { + yield function ($name) use ($object) { + $object->setLink($name, 'https://example.com'); + }; + } } } From 7615a38b7f84d7ed13dab179302b0b884f25c399 Mon Sep 17 00:00:00 2001 From: Alexey Date: Tue, 24 Oct 2017 23:25:21 -0700 Subject: [PATCH 17/45] Update README (#71) --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index fdc1cc9..acc8573 100644 --- a/README.md +++ b/README.md @@ -27,22 +27,22 @@ A simple example to illustrate the general idea. This JSON representation from } } ``` -can be built with the following php code (less imports): +can be built with the following php code: ```php setLink('self', '/articles/1/relationships/author'); -$author->setLink('related','/articles/1/author'); +$author->setLink('related', '/articles/1/author'); +$articles = new ResourceObject('articles', '1'); $articles->setRelationship('author', $author); $articles->setAttribute('title', 'Rails is Omakase'); -$doc = \JsonApiPhp\JsonApi\Document::fromResource($articles); -echo json_encode($doc, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); +echo json_encode(Document::fromResource($articles), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); ``` Please refer to [the tests](./test) for the full API documentation: From 5979425687bbb516a176b0c3be91b1b96d72e858 Mon Sep 17 00:00:00 2001 From: Alexey Date: Fri, 27 Oct 2017 13:45:59 -0700 Subject: [PATCH 18/45] Make exceptions more precise (#72) --- src/Document.php | 6 +++--- src/Document/Resource/ResourceObject.php | 8 ++++---- test/Document/CompoundDocumentTest.php | 6 +++--- test/Document/Resource/ResourceFieldsTest.php | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Document.php b/src/Document.php index 32fdccb..925f7c7 100644 --- a/src/Document.php +++ b/src/Document.php @@ -108,11 +108,11 @@ public function setApiMeta(iterable $meta): void public function setIncluded(ResourceObject ...$resources): void { if (null === $this->data) { - throw new \LogicException('Document with no data cannot contain included resources'); + throw new \DomainException('Document with no data cannot contain included resources'); } foreach ($resources as $resource) { if (isset($this->included[(string) $resource->toIdentifier()])) { - throw new \LogicException("Resource {$resource->toIdentifier()} is already included"); + throw new \DomainException("Resource {$resource->toIdentifier()} is already included"); } $this->included[(string) $resource->toIdentifier()] = $resource; } @@ -150,7 +150,7 @@ private function enforceFullLinkage(): void continue 2; } } - throw new \LogicException("Full linkage is required for {$included->toIdentifier()}"); + throw new \DomainException("Full linkage is required for {$included->toIdentifier()}"); } } } diff --git a/src/Document/Resource/ResourceObject.php b/src/Document/Resource/ResourceObject.php index bb631dc..0cfeabf 100644 --- a/src/Document/Resource/ResourceObject.php +++ b/src/Document/Resource/ResourceObject.php @@ -40,13 +40,13 @@ public function setMeta(iterable $meta) public function setAttribute(string $name, $value) { if ($this->isReservedName($name)) { - throw new \InvalidArgumentException("Can not use a reserved name '$name'"); + throw new \DomainException("Can not use a reserved name '$name'"); } if (! isValidMemberName($name)) { throw new \OutOfBoundsException("Invalid member name '$name'"); } if (isset($this->relationships[$name])) { - throw new \LogicException("Field '$name' already exists in relationships"); + throw new \DomainException("Field '$name' already exists in relationships"); } $this->attributes[$name] = $value; } @@ -54,13 +54,13 @@ public function setAttribute(string $name, $value) public function setRelationship(string $name, Relationship $relationship) { if ($this->isReservedName($name)) { - throw new \InvalidArgumentException("Can not use a reserved name '$name'"); + throw new \DomainException("Can not use a reserved name '$name'"); } if (! isValidMemberName($name)) { throw new \OutOfBoundsException("Invalid member name '$name'"); } if (isset($this->attributes[$name])) { - throw new \LogicException("Field '$name' already exists in attributes"); + throw new \DomainException("Field '$name' already exists in attributes"); } $this->relationships[$name] = $relationship; } diff --git a/test/Document/CompoundDocumentTest.php b/test/Document/CompoundDocumentTest.php index 20e3910..5789f26 100644 --- a/test/Document/CompoundDocumentTest.php +++ b/test/Document/CompoundDocumentTest.php @@ -164,7 +164,7 @@ public function testOfficialDocsExample() } /** - * @expectedException \LogicException + * @expectedException \DomainException * @expectedExceptionMessage Full linkage is required for apples:1 * @dataProvider documentsWithoutFullLinkage * @param Document $doc @@ -269,7 +269,7 @@ public function testIncludedResourceMayBeIdentifiedByAnotherLinkedResource() /** * A compound document MUST NOT include more than one resource object for each type and id pair. - * @expectedException \LogicException + * @expectedException \DomainException * @expectedExceptionMessage Resource apples:1 is already included */ public function testCanNotBeManyIncludedResourcesWithEqualIdentifiers() @@ -283,7 +283,7 @@ public function testCanNotBeManyIncludedResourcesWithEqualIdentifiers() /** * If a document does not contain a top-level data key, the included member MUST NOT be present either. - * @expectedException \LogicException + * @expectedException \DomainException * @expectedExceptionMessage Document with no data cannot contain included resources */ public function testIncludedMustOnlyBePresentWithData() diff --git a/test/Document/Resource/ResourceFieldsTest.php b/test/Document/Resource/ResourceFieldsTest.php index fc06e8b..9e25d6f 100644 --- a/test/Document/Resource/ResourceFieldsTest.php +++ b/test/Document/Resource/ResourceFieldsTest.php @@ -21,7 +21,7 @@ class ResourceFieldsTest extends TestCase { /** - * @expectedException \LogicException + * @expectedException \DomainException * @expectedExceptionMessage Field 'foo' already exists in attributes */ public function testCanNotSetRelationshipIfAttributeExists() @@ -32,7 +32,7 @@ public function testCanNotSetRelationshipIfAttributeExists() } /** - * @expectedException \LogicException + * @expectedException \DomainException * @expectedExceptionMessage Field 'foo' already exists in relationships */ public function testCanNotSetAttributeIfRelationshipExists() @@ -44,7 +44,7 @@ public function testCanNotSetAttributeIfRelationshipExists() /** * @param string $name - * @expectedException \InvalidArgumentException + * @expectedException \DomainException * @expectedExceptionMessage Can not use a reserved name * @dataProvider reservedAttributeNames */ @@ -56,7 +56,7 @@ public function testAttributeCanNotHaveReservedNames(string $name) /** * @param string $name - * @expectedException \InvalidArgumentException + * @expectedException \DomainException * @expectedExceptionMessage Can not use a reserved name * @dataProvider reservedAttributeNames */ From dc1a3439d85db12e605063349f1384f99a227a00 Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 21 Feb 2018 15:13:57 -0800 Subject: [PATCH 19/45] Remove doc2test dependency (#74) --- .doc2test.ini | 2 -- .travis.yml | 2 +- composer.json | 5 ++--- 3 files changed, 3 insertions(+), 6 deletions(-) delete mode 100644 .doc2test.ini diff --git a/.doc2test.ini b/.doc2test.ini deleted file mode 100644 index 550103c..0000000 --- a/.doc2test.ini +++ /dev/null @@ -1,2 +0,0 @@ -source = ./README.md -destination = ./doc-test diff --git a/.travis.yml b/.travis.yml index 55b45fc..9a292eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,8 @@ php: before_script: - composer install + - mkdir -p build/logs script: - vendor/bin/php-cs-fixer fix -v --dry-run - phpunit --coverage-clover build/logs/clover.xml - - vendor/bin/doc2test && vendor/bin/phpunit -c doc-test/phpunit.xml diff --git a/composer.json b/composer.json index 9982e20..f1c84b3 100644 --- a/composer.json +++ b/composer.json @@ -15,8 +15,7 @@ }, "require-dev": { "phpunit/phpunit": "^6.0", - "friendsofphp/php-cs-fixer": "^2.2", - "doc2test/doc2test": "dev-master" + "friendsofphp/php-cs-fixer": "^2.2" }, "autoload": { "psr-4": { @@ -32,6 +31,6 @@ } }, "scripts": { - "test": "php-cs-fixer fix -v --dry-run --ansi && phpunit --colors=always --coverage-text && vendor/bin/doc2test && vendor/bin/phpunit -c doc-test/phpunit.xml --coverage-text" + "test": "php-cs-fixer fix -v --dry-run --ansi && phpunit --colors=always --coverage-text" } } From 67f18adc510e8c8fa4cf059fd32d1c8b79509086 Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 21 Feb 2018 15:17:47 -0800 Subject: [PATCH 20/45] Grammar (#73) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e232deb..0f3169b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,6 @@ Thank you for you interest! Here are some key guidelines. ## PSR We want to follow the latest actual [PSR](http://www.php-fig.org/psr/) standards. -## Tests are documentation +## Tests are the documentation The use cases and library's API must be expressed in a form of tests. We use PHPUnit, but it does not mean all tests are expected to be unit tests. No logic must be written without a supporting test. From ee2e7c22a0bad2f60360d223af9a0ebe5b0cf988 Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 21 Feb 2018 16:36:50 -0800 Subject: [PATCH 21/45] Release 1.0.2 (#75) --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9861fa5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.0.2] - 2018-02-21 +### Added +- This changelog + +### Removed +- Removed doc2test dependency + +## [1.0.1] - 2017-10-27 +### Changed +- Exceptions precision. Using DomainException instead of LogicException + +## [1.0.0] - 2017-10-24 +Initial stable release + +[1.0.2]: https://github.com/json-api-php/json-api/compare/1.0.1...1.0.2 +[1.0.1]: https://github.com/json-api-php/json-api/compare/1.0.0...1.0.1 From 29265dc784344c928d9c9467f8785c6d7989a17f Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 21 Feb 2018 16:44:06 -0800 Subject: [PATCH 22/45] Update CHANGELOG.md (#76) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9861fa5..604c8b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,5 +20,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [1.0.0] - 2017-10-24 Initial stable release +[Unreleased]: https://github.com/json-api-php/json-api/compare/1.0.2...HEAD [1.0.2]: https://github.com/json-api-php/json-api/compare/1.0.1...1.0.2 [1.0.1]: https://github.com/json-api-php/json-api/compare/1.0.0...1.0.1 From bc1bbfc0bf823a818dfa6a9a888304398de09ad9 Mon Sep 17 00:00:00 2001 From: Alexey Date: Mon, 26 Feb 2018 19:03:14 -0800 Subject: [PATCH 23/45] V2 (#77) --- .php_cs.dist | 2 + .travis.yml | 3 +- CHANGELOG.md | 17 +- CONTRIBUTING.md | 3 +- README.md | 81 +++-- composer.json | 2 +- examples/compound_doc.php | 74 +++++ examples/simple_doc.php | 29 ++ src/Attachable.php | 11 + src/AttachableValue.php | 22 ++ src/Attribute.php | 13 + src/CompoundDocument.php | 24 ++ src/DataDocument.php | 13 + src/DataDocumentMember.php | 10 + src/Document.php | 156 ---------- src/Document/Container.php | 33 -- src/Document/Error.php | 81 ----- src/Document/LinksTrait.php | 18 -- src/Document/MetaTrait.php | 14 - .../PrimaryData/MultiIdentifierData.php | 32 -- .../PrimaryData/MultiResourceData.php | 31 -- src/Document/PrimaryData/NullData.php | 19 -- .../PrimaryData/PrimaryDataInterface.php | 11 - .../PrimaryData/SingleIdentifierData.php | 27 -- .../PrimaryData/SingleResourceData.php | 26 -- .../Resource/Linkage/LinkageInterface.php | 13 - .../Resource/Linkage/MultiLinkage.php | 32 -- src/Document/Resource/Linkage/NullLinkage.php | 19 -- .../Resource/Linkage/SingleLinkage.php | 27 -- src/Document/Resource/Relationship.php | 66 ---- src/Document/Resource/ResourceIdentifier.php | 59 ---- src/Document/Resource/ResourceObject.php | 101 ------ src/DocumentMember.php | 10 + src/Error.php | 18 ++ src/Error/Code.php | 16 + src/Error/Detail.php | 16 + src/Error/ErrorMember.php | 12 + src/Error/Id.php | 16 + src/Error/Parameter.php | 22 ++ src/Error/Pointer.php | 22 ++ src/Error/Status.php | 16 + src/Error/Title.php | 17 + src/ErrorDocument.php | 11 + src/ErrorDocumentMember.php | 10 + src/Included.php | 25 ++ src/JsonApi.php | 17 + src/JsonSerializableValue.php | 21 ++ src/Link/AboutLink.php | 13 + src/Link/AttachableLink.php | 22 ++ src/Link/FirstLink.php | 15 + src/Link/LastLink.php | 15 + src/Link/Link.php | 10 + src/Link/LinkObject.php | 20 ++ src/Link/NextLink.php | 15 + src/Link/PrevLink.php | 15 + src/Link/RelatedLink.php | 13 + src/Link/SelfLink.php | 15 + src/Link/Url.php | 13 + src/Meta.php | 23 ++ src/MetaDocument.php | 11 + src/MultiLinkage.php | 29 ++ src/NullData.php | 18 ++ src/PrimaryData/Identifier.php | 13 + src/PrimaryData/PrimaryData.php | 10 + src/PrimaryData/ResourceField.php | 31 ++ src/PrimaryData/ResourceMember.php | 12 + src/Relationship.php | 39 +++ src/RelationshipMember.php | 10 + src/ResourceIdentifier.php | 35 +++ src/ResourceIdentifierSet.php | 29 ++ src/ResourceObject.php | 64 ++++ src/ResourceObjectSet.php | 29 ++ src/SingleLinkage.php | 21 ++ src/TopLevelDocumentMember.php | 10 + src/functions.php | 28 +- test/BaseTestCase.php | 5 +- test/CompoundDocumentTest.php | 257 +++++++++++++++ .../ManyResourceIdentifiersTest.php | 72 +++++ test/DataDocument/ManyResourceObjectsTest.php | 85 +++++ test/DataDocument/NullDataTest.php | 53 ++++ .../SingleResourceIdentifierTest.php | 63 ++++ .../DataDocument/SingleResourceObjectTest.php | 70 +++++ test/Document/CompoundDocumentTest.php | 294 ------------------ test/Document/DocumentTest.php | 186 ----------- test/Document/ErrorTest.php | 51 --- .../Resource/Relationship/LinkageTest.php | 119 ------- .../Relationship/RelationshipTest.php | 95 ------ test/Document/Resource/ResourceFieldsTest.php | 76 ----- .../Resource/ResourceIdentifierTest.php | 44 --- test/Document/Resource/ResourceObjectTest.php | 110 ------- test/ErrorDocumentTest.php | 122 ++++++++ test/ExamplesTest.php | 26 ++ test/IntegrationTest.php | 58 ---- test/JsonApiTest.php | 25 ++ test/LinkObjectTest.php | 34 ++ test/MemberNamesTest.php | 117 ------- test/MetaDocumentTest.php | 51 +++ test/MetaTest.php | 16 + test/PaginationLinksTest.php | 45 +++ test/ResourceObjectTest.php | 215 +++++++++++++ 100 files changed, 2205 insertions(+), 1980 deletions(-) create mode 100644 examples/compound_doc.php create mode 100644 examples/simple_doc.php create mode 100644 src/Attachable.php create mode 100644 src/AttachableValue.php create mode 100644 src/Attribute.php create mode 100644 src/CompoundDocument.php create mode 100644 src/DataDocument.php create mode 100644 src/DataDocumentMember.php delete mode 100644 src/Document.php delete mode 100644 src/Document/Container.php delete mode 100644 src/Document/Error.php delete mode 100644 src/Document/LinksTrait.php delete mode 100644 src/Document/MetaTrait.php delete mode 100644 src/Document/PrimaryData/MultiIdentifierData.php delete mode 100644 src/Document/PrimaryData/MultiResourceData.php delete mode 100644 src/Document/PrimaryData/NullData.php delete mode 100644 src/Document/PrimaryData/PrimaryDataInterface.php delete mode 100644 src/Document/PrimaryData/SingleIdentifierData.php delete mode 100644 src/Document/PrimaryData/SingleResourceData.php delete mode 100644 src/Document/Resource/Linkage/LinkageInterface.php delete mode 100644 src/Document/Resource/Linkage/MultiLinkage.php delete mode 100644 src/Document/Resource/Linkage/NullLinkage.php delete mode 100644 src/Document/Resource/Linkage/SingleLinkage.php delete mode 100644 src/Document/Resource/Relationship.php delete mode 100644 src/Document/Resource/ResourceIdentifier.php delete mode 100644 src/Document/Resource/ResourceObject.php create mode 100644 src/DocumentMember.php create mode 100644 src/Error.php create mode 100644 src/Error/Code.php create mode 100644 src/Error/Detail.php create mode 100644 src/Error/ErrorMember.php create mode 100644 src/Error/Id.php create mode 100644 src/Error/Parameter.php create mode 100644 src/Error/Pointer.php create mode 100644 src/Error/Status.php create mode 100644 src/Error/Title.php create mode 100644 src/ErrorDocument.php create mode 100644 src/ErrorDocumentMember.php create mode 100644 src/Included.php create mode 100644 src/JsonApi.php create mode 100644 src/JsonSerializableValue.php create mode 100644 src/Link/AboutLink.php create mode 100644 src/Link/AttachableLink.php create mode 100644 src/Link/FirstLink.php create mode 100644 src/Link/LastLink.php create mode 100644 src/Link/Link.php create mode 100644 src/Link/LinkObject.php create mode 100644 src/Link/NextLink.php create mode 100644 src/Link/PrevLink.php create mode 100644 src/Link/RelatedLink.php create mode 100644 src/Link/SelfLink.php create mode 100644 src/Link/Url.php create mode 100644 src/Meta.php create mode 100644 src/MetaDocument.php create mode 100644 src/MultiLinkage.php create mode 100644 src/NullData.php create mode 100644 src/PrimaryData/Identifier.php create mode 100644 src/PrimaryData/PrimaryData.php create mode 100644 src/PrimaryData/ResourceField.php create mode 100644 src/PrimaryData/ResourceMember.php create mode 100644 src/Relationship.php create mode 100644 src/RelationshipMember.php create mode 100644 src/ResourceIdentifier.php create mode 100644 src/ResourceIdentifierSet.php create mode 100644 src/ResourceObject.php create mode 100644 src/ResourceObjectSet.php create mode 100644 src/SingleLinkage.php create mode 100644 src/TopLevelDocumentMember.php create mode 100644 test/CompoundDocumentTest.php create mode 100644 test/DataDocument/ManyResourceIdentifiersTest.php create mode 100644 test/DataDocument/ManyResourceObjectsTest.php create mode 100644 test/DataDocument/NullDataTest.php create mode 100644 test/DataDocument/SingleResourceIdentifierTest.php create mode 100644 test/DataDocument/SingleResourceObjectTest.php delete mode 100644 test/Document/CompoundDocumentTest.php delete mode 100644 test/Document/DocumentTest.php delete mode 100644 test/Document/ErrorTest.php delete mode 100644 test/Document/Resource/Relationship/LinkageTest.php delete mode 100644 test/Document/Resource/Relationship/RelationshipTest.php delete mode 100644 test/Document/Resource/ResourceFieldsTest.php delete mode 100644 test/Document/Resource/ResourceIdentifierTest.php delete mode 100644 test/Document/Resource/ResourceObjectTest.php create mode 100644 test/ErrorDocumentTest.php create mode 100644 test/ExamplesTest.php delete mode 100644 test/IntegrationTest.php create mode 100644 test/JsonApiTest.php create mode 100644 test/LinkObjectTest.php delete mode 100644 test/MemberNamesTest.php create mode 100644 test/MetaDocumentTest.php create mode 100644 test/MetaTest.php create mode 100644 test/PaginationLinksTest.php create mode 100644 test/ResourceObjectTest.php diff --git a/.php_cs.dist b/.php_cs.dist index 9c1b5d4..06b82a3 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -2,6 +2,7 @@ $finder = PhpCsFixer\Finder::create() ->files() ->name('*.php') + ->in(__DIR__ . '/examples') ->in(__DIR__ . '/src') ->in(__DIR__ . '/test'); return PhpCsFixer\Config::create() @@ -42,6 +43,7 @@ return PhpCsFixer\Config::create() 'psr0' => true, 'short_scalar_cast' => true, 'single_blank_line_before_namespace' => true, + 'single_quote' => true, 'standardize_not_equals' => true, 'strict_comparison' => true, 'ternary_operator_spaces' => true, diff --git a/.travis.yml b/.travis.yml index 9a292eb..bef7869 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,10 @@ language: php php: - - '7.1' - '7.2' before_script: - composer install - - mkdir -p build/logs + - mkdir build/logs -p script: - vendor/bin/php-cs-fixer fix -v --dry-run diff --git a/CHANGELOG.md b/CHANGELOG.md index 604c8b6..548098b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,20 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] -## [1.0.2] - 2018-02-21 -### Added -- This changelog - -### Removed -- Removed doc2test dependency - -## [1.0.1] - 2017-10-27 -### Changed -- Exceptions precision. Using DomainException instead of LogicException - -## [1.0.0] - 2017-10-24 +## [2.0.0-rc.0] - 2018-02-26 Initial stable release -[Unreleased]: https://github.com/json-api-php/json-api/compare/1.0.2...HEAD -[1.0.2]: https://github.com/json-api-php/json-api/compare/1.0.1...1.0.2 -[1.0.1]: https://github.com/json-api-php/json-api/compare/1.0.0...1.0.1 +[Unreleased]: https://github.com/json-api-php/json-api/compare/2.0.0-rc.0...HEAD diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f3169b..761757a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,5 +5,4 @@ Thank you for you interest! Here are some key guidelines. We want to follow the latest actual [PSR](http://www.php-fig.org/psr/) standards. ## Tests are the documentation -The use cases and library's API must be expressed in a form of tests. We use PHPUnit, but it does not mean all tests -are expected to be unit tests. No logic must be written without a supporting test. +The use cases and library's API must be expressed as tests. No logic must be written without a supporting test. diff --git a/README.md b/README.md index acc8573..9431d4e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ -# Implementation of [JSON API](http://jsonapi.org) in PHP 7 -This library is an attempt to express business rules of JSON API specification in a set of PHP 7 classes. +# [JSON API](http://jsonapi.org) spec implemented in PHP 7. Immutable -A simple example to illustrate the general idea. This JSON representation from -[the documentation](http://jsonapi.org/format/#document-resource-objects) - +**This is v2 of the implementation. For v1 click [here](/json-api-php/json-api/tree/v2).** + +The goal of this library is to ensure strict validity of JSON API documents being produced. + +JSON: ```json { "data": { @@ -27,32 +28,56 @@ A simple example to illustrate the general idea. This JSON representation from } } ``` -can be built with the following php code: - +PHP: ```php setLink('self', '/articles/1/relationships/author'); -$author->setLink('related', '/articles/1/author'); -$articles = new ResourceObject('articles', '1'); -$articles->setRelationship('author', $author); -$articles->setAttribute('title', 'Rails is Omakase'); -echo json_encode(Document::fromResource($articles), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); +echo json_encode( + new DataDocument( + new ResourceObject( + 'articles', + '1', + new Attribute('title', 'Rails is Omakase'), + new Relationship( + 'author', + new SingleLinkage(new ResourceIdentifier('author', '9')), + new SelfLink(new Url('/articles/1/relationships/author')), + new RelatedLink(new Url('/articles/1/author')) + ) + ) + ), + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES +); ``` +## Installation +`composer require json-api-php/json-api` -Please refer to [the tests](./test) for the full API documentation: -* [Documents](./test/Document/DocumentTest.php). Creating documents with primary data, errors, and meta. -Adding links and API version to a document. - * [Compound Documents](./test/Document/CompoundDocumentTest.php). Resource linkage. -* [Errors](./test/Document/ErrorTest.php) -* [Resources](./test/Document/Resource/ResourceTest.php) -* [Relationships](./test/Document/Resource/Relationship/RelationshipTest.php) -* [Linkage](./test/Document/Resource/Relationship/LinkageTest.php) +## Documentation -## Installation -With [composer](https://getcomposer.org/): `json-api-php/json-api`. +First, take a look at the examples. All of them are runnable. +- [Simple Document](./examples/simple_doc.php) (the same as above) +- [Extensive Compound Document](./examples/compound_doc.php) + +The library API and use-cases are expressed in comprehensive suite of tests. +- Data Documents (containing primary data) + - [with a single Resource Object](./test/DataDocument/SingleResourceObjectTest.php) + - [with a single Resource Identifier](./test/DataDocument/SingleResourceIdentifierTest.php) + - [with null data](./test/DataDocument/NullDataTest.php) + - [with multiple Resource Objects](./test/DataDocument/ManyResourceObjectsTest.php) + - [with multiple Resource Identifiers](./test/DataDocument/ManyResourceIdentifiersTest.php) +- [Compound Documents](./test/CompoundDocumentTest.php) +- [Error Documents](./test/ErrorDocumentTest.php) +- [Meta Documents (containing neither data nor errors)](./test/MetaDocumentTest.php) +- [Pagination links](./test/PaginationLinksTest.php) +- [Link Objects](./test/LinkObjectTest.php) +- [JSON API Object](./test/JsonApiTest.php) +- [Meta Objects](./test/MetaTest.php) diff --git a/composer.json b/composer.json index f1c84b3..a83d94f 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ } ], "require": { - "php": ">=7.1" + "php": ">=7.2" }, "require-dev": { "phpunit/phpunit": "^6.0", diff --git a/examples/compound_doc.php b/examples/compound_doc.php new file mode 100644 index 0000000..02fdac0 --- /dev/null +++ b/examples/compound_doc.php @@ -0,0 +1,74 @@ +identifier())) +); + +$document = new CompoundDocument( + new ResourceObjectSet( + new ResourceObject( + 'articles', + '1', + new Attribute('title', 'JSON API paints my bikeshed!'), + new SelfLink(new Url('http://example.com/articles/1')), + new Relationship( + 'author', + new SingleLinkage($dan->identifier()), + new SelfLink(new Url('http://example.com/articles/1/relationships/author')), + new RelatedLink(new Url('http://example.com/articles/1/author')) + ), + new Relationship( + 'comments', + new MultiLinkage( + $comment05->identifier(), + $comment12->identifier() + ), + new SelfLink(new Url('http://example.com/articles/1/relationships/comments')), + new RelatedLink(new Url('http://example.com/articles/1/comments')) + ) + ) + ), + new Included($dan, $comment05, $comment12), + new SelfLink(new Url('http://example.com/articles')), + new NextLink(new Url('http://example.com/articles?page[offset]=2')), + new LastLink(new Url('http://example.com/articles?page[offset]=10')) +); + +echo json_encode($document, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); diff --git a/examples/simple_doc.php b/examples/simple_doc.php new file mode 100644 index 0000000..48b60b7 --- /dev/null +++ b/examples/simple_doc.php @@ -0,0 +1,29 @@ +key = $key; + } + + public function attachTo(object $o) + { + $o->{$this->key} = $this; + } +} diff --git a/src/Attribute.php b/src/Attribute.php new file mode 100644 index 0000000..dde5b80 --- /dev/null +++ b/src/Attribute.php @@ -0,0 +1,13 @@ +identifies($resource)) { + continue; + } + foreach ($included as $anotherResource) { + if ($anotherResource->identifies($resource)) { + continue 2; + } + } + throw new \DomainException('Full linkage required for '.json_encode($resource->identifier())); + } + parent::__construct(combine($data, $included, ...$members)); + } +} diff --git a/src/DataDocument.php b/src/DataDocument.php new file mode 100644 index 0000000..64b4e31 --- /dev/null +++ b/src/DataDocument.php @@ -0,0 +1,13 @@ +setMeta($meta); - return $doc; - } - - public static function fromErrors(Error ...$errors): self - { - $doc = new self; - $doc->errors = $errors; - return $doc; - } - - public static function fromResource(ResourceObject $resource): self - { - $doc = new self; - $doc->data = new SingleResourceData($resource); - return $doc; - } - - public static function fromResources(ResourceObject ...$resources): self - { - $doc = new self; - $doc->data = new MultiResourceData(...$resources); - return $doc; - } - - public static function fromIdentifier(ResourceIdentifier $identifier): self - { - $doc = new self; - $doc->data = new SingleIdentifierData($identifier); - return $doc; - } - - public static function fromIdentifiers(ResourceIdentifier... $identifiers): self - { - $doc = new self; - $doc->data = new MultiIdentifierData(...$identifiers); - return $doc; - } - - public static function nullDocument(): self - { - $doc = new self; - $doc->data = new NullData(); - return $doc; - } - - public function setApiVersion(string $version = self::DEFAULT_API_VERSION) - { - $this->api['version'] = $version; - } - - public function setApiMeta(iterable $meta): void - { - $this->api['meta'] = new Container($meta); - } - - public function setIncluded(ResourceObject ...$resources): void - { - if (null === $this->data) { - throw new \DomainException('Document with no data cannot contain included resources'); - } - foreach ($resources as $resource) { - if (isset($this->included[(string) $resource->toIdentifier()])) { - throw new \DomainException("Resource {$resource->toIdentifier()} is already included"); - } - $this->included[(string) $resource->toIdentifier()] = $resource; - } - } - - public function markSparse(): void - { - $this->sparse = true; - } - - public function jsonSerialize() - { - $this->enforceFullLinkage(); - return filterNulls([ - 'data' => $this->data, - 'errors' => $this->errors, - 'meta' => $this->meta, - 'jsonapi' => $this->api, - 'links' => $this->links, - 'included' => $this->included ? array_values($this->included) : null, - ]); - } - - private function enforceFullLinkage(): void - { - if ($this->sparse || empty($this->included)) { - return; - } - foreach ($this->included as $included) { - if ($this->data->hasLinkTo($included)) { - continue; - } - foreach ($this->included as $anotherIncluded) { - if ($anotherIncluded->identifies($included)) { - continue 2; - } - } - throw new \DomainException("Full linkage is required for {$included->toIdentifier()}"); - } - } -} diff --git a/src/Document/Container.php b/src/Document/Container.php deleted file mode 100644 index 694f504..0000000 --- a/src/Document/Container.php +++ /dev/null @@ -1,33 +0,0 @@ - $v) { - $this->set((string) $k, $v); - } - } - } - - public function set(string $name, $value) - { - if (! isValidMemberName($name)) { - throw new \OutOfBoundsException("Invalid member name '$name'"); - } - $this->data[$name] = $value; - } - - public function jsonSerialize() - { - return (object) $this->data; - } -} diff --git a/src/Document/Error.php b/src/Document/Error.php deleted file mode 100644 index b8ed3a8..0000000 --- a/src/Document/Error.php +++ /dev/null @@ -1,81 +0,0 @@ -id = $id; - } - - public function setId(string $id) - { - $this->id = $id; - } - - public function setAboutLink(string $link) - { - $this->links['about'] = $link; - } - - public function setStatus(string $status) - { - $this->status = $status; - } - - public function setCode(string $code) - { - $this->code = $code; - } - - public function setTitle(string $title) - { - $this->title = $title; - } - - public function setDetail(string $detail) - { - $this->detail = $detail; - } - - public function setSourcePointer(string $pointer) - { - $this->source['pointer'] = $pointer; - } - - public function setSourceParameter(string $parameter) - { - $this->source['parameter'] = $parameter; - } - - public function jsonSerialize() - { - return filterNulls([ - 'id' => $this->id, - 'links' => $this->links, - 'status' => $this->status, - 'code' => $this->code, - 'title' => $this->title, - 'detail' => $this->detail, - 'source' => $this->source, - 'meta' => $this->meta, - ]) ?: (object) []; - } -} diff --git a/src/Document/LinksTrait.php b/src/Document/LinksTrait.php deleted file mode 100644 index 8f84849..0000000 --- a/src/Document/LinksTrait.php +++ /dev/null @@ -1,18 +0,0 @@ -links = $this->links ?: new Container(); - $this->links->set($name, $meta ? ['meta' => new Container($meta), 'href' => $url] : $url); - } -} diff --git a/src/Document/MetaTrait.php b/src/Document/MetaTrait.php deleted file mode 100644 index 562402d..0000000 --- a/src/Document/MetaTrait.php +++ /dev/null @@ -1,14 +0,0 @@ -meta = new Container($meta); - } -} diff --git a/src/Document/PrimaryData/MultiIdentifierData.php b/src/Document/PrimaryData/MultiIdentifierData.php deleted file mode 100644 index 008fee2..0000000 --- a/src/Document/PrimaryData/MultiIdentifierData.php +++ /dev/null @@ -1,32 +0,0 @@ -identifiers = $identifiers; - } - - public function hasLinkTo(ResourceObject $resource): bool - { - foreach ($this->identifiers as $identifier) { - if ($identifier->identifies($resource)) { - return true; - } - } - return false; - } - - public function jsonSerialize() - { - return $this->identifiers; - } -} diff --git a/src/Document/PrimaryData/MultiResourceData.php b/src/Document/PrimaryData/MultiResourceData.php deleted file mode 100644 index a607ee9..0000000 --- a/src/Document/PrimaryData/MultiResourceData.php +++ /dev/null @@ -1,31 +0,0 @@ -resources = $resources; - } - - public function hasLinkTo(ResourceObject $resource): bool - { - foreach ($this->resources as $myResource) { - if ($myResource->identifies($resource)) { - return true; - } - } - return false; - } - - public function jsonSerialize() - { - return $this->resources; - } -} diff --git a/src/Document/PrimaryData/NullData.php b/src/Document/PrimaryData/NullData.php deleted file mode 100644 index 9f577cd..0000000 --- a/src/Document/PrimaryData/NullData.php +++ /dev/null @@ -1,19 +0,0 @@ -identifier = $identifier; - } - - public function hasLinkTo(ResourceObject $resource): bool - { - return $this->identifier->identifies($resource); - } - - public function jsonSerialize() - { - return $this->identifier; - } -} diff --git a/src/Document/PrimaryData/SingleResourceData.php b/src/Document/PrimaryData/SingleResourceData.php deleted file mode 100644 index b81279b..0000000 --- a/src/Document/PrimaryData/SingleResourceData.php +++ /dev/null @@ -1,26 +0,0 @@ -resource = $resource; - } - - public function hasLinkTo(ResourceObject $resource): bool - { - return $this->resource->identifies($resource); - } - - public function jsonSerialize() - { - return $this->resource; - } -} diff --git a/src/Document/Resource/Linkage/LinkageInterface.php b/src/Document/Resource/Linkage/LinkageInterface.php deleted file mode 100644 index 466a762..0000000 --- a/src/Document/Resource/Linkage/LinkageInterface.php +++ /dev/null @@ -1,13 +0,0 @@ -identifiers = $identifiers; - } - - public function isLinkedTo(ResourceObject $resource): bool - { - foreach ($this->identifiers as $identifier) { - if ($identifier->identifies($resource)) { - return true; - } - } - return false; - } - - public function jsonSerialize() - { - return $this->identifiers; - } -} diff --git a/src/Document/Resource/Linkage/NullLinkage.php b/src/Document/Resource/Linkage/NullLinkage.php deleted file mode 100644 index a44c2ba..0000000 --- a/src/Document/Resource/Linkage/NullLinkage.php +++ /dev/null @@ -1,19 +0,0 @@ -identifier = $identifier; - } - - public function isLinkedTo(ResourceObject $resource): bool - { - return $this->identifier->identifies($resource); - } - - public function jsonSerialize() - { - return $this->identifier; - } -} diff --git a/src/Document/Resource/Relationship.php b/src/Document/Resource/Relationship.php deleted file mode 100644 index da2bdd3..0000000 --- a/src/Document/Resource/Relationship.php +++ /dev/null @@ -1,66 +0,0 @@ -setMeta($meta); - return $r; - } - - public static function fromSelfLink(string $url, iterable $meta = null): self - { - $r = new self; - $r->setLink('self', $url, $meta); - return $r; - } - - public static function fromRelatedLink(string $url, iterable $meta = null): self - { - $r = new self; - $r->setLink('related', $url, $meta); - return $r; - } - - public static function fromLinkage(LinkageInterface $linkage): self - { - $r = new self; - $r->linkage = $linkage; - return $r; - } - - public function hasLinkageTo(ResourceObject $resource): bool - { - return ($this->linkage && $this->linkage->isLinkedTo($resource)); - } - - public function jsonSerialize() - { - return filterNulls([ - 'data' => $this->linkage, - 'links' => $this->links, - 'meta' => $this->meta, - ]); - } -} diff --git a/src/Document/Resource/ResourceIdentifier.php b/src/Document/Resource/ResourceIdentifier.php deleted file mode 100644 index 77e5cba..0000000 --- a/src/Document/Resource/ResourceIdentifier.php +++ /dev/null @@ -1,59 +0,0 @@ -type = $type; - $this->id = $id; - if ($meta) { - $this->meta = new Container($meta); - } - } - - public function jsonSerialize() - { - return filterNulls([ - 'type' => $this->type, - 'id' => $this->id, - 'meta' => $this->meta, - ]); - } - - public function identifies(ResourceObject $resource): bool - { - return $resource->toIdentifier()->equals($this); - } - - private function equals(ResourceIdentifier $that) - { - return $this->type === $that->type && $this->id === $that->id; - } - - public function __toString(): string - { - return "$this->type:$this->id"; - } -} diff --git a/src/Document/Resource/ResourceObject.php b/src/Document/Resource/ResourceObject.php deleted file mode 100644 index 0cfeabf..0000000 --- a/src/Document/Resource/ResourceObject.php +++ /dev/null @@ -1,101 +0,0 @@ -type = $type; - $this->id = $id; - } - - public function setMeta(iterable $meta) - { - $this->meta = new Container($meta); - } - - public function setAttribute(string $name, $value) - { - if ($this->isReservedName($name)) { - throw new \DomainException("Can not use a reserved name '$name'"); - } - if (! isValidMemberName($name)) { - throw new \OutOfBoundsException("Invalid member name '$name'"); - } - if (isset($this->relationships[$name])) { - throw new \DomainException("Field '$name' already exists in relationships"); - } - $this->attributes[$name] = $value; - } - - public function setRelationship(string $name, Relationship $relationship) - { - if ($this->isReservedName($name)) { - throw new \DomainException("Can not use a reserved name '$name'"); - } - if (! isValidMemberName($name)) { - throw new \OutOfBoundsException("Invalid member name '$name'"); - } - if (isset($this->attributes[$name])) { - throw new \DomainException("Field '$name' already exists in attributes"); - } - $this->relationships[$name] = $relationship; - } - - public function toIdentifier(): ResourceIdentifier - { - return new ResourceIdentifier($this->type, $this->id); - } - - public function jsonSerialize() - { - return filterNulls([ - 'type' => $this->type, - 'id' => $this->id, - 'attributes' => $this->attributes, - 'relationships' => $this->relationships, - 'links' => $this->links, - 'meta' => $this->meta, - ]); - } - - public function identifies(ResourceObject $resource): bool - { - if ($this->relationships) { - foreach ($this->relationships as $relationship) { - if ($relationship->hasLinkageTo($resource)) { - return true; - } - } - } - return false; - } - - private function isReservedName(string $name): bool - { - return in_array($name, ['id', 'type']); - } -} diff --git a/src/DocumentMember.php b/src/DocumentMember.php new file mode 100644 index 0000000..d69a5ea --- /dev/null +++ b/src/DocumentMember.php @@ -0,0 +1,10 @@ +errors[] = $this; + } +} diff --git a/src/Error/Code.php b/src/Error/Code.php new file mode 100644 index 0000000..fc3dc92 --- /dev/null +++ b/src/Error/Code.php @@ -0,0 +1,16 @@ +identifier()); + if (isset($this->resources[$string_id])) { + throw new \LogicException("Resource $string_id is already included"); + } + $this->resources[$string_id] = $resource; + } + parent::__construct('included', $resources); + } + + public function getIterator() + { + return new \ArrayIterator($this->resources); + } +} diff --git a/src/JsonApi.php b/src/JsonApi.php new file mode 100644 index 0000000..a769c17 --- /dev/null +++ b/src/JsonApi.php @@ -0,0 +1,17 @@ + $version, + ]; + if ($meta) { + $meta->attachTo($jsonapi); + } + parent::__construct('jsonapi', $jsonapi); + } +} diff --git a/src/JsonSerializableValue.php b/src/JsonSerializableValue.php new file mode 100644 index 0000000..5784c9f --- /dev/null +++ b/src/JsonSerializableValue.php @@ -0,0 +1,21 @@ +value = $value; + } + + public function jsonSerialize() + { + return $this->value; + } +} diff --git a/src/Link/AboutLink.php b/src/Link/AboutLink.php new file mode 100644 index 0000000..036a185 --- /dev/null +++ b/src/Link/AboutLink.php @@ -0,0 +1,13 @@ + $href, + ]; + if ($meta) { + $meta->attachTo($link); + } + parent::__construct($link); + } +} diff --git a/src/Link/NextLink.php b/src/Link/NextLink.php new file mode 100644 index 0000000..b40bad2 --- /dev/null +++ b/src/Link/NextLink.php @@ -0,0 +1,15 @@ +identifiers = $identifiers; + } + + public function identifies(ResourceObject $resource): bool + { + foreach ($this->identifiers as $identifier) { + if ($identifier->identifies($resource)) { + return true; + } + } + return false; + } +} diff --git a/src/NullData.php b/src/NullData.php new file mode 100644 index 0000000..7d39ca6 --- /dev/null +++ b/src/NullData.php @@ -0,0 +1,18 @@ +key = $key; + } + + public function key(): string + { + return $this->key; + } +} diff --git a/src/PrimaryData/ResourceMember.php b/src/PrimaryData/ResourceMember.php new file mode 100644 index 0000000..0a88dff --- /dev/null +++ b/src/PrimaryData/ResourceMember.php @@ -0,0 +1,12 @@ +identifiers[] = $m; + } + } + } + + public function attachTo(object $o) + { + parent::attachTo(child($o, 'relationships')); + } + + public function identifies(ResourceObject $resource): bool + { + foreach ($this->identifiers as $identifier) { + if ($identifier->identifies($resource)) { + return true; + } + } + return false; + } +} diff --git a/src/RelationshipMember.php b/src/RelationshipMember.php new file mode 100644 index 0000000..8360f92 --- /dev/null +++ b/src/RelationshipMember.php @@ -0,0 +1,10 @@ + $type, + 'id' => $id, + ]; + if ($meta) { + $meta->attachTo($identifier); + } + parent::__construct('data', $identifier); + $this->type = $type; + $this->id = $id; + } + + public function identifies(ResourceObject $resource): bool + { + return $resource->identifier()->equals($this); + } + + public function equals(ResourceIdentifier $that): bool + { + return $this->type === $that->type && $this->id === $that->id; + } +} diff --git a/src/ResourceIdentifierSet.php b/src/ResourceIdentifierSet.php new file mode 100644 index 0000000..e63efc0 --- /dev/null +++ b/src/ResourceIdentifierSet.php @@ -0,0 +1,29 @@ +identifiers = $identifiers; + } + + public function identifies(ResourceObject $resource): bool + { + foreach ($this->identifiers as $identifier) { + if ($identifier->identifies($resource)) { + return true; + } + } + return false; + } +} diff --git a/src/ResourceObject.php b/src/ResourceObject.php new file mode 100644 index 0000000..b7e2cb8 --- /dev/null +++ b/src/ResourceObject.php @@ -0,0 +1,64 @@ +checkUniqueness(...$members); + $obj = combine(...$members); + $obj->type = $type; + $obj->id = $id; + parent::__construct('data', $obj); + $this->type = $type; + $this->id = $id; + foreach ($members as $member) { + if ($member instanceof Identifier) { + $this->identifiers[] = $member; + } + } + } + + public function identifier(): ResourceIdentifier + { + return new ResourceIdentifier($this->type, $this->id); + } + + public function identifies(ResourceObject $resource): bool + { + foreach ($this->identifiers as $identifier) { + if ($identifier->identifies($resource)) { + return true; + } + } + return false; + } + + private function checkUniqueness(ResourceMember ...$members): void + { + $keys = []; + foreach ($members as $member) { + if ($member instanceof ResourceField) { + $key = $member->key(); + if (isset($keys[$key])) { + throw new \LogicException("Field '$key' already exists'"); + } + $keys[$key] = true; + } + } + } +} diff --git a/src/ResourceObjectSet.php b/src/ResourceObjectSet.php new file mode 100644 index 0000000..2eb37c5 --- /dev/null +++ b/src/ResourceObjectSet.php @@ -0,0 +1,29 @@ +resources = $resources; + parent::__construct('data', $this->resources); + } + + public function identifies(ResourceObject $resource): bool + { + foreach ($this->resources as $myResource) { + if ($myResource->identifies($resource)) { + return true; + } + } + return false; + } +} diff --git a/src/SingleLinkage.php b/src/SingleLinkage.php new file mode 100644 index 0000000..e53b74a --- /dev/null +++ b/src/SingleLinkage.php @@ -0,0 +1,21 @@ +identifier = $identifier; + } + + public function identifies(ResourceObject $resource): bool + { + return $this->identifier && $this->identifier->identifies($resource); + } +} diff --git a/src/TopLevelDocumentMember.php b/src/TopLevelDocumentMember.php new file mode 100644 index 0000000..57c8210 --- /dev/null +++ b/src/TopLevelDocumentMember.php @@ -0,0 +1,10 @@ +attachTo($obj); + } + return $obj; } -function isValidResourceType(string $name): bool +function child(object $o, string $name): object { - /** - * The values of type members MUST adhere to the same constraints as member names. - * @see http://jsonapi.org/format/#document-resource-object-identification - */ - return isValidMemberName($name); + if (empty($o->{$name})) { + $o->{$name} = (object) []; + } + return $o->{$name}; } -function filterNulls(array $a): array +function isValidName(string $name): bool { - return array_filter($a, function ($v) { - return $v !== null; - }); + return preg_match('/^(?=[^-_ ])[a-zA-Z0-9\x{0080}-\x{FFFF}-_ ]*(?<=[^-_ ])$/u', $name) === 1; } diff --git a/test/BaseTestCase.php b/test/BaseTestCase.php index c9841b4..2485e47 100644 --- a/test/BaseTestCase.php +++ b/test/BaseTestCase.php @@ -1,5 +1,4 @@ -identifier())) + ); + + $document = new CompoundDocument( + new ResourceObjectSet( + new ResourceObject( + 'articles', + '1', + new Attribute('title', 'JSON API paints my bikeshed!'), + new SelfLink(new Url('http://example.com/articles/1')), + new Relationship( + 'author', + new SingleLinkage($dan->identifier()), + new SelfLink(new Url('http://example.com/articles/1/relationships/author')), + new RelatedLink(new Url('http://example.com/articles/1/author')) + ), + new Relationship( + 'comments', + new MultiLinkage( + $comment05->identifier(), + $comment12->identifier() + ), + new SelfLink(new Url('http://example.com/articles/1/relationships/comments')), + new RelatedLink(new Url('http://example.com/articles/1/comments')) + ) + ) + ), + new Included($dan, $comment05, $comment12), + new SelfLink(new Url('http://example.com/articles')), + new NextLink(new Url('http://example.com/articles?page[offset]=2')), + new LastLink(new Url('http://example.com/articles?page[offset]=10')) + ); + $this->assertEncodesTo( + ' + { + "links": { + "self": "http://example.com/articles", + "next": "http://example.com/articles?page[offset]=2", + "last": "http://example.com/articles?page[offset]=10" + }, + "data": [{ + "type": "articles", + "id": "1", + "attributes": { + "title": "JSON API paints my bikeshed!" + }, + "links": { + "self": "http://example.com/articles/1" + }, + "relationships": { + "author": { + "links": { + "self": "http://example.com/articles/1/relationships/author", + "related": "http://example.com/articles/1/author" + }, + "data": { "type": "people", "id": "9" } + }, + "comments": { + "links": { + "self": "http://example.com/articles/1/relationships/comments", + "related": "http://example.com/articles/1/comments" + }, + "data": [ + { "type": "comments", "id": "5" }, + { "type": "comments", "id": "12" } + ] + } + } + }], + "included": [{ + "type": "people", + "id": "9", + "attributes": { + "first-name": "Dan", + "last-name": "Gebhardt", + "twitter": "dgeb" + }, + "links": { + "self": "http://example.com/people/9" + } + }, { + "type": "comments", + "id": "5", + "attributes": { + "body": "First!" + }, + "relationships": { + "author": { + "data": { "type": "people", "id": "2" } + } + }, + "links": { + "self": "http://example.com/comments/5" + } + }, { + "type": "comments", + "id": "12", + "attributes": { + "body": "I like XML better" + }, + "relationships": { + "author": { + "data": { "type": "people", "id": "9" } + } + }, + "links": { + "self": "http://example.com/comments/12" + } + }] + } + ', + $document + ); + } + + /** + * Compound documents require “full linkage”, meaning that every included resource MUST be identified + * by at least one resource identifier object in the same document. + * These resource identifier objects could either be primary data or represent resource linkage + * contained within primary or included resources. + * + * @dataProvider documentsWithoutFullLinkage + * @param callable $create_doc + */ + public function testFullLinkage(callable $create_doc) + { + $this->expectException(\DomainException::class); + $this->expectExceptionMessage('Full linkage required for {"type":"apples","id":"1"}'); + $create_doc(); + } + + public function documentsWithoutFullLinkage(): array + { + $included = new Included(new ResourceObject('apples', '1')); + return [ + [ + function () use ($included) { + return new CompoundDocument(new NullData(), $included); + }, + ], + [ + function () use ($included) { + return new CompoundDocument(new ResourceObjectSet(), $included); + }, + ], + [ + function () use ($included) { + return new CompoundDocument(new ResourceIdentifier('oranges', '1'), $included); + }, + ], + [ + function () use ($included) { + return new CompoundDocument( + new ResourceIdentifierSet(new ResourceIdentifier('oranges', '1'), new ResourceIdentifier('oranges', '1')), + $included + ); + }, + ], + [ + function () use ($included) { + return new CompoundDocument( + new ResourceObjectSet(new ResourceObject('oranges', '1'), new ResourceObject('oranges', '1')), + $included + ); + }, + ], + ]; + } + + public function testIncludedResourceMayBeIdentifiedByLinkageInPrimaryData() + { + $author = new ResourceObject('people', '9'); + $article = new ResourceObject( + 'articles', + '1', + new Relationship('author', new SingleLinkage($author->identifier())) + ); + $doc = new CompoundDocument($article, new Included($author)); + $this->assertNotEmpty($doc); + } + + public function testIncludedResourceMayBeIdentifiedByAnotherLinkedResource() + { + $writer = new ResourceObject('writers', '3', new Attribute('name', 'Eric Evans')); + $book = new ResourceObject( + 'books', + '2', + new Attribute('name', 'Domain Driven Design'), + new Relationship('author', new SingleLinkage($writer->identifier())) + ); + $cart = new ResourceObject( + 'shopping-carts', + '1', + new Relationship('contents', new MultiLinkage($book->identifier())) + ); + $doc = new CompoundDocument($cart, new Included($book, $writer)); + $this->assertNotEmpty($doc); + } + + /** + * A compound document MUST NOT include more than one resource object for each type and id pair. + * @expectedException \LogicException + * @expectedExceptionMessage Resource {"type":"apples","id":"1"} is already included + */ + public function testCanNotBeManyIncludedResourcesWithEqualIdentifiers() + { + $apple = new ResourceObject('apples', '1'); + new CompoundDocument($apple->identifier(), new Included($apple, $apple)); + } +} diff --git a/test/DataDocument/ManyResourceIdentifiersTest.php b/test/DataDocument/ManyResourceIdentifiersTest.php new file mode 100644 index 0000000..6aaa5bf --- /dev/null +++ b/test/DataDocument/ManyResourceIdentifiersTest.php @@ -0,0 +1,72 @@ +assertEncodesTo( + ' + { + "data": [] + } + ', + new DataDocument( + new ResourceIdentifierSet() + ) + ); + } + + public function testExtendedDocument() + { + $this->assertEncodesTo( + ' + { + "data": [{ + "type": "apples", + "id": "1", + "meta": {"apple_meta": "foo"} + },{ + "type": "apples", + "id": "2", + "meta": {"apple_meta": "foo"} + }], + "links": { + "self": "/apples" + }, + "jsonapi": { + "version": "1.0" + }, + "meta": {"document_meta": "bar"} + } + ', + new DataDocument( + new ResourceIdentifierSet( + new ResourceIdentifier( + 'apples', + '1', + new Meta('apple_meta', 'foo') + ), + new ResourceIdentifier( + 'apples', + '2', + new Meta('apple_meta', 'foo') + ) + ), + new SelfLink(new Url('/apples')), + new JsonApi(), + new Meta('document_meta', 'bar') + ) + ); + } +} diff --git a/test/DataDocument/ManyResourceObjectsTest.php b/test/DataDocument/ManyResourceObjectsTest.php new file mode 100644 index 0000000..ef3da2c --- /dev/null +++ b/test/DataDocument/ManyResourceObjectsTest.php @@ -0,0 +1,85 @@ +assertEncodesTo( + ' + { + "data": [] + } + ', + new DataDocument( + new ResourceObjectSet() + ) + ); + } + + public function testExtendedDocument() + { + $this->assertEncodesTo( + ' + { + "data": [{ + "type": "apples", + "id": "1", + "attributes": { + "color": "red", + "sort": "Fuji" + }, + "meta": {"apple_meta": "foo"} + },{ + "type": "apples", + "id": "2", + "attributes": { + "color": "yellow", + "sort": "Gala" + }, + "meta": {"apple_meta": "foo"} + }], + "links": { + "self": "/apples" + }, + "jsonapi": { + "version": "1.0" + }, + "meta": {"document_meta": "bar"} + } + ', + new DataDocument( + new ResourceObjectSet( + new ResourceObject( + 'apples', + '1', + new Attribute('color', 'red'), + new Attribute('sort', 'Fuji'), + new Meta('apple_meta', 'foo') + ), + new ResourceObject( + 'apples', + '2', + new Attribute('color', 'yellow'), + new Attribute('sort', 'Gala'), + new Meta('apple_meta', 'foo') + ) + ), + new SelfLink(new Url('/apples')), + new JsonApi(), + new Meta('document_meta', 'bar') + ) + ); + } +} diff --git a/test/DataDocument/NullDataTest.php b/test/DataDocument/NullDataTest.php new file mode 100644 index 0000000..9d436e7 --- /dev/null +++ b/test/DataDocument/NullDataTest.php @@ -0,0 +1,53 @@ +assertEncodesTo( + ' + { + "data": null + } + ', + new DataDocument( + new NullData() + ) + ); + } + + public function testExtendedDocument() + { + $this->assertEncodesTo( + ' + { + "data": null, + "links": { + "self": "/apples/1" + }, + "jsonapi": { + "version": "1.0" + }, + "meta": {"document_meta": "bar"} + } + ', + new DataDocument( + new NullData(), + new SelfLink(new Url('/apples/1')), + new JsonApi(), + new Meta('document_meta', 'bar') + ) + ); + } +} diff --git a/test/DataDocument/SingleResourceIdentifierTest.php b/test/DataDocument/SingleResourceIdentifierTest.php new file mode 100644 index 0000000..becb005 --- /dev/null +++ b/test/DataDocument/SingleResourceIdentifierTest.php @@ -0,0 +1,63 @@ +assertEncodesTo( + ' + { + "data": { + "type": "apples", + "id": "1" + } + } + ', + new DataDocument( + new ResourceIdentifier('apples', '1') + ) + ); + } + + public function testExtendedDocument() + { + $this->assertEncodesTo( + ' + { + "data": { + "type": "apples", + "id": "1", + "meta": {"apple_meta": "foo"} + }, + "links": { + "self": "/apples/1" + }, + "jsonapi": { + "version": "1.0" + }, + "meta": {"document_meta": "bar"} + } + ', + new DataDocument( + new ResourceIdentifier( + 'apples', + '1', + new Meta('apple_meta', 'foo') + ), + new SelfLink(new Url('/apples/1')), + new JsonApi(), + new Meta('document_meta', 'bar') + ) + ); + } +} diff --git a/test/DataDocument/SingleResourceObjectTest.php b/test/DataDocument/SingleResourceObjectTest.php new file mode 100644 index 0000000..5d9af4b --- /dev/null +++ b/test/DataDocument/SingleResourceObjectTest.php @@ -0,0 +1,70 @@ +assertEncodesTo( + ' + { + "data": { + "type": "apples", + "id": "1" + } + } + ', + new DataDocument( + new ResourceObject('apples', '1') + ) + ); + } + + public function testExtendedDocument() + { + $this->assertEncodesTo( + ' + { + "data": { + "type": "apples", + "id": "1", + "attributes": { + "color": "red", + "sort": "Fuji" + }, + "meta": {"apple_meta": "foo"} + }, + "links": { + "self": "/apples/1" + }, + "jsonapi": { + "version": "1.0" + }, + "meta": {"document_meta": "bar"} + } + ', + new DataDocument( + new ResourceObject( + 'apples', + '1', + new Attribute('color', 'red'), + new Attribute('sort', 'Fuji'), + new Meta('apple_meta', 'foo') + ), + new SelfLink(new Url('/apples/1')), + new JsonApi(), + new Meta('document_meta', 'bar') + ) + ); + } +} diff --git a/test/Document/CompoundDocumentTest.php b/test/Document/CompoundDocumentTest.php deleted file mode 100644 index 5789f26..0000000 --- a/test/Document/CompoundDocumentTest.php +++ /dev/null @@ -1,294 +0,0 @@ -setAttribute('first-name', 'Dan'); - $dan->setAttribute('last-name', 'Gebhardt'); - $dan->setAttribute('twitter', 'dgeb'); - $dan->setLink('self', 'http://example.com/people/9'); - - $comment05 = new ResourceObject('comments', '5'); - $comment05->setAttribute('body', 'First!'); - $comment05->setLink('self', 'http://example.com/comments/5'); - $comment05->setRelationship( - 'author', - Relationship::fromLinkage(new SingleLinkage(new ResourceIdentifier('people', '2'))) - ); - - $comment12 = new ResourceObject('comments', '12'); - $comment12->setAttribute('body', 'I like XML better'); - $comment12->setLink('self', 'http://example.com/comments/12'); - $comment12->setRelationship( - 'author', - Relationship::fromLinkage(new SingleLinkage($dan->toIdentifier())) - ); - - $author = Relationship::fromLinkage(new SingleLinkage($dan->toIdentifier())); - $author->setLink('self', 'http://example.com/articles/1/relationships/author'); - $author->setLink('related', 'http://example.com/articles/1/author'); - - $comments = Relationship::fromLinkage(new MultiLinkage($comment05->toIdentifier(), $comment12->toIdentifier())); - $comments->setLink('self', 'http://example.com/articles/1/relationships/comments'); - $comments->setLink('related', 'http://example.com/articles/1/comments'); - - $article = new ResourceObject('articles', '1'); - $article->setAttribute('title', 'JSON API paints my bikeshed!'); - $article->setLink('self', 'http://example.com/articles/1'); - $article->setRelationship('author', $author); - $article->setRelationship('comments', $comments); - - $doc = Document::fromResources($article); - $doc->setIncluded($dan, $comment05, $comment12); - $doc->setLink('self', 'http://example.com/articles'); - $doc->setLink('next', 'http://example.com/articles?page[offset]=2'); - $doc->setLink('last', 'http://example.com/articles?page[offset]=10'); - - $this->assertEncodesTo( - ' - { - "links": { - "self": "http://example.com/articles", - "next": "http://example.com/articles?page[offset]=2", - "last": "http://example.com/articles?page[offset]=10" - }, - "data": [{ - "type": "articles", - "id": "1", - "attributes": { - "title": "JSON API paints my bikeshed!" - }, - "links": { - "self": "http://example.com/articles/1" - }, - "relationships": { - "author": { - "links": { - "self": "http://example.com/articles/1/relationships/author", - "related": "http://example.com/articles/1/author" - }, - "data": { "type": "people", "id": "9" } - }, - "comments": { - "links": { - "self": "http://example.com/articles/1/relationships/comments", - "related": "http://example.com/articles/1/comments" - }, - "data": [ - { "type": "comments", "id": "5" }, - { "type": "comments", "id": "12" } - ] - } - } - }], - "included": [{ - "type": "people", - "id": "9", - "attributes": { - "first-name": "Dan", - "last-name": "Gebhardt", - "twitter": "dgeb" - }, - "links": { - "self": "http://example.com/people/9" - } - }, { - "type": "comments", - "id": "5", - "attributes": { - "body": "First!" - }, - "relationships": { - "author": { - "data": { "type": "people", "id": "2" } - } - }, - "links": { - "self": "http://example.com/comments/5" - } - }, { - "type": "comments", - "id": "12", - "attributes": { - "body": "I like XML better" - }, - "relationships": { - "author": { - "data": { "type": "people", "id": "9" } - } - }, - "links": { - "self": "http://example.com/comments/12" - } - }] - } - ', - $doc - ); - } - - /** - * @expectedException \DomainException - * @expectedExceptionMessage Full linkage is required for apples:1 - * @dataProvider documentsWithoutFullLinkage - * @param Document $doc - */ - public function testFullLinkageIsRequired(Document $doc) - { - $doc->setIncluded(new ResourceObject('apples', '1')); - json_encode($doc); - } - - public function documentsWithoutFullLinkage(): array - { - return [ - [Document::nullDocument()], - [Document::fromIdentifier(new ResourceIdentifier('oranges', '1'))], - [Document::fromIdentifiers(new ResourceIdentifier('oranges', '1'), new ResourceIdentifier('oranges', '2'))], - [Document::fromResource(new ResourceObject('oranges', '1'))], - [Document::fromResources(new ResourceObject('oranges', '1'), new ResourceObject('oranges', '1'))], - ]; - } - - /** - * A compound document must be explicitly marked as sparse. In this case full linkage is not required. - */ - public function testFullLinkageIsNotRequiredIfSparse() - { - $doc = Document::nullDocument(); - $doc->markSparse(); - $doc->setIncluded(new ResourceObject('apples', '1')); - $this->assertEncodesTo( - ' - { - "data": null, - "included": [ - { - "type": "apples", - "id": "1" - } - ] - } - ', - $doc - ); - } - - /** - * Compound documents require “full linkage”, meaning that every included resource MUST be identified - * by at least one resource identifier object in the same document. - * These resource identifier objects could either be primary data or represent resource linkage - * contained within primary or included resources. - */ - public function testIncludedResourceMayBeIdentifiedByPrimaryData() - { - $apple = new ResourceObject('apples', '1'); - $apple->setAttribute('color', 'red'); - $doc = Document::fromIdentifier($apple->toIdentifier()); - $doc->setIncluded($apple); - $this->assertJson(json_encode($doc)); - } - - public function testIncludedResourceMayBeIdentifiedByLinkageInPrimaryData() - { - $author = new ResourceObject('people', '9'); - $author->setAttribute('first-name', 'Dan'); - - $article = new ResourceObject('articles', '1'); - $article->setAttribute('title', 'JSON API paints my bikeshed!'); - $article->setRelationship( - 'author', - Relationship::fromLinkage(new SingleLinkage($author->toIdentifier())) - ); - - $doc = Document::fromResource($article); - $doc->setIncluded($author); - $this->assertJson(json_encode($doc)); - } - - public function testIncludedResourceMayBeIdentifiedByAnotherLinkedResource() - { - $writer = new ResourceObject('writers', '3'); - $writer->setAttribute('name', 'Eric Evans'); - - $book = new ResourceObject('books', '2'); - $book->setAttribute('name', 'Domain Driven Design'); - $book->setRelationship( - 'author', - Relationship::fromLinkage(new SingleLinkage($writer->toIdentifier())) - ); - - $cart = new ResourceObject('shopping-carts', '1'); - $cart->setRelationship( - 'contents', - Relationship::fromLinkage(new MultiLinkage($book->toIdentifier())) - ); - - $this->assertTrue($book->identifies($writer)); - - $doc = Document::fromResource($cart); - $doc->setIncluded($book, $writer); - $this->assertJson(json_encode($doc)); - } - - /** - * A compound document MUST NOT include more than one resource object for each type and id pair. - * @expectedException \DomainException - * @expectedExceptionMessage Resource apples:1 is already included - */ - public function testCanNotBeManyIncludedResourcesWithEqualIdentifiers() - { - $apple = new ResourceObject('apples', '1'); - $apple->setAttribute('color', 'red'); - $doc = Document::fromIdentifier($apple->toIdentifier()); - $doc->setIncluded($apple, $apple); - $this->assertJson(json_encode($doc)); - } - - /** - * If a document does not contain a top-level data key, the included member MUST NOT be present either. - * @expectedException \DomainException - * @expectedExceptionMessage Document with no data cannot contain included resources - */ - public function testIncludedMustOnlyBePresentWithData() - { - $doc = Document::fromMeta(['foo' => 'bar']); - $doc->setIncluded(new ResourceObject('apples', '1')); - } -} diff --git a/test/Document/DocumentTest.php b/test/Document/DocumentTest.php deleted file mode 100644 index 705f769..0000000 --- a/test/Document/DocumentTest.php +++ /dev/null @@ -1,186 +0,0 @@ -assertEncodesTo( - ' - { - "meta": { - "foo": "bar" - } - } - ', - Document::fromMeta(['foo' => 'bar']) - ); - } - - /** - * A valid document may contain just an array of errors. - * The array of errors may even be empty, the documentation does not explicitly restrict it. - */ - public function testDocumentMayContainJustErrors() - { - $this->assertEncodesTo( - ' - { - "errors": [ - { - "id": "first" - } - ] - } - ', - Document::fromErrors(new Error('first')) - ); - - $this->assertEncodesTo( - ' - { - "errors": [] - } - ', - Document::fromErrors() - ); - } - - /** - * A valid document may contain just a primary data object. - * The primary data object is represented by ResourceInterface (@see ResourceObjectTest for details). - * Here is how a document can be created from different kinds of resources: - * - null resource - * - resource identifier - * - full-fledged resource object - * - an array of resource objects/identifiers - */ - public function testDocumentMayContainJustData() - { - $this->assertEncodesTo( - ' - { - "data": null - } - ', - Document::nullDocument(), - 'The simplest document possible contains null' - ); - - $this->assertEncodesTo( - ' - { - "data": { - "type": "books", - "id": "abc123" - } - } - ', - Document::fromIdentifier(new ResourceIdentifier('books', 'abc123')), - 'Resource identifier can be used as primary data' - ); - - $apple = new ResourceObject('apples', '007'); - $apple->setAttribute('color', 'red'); - $this->assertEncodesTo( - ' - { - "data": { - "type": "apples", - "id": "007", - "attributes": { - "color": "red" - } - } - } - ', - Document::fromResource($apple), - 'Full-fledged resource object' - ); - - $this->assertEncodesTo( - ' - { - "data": [ - { - "type": "books", - "id": "12" - }, - { - "type": "carrots", - "id": "42" - } - ] - } - ', - Document::fromIdentifiers( - new ResourceIdentifier('books', '12'), - new ResourceIdentifier('carrots', '42') - ), - 'An array of resource identifiers' - ); - } - - /** - * When a document is created, it is possible to add more stuff to it: - * - API details - * - meta - * - links (@see LinkageTest for details) - */ - public function testDocumentCanHaveExtraProperties() - { - $doc = Document::fromIdentifier( - new ResourceIdentifier('apples', '42') - ); - $doc->setApiVersion('1.0'); - $doc->setApiMeta(['a' => 'b']); - $doc->setMeta(['test' => 'test']); - $doc->setLink('self', 'http://example.com/self'); - $doc->setLink('related', 'http://example.com/rel', ['foo' => 'bar']); - $this->assertEncodesTo( - ' - { - "data": { - "type": "apples", - "id": "42" - }, - "meta": { - "test": "test" - }, - "jsonapi": { - "version": "1.0", - "meta": { - "a": "b" - } - }, - "links": { - "self": "http://example.com/self", - "related": { - "href": "http://example.com/rel", - "meta": { - "foo": "bar" - } - } - } - } - ', - $doc - ); - } -} diff --git a/test/Document/ErrorTest.php b/test/Document/ErrorTest.php deleted file mode 100644 index 995a8e9..0000000 --- a/test/Document/ErrorTest.php +++ /dev/null @@ -1,51 +0,0 @@ -assertEncodesTo('{}', new Error()); - } - - public function testErrorWithFullSetOfProperties() - { - $e = new Error(); - $e->setId('test_id'); - $e->setAboutLink('http://localhost'); - $e->setStatus('404'); - $e->setCode('OMG'); - $e->setTitle('Error'); - $e->setDetail('Nothing is found'); - $e->setSourcePointer('/data'); - $e->setSourceParameter('test_param'); - $e->setMeta(['foo' => 'bar']); - - $this->assertEncodesTo( - ' - { - "id": "test_id", - "links": { - "about":"http://localhost" - }, - "status": "404", - "code": "OMG", - "title": "Error", - "detail": "Nothing is found", - "source": { - "pointer": "/data", - "parameter": "test_param" - }, - "meta": { - "foo":"bar" - } - } - ', - $e - ); - } -} diff --git a/test/Document/Resource/Relationship/LinkageTest.php b/test/Document/Resource/Relationship/LinkageTest.php deleted file mode 100644 index 21671c6..0000000 --- a/test/Document/Resource/Relationship/LinkageTest.php +++ /dev/null @@ -1,119 +0,0 @@ -assertEncodesTo( - 'null', - new NullLinkage() - ); - } - - public function testCanCreateEmptyArrayLinkage() - { - $this->assertEncodesTo( - '[]', - new MultiLinkage() - ); - } - - public function testCanCreateFromSingleResourceId() - { - $this->assertEncodesTo( - ' - { - "type": "books", - "id": "abc" - } - ', - new SingleLinkage(new ResourceIdentifier('books', 'abc')) - ); - } - - public function testCanCreateFromArrayOfResourceIds() - { - $this->assertEncodesTo( - ' - [ - { - "type": "books", - "id": "abc" - }, - { - "type": "squirrels", - "id": "123" - } - ] - ', - new MultiLinkage( - new ResourceIdentifier('books', 'abc'), - new ResourceIdentifier('squirrels', '123') - ) - ); - } - - public function testNullLinkageIsLinkedToNothing() - { - $apple = new ResourceObject('apples', '1'); - $this->assertFalse((new NullLinkage())->isLinkedTo($apple)); - } - - public function testEmptyArrayLinkageIsLinkedToNothing() - { - $apple = new ResourceObject('apples', '1'); - $this->assertFalse((new MultiLinkage())->isLinkedTo($apple)); - } - - public function testSingleLinkageIsLinkedOnlyToItself() - { - $apple = new ResourceObject('apples', '1'); - $orange = new ResourceObject('oranges', '1'); - - $linkage = new SingleLinkage($apple->toIdentifier()); - - $this->assertTrue($linkage->isLinkedTo($apple)); - $this->assertFalse($linkage->isLinkedTo($orange)); - } - - public function testMultiLinkageIsLinkedOnlyToItsMembers() - { - $apple = new ResourceObject('apples', '1'); - $orange = new ResourceObject('oranges', '1'); - $banana = new ResourceObject('bananas', '1'); - - $linkage = new MultiLinkage($apple->toIdentifier(), $orange->toIdentifier()); - - $this->assertTrue($linkage->isLinkedTo($apple)); - $this->assertTrue($linkage->isLinkedTo($orange)); - $this->assertFalse($linkage->isLinkedTo($banana)); - } -} diff --git a/test/Document/Resource/Relationship/RelationshipTest.php b/test/Document/Resource/Relationship/RelationshipTest.php deleted file mode 100644 index fc406af..0000000 --- a/test/Document/Resource/Relationship/RelationshipTest.php +++ /dev/null @@ -1,95 +0,0 @@ -assertEncodesTo( - ' - { - "links": { - "self": "http://localhost" - } - } - ', - Relationship::fromSelfLink('http://localhost') - ); - } - - public function testCanCreateFromRelatedLink() - { - $this->assertEncodesTo( - ' - { - "links": { - "related": "http://localhost" - } - } - ', - Relationship::fromRelatedLink('http://localhost') - ); - } - - public function testCanCreateFromLinkage() - { - $this->assertEncodesTo( - ' - { - "data": null - } - ', - Relationship::fromLinkage(new NullLinkage()) - ); - } - - public function testCanCreateFromMeta() - { - $this->assertEncodesTo( - ' - { - "meta": { - "a": "b" - } - } - ', - Relationship::fromMeta(['a' => 'b']) - ); - } -} diff --git a/test/Document/Resource/ResourceFieldsTest.php b/test/Document/Resource/ResourceFieldsTest.php deleted file mode 100644 index 9e25d6f..0000000 --- a/test/Document/Resource/ResourceFieldsTest.php +++ /dev/null @@ -1,76 +0,0 @@ -setAttribute('foo', 'bar'); - $res->setRelationship('foo', Relationship::fromMeta(['a' => 'b'])); - } - - /** - * @expectedException \DomainException - * @expectedExceptionMessage Field 'foo' already exists in relationships - */ - public function testCanNotSetAttributeIfRelationshipExists() - { - $res = new ResourceObject('books', '1'); - $res->setRelationship('foo', Relationship::fromMeta(['a' => 'b'])); - $res->setAttribute('foo', 'bar'); - } - - /** - * @param string $name - * @expectedException \DomainException - * @expectedExceptionMessage Can not use a reserved name - * @dataProvider reservedAttributeNames - */ - public function testAttributeCanNotHaveReservedNames(string $name) - { - $res = new ResourceObject('books', 'abc'); - $res->setAttribute($name, 1); - } - - /** - * @param string $name - * @expectedException \DomainException - * @expectedExceptionMessage Can not use a reserved name - * @dataProvider reservedAttributeNames - */ - public function testRelationshipCanNotHaveReservedNames(string $name) - { - $res = new ResourceObject('books', 'abc'); - $res->setRelationship($name, Relationship::fromMeta(['a' => 'b'])); - } - - public function reservedAttributeNames(): array - { - return [ - ['id'], - ['type'], - ]; - } -} diff --git a/test/Document/Resource/ResourceIdentifierTest.php b/test/Document/Resource/ResourceIdentifierTest.php deleted file mode 100644 index 9911355..0000000 --- a/test/Document/Resource/ResourceIdentifierTest.php +++ /dev/null @@ -1,44 +0,0 @@ -assertEncodesTo( - ' - { - "type": "books", - "id": "1" - } - ', - new ResourceIdentifier('books', '1') - ); - } - - public function testResourceIdentifierMayContainMeta() - { - $this->assertEncodesTo( - ' - { - "type": "books", - "id": "1", - "meta": { - "foo":"bar" - } - } - ', - new ResourceIdentifier('books', '1', ['foo' => 'bar']) - ); - } -} diff --git a/test/Document/Resource/ResourceObjectTest.php b/test/Document/Resource/ResourceObjectTest.php deleted file mode 100644 index 978a77b..0000000 --- a/test/Document/Resource/ResourceObjectTest.php +++ /dev/null @@ -1,110 +0,0 @@ -assertEncodesTo('{"type": "books"}', new ResourceObject('books')); - } - - /** - * In addition, a resource object MAY contain any of these top-level members: - * - * - attributes: an attributes object representing some of the resource’s data. - * - * - relationships: a relationships object describing relationships - * between the resource and other JSON API resources. - * - * - links: a links object containing links related to the resource. - * - * - meta: a meta object containing non-standard meta-information about a resource - * that can not be represented as an attribute or relationship. - */ - public function testResourceObjectMayContainAttributes() - { - $apple = new ResourceObject('apples', '1'); - $apple->setAttribute('color', 'red'); - $this->assertEncodesTo('{"type":"apples", "id":"1", "attributes":{"color":"red"}}', $apple); - } - - public function testResourceObjectMayContainRelationships() - { - $article = new ResourceObject('articles', '1'); - $user = new ResourceIdentifier('users', '42'); - $article->setRelationship('author', Relationship::fromLinkage(new SingleLinkage($user))); - $this->assertEncodesTo( - ' - { - "type":"articles", - "id":"1", - "relationships":{ - "author":{ - "data":{ - "type":"users", - "id":"42" - } - } - } - } - ', - $article - ); - } - - public function testResourceObjectMayContainLinks() - { - $article = new ResourceObject('articles', '1'); - $article->setLink('self', 'https://example.com'); - $this->assertEncodesTo( - ' - { - "type":"articles", - "id":"1", - "links":{ - "self":"https://example.com" - } - } - ', - $article - ); - } - - public function testResourceObjectMayContainMeta() - { - $article = new ResourceObject('articles', '1'); - $article->setMeta(['tags' => ['cool', 'new']]); - $this->assertEncodesTo( - ' - { - "type":"articles", - "id":"1", - "meta":{ - "tags":[ - "cool", - "new" - ] - } - } - ', - $article - ); - } -} diff --git a/test/ErrorDocumentTest.php b/test/ErrorDocumentTest.php new file mode 100644 index 0000000..5ed715a --- /dev/null +++ b/test/ErrorDocumentTest.php @@ -0,0 +1,122 @@ +assertEncodesTo( + ' + { + "errors": [{}] + } + ', + new ErrorDocument( + new Error() + ) + ); + } + + public function testExtensiveExample() + { + $this->assertEncodesTo( + ' + { + "errors": [{ + "id": "1", + "links": { + "about":"/errors/not_found" + }, + "status": "404", + "code": "not_found", + "title": "Resource not found", + "detail": "We tried hard but could not find anything", + "source": { + "pointer": "/data", + "parameter": "query_string" + }, + "meta": { + "purpose":"test" + } + }], + "meta": {"purpose": "test"}, + "jsonapi": { + "version": "1.0" + } + } + ', + new ErrorDocument( + new Error( + new Id('1'), + new AboutLink( + new Url('/errors/not_found') + ), + new Status('404'), + new Code('not_found'), + new Title('Resource not found'), + new Detail('We tried hard but could not find anything'), + new Pointer('/data'), + new Parameter('query_string'), + new Meta('purpose', 'test') + ), + new Meta('purpose', 'test'), + new JsonApi() + ) + ); + } + + public function testMultipleErrors() + { + $this->assertEncodesTo( + ' + { + "errors": [{ + "id": "1", + "code": "invalid_parameter", + "source": { + "parameter": "foo" + } + },{ + "id": "2", + "code": "invalid_parameter", + "source": { + "parameter": "bar" + } + }] + } + ', + new ErrorDocument( + new Error( + new Id('1'), + new Code('invalid_parameter'), + new Parameter('foo') + ), + new Error( + new Id('2'), + new Code('invalid_parameter'), + new Parameter('bar') + ) + ) + ); + } +} diff --git a/test/ExamplesTest.php b/test/ExamplesTest.php new file mode 100644 index 0000000..ba7a1cf --- /dev/null +++ b/test/ExamplesTest.php @@ -0,0 +1,26 @@ +assertJson(`php $file`); + } + + public function examples() + { + return [ + [__DIR__.'/../examples/compound_doc.php'], + [__DIR__.'/../examples/simple_doc.php'], + ]; + } +} diff --git a/test/IntegrationTest.php b/test/IntegrationTest.php deleted file mode 100644 index a884d86..0000000 --- a/test/IntegrationTest.php +++ /dev/null @@ -1,58 +0,0 @@ -setLink('self', '/articles/1/relationships/author'); - $author->setLink('related', '/articles/1/author'); - $articles->setRelationship('author', $author); - $articles->setAttribute('title', 'Rails is Omakase'); - $doc = Document::fromResource($articles); - - $this->assertEquals( - $json, - json_encode($doc, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) - ); - } -} diff --git a/test/JsonApiTest.php b/test/JsonApiTest.php new file mode 100644 index 0000000..99731b0 --- /dev/null +++ b/test/JsonApiTest.php @@ -0,0 +1,25 @@ +assertEncodesTo( + ' + { + "version": "1.0", + "meta": { + "foo": "bar" + } + } + ', + new JsonApi('1.0', new Meta('foo', 'bar')) + ); + } +} diff --git a/test/LinkObjectTest.php b/test/LinkObjectTest.php new file mode 100644 index 0000000..6df8fff --- /dev/null +++ b/test/LinkObjectTest.php @@ -0,0 +1,34 @@ +assertEncodesTo( + '{ + "data": {"type": "apples", "id": "1"}, + "links": { + "self": { + "href": "http://example.com", + "meta": { + "foo": "bar" + } + } + } + } + ', + new DataDocument( + new ResourceIdentifier('apples', '1'), + new SelfLink(new LinkObject('http://example.com', new Meta('foo', 'bar'))) + ) + ); + } +} diff --git a/test/MemberNamesTest.php b/test/MemberNamesTest.php deleted file mode 100644 index 196c356..0000000 --- a/test/MemberNamesTest.php +++ /dev/null @@ -1,117 +0,0 @@ -invalidAttributeNames() as $attributeName) { - foreach ($this->memberNameCallbacks() as $memberNameCallback) { - yield [$attributeName, $memberNameCallback]; - } - foreach ($this->linksCallbacks() as $linksCallback) { - yield [$attributeName, $linksCallback]; - } - } - } - - public function invalidAttributesAndResourceTypeCallbacks() - { - foreach ($this->invalidAttributeNames() as $attributeName) { - foreach ($this->resourceTypeCallbacks() as $resourceTypeCallback) { - yield [$attributeName, $resourceTypeCallback]; - } - } - } - - private function invalidAttributeNames(): array - { - return [ - '_abcde', - 'abcd_', - 'abc$EDS', - '#abcde', - 'abcde(', - 'b_', - '_a', - '$ab_c-d', - '-abc', - ]; - } - - /** - * @return callable[] - */ - private function memberNameCallbacks() - { - return [ - function ($name) { - (new ResourceObject('apples', '0'))->setRelationship( - $name, - Relationship::fromSelfLink('https://example.com') - ); - }, - function ($name) { - (new ResourceObject('apples', '0'))->setAttribute($name, 'foo'); - }, - ]; - } - - private function resourceTypeCallbacks() - { - yield function ($type) { - new ResourceIdentifier($type, 'foo'); - }; - yield function ($type) { - new ResourceObject($type, 'foo'); - }; - } - - private function linksCallbacks() - { - $objects = [ - Document::nullDocument(), - Relationship::fromSelfLink('https://example.com'), - new ResourceObject('apples', '0'), - ]; - foreach ($objects as $object) { - yield function ($name) use ($object) { - $object->setLink($name, 'https://example.com'); - }; - } - } -} diff --git a/test/MetaDocumentTest.php b/test/MetaDocumentTest.php new file mode 100644 index 0000000..7893dae --- /dev/null +++ b/test/MetaDocumentTest.php @@ -0,0 +1,51 @@ +assertEncodesTo( + ' + { + "meta": { + "foo": "bar" + } + } + ', + new MetaDocument(new Meta('foo', 'bar')) + ); + } + + /** + * A meta document may contain jsonapi member + */ + public function testMetaDocumentWithExtraMembers() + { + $this->assertEncodesTo( + ' + { + "meta": { + "foo": "bar" + }, + "jsonapi": { + "version": "1.0" + } + } + ', + new MetaDocument( + new Meta('foo', 'bar'), + new JsonApi() + ) + ); + } +} diff --git a/test/MetaTest.php b/test/MetaTest.php new file mode 100644 index 0000000..8ea6b1b --- /dev/null +++ b/test/MetaTest.php @@ -0,0 +1,16 @@ +expectException(\DomainException::class); + $this->expectExceptionMessage("Invalid character in a member name 'this+name&is'"); + new Meta('this+name&is', '1'); + } +} diff --git a/test/PaginationLinksTest.php b/test/PaginationLinksTest.php new file mode 100644 index 0000000..f1774d6 --- /dev/null +++ b/test/PaginationLinksTest.php @@ -0,0 +1,45 @@ +assertEncodesTo( + ' + { + "data": [ + {"type": "apples", "id": "1"}, + {"type": "apples", "id": "2"} + ], + "links": { + "first": "http://example.com/fruits?page=first", + "last": "http://example.com/fruits?page=last", + "prev": "http://example.com/fruits?page=3", + "next": "http://example.com/fruits?page=5" + } + } + ', + new DataDocument( + new ResourceObjectSet( + new ResourceObject('apples', '1'), + new ResourceObject('apples', '2') + ), + new FirstLink(new Url('http://example.com/fruits?page=first')), + new LastLink(new Url('http://example.com/fruits?page=last')), + new PrevLink(new Url('http://example.com/fruits?page=3')), + new NextLink(new Url('http://example.com/fruits?page=5')) + ) + ); + } +} diff --git a/test/ResourceObjectTest.php b/test/ResourceObjectTest.php new file mode 100644 index 0000000..3548dcd --- /dev/null +++ b/test/ResourceObjectTest.php @@ -0,0 +1,215 @@ +assertEncodesTo( + ' + { + "type": "apples", + "id": "1", + "attributes": { + "title": "Rails is Omakase" + }, + "meta": {"foo": "bar"}, + "links": { + "self": "http://self" + }, + "relationships": { + "author": { + "meta": {"foo": "bar"}, + "links": { + "self": "http://rel/author", + "related": "http://author" + }, + "data": null + } + } + } + ', + new ResourceObject( + 'apples', + '1', + new Meta('foo', 'bar'), + new Attribute('title', 'Rails is Omakase'), + new SelfLink(new Url('http://self')), + new Relationship( + 'author', + new Meta('foo', 'bar'), + new SelfLink(new Url('http://rel/author')), + new RelatedLink(new Url('http://author')), + new SingleLinkage() + ) + ) + ); + } + + public function testRelationshipWithSingleIdLinkage() + { + $this->assertEncodesTo( + ' + { + "data": { + "type": "apples", + "id": "1" + } + } + ', + new Relationship( + 'fruits', + new SingleLinkage( + new ResourceIdentifier('apples', '1') + ) + ) + ); + } + + public function testRelationshipWithMultiIdLinkage() + { + $this->assertEncodesTo( + ' + { + "data": [{ + "type": "apples", + "id": "1" + },{ + "type": "pears", + "id": "2" + }] + } + ', + new Relationship( + 'fruits', + new MultiLinkage( + new ResourceIdentifier('apples', '1'), + new ResourceIdentifier('pears', '2') + ) + ) + ); + } + + public function testRelationshipWithEmptyMultiIdLinkage() + { + $this->assertEncodesTo( + ' + { + "data": [] + } + ', + new Relationship( + 'fruits', + new MultiLinkage() + ) + ); + } + + public function testCanNotCreateIdAttribute() + { + $this->expectException(\DomainException::class); + $this->expectExceptionMessage("Can not use 'id' as a resource field"); + new Attribute('id', 'foo'); + } + + public function testCanNotCreateTypeAttribute() + { + $this->expectException(\DomainException::class); + $this->expectExceptionMessage("Can not use 'type' as a resource field"); + new Attribute('type', 'foo'); + } + + public function testCanNotCreateIdRelationship() + { + $this->expectException(\DomainException::class); + $this->expectExceptionMessage("Can not use 'id' as a resource field"); + new Relationship('id', new SingleLinkage(new ResourceIdentifier('apples', '1'))); + } + + public function testCanNotCreateTypeRelationship() + { + $this->expectException(\DomainException::class); + $this->expectExceptionMessage("Can not use 'type' as a resource field"); + new Relationship('type', new SingleLinkage(new ResourceIdentifier('apples', '1'))); + } + + /** + * @dataProvider invalidCharacters + * @param string $invalid_char + */ + public function testAttributeMustOnlyHaveAllowedCharacters(string $invalid_char) + { + $this->expectException(\DomainException::class); + $this->expectExceptionMessage('Invalid character in a member name'); + new Attribute("foo{$invalid_char}bar", 'plus can not be used'); + } + + /** + * @dataProvider invalidCharacters + * @param string $invalid_char + */ + public function testRelationshipMustOnlyHaveAllowedCharacters(string $invalid_char) + { + $this->expectException(\DomainException::class); + $this->expectExceptionMessage('Invalid character in a member name'); + new Relationship("foo{$invalid_char}bar", new SingleLinkage()); + } + + public function invalidCharacters() + { + return [ + ['+'], + ['!'], + ['@'], + ['/'], + ['}'], + ]; + } + + public function testResourceFieldsMustBeUnique() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage("Field 'foo' already exists"); + new ResourceObject( + 'apples', + '1', + new Attribute('foo', 'bar'), + new Relationship('foo', new SingleLinkage(new ResourceIdentifier('apples', '1'))) + ); + } + + /** + * The id member is not required when the resource object originates at the client and represents + * a new resource to be created on the server. + */ + public function testResourceIdCanBeOmitted() + { + $this->assertEncodesTo( + '{ + "type": "apples", + "id": null, + "attributes": { + "color": "red" + } + }', + new ResourceObject('apples', null, new Attribute('color', 'red')) + ); + } + + public function testEmptySingleLinkageIdentifiesNothing() + { + $this->assertFalse((new SingleLinkage())->identifies(new ResourceObject('something', '1'))); + } +} From 461ca3b89795026c0c979f7023aaee0b28dddd4c Mon Sep 17 00:00:00 2001 From: Alexey Date: Mon, 26 Feb 2018 19:09:53 -0800 Subject: [PATCH 24/45] Fix the link (#78) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9431d4e..7a0b9be 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # [JSON API](http://jsonapi.org) spec implemented in PHP 7. Immutable -**This is v2 of the implementation. For v1 click [here](/json-api-php/json-api/tree/v2).** +**This is v2 of the implementation. For v1 click [here](./tree/v1).** The goal of this library is to ensure strict validity of JSON API documents being produced. From 7fd13828cea51010a93e2705bbf1d4ebbd147be6 Mon Sep 17 00:00:00 2001 From: Alexey Date: Mon, 26 Feb 2018 19:13:19 -0800 Subject: [PATCH 25/45] Fix the link (#79) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a0b9be..93b5387 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # [JSON API](http://jsonapi.org) spec implemented in PHP 7. Immutable -**This is v2 of the implementation. For v1 click [here](./tree/v1).** +**This is v2 of the implementation. For v1 click [here](https://github.com/json-api-php/json-api/tree/v1).** The goal of this library is to ensure strict validity of JSON API documents being produced. From 911633ffe3002f1a393e5fc549b5f70b4ceb4aef Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 1 Mar 2018 01:53:13 -0800 Subject: [PATCH 26/45] Performance improvements (#81) --- README.md | 14 +- composer.json | 2 +- examples/compound_doc.php | 53 +++--- examples/simple_doc.php | 12 +- src/AttachableValue.php | 22 --- src/Attribute.php | 17 +- src/CompoundDocument.php | 26 ++- src/DataDocument.php | 14 +- src/DataDocumentMember.php | 10 - src/DocumentMember.php | 10 - src/Error.php | 14 +- src/Error/Code.php | 16 +- src/Error/Detail.php | 16 +- src/Error/ErrorMember.php | 12 -- src/Error/Id.php | 20 +- .../{Parameter.php => SourceParameter.php} | 15 +- src/Error/{Pointer.php => SourcePointer.php} | 12 +- src/Error/Status.php | 16 +- src/Error/Title.php | 16 +- src/ErrorDocument.php | 17 +- src/ErrorDocumentMember.php | 10 - src/Included.php | 34 +++- src/{ => Internal}/Attachable.php | 2 +- src/Internal/DataDocumentMember.php | 10 + src/Internal/ErrorDocumentMember.php | 10 + src/Internal/ErrorMember.php | 10 + src/Internal/Identifier.php | 11 ++ src/Internal/IdentifierRegistry.php | 27 +++ src/Internal/IdentityTrait.php | 29 +++ src/Internal/LinkTrait.php | 29 +++ src/Internal/MetaDocumentMember.php | 10 + src/Internal/PaginationLink.php | 8 + src/Internal/PrimaryData.php | 10 + src/Internal/RelationshipMember.php | 10 + src/Internal/ResourceField.php | 9 + src/Internal/ResourceFieldTrait.php | 28 +++ src/Internal/ResourceMember.php | 10 + src/JsonApi.php | 18 +- src/JsonSerializableValue.php | 21 --- src/Link/AboutLink.php | 12 +- src/Link/AttachableLink.php | 22 --- src/Link/FirstLink.php | 14 +- src/Link/LastLink.php | 14 +- src/Link/Link.php | 10 - src/Link/LinkObject.php | 20 -- src/Link/NextLink.php | 14 +- src/Link/PrevLink.php | 14 +- src/Link/RelatedLink.php | 12 +- src/Link/SelfLink.php | 16 +- src/Link/Url.php | 13 -- src/Meta.php | 22 ++- src/MetaDocument.php | 15 +- src/MultiLinkage.php | 29 --- src/NullData.php | 12 +- src/PaginatedResourceCollection.php | 24 +++ src/PaginatedResourceIdentifierCollection.php | 24 +++ src/Pagination.php | 27 +++ src/PrimaryData/Identifier.php | 13 -- src/PrimaryData/PrimaryData.php | 10 - src/PrimaryData/ResourceField.php | 31 ---- src/PrimaryData/ResourceMember.php | 12 -- src/Relationship.php | 39 ---- src/RelationshipMember.php | 10 - src/ResourceCollection.php | 34 ++++ src/ResourceIdentifier.php | 32 ++-- src/ResourceIdentifierCollection.php | 34 ++++ src/ResourceIdentifierSet.php | 29 --- src/ResourceObject.php | 79 ++++---- src/ResourceObjectSet.php | 29 --- src/SingleLinkage.php | 21 --- src/ToMany.php | 45 +++++ src/ToNull.php | 30 +++ src/ToOne.php | 39 ++++ src/TopLevelDocumentMember.php | 10 - src/functions.php | 10 +- test/CompoundDocumentTest.php | 77 ++++---- .../ManyResourceIdentifiersTest.php | 9 +- test/DataDocument/ManyResourceObjectsTest.php | 9 +- test/DataDocument/NullDataTest.php | 3 +- .../SingleResourceIdentifierTest.php | 3 +- .../DataDocument/SingleResourceObjectTest.php | 3 +- test/ErrorDocumentTest.php | 17 +- test/JsonApiTest.php | 16 +- test/LinkObjectTest.php | 6 +- test/MetaTest.php | 4 +- test/PaginationLinksTest.php | 45 ----- test/PaginationTest.php | 108 +++++++++++ test/ResourceIdentifierTest.php | 15 ++ test/ResourceObjectTest.php | 174 ++++++++++-------- test/benchmarks/compound10k.php | 117 ++++++++++++ 90 files changed, 1270 insertions(+), 787 deletions(-) delete mode 100644 src/AttachableValue.php delete mode 100644 src/DataDocumentMember.php delete mode 100644 src/DocumentMember.php delete mode 100644 src/Error/ErrorMember.php rename src/Error/{Parameter.php => SourceParameter.php} (50%) rename src/Error/{Pointer.php => SourcePointer.php} (54%) delete mode 100644 src/ErrorDocumentMember.php rename src/{ => Internal}/Attachable.php (75%) create mode 100644 src/Internal/DataDocumentMember.php create mode 100644 src/Internal/ErrorDocumentMember.php create mode 100644 src/Internal/ErrorMember.php create mode 100644 src/Internal/Identifier.php create mode 100644 src/Internal/IdentifierRegistry.php create mode 100644 src/Internal/IdentityTrait.php create mode 100644 src/Internal/LinkTrait.php create mode 100644 src/Internal/MetaDocumentMember.php create mode 100644 src/Internal/PaginationLink.php create mode 100644 src/Internal/PrimaryData.php create mode 100644 src/Internal/RelationshipMember.php create mode 100644 src/Internal/ResourceField.php create mode 100644 src/Internal/ResourceFieldTrait.php create mode 100644 src/Internal/ResourceMember.php delete mode 100644 src/JsonSerializableValue.php delete mode 100644 src/Link/AttachableLink.php delete mode 100644 src/Link/Link.php delete mode 100644 src/Link/LinkObject.php delete mode 100644 src/Link/Url.php delete mode 100644 src/MultiLinkage.php create mode 100644 src/PaginatedResourceCollection.php create mode 100644 src/PaginatedResourceIdentifierCollection.php create mode 100644 src/Pagination.php delete mode 100644 src/PrimaryData/Identifier.php delete mode 100644 src/PrimaryData/PrimaryData.php delete mode 100644 src/PrimaryData/ResourceField.php delete mode 100644 src/PrimaryData/ResourceMember.php delete mode 100644 src/Relationship.php delete mode 100644 src/RelationshipMember.php create mode 100644 src/ResourceCollection.php create mode 100644 src/ResourceIdentifierCollection.php delete mode 100644 src/ResourceIdentifierSet.php delete mode 100644 src/ResourceObjectSet.php delete mode 100644 src/SingleLinkage.php create mode 100644 src/ToMany.php create mode 100644 src/ToNull.php create mode 100644 src/ToOne.php delete mode 100644 src/TopLevelDocumentMember.php delete mode 100644 test/PaginationLinksTest.php create mode 100644 test/PaginationTest.php create mode 100644 test/ResourceIdentifierTest.php create mode 100644 test/benchmarks/compound10k.php diff --git a/README.md b/README.md index 93b5387..1677ff1 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,9 @@ use JsonApiPhp\JsonApi\Attribute; use JsonApiPhp\JsonApi\DataDocument; use JsonApiPhp\JsonApi\Link\RelatedLink; use JsonApiPhp\JsonApi\Link\SelfLink; -use JsonApiPhp\JsonApi\Link\Url; -use JsonApiPhp\JsonApi\Relationship; use JsonApiPhp\JsonApi\ResourceIdentifier; use JsonApiPhp\JsonApi\ResourceObject; -use JsonApiPhp\JsonApi\SingleLinkage; +use JsonApiPhp\JsonApi\ToOne; echo json_encode( new DataDocument( @@ -47,11 +45,11 @@ echo json_encode( 'articles', '1', new Attribute('title', 'Rails is Omakase'), - new Relationship( + new ToOne( 'author', - new SingleLinkage(new ResourceIdentifier('author', '9')), - new SelfLink(new Url('/articles/1/relationships/author')), - new RelatedLink(new Url('/articles/1/author')) + new ResourceIdentifier('author', '9'), + new SelfLink('/articles/1/relationships/author'), + new RelatedLink('/articles/1/author') ) ) ), @@ -77,7 +75,7 @@ The library API and use-cases are expressed in comprehensive suite of tests. - [Compound Documents](./test/CompoundDocumentTest.php) - [Error Documents](./test/ErrorDocumentTest.php) - [Meta Documents (containing neither data nor errors)](./test/MetaDocumentTest.php) -- [Pagination links](./test/PaginationLinksTest.php) +- [Pagination](./test/PaginationTest.php) - [Link Objects](./test/LinkObjectTest.php) - [JSON API Object](./test/JsonApiTest.php) - [Meta Objects](./test/MetaTest.php) diff --git a/composer.json b/composer.json index a83d94f..1cdeb90 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "php": ">=7.2" }, "require-dev": { - "phpunit/phpunit": "^6.0", + "phpunit/phpunit": "^7.0", "friendsofphp/php-cs-fixer": "^2.2" }, "autoload": { diff --git a/examples/compound_doc.php b/examples/compound_doc.php index 02fdac0..61842ef 100644 --- a/examples/compound_doc.php +++ b/examples/compound_doc.php @@ -1,4 +1,5 @@ identifier())) + new SelfLink('http://example.com/comments/12'), + new ToOne('author', $dan->toIdentifier()) ); $document = new CompoundDocument( - new ResourceObjectSet( + new PaginatedResourceCollection( + new Pagination( + new NextLink('http://example.com/articles?page[offset]=2'), + new LastLink('http://example.com/articles?page[offset]=10') + ), new ResourceObject( 'articles', '1', new Attribute('title', 'JSON API paints my bikeshed!'), - new SelfLink(new Url('http://example.com/articles/1')), - new Relationship( + new SelfLink('http://example.com/articles/1'), + new ToOne( 'author', - new SingleLinkage($dan->identifier()), - new SelfLink(new Url('http://example.com/articles/1/relationships/author')), - new RelatedLink(new Url('http://example.com/articles/1/author')) + $dan->toIdentifier(), + new SelfLink('http://example.com/articles/1/relationships/author'), + new RelatedLink('http://example.com/articles/1/author') ), - new Relationship( + new ToMany( 'comments', - new MultiLinkage( - $comment05->identifier(), - $comment12->identifier() + new ResourceIdentifierCollection( + $comment05->toIdentifier(), + $comment12->toIdentifier() ), - new SelfLink(new Url('http://example.com/articles/1/relationships/comments')), - new RelatedLink(new Url('http://example.com/articles/1/comments')) + new SelfLink('http://example.com/articles/1/relationships/comments'), + new RelatedLink('http://example.com/articles/1/comments') ) ) ), new Included($dan, $comment05, $comment12), - new SelfLink(new Url('http://example.com/articles')), - new NextLink(new Url('http://example.com/articles?page[offset]=2')), - new LastLink(new Url('http://example.com/articles?page[offset]=10')) + new SelfLink('http://example.com/articles') ); echo json_encode($document, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); diff --git a/examples/simple_doc.php b/examples/simple_doc.php index 48b60b7..a74263a 100644 --- a/examples/simple_doc.php +++ b/examples/simple_doc.php @@ -5,11 +5,9 @@ use JsonApiPhp\JsonApi\DataDocument; use JsonApiPhp\JsonApi\Link\RelatedLink; use JsonApiPhp\JsonApi\Link\SelfLink; -use JsonApiPhp\JsonApi\Link\Url; -use JsonApiPhp\JsonApi\Relationship; use JsonApiPhp\JsonApi\ResourceIdentifier; use JsonApiPhp\JsonApi\ResourceObject; -use JsonApiPhp\JsonApi\SingleLinkage; +use JsonApiPhp\JsonApi\ToOne; echo json_encode( new DataDocument( @@ -17,11 +15,11 @@ 'articles', '1', new Attribute('title', 'Rails is Omakase'), - new Relationship( + new ToOne( 'author', - new SingleLinkage(new ResourceIdentifier('author', '9')), - new SelfLink(new Url('/articles/1/relationships/author')), - new RelatedLink(new Url('/articles/1/author')) + new ResourceIdentifier('author', '9'), + new SelfLink('/articles/1/relationships/author'), + new RelatedLink('/articles/1/author') ) ) ), diff --git a/src/AttachableValue.php b/src/AttachableValue.php deleted file mode 100644 index 0899519..0000000 --- a/src/AttachableValue.php +++ /dev/null @@ -1,22 +0,0 @@ -key = $key; - } - - public function attachTo(object $o) - { - $o->{$this->key} = $this; - } -} diff --git a/src/Attribute.php b/src/Attribute.php index dde5b80..fb2984f 100644 --- a/src/Attribute.php +++ b/src/Attribute.php @@ -2,12 +2,23 @@ namespace JsonApiPhp\JsonApi; -use JsonApiPhp\JsonApi\PrimaryData\ResourceField; +use JsonApiPhp\JsonApi\Internal\ResourceField; +use JsonApiPhp\JsonApi\Internal\ResourceFieldTrait; -final class Attribute extends ResourceField +final class Attribute implements ResourceField { + use ResourceFieldTrait; + private $val; + + public function __construct(string $name, $val) + { + $this->validateFieldName($name); + $this->name = $name; + $this->val = $val; + } + public function attachTo(object $o) { - parent::attachTo(child($o, 'attributes')); + child($o, 'attributes')->{$this->name} = $this->val; } } diff --git a/src/CompoundDocument.php b/src/CompoundDocument.php index fe6b31e..05bc119 100644 --- a/src/CompoundDocument.php +++ b/src/CompoundDocument.php @@ -2,23 +2,21 @@ namespace JsonApiPhp\JsonApi; -use JsonApiPhp\JsonApi\PrimaryData\PrimaryData; +use JsonApiPhp\JsonApi\Internal\DataDocumentMember; +use JsonApiPhp\JsonApi\Internal\PrimaryData; -final class CompoundDocument extends JsonSerializableValue +final class CompoundDocument implements \JsonSerializable { + private $doc; + public function __construct(PrimaryData $data, Included $included, DataDocumentMember ...$members) { - foreach ($included as $resource) { - if ($data->identifies($resource)) { - continue; - } - foreach ($included as $anotherResource) { - if ($anotherResource->identifies($resource)) { - continue 2; - } - } - throw new \DomainException('Full linkage required for '.json_encode($resource->identifier())); - } - parent::__construct(combine($data, $included, ...$members)); + $included->validateLinkage($data); + $this->doc = combine($data, $included, ...$members); + } + + public function jsonSerialize() + { + return $this->doc; } } diff --git a/src/DataDocument.php b/src/DataDocument.php index 64b4e31..68fa2cd 100644 --- a/src/DataDocument.php +++ b/src/DataDocument.php @@ -2,12 +2,20 @@ namespace JsonApiPhp\JsonApi; -use JsonApiPhp\JsonApi\PrimaryData\PrimaryData; +use JsonApiPhp\JsonApi\Internal\DataDocumentMember; +use JsonApiPhp\JsonApi\Internal\PrimaryData; -final class DataDocument extends JsonSerializableValue +final class DataDocument implements \JsonSerializable { + private $value; + public function __construct(PrimaryData $data, DataDocumentMember ...$members) { - parent::__construct(combine($data, ...$members)); + $this->value = combine($data, ...$members); + } + + public function jsonSerialize() + { + return $this->value; } } diff --git a/src/DataDocumentMember.php b/src/DataDocumentMember.php deleted file mode 100644 index 9227b07..0000000 --- a/src/DataDocumentMember.php +++ /dev/null @@ -1,10 +0,0 @@ -error = (object) []; + foreach ($members as $member) { + $member->attachTo($this->error); + } } public function attachTo(object $o) { - $o->errors[] = $this; + $o->errors[] = $this->error; } } diff --git a/src/Error/Code.php b/src/Error/Code.php index fc3dc92..eafbc72 100644 --- a/src/Error/Code.php +++ b/src/Error/Code.php @@ -2,15 +2,25 @@ namespace JsonApiPhp\JsonApi\Error; -use JsonApiPhp\JsonApi\AttachableValue; +use JsonApiPhp\JsonApi\Internal\ErrorMember; -final class Code extends AttachableValue implements ErrorMember +final class Code implements ErrorMember { + /** + * @var string + */ + private $code; + /** * @param string $code an application-specific error code, expressed as a string value */ public function __construct(string $code) { - parent::__construct('code', $code); + $this->code = $code; + } + + public function attachTo(object $o): void + { + $o->code = $this->code; } } diff --git a/src/Error/Detail.php b/src/Error/Detail.php index 3acf546..5950b46 100644 --- a/src/Error/Detail.php +++ b/src/Error/Detail.php @@ -2,15 +2,25 @@ namespace JsonApiPhp\JsonApi\Error; -use JsonApiPhp\JsonApi\AttachableValue; +use JsonApiPhp\JsonApi\Internal\ErrorMember; -final class Detail extends AttachableValue implements ErrorMember +final class Detail implements ErrorMember { + /** + * @var string + */ + private $detail; + /** * @param string $detail a human-readable explanation specific to this occurrence of the problem. */ public function __construct(string $detail) { - parent::__construct('detail', $detail); + $this->detail = $detail; + } + + public function attachTo(object $o): void + { + $o->detail = $this->detail; } } diff --git a/src/Error/ErrorMember.php b/src/Error/ErrorMember.php deleted file mode 100644 index 7dd48de..0000000 --- a/src/Error/ErrorMember.php +++ /dev/null @@ -1,12 +0,0 @@ -id = $id; + } + + public function attachTo(object $o): void { - parent::__construct('id', $identifier); + $o->id = $this->id; } } diff --git a/src/Error/Parameter.php b/src/Error/SourceParameter.php similarity index 50% rename from src/Error/Parameter.php rename to src/Error/SourceParameter.php index 5c403c6..613967a 100644 --- a/src/Error/Parameter.php +++ b/src/Error/SourceParameter.php @@ -2,21 +2,26 @@ namespace JsonApiPhp\JsonApi\Error; -use JsonApiPhp\JsonApi\AttachableValue; +use JsonApiPhp\JsonApi\Internal\ErrorMember; use function JsonApiPhp\JsonApi\child; -final class Parameter extends AttachableValue implements ErrorMember +final class SourceParameter implements ErrorMember { + /** + * @var string + */ + private $parameter; + /** * @param string $parameter a string indicating which URI query parameter caused the error. */ public function __construct(string $parameter) { - parent::__construct('parameter', $parameter); + $this->parameter = $parameter; } - public function attachTo(object $o) + public function attachTo(object $o): void { - parent::attachTo(child($o, 'source')); + child($o, 'source')->parameter = $this->parameter; } } diff --git a/src/Error/Pointer.php b/src/Error/SourcePointer.php similarity index 54% rename from src/Error/Pointer.php rename to src/Error/SourcePointer.php index de768be..bfc6394 100644 --- a/src/Error/Pointer.php +++ b/src/Error/SourcePointer.php @@ -2,21 +2,23 @@ namespace JsonApiPhp\JsonApi\Error; -use JsonApiPhp\JsonApi\AttachableValue; +use JsonApiPhp\JsonApi\Internal\ErrorMember; use function JsonApiPhp\JsonApi\child; -final class Pointer extends AttachableValue implements ErrorMember +final class SourcePointer implements ErrorMember { + private $pointer; + /** * @param string $pointer JSON Pointer [RFC6901] to the associated entity in the request document */ public function __construct(string $pointer) { - parent::__construct('pointer', $pointer); + $this->pointer = $pointer; } - public function attachTo(object $o) + public function attachTo(object $o): void { - parent::attachTo(child($o, 'source')); + child($o, 'source')->pointer = $this->pointer; } } diff --git a/src/Error/Status.php b/src/Error/Status.php index d5c7105..a61031f 100644 --- a/src/Error/Status.php +++ b/src/Error/Status.php @@ -2,15 +2,25 @@ namespace JsonApiPhp\JsonApi\Error; -use JsonApiPhp\JsonApi\AttachableValue; +use JsonApiPhp\JsonApi\Internal\ErrorMember; -final class Status extends AttachableValue implements ErrorMember +final class Status implements ErrorMember { + /** + * @var string + */ + private $status; + /** * @param string $status the HTTP status code applicable to this problem, expressed as a string value */ public function __construct(string $status) { - parent::__construct('status', $status); + $this->status = $status; + } + + public function attachTo(object $o): void + { + $o->status = $this->status; } } diff --git a/src/Error/Title.php b/src/Error/Title.php index 10eddd0..d61a706 100644 --- a/src/Error/Title.php +++ b/src/Error/Title.php @@ -2,16 +2,26 @@ namespace JsonApiPhp\JsonApi\Error; -use JsonApiPhp\JsonApi\AttachableValue; +use JsonApiPhp\JsonApi\Internal\ErrorMember; -class Title extends AttachableValue implements ErrorMember +final class Title implements ErrorMember { + /** + * @var string + */ + private $title; + /** * @param string $title a short, human-readable summary of the problem that SHOULD NOT change from occurrence * to occurrence of the problem, except for purposes of localization */ public function __construct(string $title) { - parent::__construct('title', $title); + $this->title = $title; + } + + public function attachTo(object $o): void + { + $o->title = $this->title; } } diff --git a/src/ErrorDocument.php b/src/ErrorDocument.php index 6b31c40..382d5a6 100644 --- a/src/ErrorDocument.php +++ b/src/ErrorDocument.php @@ -2,10 +2,23 @@ namespace JsonApiPhp\JsonApi; -final class ErrorDocument extends JsonSerializableValue +use JsonApiPhp\JsonApi\Internal\ErrorDocumentMember; + +final class ErrorDocument implements \JsonSerializable { + private $doc; + public function __construct(Error $error, ErrorDocumentMember ...$members) { - parent::__construct(combine($error, ...$members)); + $this->doc = (object) []; + $error->attachTo($this->doc); + foreach ($members as $member) { + $member->attachTo($this->doc); + } + } + + public function jsonSerialize() + { + return $this->doc; } } diff --git a/src/ErrorDocumentMember.php b/src/ErrorDocumentMember.php deleted file mode 100644 index 4db5ec0..0000000 --- a/src/ErrorDocumentMember.php +++ /dev/null @@ -1,10 +0,0 @@ -ids = new IdentifierRegistry(); foreach ($resources as $resource) { - $string_id = json_encode($resource->identifier()); + $string_id = $resource->key(); if (isset($this->resources[$string_id])) { throw new \LogicException("Resource $string_id is already included"); } $this->resources[$string_id] = $resource; + $resource->registerIn($this->ids); + } + } + + public function validateLinkage(PrimaryData $data): void + { + $dataRegistry = new IdentifierRegistry(); + $data->registerIn($dataRegistry); + foreach ($this->resources as $resource) { + if ($dataRegistry->has($resource->key()) || $this->ids->has($resource->key())) { + continue; + } + throw new \LogicException('Full linkage required for '.$resource->key()); } - parent::__construct('included', $resources); } - public function getIterator() + public function attachTo(object $o) { - return new \ArrayIterator($this->resources); + foreach ($this->resources as $resource) { + $resource->attachAsIncludedTo($o); + } } } diff --git a/src/Attachable.php b/src/Internal/Attachable.php similarity index 75% rename from src/Attachable.php rename to src/Internal/Attachable.php index ac3f6bf..fbbe24c 100644 --- a/src/Attachable.php +++ b/src/Internal/Attachable.php @@ -1,6 +1,6 @@ ids[$id] = true; + } + + public function has(string $id): bool + { + return isset($this->ids[$id]); + } + + public function merge(IdentifierRegistry $registry) + { + $this->ids = array_merge($this->ids, $registry->ids); + } +} diff --git a/src/Internal/IdentityTrait.php b/src/Internal/IdentityTrait.php new file mode 100644 index 0000000..b46ecd4 --- /dev/null +++ b/src/Internal/IdentityTrait.php @@ -0,0 +1,29 @@ +type}:{$this->id}"; + } +} diff --git a/src/Internal/LinkTrait.php b/src/Internal/LinkTrait.php new file mode 100644 index 0000000..9d957bb --- /dev/null +++ b/src/Internal/LinkTrait.php @@ -0,0 +1,29 @@ +link = (object) ['href' => $url]; + foreach ($metas as $meta) { + $meta->attachTo($this->link); + } + } else { + $this->link = $url; + } + } +} diff --git a/src/Internal/MetaDocumentMember.php b/src/Internal/MetaDocumentMember.php new file mode 100644 index 0000000..daff35c --- /dev/null +++ b/src/Internal/MetaDocumentMember.php @@ -0,0 +1,10 @@ +name; + } +} diff --git a/src/Internal/ResourceMember.php b/src/Internal/ResourceMember.php new file mode 100644 index 0000000..b20e3b0 --- /dev/null +++ b/src/Internal/ResourceMember.php @@ -0,0 +1,10 @@ +jsonapi = (object) [ 'version' => $version, ]; if ($meta) { - $meta->attachTo($jsonapi); + $meta->attachTo($this->jsonapi); } - parent::__construct('jsonapi', $jsonapi); + } + + public function attachTo(object $o) + { + $o->jsonapi = $this->jsonapi; } } diff --git a/src/JsonSerializableValue.php b/src/JsonSerializableValue.php deleted file mode 100644 index 5784c9f..0000000 --- a/src/JsonSerializableValue.php +++ /dev/null @@ -1,21 +0,0 @@ -value = $value; - } - - public function jsonSerialize() - { - return $this->value; - } -} diff --git a/src/Link/AboutLink.php b/src/Link/AboutLink.php index 036a185..3da5849 100644 --- a/src/Link/AboutLink.php +++ b/src/Link/AboutLink.php @@ -2,12 +2,16 @@ namespace JsonApiPhp\JsonApi\Link; -use JsonApiPhp\JsonApi\Error\ErrorMember; +use JsonApiPhp\JsonApi\Internal\ErrorMember; +use JsonApiPhp\JsonApi\Internal\LinkTrait; +use function JsonApiPhp\JsonApi\child; -final class AboutLink extends AttachableLink implements ErrorMember +final class AboutLink implements ErrorMember { - public function __construct(Link $link) + use LinkTrait; + + public function attachTo(object $o) { - parent::__construct('about', $link); + child($o, 'links')->about = $this->link; } } diff --git a/src/Link/AttachableLink.php b/src/Link/AttachableLink.php deleted file mode 100644 index 50491e7..0000000 --- a/src/Link/AttachableLink.php +++ /dev/null @@ -1,22 +0,0 @@ -first = $this->link; } } diff --git a/src/Link/LastLink.php b/src/Link/LastLink.php index 0bc75ff..b745f4a 100644 --- a/src/Link/LastLink.php +++ b/src/Link/LastLink.php @@ -2,14 +2,16 @@ namespace JsonApiPhp\JsonApi\Link; -use JsonApiPhp\JsonApi\DataDocumentMember; -use JsonApiPhp\JsonApi\PrimaryData\ResourceMember; -use JsonApiPhp\JsonApi\RelationshipMember; +use JsonApiPhp\JsonApi\Internal\LinkTrait; +use JsonApiPhp\JsonApi\Internal\PaginationLink; +use function JsonApiPhp\JsonApi\child; -final class LastLink extends AttachableLink implements DataDocumentMember, ResourceMember, RelationshipMember +final class LastLink implements PaginationLink { - public function __construct(Link $link) + use LinkTrait; + + public function attachTo(object $o) { - parent::__construct('last', $link); + child($o, 'links')->last = $this->link; } } diff --git a/src/Link/Link.php b/src/Link/Link.php deleted file mode 100644 index 96335f3..0000000 --- a/src/Link/Link.php +++ /dev/null @@ -1,10 +0,0 @@ - $href, - ]; - if ($meta) { - $meta->attachTo($link); - } - parent::__construct($link); - } -} diff --git a/src/Link/NextLink.php b/src/Link/NextLink.php index b40bad2..1c60fb9 100644 --- a/src/Link/NextLink.php +++ b/src/Link/NextLink.php @@ -2,14 +2,16 @@ namespace JsonApiPhp\JsonApi\Link; -use JsonApiPhp\JsonApi\DataDocumentMember; -use JsonApiPhp\JsonApi\PrimaryData\ResourceMember; -use JsonApiPhp\JsonApi\RelationshipMember; +use JsonApiPhp\JsonApi\Internal\LinkTrait; +use JsonApiPhp\JsonApi\Internal\PaginationLink; +use function JsonApiPhp\JsonApi\child; -final class NextLink extends AttachableLink implements DataDocumentMember, ResourceMember, RelationshipMember +final class NextLink implements PaginationLink { - public function __construct(Link $link) + use LinkTrait; + + public function attachTo(object $o) { - parent::__construct('next', $link); + child($o, 'links')->next = $this->link; } } diff --git a/src/Link/PrevLink.php b/src/Link/PrevLink.php index e87eef5..bbc0b6b 100644 --- a/src/Link/PrevLink.php +++ b/src/Link/PrevLink.php @@ -2,14 +2,16 @@ namespace JsonApiPhp\JsonApi\Link; -use JsonApiPhp\JsonApi\DataDocumentMember; -use JsonApiPhp\JsonApi\PrimaryData\ResourceMember; -use JsonApiPhp\JsonApi\RelationshipMember; +use JsonApiPhp\JsonApi\Internal\LinkTrait; +use JsonApiPhp\JsonApi\Internal\PaginationLink; +use function JsonApiPhp\JsonApi\child; -final class PrevLink extends AttachableLink implements DataDocumentMember, ResourceMember, RelationshipMember +final class PrevLink implements PaginationLink { - public function __construct(Link $link) + use LinkTrait; + + public function attachTo(object $o) { - parent::__construct('prev', $link); + child($o, 'links')->prev = $this->link; } } diff --git a/src/Link/RelatedLink.php b/src/Link/RelatedLink.php index 9990acb..ca80378 100644 --- a/src/Link/RelatedLink.php +++ b/src/Link/RelatedLink.php @@ -2,12 +2,16 @@ namespace JsonApiPhp\JsonApi\Link; -use JsonApiPhp\JsonApi\RelationshipMember; +use JsonApiPhp\JsonApi\Internal\LinkTrait; +use JsonApiPhp\JsonApi\Internal\RelationshipMember; +use function JsonApiPhp\JsonApi\child; -final class RelatedLink extends AttachableLink implements RelationshipMember +final class RelatedLink implements RelationshipMember { - public function __construct(Link $link) + use LinkTrait; + + public function attachTo(object $o) { - parent::__construct('related', $link); + child($o, 'links')->related = $this->link; } } diff --git a/src/Link/SelfLink.php b/src/Link/SelfLink.php index 55d8002..3817dd3 100644 --- a/src/Link/SelfLink.php +++ b/src/Link/SelfLink.php @@ -2,14 +2,18 @@ namespace JsonApiPhp\JsonApi\Link; -use JsonApiPhp\JsonApi\DataDocumentMember; -use JsonApiPhp\JsonApi\PrimaryData\ResourceMember; -use JsonApiPhp\JsonApi\RelationshipMember; +use JsonApiPhp\JsonApi\Internal\DataDocumentMember; +use JsonApiPhp\JsonApi\Internal\LinkTrait; +use JsonApiPhp\JsonApi\Internal\RelationshipMember; +use JsonApiPhp\JsonApi\Internal\ResourceMember; +use function JsonApiPhp\JsonApi\child; -final class SelfLink extends AttachableLink implements DataDocumentMember, ResourceMember, RelationshipMember +final class SelfLink implements DataDocumentMember, ResourceMember, RelationshipMember { - public function __construct(Link $link) + use LinkTrait; + + public function attachTo(object $o): void { - parent::__construct('self', $link); + child($o, 'links')->self = $this->link; } } diff --git a/src/Link/Url.php b/src/Link/Url.php deleted file mode 100644 index 44c400c..0000000 --- a/src/Link/Url.php +++ /dev/null @@ -1,13 +0,0 @@ -key = $key; + $this->value = $value; } public function attachTo(object $o) { - parent::attachTo(child($o, 'meta')); + child($o, 'meta')->{$this->key} = $this->value; } } diff --git a/src/MetaDocument.php b/src/MetaDocument.php index 10210fa..72ca320 100644 --- a/src/MetaDocument.php +++ b/src/MetaDocument.php @@ -2,10 +2,19 @@ namespace JsonApiPhp\JsonApi; -final class MetaDocument extends JsonSerializableValue +use JsonApiPhp\JsonApi\Internal\MetaDocumentMember; + +final class MetaDocument implements \JsonSerializable { - public function __construct(Meta $meta, TopLevelDocumentMember ...$members) + private $doc; + + public function __construct(Meta $meta, MetaDocumentMember ...$members) + { + $this->doc = combine($meta, ...$members); + } + + public function jsonSerialize() { - parent::__construct(combine($meta, ...$members)); + return $this->doc; } } diff --git a/src/MultiLinkage.php b/src/MultiLinkage.php deleted file mode 100644 index b1f3ccb..0000000 --- a/src/MultiLinkage.php +++ /dev/null @@ -1,29 +0,0 @@ -identifiers = $identifiers; - } - - public function identifies(ResourceObject $resource): bool - { - foreach ($this->identifiers as $identifier) { - if ($identifier->identifies($resource)) { - return true; - } - } - return false; - } -} diff --git a/src/NullData.php b/src/NullData.php index 7d39ca6..768102d 100644 --- a/src/NullData.php +++ b/src/NullData.php @@ -2,17 +2,17 @@ namespace JsonApiPhp\JsonApi; -use JsonApiPhp\JsonApi\PrimaryData\PrimaryData; +use JsonApiPhp\JsonApi\Internal\IdentifierRegistry; +use JsonApiPhp\JsonApi\Internal\PrimaryData; -final class NullData extends AttachableValue implements PrimaryData +final class NullData implements PrimaryData { - public function __construct() + public function attachTo(object $o) { - parent::__construct('data', null); + $o->data = null; } - public function identifies(ResourceObject $resource): bool + public function registerIn(IdentifierRegistry $registry) { - return false; } } diff --git a/src/PaginatedResourceCollection.php b/src/PaginatedResourceCollection.php new file mode 100644 index 0000000..cb2a633 --- /dev/null +++ b/src/PaginatedResourceCollection.php @@ -0,0 +1,24 @@ +pagination = $pagination; + } + + public function attachTo(object $o): void + { + parent::attachTo($o); + $this->pagination->attachTo($o); + } +} diff --git a/src/PaginatedResourceIdentifierCollection.php b/src/PaginatedResourceIdentifierCollection.php new file mode 100644 index 0000000..ea4e432 --- /dev/null +++ b/src/PaginatedResourceIdentifierCollection.php @@ -0,0 +1,24 @@ +pagination = $pagination; + } + + public function attachTo(object $o) + { + parent::attachTo($o); + $this->pagination->attachTo($o); + } +} diff --git a/src/Pagination.php b/src/Pagination.php new file mode 100644 index 0000000..ba8ab41 --- /dev/null +++ b/src/Pagination.php @@ -0,0 +1,27 @@ +links = $links; + } + + public function attachTo(object $o) + { + foreach ($this->links as $link) { + $link->attachTo($o); + } + } +} diff --git a/src/PrimaryData/Identifier.php b/src/PrimaryData/Identifier.php deleted file mode 100644 index d54565b..0000000 --- a/src/PrimaryData/Identifier.php +++ /dev/null @@ -1,13 +0,0 @@ -key = $key; - } - - public function key(): string - { - return $this->key; - } -} diff --git a/src/PrimaryData/ResourceMember.php b/src/PrimaryData/ResourceMember.php deleted file mode 100644 index 0a88dff..0000000 --- a/src/PrimaryData/ResourceMember.php +++ /dev/null @@ -1,12 +0,0 @@ -identifiers[] = $m; - } - } - } - - public function attachTo(object $o) - { - parent::attachTo(child($o, 'relationships')); - } - - public function identifies(ResourceObject $resource): bool - { - foreach ($this->identifiers as $identifier) { - if ($identifier->identifies($resource)) { - return true; - } - } - return false; - } -} diff --git a/src/RelationshipMember.php b/src/RelationshipMember.php deleted file mode 100644 index 8360f92..0000000 --- a/src/RelationshipMember.php +++ /dev/null @@ -1,10 +0,0 @@ -resources = $resources; + } + + public function attachTo(object $o) + { + $o->data = []; + foreach ($this->resources as $resource) { + $resource->attachToCollection($o); + } + } + + public function registerIn(IdentifierRegistry $registry) + { + foreach ($this->resources as $resource) { + $resource->registerIn($registry); + } + } +} diff --git a/src/ResourceIdentifier.php b/src/ResourceIdentifier.php index fc2bddd..7d170b0 100644 --- a/src/ResourceIdentifier.php +++ b/src/ResourceIdentifier.php @@ -2,34 +2,44 @@ namespace JsonApiPhp\JsonApi; -use JsonApiPhp\JsonApi\PrimaryData\PrimaryData; +use JsonApiPhp\JsonApi\Internal\IdentifierRegistry; +use JsonApiPhp\JsonApi\Internal\IdentityTrait; +use JsonApiPhp\JsonApi\Internal\PrimaryData; -final class ResourceIdentifier extends AttachableValue implements PrimaryData +final class ResourceIdentifier implements PrimaryData { - private $type; - private $id; + use IdentityTrait; + private $obj; public function __construct(string $type, string $id, Meta $meta = null) { - $identifier = (object) [ + if (isValidName($type) === false) { + throw new \DomainException("Invalid type value: $type"); + } + + $this->obj = (object) [ 'type' => $type, 'id' => $id, ]; if ($meta) { - $meta->attachTo($identifier); + $meta->attachTo($this->obj); } - parent::__construct('data', $identifier); $this->type = $type; $this->id = $id; } - public function identifies(ResourceObject $resource): bool + public function attachTo(object $o) + { + $o->data = $this->obj; + } + + public function attachToCollection(object $o): void { - return $resource->identifier()->equals($this); + $o->data[] = $this->obj; } - public function equals(ResourceIdentifier $that): bool + public function registerIn(IdentifierRegistry $registry) { - return $this->type === $that->type && $this->id === $that->id; + $registry->add($this->key()); } } diff --git a/src/ResourceIdentifierCollection.php b/src/ResourceIdentifierCollection.php new file mode 100644 index 0000000..1786c1d --- /dev/null +++ b/src/ResourceIdentifierCollection.php @@ -0,0 +1,34 @@ +identifiers = $identifiers; + } + + public function attachTo(object $o) + { + $o->data = []; + foreach ($this->identifiers as $identifier) { + $identifier->attachToCollection($o); + } + } + + public function registerIn(IdentifierRegistry $registry) + { + foreach ($this->identifiers as $identifier) { + $identifier->registerIn($registry); + } + } +} diff --git a/src/ResourceIdentifierSet.php b/src/ResourceIdentifierSet.php deleted file mode 100644 index e63efc0..0000000 --- a/src/ResourceIdentifierSet.php +++ /dev/null @@ -1,29 +0,0 @@ -identifiers = $identifiers; - } - - public function identifies(ResourceObject $resource): bool - { - foreach ($this->identifiers as $identifier) { - if ($identifier->identifies($resource)) { - return true; - } - } - return false; - } -} diff --git a/src/ResourceObject.php b/src/ResourceObject.php index b7e2cb8..02701d2 100644 --- a/src/ResourceObject.php +++ b/src/ResourceObject.php @@ -2,63 +2,66 @@ namespace JsonApiPhp\JsonApi; -use JsonApiPhp\JsonApi\PrimaryData\Identifier; -use JsonApiPhp\JsonApi\PrimaryData\PrimaryData; -use JsonApiPhp\JsonApi\PrimaryData\ResourceField; -use JsonApiPhp\JsonApi\PrimaryData\ResourceMember; +use JsonApiPhp\JsonApi\Internal\Identifier; +use JsonApiPhp\JsonApi\Internal\IdentifierRegistry; +use JsonApiPhp\JsonApi\Internal\IdentityTrait; +use JsonApiPhp\JsonApi\Internal\PrimaryData; +use JsonApiPhp\JsonApi\Internal\ResourceField; +use JsonApiPhp\JsonApi\Internal\ResourceMember; -final class ResourceObject extends AttachableValue implements PrimaryData +final class ResourceObject implements PrimaryData { - private $type; - private $id; + use IdentityTrait; + private $obj; + private $registry; - /** - * @var Identifier[] - */ - private $identifiers = []; - - public function __construct(string $type, string $id = null, ResourceMember ...$members) + public function __construct(string $type, string $id, ResourceMember ...$members) { - $this->checkUniqueness(...$members); - $obj = combine(...$members); - $obj->type = $type; - $obj->id = $id; - parent::__construct('data', $obj); + if (isValidName($type) === false) { + throw new \DomainException("Invalid type value: $type"); + } $this->type = $type; $this->id = $id; + $this->registry = new IdentifierRegistry(); + $this->obj = (object) ['type' => $type, 'id' => $id]; + $fields = []; foreach ($members as $member) { if ($member instanceof Identifier) { - $this->identifiers[] = $member; + $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); } } - public function identifier(): ResourceIdentifier + public function toIdentifier(): ResourceIdentifier { return new ResourceIdentifier($this->type, $this->id); } - public function identifies(ResourceObject $resource): bool + public function registerIn(IdentifierRegistry $registry) { - foreach ($this->identifiers as $identifier) { - if ($identifier->identifies($resource)) { - return true; - } - } - return false; + $registry->merge($this->registry); } - private function checkUniqueness(ResourceMember ...$members): void + public function attachTo(object $o) { - $keys = []; - foreach ($members as $member) { - if ($member instanceof ResourceField) { - $key = $member->key(); - if (isset($keys[$key])) { - throw new \LogicException("Field '$key' already exists'"); - } - $keys[$key] = true; - } - } + $o->data = $this->obj; + } + + public function attachAsIncludedTo(object $o): void + { + $o->included[] = $this->obj; + } + + public function attachToCollection(object $o): void + { + $o->data[] = $this->obj; } } diff --git a/src/ResourceObjectSet.php b/src/ResourceObjectSet.php deleted file mode 100644 index 2eb37c5..0000000 --- a/src/ResourceObjectSet.php +++ /dev/null @@ -1,29 +0,0 @@ -resources = $resources; - parent::__construct('data', $this->resources); - } - - public function identifies(ResourceObject $resource): bool - { - foreach ($this->resources as $myResource) { - if ($myResource->identifies($resource)) { - return true; - } - } - return false; - } -} diff --git a/src/SingleLinkage.php b/src/SingleLinkage.php deleted file mode 100644 index e53b74a..0000000 --- a/src/SingleLinkage.php +++ /dev/null @@ -1,21 +0,0 @@ -identifier = $identifier; - } - - public function identifies(ResourceObject $resource): bool - { - return $this->identifier && $this->identifier->identifies($resource); - } -} diff --git a/src/ToMany.php b/src/ToMany.php new file mode 100644 index 0000000..d8b2ba3 --- /dev/null +++ b/src/ToMany.php @@ -0,0 +1,45 @@ +validateFieldName($name); + $this->name = $name; + $this->members = $members; + $this->collection = $collection; + } + + public function attachTo(object $o) + { + $rel = child(child($o, 'relationships'), $this->name); + $rel->data = []; + $this->collection->attachTo($rel); + foreach ($this->members as $member) { + $member->attachTo($rel); + } + } + + public function registerIn(IdentifierRegistry $registry) + { + $this->collection->registerIn($registry); + } +} diff --git a/src/ToNull.php b/src/ToNull.php new file mode 100644 index 0000000..c163d67 --- /dev/null +++ b/src/ToNull.php @@ -0,0 +1,30 @@ +validateFieldName($name); + $this->name = $name; + $this->members = $members; + } + + public function attachTo(object $o) + { + $obj = combine(...$this->members); + $obj->data = null; + child($o, 'relationships')->{$this->name} = $obj; + } +} diff --git a/src/ToOne.php b/src/ToOne.php new file mode 100644 index 0000000..b696710 --- /dev/null +++ b/src/ToOne.php @@ -0,0 +1,39 @@ +validateFieldName($name); + $this->name = $name; + $this->obj = combine($identifier, ...$members); + $this->identifier = $identifier; + } + + public function attachTo(object $o) + { + child($o, 'relationships')->{$this->name} = $this->obj; + } + + public function registerIn(IdentifierRegistry $registry) + { + $this->identifier->registerIn($registry); + } +} diff --git a/src/TopLevelDocumentMember.php b/src/TopLevelDocumentMember.php deleted file mode 100644 index 57c8210..0000000 --- a/src/TopLevelDocumentMember.php +++ /dev/null @@ -1,10 +0,0 @@ -attachTo($obj); + foreach ($members as $member) { + $member->attachTo($obj); } return $obj; } function child(object $o, string $name): object { - if (empty($o->{$name})) { + if (!isset($o->{$name})) { $o->{$name} = (object) []; } return $o->{$name}; diff --git a/test/CompoundDocumentTest.php b/test/CompoundDocumentTest.php index 258fa6e..01ede9c 100644 --- a/test/CompoundDocumentTest.php +++ b/test/CompoundDocumentTest.php @@ -9,15 +9,15 @@ use JsonApiPhp\JsonApi\Link\NextLink; use JsonApiPhp\JsonApi\Link\RelatedLink; use JsonApiPhp\JsonApi\Link\SelfLink; -use JsonApiPhp\JsonApi\Link\Url; -use JsonApiPhp\JsonApi\MultiLinkage; use JsonApiPhp\JsonApi\NullData; -use JsonApiPhp\JsonApi\Relationship; +use JsonApiPhp\JsonApi\PaginatedResourceCollection; +use JsonApiPhp\JsonApi\Pagination; +use JsonApiPhp\JsonApi\ResourceCollection; use JsonApiPhp\JsonApi\ResourceIdentifier; -use JsonApiPhp\JsonApi\ResourceIdentifierSet; +use JsonApiPhp\JsonApi\ResourceIdentifierCollection; use JsonApiPhp\JsonApi\ResourceObject; -use JsonApiPhp\JsonApi\ResourceObjectSet; -use JsonApiPhp\JsonApi\SingleLinkage; +use JsonApiPhp\JsonApi\ToMany; +use JsonApiPhp\JsonApi\ToOne; class CompoundDocumentTest extends BaseTestCase { @@ -29,53 +29,55 @@ public function testOfficialDocsExample() new Attribute('first-name', 'Dan'), new Attribute('last-name', 'Gebhardt'), new Attribute('twitter', 'dgeb'), - new SelfLink(new Url('http://example.com/people/9')) + new SelfLink('http://example.com/people/9') ); $comment05 = new ResourceObject( 'comments', '5', new Attribute('body', 'First!'), - new SelfLink(new Url('http://example.com/comments/5')), - new Relationship('author', new SingleLinkage(new ResourceIdentifier('people', '2'))) + new SelfLink('http://example.com/comments/5'), + new ToOne('author', new ResourceIdentifier('people', '2')) ); $comment12 = new ResourceObject( 'comments', '12', new Attribute('body', 'I like XML better'), - new SelfLink(new Url('http://example.com/comments/12')), - new Relationship('author', new SingleLinkage($dan->identifier())) + new SelfLink('http://example.com/comments/12'), + new ToOne('author', $dan->toIdentifier()) ); $document = new CompoundDocument( - new ResourceObjectSet( + new PaginatedResourceCollection( + new Pagination( + new NextLink('http://example.com/articles?page[offset]=2'), + new LastLink('http://example.com/articles?page[offset]=10') + ), new ResourceObject( 'articles', '1', new Attribute('title', 'JSON API paints my bikeshed!'), - new SelfLink(new Url('http://example.com/articles/1')), - new Relationship( + new SelfLink('http://example.com/articles/1'), + new ToOne( 'author', - new SingleLinkage($dan->identifier()), - new SelfLink(new Url('http://example.com/articles/1/relationships/author')), - new RelatedLink(new Url('http://example.com/articles/1/author')) + $dan->toIdentifier(), + new SelfLink('http://example.com/articles/1/relationships/author'), + new RelatedLink('http://example.com/articles/1/author') ), - new Relationship( + new ToMany( 'comments', - new MultiLinkage( - $comment05->identifier(), - $comment12->identifier() + new ResourceIdentifierCollection( + $comment05->toIdentifier(), + $comment12->toIdentifier() ), - new SelfLink(new Url('http://example.com/articles/1/relationships/comments')), - new RelatedLink(new Url('http://example.com/articles/1/comments')) + new SelfLink('http://example.com/articles/1/relationships/comments'), + new RelatedLink('http://example.com/articles/1/comments') ) ) ), new Included($dan, $comment05, $comment12), - new SelfLink(new Url('http://example.com/articles')), - new NextLink(new Url('http://example.com/articles?page[offset]=2')), - new LastLink(new Url('http://example.com/articles?page[offset]=10')) + new SelfLink('http://example.com/articles') ); $this->assertEncodesTo( ' @@ -171,8 +173,8 @@ public function testOfficialDocsExample() */ public function testFullLinkage(callable $create_doc) { - $this->expectException(\DomainException::class); - $this->expectExceptionMessage('Full linkage required for {"type":"apples","id":"1"}'); + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('Full linkage required for apples:1'); $create_doc(); } @@ -187,7 +189,7 @@ function () use ($included) { ], [ function () use ($included) { - return new CompoundDocument(new ResourceObjectSet(), $included); + return new CompoundDocument(new ResourceCollection(), $included); }, ], [ @@ -198,7 +200,10 @@ function () use ($included) { [ function () use ($included) { return new CompoundDocument( - new ResourceIdentifierSet(new ResourceIdentifier('oranges', '1'), new ResourceIdentifier('oranges', '1')), + new ResourceIdentifierCollection( + new ResourceIdentifier('oranges', '1'), + new ResourceIdentifier('oranges', '1') + ), $included ); }, @@ -206,7 +211,7 @@ function () use ($included) { [ function () use ($included) { return new CompoundDocument( - new ResourceObjectSet(new ResourceObject('oranges', '1'), new ResourceObject('oranges', '1')), + new ResourceCollection(new ResourceObject('oranges', '1'), new ResourceObject('oranges', '1')), $included ); }, @@ -220,7 +225,7 @@ public function testIncludedResourceMayBeIdentifiedByLinkageInPrimaryData() $article = new ResourceObject( 'articles', '1', - new Relationship('author', new SingleLinkage($author->identifier())) + new ToOne('author', $author->toIdentifier()) ); $doc = new CompoundDocument($article, new Included($author)); $this->assertNotEmpty($doc); @@ -233,12 +238,12 @@ public function testIncludedResourceMayBeIdentifiedByAnotherLinkedResource() 'books', '2', new Attribute('name', 'Domain Driven Design'), - new Relationship('author', new SingleLinkage($writer->identifier())) + new ToOne('author', $writer->toIdentifier()) ); $cart = new ResourceObject( 'shopping-carts', '1', - new Relationship('contents', new MultiLinkage($book->identifier())) + new ToMany('contents', new ResourceIdentifierCollection($book->toIdentifier())) ); $doc = new CompoundDocument($cart, new Included($book, $writer)); $this->assertNotEmpty($doc); @@ -247,11 +252,11 @@ public function testIncludedResourceMayBeIdentifiedByAnotherLinkedResource() /** * A compound document MUST NOT include more than one resource object for each type and id pair. * @expectedException \LogicException - * @expectedExceptionMessage Resource {"type":"apples","id":"1"} is already included + * @expectedExceptionMessage Resource apples:1 is already included */ public function testCanNotBeManyIncludedResourcesWithEqualIdentifiers() { $apple = new ResourceObject('apples', '1'); - new CompoundDocument($apple->identifier(), new Included($apple, $apple)); + new CompoundDocument($apple->toIdentifier(), new Included($apple, $apple)); } } diff --git a/test/DataDocument/ManyResourceIdentifiersTest.php b/test/DataDocument/ManyResourceIdentifiersTest.php index 6aaa5bf..eb12973 100644 --- a/test/DataDocument/ManyResourceIdentifiersTest.php +++ b/test/DataDocument/ManyResourceIdentifiersTest.php @@ -5,10 +5,9 @@ use JsonApiPhp\JsonApi\DataDocument; use JsonApiPhp\JsonApi\JsonApi; use JsonApiPhp\JsonApi\Link\SelfLink; -use JsonApiPhp\JsonApi\Link\Url; use JsonApiPhp\JsonApi\Meta; use JsonApiPhp\JsonApi\ResourceIdentifier; -use JsonApiPhp\JsonApi\ResourceIdentifierSet; +use JsonApiPhp\JsonApi\ResourceIdentifierCollection; use JsonApiPhp\JsonApi\Test\BaseTestCase; class ManyResourceIdentifiersTest extends BaseTestCase @@ -22,7 +21,7 @@ public function testMinimalDocument() } ', new DataDocument( - new ResourceIdentifierSet() + new ResourceIdentifierCollection() ) ); } @@ -51,7 +50,7 @@ public function testExtendedDocument() } ', new DataDocument( - new ResourceIdentifierSet( + new ResourceIdentifierCollection( new ResourceIdentifier( 'apples', '1', @@ -63,7 +62,7 @@ public function testExtendedDocument() new Meta('apple_meta', 'foo') ) ), - new SelfLink(new Url('/apples')), + new SelfLink('/apples'), new JsonApi(), new Meta('document_meta', 'bar') ) diff --git a/test/DataDocument/ManyResourceObjectsTest.php b/test/DataDocument/ManyResourceObjectsTest.php index ef3da2c..3a2be82 100644 --- a/test/DataDocument/ManyResourceObjectsTest.php +++ b/test/DataDocument/ManyResourceObjectsTest.php @@ -6,10 +6,9 @@ use JsonApiPhp\JsonApi\DataDocument; use JsonApiPhp\JsonApi\JsonApi; use JsonApiPhp\JsonApi\Link\SelfLink; -use JsonApiPhp\JsonApi\Link\Url; use JsonApiPhp\JsonApi\Meta; +use JsonApiPhp\JsonApi\ResourceCollection; use JsonApiPhp\JsonApi\ResourceObject; -use JsonApiPhp\JsonApi\ResourceObjectSet; use JsonApiPhp\JsonApi\Test\BaseTestCase; class ManyResourceObjectsTest extends BaseTestCase @@ -23,7 +22,7 @@ public function testMinimalDocument() } ', new DataDocument( - new ResourceObjectSet() + new ResourceCollection() ) ); } @@ -60,7 +59,7 @@ public function testExtendedDocument() } ', new DataDocument( - new ResourceObjectSet( + new ResourceCollection( new ResourceObject( 'apples', '1', @@ -76,7 +75,7 @@ public function testExtendedDocument() new Meta('apple_meta', 'foo') ) ), - new SelfLink(new Url('/apples')), + new SelfLink('/apples'), new JsonApi(), new Meta('document_meta', 'bar') ) diff --git a/test/DataDocument/NullDataTest.php b/test/DataDocument/NullDataTest.php index 9d436e7..05746cd 100644 --- a/test/DataDocument/NullDataTest.php +++ b/test/DataDocument/NullDataTest.php @@ -6,7 +6,6 @@ use JsonApiPhp\JsonApi\DataDocument; use JsonApiPhp\JsonApi\JsonApi; use JsonApiPhp\JsonApi\Link\SelfLink; -use JsonApiPhp\JsonApi\Link\Url; use JsonApiPhp\JsonApi\Meta; use JsonApiPhp\JsonApi\NullData; use JsonApiPhp\JsonApi\Test\BaseTestCase; @@ -44,7 +43,7 @@ public function testExtendedDocument() ', new DataDocument( new NullData(), - new SelfLink(new Url('/apples/1')), + new SelfLink('/apples/1'), new JsonApi(), new Meta('document_meta', 'bar') ) diff --git a/test/DataDocument/SingleResourceIdentifierTest.php b/test/DataDocument/SingleResourceIdentifierTest.php index becb005..92a91d5 100644 --- a/test/DataDocument/SingleResourceIdentifierTest.php +++ b/test/DataDocument/SingleResourceIdentifierTest.php @@ -5,7 +5,6 @@ use JsonApiPhp\JsonApi\DataDocument; use JsonApiPhp\JsonApi\JsonApi; use JsonApiPhp\JsonApi\Link\SelfLink; -use JsonApiPhp\JsonApi\Link\Url; use JsonApiPhp\JsonApi\Meta; use JsonApiPhp\JsonApi\ResourceIdentifier; use JsonApiPhp\JsonApi\Test\BaseTestCase; @@ -54,7 +53,7 @@ public function testExtendedDocument() '1', new Meta('apple_meta', 'foo') ), - new SelfLink(new Url('/apples/1')), + new SelfLink('/apples/1'), new JsonApi(), new Meta('document_meta', 'bar') ) diff --git a/test/DataDocument/SingleResourceObjectTest.php b/test/DataDocument/SingleResourceObjectTest.php index 5d9af4b..dfa25a1 100644 --- a/test/DataDocument/SingleResourceObjectTest.php +++ b/test/DataDocument/SingleResourceObjectTest.php @@ -6,7 +6,6 @@ use JsonApiPhp\JsonApi\DataDocument; use JsonApiPhp\JsonApi\JsonApi; use JsonApiPhp\JsonApi\Link\SelfLink; -use JsonApiPhp\JsonApi\Link\Url; use JsonApiPhp\JsonApi\Meta; use JsonApiPhp\JsonApi\ResourceObject; use JsonApiPhp\JsonApi\Test\BaseTestCase; @@ -61,7 +60,7 @@ public function testExtendedDocument() new Attribute('sort', 'Fuji'), new Meta('apple_meta', 'foo') ), - new SelfLink(new Url('/apples/1')), + new SelfLink('/apples/1'), new JsonApi(), new Meta('document_meta', 'bar') ) diff --git a/test/ErrorDocumentTest.php b/test/ErrorDocumentTest.php index 5ed715a..9a9131d 100644 --- a/test/ErrorDocumentTest.php +++ b/test/ErrorDocumentTest.php @@ -7,14 +7,13 @@ use JsonApiPhp\JsonApi\Error\Code; use JsonApiPhp\JsonApi\Error\Detail; use JsonApiPhp\JsonApi\Error\Id; -use JsonApiPhp\JsonApi\Error\Parameter; -use JsonApiPhp\JsonApi\Error\Pointer; +use JsonApiPhp\JsonApi\Error\SourceParameter; +use JsonApiPhp\JsonApi\Error\SourcePointer; use JsonApiPhp\JsonApi\Error\Status; use JsonApiPhp\JsonApi\Error\Title; use JsonApiPhp\JsonApi\ErrorDocument; use JsonApiPhp\JsonApi\JsonApi; use JsonApiPhp\JsonApi\Link\AboutLink; -use JsonApiPhp\JsonApi\Link\Url; use JsonApiPhp\JsonApi\Meta; class ErrorDocumentTest extends BaseTestCase @@ -68,15 +67,13 @@ public function testExtensiveExample() new ErrorDocument( new Error( new Id('1'), - new AboutLink( - new Url('/errors/not_found') - ), + new AboutLink('/errors/not_found'), new Status('404'), new Code('not_found'), new Title('Resource not found'), new Detail('We tried hard but could not find anything'), - new Pointer('/data'), - new Parameter('query_string'), + new SourcePointer('/data'), + new SourceParameter('query_string'), new Meta('purpose', 'test') ), new Meta('purpose', 'test'), @@ -109,12 +106,12 @@ public function testMultipleErrors() new Error( new Id('1'), new Code('invalid_parameter'), - new Parameter('foo') + new SourceParameter('foo') ), new Error( new Id('2'), new Code('invalid_parameter'), - new Parameter('bar') + new SourceParameter('bar') ) ) ); diff --git a/test/JsonApiTest.php b/test/JsonApiTest.php index 99731b0..49c5982 100644 --- a/test/JsonApiTest.php +++ b/test/JsonApiTest.php @@ -2,9 +2,9 @@ namespace JsonApiPhp\JsonApi\Test; -use JsonApiPhp\JsonApi\Document\JsonApi\Version; use JsonApiPhp\JsonApi\JsonApi; use JsonApiPhp\JsonApi\Meta; +use JsonApiPhp\JsonApi\MetaDocument; class JsonApiTest extends BaseTestCase { @@ -13,13 +13,21 @@ public function testJsonApiMayContainVersionAndMeta() $this->assertEncodesTo( ' { - "version": "1.0", "meta": { - "foo": "bar" + "test": "test" + }, + "jsonapi": { + "version": "1.0", + "meta": { + "foo": "bar" + } } } ', - new JsonApi('1.0', new Meta('foo', 'bar')) + new MetaDocument( + new Meta('test', 'test'), + new JsonApi('1.0', new Meta('foo', 'bar')) + ) ); } } diff --git a/test/LinkObjectTest.php b/test/LinkObjectTest.php index 6df8fff..a69443c 100644 --- a/test/LinkObjectTest.php +++ b/test/LinkObjectTest.php @@ -3,7 +3,6 @@ namespace JsonApiPhp\JsonApi\Test; use JsonApiPhp\JsonApi\DataDocument; -use JsonApiPhp\JsonApi\Link\LinkObject; use JsonApiPhp\JsonApi\Link\SelfLink; use JsonApiPhp\JsonApi\Meta; use JsonApiPhp\JsonApi\ResourceIdentifier; @@ -19,7 +18,8 @@ public function testLinkObject() "self": { "href": "http://example.com", "meta": { - "foo": "bar" + "foo": "bar", + "test": true } } } @@ -27,7 +27,7 @@ public function testLinkObject() ', new DataDocument( new ResourceIdentifier('apples', '1'), - new SelfLink(new LinkObject('http://example.com', new Meta('foo', 'bar'))) + new SelfLink('http://example.com', new Meta('foo', 'bar'), new Meta('test', true)) ) ); } diff --git a/test/MetaTest.php b/test/MetaTest.php index 8ea6b1b..67d3097 100644 --- a/test/MetaTest.php +++ b/test/MetaTest.php @@ -10,7 +10,7 @@ class MetaTest extends TestCase public function testKeyMustBeValid() { $this->expectException(\DomainException::class); - $this->expectExceptionMessage("Invalid character in a member name 'this+name&is'"); - new Meta('this+name&is', '1'); + $this->expectExceptionMessage("Invalid character in a member name 'invalid:name'"); + new Meta('invalid:name', '1'); } } diff --git a/test/PaginationLinksTest.php b/test/PaginationLinksTest.php deleted file mode 100644 index f1774d6..0000000 --- a/test/PaginationLinksTest.php +++ /dev/null @@ -1,45 +0,0 @@ -assertEncodesTo( - ' - { - "data": [ - {"type": "apples", "id": "1"}, - {"type": "apples", "id": "2"} - ], - "links": { - "first": "http://example.com/fruits?page=first", - "last": "http://example.com/fruits?page=last", - "prev": "http://example.com/fruits?page=3", - "next": "http://example.com/fruits?page=5" - } - } - ', - new DataDocument( - new ResourceObjectSet( - new ResourceObject('apples', '1'), - new ResourceObject('apples', '2') - ), - new FirstLink(new Url('http://example.com/fruits?page=first')), - new LastLink(new Url('http://example.com/fruits?page=last')), - new PrevLink(new Url('http://example.com/fruits?page=3')), - new NextLink(new Url('http://example.com/fruits?page=5')) - ) - ); - } -} diff --git a/test/PaginationTest.php b/test/PaginationTest.php new file mode 100644 index 0000000..e00ab53 --- /dev/null +++ b/test/PaginationTest.php @@ -0,0 +1,108 @@ +assertEncodesTo( + ' + { + "data": [ + {"type": "apples", "id": "1"}, + {"type": "apples", "id": "2"} + ], + "links": { + "first": "http://example.com/fruits?page=first", + "last": "http://example.com/fruits?page=last", + "prev": "http://example.com/fruits?page=3", + "next": "http://example.com/fruits?page=5" + } + } + ', + new DataDocument( + new PaginatedResourceCollection( + new Pagination( + new FirstLink('http://example.com/fruits?page=first'), + new PrevLink('http://example.com/fruits?page=3'), + new NextLink('http://example.com/fruits?page=5'), + new LastLink('http://example.com/fruits?page=last') + ), + new ResourceObject('apples', '1'), + new ResourceObject('apples', '2') + ) + ) + ); + } + + public function testPaginatedResourceIdentifierCollection() + { + $this->assertEncodesTo( + ' + { + "data": { + "type": "baskets", + "id": "1", + "relationships": { + "fruits": { + "data": [ + {"type": "apples", "id": "1"}, + {"type": "apples", "id": "2"} + ], + "links": { + "first": "http://example.com/basket/1/fruits?page=first", + "last": "http://example.com/basket/1/fruits?page=last", + "prev": "http://example.com/basket/1/fruits?page=3", + "next": "http://example.com/basket/1/fruits?page=5" + } + } + } + }, + "included": [ + {"type": "apples", "id": "1", "attributes": {"color": "red"}}, + {"type": "apples", "id": "2", "attributes": {"color": "yellow"}} + ] + } + ', + new CompoundDocument( + new ResourceObject( + 'baskets', + '1', + new ToMany( + 'fruits', + new PaginatedResourceIdentifierCollection( + new Pagination( + new FirstLink('http://example.com/basket/1/fruits?page=first'), + new PrevLink('http://example.com/basket/1/fruits?page=3'), + new NextLink('http://example.com/basket/1/fruits?page=5'), + new LastLink('http://example.com/basket/1/fruits?page=last') + ), + new ResourceIdentifier('apples', '1'), + new ResourceIdentifier('apples', '2') + ) + ) + ), + new Included( + new ResourceObject('apples', '1', new Attribute('color', 'red')), + new ResourceObject('apples', '2', new Attribute('color', 'yellow')) + ) + ) + ); + } +} diff --git a/test/ResourceIdentifierTest.php b/test/ResourceIdentifierTest.php new file mode 100644 index 0000000..5dcd959 --- /dev/null +++ b/test/ResourceIdentifierTest.php @@ -0,0 +1,15 @@ +expectException(\DomainException::class); + new ResourceIdentifier('invalid:id', 'foo'); + } +} diff --git a/test/ResourceObjectTest.php b/test/ResourceObjectTest.php index 3548dcd..37e845f 100644 --- a/test/ResourceObjectTest.php +++ b/test/ResourceObjectTest.php @@ -3,15 +3,16 @@ namespace JsonApiPhp\JsonApi\Test; use JsonApiPhp\JsonApi\Attribute; +use JsonApiPhp\JsonApi\DataDocument; use JsonApiPhp\JsonApi\Link\RelatedLink; use JsonApiPhp\JsonApi\Link\SelfLink; -use JsonApiPhp\JsonApi\Link\Url; use JsonApiPhp\JsonApi\Meta; -use JsonApiPhp\JsonApi\MultiLinkage; -use JsonApiPhp\JsonApi\Relationship; use JsonApiPhp\JsonApi\ResourceIdentifier; +use JsonApiPhp\JsonApi\ResourceIdentifierCollection; use JsonApiPhp\JsonApi\ResourceObject; -use JsonApiPhp\JsonApi\SingleLinkage; +use JsonApiPhp\JsonApi\ToMany; +use JsonApiPhp\JsonApi\ToNull; +use JsonApiPhp\JsonApi\ToOne; class ResourceObjectTest extends BaseTestCase { @@ -20,39 +21,42 @@ public function testFullFledgedResourceObject() $this->assertEncodesTo( ' { - "type": "apples", - "id": "1", - "attributes": { - "title": "Rails is Omakase" - }, - "meta": {"foo": "bar"}, - "links": { - "self": "http://self" - }, - "relationships": { - "author": { - "meta": {"foo": "bar"}, - "links": { - "self": "http://rel/author", - "related": "http://author" - }, - "data": null + "data": { + "type": "apples", + "id": "1", + "attributes": { + "title": "Rails is Omakase" + }, + "meta": {"foo": "bar"}, + "links": { + "self": "http://self" + }, + "relationships": { + "author": { + "meta": {"foo": "bar"}, + "links": { + "self": "http://rel/author", + "related": "http://author" + }, + "data": null + } } } } ', - new ResourceObject( - 'apples', - '1', - new Meta('foo', 'bar'), - new Attribute('title', 'Rails is Omakase'), - new SelfLink(new Url('http://self')), - new Relationship( - 'author', + new DataDocument( + new ResourceObject( + 'apples', + '1', new Meta('foo', 'bar'), - new SelfLink(new Url('http://rel/author')), - new RelatedLink(new Url('http://author')), - new SingleLinkage() + new Attribute('title', 'Rails is Omakase'), + new SelfLink('http://self'), + new ToNull( + 'author', + new Meta('foo', 'bar'), + new SelfLink('http://rel/author'), + new RelatedLink('http://author') + ) ) ) ); @@ -64,15 +68,21 @@ public function testRelationshipWithSingleIdLinkage() ' { "data": { - "type": "apples", - "id": "1" + "type": "basket", + "id": "1", + "relationships": { + "content": { + "data": {"type": "apples", "id": "1"} + } + } } } ', - new Relationship( - 'fruits', - new SingleLinkage( - new ResourceIdentifier('apples', '1') + new DataDocument( + new ResourceObject( + 'basket', + '1', + new ToOne('content', new ResourceIdentifier('apples', '1')) ) ) ); @@ -83,20 +93,34 @@ public function testRelationshipWithMultiIdLinkage() $this->assertEncodesTo( ' { - "data": [{ - "type": "apples", - "id": "1" - },{ - "type": "pears", - "id": "2" - }] + "data": { + "type": "basket", + "id": "1", + "relationships": { + "content": { + "data": [{ + "type": "apples", + "id": "1" + },{ + "type": "pears", + "id": "2" + }] + } + } + } } ', - new Relationship( - 'fruits', - new MultiLinkage( - new ResourceIdentifier('apples', '1'), - new ResourceIdentifier('pears', '2') + new DataDocument( + new ResourceObject( + 'basket', + '1', + new ToMany( + 'content', + new ResourceIdentifierCollection( + new ResourceIdentifier('apples', '1'), + new ResourceIdentifier('pears', '2') + ) + ) ) ) ); @@ -107,12 +131,23 @@ public function testRelationshipWithEmptyMultiIdLinkage() $this->assertEncodesTo( ' { - "data": [] + "data": { + "type": "basket", + "id": "1", + "relationships": { + "content": { + "data": [] + } + } + } } ', - new Relationship( - 'fruits', - new MultiLinkage() + new DataDocument( + new ResourceObject( + 'basket', + '1', + new ToMany('content', new ResourceIdentifierCollection()) + ) ) ); } @@ -135,14 +170,14 @@ public function testCanNotCreateIdRelationship() { $this->expectException(\DomainException::class); $this->expectExceptionMessage("Can not use 'id' as a resource field"); - new Relationship('id', new SingleLinkage(new ResourceIdentifier('apples', '1'))); + new ToOne('id', new ResourceIdentifier('apples', '1')); } public function testCanNotCreateTypeRelationship() { $this->expectException(\DomainException::class); $this->expectExceptionMessage("Can not use 'type' as a resource field"); - new Relationship('type', new SingleLinkage(new ResourceIdentifier('apples', '1'))); + new ToOne('type', new ResourceIdentifier('apples', '1')); } /** @@ -164,7 +199,7 @@ public function testRelationshipMustOnlyHaveAllowedCharacters(string $invalid_ch { $this->expectException(\DomainException::class); $this->expectExceptionMessage('Invalid character in a member name'); - new Relationship("foo{$invalid_char}bar", new SingleLinkage()); + new ToNull("foo{$invalid_char}bar"); } public function invalidCharacters() @@ -186,30 +221,13 @@ public function testResourceFieldsMustBeUnique() 'apples', '1', new Attribute('foo', 'bar'), - new Relationship('foo', new SingleLinkage(new ResourceIdentifier('apples', '1'))) - ); - } - - /** - * The id member is not required when the resource object originates at the client and represents - * a new resource to be created on the server. - */ - public function testResourceIdCanBeOmitted() - { - $this->assertEncodesTo( - '{ - "type": "apples", - "id": null, - "attributes": { - "color": "red" - } - }', - new ResourceObject('apples', null, new Attribute('color', 'red')) + new ToOne('foo', new ResourceIdentifier('apples', '1')) ); } - public function testEmptySingleLinkageIdentifiesNothing() + public function testNameValidation() { - $this->assertFalse((new SingleLinkage())->identifies(new ResourceObject('something', '1'))); + $this->expectException(\DomainException::class); + new ResourceObject('invalid:id', 'foo'); } } diff --git a/test/benchmarks/compound10k.php b/test/benchmarks/compound10k.php new file mode 100644 index 0000000..8f2fad4 --- /dev/null +++ b/test/benchmarks/compound10k.php @@ -0,0 +1,117 @@ +toIdentifier()) + ); + + $data_document = new CompoundDocument( + new PaginatedResourceCollection( + new Pagination( + new NextLink('http://example.com/articles?page[offset]=2'), + new LastLink('http://example.com/articles?page[offset]=10') + ), + new ResourceObject( + 'articles', + '1', + new Attribute('title', 'JSON API paints my bikeshed!'), + new SelfLink('http://example.com/articles/1'), + new ToOne( + 'author', + $dan->toIdentifier(), + new SelfLink('http://example.com/articles/1/relationships/author'), + new RelatedLink('http://example.com/articles/1/author') + ), + new ToMany( + 'comments', + new ResourceIdentifierCollection( + $comment05->toIdentifier(), + $comment12->toIdentifier() + ), + new SelfLink('http://example.com/articles/1/relationships/comments'), + new RelatedLink('http://example.com/articles/1/comments') + ) + ) + ), + new Included($dan, $comment05, $comment12), + new SelfLink('http://example.com/articles') + ); + + $data_doc_json = json_encode($data_document); +} + +for ($count = 0; $count < 1000; $count++) { + $error_doc = new ErrorDocument( + new Error( + new Id('1'), + new AboutLink('/errors/not_found'), + new Status('404'), + new Code('not_found'), + new Title('Resource not found'), + new Detail('We tried hard but could not find anything'), + new SourcePointer('/data'), + new SourceParameter('query_string'), + new Meta('purpose', 'test') + ), + new Meta('purpose', 'test'), + new JsonApi() + ); + + $error_doc_json = json_encode($error_doc); +} + +echo $data_doc_json; +echo "\n\n"; +echo $error_doc_json; From 0ffb3205a5e36a9e41edd5c2ff09645efc63f7f0 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 1 Mar 2018 02:12:04 -0800 Subject: [PATCH 27/45] Update CHANGELOG.md (#82) --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 548098b..5696c3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [2.0.0-rc.1] - 2018-03-01 +### Changed +- BC-breaking! Improved performance + ## [2.0.0-rc.0] - 2018-02-26 -Initial stable release +V2 release candidate -[Unreleased]: https://github.com/json-api-php/json-api/compare/2.0.0-rc.0...HEAD +[Unreleased]: https://github.com/json-api-php/json-api/compare/2.0.0-rc.1...HEAD +[2.0.0-rc.1]: https://github.com/json-api-php/json-api/compare/2.0.0-rc.0...2.0.0-rc.1 From 42d32f3a703920ea5490c18fa0b20fabf93d5400 Mon Sep 17 00:00:00 2001 From: Alexey Date: Sat, 3 Mar 2018 00:08:17 -0800 Subject: [PATCH 28/45] Simplification, Formatting, Performance (#83) --- examples/compound_doc.php | 45 ++++++++-------- src/Attribute.php | 5 +- src/CompoundDocument.php | 4 ++ src/DataDocument.php | 4 ++ src/Error.php | 6 ++- src/ErrorDocument.php | 14 +++-- src/Included.php | 24 ++++----- src/Internal/Attachable.php | 2 +- src/Internal/Collection.php | 7 +++ src/Internal/Identifier.php | 6 ++- src/Internal/IdentifierRegistry.php | 27 ---------- src/Internal/IdentityTrait.php | 29 ---------- src/Internal/LinkTrait.php | 3 +- src/Internal/ToManyMember.php | 7 +++ ...RelationshipMember.php => ToOneMember.php} | 2 +- src/JsonApi.php | 10 ++-- src/Link/AboutLink.php | 2 +- src/Link/FirstLink.php | 2 +- src/Link/LastLink.php | 2 +- src/Link/NextLink.php | 2 +- src/Link/PrevLink.php | 2 +- src/Link/RelatedLink.php | 6 +-- src/Link/SelfLink.php | 4 +- src/Meta.php | 6 +-- src/NullData.php | 5 +- src/PaginatedCollection.php | 39 ++++++++++++++ src/PaginatedResourceCollection.php | 24 --------- src/PaginatedResourceIdentifierCollection.php | 24 --------- src/Pagination.php | 6 +-- src/ResourceCollection.php | 8 +-- src/ResourceIdentifier.php | 17 +++--- src/ResourceIdentifierCollection.php | 8 +-- src/ResourceObject.php | 36 +++++++++---- src/ToMany.php | 11 ++-- src/ToNull.php | 8 +-- src/ToOne.php | 9 ++-- src/functions.php | 5 ++ test/CompoundDocumentTest.php | 52 +++++++++--------- test/PaginationTest.php | 27 +++++----- test/benchmarks/compound10k.php | 53 ++++++++++--------- 40 files changed, 277 insertions(+), 276 deletions(-) create mode 100644 src/Internal/Collection.php delete mode 100644 src/Internal/IdentifierRegistry.php delete mode 100644 src/Internal/IdentityTrait.php create mode 100644 src/Internal/ToManyMember.php rename src/Internal/{RelationshipMember.php => ToOneMember.php} (66%) create mode 100644 src/PaginatedCollection.php delete mode 100644 src/PaginatedResourceCollection.php delete mode 100644 src/PaginatedResourceIdentifierCollection.php diff --git a/examples/compound_doc.php b/examples/compound_doc.php index 61842ef..ddb6fd9 100644 --- a/examples/compound_doc.php +++ b/examples/compound_doc.php @@ -7,8 +7,9 @@ use JsonApiPhp\JsonApi\Link\NextLink; use JsonApiPhp\JsonApi\Link\RelatedLink; use JsonApiPhp\JsonApi\Link\SelfLink; -use JsonApiPhp\JsonApi\PaginatedResourceCollection; +use JsonApiPhp\JsonApi\PaginatedCollection; use JsonApiPhp\JsonApi\Pagination; +use JsonApiPhp\JsonApi\ResourceCollection; use JsonApiPhp\JsonApi\ResourceIdentifier; use JsonApiPhp\JsonApi\ResourceIdentifierCollection; use JsonApiPhp\JsonApi\ResourceObject; @@ -39,34 +40,36 @@ '12', new Attribute('body', 'I like XML better'), new SelfLink('http://example.com/comments/12'), - new ToOne('author', $dan->toIdentifier()) + new ToOne('author', $dan->identifier()) ); $document = new CompoundDocument( - new PaginatedResourceCollection( + new PaginatedCollection( new Pagination( new NextLink('http://example.com/articles?page[offset]=2'), new LastLink('http://example.com/articles?page[offset]=10') ), - new ResourceObject( - 'articles', - '1', - new Attribute('title', 'JSON API paints my bikeshed!'), - new SelfLink('http://example.com/articles/1'), - new ToOne( - 'author', - $dan->toIdentifier(), - new SelfLink('http://example.com/articles/1/relationships/author'), - new RelatedLink('http://example.com/articles/1/author') - ), - new ToMany( - 'comments', - new ResourceIdentifierCollection( - $comment05->toIdentifier(), - $comment12->toIdentifier() + new ResourceCollection( + new ResourceObject( + 'articles', + '1', + new Attribute('title', 'JSON API paints my bikeshed!'), + new SelfLink('http://example.com/articles/1'), + new ToOne( + 'author', + $dan->identifier(), + new SelfLink('http://example.com/articles/1/relationships/author'), + new RelatedLink('http://example.com/articles/1/author') ), - new SelfLink('http://example.com/articles/1/relationships/comments'), - new RelatedLink('http://example.com/articles/1/comments') + new ToMany( + 'comments', + new ResourceIdentifierCollection( + $comment05->identifier(), + $comment12->identifier() + ), + new SelfLink('http://example.com/articles/1/relationships/comments'), + new RelatedLink('http://example.com/articles/1/comments') + ) ) ) ), diff --git a/src/Attribute.php b/src/Attribute.php index fb2984f..69c6621 100644 --- a/src/Attribute.php +++ b/src/Attribute.php @@ -5,6 +5,9 @@ use JsonApiPhp\JsonApi\Internal\ResourceField; use JsonApiPhp\JsonApi\Internal\ResourceFieldTrait; +/** + * @see http://jsonapi.org/format/#document-resource-object-attributes + */ final class Attribute implements ResourceField { use ResourceFieldTrait; @@ -17,7 +20,7 @@ public function __construct(string $name, $val) $this->val = $val; } - public function attachTo(object $o) + public function attachTo(object $o): void { child($o, 'attributes')->{$this->name} = $this->val; } diff --git a/src/CompoundDocument.php b/src/CompoundDocument.php index 05bc119..7e84887 100644 --- a/src/CompoundDocument.php +++ b/src/CompoundDocument.php @@ -5,6 +5,10 @@ use JsonApiPhp\JsonApi\Internal\DataDocumentMember; use JsonApiPhp\JsonApi\Internal\PrimaryData; +/** + * A Document with the "included" member + * @see http://jsonapi.org/format/#document-compound-documents + */ final class CompoundDocument implements \JsonSerializable { private $doc; diff --git a/src/DataDocument.php b/src/DataDocument.php index 68fa2cd..d8b986d 100644 --- a/src/DataDocument.php +++ b/src/DataDocument.php @@ -5,6 +5,10 @@ use JsonApiPhp\JsonApi\Internal\DataDocumentMember; use JsonApiPhp\JsonApi\Internal\PrimaryData; +/** + * A Document containing the "data" member + * @see http://jsonapi.org/format/#document-top-level + */ final class DataDocument implements \JsonSerializable { private $value; diff --git a/src/Error.php b/src/Error.php index 65b3071..65a012d 100644 --- a/src/Error.php +++ b/src/Error.php @@ -5,6 +5,10 @@ use JsonApiPhp\JsonApi\Internal\ErrorDocumentMember; use JsonApiPhp\JsonApi\Internal\ErrorMember; +/** + * An Error Object + * @see + */ final class Error implements ErrorDocumentMember { private $error; @@ -17,7 +21,7 @@ public function __construct(ErrorMember ...$members) } } - public function attachTo(object $o) + public function attachTo(object $o): void { $o->errors[] = $this->error; } diff --git a/src/ErrorDocument.php b/src/ErrorDocument.php index 382d5a6..e4a52e5 100644 --- a/src/ErrorDocument.php +++ b/src/ErrorDocument.php @@ -4,21 +4,25 @@ use JsonApiPhp\JsonApi\Internal\ErrorDocumentMember; +/** + * A Document containing an array of Error objects + * @see http://jsonapi.org/format/#document-top-level + */ final class ErrorDocument implements \JsonSerializable { - private $doc; + private $obj; public function __construct(Error $error, ErrorDocumentMember ...$members) { - $this->doc = (object) []; - $error->attachTo($this->doc); + $this->obj = (object) []; + $error->attachTo($this->obj); foreach ($members as $member) { - $member->attachTo($this->doc); + $member->attachTo($this->obj); } } public function jsonSerialize() { - return $this->doc; + return $this->obj; } } diff --git a/src/Included.php b/src/Included.php index 30da0c0..04c8d1c 100644 --- a/src/Included.php +++ b/src/Included.php @@ -3,7 +3,6 @@ namespace JsonApiPhp\JsonApi; use JsonApiPhp\JsonApi\Internal\Attachable; -use JsonApiPhp\JsonApi\Internal\IdentifierRegistry; use JsonApiPhp\JsonApi\Internal\PrimaryData; final class Included implements Attachable @@ -13,34 +12,33 @@ final class Included implements Attachable */ private $resources = []; - private $ids; + private $identifiers = []; public function __construct(ResourceObject ...$resources) { - $this->ids = new IdentifierRegistry(); foreach ($resources as $resource) { - $string_id = $resource->key(); - if (isset($this->resources[$string_id])) { - throw new \LogicException("Resource $string_id is already included"); + $key = $resource->key(); + if (isset($this->resources[$key])) { + throw new \LogicException("Resource $resource is already included"); } - $this->resources[$string_id] = $resource; - $resource->registerIn($this->ids); + $this->resources[$key] = $resource; + $resource->registerIn($this->identifiers); } } public function validateLinkage(PrimaryData $data): void { - $dataRegistry = new IdentifierRegistry(); - $data->registerIn($dataRegistry); + $registry = []; + $data->registerIn($registry); foreach ($this->resources as $resource) { - if ($dataRegistry->has($resource->key()) || $this->ids->has($resource->key())) { + if (isset($registry[$resource->key()]) || isset($this->identifiers[$resource->key()])) { continue; } - throw new \LogicException('Full linkage required for '.$resource->key()); + throw new \LogicException('Full linkage required for '.$resource); } } - public function attachTo(object $o) + public function attachTo(object $o): void { foreach ($this->resources as $resource) { $resource->attachAsIncludedTo($o); diff --git a/src/Internal/Attachable.php b/src/Internal/Attachable.php index fbbe24c..cccd352 100644 --- a/src/Internal/Attachable.php +++ b/src/Internal/Attachable.php @@ -7,5 +7,5 @@ */ interface Attachable { - public function attachTo(object $o); + public function attachTo(object $o): void; } diff --git a/src/Internal/Collection.php b/src/Internal/Collection.php new file mode 100644 index 0000000..e0e73d0 --- /dev/null +++ b/src/Internal/Collection.php @@ -0,0 +1,7 @@ +ids[$id] = true; - } - - public function has(string $id): bool - { - return isset($this->ids[$id]); - } - - public function merge(IdentifierRegistry $registry) - { - $this->ids = array_merge($this->ids, $registry->ids); - } -} diff --git a/src/Internal/IdentityTrait.php b/src/Internal/IdentityTrait.php deleted file mode 100644 index b46ecd4..0000000 --- a/src/Internal/IdentityTrait.php +++ /dev/null @@ -1,29 +0,0 @@ -type}:{$this->id}"; - } -} diff --git a/src/Internal/LinkTrait.php b/src/Internal/LinkTrait.php index 9d957bb..006c61a 100644 --- a/src/Internal/LinkTrait.php +++ b/src/Internal/LinkTrait.php @@ -1,5 +1,4 @@ -jsonapi = (object) [ + $this->obj = (object) [ 'version' => $version, ]; if ($meta) { - $meta->attachTo($this->jsonapi); + $meta->attachTo($this->obj); } } - public function attachTo(object $o) + public function attachTo(object $o): void { - $o->jsonapi = $this->jsonapi; + $o->jsonapi = $this->obj; } } diff --git a/src/Link/AboutLink.php b/src/Link/AboutLink.php index 3da5849..31fe133 100644 --- a/src/Link/AboutLink.php +++ b/src/Link/AboutLink.php @@ -10,7 +10,7 @@ final class AboutLink implements ErrorMember { use LinkTrait; - public function attachTo(object $o) + public function attachTo(object $o): void { child($o, 'links')->about = $this->link; } diff --git a/src/Link/FirstLink.php b/src/Link/FirstLink.php index 12b10ed..f41ab3c 100644 --- a/src/Link/FirstLink.php +++ b/src/Link/FirstLink.php @@ -10,7 +10,7 @@ final class FirstLink implements PaginationLink { use LinkTrait; - public function attachTo(object $o) + public function attachTo(object $o): void { child($o, 'links')->first = $this->link; } diff --git a/src/Link/LastLink.php b/src/Link/LastLink.php index b745f4a..b174cb2 100644 --- a/src/Link/LastLink.php +++ b/src/Link/LastLink.php @@ -10,7 +10,7 @@ final class LastLink implements PaginationLink { use LinkTrait; - public function attachTo(object $o) + public function attachTo(object $o): void { child($o, 'links')->last = $this->link; } diff --git a/src/Link/NextLink.php b/src/Link/NextLink.php index 1c60fb9..e1cce27 100644 --- a/src/Link/NextLink.php +++ b/src/Link/NextLink.php @@ -10,7 +10,7 @@ final class NextLink implements PaginationLink { use LinkTrait; - public function attachTo(object $o) + public function attachTo(object $o): void { child($o, 'links')->next = $this->link; } diff --git a/src/Link/PrevLink.php b/src/Link/PrevLink.php index bbc0b6b..99a61c3 100644 --- a/src/Link/PrevLink.php +++ b/src/Link/PrevLink.php @@ -10,7 +10,7 @@ final class PrevLink implements PaginationLink { use LinkTrait; - public function attachTo(object $o) + public function attachTo(object $o): void { child($o, 'links')->prev = $this->link; } diff --git a/src/Link/RelatedLink.php b/src/Link/RelatedLink.php index ca80378..c0005ff 100644 --- a/src/Link/RelatedLink.php +++ b/src/Link/RelatedLink.php @@ -3,14 +3,14 @@ namespace JsonApiPhp\JsonApi\Link; use JsonApiPhp\JsonApi\Internal\LinkTrait; -use JsonApiPhp\JsonApi\Internal\RelationshipMember; +use JsonApiPhp\JsonApi\Internal\ToOneMember; use function JsonApiPhp\JsonApi\child; -final class RelatedLink implements RelationshipMember +final class RelatedLink implements ToOneMember { use LinkTrait; - public function attachTo(object $o) + public function attachTo(object $o): void { child($o, 'links')->related = $this->link; } diff --git a/src/Link/SelfLink.php b/src/Link/SelfLink.php index 3817dd3..17d96cd 100644 --- a/src/Link/SelfLink.php +++ b/src/Link/SelfLink.php @@ -4,11 +4,11 @@ use JsonApiPhp\JsonApi\Internal\DataDocumentMember; use JsonApiPhp\JsonApi\Internal\LinkTrait; -use JsonApiPhp\JsonApi\Internal\RelationshipMember; use JsonApiPhp\JsonApi\Internal\ResourceMember; +use JsonApiPhp\JsonApi\Internal\ToOneMember; use function JsonApiPhp\JsonApi\child; -final class SelfLink implements DataDocumentMember, ResourceMember, RelationshipMember +final class SelfLink implements DataDocumentMember, ResourceMember, ToOneMember { use LinkTrait; diff --git a/src/Meta.php b/src/Meta.php index b8252cb..e186648 100644 --- a/src/Meta.php +++ b/src/Meta.php @@ -6,10 +6,10 @@ use JsonApiPhp\JsonApi\Internal\ErrorDocumentMember; use JsonApiPhp\JsonApi\Internal\ErrorMember; use JsonApiPhp\JsonApi\Internal\MetaDocumentMember; -use JsonApiPhp\JsonApi\Internal\RelationshipMember; use JsonApiPhp\JsonApi\Internal\ResourceMember; +use JsonApiPhp\JsonApi\Internal\ToOneMember; -final class Meta implements ErrorMember, ErrorDocumentMember, MetaDocumentMember, DataDocumentMember, ResourceMember, RelationshipMember +final class Meta implements ErrorMember, ErrorDocumentMember, MetaDocumentMember, DataDocumentMember, ResourceMember, ToOneMember { /** * @var string @@ -26,7 +26,7 @@ public function __construct(string $key, $value) $this->value = $value; } - public function attachTo(object $o) + public function attachTo(object $o): void { child($o, 'meta')->{$this->key} = $this->value; } diff --git a/src/NullData.php b/src/NullData.php index 768102d..26dd8a5 100644 --- a/src/NullData.php +++ b/src/NullData.php @@ -2,17 +2,16 @@ namespace JsonApiPhp\JsonApi; -use JsonApiPhp\JsonApi\Internal\IdentifierRegistry; use JsonApiPhp\JsonApi\Internal\PrimaryData; final class NullData implements PrimaryData { - public function attachTo(object $o) + public function attachTo(object $o): void { $o->data = null; } - public function registerIn(IdentifierRegistry $registry) + public function registerIn(array &$registry): void { } } diff --git a/src/PaginatedCollection.php b/src/PaginatedCollection.php new file mode 100644 index 0000000..8b8e0bd --- /dev/null +++ b/src/PaginatedCollection.php @@ -0,0 +1,39 @@ +pagination = $pagination; + $this->collection = $collection; + } + + public function attachTo(object $o): void + { + $this->collection->attachTo($o); + $this->pagination->attachTo($o); + } + + /** + * @internal + * @param array $registry + */ + public function registerIn(array &$registry): void + { + $this->collection->registerIn($registry); + } +} diff --git a/src/PaginatedResourceCollection.php b/src/PaginatedResourceCollection.php deleted file mode 100644 index cb2a633..0000000 --- a/src/PaginatedResourceCollection.php +++ /dev/null @@ -1,24 +0,0 @@ -pagination = $pagination; - } - - public function attachTo(object $o): void - { - parent::attachTo($o); - $this->pagination->attachTo($o); - } -} diff --git a/src/PaginatedResourceIdentifierCollection.php b/src/PaginatedResourceIdentifierCollection.php deleted file mode 100644 index ea4e432..0000000 --- a/src/PaginatedResourceIdentifierCollection.php +++ /dev/null @@ -1,24 +0,0 @@ -pagination = $pagination; - } - - public function attachTo(object $o) - { - parent::attachTo($o); - $this->pagination->attachTo($o); - } -} diff --git a/src/Pagination.php b/src/Pagination.php index ba8ab41..aa4f9c3 100644 --- a/src/Pagination.php +++ b/src/Pagination.php @@ -3,10 +3,10 @@ namespace JsonApiPhp\JsonApi; -use JsonApiPhp\JsonApi\Internal\Attachable; use JsonApiPhp\JsonApi\Internal\PaginationLink; +use JsonApiPhp\JsonApi\Internal\ToManyMember; -class Pagination implements Attachable +class Pagination implements ToManyMember { /** * @var PaginationLink[] @@ -18,7 +18,7 @@ public function __construct(PaginationLink ...$links) $this->links = $links; } - public function attachTo(object $o) + public function attachTo(object $o): void { foreach ($this->links as $link) { $link->attachTo($o); diff --git a/src/ResourceCollection.php b/src/ResourceCollection.php index e29ee55..77ac09f 100644 --- a/src/ResourceCollection.php +++ b/src/ResourceCollection.php @@ -2,10 +2,10 @@ namespace JsonApiPhp\JsonApi; -use JsonApiPhp\JsonApi\Internal\IdentifierRegistry; +use JsonApiPhp\JsonApi\Internal\Collection; use JsonApiPhp\JsonApi\Internal\PrimaryData; -class ResourceCollection implements PrimaryData +class ResourceCollection implements PrimaryData, Collection { /** * @var ResourceObject[] @@ -17,7 +17,7 @@ public function __construct(ResourceObject ...$resources) $this->resources = $resources; } - public function attachTo(object $o) + public function attachTo(object $o): void { $o->data = []; foreach ($this->resources as $resource) { @@ -25,7 +25,7 @@ public function attachTo(object $o) } } - public function registerIn(IdentifierRegistry $registry) + public function registerIn(array &$registry): void { foreach ($this->resources as $resource) { $resource->registerIn($registry); diff --git a/src/ResourceIdentifier.php b/src/ResourceIdentifier.php index 7d170b0..221e096 100644 --- a/src/ResourceIdentifier.php +++ b/src/ResourceIdentifier.php @@ -2,14 +2,19 @@ namespace JsonApiPhp\JsonApi; -use JsonApiPhp\JsonApi\Internal\IdentifierRegistry; -use JsonApiPhp\JsonApi\Internal\IdentityTrait; use JsonApiPhp\JsonApi\Internal\PrimaryData; final class ResourceIdentifier implements PrimaryData { - use IdentityTrait; private $obj; + /** + * @var string + */ + private $type; + /** + * @var string + */ + private $id; public function __construct(string $type, string $id, Meta $meta = null) { @@ -28,7 +33,7 @@ public function __construct(string $type, string $id, Meta $meta = null) $this->id = $id; } - public function attachTo(object $o) + public function attachTo(object $o): void { $o->data = $this->obj; } @@ -38,8 +43,8 @@ public function attachToCollection(object $o): void $o->data[] = $this->obj; } - public function registerIn(IdentifierRegistry $registry) + public function registerIn(array &$registry): void { - $registry->add($this->key()); + $registry[compositeKey($this->type, $this->id)] = true; } } diff --git a/src/ResourceIdentifierCollection.php b/src/ResourceIdentifierCollection.php index 1786c1d..d13eb11 100644 --- a/src/ResourceIdentifierCollection.php +++ b/src/ResourceIdentifierCollection.php @@ -2,10 +2,10 @@ namespace JsonApiPhp\JsonApi; -use JsonApiPhp\JsonApi\Internal\IdentifierRegistry; +use JsonApiPhp\JsonApi\Internal\Collection; use JsonApiPhp\JsonApi\Internal\PrimaryData; -class ResourceIdentifierCollection implements PrimaryData +class ResourceIdentifierCollection implements PrimaryData, Collection { /** * @var ResourceIdentifier[] @@ -17,7 +17,7 @@ public function __construct(ResourceIdentifier ...$identifiers) $this->identifiers = $identifiers; } - public function attachTo(object $o) + public function attachTo(object $o): void { $o->data = []; foreach ($this->identifiers as $identifier) { @@ -25,7 +25,7 @@ public function attachTo(object $o) } } - public function registerIn(IdentifierRegistry $registry) + public function registerIn(array &$registry): void { foreach ($this->identifiers as $identifier) { $identifier->registerIn($registry); diff --git a/src/ResourceObject.php b/src/ResourceObject.php index 02701d2..de0a478 100644 --- a/src/ResourceObject.php +++ b/src/ResourceObject.php @@ -3,26 +3,28 @@ namespace JsonApiPhp\JsonApi; use JsonApiPhp\JsonApi\Internal\Identifier; -use JsonApiPhp\JsonApi\Internal\IdentifierRegistry; -use JsonApiPhp\JsonApi\Internal\IdentityTrait; use JsonApiPhp\JsonApi\Internal\PrimaryData; use JsonApiPhp\JsonApi\Internal\ResourceField; use JsonApiPhp\JsonApi\Internal\ResourceMember; final class ResourceObject implements PrimaryData { - use IdentityTrait; private $obj; - private $registry; + 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->type = $type; - $this->id = $id; - $this->registry = new IdentifierRegistry(); $this->obj = (object) ['type' => $type, 'id' => $id]; $fields = []; foreach ($members as $member) { @@ -38,19 +40,26 @@ public function __construct(string $type, string $id, ResourceMember ...$members } $member->attachTo($this->obj); } + $this->type = $type; + $this->id = $id; } - public function toIdentifier(): ResourceIdentifier + public function identifier(): ResourceIdentifier { return new ResourceIdentifier($this->type, $this->id); } - public function registerIn(IdentifierRegistry $registry) + public function key(): string + { + return compositeKey($this->type, $this->id); + } + + public function registerIn(array &$registry): void { - $registry->merge($this->registry); + $registry = array_merge($registry, $this->registry); } - public function attachTo(object $o) + public function attachTo(object $o): void { $o->data = $this->obj; } @@ -64,4 +73,9 @@ public function attachToCollection(object $o): void { $o->data[] = $this->obj; } + + public function __toString(): string + { + return $this->key(); + } } diff --git a/src/ToMany.php b/src/ToMany.php index d8b2ba3..94b873b 100644 --- a/src/ToMany.php +++ b/src/ToMany.php @@ -3,16 +3,15 @@ namespace JsonApiPhp\JsonApi; use JsonApiPhp\JsonApi\Internal\Identifier; -use JsonApiPhp\JsonApi\Internal\IdentifierRegistry; -use JsonApiPhp\JsonApi\Internal\RelationshipMember; use JsonApiPhp\JsonApi\Internal\ResourceField; use JsonApiPhp\JsonApi\Internal\ResourceFieldTrait; +use JsonApiPhp\JsonApi\Internal\ToOneMember; final class ToMany implements Identifier, ResourceField { use ResourceFieldTrait; /** - * @var RelationshipMember[] + * @var ToOneMember[] */ private $members; /** @@ -20,7 +19,7 @@ final class ToMany implements Identifier, ResourceField */ private $collection; - public function __construct(string $name, ResourceIdentifierCollection $collection, RelationshipMember ...$members) + public function __construct(string $name, ResourceIdentifierCollection $collection, ToOneMember ...$members) { $this->validateFieldName($name); $this->name = $name; @@ -28,7 +27,7 @@ public function __construct(string $name, ResourceIdentifierCollection $collecti $this->collection = $collection; } - public function attachTo(object $o) + public function attachTo(object $o): void { $rel = child(child($o, 'relationships'), $this->name); $rel->data = []; @@ -38,7 +37,7 @@ public function attachTo(object $o) } } - public function registerIn(IdentifierRegistry $registry) + public function registerIn(array &$registry): void { $this->collection->registerIn($registry); } diff --git a/src/ToNull.php b/src/ToNull.php index c163d67..0efaef0 100644 --- a/src/ToNull.php +++ b/src/ToNull.php @@ -2,26 +2,26 @@ namespace JsonApiPhp\JsonApi; -use JsonApiPhp\JsonApi\Internal\RelationshipMember; use JsonApiPhp\JsonApi\Internal\ResourceField; use JsonApiPhp\JsonApi\Internal\ResourceFieldTrait; +use JsonApiPhp\JsonApi\Internal\ToOneMember; final class ToNull implements ResourceField { use ResourceFieldTrait; /** - * @var RelationshipMember[] + * @var ToOneMember[] */ private $members; - public function __construct(string $name, RelationshipMember ...$members) + public function __construct(string $name, ToOneMember ...$members) { $this->validateFieldName($name); $this->name = $name; $this->members = $members; } - public function attachTo(object $o) + public function attachTo(object $o): void { $obj = combine(...$this->members); $obj->data = null; diff --git a/src/ToOne.php b/src/ToOne.php index b696710..c6d32da 100644 --- a/src/ToOne.php +++ b/src/ToOne.php @@ -3,10 +3,9 @@ namespace JsonApiPhp\JsonApi; use JsonApiPhp\JsonApi\Internal\Identifier; -use JsonApiPhp\JsonApi\Internal\IdentifierRegistry; -use JsonApiPhp\JsonApi\Internal\RelationshipMember; use JsonApiPhp\JsonApi\Internal\ResourceField; use JsonApiPhp\JsonApi\Internal\ResourceFieldTrait; +use JsonApiPhp\JsonApi\Internal\ToOneMember; final class ToOne implements Identifier, ResourceField { @@ -19,7 +18,7 @@ final class ToOne implements Identifier, ResourceField private $obj; - public function __construct(string $name, ResourceIdentifier $identifier, RelationshipMember ...$members) + public function __construct(string $name, ResourceIdentifier $identifier, ToOneMember ...$members) { $this->validateFieldName($name); $this->name = $name; @@ -27,12 +26,12 @@ public function __construct(string $name, ResourceIdentifier $identifier, Relati $this->identifier = $identifier; } - public function attachTo(object $o) + public function attachTo(object $o): void { child($o, 'relationships')->{$this->name} = $this->obj; } - public function registerIn(IdentifierRegistry $registry) + public function registerIn(array &$registry): void { $this->identifier->registerIn($registry); } diff --git a/src/functions.php b/src/functions.php index 458cb53..d7c89a4 100644 --- a/src/functions.php +++ b/src/functions.php @@ -25,3 +25,8 @@ function isValidName(string $name): bool { return preg_match('/^(?=[^-_ ])[a-zA-Z0-9\x{0080}-\x{FFFF}-_ ]*(?<=[^-_ ])$/u', $name) === 1; } + +function compositeKey(string $type, string $id): string +{ + return "{$type}:{$id}"; +} diff --git a/test/CompoundDocumentTest.php b/test/CompoundDocumentTest.php index 01ede9c..3d62ff9 100644 --- a/test/CompoundDocumentTest.php +++ b/test/CompoundDocumentTest.php @@ -10,7 +10,7 @@ use JsonApiPhp\JsonApi\Link\RelatedLink; use JsonApiPhp\JsonApi\Link\SelfLink; use JsonApiPhp\JsonApi\NullData; -use JsonApiPhp\JsonApi\PaginatedResourceCollection; +use JsonApiPhp\JsonApi\PaginatedCollection; use JsonApiPhp\JsonApi\Pagination; use JsonApiPhp\JsonApi\ResourceCollection; use JsonApiPhp\JsonApi\ResourceIdentifier; @@ -45,34 +45,36 @@ public function testOfficialDocsExample() '12', new Attribute('body', 'I like XML better'), new SelfLink('http://example.com/comments/12'), - new ToOne('author', $dan->toIdentifier()) + new ToOne('author', $dan->identifier()) ); $document = new CompoundDocument( - new PaginatedResourceCollection( + new PaginatedCollection( new Pagination( new NextLink('http://example.com/articles?page[offset]=2'), new LastLink('http://example.com/articles?page[offset]=10') ), - new ResourceObject( - 'articles', - '1', - new Attribute('title', 'JSON API paints my bikeshed!'), - new SelfLink('http://example.com/articles/1'), - new ToOne( - 'author', - $dan->toIdentifier(), - new SelfLink('http://example.com/articles/1/relationships/author'), - new RelatedLink('http://example.com/articles/1/author') - ), - new ToMany( - 'comments', - new ResourceIdentifierCollection( - $comment05->toIdentifier(), - $comment12->toIdentifier() + new ResourceCollection( + new ResourceObject( + 'articles', + '1', + new Attribute('title', 'JSON API paints my bikeshed!'), + new SelfLink('http://example.com/articles/1'), + new ToOne( + 'author', + $dan->identifier(), + new SelfLink('http://example.com/articles/1/relationships/author'), + new RelatedLink('http://example.com/articles/1/author') ), - new SelfLink('http://example.com/articles/1/relationships/comments'), - new RelatedLink('http://example.com/articles/1/comments') + new ToMany( + 'comments', + new ResourceIdentifierCollection( + $comment05->identifier(), + $comment12->identifier() + ), + new SelfLink('http://example.com/articles/1/relationships/comments'), + new RelatedLink('http://example.com/articles/1/comments') + ) ) ) ), @@ -225,7 +227,7 @@ public function testIncludedResourceMayBeIdentifiedByLinkageInPrimaryData() $article = new ResourceObject( 'articles', '1', - new ToOne('author', $author->toIdentifier()) + new ToOne('author', $author->identifier()) ); $doc = new CompoundDocument($article, new Included($author)); $this->assertNotEmpty($doc); @@ -238,12 +240,12 @@ public function testIncludedResourceMayBeIdentifiedByAnotherLinkedResource() 'books', '2', new Attribute('name', 'Domain Driven Design'), - new ToOne('author', $writer->toIdentifier()) + new ToOne('author', $writer->identifier()) ); $cart = new ResourceObject( 'shopping-carts', '1', - new ToMany('contents', new ResourceIdentifierCollection($book->toIdentifier())) + new ToMany('contents', new ResourceIdentifierCollection($book->identifier())) ); $doc = new CompoundDocument($cart, new Included($book, $writer)); $this->assertNotEmpty($doc); @@ -257,6 +259,6 @@ public function testIncludedResourceMayBeIdentifiedByAnotherLinkedResource() public function testCanNotBeManyIncludedResourcesWithEqualIdentifiers() { $apple = new ResourceObject('apples', '1'); - new CompoundDocument($apple->toIdentifier(), new Included($apple, $apple)); + new CompoundDocument($apple->identifier(), new Included($apple, $apple)); } } diff --git a/test/PaginationTest.php b/test/PaginationTest.php index e00ab53..17ee668 100644 --- a/test/PaginationTest.php +++ b/test/PaginationTest.php @@ -10,10 +10,11 @@ use JsonApiPhp\JsonApi\Link\LastLink; use JsonApiPhp\JsonApi\Link\NextLink; use JsonApiPhp\JsonApi\Link\PrevLink; -use JsonApiPhp\JsonApi\PaginatedResourceCollection; -use JsonApiPhp\JsonApi\PaginatedResourceIdentifierCollection; +use JsonApiPhp\JsonApi\PaginatedCollection; use JsonApiPhp\JsonApi\Pagination; +use JsonApiPhp\JsonApi\ResourceCollection; use JsonApiPhp\JsonApi\ResourceIdentifier; +use JsonApiPhp\JsonApi\ResourceIdentifierCollection; use JsonApiPhp\JsonApi\ResourceObject; use JsonApiPhp\JsonApi\ToMany; @@ -37,15 +38,17 @@ public function testPaginatedResourceCollection() } ', new DataDocument( - new PaginatedResourceCollection( + new PaginatedCollection( new Pagination( new FirstLink('http://example.com/fruits?page=first'), new PrevLink('http://example.com/fruits?page=3'), new NextLink('http://example.com/fruits?page=5'), new LastLink('http://example.com/fruits?page=last') ), - new ResourceObject('apples', '1'), - new ResourceObject('apples', '2') + new ResourceCollection( + new ResourceObject('apples', '1'), + new ResourceObject('apples', '2') + ) ) ) ); @@ -86,15 +89,15 @@ public function testPaginatedResourceIdentifierCollection() '1', new ToMany( 'fruits', - new PaginatedResourceIdentifierCollection( - new Pagination( - new FirstLink('http://example.com/basket/1/fruits?page=first'), - new PrevLink('http://example.com/basket/1/fruits?page=3'), - new NextLink('http://example.com/basket/1/fruits?page=5'), - new LastLink('http://example.com/basket/1/fruits?page=last') - ), + new ResourceIdentifierCollection( new ResourceIdentifier('apples', '1'), new ResourceIdentifier('apples', '2') + ), + new Pagination( + new FirstLink('http://example.com/basket/1/fruits?page=first'), + new PrevLink('http://example.com/basket/1/fruits?page=3'), + new NextLink('http://example.com/basket/1/fruits?page=5'), + new LastLink('http://example.com/basket/1/fruits?page=last') ) ) ), diff --git a/test/benchmarks/compound10k.php b/test/benchmarks/compound10k.php index 8f2fad4..c1a0ae2 100644 --- a/test/benchmarks/compound10k.php +++ b/test/benchmarks/compound10k.php @@ -19,8 +19,9 @@ use JsonApiPhp\JsonApi\Link\RelatedLink; use JsonApiPhp\JsonApi\Link\SelfLink; use JsonApiPhp\JsonApi\Meta; -use JsonApiPhp\JsonApi\PaginatedResourceCollection; +use JsonApiPhp\JsonApi\PaginatedCollection; use JsonApiPhp\JsonApi\Pagination; +use JsonApiPhp\JsonApi\ResourceCollection; use JsonApiPhp\JsonApi\ResourceIdentifier; use JsonApiPhp\JsonApi\ResourceIdentifierCollection; use JsonApiPhp\JsonApi\ResourceObject; @@ -54,34 +55,36 @@ '12', new Attribute('body', 'I like XML better'), new SelfLink('http://example.com/comments/12'), - new ToOne('author', $dan->toIdentifier()) + new ToOne('author', $dan->identifier()) ); - $data_document = new CompoundDocument( - new PaginatedResourceCollection( + $document = new CompoundDocument( + new PaginatedCollection( new Pagination( new NextLink('http://example.com/articles?page[offset]=2'), new LastLink('http://example.com/articles?page[offset]=10') ), - new ResourceObject( - 'articles', - '1', - new Attribute('title', 'JSON API paints my bikeshed!'), - new SelfLink('http://example.com/articles/1'), - new ToOne( - 'author', - $dan->toIdentifier(), - new SelfLink('http://example.com/articles/1/relationships/author'), - new RelatedLink('http://example.com/articles/1/author') - ), - new ToMany( - 'comments', - new ResourceIdentifierCollection( - $comment05->toIdentifier(), - $comment12->toIdentifier() + new ResourceCollection( + new ResourceObject( + 'articles', + '1', + new Attribute('title', 'JSON API paints my bikeshed!'), + new SelfLink('http://example.com/articles/1'), + new ToOne( + 'author', + $dan->identifier(), + new SelfLink('http://example.com/articles/1/relationships/author'), + new RelatedLink('http://example.com/articles/1/author') ), - new SelfLink('http://example.com/articles/1/relationships/comments'), - new RelatedLink('http://example.com/articles/1/comments') + new ToMany( + 'comments', + new ResourceIdentifierCollection( + $comment05->identifier(), + $comment12->identifier() + ), + new SelfLink('http://example.com/articles/1/relationships/comments'), + new RelatedLink('http://example.com/articles/1/comments') + ) ) ) ), @@ -89,11 +92,11 @@ new SelfLink('http://example.com/articles') ); - $data_doc_json = json_encode($data_document); + $data_doc_json = json_encode($document); } for ($count = 0; $count < 1000; $count++) { - $error_doc = new ErrorDocument( + $error = new ErrorDocument( new Error( new Id('1'), new AboutLink('/errors/not_found'), @@ -109,7 +112,7 @@ new JsonApi() ); - $error_doc_json = json_encode($error_doc); + $error_doc_json = json_encode($error); } echo $data_doc_json; From 9fa670bdc7194e101af13216e7c4e958d7f82e34 Mon Sep 17 00:00:00 2001 From: Alexey Date: Sat, 3 Mar 2018 00:15:46 -0800 Subject: [PATCH 29/45] v2 release (#84) --- CHANGELOG.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5696c3d..bebaa62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] -## [2.0.0-rc.1] - 2018-03-01 -### Changed -- BC-breaking! Improved performance +## [2.0.0] - 2018-02-26 +V2 initial release -## [2.0.0-rc.0] - 2018-02-26 -V2 release candidate - -[Unreleased]: https://github.com/json-api-php/json-api/compare/2.0.0-rc.1...HEAD -[2.0.0-rc.1]: https://github.com/json-api-php/json-api/compare/2.0.0-rc.0...2.0.0-rc.1 +[Unreleased]: https://github.com/json-api-php/json-api/compare/2.0.0...HEAD From facea7471f7af674d75eef00eda2c1a60575c746 Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 29 Aug 2018 10:46:49 -0700 Subject: [PATCH 30/45] Remove the confusing statement (#86) --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 1677ff1..8e831cf 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # [JSON API](http://jsonapi.org) spec implemented in PHP 7. Immutable -**This is v2 of the implementation. For v1 click [here](https://github.com/json-api-php/json-api/tree/v1).** - The goal of this library is to ensure strict validity of JSON API documents being produced. JSON: From 0e9681a9c91ae94ab2c81a22dfdf879b017e305f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Machulda?= Date: Sat, 29 Sep 2018 00:52:23 +0200 Subject: [PATCH 31/45] MetaDocumentMember should be Attachable (#87) --- src/Internal/MetaDocumentMember.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Internal/MetaDocumentMember.php b/src/Internal/MetaDocumentMember.php index daff35c..27aded5 100644 --- a/src/Internal/MetaDocumentMember.php +++ b/src/Internal/MetaDocumentMember.php @@ -5,6 +5,6 @@ /** * @internal */ -interface MetaDocumentMember +interface MetaDocumentMember extends Attachable { } From 6b9374a46c5c43bf9a6daf5e688ee417e653c5f8 Mon Sep 17 00:00:00 2001 From: Alexey Date: Mon, 31 Dec 2018 16:34:57 -0800 Subject: [PATCH 32/45] Downgrade min required php version to 7.1 (fixes #89) (#90) --- .travis.yml | 2 +- composer.json | 4 ++-- src/Attribute.php | 5 ++++- src/Error.php | 5 ++++- src/Error/Code.php | 5 ++++- src/Error/Detail.php | 5 ++++- src/Error/Id.php | 5 ++++- src/Error/SourceParameter.php | 5 ++++- src/Error/SourcePointer.php | 5 ++++- src/Error/Status.php | 5 ++++- src/Error/Title.php | 5 ++++- src/Included.php | 5 ++++- src/Internal/Attachable.php | 5 ++++- src/JsonApi.php | 5 ++++- src/Link/AboutLink.php | 5 ++++- src/Link/FirstLink.php | 5 ++++- src/Link/LastLink.php | 5 ++++- src/Link/NextLink.php | 5 ++++- src/Link/PrevLink.php | 5 ++++- src/Link/RelatedLink.php | 5 ++++- src/Link/SelfLink.php | 5 ++++- src/Meta.php | 2 +- src/NullData.php | 5 ++++- src/PaginatedCollection.php | 5 ++++- src/Pagination.php | 5 ++++- src/ResourceCollection.php | 5 ++++- src/ResourceIdentifier.php | 10 ++++++++-- src/ResourceIdentifierCollection.php | 5 ++++- src/ResourceObject.php | 15 ++++++++++++--- src/ToMany.php | 5 ++++- src/ToNull.php | 5 ++++- src/ToOne.php | 5 ++++- src/functions.php | 4 ++-- test/BaseTestCase.php | 2 +- 34 files changed, 135 insertions(+), 39 deletions(-) diff --git a/.travis.yml b/.travis.yml index bef7869..8ba1558 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: php php: - - '7.2' + - '7.1' before_script: - composer install diff --git a/composer.json b/composer.json index 1cdeb90..50b9f32 100644 --- a/composer.json +++ b/composer.json @@ -11,11 +11,11 @@ } ], "require": { - "php": ">=7.2" + "php": ">=7.1" }, "require-dev": { "phpunit/phpunit": "^7.0", - "friendsofphp/php-cs-fixer": "^2.2" + "friendsofphp/php-cs-fixer": "^2.13" }, "autoload": { "psr-4": { diff --git a/src/Attribute.php b/src/Attribute.php index 69c6621..ba1f50b 100644 --- a/src/Attribute.php +++ b/src/Attribute.php @@ -20,7 +20,10 @@ public function __construct(string $name, $val) $this->val = $val; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { child($o, 'attributes')->{$this->name} = $this->val; } diff --git a/src/Error.php b/src/Error.php index 65a012d..35b7b8d 100644 --- a/src/Error.php +++ b/src/Error.php @@ -21,7 +21,10 @@ public function __construct(ErrorMember ...$members) } } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $o->errors[] = $this->error; } diff --git a/src/Error/Code.php b/src/Error/Code.php index eafbc72..6705037 100644 --- a/src/Error/Code.php +++ b/src/Error/Code.php @@ -19,7 +19,10 @@ public function __construct(string $code) $this->code = $code; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $o->code = $this->code; } diff --git a/src/Error/Detail.php b/src/Error/Detail.php index 5950b46..dfeb86d 100644 --- a/src/Error/Detail.php +++ b/src/Error/Detail.php @@ -19,7 +19,10 @@ public function __construct(string $detail) $this->detail = $detail; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $o->detail = $this->detail; } diff --git a/src/Error/Id.php b/src/Error/Id.php index 3553f02..40eb903 100644 --- a/src/Error/Id.php +++ b/src/Error/Id.php @@ -19,7 +19,10 @@ public function __construct(string $id) $this->id = $id; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $o->id = $this->id; } diff --git a/src/Error/SourceParameter.php b/src/Error/SourceParameter.php index 613967a..05b711a 100644 --- a/src/Error/SourceParameter.php +++ b/src/Error/SourceParameter.php @@ -20,7 +20,10 @@ public function __construct(string $parameter) $this->parameter = $parameter; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { child($o, 'source')->parameter = $this->parameter; } diff --git a/src/Error/SourcePointer.php b/src/Error/SourcePointer.php index bfc6394..84815db 100644 --- a/src/Error/SourcePointer.php +++ b/src/Error/SourcePointer.php @@ -17,7 +17,10 @@ public function __construct(string $pointer) $this->pointer = $pointer; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { child($o, 'source')->pointer = $this->pointer; } diff --git a/src/Error/Status.php b/src/Error/Status.php index a61031f..0d396f6 100644 --- a/src/Error/Status.php +++ b/src/Error/Status.php @@ -19,7 +19,10 @@ public function __construct(string $status) $this->status = $status; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $o->status = $this->status; } diff --git a/src/Error/Title.php b/src/Error/Title.php index d61a706..1c18f2c 100644 --- a/src/Error/Title.php +++ b/src/Error/Title.php @@ -20,7 +20,10 @@ public function __construct(string $title) $this->title = $title; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $o->title = $this->title; } diff --git a/src/Included.php b/src/Included.php index 04c8d1c..220270b 100644 --- a/src/Included.php +++ b/src/Included.php @@ -38,7 +38,10 @@ public function validateLinkage(PrimaryData $data): void } } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { foreach ($this->resources as $resource) { $resource->attachAsIncludedTo($o); diff --git a/src/Internal/Attachable.php b/src/Internal/Attachable.php index cccd352..edd2de2 100644 --- a/src/Internal/Attachable.php +++ b/src/Internal/Attachable.php @@ -7,5 +7,8 @@ */ interface Attachable { - public function attachTo(object $o): void; + /** + * @param object $o + */ + public function attachTo($o): void; } diff --git a/src/JsonApi.php b/src/JsonApi.php index c0f2220..c895bb6 100644 --- a/src/JsonApi.php +++ b/src/JsonApi.php @@ -20,7 +20,10 @@ public function __construct(string $version = '1.0', Meta $meta = null) } } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $o->jsonapi = $this->obj; } diff --git a/src/Link/AboutLink.php b/src/Link/AboutLink.php index 31fe133..054129e 100644 --- a/src/Link/AboutLink.php +++ b/src/Link/AboutLink.php @@ -10,7 +10,10 @@ final class AboutLink implements ErrorMember { use LinkTrait; - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { child($o, 'links')->about = $this->link; } diff --git a/src/Link/FirstLink.php b/src/Link/FirstLink.php index f41ab3c..e569e86 100644 --- a/src/Link/FirstLink.php +++ b/src/Link/FirstLink.php @@ -10,7 +10,10 @@ final class FirstLink implements PaginationLink { use LinkTrait; - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { child($o, 'links')->first = $this->link; } diff --git a/src/Link/LastLink.php b/src/Link/LastLink.php index b174cb2..36a91e8 100644 --- a/src/Link/LastLink.php +++ b/src/Link/LastLink.php @@ -10,7 +10,10 @@ final class LastLink implements PaginationLink { use LinkTrait; - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { child($o, 'links')->last = $this->link; } diff --git a/src/Link/NextLink.php b/src/Link/NextLink.php index e1cce27..1b2caa1 100644 --- a/src/Link/NextLink.php +++ b/src/Link/NextLink.php @@ -10,7 +10,10 @@ final class NextLink implements PaginationLink { use LinkTrait; - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { child($o, 'links')->next = $this->link; } diff --git a/src/Link/PrevLink.php b/src/Link/PrevLink.php index 99a61c3..a02c69b 100644 --- a/src/Link/PrevLink.php +++ b/src/Link/PrevLink.php @@ -10,7 +10,10 @@ final class PrevLink implements PaginationLink { use LinkTrait; - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { child($o, 'links')->prev = $this->link; } diff --git a/src/Link/RelatedLink.php b/src/Link/RelatedLink.php index c0005ff..70ddc34 100644 --- a/src/Link/RelatedLink.php +++ b/src/Link/RelatedLink.php @@ -10,7 +10,10 @@ final class RelatedLink implements ToOneMember { use LinkTrait; - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { child($o, 'links')->related = $this->link; } diff --git a/src/Link/SelfLink.php b/src/Link/SelfLink.php index 17d96cd..f886435 100644 --- a/src/Link/SelfLink.php +++ b/src/Link/SelfLink.php @@ -12,7 +12,10 @@ final class SelfLink implements DataDocumentMember, ResourceMember, ToOneMember { use LinkTrait; - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { child($o, 'links')->self = $this->link; } diff --git a/src/Meta.php b/src/Meta.php index e186648..a267bdb 100644 --- a/src/Meta.php +++ b/src/Meta.php @@ -26,7 +26,7 @@ public function __construct(string $key, $value) $this->value = $value; } - public function attachTo(object $o): void + public function attachTo($o): void { child($o, 'meta')->{$this->key} = $this->value; } diff --git a/src/NullData.php b/src/NullData.php index 26dd8a5..03cd15e 100644 --- a/src/NullData.php +++ b/src/NullData.php @@ -6,7 +6,10 @@ final class NullData implements PrimaryData { - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $o->data = null; } diff --git a/src/PaginatedCollection.php b/src/PaginatedCollection.php index 8b8e0bd..c4a51da 100644 --- a/src/PaginatedCollection.php +++ b/src/PaginatedCollection.php @@ -22,7 +22,10 @@ public function __construct(Pagination $pagination, Collection $collection) $this->collection = $collection; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $this->collection->attachTo($o); $this->pagination->attachTo($o); diff --git a/src/Pagination.php b/src/Pagination.php index aa4f9c3..42f2ef7 100644 --- a/src/Pagination.php +++ b/src/Pagination.php @@ -18,7 +18,10 @@ public function __construct(PaginationLink ...$links) $this->links = $links; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { foreach ($this->links as $link) { $link->attachTo($o); diff --git a/src/ResourceCollection.php b/src/ResourceCollection.php index 77ac09f..e2f0c4b 100644 --- a/src/ResourceCollection.php +++ b/src/ResourceCollection.php @@ -17,7 +17,10 @@ public function __construct(ResourceObject ...$resources) $this->resources = $resources; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $o->data = []; foreach ($this->resources as $resource) { diff --git a/src/ResourceIdentifier.php b/src/ResourceIdentifier.php index 221e096..6967856 100644 --- a/src/ResourceIdentifier.php +++ b/src/ResourceIdentifier.php @@ -33,12 +33,18 @@ public function __construct(string $type, string $id, Meta $meta = null) $this->id = $id; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $o->data = $this->obj; } - public function attachToCollection(object $o): void + /** + * @param object $o + */ + public function attachToCollection($o): void { $o->data[] = $this->obj; } diff --git a/src/ResourceIdentifierCollection.php b/src/ResourceIdentifierCollection.php index d13eb11..5e87189 100644 --- a/src/ResourceIdentifierCollection.php +++ b/src/ResourceIdentifierCollection.php @@ -17,7 +17,10 @@ public function __construct(ResourceIdentifier ...$identifiers) $this->identifiers = $identifiers; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $o->data = []; foreach ($this->identifiers as $identifier) { diff --git a/src/ResourceObject.php b/src/ResourceObject.php index de0a478..bab4f25 100644 --- a/src/ResourceObject.php +++ b/src/ResourceObject.php @@ -59,17 +59,26 @@ public function registerIn(array &$registry): void $registry = array_merge($registry, $this->registry); } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $o->data = $this->obj; } - public function attachAsIncludedTo(object $o): void + /** + * @param object $o + */ + public function attachAsIncludedTo($o): void { $o->included[] = $this->obj; } - public function attachToCollection(object $o): void + /** + * @param object $o + */ + public function attachToCollection($o): void { $o->data[] = $this->obj; } diff --git a/src/ToMany.php b/src/ToMany.php index 94b873b..e4227eb 100644 --- a/src/ToMany.php +++ b/src/ToMany.php @@ -27,7 +27,10 @@ public function __construct(string $name, ResourceIdentifierCollection $collecti $this->collection = $collection; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $rel = child(child($o, 'relationships'), $this->name); $rel->data = []; diff --git a/src/ToNull.php b/src/ToNull.php index 0efaef0..4e30861 100644 --- a/src/ToNull.php +++ b/src/ToNull.php @@ -21,7 +21,10 @@ public function __construct(string $name, ToOneMember ...$members) $this->members = $members; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { $obj = combine(...$this->members); $obj->data = null; diff --git a/src/ToOne.php b/src/ToOne.php index c6d32da..dcd2e7a 100644 --- a/src/ToOne.php +++ b/src/ToOne.php @@ -26,7 +26,10 @@ public function __construct(string $name, ResourceIdentifier $identifier, ToOneM $this->identifier = $identifier; } - public function attachTo(object $o): void + /** + * @param object $o + */ + public function attachTo($o): void { child($o, 'relationships')->{$this->name} = $this->obj; } diff --git a/src/functions.php b/src/functions.php index d7c89a4..df50e2d 100644 --- a/src/functions.php +++ b/src/functions.php @@ -4,7 +4,7 @@ use JsonApiPhp\JsonApi\Internal\Attachable; -function combine(Attachable ...$members): object +function combine(Attachable ...$members) { $obj = (object) []; foreach ($members as $member) { @@ -13,7 +13,7 @@ function combine(Attachable ...$members): object return $obj; } -function child(object $o, string $name): object +function child($o, string $name) { if (!isset($o->{$name})) { $o->{$name} = (object) []; diff --git a/test/BaseTestCase.php b/test/BaseTestCase.php index 2485e47..eca3598 100644 --- a/test/BaseTestCase.php +++ b/test/BaseTestCase.php @@ -6,7 +6,7 @@ abstract class BaseTestCase extends TestCase { - public static function assertEncodesTo(string $expected, object $obj, string $message = '') + public static function assertEncodesTo(string $expected, $obj, string $message = '') { self::assertEquals( json_decode($expected), From 668ec0c9e75a44f4e72953a73d5586628c67f46c Mon Sep 17 00:00:00 2001 From: Alexey Date: Mon, 31 Dec 2018 16:44:00 -0800 Subject: [PATCH 33/45] Update CHANGELOG.md (#91) --- CHANGELOG.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bebaa62..86ca5ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] -## [2.0.0] - 2018-02-26 -V2 initial release +## [2.0.1] - 2018-12-31 +### Changed +- Downgraded min required php version to 7.1 -[Unreleased]: https://github.com/json-api-php/json-api/compare/2.0.0...HEAD +## 2.0.0 - 2018-02-26 +### Added +- v2 initial release + +[Unreleased]: https://github.com/json-api-php/json-api/compare/2.0.1...HEAD +[2.0.1]: https://github.com/json-api-php/json-api/compare/2.0.0...2.0.1 From 5f47c15571f8b8f616febcdc872ff5ddc70155aa Mon Sep 17 00:00:00 2001 From: Alexey Date: Mon, 25 Feb 2019 11:23:07 -0800 Subject: [PATCH 34/45] Relationship with no "data" member. Fixed #92 (#93) --- .travis.yml | 2 + CHANGELOG.md | 7 +++- phpunit.xml.dist | 3 +- src/EmptyRelationship.php | 31 +++++++++++++++ src/Error/SourceParameter.php | 2 +- src/Error/SourcePointer.php | 2 +- src/Internal/RelationshipMember.php | 10 +++++ src/Internal/ToManyMember.php | 5 ++- src/Link/AboutLink.php | 2 +- src/Link/FirstLink.php | 2 +- src/Link/LastLink.php | 2 +- src/Link/NextLink.php | 2 +- src/Link/PrevLink.php | 2 +- src/Link/RelatedLink.php | 6 +-- src/Link/SelfLink.php | 6 +-- src/Meta.php | 4 +- src/ToMany.php | 6 +-- test/CompoundDocumentTest.php | 4 +- test/ResourceObjectTest.php | 58 +++++++++++++++++++++++++++++ 19 files changed, 132 insertions(+), 24 deletions(-) create mode 100644 src/EmptyRelationship.php create mode 100644 src/Internal/RelationshipMember.php diff --git a/.travis.yml b/.travis.yml index 8ba1558..fe3c026 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: php php: - '7.1' + - '7.2' + - '7.3' before_script: - composer install diff --git a/CHANGELOG.md b/CHANGELOG.md index 86ca5ab..9185eeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [2.1.0] - 2019-02-25 +### Fixed +- Relationship without data property (#92) + ## [2.0.1] - 2018-12-31 ### Changed - Downgraded min required php version to 7.1 @@ -14,5 +18,6 @@ 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.0.1...HEAD +[Unreleased]: https://github.com/json-api-php/json-api/compare/2.1.0...HEAD +[2.1.0]: https://github.com/json-api-php/json-api/compare/2.0.1...2.1.0 [2.0.1]: https://github.com/json-api-php/json-api/compare/2.0.0...2.0.1 diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 48bfa68..884a29a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -3,8 +3,7 @@ bootstrap="vendor/autoload.php" stopOnFailure="false" verbose="true" - colors="true" - syntaxCheck="true"> + colors="true"> ./test diff --git a/src/EmptyRelationship.php b/src/EmptyRelationship.php new file mode 100644 index 0000000..1482792 --- /dev/null +++ b/src/EmptyRelationship.php @@ -0,0 +1,31 @@ +name = $name; + $this->obj = combine($member, ...$members); + } + + /** + * @param object $o + */ + public function attachTo($o): void + { + child($o, 'relationships')->{$this->name} = $this->obj; + } +} diff --git a/src/Error/SourceParameter.php b/src/Error/SourceParameter.php index 05b711a..f3bc37a 100644 --- a/src/Error/SourceParameter.php +++ b/src/Error/SourceParameter.php @@ -2,8 +2,8 @@ namespace JsonApiPhp\JsonApi\Error; -use JsonApiPhp\JsonApi\Internal\ErrorMember; use function JsonApiPhp\JsonApi\child; +use JsonApiPhp\JsonApi\Internal\ErrorMember; final class SourceParameter implements ErrorMember { diff --git a/src/Error/SourcePointer.php b/src/Error/SourcePointer.php index 84815db..0af0b5b 100644 --- a/src/Error/SourcePointer.php +++ b/src/Error/SourcePointer.php @@ -2,8 +2,8 @@ namespace JsonApiPhp\JsonApi\Error; -use JsonApiPhp\JsonApi\Internal\ErrorMember; use function JsonApiPhp\JsonApi\child; +use JsonApiPhp\JsonApi\Internal\ErrorMember; final class SourcePointer implements ErrorMember { diff --git a/src/Internal/RelationshipMember.php b/src/Internal/RelationshipMember.php new file mode 100644 index 0000000..3746dee --- /dev/null +++ b/src/Internal/RelationshipMember.php @@ -0,0 +1,10 @@ +validateFieldName($name); $this->name = $name; diff --git a/test/CompoundDocumentTest.php b/test/CompoundDocumentTest.php index 3d62ff9..7441200 100644 --- a/test/CompoundDocumentTest.php +++ b/test/CompoundDocumentTest.php @@ -253,11 +253,11 @@ public function testIncludedResourceMayBeIdentifiedByAnotherLinkedResource() /** * A compound document MUST NOT include more than one resource object for each type and id pair. - * @expectedException \LogicException - * @expectedExceptionMessage Resource apples:1 is already included */ public function testCanNotBeManyIncludedResourcesWithEqualIdentifiers() { + $this->expectException('LogicException'); + $this->expectExceptionMessage('Resource apples:1 is already included'); $apple = new ResourceObject('apples', '1'); new CompoundDocument($apple->identifier(), new Included($apple, $apple)); } diff --git a/test/ResourceObjectTest.php b/test/ResourceObjectTest.php index 37e845f..3541c8d 100644 --- a/test/ResourceObjectTest.php +++ b/test/ResourceObjectTest.php @@ -4,6 +4,7 @@ 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; @@ -152,6 +153,63 @@ public function testRelationshipWithEmptyMultiIdLinkage() ); } + public function testRelationshipWithNoData() + { + $this->assertEncodesTo( + ' + { + "data": { + "type": "basket", + "id": "1", + "relationships": { + "empty": { + "links": { + "related": "/foo" + } + } + } + } + } + ', + new DataDocument( + new ResourceObject( + 'basket', + '1', + new EmptyRelationship('empty', new RelatedLink('/foo')) + ) + ) + ); + + $this->assertEncodesTo( + ' + { + "data": { + "type": "basket", + "id": "1", + "relationships": { + "empty": { + "links": { + "related": "/foo", + "self": "/bar" + }, + "meta": { + "foo": "bar" + } + } + } + } + } + ', + new DataDocument( + new ResourceObject( + 'basket', + '1', + new EmptyRelationship('empty', new RelatedLink('/foo'), new SelfLink('/bar'), new Meta('foo', 'bar')) + ) + ) + ); + } + public function testCanNotCreateIdAttribute() { $this->expectException(\DomainException::class); From bc68950744ee9ba056dda5ca4b3b4fc935ef8d3c Mon Sep 17 00:00:00 2001 From: Alexey Date: Mon, 9 Sep 2019 12:43:33 -0700 Subject: [PATCH 35/45] Fix travis builds (#96) --- .travis.yml | 4 ++-- examples/compound_doc.php | 1 - test/CompoundDocumentTest.php | 1 - test/benchmarks/compound10k.php | 1 - 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index fe3c026..59e3899 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,5 +9,5 @@ before_script: - mkdir build/logs -p script: - - vendor/bin/php-cs-fixer fix -v --dry-run - - phpunit --coverage-clover build/logs/clover.xml + - ./vendor/bin/php-cs-fixer fix -v --dry-run + - ./vendor/bin/phpunit --coverage-clover build/logs/clover.xml diff --git a/examples/compound_doc.php b/examples/compound_doc.php index ddb6fd9..aef8cbf 100644 --- a/examples/compound_doc.php +++ b/examples/compound_doc.php @@ -33,7 +33,6 @@ new Attribute('body', 'First!'), new SelfLink('http://example.com/comments/5'), new ToOne('author', new ResourceIdentifier('people', '2')) - ); $comment12 = new ResourceObject( 'comments', diff --git a/test/CompoundDocumentTest.php b/test/CompoundDocumentTest.php index 7441200..d3328a1 100644 --- a/test/CompoundDocumentTest.php +++ b/test/CompoundDocumentTest.php @@ -38,7 +38,6 @@ public function testOfficialDocsExample() new Attribute('body', 'First!'), new SelfLink('http://example.com/comments/5'), new ToOne('author', new ResourceIdentifier('people', '2')) - ); $comment12 = new ResourceObject( 'comments', diff --git a/test/benchmarks/compound10k.php b/test/benchmarks/compound10k.php index c1a0ae2..d220851 100644 --- a/test/benchmarks/compound10k.php +++ b/test/benchmarks/compound10k.php @@ -48,7 +48,6 @@ new Attribute('body', 'First!'), new SelfLink('http://example.com/comments/5'), new ToOne('author', new ResourceIdentifier('people', '2')) - ); $comment12 = new ResourceObject( 'comments', From 2338d0f8c4f9e97583beaad32e56148498deedce Mon Sep 17 00:00:00 2001 From: Alexey Date: Mon, 9 Sep 2019 21:14:20 -0700 Subject: [PATCH 36/45] Add "ext-json" as a dependency to composer.json, allow phpunit 8.0. (#97) --- README.md | 2 +- composer.json | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8e831cf..6909798 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ First, take a look at the examples. All of them are runnable. - [Simple Document](./examples/simple_doc.php) (the same as above) - [Extensive Compound Document](./examples/compound_doc.php) -The library API and use-cases are expressed in comprehensive suite of tests. +The library API and use-cases are expressed in a comprehensive suite of tests. - Data Documents (containing primary data) - [with a single Resource Object](./test/DataDocument/SingleResourceObjectTest.php) - [with a single Resource Identifier](./test/DataDocument/SingleResourceIdentifierTest.php) diff --git a/composer.json b/composer.json index 50b9f32..76bbb66 100644 --- a/composer.json +++ b/composer.json @@ -11,10 +11,12 @@ } ], "require": { - "php": ">=7.1" + "php": ">=7.1", + "ext-json": "*" + }, "require-dev": { - "phpunit/phpunit": "^7.0", + "phpunit/phpunit": "^7.0||^8.0", "friendsofphp/php-cs-fixer": "^2.13" }, "autoload": { From 4fb3c7a30b6f155f41fb2dc167934f4dc0acdf7b Mon Sep 17 00:00:00 2001 From: Sergey Braga Date: Tue, 10 Sep 2019 20:16:17 +0300 Subject: [PATCH 37/45] Add "@internal" annotation to attachTo() (#94) --- examples/compound_doc.php | 1 + src/Attribute.php | 1 + src/EmptyRelationship.php | 1 + src/Error.php | 1 + src/Error/Code.php | 1 + src/Error/Detail.php | 1 + src/Error/Id.php | 1 + src/Error/SourceParameter.php | 1 + src/Error/SourcePointer.php | 1 + src/Error/Status.php | 1 + src/Error/Title.php | 1 + src/ErrorDocument.php | 3 +-- src/Included.php | 1 + src/Internal/Attachable.php | 1 + src/JsonApi.php | 1 + src/Link/AboutLink.php | 1 + src/Link/FirstLink.php | 1 + src/Link/LastLink.php | 1 + src/Link/NextLink.php | 1 + src/Link/PrevLink.php | 1 + src/Link/RelatedLink.php | 1 + src/Link/SelfLink.php | 1 + src/Meta.php | 4 ++++ src/NullData.php | 1 + src/PaginatedCollection.php | 1 + src/Pagination.php | 1 + src/ResourceCollection.php | 1 + src/ResourceIdentifier.php | 2 ++ src/ResourceIdentifierCollection.php | 1 + src/ResourceObject.php | 2 ++ src/ToMany.php | 1 + src/ToNull.php | 1 + src/ToOne.php | 1 + test/CompoundDocumentTest.php | 1 + test/benchmarks/compound10k.php | 1 + 35 files changed, 40 insertions(+), 2 deletions(-) diff --git a/examples/compound_doc.php b/examples/compound_doc.php index aef8cbf..c9feae0 100644 --- a/examples/compound_doc.php +++ b/examples/compound_doc.php @@ -34,6 +34,7 @@ new SelfLink('http://example.com/comments/5'), new ToOne('author', new ResourceIdentifier('people', '2')) ); + $comment12 = new ResourceObject( 'comments', '12', diff --git a/src/Attribute.php b/src/Attribute.php index ba1f50b..d45834c 100644 --- a/src/Attribute.php +++ b/src/Attribute.php @@ -22,6 +22,7 @@ public function __construct(string $name, $val) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/EmptyRelationship.php b/src/EmptyRelationship.php index 1482792..659a9df 100644 --- a/src/EmptyRelationship.php +++ b/src/EmptyRelationship.php @@ -23,6 +23,7 @@ public function __construct(string $name, RelationshipMember $member, Relationsh /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Error.php b/src/Error.php index 35b7b8d..ba0e3a0 100644 --- a/src/Error.php +++ b/src/Error.php @@ -23,6 +23,7 @@ public function __construct(ErrorMember ...$members) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Error/Code.php b/src/Error/Code.php index 6705037..a03b827 100644 --- a/src/Error/Code.php +++ b/src/Error/Code.php @@ -21,6 +21,7 @@ public function __construct(string $code) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Error/Detail.php b/src/Error/Detail.php index dfeb86d..f4d169c 100644 --- a/src/Error/Detail.php +++ b/src/Error/Detail.php @@ -21,6 +21,7 @@ public function __construct(string $detail) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Error/Id.php b/src/Error/Id.php index 40eb903..11c5b10 100644 --- a/src/Error/Id.php +++ b/src/Error/Id.php @@ -21,6 +21,7 @@ public function __construct(string $id) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Error/SourceParameter.php b/src/Error/SourceParameter.php index f3bc37a..3e48461 100644 --- a/src/Error/SourceParameter.php +++ b/src/Error/SourceParameter.php @@ -22,6 +22,7 @@ public function __construct(string $parameter) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Error/SourcePointer.php b/src/Error/SourcePointer.php index 0af0b5b..d6cecaa 100644 --- a/src/Error/SourcePointer.php +++ b/src/Error/SourcePointer.php @@ -19,6 +19,7 @@ public function __construct(string $pointer) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Error/Status.php b/src/Error/Status.php index 0d396f6..6be53aa 100644 --- a/src/Error/Status.php +++ b/src/Error/Status.php @@ -21,6 +21,7 @@ public function __construct(string $status) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Error/Title.php b/src/Error/Title.php index 1c18f2c..2226f53 100644 --- a/src/Error/Title.php +++ b/src/Error/Title.php @@ -22,6 +22,7 @@ public function __construct(string $title) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/ErrorDocument.php b/src/ErrorDocument.php index e4a52e5..4d4fe73 100644 --- a/src/ErrorDocument.php +++ b/src/ErrorDocument.php @@ -12,10 +12,9 @@ final class ErrorDocument implements \JsonSerializable { private $obj; - public function __construct(Error $error, ErrorDocumentMember ...$members) + public function __construct(ErrorDocumentMember ...$members) { $this->obj = (object) []; - $error->attachTo($this->obj); foreach ($members as $member) { $member->attachTo($this->obj); } diff --git a/src/Included.php b/src/Included.php index 220270b..5b2dbd4 100644 --- a/src/Included.php +++ b/src/Included.php @@ -40,6 +40,7 @@ public function validateLinkage(PrimaryData $data): void /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Internal/Attachable.php b/src/Internal/Attachable.php index edd2de2..0178295 100644 --- a/src/Internal/Attachable.php +++ b/src/Internal/Attachable.php @@ -9,6 +9,7 @@ interface Attachable { /** * @param object $o + * @internal */ public function attachTo($o): void; } diff --git a/src/JsonApi.php b/src/JsonApi.php index c895bb6..3c1e08b 100644 --- a/src/JsonApi.php +++ b/src/JsonApi.php @@ -22,6 +22,7 @@ public function __construct(string $version = '1.0', Meta $meta = null) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Link/AboutLink.php b/src/Link/AboutLink.php index 6035330..e317ca8 100644 --- a/src/Link/AboutLink.php +++ b/src/Link/AboutLink.php @@ -12,6 +12,7 @@ final class AboutLink implements ErrorMember /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Link/FirstLink.php b/src/Link/FirstLink.php index 755a12b..af13251 100644 --- a/src/Link/FirstLink.php +++ b/src/Link/FirstLink.php @@ -12,6 +12,7 @@ final class FirstLink implements PaginationLink /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Link/LastLink.php b/src/Link/LastLink.php index c11b2e0..037b228 100644 --- a/src/Link/LastLink.php +++ b/src/Link/LastLink.php @@ -12,6 +12,7 @@ final class LastLink implements PaginationLink /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Link/NextLink.php b/src/Link/NextLink.php index 39f5641..c2ba366 100644 --- a/src/Link/NextLink.php +++ b/src/Link/NextLink.php @@ -12,6 +12,7 @@ final class NextLink implements PaginationLink /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Link/PrevLink.php b/src/Link/PrevLink.php index f38d94b..b612450 100644 --- a/src/Link/PrevLink.php +++ b/src/Link/PrevLink.php @@ -12,6 +12,7 @@ final class PrevLink implements PaginationLink /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Link/RelatedLink.php b/src/Link/RelatedLink.php index 7434423..c1924ee 100644 --- a/src/Link/RelatedLink.php +++ b/src/Link/RelatedLink.php @@ -12,6 +12,7 @@ final class RelatedLink implements RelationshipMember /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Link/SelfLink.php b/src/Link/SelfLink.php index 5a9a31f..fde232b 100644 --- a/src/Link/SelfLink.php +++ b/src/Link/SelfLink.php @@ -14,6 +14,7 @@ final class SelfLink implements DataDocumentMember, ResourceMember, Relationship /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Meta.php b/src/Meta.php index 5b9a2c3..758c3bb 100644 --- a/src/Meta.php +++ b/src/Meta.php @@ -26,6 +26,10 @@ public function __construct(string $key, $value) $this->value = $value; } + /** + * @param object $o + * @internal + */ public function attachTo($o): void { child($o, 'meta')->{$this->key} = $this->value; diff --git a/src/NullData.php b/src/NullData.php index 03cd15e..bbb3dbc 100644 --- a/src/NullData.php +++ b/src/NullData.php @@ -8,6 +8,7 @@ final class NullData implements PrimaryData { /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/PaginatedCollection.php b/src/PaginatedCollection.php index c4a51da..15e3cc7 100644 --- a/src/PaginatedCollection.php +++ b/src/PaginatedCollection.php @@ -24,6 +24,7 @@ public function __construct(Pagination $pagination, Collection $collection) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/Pagination.php b/src/Pagination.php index 42f2ef7..674f375 100644 --- a/src/Pagination.php +++ b/src/Pagination.php @@ -20,6 +20,7 @@ public function __construct(PaginationLink ...$links) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/ResourceCollection.php b/src/ResourceCollection.php index e2f0c4b..f337ad5 100644 --- a/src/ResourceCollection.php +++ b/src/ResourceCollection.php @@ -19,6 +19,7 @@ public function __construct(ResourceObject ...$resources) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/ResourceIdentifier.php b/src/ResourceIdentifier.php index 6967856..2974492 100644 --- a/src/ResourceIdentifier.php +++ b/src/ResourceIdentifier.php @@ -35,6 +35,7 @@ public function __construct(string $type, string $id, Meta $meta = null) /** * @param object $o + * @internal */ public function attachTo($o): void { @@ -43,6 +44,7 @@ public function attachTo($o): void /** * @param object $o + * @internal */ public function attachToCollection($o): void { diff --git a/src/ResourceIdentifierCollection.php b/src/ResourceIdentifierCollection.php index 5e87189..84b8887 100644 --- a/src/ResourceIdentifierCollection.php +++ b/src/ResourceIdentifierCollection.php @@ -19,6 +19,7 @@ public function __construct(ResourceIdentifier ...$identifiers) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/ResourceObject.php b/src/ResourceObject.php index bab4f25..20ce548 100644 --- a/src/ResourceObject.php +++ b/src/ResourceObject.php @@ -61,6 +61,7 @@ public function registerIn(array &$registry): void /** * @param object $o + * @internal */ public function attachTo($o): void { @@ -77,6 +78,7 @@ public function attachAsIncludedTo($o): void /** * @param object $o + * @internal */ public function attachToCollection($o): void { diff --git a/src/ToMany.php b/src/ToMany.php index 0811006..c0a7278 100644 --- a/src/ToMany.php +++ b/src/ToMany.php @@ -29,6 +29,7 @@ public function __construct(string $name, ResourceIdentifierCollection $collecti /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/ToNull.php b/src/ToNull.php index 4e30861..8b2d3f1 100644 --- a/src/ToNull.php +++ b/src/ToNull.php @@ -23,6 +23,7 @@ public function __construct(string $name, ToOneMember ...$members) /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/src/ToOne.php b/src/ToOne.php index dcd2e7a..dbbd7ac 100644 --- a/src/ToOne.php +++ b/src/ToOne.php @@ -28,6 +28,7 @@ public function __construct(string $name, ResourceIdentifier $identifier, ToOneM /** * @param object $o + * @internal */ public function attachTo($o): void { diff --git a/test/CompoundDocumentTest.php b/test/CompoundDocumentTest.php index d3328a1..bb013ce 100644 --- a/test/CompoundDocumentTest.php +++ b/test/CompoundDocumentTest.php @@ -39,6 +39,7 @@ public function testOfficialDocsExample() new SelfLink('http://example.com/comments/5'), new ToOne('author', new ResourceIdentifier('people', '2')) ); + $comment12 = new ResourceObject( 'comments', '12', diff --git a/test/benchmarks/compound10k.php b/test/benchmarks/compound10k.php index d220851..513518b 100644 --- a/test/benchmarks/compound10k.php +++ b/test/benchmarks/compound10k.php @@ -49,6 +49,7 @@ new SelfLink('http://example.com/comments/5'), new ToOne('author', new ResourceIdentifier('people', '2')) ); + $comment12 = new ResourceObject( 'comments', '12', From 5a683218ea20258ccc0cfe9ba0ae677925c60d50 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 19 Dec 2019 13:59:57 -0800 Subject: [PATCH 38/45] ResourceIdentifier to allow multiple meta props (fixes #99) (#100) --- .travis.yml | 1 + CHANGELOG.md | 7 ++++++- src/ResourceIdentifier.php | 4 ++-- test/DataDocument/SingleResourceIdentifierTest.php | 8 ++++++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 59e3899..b2a600d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ php: - '7.1' - '7.2' - '7.3' + - '7.4' before_script: - composer install diff --git a/CHANGELOG.md b/CHANGELOG.md index 9185eeb..d565111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [2.1.1] - 2019-12-19 +### Fixed +- ResourceIdentifier does not allow multiple meta members (#99) + ## [2.1.0] - 2019-02-25 ### Fixed - Relationship without data property (#92) @@ -18,6 +22,7 @@ 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.0...HEAD +[Unreleased]: https://github.com/json-api-php/json-api/compare/2.1.1...HEAD +[2.2.0]: 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 [2.0.1]: https://github.com/json-api-php/json-api/compare/2.0.0...2.0.1 diff --git a/src/ResourceIdentifier.php b/src/ResourceIdentifier.php index 2974492..dff34f8 100644 --- a/src/ResourceIdentifier.php +++ b/src/ResourceIdentifier.php @@ -16,7 +16,7 @@ final class ResourceIdentifier implements PrimaryData */ private $id; - public function __construct(string $type, string $id, Meta $meta = null) + public function __construct(string $type, string $id, Meta ...$metas) { if (isValidName($type) === false) { throw new \DomainException("Invalid type value: $type"); @@ -26,7 +26,7 @@ public function __construct(string $type, string $id, Meta $meta = null) 'type' => $type, 'id' => $id, ]; - if ($meta) { + foreach ($metas as $meta) { $meta->attachTo($this->obj); } $this->type = $type; diff --git a/test/DataDocument/SingleResourceIdentifierTest.php b/test/DataDocument/SingleResourceIdentifierTest.php index 92a91d5..1d12a77 100644 --- a/test/DataDocument/SingleResourceIdentifierTest.php +++ b/test/DataDocument/SingleResourceIdentifierTest.php @@ -36,7 +36,10 @@ public function testExtendedDocument() "data": { "type": "apples", "id": "1", - "meta": {"apple_meta": "foo"} + "meta": { + "apple_meta": "foo", + "bar": [42] + } }, "links": { "self": "/apples/1" @@ -51,7 +54,8 @@ public function testExtendedDocument() new ResourceIdentifier( 'apples', '1', - new Meta('apple_meta', 'foo') + new Meta('apple_meta', 'foo'), + new Meta('bar', [42]) ), new SelfLink('/apples/1'), new JsonApi(), From 1f74ccf36fa80149401b94f279a3d535acb3bd49 Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 30 Jan 2020 13:03:12 -0800 Subject: [PATCH 39/45] Add a self link to the paginated collection example (#102) --- test/PaginationTest.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/PaginationTest.php b/test/PaginationTest.php index 17ee668..7add66f 100644 --- a/test/PaginationTest.php +++ b/test/PaginationTest.php @@ -10,6 +10,7 @@ use JsonApiPhp\JsonApi\Link\LastLink; use JsonApiPhp\JsonApi\Link\NextLink; use JsonApiPhp\JsonApi\Link\PrevLink; +use JsonApiPhp\JsonApi\Link\SelfLink; use JsonApiPhp\JsonApi\PaginatedCollection; use JsonApiPhp\JsonApi\Pagination; use JsonApiPhp\JsonApi\ResourceCollection; @@ -33,7 +34,8 @@ public function testPaginatedResourceCollection() "first": "http://example.com/fruits?page=first", "last": "http://example.com/fruits?page=last", "prev": "http://example.com/fruits?page=3", - "next": "http://example.com/fruits?page=5" + "next": "http://example.com/fruits?page=5", + "self": "http://example.com/fruits?page=4" } } ', @@ -49,7 +51,8 @@ public function testPaginatedResourceCollection() new ResourceObject('apples', '1'), new ResourceObject('apples', '2') ) - ) + ), + new SelfLink('http://example.com/fruits?page=4') ) ); } From 33235447a5b3e27809adc1a98f88484b3506788a Mon Sep 17 00:00:00 2001 From: Alexey Date: Thu, 30 Jan 2020 23:17:21 -0800 Subject: [PATCH 40/45] Create FUNDING.yml (#103) --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..66a983e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +custom: "https://www.paypal.me/f3ath" + From 4eb1497ffe1dd685716a66f92ceb1561d7bc7045 Mon Sep 17 00:00:00 2001 From: Alexey Date: Mon, 16 Mar 2020 18:53:17 -0700 Subject: [PATCH 41/45] Allow RelatedLink in Document. Fixes #104 (#105) * Allow RelatedLink in Document. Fixes #104 --- CHANGELOG.md | 9 +++++-- composer.json | 1 - src/Internal/Identifier.php | 2 +- src/Link/RelatedLink.php | 3 ++- src/PaginatedCollection.php | 2 +- test/DataDocument/ManyResourceObjectsTest.php | 27 +++++++++---------- .../SingleResourceIdentifierTest.php | 15 ++++++----- 7 files changed, 33 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d565111..3f4bc42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [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) @@ -22,7 +26,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.1...HEAD -[2.2.0]: https://github.com/json-api-php/json-api/compare/2.1.0...2.1.1 +[Unreleased]: https://github.com/json-api-php/json-api/compare/2.1.2...HEAD +[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 [2.0.1]: https://github.com/json-api-php/json-api/compare/2.0.0...2.0.1 diff --git a/composer.json b/composer.json index 76bbb66..ffcd77e 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,6 @@ "require": { "php": ">=7.1", "ext-json": "*" - }, "require-dev": { "phpunit/phpunit": "^7.0||^8.0", diff --git a/src/Internal/Identifier.php b/src/Internal/Identifier.php index fa435a1..f8f9e47 100644 --- a/src/Internal/Identifier.php +++ b/src/Internal/Identifier.php @@ -8,8 +8,8 @@ interface Identifier { /** - * @internal * @param array $registry + * @internal */ public function registerIn(array &$registry): void; } diff --git a/src/Link/RelatedLink.php b/src/Link/RelatedLink.php index c1924ee..1def33c 100644 --- a/src/Link/RelatedLink.php +++ b/src/Link/RelatedLink.php @@ -3,10 +3,11 @@ namespace JsonApiPhp\JsonApi\Link; use function JsonApiPhp\JsonApi\child; +use JsonApiPhp\JsonApi\Internal\DataDocumentMember; use JsonApiPhp\JsonApi\Internal\LinkTrait; use JsonApiPhp\JsonApi\Internal\RelationshipMember; -final class RelatedLink implements RelationshipMember +final class RelatedLink implements RelationshipMember, DataDocumentMember { use LinkTrait; diff --git a/src/PaginatedCollection.php b/src/PaginatedCollection.php index 15e3cc7..e59c66f 100644 --- a/src/PaginatedCollection.php +++ b/src/PaginatedCollection.php @@ -33,8 +33,8 @@ public function attachTo($o): void } /** - * @internal * @param array $registry + * @internal */ public function registerIn(array &$registry): void { diff --git a/test/DataDocument/ManyResourceObjectsTest.php b/test/DataDocument/ManyResourceObjectsTest.php index 3a2be82..54736e2 100644 --- a/test/DataDocument/ManyResourceObjectsTest.php +++ b/test/DataDocument/ManyResourceObjectsTest.php @@ -5,6 +5,7 @@ use JsonApiPhp\JsonApi\Attribute; use JsonApiPhp\JsonApi\DataDocument; use JsonApiPhp\JsonApi\JsonApi; +use JsonApiPhp\JsonApi\Link\RelatedLink; use JsonApiPhp\JsonApi\Link\SelfLink; use JsonApiPhp\JsonApi\Meta; use JsonApiPhp\JsonApi\ResourceCollection; @@ -33,24 +34,23 @@ public function testExtendedDocument() ' { "data": [{ - "type": "apples", + "type": "people", "id": "1", "attributes": { - "color": "red", - "sort": "Fuji" + "name": "Martin Fowler" }, "meta": {"apple_meta": "foo"} },{ - "type": "apples", + "type": "people", "id": "2", "attributes": { - "color": "yellow", - "sort": "Gala" + "name": "Kent Beck" }, "meta": {"apple_meta": "foo"} }], "links": { - "self": "/apples" + "self": "/books/123/relationships/authors", + "related": "/books/123/authors" }, "jsonapi": { "version": "1.0" @@ -61,21 +61,20 @@ public function testExtendedDocument() new DataDocument( new ResourceCollection( new ResourceObject( - 'apples', + 'people', '1', - new Attribute('color', 'red'), - new Attribute('sort', 'Fuji'), + new Attribute('name', 'Martin Fowler'), new Meta('apple_meta', 'foo') ), new ResourceObject( - 'apples', + 'people', '2', - new Attribute('color', 'yellow'), - new Attribute('sort', 'Gala'), + new Attribute('name', 'Kent Beck'), new Meta('apple_meta', 'foo') ) ), - new SelfLink('/apples'), + new SelfLink('/books/123/relationships/authors'), + new RelatedLink('/books/123/authors'), new JsonApi(), new Meta('document_meta', 'bar') ) diff --git a/test/DataDocument/SingleResourceIdentifierTest.php b/test/DataDocument/SingleResourceIdentifierTest.php index 1d12a77..165a625 100644 --- a/test/DataDocument/SingleResourceIdentifierTest.php +++ b/test/DataDocument/SingleResourceIdentifierTest.php @@ -4,6 +4,7 @@ use JsonApiPhp\JsonApi\DataDocument; use JsonApiPhp\JsonApi\JsonApi; +use JsonApiPhp\JsonApi\Link\RelatedLink; use JsonApiPhp\JsonApi\Link\SelfLink; use JsonApiPhp\JsonApi\Meta; use JsonApiPhp\JsonApi\ResourceIdentifier; @@ -17,13 +18,13 @@ public function testMinimalDocument() ' { "data": { - "type": "apples", + "type": "companies", "id": "1" } } ', new DataDocument( - new ResourceIdentifier('apples', '1') + new ResourceIdentifier('companies', '1') ) ); } @@ -34,7 +35,7 @@ public function testExtendedDocument() ' { "data": { - "type": "apples", + "type": "companies", "id": "1", "meta": { "apple_meta": "foo", @@ -42,7 +43,8 @@ public function testExtendedDocument() } }, "links": { - "self": "/apples/1" + "self": "/books/123/relationships/publisher", + "related": "/books/123/publisher" }, "jsonapi": { "version": "1.0" @@ -52,12 +54,13 @@ public function testExtendedDocument() ', new DataDocument( new ResourceIdentifier( - 'apples', + 'companies', '1', new Meta('apple_meta', 'foo'), new Meta('bar', [42]) ), - new SelfLink('/apples/1'), + new SelfLink('/books/123/relationships/publisher'), + new RelatedLink('/books/123/publisher'), new JsonApi(), new Meta('document_meta', 'bar') ) From fcecfd38ee5ea58885cfb70e6033eb3983c4fc10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?The=20=D0=9A=D0=BE=D0=BD=D1=8C?= Date: Mon, 12 Oct 2020 17:41:38 -0700 Subject: [PATCH 42/45] NewResourceObject to allow omitting id in resources to-be-created (#108) --- CHANGELOG.md | 9 +- README.md | 1 + src/Internal/Attachable.php | 1 + src/Internal/BaseResource.php | 61 ++++++++++ src/Internal/PrimaryData.php | 2 +- src/NewResourceObject.php | 20 ++++ src/ResourceObject.php | 31 +---- test/NewResourceObjectTest.php | 207 +++++++++++++++++++++++++++++++++ 8 files changed, 302 insertions(+), 30 deletions(-) create mode 100644 src/Internal/BaseResource.php create mode 100644 src/NewResourceObject.php create mode 100644 test/NewResourceObjectTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f4bc42..9b5bc97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 diff --git a/README.md b/README.md index 6909798..2272090 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/src/Internal/Attachable.php b/src/Internal/Attachable.php index 0178295..f11bbba 100644 --- a/src/Internal/Attachable.php +++ b/src/Internal/Attachable.php @@ -8,6 +8,7 @@ interface Attachable { /** + * Adds this object's data to $o * @param object $o * @internal */ diff --git a/src/Internal/BaseResource.php b/src/Internal/BaseResource.php new file mode 100644 index 0000000..48e8635 --- /dev/null +++ b/src/Internal/BaseResource.php @@ -0,0 +1,61 @@ +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; + } +} diff --git a/src/Internal/PrimaryData.php b/src/Internal/PrimaryData.php index aeeb7ad..c15b956 100644 --- a/src/Internal/PrimaryData.php +++ b/src/Internal/PrimaryData.php @@ -5,6 +5,6 @@ /** * @internal */ -interface PrimaryData extends Attachable, Identifier +interface PrimaryData extends Attachable { } diff --git a/src/NewResourceObject.php b/src/NewResourceObject.php new file mode 100644 index 0000000..0f54e5b --- /dev/null +++ b/src/NewResourceObject.php @@ -0,0 +1,20 @@ +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; } diff --git a/test/NewResourceObjectTest.php b/test/NewResourceObjectTest.php new file mode 100644 index 0000000..6182ad9 --- /dev/null +++ b/test/NewResourceObjectTest.php @@ -0,0 +1,207 @@ +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'); + } +} From 1bc2e2155cb9a91c43571fe5a7a9198dbf166766 Mon Sep 17 00:00:00 2001 From: Michal Vrchota Date: Wed, 27 Jul 2022 00:17:21 +0200 Subject: [PATCH 43/45] Suppress deprecated notices in PHP 8.1 (#111) --- src/CompoundDocument.php | 1 + src/DataDocument.php | 1 + src/ErrorDocument.php | 1 + src/MetaDocument.php | 1 + 4 files changed, 4 insertions(+) diff --git a/src/CompoundDocument.php b/src/CompoundDocument.php index 7e84887..a01c84d 100644 --- a/src/CompoundDocument.php +++ b/src/CompoundDocument.php @@ -19,6 +19,7 @@ public function __construct(PrimaryData $data, Included $included, DataDocumentM $this->doc = combine($data, $included, ...$members); } + #[\ReturnTypeWillChange] public function jsonSerialize() { return $this->doc; diff --git a/src/DataDocument.php b/src/DataDocument.php index d8b986d..3678afa 100644 --- a/src/DataDocument.php +++ b/src/DataDocument.php @@ -18,6 +18,7 @@ public function __construct(PrimaryData $data, DataDocumentMember ...$members) $this->value = combine($data, ...$members); } + #[\ReturnTypeWillChange] public function jsonSerialize() { return $this->value; diff --git a/src/ErrorDocument.php b/src/ErrorDocument.php index 4d4fe73..554c8d5 100644 --- a/src/ErrorDocument.php +++ b/src/ErrorDocument.php @@ -20,6 +20,7 @@ public function __construct(ErrorDocumentMember ...$members) } } + #[\ReturnTypeWillChange] public function jsonSerialize() { return $this->obj; diff --git a/src/MetaDocument.php b/src/MetaDocument.php index 72ca320..45199c9 100644 --- a/src/MetaDocument.php +++ b/src/MetaDocument.php @@ -13,6 +13,7 @@ public function __construct(Meta $meta, MetaDocumentMember ...$members) $this->doc = combine($meta, ...$members); } + #[\ReturnTypeWillChange] public function jsonSerialize() { return $this->doc; From ba2c50d7497abd127a7e1fc1373b4c9f38d28b9d Mon Sep 17 00:00:00 2001 From: Kara Date: Tue, 26 Jul 2022 17:47:30 -0700 Subject: [PATCH 44/45] Create php.yml --- .github/workflows/php.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/php.yml diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 0000000..eae93ab --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,36 @@ +name: PHP Composer + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Run test suite + run: composer run-script test From e34075c8335cb3512e38059c13a9a37217d3a178 Mon Sep 17 00:00:00 2001 From: Kara Date: Tue, 26 Jul 2022 18:10:35 -0700 Subject: [PATCH 45/45] PHP 8 (#112) --- .gitignore | 2 + .php-cs-fixer.php | 16 ++++ .php_cs.dist | 53 ------------- .travis.yml | 14 ---- CHANGELOG.md | 11 ++- composer.json | 8 +- examples/compound_doc.php | 4 +- examples/simple_doc.php | 4 +- phpunit.xml.dist | 28 +++---- src/Attribute.php | 16 ++-- src/CompoundDocument.php | 18 ++--- src/DataDocument.php | 17 ++--- src/EmptyRelationship.php | 20 ++--- src/Error.php | 17 ++--- src/Error/Code.php | 19 ++--- src/Error/Detail.php | 19 ++--- src/Error/Id.php | 19 ++--- src/Error/SourceParameter.php | 20 ++--- src/Error/SourcePointer.php | 17 ++--- src/Error/Status.php | 19 ++--- src/Error/Title.php | 19 ++--- src/ErrorDocument.php | 19 +++-- src/Included.php | 36 +++------ src/Internal/Attachable.php | 9 ++- src/Internal/BaseResource.php | 37 ++++----- src/Internal/Collection.php | 7 +- src/Internal/DataDocumentMember.php | 7 +- src/Internal/ErrorDocumentMember.php | 7 +- src/Internal/ErrorMember.php | 7 +- src/Internal/Identifier.php | 12 +-- src/Internal/LinkTrait.php | 17 ++--- src/Internal/MetaDocumentMember.php | 7 +- src/Internal/PaginationLink.php | 4 +- src/Internal/PrimaryData.php | 7 +- src/Internal/RelationshipMember.php | 7 +- src/Internal/ResourceField.php | 4 +- src/Internal/ResourceFieldTrait.php | 23 +++--- src/Internal/ResourceMember.php | 7 +- src/Internal/ToManyMember.php | 7 +- src/Internal/ToOneMember.php | 7 +- src/JsonApi.php | 21 +++-- src/Link/AboutLink.php | 13 ++-- src/Link/FirstLink.php | 13 ++-- src/Link/LastLink.php | 13 ++-- src/Link/NextLink.php | 13 ++-- src/Link/PrevLink.php | 13 ++-- src/Link/RelatedLink.php | 13 ++-- src/Link/SelfLink.php | 13 ++-- src/Meta.php | 49 ++++++------ src/MetaDocument.php | 17 ++--- src/NewResourceObject.php | 10 +-- src/NullData.php | 14 ++-- src/PaginatedCollection.php | 33 ++------ src/Pagination.php | 12 ++- src/ResourceCollection.php | 22 ++---- src/ResourceIdentifier.php | 39 +++------- src/ResourceIdentifierCollection.php | 22 ++---- src/ResourceObject.php | 39 +++------- src/ToMany.php | 30 +++----- src/ToNull.php | 16 ++-- src/ToOne.php | 26 ++----- src/functions.php | 20 +++-- test/BaseTestCase.php | 10 +-- test/CompoundDocumentTest.php | 76 ++----------------- .../ManyResourceIdentifiersTest.php | 13 ++-- test/DataDocument/ManyResourceObjectsTest.php | 13 ++-- test/DataDocument/NullDataTest.php | 10 +-- .../SingleResourceIdentifierTest.php | 13 ++-- .../DataDocument/SingleResourceObjectTest.php | 13 ++-- test/ErrorDocumentTest.php | 13 ++-- test/ExamplesTest.php | 10 +-- test/JsonApiTest.php | 10 +-- test/LinkObjectTest.php | 10 +-- test/MetaDocumentTest.php | 10 +-- test/MetaTest.php | 10 +-- test/NewResourceObjectTest.php | 28 +++---- test/PaginationTest.php | 13 ++-- test/ResourceIdentifierTest.php | 7 +- test/ResourceObjectTest.php | 49 +++++------- test/benchmarks/compound10k.php | 4 +- 80 files changed, 538 insertions(+), 826 deletions(-) create mode 100644 .php-cs-fixer.php delete mode 100644 .php_cs.dist delete mode 100644 .travis.yml diff --git a/.gitignore b/.gitignore index f22a32d..c195581 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ composer.phar /vendor/ /composer.lock /.php_cs.cache +.php-cs-fixer.cache +.phpunit.result.cache diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..acd1414 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,16 @@ +exclude('somedir') + ->notPath('src/Symfony/Component/Translation/Tests/fixtures/resources.php') + ->in(__DIR__); + +$config = new PhpCsFixer\Config(); +return $config->setRules([ + '@PSR12' => true, + 'array_syntax' => ['syntax' => 'short'], + 'braces' => [ + 'allow_single_line_closure' => true, + 'position_after_functions_and_oop_constructs' => 'same'], +]) + ->setFinder($finder); \ No newline at end of file diff --git a/.php_cs.dist b/.php_cs.dist deleted file mode 100644 index 06b82a3..0000000 --- a/.php_cs.dist +++ /dev/null @@ -1,53 +0,0 @@ -files() - ->name('*.php') - ->in(__DIR__ . '/examples') - ->in(__DIR__ . '/src') - ->in(__DIR__ . '/test'); -return PhpCsFixer\Config::create() - ->setUsingCache(true) - ->setRiskyAllowed(true) - ->setRules([ - '@PSR2' => true, - 'array_syntax' => ['syntax' => 'short'], - 'binary_operator_spaces' => true, - 'cast_spaces' => true, - 'concat_space' => true, - 'declare_strict_types' => true, - 'include' => true, - 'is_null' => true, - 'lowercase_cast' => true, - 'mb_str_functions' => true, - 'method_separation' => true, - 'native_function_casing' => true, - 'no_blank_lines_after_class_opening' => true, - 'no_blank_lines_after_phpdoc' => true, - 'no_empty_statement' => true, - 'no_extra_consecutive_blank_lines' => true, - 'no_leading_import_slash' => true, - 'no_leading_namespace_whitespace' => true, - 'no_trailing_comma_in_singleline_array' => true, - 'no_unused_imports' => true, - 'no_whitespace_in_blank_line' => true, - 'object_operator_without_whitespace' => true, - 'ordered_imports' => true, - 'phpdoc_align' => true, - 'phpdoc_indent' => true, - 'phpdoc_no_access' => true, - 'phpdoc_no_package' => true, - 'phpdoc_order' => true, - 'phpdoc_scalar' => true, - 'phpdoc_trim' => true, - 'phpdoc_types' => true, - 'psr0' => true, - 'short_scalar_cast' => true, - 'single_blank_line_before_namespace' => true, - 'single_quote' => true, - 'standardize_not_equals' => true, - 'strict_comparison' => true, - 'ternary_operator_spaces' => true, - 'trailing_comma_in_multiline_array' => true, - 'whitespace_after_comma_in_array' => true, - ]) - ->setFinder($finder); diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b2a600d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: php -php: - - '7.1' - - '7.2' - - '7.3' - - '7.4' - -before_script: - - composer install - - mkdir build/logs -p - -script: - - ./vendor/bin/php-cs-fixer fix -v --dry-run - - ./vendor/bin/phpunit --coverage-clover build/logs/clover.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b5bc97..22fd9fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [3.0.0] - 2022-07-26 +### Changed +- The package is migrated to PHP 8.1 + +### Removed +- Support for PHP 7 and older versions +- Compound document validation logic is dropped + ## [2.2.0] - 2020-10-12 ### Added - `NewResourceObject` to allow omitting `id` in resources to-be-created (#108) @@ -30,7 +38,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.2.0...HEAD +[Unreleased]: https://github.com/json-api-php/json-api/compare/3.0.0...HEAD +[3.0.0]: https://github.com/json-api-php/json-api/compare/2.2.2...3.0.0 [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 diff --git a/composer.json b/composer.json index ffcd77e..5a7e2b9 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "json-api-php/json-api", - "description": "An attempt to express JSON API specs (jsonapi.org) in object-oriented way as a set of PHP 7 classes", + "description": "JSON API specs (jsonapi.org) as a set of PHP classes", "type": "library", "prefer-stable": true, "license": "MIT", @@ -11,12 +11,12 @@ } ], "require": { - "php": ">=7.1", + "php": ">=8.1", "ext-json": "*" }, "require-dev": { - "phpunit/phpunit": "^7.0||^8.0", - "friendsofphp/php-cs-fixer": "^2.13" + "phpunit/phpunit": "^9.0", + "friendsofphp/php-cs-fixer": "^3.9" }, "autoload": { "psr-4": { diff --git a/examples/compound_doc.php b/examples/compound_doc.php index c9feae0..1d0cbbb 100644 --- a/examples/compound_doc.php +++ b/examples/compound_doc.php @@ -1,4 +1,6 @@ - - - - - ./test - - - - - ./src/ - - + + + + + ./src/ + + + + + ./test + + diff --git a/src/Attribute.php b/src/Attribute.php index d45834c..8276746 100644 --- a/src/Attribute.php +++ b/src/Attribute.php @@ -1,4 +1,6 @@ -validateFieldName($name); $this->name = $name; $this->val = $val; @@ -24,8 +25,7 @@ public function __construct(string $name, $val) * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { child($o, 'attributes')->{$this->name} = $this->val; } } diff --git a/src/CompoundDocument.php b/src/CompoundDocument.php index a01c84d..7a15492 100644 --- a/src/CompoundDocument.php +++ b/src/CompoundDocument.php @@ -1,27 +1,25 @@ -validateLinkage($data); + public function __construct(PrimaryData $data, Included $included, DataDocumentMember ...$members) { $this->doc = combine($data, $included, ...$members); } - #[\ReturnTypeWillChange] - public function jsonSerialize() - { + public function jsonSerialize(): object { return $this->doc; } } diff --git a/src/DataDocument.php b/src/DataDocument.php index 3678afa..20d0e7c 100644 --- a/src/DataDocument.php +++ b/src/DataDocument.php @@ -1,26 +1,25 @@ -value = combine($data, ...$members); } - #[\ReturnTypeWillChange] - public function jsonSerialize() - { + public function jsonSerialize(): object { return $this->value; } } diff --git a/src/EmptyRelationship.php b/src/EmptyRelationship.php index 659a9df..2c123bb 100644 --- a/src/EmptyRelationship.php +++ b/src/EmptyRelationship.php @@ -1,4 +1,6 @@ -name = $name; + public function __construct( + private readonly string $name, + RelationshipMember $member, + RelationshipMember ...$members + ) { $this->obj = combine($member, ...$members); } @@ -25,8 +28,7 @@ public function __construct(string $name, RelationshipMember $member, Relationsh * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { child($o, 'relationships')->{$this->name} = $this->obj; } } diff --git a/src/Error.php b/src/Error.php index ba0e3a0..8a222fe 100644 --- a/src/Error.php +++ b/src/Error.php @@ -1,4 +1,6 @@ -error = (object) []; + public function __construct(ErrorMember ...$members) { + $this->error = (object)[]; foreach ($members as $member) { $member->attachTo($this->error); } @@ -25,8 +25,7 @@ public function __construct(ErrorMember ...$members) * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $o->errors[] = $this->error; } } diff --git a/src/Error/Code.php b/src/Error/Code.php index a03b827..40455dd 100644 --- a/src/Error/Code.php +++ b/src/Error/Code.php @@ -1,30 +1,23 @@ -code = $code; + public function __construct(private readonly string $code) { } /** * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $o->code = $this->code; } } diff --git a/src/Error/Detail.php b/src/Error/Detail.php index f4d169c..217c43d 100644 --- a/src/Error/Detail.php +++ b/src/Error/Detail.php @@ -1,30 +1,23 @@ -detail = $detail; + public function __construct(private readonly string $detail) { } /** * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $o->detail = $this->detail; } } diff --git a/src/Error/Id.php b/src/Error/Id.php index 11c5b10..371884f 100644 --- a/src/Error/Id.php +++ b/src/Error/Id.php @@ -1,30 +1,23 @@ -id = $id; + public function __construct(private readonly string $id) { } /** * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $o->id = $this->id; } } diff --git a/src/Error/SourceParameter.php b/src/Error/SourceParameter.php index 3e48461..d344d64 100644 --- a/src/Error/SourceParameter.php +++ b/src/Error/SourceParameter.php @@ -1,31 +1,25 @@ -parameter = $parameter; + public function __construct(private readonly string $parameter) { } /** * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { child($o, 'source')->parameter = $this->parameter; } } diff --git a/src/Error/SourcePointer.php b/src/Error/SourcePointer.php index d6cecaa..c33d0b1 100644 --- a/src/Error/SourcePointer.php +++ b/src/Error/SourcePointer.php @@ -1,28 +1,25 @@ -pointer = $pointer; + public function __construct(private readonly string $pointer) { } /** * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { child($o, 'source')->pointer = $this->pointer; } } diff --git a/src/Error/Status.php b/src/Error/Status.php index 6be53aa..2083373 100644 --- a/src/Error/Status.php +++ b/src/Error/Status.php @@ -1,30 +1,23 @@ -status = $status; + public function __construct(private readonly string $status) { } /** * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $o->status = $this->status; } } diff --git a/src/Error/Title.php b/src/Error/Title.php index 2226f53..1352eb5 100644 --- a/src/Error/Title.php +++ b/src/Error/Title.php @@ -1,31 +1,24 @@ -title = $title; + public function __construct(private readonly string $title) { } /** * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $o->title = $this->title; } } diff --git a/src/ErrorDocument.php b/src/ErrorDocument.php index 554c8d5..4cb70b5 100644 --- a/src/ErrorDocument.php +++ b/src/ErrorDocument.php @@ -1,28 +1,27 @@ -obj = (object) []; + public function __construct(ErrorDocumentMember ...$members) { + $this->obj = (object)[]; foreach ($members as $member) { $member->attachTo($this->obj); } } - #[\ReturnTypeWillChange] - public function jsonSerialize() - { + public function jsonSerialize(): object { return $this->obj; } } diff --git a/src/Included.php b/src/Included.php index 5b2dbd4..96a1edb 100644 --- a/src/Included.php +++ b/src/Included.php @@ -1,40 +1,29 @@ -key(); if (isset($this->resources[$key])) { - throw new \LogicException("Resource $resource is already included"); + throw new LogicException("Resource $resource is already included"); } $this->resources[$key] = $resource; - $resource->registerIn($this->identifiers); - } - } - - public function validateLinkage(PrimaryData $data): void - { - $registry = []; - $data->registerIn($registry); - foreach ($this->resources as $resource) { - if (isset($registry[$resource->key()]) || isset($this->identifiers[$resource->key()])) { - continue; - } - throw new \LogicException('Full linkage required for '.$resource); } } @@ -42,8 +31,7 @@ public function validateLinkage(PrimaryData $data): void * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { foreach ($this->resources as $resource) { $resource->attachAsIncludedTo($o); } diff --git a/src/Internal/Attachable.php b/src/Internal/Attachable.php index f11bbba..01de494 100644 --- a/src/Internal/Attachable.php +++ b/src/Internal/Attachable.php @@ -1,16 +1,17 @@ -obj = (object) ['type' => $type]; - $this->type = $type; + $this->obj = (object)['type' => $type]; $this->addMembers(...$members); } @@ -32,17 +30,13 @@ public function __construct(string $type, ResourceMember ...$members) * @param ResourceMember ...$members * @internal */ - protected function addMembers(ResourceMember ...$members): void - { + 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'"); + throw new LogicException("Field '$name' already exists'"); } $fields[$name] = true; } @@ -54,8 +48,7 @@ protected function addMembers(ResourceMember ...$members): void * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $o->data = $this->obj; } } diff --git a/src/Internal/Collection.php b/src/Internal/Collection.php index e0e73d0..cd8b8d6 100644 --- a/src/Internal/Collection.php +++ b/src/Internal/Collection.php @@ -1,7 +1,8 @@ -link = (object) ['href' => $url]; + $this->link = (object)['href' => $url]; foreach ($metas as $meta) { $meta->attachTo($this->link); } diff --git a/src/Internal/MetaDocumentMember.php b/src/Internal/MetaDocumentMember.php index 27aded5..e204a70 100644 --- a/src/Internal/MetaDocumentMember.php +++ b/src/Internal/MetaDocumentMember.php @@ -1,10 +1,11 @@ -name; } } diff --git a/src/Internal/ResourceMember.php b/src/Internal/ResourceMember.php index b20e3b0..ba4918d 100644 --- a/src/Internal/ResourceMember.php +++ b/src/Internal/ResourceMember.php @@ -1,10 +1,11 @@ -obj = (object) [ + public function __construct(string $version = '1.0', Meta $meta = null) { + $this->obj = (object)[ 'version' => $version, ]; - if ($meta) { - $meta->attachTo($this->obj); - } + $meta?->attachTo($this->obj); } /** * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $o->jsonapi = $this->obj; } } diff --git a/src/Link/AboutLink.php b/src/Link/AboutLink.php index e317ca8..fdafefd 100644 --- a/src/Link/AboutLink.php +++ b/src/Link/AboutLink.php @@ -1,21 +1,22 @@ -about = $this->link; } } diff --git a/src/Link/FirstLink.php b/src/Link/FirstLink.php index af13251..dfc5b6a 100644 --- a/src/Link/FirstLink.php +++ b/src/Link/FirstLink.php @@ -1,21 +1,22 @@ -first = $this->link; } } diff --git a/src/Link/LastLink.php b/src/Link/LastLink.php index 037b228..a2215b8 100644 --- a/src/Link/LastLink.php +++ b/src/Link/LastLink.php @@ -1,21 +1,22 @@ -last = $this->link; } } diff --git a/src/Link/NextLink.php b/src/Link/NextLink.php index c2ba366..082fbcc 100644 --- a/src/Link/NextLink.php +++ b/src/Link/NextLink.php @@ -1,21 +1,22 @@ -next = $this->link; } } diff --git a/src/Link/PrevLink.php b/src/Link/PrevLink.php index b612450..d38d025 100644 --- a/src/Link/PrevLink.php +++ b/src/Link/PrevLink.php @@ -1,21 +1,22 @@ -prev = $this->link; } } diff --git a/src/Link/RelatedLink.php b/src/Link/RelatedLink.php index 1def33c..501c2ec 100644 --- a/src/Link/RelatedLink.php +++ b/src/Link/RelatedLink.php @@ -1,22 +1,23 @@ -related = $this->link; } } diff --git a/src/Link/SelfLink.php b/src/Link/SelfLink.php index fde232b..0168cb1 100644 --- a/src/Link/SelfLink.php +++ b/src/Link/SelfLink.php @@ -1,23 +1,24 @@ -self = $this->link; } } diff --git a/src/Meta.php b/src/Meta.php index 758c3bb..ebec4dc 100644 --- a/src/Meta.php +++ b/src/Meta.php @@ -1,7 +1,10 @@ -key = $key; - $this->value = $value; - } - /** - * @param object $o - * @internal - */ - public function attachTo($o): void - { - child($o, 'meta')->{$this->key} = $this->value; + /** + * @param object $o + * @internal + */ + public function attachTo(object $o): void { + child($o, 'meta')->{$this->key} = $this->value; + } } -} diff --git a/src/MetaDocument.php b/src/MetaDocument.php index 45199c9..14f0334 100644 --- a/src/MetaDocument.php +++ b/src/MetaDocument.php @@ -1,21 +1,20 @@ -doc = combine($meta, ...$members); } - #[\ReturnTypeWillChange] - public function jsonSerialize() - { + public function jsonSerialize(): object { return $this->doc; } } diff --git a/src/NewResourceObject.php b/src/NewResourceObject.php index 0f54e5b..44364f1 100644 --- a/src/NewResourceObject.php +++ b/src/NewResourceObject.php @@ -1,4 +1,6 @@ -data = null; } - - public function registerIn(array &$registry): void - { - } } diff --git a/src/PaginatedCollection.php b/src/PaginatedCollection.php index e59c66f..ffc9050 100644 --- a/src/PaginatedCollection.php +++ b/src/PaginatedCollection.php @@ -1,43 +1,22 @@ -pagination = $pagination; - $this->collection = $collection; +final class PaginatedCollection implements PrimaryData { + public function __construct(private readonly Pagination $pagination, private readonly Collection $collection) { } /** * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $this->collection->attachTo($o); $this->pagination->attachTo($o); } - - /** - * @param array $registry - * @internal - */ - public function registerIn(array &$registry): void - { - $this->collection->registerIn($registry); - } } diff --git a/src/Pagination.php b/src/Pagination.php index 674f375..dc2c642 100644 --- a/src/Pagination.php +++ b/src/Pagination.php @@ -1,4 +1,5 @@ links = $links; } @@ -22,8 +21,7 @@ public function __construct(PaginationLink ...$links) * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { foreach ($this->links as $link) { $link->attachTo($o); } diff --git a/src/ResourceCollection.php b/src/ResourceCollection.php index f337ad5..592dd21 100644 --- a/src/ResourceCollection.php +++ b/src/ResourceCollection.php @@ -1,19 +1,19 @@ -resources = $resources; } @@ -21,18 +21,10 @@ public function __construct(ResourceObject ...$resources) * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $o->data = []; foreach ($this->resources as $resource) { $resource->attachToCollection($o); } } - - public function registerIn(array &$registry): void - { - foreach ($this->resources as $resource) { - $resource->registerIn($registry); - } - } } diff --git a/src/ResourceIdentifier.php b/src/ResourceIdentifier.php index dff34f8..cf1cf88 100644 --- a/src/ResourceIdentifier.php +++ b/src/ResourceIdentifier.php @@ -1,44 +1,33 @@ -obj = (object) [ + $this->obj = (object)[ 'type' => $type, 'id' => $id, ]; foreach ($metas as $meta) { $meta->attachTo($this->obj); } - $this->type = $type; - $this->id = $id; } /** * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $o->data = $this->obj; } @@ -46,13 +35,7 @@ public function attachTo($o): void * @param object $o * @internal */ - public function attachToCollection($o): void - { + public function attachToCollection(object $o): void { $o->data[] = $this->obj; } - - public function registerIn(array &$registry): void - { - $registry[compositeKey($this->type, $this->id)] = true; - } } diff --git a/src/ResourceIdentifierCollection.php b/src/ResourceIdentifierCollection.php index 84b8887..794789e 100644 --- a/src/ResourceIdentifierCollection.php +++ b/src/ResourceIdentifierCollection.php @@ -1,19 +1,19 @@ -identifiers = $identifiers; } @@ -21,18 +21,10 @@ public function __construct(ResourceIdentifier ...$identifiers) * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $o->data = []; foreach ($this->identifiers as $identifier) { $identifier->attachToCollection($o); } } - - public function registerIn(array &$registry): void - { - foreach ($this->identifiers as $identifier) { - $identifier->registerIn($registry); - } - } } diff --git a/src/ResourceObject.php b/src/ResourceObject.php index 416d423..f65d575 100644 --- a/src/ResourceObject.php +++ b/src/ResourceObject.php @@ -1,4 +1,6 @@ -obj->id = $id; $this->type = $type; - $this->id = $id; } - public function identifier(): ResourceIdentifier - { + public function identifier(): ResourceIdentifier { return new ResourceIdentifier($this->type, $this->id); } - public function key(): string - { + public function key(): string { return compositeKey($this->type, $this->id); } - public function registerIn(array &$registry): void - { - $registry = array_merge($registry, $this->registry); - } - /** * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $o->data = $this->obj; } /** * @param object $o */ - public function attachAsIncludedTo($o): void - { + public function attachAsIncludedTo(object $o): void { $o->included[] = $this->obj; } @@ -57,13 +42,11 @@ public function attachAsIncludedTo($o): void * @param object $o * @internal */ - public function attachToCollection($o): void - { + public function attachToCollection(object $o): void { $o->data[] = $this->obj; } - public function __toString(): string - { + public function __toString(): string { return $this->key(); } } diff --git a/src/ToMany.php b/src/ToMany.php index c0a7278..afe4987 100644 --- a/src/ToMany.php +++ b/src/ToMany.php @@ -1,4 +1,6 @@ -validateFieldName($name); $this->name = $name; $this->members = $members; - $this->collection = $collection; } /** * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $rel = child(child($o, 'relationships'), $this->name); $rel->data = []; $this->collection->attachTo($rel); @@ -40,9 +39,4 @@ public function attachTo($o): void $member->attachTo($rel); } } - - public function registerIn(array &$registry): void - { - $this->collection->registerIn($registry); - } } diff --git a/src/ToNull.php b/src/ToNull.php index 8b2d3f1..cbaec93 100644 --- a/src/ToNull.php +++ b/src/ToNull.php @@ -1,4 +1,6 @@ -validateFieldName($name); $this->name = $name; $this->members = $members; @@ -25,8 +26,7 @@ public function __construct(string $name, ToOneMember ...$members) * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo(object $o): void { $obj = combine(...$this->members); $obj->data = null; child($o, 'relationships')->{$this->name} = $obj; diff --git a/src/ToOne.php b/src/ToOne.php index dbbd7ac..eb86853 100644 --- a/src/ToOne.php +++ b/src/ToOne.php @@ -1,4 +1,6 @@ -validateFieldName($name); $this->name = $name; $this->obj = combine($identifier, ...$members); - $this->identifier = $identifier; } /** * @param object $o * @internal */ - public function attachTo($o): void - { + public function attachTo($o): void { child($o, 'relationships')->{$this->name} = $this->obj; } - - public function registerIn(array &$registry): void - { - $this->identifier->registerIn($registry); - } } diff --git a/src/functions.php b/src/functions.php index df50e2d..5a08291 100644 --- a/src/functions.php +++ b/src/functions.php @@ -1,32 +1,30 @@ -attachTo($obj); } return $obj; } -function child($o, string $name) -{ +function child($o, string $name): mixed { if (!isset($o->{$name})) { - $o->{$name} = (object) []; + $o->{$name} = (object)[]; } return $o->{$name}; } -function isValidName(string $name): bool -{ +function isValidName(string $name): bool { return preg_match('/^(?=[^-_ ])[a-zA-Z0-9\x{0080}-\x{FFFF}-_ ]*(?<=[^-_ ])$/u', $name) === 1; } -function compositeKey(string $type, string $id): string -{ +function compositeKey(string $type, string $id): string { return "{$type}:{$id}"; } diff --git a/test/BaseTestCase.php b/test/BaseTestCase.php index eca3598..e649b2c 100644 --- a/test/BaseTestCase.php +++ b/test/BaseTestCase.php @@ -1,13 +1,13 @@ -expectException(\LogicException::class); - $this->expectExceptionMessage('Full linkage required for apples:1'); - $create_doc(); - } - - public function documentsWithoutFullLinkage(): array - { - $included = new Included(new ResourceObject('apples', '1')); - return [ - [ - function () use ($included) { - return new CompoundDocument(new NullData(), $included); - }, - ], - [ - function () use ($included) { - return new CompoundDocument(new ResourceCollection(), $included); - }, - ], - [ - function () use ($included) { - return new CompoundDocument(new ResourceIdentifier('oranges', '1'), $included); - }, - ], - [ - function () use ($included) { - return new CompoundDocument( - new ResourceIdentifierCollection( - new ResourceIdentifier('oranges', '1'), - new ResourceIdentifier('oranges', '1') - ), - $included - ); - }, - ], - [ - function () use ($included) { - return new CompoundDocument( - new ResourceCollection(new ResourceObject('oranges', '1'), new ResourceObject('oranges', '1')), - $included - ); - }, - ], - ]; - } - - public function testIncludedResourceMayBeIdentifiedByLinkageInPrimaryData() - { + public function testIncludedResourceMayBeIdentifiedByLinkageInPrimaryData() { $author = new ResourceObject('people', '9'); $article = new ResourceObject( 'articles', @@ -233,8 +175,7 @@ public function testIncludedResourceMayBeIdentifiedByLinkageInPrimaryData() $this->assertNotEmpty($doc); } - public function testIncludedResourceMayBeIdentifiedByAnotherLinkedResource() - { + public function testIncludedResourceMayBeIdentifiedByAnotherLinkedResource() { $writer = new ResourceObject('writers', '3', new Attribute('name', 'Eric Evans')); $book = new ResourceObject( 'books', @@ -254,8 +195,7 @@ public function testIncludedResourceMayBeIdentifiedByAnotherLinkedResource() /** * A compound document MUST NOT include more than one resource object for each type and id pair. */ - public function testCanNotBeManyIncludedResourcesWithEqualIdentifiers() - { + public function testCanNotBeManyIncludedResourcesWithEqualIdentifiers() { $this->expectException('LogicException'); $this->expectExceptionMessage('Resource apples:1 is already included'); $apple = new ResourceObject('apples', '1'); diff --git a/test/DataDocument/ManyResourceIdentifiersTest.php b/test/DataDocument/ManyResourceIdentifiersTest.php index eb12973..2d4728d 100644 --- a/test/DataDocument/ManyResourceIdentifiersTest.php +++ b/test/DataDocument/ManyResourceIdentifiersTest.php @@ -1,4 +1,6 @@ -assertEncodesTo( ' { @@ -26,8 +26,7 @@ public function testMinimalDocument() ); } - public function testExtendedDocument() - { + public function testExtendedDocument() { $this->assertEncodesTo( ' { diff --git a/test/DataDocument/ManyResourceObjectsTest.php b/test/DataDocument/ManyResourceObjectsTest.php index 54736e2..a555e0c 100644 --- a/test/DataDocument/ManyResourceObjectsTest.php +++ b/test/DataDocument/ManyResourceObjectsTest.php @@ -1,4 +1,6 @@ -assertEncodesTo( ' { @@ -28,8 +28,7 @@ public function testMinimalDocument() ); } - public function testExtendedDocument() - { + public function testExtendedDocument() { $this->assertEncodesTo( ' { diff --git a/test/DataDocument/NullDataTest.php b/test/DataDocument/NullDataTest.php index 05746cd..4bb759d 100644 --- a/test/DataDocument/NullDataTest.php +++ b/test/DataDocument/NullDataTest.php @@ -1,4 +1,5 @@ assertEncodesTo( ' { @@ -26,8 +25,7 @@ public function testMinimalDocument() ); } - public function testExtendedDocument() - { + public function testExtendedDocument() { $this->assertEncodesTo( ' { diff --git a/test/DataDocument/SingleResourceIdentifierTest.php b/test/DataDocument/SingleResourceIdentifierTest.php index 165a625..3eea202 100644 --- a/test/DataDocument/SingleResourceIdentifierTest.php +++ b/test/DataDocument/SingleResourceIdentifierTest.php @@ -1,4 +1,6 @@ -assertEncodesTo( ' { @@ -29,8 +29,7 @@ public function testMinimalDocument() ); } - public function testExtendedDocument() - { + public function testExtendedDocument() { $this->assertEncodesTo( ' { diff --git a/test/DataDocument/SingleResourceObjectTest.php b/test/DataDocument/SingleResourceObjectTest.php index dfa25a1..0d19dde 100644 --- a/test/DataDocument/SingleResourceObjectTest.php +++ b/test/DataDocument/SingleResourceObjectTest.php @@ -1,4 +1,6 @@ -assertEncodesTo( ' { @@ -29,8 +29,7 @@ public function testMinimalDocument() ); } - public function testExtendedDocument() - { + public function testExtendedDocument() { $this->assertEncodesTo( ' { diff --git a/test/ErrorDocumentTest.php b/test/ErrorDocumentTest.php index 9a9131d..cb0bc4c 100644 --- a/test/ErrorDocumentTest.php +++ b/test/ErrorDocumentTest.php @@ -1,4 +1,5 @@ assertEncodesTo( ' { @@ -36,8 +35,7 @@ public function testMinimalExample() ); } - public function testExtensiveExample() - { + public function testExtensiveExample() { $this->assertEncodesTo( ' { @@ -82,8 +80,7 @@ public function testExtensiveExample() ); } - public function testMultipleErrors() - { + public function testMultipleErrors() { $this->assertEncodesTo( ' { diff --git a/test/ExamplesTest.php b/test/ExamplesTest.php index ba7a1cf..7dc5da4 100644 --- a/test/ExamplesTest.php +++ b/test/ExamplesTest.php @@ -1,23 +1,21 @@ assertJson(`php $file`); } - public function examples() - { + public function examples() { return [ [__DIR__.'/../examples/compound_doc.php'], [__DIR__.'/../examples/simple_doc.php'], diff --git a/test/JsonApiTest.php b/test/JsonApiTest.php index 49c5982..8a5ce91 100644 --- a/test/JsonApiTest.php +++ b/test/JsonApiTest.php @@ -1,4 +1,6 @@ -assertEncodesTo( ' { diff --git a/test/LinkObjectTest.php b/test/LinkObjectTest.php index a69443c..00a9a78 100644 --- a/test/LinkObjectTest.php +++ b/test/LinkObjectTest.php @@ -1,4 +1,6 @@ -assertEncodesTo( '{ "data": {"type": "apples", "id": "1"}, diff --git a/test/MetaDocumentTest.php b/test/MetaDocumentTest.php index 7893dae..1837f3e 100644 --- a/test/MetaDocumentTest.php +++ b/test/MetaDocumentTest.php @@ -1,4 +1,5 @@ assertEncodesTo( ' { @@ -29,8 +28,7 @@ public function testMetaDocument() /** * A meta document may contain jsonapi member */ - public function testMetaDocumentWithExtraMembers() - { + public function testMetaDocumentWithExtraMembers() { $this->assertEncodesTo( ' { diff --git a/test/MetaTest.php b/test/MetaTest.php index 67d3097..c8d8d93 100644 --- a/test/MetaTest.php +++ b/test/MetaTest.php @@ -1,14 +1,14 @@ -expectException(\DomainException::class); $this->expectExceptionMessage("Invalid character in a member name 'invalid:name'"); new Meta('invalid:name', '1'); diff --git a/test/NewResourceObjectTest.php b/test/NewResourceObjectTest.php index 6182ad9..a2fc94e 100644 --- a/test/NewResourceObjectTest.php +++ b/test/NewResourceObjectTest.php @@ -1,4 +1,6 @@ -assertEncodesTo( ' { @@ -51,8 +51,7 @@ public function testFullFledgedResourceObject() ); } - public function testRelationshipWithSingleIdLinkage() - { + public function testRelationshipWithSingleIdLinkage() { $this->assertEncodesTo( ' { @@ -75,8 +74,7 @@ public function testRelationshipWithSingleIdLinkage() ); } - public function testRelationshipWithMultiIdLinkage() - { + public function testRelationshipWithMultiIdLinkage() { $this->assertEncodesTo( ' { @@ -111,8 +109,7 @@ public function testRelationshipWithMultiIdLinkage() ); } - public function testRelationshipWithEmptyMultiIdLinkage() - { + public function testRelationshipWithEmptyMultiIdLinkage() { $this->assertEncodesTo( ' { @@ -135,8 +132,7 @@ public function testRelationshipWithEmptyMultiIdLinkage() ); } - public function testRelationshipWithNoData() - { + public function testRelationshipWithNoData() { $this->assertEncodesTo( ' { @@ -188,8 +184,7 @@ public function testRelationshipWithNoData() ); } - public function testResourceFieldsMustBeUnique() - { + public function testResourceFieldsMustBeUnique() { $this->expectException(\LogicException::class); $this->expectExceptionMessage("Field 'foo' already exists"); new NewResourceObject( @@ -199,8 +194,7 @@ public function testResourceFieldsMustBeUnique() ); } - public function testNameValidation() - { + public function testNameValidation() { $this->expectException(\DomainException::class); new NewResourceObject('invalid:id'); } diff --git a/test/PaginationTest.php b/test/PaginationTest.php index 7add66f..a9307d8 100644 --- a/test/PaginationTest.php +++ b/test/PaginationTest.php @@ -1,4 +1,6 @@ -assertEncodesTo( ' { @@ -57,8 +57,7 @@ public function testPaginatedResourceCollection() ); } - public function testPaginatedResourceIdentifierCollection() - { + public function testPaginatedResourceIdentifierCollection() { $this->assertEncodesTo( ' { diff --git a/test/ResourceIdentifierTest.php b/test/ResourceIdentifierTest.php index 5dcd959..eb479ee 100644 --- a/test/ResourceIdentifierTest.php +++ b/test/ResourceIdentifierTest.php @@ -1,14 +1,13 @@ expectException(\DomainException::class); new ResourceIdentifier('invalid:id', 'foo'); } diff --git a/test/ResourceObjectTest.php b/test/ResourceObjectTest.php index 3541c8d..b565d7d 100644 --- a/test/ResourceObjectTest.php +++ b/test/ResourceObjectTest.php @@ -1,4 +1,6 @@ -assertEncodesTo( ' { @@ -63,8 +63,7 @@ public function testFullFledgedResourceObject() ); } - public function testRelationshipWithSingleIdLinkage() - { + public function testRelationshipWithSingleIdLinkage() { $this->assertEncodesTo( ' { @@ -89,8 +88,7 @@ public function testRelationshipWithSingleIdLinkage() ); } - public function testRelationshipWithMultiIdLinkage() - { + public function testRelationshipWithMultiIdLinkage() { $this->assertEncodesTo( ' { @@ -127,8 +125,7 @@ public function testRelationshipWithMultiIdLinkage() ); } - public function testRelationshipWithEmptyMultiIdLinkage() - { + public function testRelationshipWithEmptyMultiIdLinkage() { $this->assertEncodesTo( ' { @@ -153,8 +150,7 @@ public function testRelationshipWithEmptyMultiIdLinkage() ); } - public function testRelationshipWithNoData() - { + public function testRelationshipWithNoData() { $this->assertEncodesTo( ' { @@ -210,29 +206,25 @@ public function testRelationshipWithNoData() ); } - public function testCanNotCreateIdAttribute() - { + public function testCanNotCreateIdAttribute() { $this->expectException(\DomainException::class); $this->expectExceptionMessage("Can not use 'id' as a resource field"); new Attribute('id', 'foo'); } - public function testCanNotCreateTypeAttribute() - { + public function testCanNotCreateTypeAttribute() { $this->expectException(\DomainException::class); $this->expectExceptionMessage("Can not use 'type' as a resource field"); new Attribute('type', 'foo'); } - public function testCanNotCreateIdRelationship() - { + public function testCanNotCreateIdRelationship() { $this->expectException(\DomainException::class); $this->expectExceptionMessage("Can not use 'id' as a resource field"); new ToOne('id', new ResourceIdentifier('apples', '1')); } - public function testCanNotCreateTypeRelationship() - { + public function testCanNotCreateTypeRelationship() { $this->expectException(\DomainException::class); $this->expectExceptionMessage("Can not use 'type' as a resource field"); new ToOne('type', new ResourceIdentifier('apples', '1')); @@ -242,8 +234,7 @@ public function testCanNotCreateTypeRelationship() * @dataProvider invalidCharacters * @param string $invalid_char */ - public function testAttributeMustOnlyHaveAllowedCharacters(string $invalid_char) - { + public function testAttributeMustOnlyHaveAllowedCharacters(string $invalid_char) { $this->expectException(\DomainException::class); $this->expectExceptionMessage('Invalid character in a member name'); new Attribute("foo{$invalid_char}bar", 'plus can not be used'); @@ -253,15 +244,13 @@ public function testAttributeMustOnlyHaveAllowedCharacters(string $invalid_char) * @dataProvider invalidCharacters * @param string $invalid_char */ - public function testRelationshipMustOnlyHaveAllowedCharacters(string $invalid_char) - { + public function testRelationshipMustOnlyHaveAllowedCharacters(string $invalid_char) { $this->expectException(\DomainException::class); $this->expectExceptionMessage('Invalid character in a member name'); new ToNull("foo{$invalid_char}bar"); } - public function invalidCharacters() - { + public function invalidCharacters() { return [ ['+'], ['!'], @@ -271,8 +260,7 @@ public function invalidCharacters() ]; } - public function testResourceFieldsMustBeUnique() - { + public function testResourceFieldsMustBeUnique() { $this->expectException(\LogicException::class); $this->expectExceptionMessage("Field 'foo' already exists"); new ResourceObject( @@ -283,8 +271,7 @@ public function testResourceFieldsMustBeUnique() ); } - public function testNameValidation() - { + public function testNameValidation() { $this->expectException(\DomainException::class); new ResourceObject('invalid:id', 'foo'); } diff --git a/test/benchmarks/compound10k.php b/test/benchmarks/compound10k.php index 513518b..44664fd 100644 --- a/test/benchmarks/compound10k.php +++ b/test/benchmarks/compound10k.php @@ -1,4 +1,6 @@ -