Skip to content

Commit e269399

Browse files
committed
Add (rough) version of PhpDocumentor reflection to get method arguments.
1 parent 08acdd3 commit e269399

File tree

7 files changed

+151
-50
lines changed

7 files changed

+151
-50
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"require": {
2323
"php": "^7.1 || ^8.0",
2424
"netresearch/jsonmapper": "^1.0 || ^2.0",
25-
"phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0.0"
25+
"phpdocumentor/reflection": "^4.0"
26+
2627
},
2728
"require-dev": {
2829
"phpunit/phpunit": "^7.0 || ^8.0"

lib/Dispatcher.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace AdvancedJsonRpc;
55

6-
use AdvancedJsonRpc\Reflection\NativeReflection;
6+
use AdvancedJsonRpc\Reflection;
77
use JsonMapper;
88
use JsonMapper_Exception;
99

@@ -20,7 +20,7 @@ class Dispatcher
2020
private $delimiter;
2121

2222
/**
23-
* @var Reflection\NativeReflection
23+
* @var Reflection\ReflectionInterface
2424
*/
2525
private $reflection;
2626

@@ -38,7 +38,7 @@ public function __construct($target, $delimiter = '->')
3838
$this->target = $target;
3939
$this->delimiter = $delimiter;
4040
$this->mapper = new JsonMapper();
41-
$this->reflection = new NativeReflection();
41+
$this->reflection = new Reflection\NativeReflection();
4242
}
4343

4444
/**
@@ -97,18 +97,18 @@ public function dispatch($msg)
9797
// If the type is structured (array or object), map it with JsonMapper
9898
if (is_object($value)) {
9999
// Does the parameter have a type hint?
100-
$param = $method->getParameters()[$position];
100+
$param = $method->getParameter($position);
101101
if ($param->hasType()) {
102102
$class = $param->getType()->getName();
103103
$value = $this->mapper->map($value, new $class());
104104
}
105-
} else if (is_array($value) && $method->getDocComment() !== '') {
106-
if (!array_key_exists($position, $method->getParamTags())) {
107-
throw new Error('Type is not matching @param tag', ErrorCode::INVALID_PARAMS);
105+
} else if (is_array($value)) {
106+
if (!$method->hasParameter($position)) {
107+
throw new Error('Type information is missing', ErrorCode::INVALID_PARAMS);
108108
}
109109

110110
// Get the array type from the DocBlock
111-
$class = $method->getParamTags()[$position]->getType()->getName();
111+
$class = $method->getParameter($position)->getType()->getName();
112112
$value = $this->mapper->mapArray($value, [], $class);
113113
}
114114
} catch (JsonMapper_Exception $e) {

lib/Reflection/Dto/Method.php

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,43 +6,26 @@
66

77
class Method
88
{
9-
/** @var string */
10-
private $declaringClass;
11-
/** @var string|null */
12-
private $docComment;
139
/** @var Parameter[] */
1410
private $parameters;
15-
/** @var Parameter[] */
16-
private $paramTags;
1711

18-
/**
19-
* @param string|null $docComment
20-
*/
21-
public function __construct(string $declaringClass, $docComment, array $parameters, array $paramTags)
12+
public function __construct(array $parameters)
2213
{
23-
$this->declaringClass = $declaringClass;
24-
$this->docComment = $docComment;
2514
$this->parameters = $parameters;
26-
$this->paramTags = $paramTags;
27-
}
28-
29-
public function getDeclaringClass(): string
30-
{
31-
return $this->declaringClass;
3215
}
3316

34-
public function getDocComment(): string
17+
public function getParameters(): array
3518
{
36-
return $this->docComment;
19+
return $this->parameters;
3720
}
3821

39-
public function getParameters(): array
22+
public function hasParameter(int $name): bool
4023
{
41-
return $this->parameters;
24+
return array_key_exists($name, $this->parameters);
4225
}
4326

44-
public function getParamTags(): array
27+
public function getParameter(int $name): Parameter
4528
{
46-
return $this->paramTags;
29+
return $this->parameters[$name];
4730
}
4831
}

