From bbb7822f95cc8b047d8b823bf7f965445e110147 Mon Sep 17 00:00:00 2001 From: Luke Gibson Date: Tue, 19 Feb 2019 16:50:12 +0000 Subject: [PATCH 1/5] Throw a method not supported on POST singular relation update --- src/actions/UpdateRelationshipAction.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/actions/UpdateRelationshipAction.php b/src/actions/UpdateRelationshipAction.php index 54deb24..d2b479e 100644 --- a/src/actions/UpdateRelationshipAction.php +++ b/src/actions/UpdateRelationshipAction.php @@ -9,6 +9,7 @@ use yii\db\BaseActiveRecord; use yii\web\BadRequestHttpException; use yii\web\NotFoundHttpException; +use yii\web\MethodNotAllowedHttpException; use Yii; /** @@ -34,6 +35,10 @@ public function run($id, $name) throw new NotFoundHttpException('Relationship does not exist'); } + if (!$related->multiple && Yii::$app->request->isPost) { + throw new MethodNotAllowedHttpException(); + } + if ($this->checkAccess) { call_user_func($this->checkAccess, $this->id, $model, $name); } From df6b29fd432066a72573ab77f67565c82bd50679 Mon Sep 17 00:00:00 2001 From: Luke Gibson Date: Wed, 20 Feb 2019 13:53:20 +0000 Subject: [PATCH 2/5] Added in view relationship actions as per the json:api sec --- src/Relationship.php | 25 ++++++++ src/Serializer.php | 39 +++++++++++- src/actions/ViewRelationshipAction.php | 52 ++++++++++++++++ tests/SerializerTest.php | 62 ++++++++++++++++++++ tests/actions/ViewRelationshipActionTest.php | 56 ++++++++++++++++++ 5 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 src/Relationship.php create mode 100644 src/actions/ViewRelationshipAction.php create mode 100644 tests/actions/ViewRelationshipActionTest.php diff --git a/src/Relationship.php b/src/Relationship.php new file mode 100644 index 0000000..25744ed --- /dev/null +++ b/src/Relationship.php @@ -0,0 +1,25 @@ +addRelated([$related]); + } + + $this->relations = array_merge($this->relations, $related); + return $this; + } +} \ No newline at end of file diff --git a/src/Serializer.php b/src/Serializer.php index 481f79c..eb3bd65 100644 --- a/src/Serializer.php +++ b/src/Serializer.php @@ -96,6 +96,8 @@ public function serialize($data) return $this->serializeResource($data); } elseif ($data instanceof DataProviderInterface) { return $this->serializeDataProvider($data); + } elseif ($data instanceof Relationship) { + return $this->serializeRelationshipResource($data); } else { return $data; } @@ -109,7 +111,7 @@ public function serialize($data) protected function serializeModel(ResourceInterface $model, array $included = []) { $fields = $this->getRequestedFields(); - $type = $this->pluralize ? Inflector::pluralize($model->getType()) : $model->getType(); + $type = $this->getType($model); $fields = isset($fields[$type]) ? $fields[$type] : []; $topLevel = array_map(function($item) { @@ -333,6 +335,36 @@ protected function serializeModelErrors($model) return $result; } + protected function serializeRelationshipResource($relationship) + { + return [ + 'data' => $this->serializeRelationships($relationship) + ]; + } + + public function serializeRelationships($relationship) + { + if (!$relationship->multiple) { + if (!count($relationship->relations)) { + return null; + } + return $this->serializeRelationship($relationship->relations[0]); + } + + return array_map(function($relation) { + return $this->serializeRelationship($relation); + }, $relationship->relations); + } + + public function serializeRelationship($relationship) + { + $primaryKey = $relationship->getPrimaryKey(true); + return [ + "id" => implode("-", $primaryKey), + "type" => $this->getType($relationship), + ]; + } + /** * @return array */ @@ -369,4 +401,9 @@ protected function prepareMemberNames(array $memberNames = []) { return array_map($this->prepareMemberName, $memberNames); } + + protected function getType($model) + { + return $this->pluralize ? Inflector::pluralize($model->getType()) : $model->getType(); + } } diff --git a/src/actions/ViewRelationshipAction.php b/src/actions/ViewRelationshipAction.php new file mode 100644 index 0000000..2308a1a --- /dev/null +++ b/src/actions/ViewRelationshipAction.php @@ -0,0 +1,52 @@ +findModel($id); + + if (!$related = $model->getRelation($name, false)) { + throw new NotFoundHttpException('Relationship does not exist'); + } + + if ($this->checkAccess) { + call_user_func($this->checkAccess, $this->id, $model, $name); + } + + $relationship = new Relationship([ + 'multiple' => $related->multiple + ]); + + if ($related->multiple) { + return $relationship->addRelated($related->all()); + } + + return $relationship->addRelated($related->one()); + } +} \ No newline at end of file diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php index eba491d..b0cd1a7 100644 --- a/tests/SerializerTest.php +++ b/tests/SerializerTest.php @@ -6,6 +6,7 @@ use tuyakhov\jsonapi\tests\data\ResourceModel; use tuyakhov\jsonapi\Serializer; +use tuyakhov\jsonapi\Relationship; use yii\base\InvalidValueException; use yii\data\ArrayDataProvider; @@ -490,4 +491,65 @@ public function testTypeInflection() ] ], $serializer->serialize($model)); } + + public function testSerializeRelationshipMultipleEmpty() + { + $serializer = new Serializer(); + $relationship = new Relationship([ + 'multiple' => true + ]); + $response = $serializer->serialize($relationship); + $this->assertSame([ + 'data' => [] + ], $response); + } + + public function testSerializeRelationshipMultipleSuccess() + { + $serializer = new Serializer(); + $models = [ + new ResourceModel(), + ]; + $relationship = new Relationship([ + 'multiple' => true, + 'relations' => $models + ]); + $response = $serializer->serialize($relationship); + $this->assertSame([ + 'data' => [ + [ + 'id' => '123', + 'type' => 'resource-models' + ] + ] + ], $response); + } + + public function testSerializeRelationshipSingleEmpty() + { + $serializer = new Serializer(); + $relationship = new Relationship([]); + $response = $serializer->serialize($relationship); + $this->assertSame([ + 'data' => null + ], $response); + } + + public function testSerializeRelationshipSingleSuccess() + { + $serializer = new Serializer(); + $models = [ + new ResourceModel(), + ]; + $relationship = new Relationship([ + 'relations' => $models + ]); + $response = $serializer->serialize($relationship); + $this->assertSame([ + 'data' => [ + 'id' => '123', + 'type' => 'resource-models' + ] + ], $response); + } } diff --git a/tests/actions/ViewRelationshipActionTest.php b/tests/actions/ViewRelationshipActionTest.php new file mode 100644 index 0000000..acb44b1 --- /dev/null +++ b/tests/actions/ViewRelationshipActionTest.php @@ -0,0 +1,56 @@ + + */ + +namespace tuyakhov\jsonapi\tests\actions; + +use tuyakhov\jsonapi\actions\ViewRelationshipAction; +use tuyakhov\jsonapi\tests\data\ActiveQuery; +use tuyakhov\jsonapi\tests\data\ResourceModel; +use tuyakhov\jsonapi\tests\TestCase; +use yii\data\ActiveDataProvider; +use yii\web\Controller; +use yii\web\ForbiddenHttpException; +use tuyakhov\jsonapi\Relationship; + +class ViewRelationshipActionTest extends TestCase +{ + public function testMultipleSuccess() + { + $model = new ResourceModel(); + $action = new ViewRelationshipAction('test', new Controller('test', \Yii::$app), [ + 'modelClass' => ResourceModel::className() + ]); + ResourceModel::$related = [ + 'extraField1' => new ActiveQuery(ResourceModel::className(), ['multiple' => true]), + ]; + $action->findModel = function ($id, $action) use($model) { + return $model; + }; + $model->extraField1 = [new ResourceModel()]; + $this->assertInstanceOf(Relationship::className(), $relationship = $action->run(1, 'extraField1')); + $this->assertTrue($relationship->multiple); + $this->assertCount(2, $relationship->relations); + $this->assertInstanceOf(ResourceModel::className(), $relationship->relations[0]); + } + + public function testSingleSuccess() + { + $model = new ResourceModel(); + $action = new ViewRelationshipAction('test', new Controller('test', \Yii::$app), [ + 'modelClass' => ResourceModel::className() + ]); + ResourceModel::$related = [ + 'extraField1' => new ActiveQuery(ResourceModel::className()), + ]; + $action->findModel = function ($id, $action) use($model) { + return $model; + }; + $model->extraField1 = new ResourceModel(); + $this->assertInstanceOf(Relationship::className(), $relationship = $action->run(1, 'extraField1')); + $this->assertCount(1, $relationship->relations); + $this->assertInstanceOf(ResourceModel::className(), $relationship->relations[0]); + } + +} \ No newline at end of file From 3321f66059834268d828ed9361acb567764cf334 Mon Sep 17 00:00:00 2001 From: Luke Gibson Date: Wed, 20 Feb 2019 14:16:04 +0000 Subject: [PATCH 3/5] Revert accidental commit --- src/actions/UpdateRelationshipAction.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/actions/UpdateRelationshipAction.php b/src/actions/UpdateRelationshipAction.php index d2b479e..54deb24 100644 --- a/src/actions/UpdateRelationshipAction.php +++ b/src/actions/UpdateRelationshipAction.php @@ -9,7 +9,6 @@ use yii\db\BaseActiveRecord; use yii\web\BadRequestHttpException; use yii\web\NotFoundHttpException; -use yii\web\MethodNotAllowedHttpException; use Yii; /** @@ -35,10 +34,6 @@ public function run($id, $name) throw new NotFoundHttpException('Relationship does not exist'); } - if (!$related->multiple && Yii::$app->request->isPost) { - throw new MethodNotAllowedHttpException(); - } - if ($this->checkAccess) { call_user_func($this->checkAccess, $this->id, $model, $name); } From d772ecdbd5ff3aedcda56a49f1f744c11ca9e243 Mon Sep 17 00:00:00 2001 From: Luke Gibson Date: Wed, 20 Feb 2019 15:05:48 +0000 Subject: [PATCH 4/5] Updated README.md and added rule into the proper location --- README.md | 14 ++++++-------- src/UrlRule.php | 3 ++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2a91471..3f8ea88 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,10 @@ class UserController extends \yii\rest\Controller 'class' => 'tuyakhov\jsonapi\actions\ViewRelatedAction', 'modelClass' => ExampleModel::className() ], + 'view-relationship' => [ + 'class' => 'tuyakhov\jsonapi\actions\ViewRelationsipAction', + 'modelClass' => ExampleModel::className() + ], 'update-relationship' => [ 'class' => 'tuyakhov\jsonapi\actions\UpdateRelationshipAction', 'modelClass' => ExampleModel::className() @@ -313,14 +317,8 @@ return [ 'urlManager' => [ 'rules' => [ [ - 'class' => 'yii\rest\UrlRule', - 'controller' => 'user', - 'extraPatterns' => [ - 'GET {id}/' => 'view-related', - 'PATCH {id}/relationships/' => 'update-relationship', - 'DELETE {id}/relationships/' => 'delete-relationship', - '{id}/' => 'options' - ], + 'class' => 'tukyahov\jsonapi\UrlRule', + 'controller' => ['user'], 'except' => ['index'], ], diff --git a/src/UrlRule.php b/src/UrlRule.php index a5206fd..c6ef169 100644 --- a/src/UrlRule.php +++ b/src/UrlRule.php @@ -24,7 +24,8 @@ public function init() 'DELETE {id}/relationships/{relationship}' => 'delete-relationship', 'POST,PATCH {id}/relationships/{relationship}' => 'update-relationship', 'GET {id}/{relationship}' => 'view-related', - '{id}/{relationship}' => 'options' + '{id}/{relationship}' => 'options', + 'GET {id}/relationships/{relationship}' => 'view-relationship', ]); parent::init(); } From aab5d07b90c6a9591a41beec8e1e43ec456fce9c Mon Sep 17 00:00:00 2001 From: Luke Gibson Date: Mon, 25 Feb 2019 11:10:09 +0000 Subject: [PATCH 5/5] added some additional rules into the default rules to adhere better to json:api --- src/UrlRule.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/UrlRule.php b/src/UrlRule.php index c6ef169..bee6d8a 100644 --- a/src/UrlRule.php +++ b/src/UrlRule.php @@ -21,11 +21,12 @@ public function init() '{relationship}' => '' ])); $this->patterns = array_merge($this->patterns, [ + 'POST' => 'create', 'DELETE {id}/relationships/{relationship}' => 'delete-relationship', 'POST,PATCH {id}/relationships/{relationship}' => 'update-relationship', - 'GET {id}/{relationship}' => 'view-related', + 'HEAD,GET {id}/{relationship}' => 'view-related', '{id}/{relationship}' => 'options', - 'GET {id}/relationships/{relationship}' => 'view-relationship', + 'HEAD,GET {id}/relationships/{relationship}' => 'view-relationship', ]); parent::init(); }