Skip to content

Commit

Permalink
Add (rough) version of PhpDocumentor reflection to get method arguments.
Browse files Browse the repository at this point in the history
  • Loading branch information
DannyvdSluijs committed Jan 8, 2021
1 parent f83d3e4 commit 8006920
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 50 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"require": {
"php": ">=7.0",
"netresearch/jsonmapper": "^1.0 || ^2.0",
"phpdocumentor/reflection-docblock": "^4.0.0 || ^5.0.0"
"phpdocumentor/reflection": "^4.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0.0"
Expand Down
16 changes: 8 additions & 8 deletions lib/Dispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace AdvancedJsonRpc;

use AdvancedJsonRpc\Reflection\NativeReflection;
use AdvancedJsonRpc\Reflection;
use JsonMapper;
use JsonMapper_Exception;

Expand All @@ -20,7 +20,7 @@ class Dispatcher
private $delimiter;

/**
* @var Reflection\NativeReflection
* @var Reflection\ReflectionInterface
*/
private $reflection;

Expand All @@ -38,7 +38,7 @@ public function __construct($target, $delimiter = '->')
$this->target = $target;
$this->delimiter = $delimiter;
$this->mapper = new JsonMapper();
$this->reflection = new NativeReflection();
$this->reflection = new Reflection\NativeReflection();
}

/**
Expand Down Expand Up @@ -97,18 +97,18 @@ public function dispatch($msg)
// If the type is structured (array or object), map it with JsonMapper
if (is_object($value)) {
// Does the parameter have a type hint?
$param = $method->getParameters()[$position];
$param = $method->getParameter($position);
if ($param->hasType()) {
$class = $param->getType()->getName();
$value = $this->mapper->map($value, new $class());
}
} 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);
} else if (is_array($value)) {
if (!$method->hasParameter($position)) {
throw new Error('Type information is missing', ErrorCode::INVALID_PARAMS);
}

// Get the array type from the DocBlock
$class = $method->getParamTags()[$position]->getType()->getName();
$class = $method->getParameter($position)->getType()->getName();
$value = $this->mapper->mapArray($value, [], $class);
}
} catch (JsonMapper_Exception $e) {
Expand Down
31 changes: 7 additions & 24 deletions lib/Reflection/Dto/Method.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,26 @@

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)
public function __construct(array $parameters)
{
$this->declaringClass = $declaringClass;
$this->docComment = $docComment;
$this->parameters = $parameters;
$this->paramTags = $paramTags;
}

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

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

public function getParameters(): array
public function hasParameter(int $name): bool
{
return $this->parameters;
return array_key_exists($name, $this->parameters);
}

public function getParamTags(): array
public function getParameter(int $name): Parameter
{
return $this->paramTags;
return $this->parameters[$name];
}
}
2 changes: 1 addition & 1 deletion lib/Reflection/Dto/Parameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ public function getType(): Type

public function hasType(): bool
{
return isset($this->type);
return $this->type !== null;
}
}
45 changes: 29 additions & 16 deletions lib/Reflection/NativeReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use ReflectionMethod;
use ReflectionNamedType;

class NativeReflection
class NativeReflection implements ReflectionInterface
{
/** @var DocBlockFactory */
private $docBlockFactory;
Expand All @@ -44,21 +44,32 @@ public function getMethodDetails($rpcMethod, $target, $nativeMethod): Method
throw new Error($e->getMessage(), ErrorCode::METHOD_NOT_FOUND, null, $e);
}

$paramTags = [];
$parameters = array_map(function($p) { return $this->mapNativeReflectionParameterToParameter($p); }, $nativeMethod->getParameters());

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

/* Improve types from the doc block */
if ($docBlock !== null) {
$docBlockParameters = [];

foreach ($docBlock->getTagsByName('param') as $param) {
$docBlockParameters[$param->getVariableName()] = $this->mapDocBlockTagToParameter($param);
}

foreach ($parameters as $position => $param) {
if (array_key_exists($param->getName(), $docBlockParameters) && $docBlockParameters[$param->getName()]->hasType())
{
$parameters[$position] = $docBlockParameters[$param->getName()];
}
}
}
}

$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)
);
$method = new Method($parameters);

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

Expand All @@ -67,8 +78,8 @@ public function getMethodDetails($rpcMethod, $target, $nativeMethod): Method

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