lib/Reflection/Dto/Parameter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ public function getType(): Type
3030

3131
public function hasType(): bool
3232
{
33-
return isset($this->type);
33+
return $this->type !== null;
3434
}
3535
}

lib/Reflection/NativeReflection.php

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use ReflectionMethod;
1818
use ReflectionNamedType;
1919

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

47-
$paramTags = [];
47+
$parameters = array_map(function($p) { return $this->mapNativeReflectionParameterToParameter($p); }, $nativeMethod->getParameters());
48+
4849
if ($nativeMethod->getDocComment()) {
4950
$docBlock = $this->docBlockFactory->create(
5051
$nativeMethod->getDocComment(),
5152
$this->contextFactory->createFromReflector($nativeMethod->getDeclaringClass())
5253
);
53-
$paramTags = $docBlock->getTagsByName('param');
54+
55+
/* Improve types from the doc block */
56+
if ($docBlock !== null) {
57+
$docBlockParameters = [];
58+
59+
foreach ($docBlock->getTagsByName('param') as $param) {
60+
$docBlockParameters[$param->getVariableName()] = $this->mapDocBlockTagToParameter($param);
61+
}
62+
63+
foreach ($parameters as $position => $param) {
64+
if (array_key_exists($param->getName(), $docBlockParameters) && $docBlockParameters[$param->getName()]->hasType())
65+
{
66+
$parameters[$position] = $docBlockParameters[$param->getName()];
67+
}
68+
}
69+
}
5470
}
5571

56-
$method = new Method(
57-
$nativeMethod->getDeclaringClass()->getName(),
58-
$nativeMethod->getDocComment() ?: null,
59-
array_map(function($p) { return $this->mapNativeReflectionParameterToParameter($p); }, $nativeMethod->getParameters()),
60-
array_map(function($p) { return $this->mapDocBlockTagToParameter($p); }, $paramTags)
61-
);
72+
$method = new Method($parameters);
6273

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

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

6879
private function mapNativeReflectionParameterToParameter(\ReflectionParameter $native): Parameter
6980
{
70-
$types = $this->mapNativeReflectionTypeToType($native->getType());
71-
return new Parameter($native->getName(), $types);
81+
$type = $this->mapNativeReflectionTypeToType($native->getType());
82+
return new Parameter($native->getName(), $type);
7283
}
7384

7485
private function mapDocBlockTagToParameter(Tag $tag): Parameter
@@ -82,21 +93,23 @@ private function mapDocBlockTagToParameter(Tag $tag): Parameter
8293
&& $t->getValueType() instanceof Types\Object_
8394
&& (string)$t->getValueType() !== 'object'
8495
) {
85-
return new Parameter($tag->getName(), new Type((string)$t->getValueType()->getFqsen()));
96+
return new Parameter($tag->getVariableName(), new Type((string)$t->getValueType()->getFqsen()));
8697
}
8798
}
8899
} else if ($type instanceof Types\Array_) {
89-
return new Parameter($tag->getName(), new Type((string)$type->getValueType()->getFqsen()));
100+
return new Parameter($tag->getVariableName(), new Type((string)$type->getValueType()->getFqsen()));
90101
}
91102
}
92103

