1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of the Ray.Aop package |
4
|
|
|
* |
5
|
|
|
* @license http://opensource.org/licenses/bsd-license.php BSD |
6
|
|
|
*/ |
7
|
|
|
namespace Ray\Aop; |
8
|
|
|
|
9
|
|
|
use Doctrine\Common\Annotations\AnnotationReader; |
10
|
|
|
use PHPParser\Builder\Method; |
11
|
|
|
use PhpParser\Builder\Param; |
12
|
|
|
use PhpParser\BuilderFactory; |
13
|
|
|
use PhpParser\Comment\Doc; |
14
|
|
|
use PhpParser\Parser; |
15
|
|
|
use PhpParser\PrettyPrinter\Standard; |
16
|
|
|
use Ray\Aop\Annotation\AbstractAssisted; |
17
|
|
|
|
18
|
|
|
final class CodeGenMethod |
19
|
|
|
{ |
20
|
|
|
/** |
21
|
|
|
* @var \PHPParser\Parser |
22
|
|
|
*/ |
23
|
|
|
private $parser; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @var \PHPParser\BuilderFactory |
27
|
|
|
*/ |
28
|
|
|
private $factory; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var \PHPParser\PrettyPrinter\Standard |
32
|
|
|
*/ |
33
|
|
|
private $printer; |
34
|
|
|
|
35
|
|
|
private $reader; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var AbstractAssisted |
39
|
|
|
*/ |
40
|
|
|
private $assisted = []; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @param \PHPParser\Parser $parser |
44
|
|
|
* @param \PHPParser\BuilderFactory $factory |
45
|
|
|
* @param \PHPParser\PrettyPrinter\Standard $printer |
46
|
|
|
*/ |
47
|
24 |
|
public function __construct( |
48
|
|
|
Parser $parser, |
49
|
|
|
BuilderFactory $factory, |
50
|
|
|
Standard $printer |
51
|
|
|
) { |
52
|
24 |
|
$this->parser = $parser; |
53
|
24 |
|
$this->factory = $factory; |
54
|
24 |
|
$this->printer = $printer; |
55
|
24 |
|
$this->reader = new AnnotationReader; |
56
|
24 |
|
} |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @param \ReflectionClass $class |
60
|
|
|
* |
61
|
|
|
* @return array |
62
|
|
|
*/ |
63
|
9 |
|
public function getMethods(\ReflectionClass $class, BindInterface $bind) |
64
|
|
|
{ |
65
|
9 |
|
$bindingMethods = array_keys($bind->getBindings()); |
66
|
9 |
|
$stmts = []; |
67
|
9 |
|
$methods = $class->getMethods(); |
68
|
9 |
|
foreach ($methods as $method) { |
69
|
9 |
|
$this->assisted = $this->reader->getMethodAnnotation($method, AbstractAssisted::class); |
70
|
9 |
|
$isBindingMethod = in_array($method->getName(), $bindingMethods); |
71
|
|
|
/* @var $method \ReflectionMethod */ |
72
|
9 |
|
if ($isBindingMethod && $method->isPublic()) { |
73
|
8 |
|
$stmts[] = $this->getMethod($method); |
74
|
8 |
|
} |
75
|
9 |
|
} |
76
|
|
|
|
77
|
9 |
|
return $stmts; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Return method statement |
82
|
|
|
* |
83
|
|
|
* @param \ReflectionMethod $method |
84
|
|
|
* |
85
|
|
|
* @return \PhpParser\Node\Stmt\ClassMethod |
86
|
|
|
*/ |
87
|
8 |
|
private function getMethod(\ReflectionMethod $method) |
88
|
|
|
{ |
89
|
8 |
|
$methodStmt = $this->factory->method($method->name); |
90
|
8 |
|
$params = $method->getParameters(); |
91
|
8 |
|
foreach ($params as $param) { |
92
|
8 |
|
$methodStmt = $this->getMethodStatement($param, $methodStmt); |
93
|
8 |
|
} |
94
|
8 |
|
$methodInsideStatements = $this->getMethodInsideStatement(); |
95
|
8 |
|
$methodStmt->addStmts($methodInsideStatements); |
96
|
8 |
|
$node = $this->addMethodDocComment($methodStmt, $method); |
97
|
|
|
|
98
|
8 |
|
return $node; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Return parameter reflection |
103
|
|
|
* |
104
|
|
|
* @param \ReflectionParameter $param |
105
|
|
|
* @param \PHPParser\Builder\Method $methodStmt |
106
|
|
|
* |
107
|
|
|
* @return \PHPParser\Builder\Method |
108
|
|
|
*/ |
109
|
8 |
|
private function getMethodStatement(\ReflectionParameter $param, Method $methodStmt) |
110
|
|
|
{ |
111
|
|
|
$isOverPhp7 = version_compare(PHP_VERSION, '7.0.0') >= 0; |
112
|
8 |
|
/** @var $paramStmt Param */ |
113
|
|
|
$paramStmt = $this->factory->param($param->name); |
114
|
8 |
|
/* @var $param \ReflectionParameter */ |
115
|
8 |
|
$typeHint = $param->getClass(); |
116
|
8 |
|
$this->setParameterType($param, $paramStmt, $isOverPhp7, $typeHint); |
117
|
8 |
|
$this->setDefault($param, $paramStmt); |
118
|
|
|
if ($isOverPhp7) { |
119
|
8 |
|
$this->setReturnType($param, $methodStmt, $isOverPhp7); |
|
|
|
|
120
|
|
|
} |
121
|
|
|
$methodStmt->addParam($paramStmt); |
122
|
|
|
|
123
|
|
|
return $methodStmt; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* @param Method $methodStmt |
128
|
8 |
|
* @param \ReflectionMethod $method |
129
|
|
|
* |
130
|
8 |
|
* @return \PhpParser\Node\Stmt\ClassMethod |
131
|
8 |
|
*/ |
132
|
8 |
|
private function addMethodDocComment(Method $methodStmt, \ReflectionMethod $method) |
133
|
5 |
|
{ |
134
|
5 |
|
$node = $methodStmt->getNode(); |
135
|
|
|
$docComment = $method->getDocComment(); |
136
|
8 |
|
if ($docComment) { |
137
|
|
|
$node->setAttribute('comments', [new Doc($docComment)]); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
return $node; |
141
|
|
|
} |
142
|
8 |
|
|
143
|
|
|
/** |
144
|
8 |
|
* @return \PHPParser\Node[] |
145
|
8 |
|
*/ |
146
|
|
|
private function getMethodInsideStatement() |
147
|
8 |
|
{ |
148
|
|
|
$code = file_get_contents(dirname(__DIR__) . '/src-data/CodeGenTemplate.php'); |
149
|
8 |
|
$node = $this->parser->parse($code)[0]; |
150
|
|
|
/** @var $node \PHPParser\Node\Stmt\Class_ */ |
151
|
|
|
$node = $node->getMethods()[0]; |
152
|
|
|
|
153
|
|
|
return $node->stmts; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
8 |
|
* @param \ReflectionParameter $param |
158
|
|
|
* @param Param $paramStmt |
159
|
8 |
|
* @param \ReflectionClass $typeHint |
160
|
2 |
|
* |
161
|
2 |
|
* @codeCoverageIgnore |
162
|
8 |
|
*/ |
163
|
1 |
|
private function setTypeHint(\ReflectionParameter $param, Param $paramStmt, \ReflectionClass $typeHint = null) |
164
|
1 |
|
{ |
165
|
8 |
|
if ($typeHint) { |
166
|
1 |
|
$paramStmt->setTypeHint($typeHint->name); |
167
|
1 |
|
} |
168
|
8 |
|
if ($param->isArray()) { |
169
|
|
|
$paramStmt->setTypeHint('array'); |
170
|
|
|
} |
171
|
|
|
if ($param->isCallable()) { |
172
|
|
|
$paramStmt->setTypeHint('callable'); |
173
|
|
|
} |
174
|
8 |
|
} |
175
|
|
|
|
176
|
8 |
|
/** |
177
|
2 |
|
* @param \ReflectionParameter $param |
178
|
|
|
* @param Param $paramStmt |
179
|
2 |
|
*/ |
180
|
|
|
private function setDefault(\ReflectionParameter $param, $paramStmt) |
181
|
8 |
|
{ |
182
|
1 |
|
if ($param->isDefaultValueAvailable()) { |
183
|
1 |
|
$paramStmt->setDefault($param->getDefaultValue()); |
184
|
8 |
|
|
185
|
|
|
return; |
186
|
|
|
} |
187
|
|
|
if ($this->assisted && in_array($param->getName(), $this->assisted->values)) { |
188
|
|
|
$paramStmt->setDefault(null); |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* @param \ReflectionParameter $param |
194
|
|
|
* @param Param $paramStmt |
195
|
|
|
* @param bool $isOverPhp7 |
196
|
|
|
* @param \ReflectionClass $typeHint |
197
|
|
|
*/ |
198
|
|
|
private function setParameterType(\ReflectionParameter $param, Param $paramStmt, $isOverPhp7, \ReflectionClass $typeHint = null) |
199
|
|
|
{ |
200
|
|
|
if (! $isOverPhp7) { |
201
|
|
|
$this->setTypeHint($param, $paramStmt, $typeHint); // @codeCoverageIgnore |
202
|
|
|
|
203
|
|
|
return; // @codeCoverageIgnore |
204
|
|
|
} |
205
|
|
|
$type = $param->getType(); |
206
|
|
|
if ($type) { |
207
|
|
|
$paramStmt->setTypeHint((string) $type); |
208
|
|
|
} |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* @param \ReflectionParameter $param |
213
|
|
|
* @param Method $methodStmt |
|
|
|
|
214
|
|
|
*/ |
215
|
|
|
private function setReturnType(\ReflectionParameter $param, Method $methodStmt) |
216
|
|
|
{ |
217
|
|
|
$returnType = $param->getDeclaringFunction()->getReturnType(); |
218
|
|
|
if ($returnType && method_exists($methodStmt, 'setReturnType')) { |
219
|
|
|
$methodStmt->setReturnType((string)$returnType); // @codeCoverageIgnore |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.