diff --git a/CHANGELOG.md b/CHANGELOG.md index f6b226a..8434762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. N.B. This is a breaking change if you implement the interface yourself or extend the `DocumentClient`. [#34](https://github.com/swisnl/json-api-client/pull/34) * `Repository` doesn't throw exceptions anymore. [#41](https://github.com/swisnl/json-api-client/pull/41) N.B. This is a breaking change if you catch `DocumentNotFoundException` or `DocumentTypeException`. If you would like the old behaviour, you can simply extend the `Repository` and implement it yourself. +* A HasOne or MorphTo relation do not set a `[relationship]_id` field on the parent when associating a related item. ### Removed @@ -27,6 +28,7 @@ N.B. This is a breaking change if you catch these exceptions. ### Fixed * Do not fail on, but skip relationships without data [#38](https://github.com/swisnl/json-api-client/pull/38) +* Dissociating a related item now produces valid JSON ## [0.11.0] - 2018-12-21 diff --git a/src/Item.php b/src/Item.php index f10a6af..892c654 100644 --- a/src/Item.php +++ b/src/Item.php @@ -138,12 +138,16 @@ public function getRelationships(): array /** @var \Swis\JsonApi\Client\Interfaces\RelationInterface $relationship */ foreach ($this->relationships as $name => $relationship) { if ($relationship instanceof HasOneRelation) { - $relationships[$name] = [ - 'data' => [ - 'type' => $relationship->getType(), - 'id' => $relationship->getId(), - ], - ]; + $relationships[$name] = ['data' => null]; + + if ($relationship->getIncluded() !== null) { + $relationships[$name] = [ + 'data' => [ + 'type' => $relationship->getType(), + 'id' => $relationship->getId(), + ], + ]; + } } elseif ($relationship instanceof HasManyRelation) { $relationships[$name]['data'] = []; @@ -155,12 +159,16 @@ public function getRelationships(): array ]; } } elseif ($relationship instanceof MorphToRelation) { - $relationships[$name] = [ - 'data' => [ - 'type' => $relationship->getIncluded()->getType(), - 'id' => $relationship->getIncluded()->getId(), - ], - ]; + $relationships[$name] = ['data' => null]; + + if ($relationship->getIncluded() !== null) { + $relationships[$name] = [ + 'data' => [ + 'type' => $relationship->getIncluded()->getType(), + 'id' => $relationship->getIncluded()->getId(), + ], + ]; + } } elseif ($relationship instanceof MorphToManyRelation) { $relationships[$name]['data'] = []; @@ -302,6 +310,18 @@ public function hasRelationship(string $name): bool return array_key_exists($name, $this->relationships); } + /** + * @param $name + * + * @return static + */ + public function removeRelationship(string $name) + { + unset($this->relationships[$name]); + + return $this; + } + /** * Create a singular relation to another item. * @@ -316,7 +336,7 @@ public function hasOne(string $class, string $relationName = null) $itemType = (new $class())->getType(); if (!array_key_exists($relationName, $this->relationships)) { - $this->relationships[$relationName] = new HasOneRelation($itemType, $this); + $this->relationships[$relationName] = new HasOneRelation($itemType); } return $this->relationships[$relationName]; @@ -354,7 +374,7 @@ public function morphTo(string $relationName = null) $relationName = $relationName ?: snake_case(debug_backtrace()[1]['function']); if (!array_key_exists($relationName, $this->relationships)) { - $this->relationships[$relationName] = new MorphToRelation($this); + $this->relationships[$relationName] = new MorphToRelation(); } return $this->relationships[$relationName]; diff --git a/src/Relations/AbstractOneRelation.php b/src/Relations/AbstractOneRelation.php index 848b24f..da7bfa7 100644 --- a/src/Relations/AbstractOneRelation.php +++ b/src/Relations/AbstractOneRelation.php @@ -8,7 +8,7 @@ abstract class AbstractOneRelation extends AbstractRelation { /** - * @var \Swis\JsonApi\Client\Interfaces\ItemInterface + * @var \Swis\JsonApi\Client\Interfaces\ItemInterface|null */ protected $included; @@ -33,7 +33,6 @@ public function associate(DataInterface $included) } $this->setId($included->getId()); - $this->setType($included->getType()); $this->included = $included; @@ -45,6 +44,8 @@ public function associate(DataInterface $included) */ public function dissociate() { + $this->setId(null); + $this->included = null; return $this; diff --git a/src/Relations/AbstractRelation.php b/src/Relations/AbstractRelation.php index 2db9239..943f8b6 100644 --- a/src/Relations/AbstractRelation.php +++ b/src/Relations/AbstractRelation.php @@ -29,7 +29,7 @@ public function setType(string $type) } /** - * @return string|null + * @return string */ public function getType(): string { diff --git a/src/Relations/HasOneRelation.php b/src/Relations/HasOneRelation.php index 6e9168e..13df3ff 100644 --- a/src/Relations/HasOneRelation.php +++ b/src/Relations/HasOneRelation.php @@ -2,53 +2,13 @@ namespace Swis\JsonApi\Client\Relations; -use Swis\JsonApi\Client\Interfaces\DataInterface; -use Swis\JsonApi\Client\Interfaces\ItemInterface; - class HasOneRelation extends AbstractOneRelation { /** - * @var \Swis\JsonApi\Client\Interfaces\ItemInterface + * @param string $type */ - protected $parentItem; - - /** - * @param string $type - * @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item - */ - public function __construct(string $type, ItemInterface $item) + public function __construct(string $type) { $this->type = $type; - $this->parentItem = $item; - } - - /** - * @param \Swis\JsonApi\Client\Interfaces\DataInterface $included - * - * @throws \InvalidArgumentException - * - * @return $this - */ - public function associate(DataInterface $included) - { - $result = parent::associate($included); - - // Set the $relation.'_id' on the parent - $this->parentItem->setAttribute($this->type.'_id', $this->getId()); - - return $result; - } - - /** - * @return $this - */ - public function dissociate() - { - $result = parent::dissociate(); - - // Remove the $relation.'_id' on the parent - $this->parentItem->setAttribute($this->type.'_id', null); - - return $result; } } diff --git a/src/Relations/MorphToRelation.php b/src/Relations/MorphToRelation.php index 3d0be41..90e12e9 100644 --- a/src/Relations/MorphToRelation.php +++ b/src/Relations/MorphToRelation.php @@ -2,20 +2,33 @@ namespace Swis\JsonApi\Client\Relations; -use Swis\JsonApi\Client\Interfaces\ItemInterface; +use Swis\JsonApi\Client\Interfaces\DataInterface; class MorphToRelation extends AbstractOneRelation { /** - * @var \Swis\JsonApi\Client\Interfaces\ItemInterface + * {@inheritdoc} */ - protected $parentItem; + public function associate(DataInterface $included) + { + parent::associate($included); + + /* @var \Swis\JsonApi\Client\Interfaces\ItemInterface $included */ + + $this->type = $included->getType(); + + return $this; + } /** - * @param \Swis\JsonApi\Client\Interfaces\ItemInterface $item + * {@inheritdoc} */ - public function __construct(ItemInterface $item) + public function dissociate() { - $this->parentItem = $item; + parent::dissociate(); + + $this->type = null; + + return $this; } } diff --git a/tests/ItemTest.php b/tests/ItemTest.php index 551b329..7cb3cc6 100644 --- a/tests/ItemTest.php +++ b/tests/ItemTest.php @@ -2,9 +2,12 @@ namespace Swis\JsonApi\Client\Tests; +use Swis\JsonApi\Client\Collection; use Swis\JsonApi\Client\Item; +use Swis\JsonApi\Client\Tests\Mocks\Items\RelatedItem; use Swis\JsonApi\Client\Tests\Mocks\Items\WithGetMutatorItem; use Swis\JsonApi\Client\Tests\Mocks\Items\WithHiddenItem; +use Swis\JsonApi\Client\Tests\Mocks\Items\WithRelationshipItem; class ItemTest extends AbstractTest { @@ -169,4 +172,204 @@ public function is_does_not_show_attributes_in_to_json_api_array_when_it_has_no_ $item->toJsonApiArray() ); } + + /** + * @test + */ + public function is_adds_hasone_relation_in_to_json_api_array() + { + $item = new WithRelationshipItem(); + $item->setId(1234); + $item->hasoneRelation()->associate((new RelatedItem())->setId(5678)); + + $this->assertEquals( + [ + 'type' => 'item-with-relationship', + 'id' => 1234, + 'relationships' => [ + 'hasone_relation' => [ + 'data' => [ + 'type' => 'related-item', + 'id' => 5678, + ], + ], + ], + ], + $item->toJsonApiArray() + ); + } + + /** + * @test + */ + public function is_adds_empty_hasone_relation_in_to_json_api_array() + { + $item = new WithRelationshipItem(); + $item->setId(1234); + $item->hasoneRelation()->dissociate(); + + $this->assertEquals( + [ + 'type' => 'item-with-relationship', + 'id' => 1234, + 'relationships' => [ + 'hasone_relation' => [ + 'data' => null, + ], + ], + ], + $item->toJsonApiArray() + ); + } + + /** + * @test + */ + public function is_adds_morphto_relation_in_to_json_api_array() + { + $item = new WithRelationshipItem(); + $item->setId(1234); + $item->morphtoRelation()->associate((new RelatedItem())->setId(5678)); + + $this->assertEquals( + [ + 'type' => 'item-with-relationship', + 'id' => 1234, + 'relationships' => [ + 'morphto_relation' => [ + 'data' => [ + 'type' => 'related-item', + 'id' => 5678, + ], + ], + ], + ], + $item->toJsonApiArray() + ); + } + + /** + * @test + */ + public function is_adds_empty_morphto_relation_in_to_json_api_array() + { + $item = new WithRelationshipItem(); + $item->setId(1234); + $item->morphtoRelation()->dissociate(); + + $this->assertEquals( + [ + 'type' => 'item-with-relationship', + 'id' => 1234, + 'relationships' => [ + 'morphto_relation' => [ + 'data' => null, + ], + ], + ], + $item->toJsonApiArray() + ); + } + + /** + * @test + */ + public function is_adds_hasmany_relation_in_to_json_api_array() + { + $item = new WithRelationshipItem(); + $item->setId(1234); + $item->hasmanyRelation()->associate(new Collection([(new RelatedItem())->setId(5678)])); + + $this->assertEquals( + [ + 'type' => 'item-with-relationship', + 'id' => 1234, + 'relationships' => [ + 'hasmany_relation' => [ + 'data' => [ + [ + 'type' => 'related-item', + 'id' => 5678, + ], + ], + ], + ], + ], + $item->toJsonApiArray() + ); + } + + /** + * @test + */ + public function is_adds_empty_hasmany_relation_in_to_json_api_array() + { + $item = new WithRelationshipItem(); + $item->setId(1234); + $item->hasmanyRelation()->dissociate(); + + $this->assertEquals( + [ + 'type' => 'item-with-relationship', + 'id' => 1234, + 'relationships' => [ + 'hasmany_relation' => [ + 'data' => [], + ], + ], + ], + $item->toJsonApiArray() + ); + } + + /** + * @test + */ + public function is_adds_morphtomany_relation_in_to_json_api_array() + { + $item = new WithRelationshipItem(); + $item->setId(1234); + $item->morphtomanyRelation()->associate(new Collection([(new RelatedItem())->setId(5678)])); + + $this->assertEquals( + [ + 'type' => 'item-with-relationship', + 'id' => 1234, + 'relationships' => [ + 'morphtomany_relation' => [ + 'data' => [ + [ + 'type' => 'related-item', + 'id' => 5678, + ], + ], + ], + ], + ], + $item->toJsonApiArray() + ); + } + + /** + * @test + */ + public function is_adds_empty_morphtomany_relation_in_to_json_api_array() + { + $item = new WithRelationshipItem(); + $item->setId(1234); + $item->morphtomanyRelation()->dissociate(); + + $this->assertEquals( + [ + 'type' => 'item-with-relationship', + 'id' => 1234, + 'relationships' => [ + 'morphtomany_relation' => [ + 'data' => [], + ], + ], + ], + $item->toJsonApiArray() + ); + } }