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,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;
}
}