service = $service; $this->nextHandler = $nextHandler; } public function __invoke(CommandInterface $cmd) { $this->command = $cmd; $nextHandler = $this->nextHandler; $op = $this->service->getOperation($cmd->getName()); $inputMembers = $op->getInput()->getMembers(); $input = $cmd->toArray(); foreach ($input as $param => $value) { if (isset($inputMembers[$param])) { $shape = $inputMembers[$param]; $this->processInput($value, $shape, [$param]); } } return $nextHandler($this->command); } /** * Recurses a given input shape. if a given scalar input does not match its * modeled type, it is cast to its modeled type. * * @param $input * @param $shape * @param array $path * * @return void */ private function processInput($input, $shape, array $path) : void { switch ($shape->getType()) { case 'structure': $this->processStructure($input, $shape, $path); break; case 'list': $this->processList($input, $shape, $path); break; case 'map': $this->processMap($input, $shape, $path); break; default: $this->processScalar($input, $shape, $path); } } /** * @param array $input * @param StructureShape $shape * @param array $path * * @return void */ private function processStructure(array $input, StructureShape $shape, array $path) : void { foreach ($input as $param => $value) { if ($shape->hasMember($param)) { $memberPath = \array_merge($path, [$param]); $this->processInput($value, $shape->getMember($param), $memberPath); } } } /** * @param array $input * @param ListShape $shape * @param array $path * * @return void */ private function processList(array $input, ListShape $shape, array $path) : void { foreach ($input as $param => $value) { $memberPath = \array_merge($path, [$param]); $this->processInput($value, $shape->getMember(), $memberPath); } } /** * @param array $input * @param MapShape $shape * @param array $path * * @return void */ private function processMap(array $input, MapShape $shape, array $path) : void { foreach ($input as $param => $value) { $memberPath = \array_merge($path, [$param]); $this->processInput($value, $shape->getValue(), $memberPath); } } /** * @param $input * @param Shape $shape * @param array $path * * @return void */ private function processScalar($input, Shape $shape, array $path) : void { $expectedType = $shape->getType(); if (!$this->isModeledType($input, $expectedType)) { \trigger_error("The provided type for `" . \implode(' -> ', $path) . "` value was `" . (\gettype($input) === 'double' ? 'float' : \gettype($input)) . "`." . " The modeled type is `{$expectedType}`.", \E_USER_WARNING); $value = $this->castValue($input, $expectedType); $this->changeValueAtPath($path, $value); } } /** * Modifies command in place * * @param array $path * @param $newValue * * @return void */ private function changeValueAtPath(array $path, $newValue) : void { $commandRef =& $this->command; foreach ($path as $segment) { if (!isset($commandRef[$segment])) { return; } $commandRef =& $commandRef[$segment]; } $commandRef = $newValue; } /** * @param $value * @param $type * * @return bool */ private function isModeledType($value, $type) : bool { switch ($type) { case 'string': return \is_string($value); case 'integer': case 'long': return \is_int($value); case 'float': return \is_float($value); default: return \true; } } /** * @param $value * @param $type * * @return float|int|mixed|string */ private function castValue($value, $type) { switch ($type) { case 'integer': return (int) $value; case 'long': return $value + 0; case 'float': return (float) $value; case 'string': return (string) $value; default: return $value; } } }