.travis.yml000064400000001614144760112400006656 0ustar00# TravisCI configuration for felixfbecker/php-advanced-json-rpc branches: except: - /^v\d+\.\d+\.\d+$/ language: php os: - "linux" dist: "xenial" php: - "7.1" - "7.2" - "7.3" - "7.4" - "8.0" - nightly stages: - name: test - name: release if: branch = master AND type = push AND fork = false jobs: fast_finish: true include: - stage: release language: node_js node_js: "8" install: - npm ci script: - npm run semantic-release after_success: skip allow_failures: - stage: test php: nightly env: global: - FORCE_COLOR=1 cache: npm: true directories: - $HOME/.composer/cache install: - composer install --prefer-dist script: - vendor/bin/phpunit --coverage-clover=coverage.xml --whitelist lib --bootstrap vendor/autoload.php tests after_success: - bash <(curl -s https://codecov.io/bash) LICENSE000064400000001362144760112400005552 0ustar00ISC License Copyright (c) 2016, Felix Frederick Becker Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. composer.json000064400000001411144760112400007262 0ustar00{ "name": "felixfbecker/advanced-json-rpc", "description": "A more advanced JSONRPC implementation", "type": "library", "license": "ISC", "authors": [ { "name": "Felix Becker", "email": "felix.b@outlook.com" } ], "autoload": { "psr-4": { "AdvancedJsonRpc\\": "lib/" } }, "autoload-dev": { "psr-4": { "AdvancedJsonRpc\\Tests\\": "tests/" } }, "require": { "php": "^7.1 || ^8.0", "netresearch/jsonmapper": "^1.0 || ^2.0", "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0" }, "require-dev": { "phpunit/phpunit": "^7.0 || ^8.0" }, "minimum-stability": "dev", "prefer-stable": true } lib/Dispatcher.php000064400000015260144760112400010114 0ustar00 ReflectionMethod[] * * @var ReflectionMethod */ private $methods; /** * @var \phpDocumentor\Reflection\DocBlockFactory */ private $docBlockFactory; /** * @var \phpDocumentor\Reflection\Types\ContextFactory */ private $contextFactory; /** * @param object $target The target object that should receive the method calls * @param string $delimiter A delimiter for method calls on properties, for example someProperty->someMethod */ public function __construct($target, $delimiter = '->') { $this->target = $target; $this->delimiter = $delimiter; $this->docBlockFactory = DocBlockFactory::createInstance(); $this->contextFactory = new Types\ContextFactory(); $this->mapper = new JsonMapper(); } /** * Calls the appropriate method handler for an incoming Message * * @param string|object $msg The incoming message * @return mixed */ public function dispatch($msg) { if (is_string($msg)) { $msg = json_decode($msg); if (json_last_error() !== JSON_ERROR_NONE) { throw new Error(json_last_error_msg(), ErrorCode::PARSE_ERROR); } } // Find out the object and function that should be called $obj = $this->target; $parts = explode($this->delimiter, $msg->method); // The function to call is always the last part of the method $fn = array_pop($parts); // For namespaced methods like textDocument/didOpen, call the didOpen method on the $textDocument property // For simple methods like initialize, shutdown, exit, this loop will simply not be entered and $obj will be // the target foreach ($parts as $part) { if (!isset($obj->$part)) { throw new Error("Method {$msg->method} is not implemented", ErrorCode::METHOD_NOT_FOUND); } $obj = $obj->$part; } if (!isset($this->methods[$msg->method])) { try { $method = new ReflectionMethod($obj, $fn); $this->methods[$msg->method] = $method; } catch (ReflectionException $e) { throw new Error($e->getMessage(), ErrorCode::METHOD_NOT_FOUND, null, $e); } } $method = $this->methods[$msg->method]; $parameters = $method->getParameters(); if ($method->getDocComment()) { $docBlock = $this->docBlockFactory->create( $method->getDocComment(), $this->contextFactory->createFromReflector($method->getDeclaringClass()) ); $paramTags = $docBlock->getTagsByName('param'); } $args = []; if (isset($msg->params)) { // Find out the position if (is_array($msg->params)) { $args = $msg->params; } else if (is_object($msg->params)) { foreach ($parameters as $pos => $parameter) { $value = null; foreach(get_object_vars($msg->params) as $key => $val) { if ($parameter->name === $key) { $value = $val; break; } } $args[$pos] = $value; } } else { throw new Error('Params must be structured or omitted', ErrorCode::INVALID_REQUEST); } foreach ($args as $position => $value) { try { // If the type is structured (array or object), map it with JsonMapper if (is_object($value)) { // Does the parameter have a type hint? $param = $parameters[$position]; if ($param->hasType()) { $paramType = $param->getType(); if ($paramType instanceof ReflectionNamedType) { // We have object data to map and want the class name. // This should not include the `?` if the type was nullable. $class = $paramType->getName(); } else { // Fallback for php 7.0, which is still supported (and doesn't have nullable). $class = (string)$paramType; } $value = $this->mapper->map($value, new $class()); } } else if (is_array($value) && isset($docBlock)) { // Get the array type from the DocBlock $type = $paramTags[$position]->getType(); // For union types, use the first one that is a class array (often it is SomeClass[]|null) if ($type instanceof Types\Compound) { for ($i = 0; $t = $type->get($i); $i++) { if ( $t instanceof Types\Array_ && $t->getValueType() instanceof Types\Object_ && (string)$t->getValueType() !== 'object' ) { $class = (string)$t->getValueType()->getFqsen(); $value = $this->mapper->mapArray($value, [], $class); break; } } } else if ($type instanceof Types\Array_) { $class = (string)$type->getValueType()->getFqsen(); $value = $this->mapper->mapArray($value, [], $class); } else { throw new Error('Type is not matching @param tag', ErrorCode::INVALID_PARAMS); } } } catch (JsonMapper_Exception $e) { throw new Error($e->getMessage(), ErrorCode::INVALID_PARAMS, null, $e); } $args[$position] = $value; } } ksort($args); $result = $obj->$fn(...$args); return $result; } } lib/Error.php000064400000001655144760112400007122 0ustar00data = $data; } } lib/ErrorCode.php000064400000002375144760112400007715 0ustar00id) && isset($msg->error); } /** * @param int|string $id * @param \AdvancedJsonRpc\Error $error */ public function __construct($id, Error $error) { parent::__construct($id); $this->error = $error; } } lib/Message.php000064400000003127144760112400007411 0ustar00method, $decoded->params ?? null); } else if (Request::isRequest($decoded)) { $obj = new Request($decoded->id, $decoded->method, $decoded->params ?? null); } else if (SuccessResponse::isSuccessResponse($decoded)) { $obj = new SuccessResponse($decoded->id, $decoded->result); } else if (ErrorResponse::isErrorResponse($decoded)) { $obj = new ErrorResponse($decoded->id, new Error($decoded->error->message, $decoded->error->code, $decoded->error->data ?? null)); } else { throw new Error('Invalid message', ErrorCode::INVALID_REQUEST); } return $obj; } public function __toString(): string { $encoded = json_encode($this); if ($encoded === false) { throw new Error(json_last_error_msg(), ErrorCode::INTERNAL_ERROR); } return $encoded; } } lib/Notification.php000064400000004333144760112400010453 0ustar00method); } /** * @param string $method * @param mixed $params */ public function __construct(string $method, $params = null) { $this->method = $method; $this->params = $params; } } lib/Request.php000064400000004157144760112400007461 0ustar00method); } /** * @param string|int $id * @param string $method * @param object|array $params */ public function __construct($id, string $method, $params = null) { $this->id = $id; $this->method = $method; $this->params = $params; } } lib/Response.php000064400000002171144760112400007621 0ustar00error)); } /** * @param int|string $id * @param mixed $result * @param ResponseError $error */ public function __construct($id) { $this->id = $id; } } lib/SuccessResponse.php000064400000002120144760112400011144 0ustar00result = $result; } }