diff --git a/CHANGELOG.md b/CHANGELOG.md index 28eb411..901d9e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added -* Added facades for `CollectionDocumentBuilder`, `ItemDocumentBuilder`, `ItemHydrator` and `TypeMapper`. +* Added `DocumentFactory`. +* Added facades for `DocumentFactory`, `ItemHydrator` and `TypeMapper`. ### Changed * The `TypeMapper` now checks if the class exists in the setter instead of the getter. * The `ItemHydrator` now also hydrates the id if provided. +* Added `hasType`, `hasAttributes`, `hasRelationships` and `getRelations` to `ItemInterface`. +* Removed `canBeIncluded` and `getIncluded` from `ItemInterface` as the `DocumentFactory` is now responsible for gathering the included items. + +### Removed + +* Removed `CollectionDocumentBuilder` and `ItemDocumentBuilder` in favor of `DocumentFactory`. ## [0.18.0] - 2019-07-01 diff --git a/composer.json b/composer.json index a34a038..9034a82 100644 --- a/composer.json +++ b/composer.json @@ -59,8 +59,7 @@ "Swis\\JsonApi\\Client\\Providers\\ServiceProvider" ], "aliases":{ - "CollectionDocumentBuilder": "Swis\\JsonApi\\Client\\Facades\\CollectionDocumentBuilderFacade", - "ItemDocumentBuilder": "Swis\\JsonApi\\Client\\Facades\\ItemDocumentBuilderFacade", + "DocumentFactory": "Swis\\JsonApi\\Client\\Facades\\DocumentFactoryFacade", "ItemHydrator": "Swis\\JsonApi\\Client\\Facades\\ItemHydratorFacade", "TypeMapper": "Swis\\JsonApi\\Client\\Facades\\TypeMapperFacade" } diff --git a/src/CollectionDocumentBuilder.php b/src/CollectionDocumentBuilder.php deleted file mode 100644 index cb2a4f9..0000000 --- a/src/CollectionDocumentBuilder.php +++ /dev/null @@ -1,16 +0,0 @@ -setData(new Collection($items)); - } -} diff --git a/src/DocumentFactory.php b/src/DocumentFactory.php new file mode 100644 index 0000000..4ae1ed1 --- /dev/null +++ b/src/DocumentFactory.php @@ -0,0 +1,93 @@ +setData($data)->setIncluded($this->getIncluded($data)); + } + + /** + * @param \Swis\JsonApi\Client\Interfaces\DataInterface $data + * + * @return \Swis\JsonApi\Client\Collection + */ + private function getIncluded(DataInterface $data): Collection + { + return Collection::wrap($data) + ->flatMap( + function (ItemInterface $item) { + return $this->getIncludedFromItem($item); + } + ) + ->unique( + static function (ItemInterface $item) { + return sprintf('%s:%s', $item->getType(), $item->getId()); + } + ) + ->values(); + } + + /** + * @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item + * + * @return \Swis\JsonApi\Client\Collection + */ + private function getIncludedFromItem(ItemInterface $item): Collection + { + return Collection::make($item->getRelations()) + ->reject( + static function ($relationship) { + /* @var \Swis\JsonApi\Client\Interfaces\OneRelationInterface|\Swis\JsonApi\Client\Interfaces\ManyRelationInterface $relationship */ + return $relationship->shouldOmitIncluded() || !$relationship->hasIncluded(); + } + ) + ->flatMap( + static function ($relationship) { + /* @var \Swis\JsonApi\Client\Interfaces\OneRelationInterface|\Swis\JsonApi\Client\Interfaces\ManyRelationInterface $relationship */ + return Collection::wrap($relationship->getIncluded()); + } + ) + ->flatMap( + function (ItemInterface $item) { + return Collection::wrap($item)->merge($this->getIncludedFromItem($item)); + } + ) + ->filter( + function (ItemInterface $item) { + return $this->itemCanBeIncluded($item); + } + ); + } + + /** + * @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item + * + * @return bool + */ + private function itemCanBeIncluded(ItemInterface $item): bool + { + return $item->hasType() + && $item->hasId() + && ($item->hasAttributes() || $item->hasRelationships()); + } +} diff --git a/src/Facades/CollectionDocumentBuilderFacade.php b/src/Facades/CollectionDocumentBuilderFacade.php deleted file mode 100644 index 20bce9e..0000000 --- a/src/Facades/CollectionDocumentBuilderFacade.php +++ /dev/null @@ -1,22 +0,0 @@ -relationships as $name => $relationship) { - if ($relationship->shouldOmitIncluded() || !$relationship->hasIncluded()) { - continue; - } - - if ($relationship instanceof OneRelationInterface) { - /** @var \Swis\JsonApi\Client\Interfaces\ItemInterface $item */ - $item = $relationship->getIncluded(); - if ($item->canBeIncluded()) { - $included->push($item->toJsonApiArray()); - } - $included = $included->merge($item->getIncluded()); - } elseif ($relationship instanceof ManyRelationInterface) { - $relationship->getIncluded()->each( - function (ItemInterface $item) use (&$included) { - if ($item->canBeIncluded()) { - $included->push($item->toJsonApiArray()); - } - $included = $included->merge($item->getIncluded()); - } - ); - } - } - - return $included - ->unique( - function (array $item) { - return $item['type'].':'.$item['id']; - } - ) - ->values(); - } - /** * @return bool */ - public function canBeIncluded(): bool + public function hasRelationships(): bool { - if (empty($this->getType())) { - return false; - } - - if (null === $this->getId()) { - return false; - } - - if (empty($this->relationships) && empty($this->toArray())) { - return false; - } - - return true; + return !empty($this->getRelationships()); } /** @@ -261,6 +207,14 @@ public function hasAttribute($key): bool return array_key_exists($key, $this->attributes); } + /** + * @return bool + */ + public function hasAttributes(): bool + { + return !empty($this->toArray()); + } + /** * @param string $key * @param mixed $value @@ -510,4 +464,12 @@ public function setRelation(string $relation, DataInterface $value, Links $links return $this; } + + /** + * @return array + */ + public function getRelations(): array + { + return $this->relationships; + } } diff --git a/src/ItemDocumentBuilder.php b/src/ItemDocumentBuilder.php deleted file mode 100644 index bfac3ae..0000000 --- a/src/ItemDocumentBuilder.php +++ /dev/null @@ -1,35 +0,0 @@ -itemHydrator = $itemHydrator; - } - - /** - * @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item - * @param array $attributes - * @param string|null $id - * - * @return \Swis\JsonApi\Client\ItemDocument - */ - public function build(ItemInterface $item, array $attributes, string $id = null) - { - $this->itemHydrator->hydrate($item, $attributes, $id); - - return (new ItemDocument())->setData($item)->setIncluded($item->getIncluded()); - } -} diff --git a/src/Traits/HasType.php b/src/Traits/HasType.php index 5c09806..e7b15e8 100644 --- a/src/Traits/HasType.php +++ b/src/Traits/HasType.php @@ -28,4 +28,12 @@ public function getType(): string { return $this->type; } + + /** + * @return bool + */ + public function hasType(): bool + { + return (bool)$this->type; + } } diff --git a/tests/CollectionDocumentBuilderTest.php b/tests/CollectionDocumentBuilderTest.php deleted file mode 100644 index e1babaa..0000000 --- a/tests/CollectionDocumentBuilderTest.php +++ /dev/null @@ -1,37 +0,0 @@ - 'value1']))->setId(1), - (new Item(['key2' => 'value2']))->setId(2), - ]; - - $collectionDocument = $collectionDocumentBuilder->build($data); - - /** @var \Swis\JsonApi\Client\Collection $items */ - $items = $collectionDocument->getData(); - static::assertInstanceOf(Collection::class, $items); - - static::assertInstanceOf(Item::class, $items[0]); - static::assertEquals(1, $items[0]->getId()); - static::assertEquals($data[0]['key1'], $items[0]->key1); - - static::assertInstanceOf(Item::class, $items[1]); - static::assertEquals(2, $items[1]->getId()); - static::assertEquals($data[1]['key2'], $items[1]->key2); - } -} diff --git a/tests/DocumentFactoryTest.php b/tests/DocumentFactoryTest.php new file mode 100644 index 0000000..c911d93 --- /dev/null +++ b/tests/DocumentFactoryTest.php @@ -0,0 +1,158 @@ + 'bar']))->setId('123'); + + $documentFactory = new DocumentFactory(); + + $document = $documentFactory->make($item); + + static::assertInstanceOf(ItemDocument::class, $document); + static::assertSame($item, $document->getData()); + } + + /** + * @test + */ + public function it_makes_a_collectiondocument_for_a_collection() + { + $item = (new Item(['foo' => 'bar']))->setId('123'); + $collection = new Collection([$item]); + + $documentFactory = new DocumentFactory(); + + $document = $documentFactory->make($collection); + + static::assertInstanceOf(CollectionDocument::class, $document); + static::assertSame($collection, $document->getData()); + } + + /** + * @test + */ + public function it_adds_included_to_the_document_for_an_item() + { + $item = (new Item(['foo' => 'bar']))->setType('foo-bar')->setId('123'); + $childItem = (new Item(['foo' => 'bar']))->setType('foo-bar')->setId('456'); + $item->setRelation('child', $childItem); + + $documentFactory = new DocumentFactory(); + + $document = $documentFactory->make($item); + + static::assertContains($childItem, $document->getIncluded()); + } + + /** + * @test + */ + public function it_adds_included_to_the_document_for_a_collection() + { + $item = (new Item(['foo' => 'bar']))->setType('foo-bar')->setId('123'); + $childItem = (new Item(['foo' => 'bar']))->setType('foo-bar')->setId('456'); + $item->setRelation('child', $childItem); + $collection = new Collection([$item]); + + $documentFactory = new DocumentFactory(); + + $document = $documentFactory->make($collection); + + static::assertContains($childItem, $document->getIncluded()); + } + + /** + * @test + */ + public function it_does_not_add_included_to_the_document_if_it_has_no_type() + { + $item = (new Item(['foo' => 'bar']))->setType('foo-bar')->setId('123'); + $childItem = (new Item(['foo' => 'bar']))->setId('456'); + $item->setRelation('child', $childItem); + + $documentFactory = new DocumentFactory(); + + $document = $documentFactory->make($item); + + static::assertNotContains($childItem, $document->getIncluded()); + } + + /** + * @test + */ + public function it_does_not_add_included_to_the_document_if_it_has_no_id() + { + $item = (new Item(['foo' => 'bar']))->setType('foo-bar')->setId('123'); + $childItem = (new Item(['foo' => 'bar']))->setType('foo-bar'); + $item->setRelation('child', $childItem); + + $documentFactory = new DocumentFactory(); + + $document = $documentFactory->make($item); + + static::assertNotContains($childItem, $document->getIncluded()); + } + + /** + * @test + */ + public function it_does_not_add_included_to_the_document_if_it_has_no_attributes_or_relationships() + { + $item = (new Item(['foo' => 'bar']))->setType('foo-bar')->setId('123'); + $childItem = (new Item())->setType('foo-bar')->setId('456'); + $item->setRelation('child', $childItem); + + $documentFactory = new DocumentFactory(); + + $document = $documentFactory->make($item); + + static::assertNotContains($childItem, $document->getIncluded()); + } + + /** + * @test + */ + public function it_adds_included_to_the_document_if_it_has_a_type_id_and_attributes_but_no_relationships() + { + $item = (new Item(['foo' => 'bar']))->setType('foo-bar')->setId('123'); + $childItem = (new Item(['foo' => 'bar']))->setType('foo-bar')->setId('456'); + $item->setRelation('child', $childItem); + + $documentFactory = new DocumentFactory(); + + $document = $documentFactory->make($item); + + static::assertContains($childItem, $document->getIncluded()); + } + + /** + * @test + */ + public function it_adds_included_to_the_document_if_it_has_a_type_id_and_relationships_but_no_attributes() + { + $item = (new Item(['foo' => 'bar']))->setType('foo-bar')->setId('123'); + $childItem = (new Item())->setType('foo-bar')->setId('456'); + $item->setRelation('child', $childItem); + $anotherChildItem = (new Item(['foo' => 'bar']))->setType('foo-bar')->setId('789'); + $childItem->setRelation('child', $anotherChildItem); + + $documentFactory = new DocumentFactory(); + + $document = $documentFactory->make($item); + + static::assertContains($childItem, $document->getIncluded()); + } +} diff --git a/tests/ItemDocumentBuilderTest.php b/tests/ItemDocumentBuilderTest.php deleted file mode 100644 index ca1404b..0000000 --- a/tests/ItemDocumentBuilderTest.php +++ /dev/null @@ -1,78 +0,0 @@ - 'value1', 'key2' => 'value2']; - - $typeMapper = new TypeMapper(); - $itemHydrator = new ItemHydrator($typeMapper); - $itemDocumentBuilder = new ItemDocumentBuilder($itemHydrator); - - $itemDocument = $itemDocumentBuilder->build(new Item(), $data, '123'); - - $item = $itemDocument->getData(); - static::assertInstanceOf(Item::class, $item); - static::assertEquals($item->getId(), '123'); - static::assertEquals($item->key1, $data['key1']); - static::assertEquals($item->key2, $data['key2']); - } - - /** - * @test - */ - public function it_fills_items_without_id() - { - $typeMapper = new TypeMapper(); - $itemHydrator = new ItemHydrator($typeMapper); - $itemDocumentBuilder = new ItemDocumentBuilder($itemHydrator); - - $itemDocument = $itemDocumentBuilder->build(new Item(), []); - - $item = $itemDocument->getData(); - static::assertInstanceOf(Item::class, $item); - static::assertNull($item->getId()); - } - - /** - * @test - * @dataProvider provideIdArguments - * - * @param $givenId - * @param $expectedId - */ - public function it_fills_the_id_when_not_null_or_empty_string($givenId, $expectedId) - { - $typeMapper = new TypeMapper(); - $itemHydrator = new ItemHydrator($typeMapper); - $itemDocumentBuilder = new ItemDocumentBuilder($itemHydrator); - - $itemDocument = $itemDocumentBuilder->build(new Item(), [], $givenId); - - $item = $itemDocument->getData(); - static::assertInstanceOf(Item::class, $item); - static::assertSame($item->getId(), $expectedId); - } - - public function provideIdArguments(): array - { - return [ - ['0', '0'], - ['12', '12'], - ['foo-bar', 'foo-bar'], - [null, null], - ['', null], - ]; - } -} diff --git a/tests/ItemRelationsTest.php b/tests/ItemRelationsTest.php deleted file mode 100644 index 009884f..0000000 --- a/tests/ItemRelationsTest.php +++ /dev/null @@ -1,45 +0,0 @@ -assertFalse($masterItem->hasRelationship('child')); - - $childItem = new ChildItem(); - $childItem->setId(1); - $masterItem->child()->associate($childItem); - $this->assertTrue($masterItem->hasRelationship('child')); - } - - /** - * @test - */ - public function it_can_get_all_relations() - { - $masterItem = new MasterItem(); - $childItem = new ChildItem(); - $childItem->setId(1); - $masterItem->child()->associate($childItem); - - $relations = $masterItem->getRelationships(); - - $this->assertSame([ - 'child' => [ - 'data' => [ - 'type' => 'child', - 'id' => '1', - ], - ], - ], $relations); - } -} diff --git a/tests/ItemTest.php b/tests/ItemTest.php index 59c9f16..b42ae53 100644 --- a/tests/ItemTest.php +++ b/tests/ItemTest.php @@ -7,6 +7,8 @@ use Swis\JsonApi\Client\Link; use Swis\JsonApi\Client\Links; use Swis\JsonApi\Client\Meta; +use Swis\JsonApi\Client\Tests\Mocks\Items\ChildItem; +use Swis\JsonApi\Client\Tests\Mocks\Items\MasterItem; use Swis\JsonApi\Client\Tests\Mocks\Items\RelatedItem; use Swis\JsonApi\Client\Tests\Mocks\Items\WithGetMutatorItem; use Swis\JsonApi\Client\Tests\Mocks\Items\WithHiddenItem; @@ -136,6 +138,19 @@ public function it_returns_attributes() $this->assertEquals($this->attributes, $item->getAttributes()); } + /** + * @test + */ + public function it_returns_a_boolean_indicating_if_it_has_attributes() + { + $item = new Item(); + $this->assertFalse($item->hasAttributes()); + + $item->fill($this->attributes); + + $this->assertTrue($item->hasAttributes()); + } + /** * @test */ @@ -171,6 +186,56 @@ public function it_uses_initial_values() $this->assertEquals(['testKey' => 9999, 'anotherTestKey' => 'someValue'], $itemBuilder->getAttributes()); } + /** + * @test + */ + public function it_has_relationships_when_added() + { + $masterItem = new MasterItem(); + $this->assertFalse($masterItem->hasRelationship('child')); + + $childItem = new ChildItem(); + $childItem->setId(1); + $masterItem->child()->associate($childItem); + $this->assertTrue($masterItem->hasRelationship('child')); + } + + /** + * @test + */ + public function it_can_get_all_relations() + { + $masterItem = new MasterItem(); + $childItem = new ChildItem(); + $childItem->setId(1); + $masterItem->child()->associate($childItem); + + $relations = $masterItem->getRelationships(); + + $this->assertSame([ + 'child' => [ + 'data' => [ + 'type' => 'child', + 'id' => '1', + ], + ], + ], $relations); + } + + /** + * @test + */ + public function it_returns_a_boolean_indicating_if_it_has_relationships() + { + $masterItem = new MasterItem(); + $this->assertFalse($masterItem->hasRelationships()); + + $childItem = (new ChildItem())->setId(1); + $masterItem->child()->associate($childItem); + + $this->assertTrue($masterItem->hasRelationships()); + } + /** * @test */