diff --git a/src/Controller/PATCH.php b/src/Controller/PATCH.php index 61d1fe6..9a9751d 100644 --- a/src/Controller/PATCH.php +++ b/src/Controller/PATCH.php @@ -16,8 +16,11 @@ */ namespace Phramework\JSONAPI\Controller; +use Phramework\Exceptions\ServerException; +use Phramework\JSONAPI\Relationship; use \Phramework\Models\Request; use \Phramework\Exceptions\RequestException; +use Phramework\Phramework; use Phramework\Validate\ObjectValidator; /** @@ -29,7 +32,7 @@ abstract class PATCH extends \Phramework\JSONAPI\Controller\POST { /** - * @param object $parameters Request parameters + * @param object $parameters Request parameters * @param string $method Request method * @param array $headers Request headers * @param integer|string $id Requested resource's id @@ -37,6 +40,8 @@ abstract class PATCH extends \Phramework\JSONAPI\Controller\POST * to be used * @param array $primaryDataParameters [Optional] Array with any * additional arguments that the primary data is requiring + * @param callable[] $validationCallbacks + * @param callable|null $viewCallback * @throws \Phramework\Exceptions\NotFound If resource not found * @throws \Phramework\Exceptions\RequestException If no fields are changed * @uses model's `getById` method to fetch resource @@ -47,6 +52,7 @@ abstract class PATCH extends \Phramework\JSONAPI\Controller\POST * @throws \Exception When Validation model for an attribute is not set * @return boolean * @todo rethink output + */ protected static function handlePATCH( $parameters, @@ -54,7 +60,9 @@ protected static function handlePATCH( $headers, $id, $modelClass, - $primaryDataParameters = [] + $primaryDataParameters = [], + $validationCallbacks = [], + $viewCallback = null ) { //Construct a validator $validator = new ObjectValidator( @@ -86,32 +94,98 @@ protected static function handlePATCH( $requestAttributes = static::getRequestAttributes($parameters); - if (count((array) $requestAttributes) === 0) { - //@todo throw exception only both attributes and relationships are 0 + if (($requestData = self::getRequestData($parameters)) !== null + && property_exists($requestData, 'relationships')) { + $requestRelationships = $requestData->relationships; + } else { + $requestRelationships = new \stdClass(); + } + + if (count((array) $requestAttributes) === 0 && count((array) $requestRelationships) === 0) { throw new RequestException('No fields updated'); } $attributes = $validator->parse($requestAttributes); + //Parse relationship attributes, NOTE type TO_ONE relationships are writing their data back to $attributes object + $parsedRelationshipAttributes = self::getParsedRelationshipAttributes( + $modelClass, + $attributes, + $requestRelationships + ); + + //Check if callbacks for TO_MANY relationships are set + foreach ($parsedRelationshipAttributes as $relationshipKey => $relationshipValues) { + $relationship = $modelClass::getRelationship($relationshipKey); + + if ($relationship->type == Relationship::TYPE_TO_MANY) { + if (!isset($relationship->callbacks->{Phramework::METHOD_PATCH})) { + throw new ServerException(sprintf( + 'PATCH callback is not implemented for relationship "%s"', + $relationshipKey + )); + } + } + } + + //TODO allow nulls foreach ($attributes as $key => $attribute) { if ($attribute === null) { unset($attributes->{$key}); } } + //Call validation callbacks if set + foreach ($validationCallbacks as $callback) { + call_user_func( + $callback, + $requestAttributes, + $requestRelationships, + $attributes, + $parsedRelationshipAttributes + ); + } + //Fetch data, to check if resource exists - $data = $modelClass::getById( + $currentResource = $modelClass::getById( $id, - null, //fields + null, //fields todo maybe add [] ...$primaryDataParameters ); //Check if resource exists (MUST exist!) - static::exists($data); + static::exists($currentResource); + + //Update the resource's attributes directly if any of then is requested to PATCH + if (count((array) $attributes) > 0) { + $patch = $modelClass::patch($id, (array)$attributes); + self::testUnknownError($patch, 'PATCH operation was not successful'); + } - $patch = $modelClass::patch($id, (array) $attributes); + //Call TO_MANY callbacks to PATCH relationships + foreach ($parsedRelationshipAttributes as $relationshipKey => $relationshipValues) { + $relationship = $modelClass::getRelationship($relationshipKey); + + if ($relationship->type == Relationship::TYPE_TO_MANY) { + call_user_func( + $relationship->callbacks->{Phramework::METHOD_PATCH}, + $relationshipValues, //complete replacement + $id, + null //$additionalAttributes + ); + } + } - self::testUnknownError($patch, 'PATCH operation was not successful'); + if ($viewCallback !== null) { + if (!is_callable($viewCallback)) { + throw new ServerException('View callback is not callable!'); + } + + return call_user_func( + $viewCallback, + $id + ); + } static::viewData( $modelClass::resource(['id' => $id]), diff --git a/src/Controller/POST.php b/src/Controller/POST.php index a316706..5bb027f 100644 --- a/src/Controller/POST.php +++ b/src/Controller/POST.php @@ -45,6 +45,7 @@ abstract class POST extends \Phramework\JSONAPI\Controller\GET * @param array $relationshipParameters *[Optional]* Array with any * additional argument primary data's relationships are requiring * @param callable[] $validationCallbacks + * @param callable|null $viewCallback * @todo handle as transaction queue, Since models usually are not producing exceptions. * Prepare data until last possible moment, * so that any exceptions can be thrown, and finally invoke the execution of the queue. @@ -128,7 +129,7 @@ protected static function handlePOST( } /** - * @var int[] + * @var string[] */ $ids = []; @@ -146,7 +147,6 @@ protected static function handlePOST( $relationships = $queueItem->getRelationships(); foreach ($relationships as $key => $relationship) { - //@TODO //Call post relationship method to post each of relationships pairs foreach ($relationship->resources as $resourceId) { call_user_func( @@ -221,7 +221,7 @@ private static function handlePOSTResource( $validator = $modelClass::getValidationModel(); $attributesValidator = ( - isset($validator->attributes) && $validator->attributes + isset($validator->attributes) && $validator->attributes ? $validator->attributes : new ObjectValidator() ); @@ -230,7 +230,71 @@ private static function handlePOSTResource( //Parse request attributes using $validationModel to validate the data $attributes = $attributesValidator->parse($requestAttributes); - $relationships = $modelClass::getRelationships(); + $parsedRelationshipAttributes = self::getParsedRelationshipAttributes( + $modelClass, + $attributes, + $requestRelationships, + $relationshipParameters + ); + + //Call Validation callbacks + foreach ($validationCallbacks as $callback) { + call_user_func( + $callback, + $requestAttributes, + $requestRelationships, + $attributes, + $parsedRelationshipAttributes + ); + } + + $queueRelationships = new \stdClass(); + + /** + * Call POST_RELATIONSHIP_BY_PREFIX handler for TO_MANY relationships + * This handler should post into database these relationships + */ + foreach ($requestRelationships as $relationshipKey => $relationshipValue) { + $relationship = $modelClass::getRelationship($relationshipKey); + + if ($relationship->type == Relationship::TYPE_TO_MANY) { + $parsedRelationshipValue = $parsedRelationshipAttributes->{$relationshipKey}; + + $relationship = $modelClass::getRelationship($relationshipKey); + + if (!isset($relationship->callbacks->{Phramework::METHOD_POST})) { + throw new ServerException(sprintf( + 'POST callback is not implemented for relationship "%s"', + $relationshipKey + )); + } + + //Push to queueRelationships + $queueRelationships->{$relationshipKey} = (object) [ + 'callback' => $relationship->callbacks->{Phramework::METHOD_POST}, //callable + 'resources' => $parsedRelationshipValue //array + ]; + } + } + + return new \Phramework\JSONAPI\Controller\POST\QueueItem( + $attributes, + $queueRelationships + ); + } + + /** + * @param string $modelClass + * @param object $attributes + * @param object $requestRelationships + * @throws RequestException + * @throws \Exception + * @throws \Phramework\Exceptions\NotFoundException + * @return object + */ + protected static function getParsedRelationshipAttributes($modelClass, &$attributes, $requestRelationships, $relationshipParameters = []) + { + $validator = $modelClass::getValidationModel(); /** * Format, object with @@ -354,14 +418,13 @@ private static function handlePOSTResource( } //Parse attributes using relationship's validation model - $parsedRelationshipAttributes = - ( + $parsedRelationshipAttributes = ( isset($validator->relationships) ? $validator->relationships->parse( $relationshipAttributes ) - : new \stdClass() - ); + : new \stdClass() + ); /** * Foreach request relationship @@ -378,7 +441,7 @@ private static function handlePOSTResource( $parsedRelationshipValue = $parsedRelationshipAttributes->{$relationshipKey}; $tempIds = ( - is_array($parsedRelationshipValue) + is_array($parsedRelationshipValue) ? $parsedRelationshipValue : [$parsedRelationshipValue] ); @@ -437,48 +500,6 @@ private static function handlePOSTResource( } } - //Call Validation callbacks - foreach ($validationCallbacks as $callback) { - call_user_func( - $callback, - $requestAttributes, - $requestRelationships, - $attributes - ); - } - - $queueRelationships = new \stdClass(); - - /** - * Call POST_RELATIONSHIP_BY_PREFIX handler for TO_MANY relationships - * This handler should post into database these relationships - */ - foreach ($requestRelationships as $relationshipKey => $relationshipValue) { - $relationship = $modelClass::getRelationship($relationshipKey); - - if ($relationship->type == Relationship::TYPE_TO_MANY) { - $parsedRelationshipValue = $parsedRelationshipAttributes->{$relationshipKey}; - - $relationship = $modelClass::getRelationship($relationshipKey); - - if (!isset($relationship->callbacks->{Phramework::METHOD_POST})) { - throw new ServerException(sprintf( - 'POST callback is not implemented for relationship "%s"', - $relationshipKey - )); - } - - //Push to queueRelationships - $queueRelationships->{$relationshipKey} = (object) [ - 'callback' => $relationship->callbacks->{Phramework::METHOD_POST}, //callable - 'resources' => $parsedRelationshipValue //array - ]; - } - } - - return new \Phramework\JSONAPI\Controller\POST\QueueItem( - $attributes, - $queueRelationships - ); + return $parsedRelationshipAttributes; } } diff --git a/tests/APP/Controllers/ArticleController.php b/tests/APP/Controllers/ArticleController.php index bd176e0..d535e9e 100644 --- a/tests/APP/Controllers/ArticleController.php +++ b/tests/APP/Controllers/ArticleController.php @@ -74,7 +74,17 @@ public static function PATCH($params, $method, $headers, $id) $method, $headers, $id, - Article::class + Article::class, + [], + [ + function ( + $requestAttributes, + $requestRelationships, + $attributes, + $parsedRelationshipAttributes + ) { + } + ] ); } diff --git a/tests/APP/Models/Article.php b/tests/APP/Models/Article.php index 312915b..0b1c525 100644 --- a/tests/APP/Models/Article.php +++ b/tests/APP/Models/Article.php @@ -179,8 +179,9 @@ public static function getRelationships() Relationship::TYPE_TO_MANY, null, (object) [ - Phramework::METHOD_GET => [Tag::class, 'getRelationshipByArticle'], - Phramework::METHOD_POST => [Tag::class, 'postRelationshipByArticle'] + Phramework::METHOD_GET => [Tag::class, 'getRelationshipArticle'], + Phramework::METHOD_POST => [Tag::class, 'postRelationshipArticle'], + Phramework::METHOD_PATCH => [Tag::class, 'patchRelationshipArticle'] ], Relationship::FLAG_DEFAULT | Relationship::FLAG_DATA ) diff --git a/tests/APP/Models/Tag.php b/tests/APP/Models/Tag.php index 81406be..a28610f 100644 --- a/tests/APP/Models/Tag.php +++ b/tests/APP/Models/Tag.php @@ -108,7 +108,7 @@ public static function get( * @param integer $return [description] * @return integer [description] */ - public static function postRelationshipByArticle( + public static function postRelationshipArticle( $tagId, $articleId, $additionalAttributes = null, @@ -127,11 +127,19 @@ public static function postRelationshipByArticle( return 1; } + public static function patchRelationshipArticle( + $tagId, + $articleId, + $additionalAttributes = null + ) { + return true; + } + /** * @param $articleId * @return RelationshipResource[] */ - public static function getRelationshipByArticle( + public static function getRelationshipArticle( $articleId, Fields $fields = null, $flags = Resource::PARSE_DEFAULT diff --git a/tests/src/Controllers/PATCHTest.php b/tests/src/Controllers/PATCHTest.php index 94bbc77..25bc3e1 100644 --- a/tests/src/Controllers/PATCHTest.php +++ b/tests/src/Controllers/PATCHTest.php @@ -16,6 +16,7 @@ */ namespace Phramework\JSONAPI\Controller; +use Phramework\JSONAPI\APP\Models\User; use \Phramework\Phramework; /** @@ -48,25 +49,28 @@ protected function prepare() $_SERVER['REQUEST_URI'] = '/article/1'; $_SERVER['REQUEST_METHOD'] = Phramework::METHOD_PATCH; - $_POST['data'] = [ + $_POST['data'] = (object) [ 'type' => 'article', - 'id' => 1, + 'id' => '1', 'attributes' => [ 'title' => 'omg' ], - 'relationships' => [ - 'creator' => [ - 'data' => [ - 'type' => 'user', 'id' => '1' + 'relationships' => (object) [ + 'creator' => (object)[ + 'data' => (object)[ + 'type' => 'user', + 'id' => '1' ] ], - 'tag' => [ + 'tag' => (object) [ 'data' => [ - [ - 'type' => 'tag', 'id' => '3' + (object) [ + 'type' => 'tag', + 'id' => '3' ], - [ - 'type' => 'tag', 'id' => '2' + (object) [ + 'type' => 'tag', + 'id' => '2' ] ] ] @@ -98,12 +102,12 @@ function ( public function testPATCHSuccess() { $this->prepare(); - //ob_clean(); $this->phramework->invoke(); - //ob_end_clean(); - //Access parameters written by invoked phramework's viewer $parameters = $this->parameters; + + //var_dump($parameters); + return; $this->assertInternalType('object', $parameters); @@ -148,11 +152,10 @@ public function testPATCHSuccess() */ public function testPATCHFailureToOne() { - return; $this->prepare(); //Set a non existing id for creator relationship - $_PATCH['data']['relationships']['creator']['data']['id'] = 4235454365434; + $_POST['data']->relationships->creator->data->id = 4235454365434; $this->phramework->invoke(); @@ -175,11 +178,10 @@ public function testPATCHFailureToOne() */ public function testPATCHFailureToMany() { - return; $this->prepare(); //Set a non existing id for tag relationship - $_PATCH['data']['relationships']['tag']['data'][0]['id'] = 4235454365434; + $_POST['data']->relationships->tag->data[0]->id = 4235454365434; $this->phramework->invoke(); diff --git a/tests/src/Controllers/POSTTest.php b/tests/src/Controllers/POSTTest.php index 9aed64f..deeb9df 100644 --- a/tests/src/Controllers/POSTTest.php +++ b/tests/src/Controllers/POSTTest.php @@ -51,23 +51,23 @@ protected function prepare() $_SERVER['REQUEST_URI'] = '/article/'; $_SERVER['REQUEST_METHOD'] = Phramework::METHOD_POST; - $_POST['data'] = [ + $_POST['data'] = (object) [ 'type' => 'article', - 'attributes' => [ + 'attributes' => (object) [ 'title' => 'omg' ], - 'relationships' => [ - 'creator' => [ - 'data' => [ + 'relationships' => (object) [ + 'creator' => (object) [ + 'data' => (object) [ 'type' => 'user', 'id' => '1' ] ], - 'tag' => [ + 'tag' => (object) [ 'data' => [ - [ + (object) [ 'type' => 'tag', 'id' => '3' ], - [ + (object) [ 'type' => 'tag', 'id' => '2' ] ] @@ -174,11 +174,10 @@ public function testPOSTSuccess() */ public function testPOSTFailureToOne() { - return; $this->prepare(); //Set a non existing id for creator relationship - $_POST['data']['relationships']['creator']['data']['id'] = 4235454365434; + $_POST['data']->relationships->creator->data->id = 4235454365434; $this->phramework->invoke(); @@ -201,11 +200,10 @@ public function testPOSTFailureToOne() */ public function testPOSTFailureToMany() { - return; $this->prepare(); //Set a non existing id for tag relationship - $_POST['data']['relationships']['tag']['data'][0]['id'] = 4235454365434; + $_POST['data']->relationships->tag->data[0]->id = 4235454365434; $this->phramework->invoke(); diff --git a/tests/src/Model/RelationshipTest.php b/tests/src/Model/RelationshipTest.php index f209b34..282a968 100644 --- a/tests/src/Model/RelationshipTest.php +++ b/tests/src/Model/RelationshipTest.php @@ -36,7 +36,7 @@ public function testGetRelationshipData() //print_r($articles); - $relationship = Tag::getRelationshipByArticle( + $relationship = Tag::getRelationshipArticle( '1', null ); diff --git a/tests/src/RelationshipTest.php b/tests/src/RelationshipTest.php index d296d05..528db1f 100644 --- a/tests/src/RelationshipTest.php +++ b/tests/src/RelationshipTest.php @@ -71,7 +71,7 @@ public function testConstruct2() Tag::class, Relationship::TYPE_TO_ONE, 'tag-id', - [Tag::class, 'getRelationshipByArticle'] + [Tag::class, 'getRelationshipArticle'] ); } @@ -85,7 +85,7 @@ public function testConstruct3() Relationship::TYPE_TO_ONE, 'tag-id', (object) [ - Phramework::METHOD_GET => [Tag::class, 'getRelationshipByArticle'] + Phramework::METHOD_GET => [Tag::class, 'getRelationshipArticle'] ] ); }