diff --git a/JsonApi/Request/ActionParser.php b/JsonApi/Request/ActionParser.php index 557d44f..92d50ec 100644 --- a/JsonApi/Request/ActionParser.php +++ b/JsonApi/Request/ActionParser.php @@ -13,14 +13,15 @@ // Recursos. use GoIntegro\Bundle\HateoasBundle\JsonApi\DocumentPagination; // JSON. -use GoIntegro\Bundle\HateoasBundle\Util\JsonCoder; +use GoIntegro\Bundle\HateoasBundle\Util; /** * @see http://jsonapi.org/format/#introduction */ class ActionParser { - const ERROR_REQUEST_SCOPE_UNKNOWN = "Could not calculate request scope; whether it affects one or many resources."; + const ERROR_REQUEST_SCOPE_UNKNOWN = "Could not calculate request scope; whether it affects one or many resources.", + ERROR_RESOURCE_CONTENT_MISSING = "The primary resource data is missing from the body."; /** * @var array This mapping is defined by JSON-API, not HTTP nor REST. @@ -32,6 +33,19 @@ class ActionParser Parser::HTTP_DELETE => RequestAction::ACTION_DELETE ]; + /** + * @var Util\JsonCoder + */ + protected $jsonCoder; + + /** + * @param Util\JsonCoder $jsonCoder + */ + public function __construct(Util\JsonCoder $jsonCoder) + { + $this->jsonCoder = $jsonCoder; + } + /** * @param Request $request * @param Params $params @@ -42,7 +56,7 @@ public function parse(Request $request, Params $params) $action = new RequestAction; $action->name = self::$methodToAction[$request->getMethod()]; - $action->type = $this->isMultipleAction($params, $action) + $action->type = $this->isMultipleAction($request, $params, $action) ? RequestAction::TYPE_MULTIPLE : RequestAction::TYPE_SINGLE; $action->target = !empty($params->relationship) @@ -53,15 +67,18 @@ public function parse(Request $request, Params $params) } /** + * @param Request $request * @param Params $params * @param RequestAction $action * @return boolean * @throws ParseException */ - private function isMultipleAction(Params $params, RequestAction $action) + private function isMultipleAction( + Request $request, Params $params, RequestAction $action) { return $this->isFilteredFetch($params, $action) - || 1 < count($this->getCountable($params, $action)); + || $this->isIdParamAList($params, $action) + || $this->isPrimaryResourceAList($request, $params, $action); } /** @@ -78,13 +95,11 @@ private function isFilteredFetch(Params $params, RequestAction $action) /** * @param Params $params * @param RequestAction $action - * @return array - * @throws ParseException + * @return boolean */ - private function getCountable(Params $params, RequestAction $action) + private function isIdParamAList(Params $params, RequestAction $action) { - if ( - in_array( + return in_array( $action->name, [ RequestAction::ACTION_FETCH, @@ -92,21 +107,37 @@ private function getCountable(Params $params, RequestAction $action) RequestAction::ACTION_DELETE ] ) - && !empty($params->primaryIds) - ) { - return $params->primaryIds; - } elseif ( - RequestAction::ACTION_CREATE === $action->name - && !empty($params->resources) - ) { - return $params->resources; - } elseif ( - RequestAction::ACTION_UPDATE === $action->name - && !empty($params->entities) - ) { - return $params->entities; - } else { - throw new ParseException(self::ERROR_REQUEST_SCOPE_UNKNOWN); + && 1 < count($params->primaryIds); + } + + /** + * @param Request $request + * @param Params $params + * @param RequestAction $action + * @return boolean + * @throws ParseException + */ + private function isPrimaryResourceAList( + Request $request, Params $params, RequestAction $action + ) + { + $json = $request->getContent(); + + if (in_array($action->name, [ + RequestAction::ACTION_CREATE, RequestAction::ACTION_UPDATE + ])) { + $data = $this->jsonCoder->decode($json); + + if (!is_array($data) || !isset($data[$params->primaryType])) { + throw new ParseException(self::ERROR_RESOURCE_CONTENT_MISSING); + } + + return !Util\ArrayHelper::isAssociative( + $data[$params->primaryType] + ); } + + + return FALSE; } } diff --git a/JsonApi/Request/CreateBodyParser.php b/JsonApi/Request/CreateBodyParser.php index 80159ee..9f635bb 100644 --- a/JsonApi/Request/CreateBodyParser.php +++ b/JsonApi/Request/CreateBodyParser.php @@ -9,8 +9,8 @@ // HTTP. use Symfony\Component\HttpFoundation\Request; -// JSON. -use GoIntegro\Bundle\HateoasBundle\Util\JsonCoder; +// Utils. +use GoIntegro\Bundle\HateoasBundle\Util; /** * @see http://jsonapi.org/format/#crud-creating-resources @@ -21,14 +21,14 @@ class CreateBodyParser const ERROR_ID_NOT_SUPPORTED = "Providing an Id on creation is not supported magically yet."; /** - * @var JsonCoder + * @var Util\JsonCoder */ protected $jsonCoder; /** - * @param JsonCoder $jsonCoder + * @param Util\JsonCoder $jsonCoder */ - public function __construct(JsonCoder $jsonCoder) + public function __construct(Util\JsonCoder $jsonCoder) { $this->jsonCoder = $jsonCoder; } @@ -46,7 +46,9 @@ public function parse(Request $request, Params $params) if (empty($data[$params->primaryType])) { throw new ParseException(static::ERROR_PRIMARY_TYPE_KEY); - } elseif ($this->isAssociative($data[$params->primaryType])) { + } elseif ( + Util\ArrayHelper::isAssociative($data[$params->primaryType]) + ) { if (isset($data[$params->primaryType]['id'])) { throw new ParseException(static::ERROR_ID_NOT_SUPPORTED); } else { @@ -64,15 +66,4 @@ public function parse(Request $request, Params $params) return $entityData; } - - /** - * @param array $array - * @return boolean - * @todo Move to helper in utils. - * @see http://stackoverflow.com/a/173479 - */ - private function isAssociative(array $array) - { - return array_keys($array) !== range(0, count($array) - 1); - } } diff --git a/JsonApi/Request/Parser.php b/JsonApi/Request/Parser.php index f0242de..1b59daf 100644 --- a/JsonApi/Request/Parser.php +++ b/JsonApi/Request/Parser.php @@ -155,15 +155,14 @@ public function parse(Request $request) } $params->filters = $this->filterParser->parse($request, $params); + $params->action = $this->actionParser->parse($request, $params); $content = $request->getContent(); if (!empty($content)) { + // Needs the params from the ActionParser. $params->resources = $this->bodyParser->parse($request, $params); } - // Needs the params from the BodyParser. - $params->action = $this->actionParser->parse($request, $params); - if (!empty($params->primaryIds)) { // Needs the params from the ActionParser. $params->entities = $this->entityFinder->find($params); diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 627bdfc..9ebfd08 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -42,6 +42,8 @@ services: hateoas.request_parser.action: class: GoIntegro\Bundle\HateoasBundle\JsonApi\Request\ActionParser public: false + arguments: + - @hateoas.json_coder hateoas.request_parser.entities: class: GoIntegro\Bundle\HateoasBundle\JsonApi\Request\ParamEntityFinder diff --git a/Tests/JsonApi/Request/ActionParserTest.php b/Tests/JsonApi/Request/ActionParserTest.php index 3f8c99f..5e76c1c 100644 --- a/Tests/JsonApi/Request/ActionParserTest.php +++ b/Tests/JsonApi/Request/ActionParserTest.php @@ -36,8 +36,9 @@ public function testParsingSingleUpdateActionRequest() $queryOverrides = [ 'getContent' => function() { return self::UPDATE_BODY; } ]; - $entity = Stub::makeEmpty( - 'GoIntegro\\Bundle\\HateoasBundle\\JsonApi\\ResourceEntityInterface' + $jsonCoder = Stub::makeEmpty( + 'GoIntegro\\Bundle\\HateoasBundle\\Util\\JsonCoder', + ['decode' => json_decode(self::HTTP_PUT_BODY, TRUE)] ); $request = self::createRequest( '/api/v1/users', @@ -49,10 +50,10 @@ public function testParsingSingleUpdateActionRequest() 'GoIntegro\\Bundle\\HateoasBundle\\JsonApi\\Request\\Params', [ 'primaryIds' => ['27'], - 'entities' => [$entity] + 'primaryType' => 'users' ] ); - $parser = new ActionParser; + $parser = new ActionParser($jsonCoder); // When... $action = $parser->parse($request, $params); // Then... diff --git a/Util/ArrayHelper.php b/Util/ArrayHelper.php new file mode 100644 index 0000000..3683ccb --- /dev/null +++ b/Util/ArrayHelper.php @@ -0,0 +1,22 @@ + + */ + +namespace GoIntegro\Bundle\HateoasBundle\Util; + +class ArrayHelper +{ + /** + * @param array $array + * @return boolean + * @todo Move to helper in utils. + * @see http://stackoverflow.com/a/173479 + */ + public function isAssociative(array $array) + { + return array_keys($array) !== range(0, count($array) - 1); + } +}