Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

View relationship #59

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -313,14 +317,8 @@ return [
'urlManager' => [
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => 'user',
'extraPatterns' => [
'GET {id}/<name:\w+>' => 'view-related',
'PATCH {id}/relationships/<name:\w+>' => 'update-relationship',
'DELETE {id}/relationships/<name:\w+>' => 'delete-relationship',
'{id}/<name:\w+>' => 'options'
],
'class' => 'tukyahov\jsonapi\UrlRule',
'controller' => ['user'],
'except' => ['index'],
],

Expand Down
25 changes: 25 additions & 0 deletions src/Relationship.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace tuyakhov\jsonapi;

use yii\base\Model;

class Relationship extends Model
{
public $multiple = false;
public $relations = [];

public function addRelated($related)
{
if (!$related) {
return $this;
}

if (!is_array($related)) {
return $this->addRelated([$related]);
}

$this->relations = array_merge($this->relations, $related);
return $this;
}
}
39 changes: 38 additions & 1 deletion src/Serializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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();
}
}
6 changes: 4 additions & 2 deletions src/UrlRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ public function init()
'{relationship}' => '<name:\w+>'
]));
$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',
'{id}/{relationship}' => 'options'
'HEAD,GET {id}/{relationship}' => 'view-related',
'{id}/{relationship}' => 'options',
'HEAD,GET {id}/relationships/{relationship}' => 'view-relationship',
]);
parent::init();
}
Expand Down
52 changes: 52 additions & 0 deletions src/actions/ViewRelationshipAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace tuyakhov\jsonapi\actions;

use Yii;
use yii\base\Model;
use yii\web\NotFoundHttpException;
use tuyakhov\jsonapi\Inflector;
use tuyakhov\jsonapi\Relationship;


class ViewRelationshipAction extends Action
{
/**
* @var string the scenario to be assigned to the new model before it is validated and saved.
*/
public $scenario = Model::SCENARIO_DEFAULT;

/**
* @var string the name of the view action. This property is need to create the URL when the model is successfully created.
*/
public $viewAction = 'view-relationship';

/**
* Gets a relationship between resources
* @return array a relationship definition between resources
* @throws NotFoundHttpException if the relationship doesn't exist
*/
public function run($id, $name)
{
/* @var $model \yii\db\ActiveRecord */
$model = $this->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());
}
}
62 changes: 62 additions & 0 deletions tests/SerializerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
}
56 changes: 56 additions & 0 deletions tests/actions/ViewRelationshipActionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
/**
* @author Anton Tuyakhov <[email protected]>
*/

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]);
}

}