private function mapDocBlockTagToParameter(Tag $tag): Parameter
Expand All @@ -82,21 +93,23 @@ private function mapDocBlockTagToParameter(Tag $tag): Parameter
&& $t->getValueType() instanceof Types\Object_
&& (string)$t->getValueType() !== 'object'
) {
return new Parameter($tag->getName(), new Type((string)$t->getValueType()->getFqsen()));
return new Parameter($tag->getVariableName(), new Type((string)$t->getValueType()->getFqsen()));
}
}
} else if ($type instanceof Types\Array_) {
return new Parameter($tag->getName(), new Type((string)$type->getValueType()->getFqsen()));
return new Parameter($tag->getVariableName(), new Type((string)$type->getValueType()->getFqsen()));
}
}

private function mapNativeReflectionTypeToType(\ReflectionType $native = null): Type
/** @return Type|null */
private function mapNativeReflectionTypeToType(\ReflectionType $native = null)
{
if ($native instanceof ReflectionNamedType) {
if ($native instanceof ReflectionNamedType && $native->getName() !== '') {
// 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 {
}
if ((string) $native !== '') {
// Fallback for php 7.0, which is still supported (and doesn't have nullable).
return new Type((string) $native);
}
Expand Down
92 changes: 92 additions & 0 deletions lib/Reflection/PhpDocumentorReflection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);

namespace AdvancedJsonRpc\Reflection;


use AdvancedJsonRpc\Reflection\Dto\Method;
use AdvancedJsonRpc\Reflection\Dto\Parameter;
use AdvancedJsonRpc\Reflection\Dto\Type;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\File\LocalFile;
use phpDocumentor\Reflection\Php\Argument;
use phpDocumentor\Reflection\Php\Project;
use phpDocumentor\Reflection\Php\ProjectFactory;
use phpDocumentor\Reflection\Types;
use ReflectionMethod;

class PhpDocumentorReflection implements ReflectionInterface
{

/** @var ProjectFactory */
private $projectFactory;

public function __construct()
{
$this->projectFactory = ProjectFactory::createInstance();
}

public function getMethodDetails($rpcMethod, $target, $nativeMethodName): Method
{
$nativeMethod = new ReflectionMethod($target, $nativeMethodName);
$projectFiles = [new LocalFile($nativeMethod->getFileName())];
/** @var Project $project */
$project = $this->projectFactory->create('php-advanced-json-rpc', $projectFiles);

/** @var \phpDocumentor\Reflection\Php\Class_ $class */
$class = $project->getFiles()[$nativeMethod->getFileName()]->getClasses()['\\' . $nativeMethod->class]; /* @todo add error handling of multiple classes in single file */
$methodName = '\\' . $nativeMethod->class . '::' . $nativeMethod->getName() . '()';
$method = $class->getMethods()[$methodName]; /* @todo add error handling for missing method */

$parameters = array_map(function ($a) { return $this->mapPhpDocumentorReflectionParameterToParameter($a); }, $method->getArguments());

/* Improve types from the doc block */
$docBlock = $method->getDocBlock();
if ($docBlock !== null) {
$docBlockParameters = [];

foreach ($method->getDocBlock()->getTagsByName('param') as $param) {
$docBlockParameters[$param->getVariableName()] = $this->mapDocBlockTagToParameter($param);
}

foreach ($parameters as $position => $param) {
if (array_key_exists($param->getName(), $docBlockParameters) && $docBlockParameters[$param->getName()]->hasType())
{
$parameters[$position] = $docBlockParameters[$param->getName()];
}
}
}

return new Method($parameters);
}

private function mapPhpDocumentorReflectionParameterToParameter(Argument $argument): Parameter
{
$phpDocumentorType = $argument->getType();
if ($phpDocumentorType === null) {
return new Parameter($argument->getName());
}

return new Parameter($argument->getName(), new Type((string) $phpDocumentorType));
}

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->getVariableName(), new Type((string)$t->getValueType()->getFqsen()));
}
}
} else if ($type instanceof Types\Array_) {
return new Parameter($tag->getVariableName(), new Type((string)$type->getValueType()->getFqsen()));
}
}
}
12 changes: 12 additions & 0 deletions lib/Reflection/ReflectionInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace AdvancedJsonRpc\Reflection;

use AdvancedJsonRpc\Reflection\Dto\Method;

interface ReflectionInterface
{
public function getMethodDetails($rpcMethod, $target, $nativeMethod): Method;
}

0 comments on commit 8006920

Please sign in to comment.