Skip to content

Commit

Permalink
Move current implementation into seperate class
Browse files Browse the repository at this point in the history
  • Loading branch information
DannyvdSluijs committed Jan 8, 2021
1 parent 7eae818 commit f83d3e4
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 67 deletions.
87 changes: 20 additions & 67 deletions lib/Dispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@

namespace AdvancedJsonRpc;

use AdvancedJsonRpc\Reflection\NativeReflection;
use JsonMapper;
use JsonMapper_Exception;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\Types;
use ReflectionException;
use ReflectionMethod;
use ReflectionNamedType;

class Dispatcher
{
Expand All @@ -24,21 +20,14 @@ class Dispatcher
private $delimiter;

/**
* method => ReflectionMethod[]
*
* @var ReflectionMethod
*/
private $methods;

/**
* @var \phpDocumentor\Reflection\DocBlockFactory
* @var Reflection\NativeReflection
*/
private $docBlockFactory;
private $reflection;

/**
* @var \phpDocumentor\Reflection\Types\ContextFactory
* @var JsonMapper
*/
private $contextFactory;
private $mapper;

/**
* @param object $target The target object that should receive the method calls
Expand All @@ -48,9 +37,8 @@ public function __construct($target, $delimiter = '->')
{
$this->target = $target;
$this->delimiter = $delimiter;
$this->docBlockFactory = DocBlockFactory::createInstance();
$this->contextFactory = new Types\ContextFactory();
$this->mapper = new JsonMapper();
$this->reflection = new NativeReflection();
}

/**
Expand Down Expand Up @@ -81,33 +69,19 @@ public function dispatch($msg)
}
$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');
}

$method = $this->reflection->getMethodDetails($msg->method, $obj, $fn);

$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) {
foreach ($method->getParameters() as $pos => $parameter) {
$value = null;
foreach(get_object_vars($msg->params) as $key => $val) {
if ($parameter->name === $key) {
if ($parameter->getName() === $key) {
$value = $val;
break;
}
Expand All @@ -117,46 +91,25 @@ public function dispatch($msg)
} 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];
$param = $method->getParameters()[$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;
}
$class = $param->getType()->getName();
$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 {
} else if (is_array($value) && $method->getDocComment() !== '') {
if (!array_key_exists($position, $method->getParamTags())) {
throw new Error('Type is not matching @param tag', ErrorCode::INVALID_PARAMS);
}

// Get the array type from the DocBlock
$class = $method->getParamTags()[$position]->getType()->getName();
$value = $this->mapper->mapArray($value, [], $class);
}
} catch (JsonMapper_Exception $e) {
throw new Error($e->getMessage(), ErrorCode::INVALID_PARAMS, null, $e);
Expand Down
48 changes: 48 additions & 0 deletions lib/Reflection/Dto/Method.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace AdvancedJsonRpc\Reflection\Dto;

class Method
{
/** @var string */
private $declaringClass;
/** @var string|null */
private $docComment;
/** @var Parameter[] */
private $parameters;
/** @var Parameter[] */
private $paramTags;

/**
* @param string|null $docComment
*/
public function __construct(string $declaringClass, $docComment, array $parameters, array $paramTags)
{
$this->declaringClass = $declaringClass;
$this->docComment = $docComment;
$this->parameters = $parameters;
$this->paramTags = $paramTags;
}

public function getDeclaringClass(): string
{
return $this->declaringClass;
}

public function getDocComment(): string
{
return $this->docComment;
}

public function getParameters(): array
{
return $this->parameters;
}

public function getParamTags(): array
{
return $this->paramTags;
}
}
35 changes: 35 additions & 0 deletions lib/Reflection/Dto/Parameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace AdvancedJsonRpc\Reflection\Dto;


class Parameter
{
/** @var string */
private $name;
/** @var Type|null */
private $type;

public function __construct(string $name, Type $type = null)
{
$this->name = $name;
$this->type = $type;
}

public function getName(): string
{
return $this->name;
}

public function getType(): Type
{
return $this->type;
}

public function hasType(): bool
{
return isset($this->type);
}
}
21 changes: 21 additions & 0 deletions lib/Reflection/Dto/Type.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace AdvancedJsonRpc\Reflection\Dto;

class Type
{
/** @var string */
private $name;

public function __construct(string $name)
{
$this->name = $name;
}

public function getName(): string
{
return $this->name;
}
}
104 changes: 104 additions & 0 deletions lib/Reflection/NativeReflection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

declare(strict_types=1);

namespace AdvancedJsonRpc\Reflection;


use AdvancedJsonRpc\Error;
use AdvancedJsonRpc\ErrorCode;
use AdvancedJsonRpc\Reflection\Dto\Method;
use AdvancedJsonRpc\Reflection\Dto\Parameter;
use AdvancedJsonRpc\Reflection\Dto\Type;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\Types;
use ReflectionException;
use ReflectionMethod;
use ReflectionNamedType;

class NativeReflection
{
/** @var DocBlockFactory */
private $docBlockFactory;
/** @var \phpDocumentor\Reflection\Types\ContextFactory */
private $contextFactory;
/** @var Method[] */
private $methods = [];

public function __construct()
{
$this->docBlockFactory = DocBlockFactory::createInstance();
$this->contextFactory = new Types\ContextFactory();
}

public function getMethodDetails($rpcMethod, $target, $nativeMethod): Method
{
if (array_key_exists($rpcMethod, $this->methods)) {
return $this->methods[$rpcMethod];
}

try {
$nativeMethod = new ReflectionMethod($target, $nativeMethod);
} catch (ReflectionException $e) {
throw new Error($e->getMessage(), ErrorCode::METHOD_NOT_FOUND, null, $e);
}

$paramTags = [];
if ($nativeMethod->getDocComment()) {
$docBlock = $this->docBlockFactory->create(
$nativeMethod->getDocComment(),
$this->contextFactory->createFromReflector($nativeMethod->getDeclaringClass())
);
$paramTags = $docBlock->getTagsByName('param');
}

$method = new Method(
$nativeMethod->getDeclaringClass()->getName(),
$nativeMethod->getDocComment() ?: null,
array_map(function($p) { return $this->mapNativeReflectionParameterToParameter($p); }, $nativeMethod->getParameters()),
array_map(function($p) { return $this->mapDocBlockTagToParameter($p); }, $paramTags)
);

$this->methods[$rpcMethod] = $method;

return $method;
}

private function mapNativeReflectionParameterToParameter(\ReflectionParameter $native): Parameter
{
$types = $this->mapNativeReflectionTypeToType($native->getType());
return new Parameter($native->getName(), $types);
}

private function mapDocBlockTagToParameter(Tag $tag): Parameter
{
$type = $tag->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'
) {
return new Parameter($tag->getName(), new Type((string)$t->getValueType()->getFqsen()));
}
}
} else if ($type instanceof Types\Array_) {
return new Parameter($tag->getName(), new Type((string)$type->getValueType()->getFqsen()));
}
}

private function mapNativeReflectionTypeToType(\ReflectionType $native = null): Type
{
if ($native instanceof ReflectionNamedType) {
// We have object data to map and want the class name.
// This should not include the `?` if the type was nullable.
return new Type($native->getName());
} else {
// Fallback for php 7.0, which is still supported (and doesn't have nullable).
return new Type((string) $native);
}
}
}

0 comments on commit f83d3e4

Please sign in to comment.