93-
private function mapNativeReflectionTypeToType(\ReflectionType $native = null): Type
104+
/** @return Type|null */
105+
private function mapNativeReflectionTypeToType(\ReflectionType $native = null)
94106
{
95-
if ($native instanceof ReflectionNamedType) {
107+
if ($native instanceof ReflectionNamedType && $native->getName() !== '') {
96108
// We have object data to map and want the class name.
97109
// This should not include the `?` if the type was nullable.
98110
return new Type($native->getName());
99-
} else {
111+
}
112+
if ((string) $native !== '') {
100113
// Fallback for php 7.0, which is still supported (and doesn't have nullable).
101114
return new Type((string) $native);
102115
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AdvancedJsonRpc\Reflection;
6+
7+
8+
use AdvancedJsonRpc\Reflection\Dto\Method;
9+
use AdvancedJsonRpc\Reflection\Dto\Parameter;
10+
use AdvancedJsonRpc\Reflection\Dto\Type;
11+
use phpDocumentor\Reflection\DocBlock\Tag;
12+
use phpDocumentor\Reflection\File\LocalFile;
13+
use phpDocumentor\Reflection\Php\Argument;
14+
use phpDocumentor\Reflection\Php\Project;
15+
use phpDocumentor\Reflection\Php\ProjectFactory;
16+
use phpDocumentor\Reflection\Types;
17+
use ReflectionMethod;
18+
19+
class PhpDocumentorReflection implements ReflectionInterface
20+
{
21+
22+
/** @var ProjectFactory */
23+
private $projectFactory;
24+
25+
public function __construct()
26+
{
27+
$this->projectFactory = ProjectFactory::createInstance();
28+
}
29+
30+
public function getMethodDetails($rpcMethod, $target, $nativeMethodName): Method
31+
{
32+
$nativeMethod = new ReflectionMethod($target, $nativeMethodName);
33+
$projectFiles = [new LocalFile($nativeMethod->getFileName())];
34+
/** @var Project $project */
35+
$project = $this->projectFactory->create('php-advanced-json-rpc', $projectFiles);
36+
37+
/** @var \phpDocumentor\Reflection\Php\Class_ $class */
38+
$class = $project->getFiles()[$nativeMethod->getFileName()]->getClasses()['\\' . $nativeMethod->class]; /* @todo add error handling of multiple classes in single file */
39+
$methodName = '\\' . $nativeMethod->class . '::' . $nativeMethod->getName() . '()';
40+
$method = $class->getMethods()[$methodName]; /* @todo add error handling for missing method */
41+
42+
$parameters = array_map(function ($a) { return $this->mapPhpDocumentorReflectionParameterToParameter($a); }, $method->getArguments());
43+
44+
/* Improve types from the doc block */
45+
$docBlock = $method->getDocBlock();
46+
if ($docBlock !== null) {
47+
$docBlockParameters = [];
48+
49+
foreach ($method->getDocBlock()->getTagsByName('param') as $param) {
50+
$docBlockParameters[$param->getVariableName()] = $this->mapDocBlockTagToParameter($param);
51+
}
52+
53+
foreach ($parameters as $position => $param) {
54+
if (array_key_exists($param->getName(), $docBlockParameters) && $docBlockParameters[$param->getName()]->hasType())
55+
{
56+
$parameters[$position] = $docBlockParameters[$param->getName()];
57+
}
58+
}
59+
}
60+
61+
return new Method($parameters);
62+
}
63+
64+
private function mapPhpDocumentorReflectionParameterToParameter(Argument $argument): Parameter
65+
{
66+
$phpDocumentorType = $argument->getType();
67+
if ($phpDocumentorType === null) {
68+
return new Parameter($argument->getName());
69+
}
70+
71+
return new Parameter($argument->getName(), new Type((string) $phpDocumentorType));
72+
}
73+
74+
private function mapDocBlockTagToParameter(Tag $tag): Parameter
75+
{
76+
$type = $tag->getType();
77+
// For union types, use the first one that is a class array (often it is SomeClass[]|null)
78+
if ($type instanceof Types\Compound) {
79+
for ($i = 0; $t = $type->get($i); $i++) {
80+
if (
81+
$t instanceof Types\Array_
82+
&& $t->getValueType() instanceof Types\Object_
83+
&& (string)$t->getValueType() !== 'object'
84+
) {
85+
return new Parameter($tag->getVariableName(), new Type((string)$t->getValueType()->getFqsen()));
86+
}
87+
}
88+
} else if ($type instanceof Types\Array_) {
89+
return new Parameter($tag->getVariableName(), new Type((string)$type->getValueType()->getFqsen()));
90+
}
91+
}
92+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AdvancedJsonRpc\Reflection;
6+
7+
use AdvancedJsonRpc\Reflection\Dto\Method;
8+
9+
interface ReflectionInterface
10+
{
11+
public function getMethodDetails($rpcMethod, $target, $nativeMethod): Method;
12+
}

0 commit comments

Comments
 (0)