Skip to content

Commit

Permalink
Merge pull request #22 from tuyakhov/rest_actions
Browse files Browse the repository at this point in the history
Json api actions
  • Loading branch information
tuyakhov committed Mar 19, 2017
2 parents acea2ed + adaf485 commit 2d21bbd
Show file tree
Hide file tree
Showing 14 changed files with 443 additions and 100 deletions.
92 changes: 66 additions & 26 deletions src/JsonApiParser.php
Expand Up @@ -6,6 +6,7 @@
namespace tuyakhov\jsonapi;

use yii\helpers\ArrayHelper;
use yii\web\BadRequestHttpException;
use \yii\web\JsonParser;

class JsonApiParser extends JsonParser
Expand Down Expand Up @@ -33,37 +34,27 @@ class JsonApiParser extends JsonParser
public function parse($rawBody, $contentType)
{
$array = parent::parse($rawBody, $contentType);
if ($type = ArrayHelper::getValue($array, 'data.type')) {
$formName = $this->typeToFormName($type);
if ($attributes = ArrayHelper::getValue($array, 'data.attributes')) {
$result[$formName] = array_combine($this->parseMemberNames(array_keys($attributes)), array_values($attributes));
} elseif ($id = ArrayHelper::getValue($array, 'data.id')) {
$result[$formName] = ['id' => $id, 'type' => $type];
}
if ($relationships = ArrayHelper::getValue($array, 'data.relationships')) {
foreach ($relationships as $name => $relationship) {
if (isset($relationship[0])) {
foreach ($relationship as $item) {
if (isset($item['type']) && isset($item['id'])) {
$formName = $this->typeToFormName($item['type']);
$result[$name][$formName][] = $item;
}
}
} elseif (isset($relationship['type']) && isset($relationship['id'])) {
$formName = $this->typeToFormName($relationship['type']);
$result[$name][$formName] = $relationship;
}
}
$data = ArrayHelper::getValue($array, 'data', []);
if (empty($data)) {
if ($this->throwException) {
throw new BadRequestHttpException('The request MUST include a single resource object as primary data.');
}
return [];
}
if (ArrayHelper::isAssociative($data)) {
$result = $this->parseResource($data);

$relObjects = ArrayHelper::getValue($data, 'relationships', []);
$result['relationships'] = $this->parseRelationships($relObjects);
} else {
$data = ArrayHelper::getValue($array, 'data', []);
foreach ($data as $relationLink) {
if (isset($relationLink['type']) && isset($relationLink['id'])) {
$formName = $this->typeToFormName($relationLink['type']);
$result[$formName][] = $relationLink;
foreach ($data as $object) {
$resource = $this->parseResource($object);
foreach (array_keys($resource) as $key) {
$result[$key][] = $resource[$key];
}
}
}

return isset($result) ? $result : $array;
}

Expand All @@ -84,4 +75,53 @@ protected function parseMemberNames(array $memberNames = [])
{
return array_map($this->memberNameCallback, $memberNames);
}

/**
* @param $item
* @return array
* @throws BadRequestHttpException
*/
protected function parseResource($item)
{
if (!$type = ArrayHelper::getValue($item, 'type')) {
if ($this->throwException) {
throw new BadRequestHttpException('The resource object MUST contain at least a type member');
}
return [];
}
$formName = $this->typeToFormName($type);

$attributes = ArrayHelper::getValue($item, 'attributes', []);
$attributes = array_combine($this->parseMemberNames(array_keys($attributes)), array_values($attributes));

if ($id = ArrayHelper::getValue($item, 'id')) {
$attributes['id'] = $id;
}

return [$formName => $attributes];
}

/**
* @param array $relObjects
* @return array
*/
protected function parseRelationships(array $relObjects = [])
{
$relationships = [];
foreach ($relObjects as $name => $relationship) {
if (!$relData = ArrayHelper::getValue($relationship, 'data')) {
continue;
}
if (!ArrayHelper::isIndexed($relData)) {
$relData = [$relData];
}
foreach ($relData as $identifier) {
if (isset($identifier['type']) && isset($identifier['id'])) {
$formName = $this->typeToFormName($identifier['type']);
$relationships[$name][$formName][] = ['id' => $identifier['id']];
}
}
}
return $relationships;
}
}
6 changes: 5 additions & 1 deletion src/ResourceTrait.php
Expand Up @@ -69,10 +69,14 @@ public function getResourceRelationships()

/**
* @param string $name the case sensitive name of the relationship.
* @param $relationship
* @param array|ActiveRecordInterface $relationship
*/
public function setResourceRelationship($name, $relationship)
{
/** @var $this ActiveRecordInterface */
if (!$this instanceof ActiveRecordInterface) {
return;
}
if (!is_array($relationship)) {
$relationship = [$relationship];
}
Expand Down
69 changes: 69 additions & 0 deletions src/actions/Action.php
@@ -0,0 +1,69 @@
<?php
/**
* @author Anton Tuyakhov <atuyakhov@gmail.com>
*/

namespace tuyakhov\jsonapi\actions;

use tuyakhov\jsonapi\ResourceInterface;
use yii\db\ActiveRecordInterface;
use yii\db\BaseActiveRecord;
use yii\helpers\ArrayHelper;

class Action extends \yii\rest\Action
{
/**
* Links the relationships with primary model.
* @var callable
*/
public $linkRelationships;

/**
* @var bool Weather allow to do a full replacement of a to-many relationship
*/
public $allowFullReplacement = true;

/**
* Links the relationships with primary model.
* @param $model ActiveRecordInterface
* @param array $data
*/
protected function linkRelationships($model, array $data = [])
{
if ($this->linkRelationships !== null) {
call_user_func($this->linkRelationships, $this, $model, $data);
return;
}

if (!$model instanceof ResourceInterface) {
return;
}

foreach ($data as $name => $relationship) {
if (!$related = $model->getRelation($name, false)) {
continue;
}
/** @var BaseActiveRecord $relatedClass */
$relatedClass = new $related->modelClass;
$relationships = ArrayHelper::keyExists($relatedClass->formName(), $relationship) ? $relationship[$relatedClass->formName()] : [];

$ids = [];
foreach ($relationships as $index => $relObject) {
if (!isset($relObject['id'])) {
continue;
}
$ids[] = $relObject['id'];
}

if (!$records = $relatedClass::find()->andWhere(['in', $relatedClass::primaryKey(), $ids])->all()) {
continue;
}

if ($related->multiple && !$this->allowFullReplacement) {
continue;
}
$model->unlinkAll($name);
$model->setResourceRelationship($name, $records);
}
}
}
61 changes: 61 additions & 0 deletions src/actions/CreateAction.php
@@ -0,0 +1,61 @@
<?php
/**
* @author Anton Tuyakhov <atuyakhov@gmail.com>
*/

namespace tuyakhov\jsonapi\actions;

use Yii;
use yii\base\Model;
use yii\helpers\Url;
use yii\web\ServerErrorHttpException;

class CreateAction 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';

/**
* Links the relationships with primary model.
* @var callable
*/
public $linkRelationships;

/**
* Creates a new resource.
* @return \yii\db\ActiveRecordInterface the model newly created
* @throws ServerErrorHttpException if there is any error when creating the model
*/
public function run()
{
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id);
}

/* @var $model \yii\db\ActiveRecord */
$model = new $this->modelClass([
'scenario' => $this->scenario,
]);

$request = Yii::$app->getRequest();
$model->load($request->getBodyParams());
if ($model->save()) {
$this->linkRelationships($model, $request->getBodyParam('relationships'));
$response = Yii::$app->getResponse();
$response->setStatusCode(201);
$id = implode(',', array_values($model->getPrimaryKey(true)));
$response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true));
} elseif (!$model->hasErrors()) {
throw new ServerErrorHttpException('Failed to create the object for unknown reason.');
}

return $model;
}
}
46 changes: 46 additions & 0 deletions src/actions/UpdateAction.php
@@ -0,0 +1,46 @@
<?php
/**
* @author Anton Tuyakhov <atuyakhov@gmail.com>
*/

namespace tuyakhov\jsonapi\actions;

use yii\base\Model;
use yii\db\ActiveRecord;
use Yii;
use yii\web\ServerErrorHttpException;

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

/**
* Updates an existing resource.
* @param string $id the primary key of the model.
* @return \yii\db\ActiveRecordInterface the model being updated
* @throws ServerErrorHttpException if there is any error when updating the model
*/
public function run($id)
{
/* @var $model ActiveRecord */
$model = $this->findModel($id);

if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id, $model);
}

$request = Yii::$app->getRequest();
$model->scenario = $this->scenario;
$model->load($request->getBodyParams());
if ($model->save() === false && !$model->hasErrors()) {
throw new ServerErrorHttpException('Failed to update the object for unknown reason.');
}

$this->linkRelationships($model, $request->getBodyParam('relationships'));

return $model;
}
}

0 comments on commit 2d21bbd

Please sign in to comment.