Install phan/phpstan local

This commit is contained in:
Clemens Schwaighofer
2023-02-08 12:02:18 +09:00
parent 53eef03387
commit f94b350ba4
1166 changed files with 298568 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
name: build
on: [push, pull_request]
env:
FORCE_COLOR: 1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
test:
strategy:
matrix:
php:
- 7.1
- 7.2
- 7.3
- 7.4
- 8.0
deps:
- lowest
- highest
include:
- php: 8.1
deps: highest
composer-options: --ignore-platform-reqs
exclude:
# that config currently breaks as older PHPUnit cannot generate coverage on PHP 8
- php: 8
deps: lowest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
- uses: ramsey/composer-install@v1
with:
dependency-versions: ${{ matrix.deps }}
composer-options: ${{ matrix.composer-options }}
- run: vendor/bin/phpunit --coverage-clover=coverage.xml --whitelist lib --bootstrap vendor/autoload.php tests
- uses: codecov/codecov-action@v1
release:
needs: test
if: github.repository_owner == 'felixfbecker' && github.event_name == 'push' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
- name: Install npm dependencies
run: npm ci
- name: Release
run: npm run semantic-release

View File

@@ -0,0 +1,15 @@
ISC 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.

View File

@@ -0,0 +1,32 @@
{
"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 || ^3.0 || ^4.0",
"phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0"
},
"require-dev": {
"phpunit/phpunit": "^7.0 || ^8.0"
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@@ -0,0 +1,171 @@
<?php
declare(strict_types = 1);
namespace AdvancedJsonRpc;
use JsonMapper;
use JsonMapper_Exception;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\Types;
use ReflectionException;
use ReflectionMethod;
use ReflectionNamedType;
class Dispatcher
{
/**
* @var object
*/
private $target;
/**
* @var string
*/
private $delimiter;
/**
* method => 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;
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types = 1);
namespace AdvancedJsonRpc;
use Exception;
use Throwable;
class Error extends Exception
{
/**
* A Number that indicates the error type that occurred. This MUST be an integer.
*
* @var int
*/
public $code;
/**
* A String providing a short description of the error. The message SHOULD be limited to a concise single sentence.
*
* @var string
*/
public $message;
/**
* A Primitive or Structured value that contains additional information about the error. This may be omitted. The
* value of this member is defined by the Server (e.g. detailed error information, nested errors etc.).
*
* @var mixed
*/
public $data;
public function __construct(string $message, int $code, $data = null, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->data = $data;
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types = 1);
namespace AdvancedJsonRpc;
/**
* The error codes from and including -32768 to -32000 are reserved for pre-defined errors. Any code within this range,
* but not defined explicitly below is reserved for future use. The error codes are nearly the same as those suggested
* for XML-RPC at the following url: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
* The remainder of the space is available for application defined errors.
*/
abstract class ErrorCode
{
/**
* Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.
*/
const PARSE_ERROR = -32700;
/**
* The JSON sent is not a valid Request object.
*/
const INVALID_REQUEST = -32600;
/**
* The method does not exist / is not available.
*/
const METHOD_NOT_FOUND = -32601;
/**
* Invalid method parameter(s).
*/
const INVALID_PARAMS = -32602;
/**
* Internal JSON-RPC error.
*/
const INTERNAL_ERROR = -32603;
/**
* Reserved for implementation-defined server-errors.
*/
const SERVER_ERROR_START = -32099;
/**
* Reserved for implementation-defined server-errors.
*/
const SERVER_ERROR_END = -32000;
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types = 1);
namespace AdvancedJsonRpc;
/**
* When a rpc call is made, the Server MUST reply with a Response, except for in the case of Notifications. The Response
* is expressed as a single JSON Object, with the following members:
*/
class ErrorResponse extends Response
{
/**
* This member is REQUIRED on error. This member MUST NOT exist if there was no error triggered during invocation.
* The value for this member MUST be an Object as defined in section 5.1.
*
* @var \AdvancedJsonRpc\Error
*/
public $error;
/**
* A message is considered a Response if it has an ID and either a result or an error
*
* @param object $msg A decoded message body
* @return bool
*/
public static function isErrorResponse($msg): bool
{
return is_object($msg) && isset($msg->id) && isset($msg->error);
}
/**
* @param int|string $id
* @param \AdvancedJsonRpc\Error $error
*/
public function __construct($id, Error $error)
{
parent::__construct($id);
$this->error = $error;
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types = 1);
namespace AdvancedJsonRpc;
/**
* Base message
*/
abstract class Message
{
/**
* A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0".
*
* @var string
*/
public $jsonrpc = '2.0';
/**
* Returns the appropriate Message subclass
*
* @param string $msg
* @return Message
*/
public static function parse(string $msg): Message
{
$decoded = json_decode($msg);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Error(json_last_error_msg(), ErrorCode::PARSE_ERROR);
}
if (Notification::isNotification($decoded)) {
$obj = new Notification($decoded->method, $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;
}
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types = 1);
namespace AdvancedJsonRpc;
/**
* A Notification is a Request object without an "id" member. A Request object that is a Notification signifies the
* Client's lack of interest in the corresponding Response object, and as such no Response object needs to be returned
* to the client. The Server MUST NOT reply to a Notification, including those that are within a batch request.
* Notifications are not confirmable by definition, since they do not have a Response object to be returned. As such,
* the Client would not be aware of any errors (like e.g. "Invalid params","Internal error").
*/
class Notification extends Message
{
/**
* A String containing the name of the method to be invoked. Method names that begin with the word rpc followed by a
* period character (U+002E or ASCII 46) are reserved for rpc-internal methods and extensions and MUST NOT be used
* for anything else.
*
* @var string
*/
public $method;
/**
* A Structured value that holds the parameter values to be used during the invocation of the method. This member
* MAY be omitted. If present, parameters for the rpc call MUST be provided as a Structured value. Either
* by-position through an Array or by-name through an Object. by-position: params MUST be an Array, containing the
* values in the Server expected order. by-name: params MUST be an Object, with member names that match the Server
* expected parameter names. The absence of expected names MAY result in an error being generated. The names MUST
* match exactly, including case, to the method's expected parameters.
*
* @var object|array|null
*/
public $params;
/**
* A message is considered a Notification if it has a method but no ID.
*
* @param object $msg A decoded message body
* @return bool
*/
public static function isNotification($msg): bool
{
return is_object($msg) && !property_exists($msg, 'id') && isset($msg->method);
}
/**
* @param string $method
* @param mixed $params
*/
public function __construct(string $method, $params = null)
{
$this->method = $method;
$this->params = $params;
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types = 1);
namespace AdvancedJsonRpc;
/**
* A rpc call is represented by sending a Request object to a Server
*/
class Request extends Message
{
/**
* An identifier established by the Client that MUST contain a String, Number, or NULL value if included. If it is
* not included it is assumed to be a notification. The value SHOULD normally not be NULL and Numbers SHOULD NOT
* contain fractional parts.
*
* @var int|string
*/
public $id;
/**
* A String containing the name of the method to be invoked. Method names that begin with the word rpc followed by a
* period character (U+002E or ASCII 46) are reserved for rpc-internal methods and extensions and MUST NOT be used
* for anything else.
*
* @var string
*/
public $method;
/**
* A Structured value that holds the parameter values to be used during the invocation of the method. This member
* MAY be omitted. If present, parameters for the rpc call MUST be provided as a Structured value. Either
* by-position through an Array or by-name through an Object. by-position: params MUST be an Array, containing the
* values in the Server expected order. by-name: params MUST be an Object, with member names that match the Server
* expected parameter names. The absence of expected names MAY result in an error being generated. The names MUST
* match exactly, including case, to the method's expected parameters.
*
* @var object|array|null
*/
public $params;
/**
* A message is considered a Request if it has an ID and a method.
*
* @param object $msg A decoded message body
* @return bool
*/
public static function isRequest($msg): bool
{
return is_object($msg) && property_exists($msg, 'id') && isset($msg->method);
}
/**
* @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;
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types = 1);
namespace AdvancedJsonRpc;
/**
* When a rpc call is made, the Server MUST reply with a Response, except for in the case of Notifications. The Response
* is expressed as a single JSON Object, with the following members:
*/
abstract class Response extends Message
{
/**
* This member is REQUIRED. It MUST be the same as the value of the id member in the Request Object. If there was an
* error in detecting the id in the Request object (e.g. Parse error/Invalid Request), it MUST be Null.
*
* @var int|string
*/
public $id;
/**
* A message is considered a Response if it has an ID and either a result or an error
*
* @param object $msg A decoded message body
* @return bool
*/
public static function isResponse($msg): bool
{
return is_object($msg) && property_exists($msg, 'id') && (property_exists($msg, 'result') || isset($msg->error));
}
/**
* @param int|string $id
* @param mixed $result
* @param ResponseError $error
*/
public function __construct($id)
{
$this->id = $id;
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types = 1);
namespace AdvancedJsonRpc;
/**
* When a rpc call is made, the Server MUST reply with a Response, except for in the case of Notifications. The Response
* is expressed as a single JSON Object, with the following members:
*/
class SuccessResponse extends Response
{
/**
* This member is REQUIRED on success. This member MUST NOT exist if there was an error invoking the method. The
* value of this member is determined by the method invoked on the Server.
*
* @var mixed
*/
public $result;
/**
* A message is considered a SuccessResponse if it has an ID and either a result
*
* @param object $msg A decoded message body
* @return bool
*/
public static function isSuccessResponse($msg): bool
{
return is_object($msg) && property_exists($msg, 'id') && property_exists($msg, 'result');
}
/**
* @param int|string $id
* @param mixed $result
*/
public function __construct($id, $result)
{
parent::__construct($id);
$this->result = $result;
}
}