1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Spiral Framework. |
4
|
|
|
* |
5
|
|
|
* @license MIT |
6
|
|
|
* @author Anton Titov (Wolfy-J) |
7
|
|
|
*/ |
8
|
|
|
namespace Spiral\Tokenizer\Reflections; |
9
|
|
|
|
10
|
|
|
use Spiral\Core\Component; |
11
|
|
|
use Spiral\Core\Traits\SaturateTrait; |
12
|
|
|
use Spiral\Tokenizer\ReflectionFileInterface; |
13
|
|
|
use Spiral\Tokenizer\TokenizerInterface; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* File reflections can fetch information about classes, interfaces, functions and traits declared |
17
|
|
|
* in file. In addition file reflection provides ability to fetch and describe every method/function |
18
|
|
|
* call. |
19
|
|
|
*/ |
20
|
|
|
class ReflectionFile extends Component implements ReflectionFileInterface |
21
|
|
|
{ |
22
|
|
|
/** |
23
|
|
|
* Development sugar. |
24
|
|
|
*/ |
25
|
|
|
use SaturateTrait; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Namespace separator. |
29
|
|
|
*/ |
30
|
|
|
const NS_SEPARATOR = '\\'; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Constants for convenience. |
34
|
|
|
*/ |
35
|
|
|
const TOKEN_TYPE = TokenizerInterface::TYPE; |
36
|
|
|
const TOKEN_CODE = TokenizerInterface::CODE; |
37
|
|
|
const TOKEN_LINE = TokenizerInterface::LINE; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Opening and closing token ids. |
41
|
|
|
*/ |
42
|
|
|
const O_TOKEN = 0; |
43
|
|
|
const C_TOKEN = 1; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Namespace uses. |
47
|
|
|
*/ |
48
|
|
|
const N_USES = 2; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Set of tokens required to detect classes, traits, interfaces and function declarations. We |
52
|
|
|
* don't need any other token for that. |
53
|
|
|
* |
54
|
|
|
* @var array |
55
|
|
|
*/ |
56
|
|
|
static private $processTokens = [ |
57
|
|
|
'{', |
58
|
|
|
'}', |
59
|
|
|
';', |
60
|
|
|
T_PAAMAYIM_NEKUDOTAYIM, |
61
|
|
|
T_NAMESPACE, |
62
|
|
|
T_STRING, |
63
|
|
|
T_CLASS, |
64
|
|
|
T_INTERFACE, |
65
|
|
|
T_TRAIT, |
66
|
|
|
T_FUNCTION, |
67
|
|
|
T_NS_SEPARATOR, |
68
|
|
|
T_INCLUDE, |
69
|
|
|
T_INCLUDE_ONCE, |
70
|
|
|
T_REQUIRE, |
71
|
|
|
T_REQUIRE_ONCE, |
72
|
|
|
T_USE, |
73
|
|
|
T_AS |
74
|
|
|
]; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* @var string |
78
|
|
|
*/ |
79
|
|
|
private $filename = ''; |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Parsed tokens array. |
83
|
|
|
* |
84
|
|
|
* @invisible |
85
|
|
|
* @var array |
86
|
|
|
*/ |
87
|
|
|
private $tokens = []; |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Total tokens count. |
91
|
|
|
* |
92
|
|
|
* @invisible |
93
|
|
|
* @var int |
94
|
|
|
*/ |
95
|
|
|
private $countTokens = 0; |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Indicator that file has external includes. |
99
|
|
|
* |
100
|
|
|
* @invisible |
101
|
|
|
* @var bool |
102
|
|
|
*/ |
103
|
|
|
private $hasIncludes = false; |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Namespaces used in file and their token positions. |
107
|
|
|
* |
108
|
|
|
* @invisible |
109
|
|
|
* @var array |
110
|
|
|
*/ |
111
|
|
|
private $namespaces = []; |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Declarations of classes, interfaces and traits. |
115
|
|
|
* |
116
|
|
|
* @invisible |
117
|
|
|
* @var array |
118
|
|
|
*/ |
119
|
|
|
private $declarations = []; |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Declarations of new functions. |
123
|
|
|
* |
124
|
|
|
* @invisible |
125
|
|
|
* @var array |
126
|
|
|
*/ |
127
|
|
|
private $functions = []; |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Every found method/function invocation. |
131
|
|
|
* |
132
|
|
|
* @invisible |
133
|
|
|
* @var ReflectionInvocation[] |
134
|
|
|
*/ |
135
|
|
|
private $invocations = []; |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* @invisible |
139
|
|
|
* @var TokenizerInterface |
140
|
|
|
*/ |
141
|
|
|
protected $tokenizer = null; |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* @param string $filename |
145
|
|
|
* @param TokenizerInterface $tokenizer |
146
|
|
|
* @param array $cache Tokenizer can construct reflection with pre-created |
147
|
|
|
* cache to speed up indexation. |
148
|
|
|
*/ |
149
|
|
|
public function __construct($filename, TokenizerInterface $tokenizer = null, array $cache = []) |
150
|
|
|
{ |
151
|
|
|
$this->filename = $filename; |
152
|
|
|
$this->tokenizer = $this->saturate($tokenizer, TokenizerInterface::class); |
153
|
|
|
|
154
|
|
|
if (!empty($cache)) { |
155
|
|
|
//Locating file schema from file, can speed up class location a LOT |
156
|
|
|
$this->importSchema($cache); |
157
|
|
|
|
158
|
|
|
return; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
//Looking for declarations |
162
|
|
|
$this->locateDeclarations(); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* {@inheritdoc} |
167
|
|
|
*/ |
168
|
|
|
public function getFilename() |
169
|
|
|
{ |
170
|
|
|
return $this->filename; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* {@inheritdoc} |
175
|
|
|
*/ |
176
|
|
|
public function getFunctions() |
177
|
|
|
{ |
178
|
|
|
return array_keys($this->functions); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* {@inheritdoc} |
183
|
|
|
*/ |
184
|
|
|
public function getClasses() |
185
|
|
|
{ |
186
|
|
|
if (!isset($this->declarations['T_CLASS'])) { |
187
|
|
|
return []; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
return array_keys($this->declarations['T_CLASS']); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* {@inheritdoc} |
195
|
|
|
*/ |
196
|
|
|
public function getTraits() |
197
|
|
|
{ |
198
|
|
|
if (!isset($this->declarations['T_TRAIT'])) { |
199
|
|
|
return []; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
return array_keys($this->declarations['T_TRAIT']); |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* {@inheritdoc} |
207
|
|
|
*/ |
208
|
|
|
public function getInterfaces() |
209
|
|
|
{ |
210
|
|
|
if (!isset($this->declarations['T_INTERFACE'])) { |
211
|
|
|
return []; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
return array_keys($this->declarations['T_INTERFACE']); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Get list of tokens associated with given file. |
219
|
|
|
* |
220
|
|
|
* @return array |
221
|
|
|
*/ |
222
|
|
|
public function getTokens() |
223
|
|
|
{ |
224
|
|
|
if (empty($this->tokens)) { |
225
|
|
|
//Happens when reflection created from cache |
226
|
|
|
$this->tokens = $this->tokenizer->fetchTokens($this->filename); |
227
|
|
|
$this->countTokens = count($this->tokens); |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
return $this->tokens; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* {@inheritdoc} |
235
|
|
|
*/ |
236
|
|
|
public function hasIncludes() |
237
|
|
|
{ |
238
|
|
|
return $this->hasIncludes; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* {@inheritdoc} |
243
|
|
|
*/ |
244
|
|
|
public function getInvocations() |
245
|
|
|
{ |
246
|
|
|
if (empty($this->invocations)) { |
247
|
|
|
$this->locateInvocations($this->getTokens()); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
return $this->invocations; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Export found declaration as array for caching purposes. |
255
|
|
|
* |
256
|
|
|
* @return array |
257
|
|
|
*/ |
258
|
|
|
public function exportSchema() |
259
|
|
|
{ |
260
|
|
|
return [$this->hasIncludes, $this->declarations, $this->functions, $this->namespaces]; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* Import cached reflection schema. |
265
|
|
|
* |
266
|
|
|
* @param array $cache |
267
|
|
|
*/ |
268
|
|
|
protected function importSchema(array $cache) |
269
|
|
|
{ |
270
|
|
|
list($this->hasIncludes, $this->declarations, $this->functions, $this->namespaces) = $cache; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Locate every class, interface, trait or function definition. |
275
|
|
|
*/ |
276
|
|
|
protected function locateDeclarations() |
277
|
|
|
{ |
278
|
|
|
foreach ($this->getTokens() as $tokenID => $token) { |
279
|
|
|
if (!in_array($token[self::TOKEN_TYPE], self::$processTokens)) { |
280
|
|
|
continue; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
switch ($token[self::TOKEN_TYPE]) { |
284
|
|
|
case T_NAMESPACE: |
285
|
|
|
$this->registerNamespace($tokenID); |
286
|
|
|
break; |
287
|
|
|
|
288
|
|
|
case T_USE: |
289
|
|
|
$this->registerUse($tokenID); |
290
|
|
|
break; |
291
|
|
|
|
292
|
|
|
case T_FUNCTION: |
293
|
|
|
$this->registerFunction($tokenID); |
294
|
|
|
break; |
295
|
|
|
|
296
|
|
|
case T_CLASS: |
297
|
|
|
case T_TRAIT; |
|
|
|
|
298
|
|
|
case T_INTERFACE: |
299
|
|
|
if ( |
300
|
|
|
$this->tokens[$tokenID][self::TOKEN_TYPE] == T_CLASS |
301
|
|
|
&& isset($this->tokens[$tokenID - 1]) |
302
|
|
|
&& $this->tokens[$tokenID - 1][self::TOKEN_TYPE] == T_PAAMAYIM_NEKUDOTAYIM |
303
|
|
|
) { |
304
|
|
|
//PHP5.5 ClassName::class constant |
305
|
|
|
continue; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
$this->registerDeclaration($tokenID, $token[self::TOKEN_TYPE]); |
309
|
|
|
break; |
310
|
|
|
|
311
|
|
|
case T_INCLUDE: |
312
|
|
|
case T_INCLUDE_ONCE: |
313
|
|
|
case T_REQUIRE: |
314
|
|
|
case T_REQUIRE_ONCE: |
315
|
|
|
$this->hasIncludes = true; |
316
|
|
|
} |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
//Dropping empty namespace |
320
|
|
|
if (isset($this->namespaces[''])) { |
321
|
|
|
$this->namespaces['\\'] = $this->namespaces['']; |
322
|
|
|
unset($this->namespaces['']); |
323
|
|
|
} |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
/** |
327
|
|
|
* Handle namespace declaration. |
328
|
|
|
* |
329
|
|
|
* @param int $tokenID |
330
|
|
|
*/ |
331
|
|
|
private function registerNamespace($tokenID) |
332
|
|
|
{ |
333
|
|
|
$namespace = ''; |
334
|
|
|
$localID = $tokenID + 1; |
335
|
|
|
|
336
|
|
|
do { |
337
|
|
|
$token = $this->tokens[$localID++]; |
338
|
|
|
if ($token[self::TOKEN_CODE] == '{') { |
339
|
|
|
break; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
$namespace .= $token[self::TOKEN_CODE]; |
343
|
|
|
} while ( |
344
|
|
|
isset($this->tokens[$localID]) |
345
|
|
|
&& $this->tokens[$localID][self::TOKEN_CODE] != '{' |
346
|
|
|
&& $this->tokens[$localID][self::TOKEN_CODE] != ';' |
347
|
|
|
); |
348
|
|
|
|
349
|
|
|
//Whitespaces |
350
|
|
|
$namespace = trim($namespace); |
351
|
|
|
|
352
|
|
|
$uses = []; |
353
|
|
|
if (isset($this->namespaces[$namespace])) { |
354
|
|
|
$uses = $this->namespaces[$namespace]; |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
if ($this->tokens[$localID][self::TOKEN_CODE] == ';') { |
358
|
|
|
$endingID = count($this->tokens) - 1; |
359
|
|
|
} else { |
360
|
|
|
$endingID = $this->endingToken($tokenID); |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
$this->namespaces[$namespace] = [ |
364
|
|
|
self::O_TOKEN => $tokenID, |
365
|
|
|
self::C_TOKEN => $endingID, |
366
|
|
|
self::N_USES => $uses |
367
|
|
|
]; |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
/** |
371
|
|
|
* Handle use (import class from another namespace). |
372
|
|
|
* |
373
|
|
|
* @param int $tokenID |
374
|
|
|
*/ |
375
|
|
|
private function registerUse($tokenID) |
376
|
|
|
{ |
377
|
|
|
$namespace = rtrim($this->activeNamespace($tokenID), '\\'); |
378
|
|
|
|
379
|
|
|
$class = ''; |
380
|
|
|
$localAlias = null; |
381
|
|
|
for ($localID = $tokenID + 1; $this->tokens[$localID][self::TOKEN_CODE] != ';'; $localID++) { |
382
|
|
|
if ($this->tokens[$localID][self::TOKEN_TYPE] == T_AS) { |
383
|
|
|
$localAlias = ''; |
384
|
|
|
continue; |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
if ($localAlias === null) { |
388
|
|
|
$class .= $this->tokens[$localID][self::TOKEN_CODE]; |
389
|
|
|
} else { |
390
|
|
|
$localAlias .= $this->tokens[$localID][self::TOKEN_CODE]; |
391
|
|
|
} |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
if (empty($localAlias)) { |
395
|
|
|
$names = explode('\\', $class); |
396
|
|
|
$localAlias = end($names); |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
$this->namespaces[$namespace][self::N_USES][trim($localAlias)] = trim($class); |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* Handle function declaration (function creation). |
404
|
|
|
* |
405
|
|
|
* @param int $tokenID |
406
|
|
|
*/ |
407
|
|
|
private function registerFunction($tokenID) |
408
|
|
|
{ |
409
|
|
|
foreach ($this->declarations as $declarations) { |
410
|
|
|
foreach ($declarations as $location) { |
411
|
|
|
if ($tokenID >= $location[self::O_TOKEN] && $tokenID <= $location[self::C_TOKEN]) { |
412
|
|
|
//We are inside class, function is method |
413
|
|
|
return; |
414
|
|
|
} |
415
|
|
|
} |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
$localID = $tokenID + 1; |
419
|
|
|
while ($this->tokens[$localID][self::TOKEN_TYPE] != T_STRING) { |
420
|
|
|
//Fetching function name |
421
|
|
|
$localID++; |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
$name = $this->tokens[$localID][self::TOKEN_CODE]; |
425
|
|
View Code Duplication |
if (!empty($namespace = $this->activeNamespace($tokenID))) { |
|
|
|
|
426
|
|
|
$name = $namespace . self::NS_SEPARATOR . $name; |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
$this->functions[$name] = [ |
430
|
|
|
self::O_TOKEN => $tokenID, |
431
|
|
|
self::C_TOKEN => $this->endingToken($tokenID) |
432
|
|
|
]; |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
/** |
436
|
|
|
* Handle declaration of class, trait of interface. Declaration will be stored under it's token |
437
|
|
|
* type in declarations array. |
438
|
|
|
* |
439
|
|
|
* @param int $tokenID |
440
|
|
|
* @param int $tokenType |
441
|
|
|
*/ |
442
|
|
|
private function registerDeclaration($tokenID, $tokenType) |
443
|
|
|
{ |
444
|
|
|
$localID = $tokenID + 1; |
445
|
|
|
while ($this->tokens[$localID][self::TOKEN_TYPE] != T_STRING) { |
446
|
|
|
$localID++; |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
$name = $this->tokens[$localID][self::TOKEN_CODE]; |
450
|
|
View Code Duplication |
if (!empty($namespace = $this->activeNamespace($tokenID))) { |
|
|
|
|
451
|
|
|
$name = $namespace . self::NS_SEPARATOR . $name; |
452
|
|
|
} |
453
|
|
|
|
454
|
|
|
$this->declarations[token_name($tokenType)][$name] = [ |
455
|
|
|
self::O_TOKEN => $tokenID, |
456
|
|
|
self::C_TOKEN => $this->endingToken($tokenID) |
457
|
|
|
]; |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
/** |
461
|
|
|
* Locate every function or static method call (including $this calls). |
462
|
|
|
* |
463
|
|
|
* @param array $tokens |
464
|
|
|
* @param int $invocationLevel |
465
|
|
|
*/ |
466
|
|
|
private function locateInvocations(array $tokens, $invocationLevel = 0) |
467
|
|
|
{ |
468
|
|
|
//Multiple "(" and ")" statements nested. |
469
|
|
|
$level = 0; |
470
|
|
|
|
471
|
|
|
//Inside array arguments |
472
|
|
|
$arrayLevel = 0; |
|
|
|
|
473
|
|
|
|
474
|
|
|
//Skip all tokens until next function |
475
|
|
|
$ignore = false; |
476
|
|
|
|
477
|
|
|
//Were function was found |
478
|
|
|
$invocationTID = 0; |
479
|
|
|
|
480
|
|
|
//Parsed arguments and their first token id |
481
|
|
|
$arguments = []; |
482
|
|
|
$argumentsTID = false; |
483
|
|
|
|
484
|
|
|
//Tokens used to re-enable token detection |
485
|
|
|
$stopTokens = [T_STRING, T_WHITESPACE, T_DOUBLE_COLON, T_OBJECT_OPERATOR, T_NS_SEPARATOR]; |
486
|
|
|
foreach ($tokens as $tokenID => $token) { |
487
|
|
|
$tokenType = $token[self::TOKEN_TYPE]; |
488
|
|
|
|
489
|
|
|
//We are not indexing function declarations or functions called from $objects. |
490
|
|
|
if ($tokenType == T_FUNCTION || $tokenType == T_OBJECT_OPERATOR || $tokenType == T_NEW) { |
491
|
|
|
if ( |
492
|
|
|
empty($argumentsTID) |
493
|
|
|
&& ( |
494
|
|
|
empty($invocationTID) |
495
|
|
|
|| $this->getSource($invocationTID, $tokenID - 1) != '$this' |
496
|
|
|
) |
497
|
|
|
) { |
498
|
|
|
//Not a call, function declaration, or object method |
499
|
|
|
$ignore = true; |
500
|
|
|
continue; |
501
|
|
|
} |
502
|
|
|
} elseif ($ignore) { |
503
|
|
|
if (!in_array($tokenType, $stopTokens)) { |
504
|
|
|
//Returning to search |
505
|
|
|
$ignore = false; |
506
|
|
|
} |
507
|
|
|
continue; |
508
|
|
|
} |
509
|
|
|
|
510
|
|
|
//We are inside function, and there is "(", indexing arguments. |
511
|
|
|
if (!empty($invocationTID) && ($tokenType == '(' || $tokenType == '[')) { |
512
|
|
|
if (empty($argumentsTID)) { |
513
|
|
|
$argumentsTID = $tokenID; |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
$level++; |
517
|
|
|
if ($level != 1) { |
518
|
|
|
//Not arguments beginning, but arguments part |
519
|
|
|
$arguments[$tokenID] = $token; |
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
continue; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
//We are inside function arguments and ")" met. |
526
|
|
|
if (!empty($invocationTID) && ($tokenType == ')' || $tokenType == ']')) { |
527
|
|
|
$level--; |
528
|
|
|
if ($level == -1) { |
529
|
|
|
$invocationTID = false; |
530
|
|
|
$level = 0; |
531
|
|
|
continue; |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
//Function fully indexed, we can process it now. |
535
|
|
|
if ($level == 0) { |
536
|
|
|
$this->registerInvocation( |
537
|
|
|
$invocationTID, |
538
|
|
|
$argumentsTID, |
539
|
|
|
$tokenID, |
540
|
|
|
$arguments, |
541
|
|
|
$invocationLevel |
542
|
|
|
); |
543
|
|
|
|
544
|
|
|
//Closing search |
545
|
|
|
$arguments = []; |
546
|
|
|
$argumentsTID = $invocationTID = false; |
547
|
|
|
} else { |
548
|
|
|
//Not arguments beginning, but arguments part |
549
|
|
|
$arguments[$tokenID] = $token; |
550
|
|
|
} |
551
|
|
|
|
552
|
|
|
continue; |
553
|
|
|
} |
554
|
|
|
|
555
|
|
|
//Still inside arguments. |
556
|
|
|
if (!empty($invocationTID) && !empty($level)) { |
557
|
|
|
$arguments[$tokenID] = $token; |
558
|
|
|
continue; |
559
|
|
|
} |
560
|
|
|
|
561
|
|
|
//Nothing valuable to remember, will be parsed later. |
562
|
|
|
if (!empty($invocationTID) && in_array($tokenType, $stopTokens)) { |
563
|
|
|
continue; |
564
|
|
|
} |
565
|
|
|
|
566
|
|
|
//Seems like we found function/method call |
567
|
|
|
if ( |
568
|
|
|
$tokenType == T_STRING |
569
|
|
|
|| $tokenType == T_STATIC |
570
|
|
|
|| $tokenType == T_NS_SEPARATOR |
571
|
|
|
|| ($tokenType == T_VARIABLE && $token[self::TOKEN_CODE] == '$this') |
572
|
|
|
) { |
573
|
|
|
$invocationTID = $tokenID; |
574
|
|
|
$level = 0; |
575
|
|
|
|
576
|
|
|
$argumentsTID = false; |
577
|
|
|
continue; |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
//Returning to search |
581
|
|
|
$invocationTID = false; |
582
|
|
|
$arguments = []; |
583
|
|
|
} |
584
|
|
|
} |
585
|
|
|
|
586
|
|
|
/** |
587
|
|
|
* Registering invocation. |
588
|
|
|
* |
589
|
|
|
* @param int $invocationID |
590
|
|
|
* @param int $argumentsID |
591
|
|
|
* @param int $endID |
592
|
|
|
* @param array $arguments |
593
|
|
|
* @param int $invocationLevel |
594
|
|
|
*/ |
595
|
|
|
private function registerInvocation( |
596
|
|
|
$invocationID, |
597
|
|
|
$argumentsID, |
598
|
|
|
$endID, |
599
|
|
|
array $arguments, |
600
|
|
|
$invocationLevel |
601
|
|
|
) { |
602
|
|
|
//Nested invocations |
603
|
|
|
$this->locateInvocations($arguments, $invocationLevel + 1); |
604
|
|
|
|
605
|
|
|
list($class, $operator, $name) = $this->fetchContext($invocationID, $argumentsID); |
606
|
|
|
if (!empty($operator) && empty($class)) { |
607
|
|
|
//Non detectable |
608
|
|
|
return; |
609
|
|
|
} |
610
|
|
|
|
611
|
|
|
$this->invocations[] = new ReflectionInvocation( |
612
|
|
|
$this->filename, |
613
|
|
|
$this->lineNumber($invocationID), |
614
|
|
|
$class, |
615
|
|
|
$operator, |
616
|
|
|
$name, |
617
|
|
|
ReflectionArgument::locateArguments($arguments), |
618
|
|
|
$this->getSource($invocationID, $endID), |
619
|
|
|
$invocationLevel |
620
|
|
|
); |
621
|
|
|
} |
622
|
|
|
|
623
|
|
|
/** |
624
|
|
|
* Fetching invocation context. |
625
|
|
|
* |
626
|
|
|
* @param int $invocationTID |
627
|
|
|
* @param int $argumentsTID |
628
|
|
|
* @return array |
629
|
|
|
*/ |
630
|
|
|
private function fetchContext($invocationTID, $argumentsTID) |
631
|
|
|
{ |
632
|
|
|
$class = $operator = ''; |
633
|
|
|
$name = trim($this->getSource($invocationTID, $argumentsTID), '( '); |
634
|
|
|
|
635
|
|
|
//Let's try to fetch all information we need |
636
|
|
|
if (strpos($name, '->') !== false) { |
637
|
|
|
$operator = '->'; |
638
|
|
|
} elseif (strpos($name, '::') !== false) { |
639
|
|
|
$operator = '::'; |
640
|
|
|
} |
641
|
|
|
|
642
|
|
|
if (!empty($operator)) { |
643
|
|
|
list($class, $name) = explode($operator, $name); |
644
|
|
|
|
645
|
|
|
//We now have to clarify class name |
646
|
|
|
if (in_array($class, ['self', 'static', '$this'])) { |
647
|
|
|
$class = $this->activeDeclaration($invocationTID); |
648
|
|
|
} |
649
|
|
|
} |
650
|
|
|
|
651
|
|
|
return [$class, $operator, $name]; |
652
|
|
|
} |
653
|
|
|
|
654
|
|
|
/** |
655
|
|
|
* Get declaration which is active in given token position. |
656
|
|
|
* |
657
|
|
|
* @param int $tokenID |
658
|
|
|
* @return string|null |
659
|
|
|
*/ |
660
|
|
|
private function activeDeclaration($tokenID) |
661
|
|
|
{ |
662
|
|
|
foreach ($this->declarations as $declarations) { |
663
|
|
|
foreach ($declarations as $name => $position) { |
664
|
|
View Code Duplication |
if ($tokenID >= $position[self::O_TOKEN] && $tokenID <= $position[self::C_TOKEN]) { |
|
|
|
|
665
|
|
|
return $name; |
666
|
|
|
} |
667
|
|
|
} |
668
|
|
|
} |
669
|
|
|
|
670
|
|
|
//Can not be detected |
671
|
|
|
return null; |
672
|
|
|
} |
673
|
|
|
|
674
|
|
|
/** |
675
|
|
|
* Get namespace name active at specified token position. |
676
|
|
|
* |
677
|
|
|
* @param int $tokenID |
678
|
|
|
* @return string |
679
|
|
|
*/ |
680
|
|
|
private function activeNamespace($tokenID) |
681
|
|
|
{ |
682
|
|
|
foreach ($this->namespaces as $namespace => $position) { |
683
|
|
View Code Duplication |
if ($tokenID >= $position[self::O_TOKEN] && $tokenID <= $position[self::C_TOKEN]) { |
|
|
|
|
684
|
|
|
return $namespace; |
685
|
|
|
} |
686
|
|
|
} |
687
|
|
|
|
688
|
|
|
//Seems like no namespace declaration |
689
|
|
|
$this->namespaces[''] = [ |
690
|
|
|
self::O_TOKEN => 0, |
691
|
|
|
self::C_TOKEN => count($this->tokens), |
692
|
|
|
self::N_USES => [] |
693
|
|
|
]; |
694
|
|
|
|
695
|
|
|
return ''; |
696
|
|
|
} |
697
|
|
|
|
698
|
|
|
/** |
699
|
|
|
* Find token ID of ending brace. |
700
|
|
|
* |
701
|
|
|
* @param int $tokenID |
702
|
|
|
* @return mixed |
703
|
|
|
*/ |
704
|
|
|
private function endingToken($tokenID) |
705
|
|
|
{ |
706
|
|
|
$level = null; |
707
|
|
|
for ($localID = $tokenID; $localID < $this->countTokens; $localID++) { |
708
|
|
|
$token = $this->tokens[$localID]; |
709
|
|
|
if ($token[self::TOKEN_CODE] == '{') { |
710
|
|
|
$level++; |
711
|
|
|
continue; |
712
|
|
|
} |
713
|
|
|
|
714
|
|
|
if ($token[self::TOKEN_CODE] == '}') { |
715
|
|
|
$level--; |
716
|
|
|
} |
717
|
|
|
|
718
|
|
|
if ($level === 0) { |
719
|
|
|
break; |
720
|
|
|
} |
721
|
|
|
} |
722
|
|
|
|
723
|
|
|
return $localID; |
724
|
|
|
} |
725
|
|
|
|
726
|
|
|
/** |
727
|
|
|
* Get line number associated with token. |
728
|
|
|
* |
729
|
|
|
* @param int $tokenID |
730
|
|
|
* @return int |
731
|
|
|
*/ |
732
|
|
|
private function lineNumber($tokenID) |
733
|
|
|
{ |
734
|
|
|
while (empty($this->tokens[$tokenID][self::TOKEN_LINE])) { |
735
|
|
|
$tokenID--; |
736
|
|
|
} |
737
|
|
|
|
738
|
|
|
return $this->tokens[$tokenID][self::TOKEN_LINE]; |
739
|
|
|
} |
740
|
|
|
|
741
|
|
|
/** |
742
|
|
|
* Get source located between two tokens. |
743
|
|
|
* |
744
|
|
|
* @param int $startID |
745
|
|
|
* @param int $endID |
746
|
|
|
* @return string |
747
|
|
|
*/ |
748
|
|
|
private function getSource($startID, $endID) |
749
|
|
|
{ |
750
|
|
|
$result = ''; |
751
|
|
|
for ($tokenID = $startID; $tokenID <= $endID; $tokenID++) { |
752
|
|
|
//Collecting function usage source |
753
|
|
|
$result .= $this->tokens[$tokenID][self::TOKEN_CODE]; |
754
|
|
|
} |
755
|
|
|
|
756
|
|
|
return $result; |
757
|
|
|
} |
758
|
|
|
} |
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.