PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.5.1
Defender Security – Malware Scanner, Login Security & Firewall v5.5.1
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / lib / packages / Laravel / SerializableClosure / Support / ReflectionClosure.php

ReflectionClosure.php in Defender Security – Malware Scanner, Login Security & Firewall 5.5.1, at lib/packages/Laravel/SerializableClosure/Support/ReflectionClosure.php

1,195 lines 44.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPMU_DEV\Defender\Vendor\Laravel\SerializableClosure\Support;
4
5 defined('T_NAME_QUALIFIED') || define('T_NAME_QUALIFIED', -4);
6 defined('T_NAME_FULLY_QUALIFIED') || define('T_NAME_FULLY_QUALIFIED', -5);
7 defined('T_FN') || define('T_FN', -6);
8 defined('T_NULLSAFE_OBJECT_OPERATOR') || define('T_NULLSAFE_OBJECT_OPERATOR', -7);
9
10 use Closure;
11 use ReflectionFunction;
12
13 class ReflectionClosure extends ReflectionFunction
14 {
15 protected $code;
16 protected $tokens;
17 protected $hashedName;
18 protected $useVariables;
19 protected $isStaticClosure;
20 protected $isScopeRequired;
21 protected $isBindingRequired;
22 protected $isShortClosure;
23
24 protected static $files = [];
25 protected static $classes = [];
26 protected static $functions = [];
27 protected static $constants = [];
28 protected static $structures = [];
29
30 /**
31 * Creates a new reflection closure instance.
32 *
33 * @param \Closure $closure
34 * @param string|null $code
35 * @return void
36 */
37 public function __construct(Closure $closure, $code = null)
38 {
39 parent::__construct($closure);
40 }
41
42 /**
43 * Checks if the closure is "static".
44 *
45 * @return bool
46 */
47 public function isStatic(): bool
48 {
49 if ($this->isStaticClosure === null) {
50 $this->isStaticClosure = strtolower(substr($this->getCode(), 0, 6)) === 'static';
51 }
52
53 return $this->isStaticClosure;
54 }
55
56 /**
57 * Checks if the closure is a "short closure".
58 *
59 * @return bool
60 */
61 public function isShortClosure()
62 {
63 if ($this->isShortClosure === null) {
64 $code = $this->getCode();
65
66 if ($this->isStatic()) {
67 $code = substr($code, 6);
68 }
69
70 $this->isShortClosure = strtolower(substr(trim($code), 0, 2)) === 'fn';
71 }
72
73 return $this->isShortClosure;
74 }
75
76 /**
77 * Get the closure's code.
78 *
79 * @return string
80 */
81 public function getCode()
82 {
83 if ($this->code !== null) {
84 return $this->code;
85 }
86
87 $fileName = $this->getFileName();
88 $line = $this->getStartLine() - 1;
89
90 $className = null;
91
92 if (null !== $className = $this->getClosureScopeClass()) {
93 $className = '\\'.trim($className->getName(), '\\');
94 }
95
96 $builtin_types = self::getBuiltinTypes();
97 $class_keywords = ['self', 'static', 'parent'];
98
99 $ns = $this->getClosureNamespaceName();
100 $nsf = $ns == '' ? '' : ($ns[0] == '\\' ? $ns : '\\'.$ns);
101
102 $_file = var_export($fileName, true);
103 $_dir = var_export(dirname($fileName), true);
104 $_namespace = var_export($ns, true);
105 $_class = var_export(trim($className ?: '', '\\'), true);
106 $_function = $ns.($ns == '' ? '' : '\\').'{closure}';
107 $_method = ($className == '' ? '' : trim($className, '\\').'::').$_function;
108 $_function = var_export($_function, true);
109 $_method = var_export($_method, true);
110 $_trait = null;
111
112 $tokens = $this->getTokens();
113 $state = $lastState = 'start';
114 $inside_structure = false;
115 $isFirstClassCallable = false;
116 $isShortClosure = false;
117
118 $inside_structure_mark = 0;
119 $open = 0;
120 $code = '';
121 $id_start = $id_start_ci = $id_name = $context = '';
122 $classes = $functions = $constants = null;
123 $use = [];
124 $lineAdd = 0;
125 $isUsingScope = false;
126 $isUsingThisObject = false;
127
128 for ($i = 0, $l = count($tokens); $i < $l; $i++) {
129 $token = $tokens[$i];
130
131 switch ($state) {
132 case 'start':
133 if ($token[0] === T_FUNCTION || $token[0] === T_STATIC) {
134 $code .= $token[1];
135
136 $state = $token[0] === T_FUNCTION ? 'function' : 'static';
137 } elseif ($token[0] === T_FN) {
138 $isShortClosure = true;
139 $code .= $token[1];
140 $state = 'closure_args';
141 } elseif ($token[0] === T_PUBLIC || $token[0] === T_PROTECTED || $token[0] === T_PRIVATE) {
142 $code = '';
143 $isFirstClassCallable = true;
144 }
145 break;
146 case 'static':
147 if ($token[0] === T_WHITESPACE || $token[0] === T_COMMENT || $token[0] === T_FUNCTION) {
148 $code .= $token[1];
149 if ($token[0] === T_FUNCTION) {
150 $state = 'function';
151 }
152 } elseif ($token[0] === T_FN) {
153 $isShortClosure = true;
154 $code .= $token[1];
155 $state = 'closure_args';
156 } else {
157 $code = '';
158 $state = 'start';
159 }
160 break;
161 case 'function':
162 switch ($token[0]) {
163 case T_STRING:
164 if ($isFirstClassCallable) {
165 $state = 'closure_args';
166 break;
167 }
168
169 $code = '';
170 $state = 'named_function';
171 break;
172 case '(':
173 $code .= '(';
174 $state = 'closure_args';
175 break;
176 default:
177 $code .= is_array($token) ? $token[1] : $token;
178 }
179 break;
180 case 'named_function':
181 if ($token[0] === T_FUNCTION || $token[0] === T_STATIC) {
182 $code = $token[1];
183 $state = $token[0] === T_FUNCTION ? 'function' : 'static';
184 } elseif ($token[0] === T_FN) {
185 $isShortClosure = true;
186 $code .= $token[1];
187 $state = 'closure_args';
188 }
189 break;
190 case 'closure_args':
191 switch ($token[0]) {
192 case T_NAME_QUALIFIED:
193 [$id_start, $id_start_ci, $id_name] = $this->parseNameQualified($token[1]);
194 $context = 'args';
195 $state = 'id_name';
196 $lastState = 'closure_args';
197 break;
198 case T_NS_SEPARATOR:
199 case T_STRING:
200 $id_start = $token[1];
201 $id_start_ci = strtolower($id_start);
202 $id_name = '';
203 $context = 'args';
204 $state = 'id_name';
205 $lastState = 'closure_args';
206 break;
207 case T_USE:
208 $code .= $token[1];
209 $state = 'use';
210 break;
211 case T_DOUBLE_ARROW:
212 $code .= $token[1];
213 if ($isShortClosure) {
214 $state = 'closure';
215 }
216 break;
217 case ':':
218 $code .= ':';
219 $state = 'return';
220 break;
221 case '{':
222 $code .= '{';
223 $state = 'closure';
224 $open++;
225 break;
226 default:
227 $code .= is_array($token) ? $token[1] : $token;
228 }
229 break;
230 case 'use':
231 switch ($token[0]) {
232 case T_VARIABLE:
233 $use[] = substr($token[1], 1);
234 $code .= $token[1];
235 break;
236 case '{':
237 $code .= '{';
238 $state = 'closure';
239 $open++;
240 break;
241 case ':':
242 $code .= ':';
243 $state = 'return';
244 break;
245 default:
246 $code .= is_array($token) ? $token[1] : $token;
247 break;
248 }
249 break;
250 case 'return':
251 switch ($token[0]) {
252 case T_WHITESPACE:
253 case T_COMMENT:
254 case T_DOC_COMMENT:
255 $code .= $token[1];
256 break;
257 case T_NS_SEPARATOR:
258 case T_STRING:
259 $id_start = $token[1];
260 $id_start_ci = strtolower($id_start);
261 $id_name = '';
262 $context = 'return_type';
263 $state = 'id_name';
264 $lastState = 'return';
265 break 2;
266 case T_NAME_QUALIFIED:
267 [$id_start, $id_start_ci, $id_name] = $this->parseNameQualified($token[1]);
268 $context = 'return_type';
269 $state = 'id_name';
270 $lastState = 'return';
271 break 2;
272 case T_DOUBLE_ARROW:
273 $code .= $token[1];
274 if ($isShortClosure) {
275 $state = 'closure';
276 }
277 break;
278 case '{':
279 $code .= '{';
280 $state = 'closure';
281 $open++;
282 break;
283 default:
284 $code .= is_array($token) ? $token[1] : $token;
285 break;
286 }
287 break;
288 case 'closure':
289 switch ($token[0]) {
290 case T_CURLY_OPEN:
291 case T_DOLLAR_OPEN_CURLY_BRACES:
292 case '{':
293 $code .= is_array($token) ? $token[1] : $token;
294 $open++;
295 break;
296 case '}':
297 $code .= '}';
298 if (--$open === 0 && ! $isShortClosure) {
299 break 3;
300 } elseif ($inside_structure) {
301 $inside_structure = ! ($open === $inside_structure_mark);
302 }
303 break;
304 case '(':
305 case '[':
306 $code .= $token[0];
307 if ($isShortClosure) {
308 $open++;
309 }
310 break;
311 case ')':
312 case ']':
313 if ($isShortClosure) {
314 if ($open === 0) {
315 break 3;
316 }
317 $open--;
318 }
319 $code .= $token[0];
320 break;
321 case ',':
322 case ';':
323 if ($isShortClosure && $open === 0) {
324 break 3;
325 }
326 $code .= $token[0];
327 break;
328 case T_LINE:
329 $code .= $token[2] - $line + $lineAdd;
330 break;
331 case T_FILE:
332 $code .= $_file;
333 break;
334 case T_DIR:
335 $code .= $_dir;
336 break;
337 case T_NS_C:
338 $code .= $_namespace;
339 break;
340 case T_CLASS_C:
341 $code .= $inside_structure ? $token[1] : $_class;
342 break;
343 case T_FUNC_C:
344 $code .= $inside_structure ? $token[1] : $_function;
345 break;
346 case T_METHOD_C:
347 $code .= $inside_structure ? $token[1] : $_method;
348 break;
349 case T_COMMENT:
350 if (substr($token[1], 0, 8) === '#trackme') {
351 $timestamp = time();
352 $code .= '/**'.PHP_EOL;
353 $code .= '* Date : '.date(DATE_W3C, $timestamp).PHP_EOL;
354 $code .= '* Timestamp : '.$timestamp.PHP_EOL;
355 $code .= '* Line : '.($line + 1).PHP_EOL;
356 $code .= '* File : '.$_file.PHP_EOL.'*/'.PHP_EOL;
357 $lineAdd += 5;
358 } else {
359 $code .= $token[1];
360 }
361 break;
362 case T_VARIABLE:
363 if ($token[1] == '$this' && ! $inside_structure) {
364 $isUsingThisObject = true;
365 }
366 $code .= $token[1];
367 break;
368 case T_STATIC:
369 case T_NS_SEPARATOR:
370 case T_STRING:
371 $id_start = $token[1];
372 $id_start_ci = strtolower($id_start);
373 $id_name = '';
374 $context = 'root';
375 $state = 'id_name';
376 $lastState = 'closure';
377 break 2;
378 case T_NAME_QUALIFIED:
379 [$id_start, $id_start_ci, $id_name] = $this->parseNameQualified($token[1]);
380 $context = 'root';
381 $state = 'id_name';
382 $lastState = 'closure';
383 break 2;
384 case T_NEW:
385 $code .= $token[1];
386 $context = 'new';
387 $state = 'id_start';
388 $lastState = 'closure';
389 break 2;
390 case T_USE:
391 $code .= $token[1];
392 $context = 'use';
393 $state = 'id_start';
394 $lastState = 'closure';
395 break;
396 case T_INSTANCEOF:
397 case T_INSTEADOF:
398 $code .= $token[1];
399 $context = 'instanceof';
400 $state = 'id_start';
401 $lastState = 'closure';
402 break;
403 case T_OBJECT_OPERATOR:
404 case T_NULLSAFE_OBJECT_OPERATOR:
405 case T_DOUBLE_COLON:
406 $code .= $token[1];
407 $lastState = 'closure';
408 $state = 'ignore_next';
409 break;
410 case T_FUNCTION:
411 $code .= $token[1];
412 $state = 'closure_args';
413 if (! $inside_structure) {
414 $inside_structure = true;
415 $inside_structure_mark = $open;
416 }
417 break;
418 case T_TRAIT_C:
419 if ($_trait === null) {
420 $startLine = $this->getStartLine();
421 $endLine = $this->getEndLine();
422 $structures = $this->getStructures();
423
424 $_trait = '';
425
426 foreach ($structures as &$struct) {
427 if ($struct['type'] === 'trait' &&
428 $struct['start'] <= $startLine &&
429 $struct['end'] >= $endLine
430 ) {
431 $_trait = ($ns == '' ? '' : $ns.'\\').$struct['name'];
432 break;
433 }
434 }
435
436 $_trait = var_export($_trait, true);
437 }
438
439 $code .= $_trait;
440 break;
441 default:
442 $code .= is_array($token) ? $token[1] : $token;
443 }
444 break;
445 case 'ignore_next':
446 switch ($token[0]) {
447 case T_WHITESPACE:
448 case T_COMMENT:
449 case T_DOC_COMMENT:
450 $code .= $token[1];
451 break;
452 case T_CLASS:
453 case T_NEW:
454 case T_STATIC:
455 case T_VARIABLE:
456 case T_STRING:
457 case T_CLASS_C:
458 case T_FILE:
459 case T_DIR:
460 case T_METHOD_C:
461 case T_FUNC_C:
462 case T_FUNCTION:
463 case T_INSTANCEOF:
464 case T_LINE:
465 case T_NS_C:
466 case T_TRAIT_C:
467 case T_USE:
468 $code .= $token[1];
469 $state = $lastState;
470 break;
471 default:
472 $state = $lastState;
473 $i--;
474 }
475 break;
476 case 'id_start':
477 switch ($token[0]) {
478 case T_WHITESPACE:
479 case T_COMMENT:
480 case T_DOC_COMMENT:
481 $code .= $token[1];
482 break;
483 case T_NS_SEPARATOR:
484 case T_NAME_FULLY_QUALIFIED:
485 case T_STRING:
486 case T_STATIC:
487 $id_start = $token[1];
488 $id_start_ci = strtolower($id_start);
489 $id_name = '';
490 $state = 'id_name';
491 break 2;
492 case T_NAME_QUALIFIED:
493 [$id_start, $id_start_ci, $id_name] = $this->parseNameQualified($token[1]);
494 $state = 'id_name';
495 break 2;
496 case T_VARIABLE:
497 $code .= $token[1];
498 $state = $lastState;
499 break;
500 case T_CLASS:
501 $code .= $token[1];
502 $state = 'anonymous';
503 break;
504 default:
505 $i--; //reprocess last
506 $state = 'id_name';
507 }
508 break;
509 case 'id_name':
510 switch ($token[0]) {
511 // named arguments...
512 case ':':
513 if ($lastState === 'closure' && $context === 'root') {
514 $state = 'closure';
515 $code .= $id_start.$token;
516 }
517
518 break;
519 case T_NAME_QUALIFIED:
520 case T_NS_SEPARATOR:
521 case T_STRING:
522 case T_WHITESPACE:
523 case T_COMMENT:
524 case T_DOC_COMMENT:
525 $id_name .= $token[1];
526 break;
527 case '(':
528 if ($isShortClosure) {
529 $open++;
530 }
531 if ($context === 'new' || false !== strpos($id_name, '\\')) {
532 if ($id_start_ci === 'self' || $id_start_ci === 'static') {
533 if (! $inside_structure) {
534 $isUsingScope = true;
535 }
536 } elseif ($id_start !== '\\' && ! in_array($id_start_ci, $class_keywords)) {
537 if ($classes === null) {
538 $classes = $this->getClasses();
539 }
540 if (isset($classes[$id_start_ci])) {
541 $id_start = $classes[$id_start_ci];
542 }
543 if ($id_start[0] !== '\\') {
544 $id_start = $nsf.'\\'.$id_start;
545 }
546 }
547 } else {
548 if ($id_start !== '\\') {
549 if ($functions === null) {
550 $functions = $this->getFunctions();
551 }
552 if (isset($functions[$id_start_ci])) {
553 $id_start = $functions[$id_start_ci];
554 } elseif ($nsf !== '\\' && function_exists($nsf.'\\'.$id_start)) {
555 $id_start = $nsf.'\\'.$id_start;
556 // Cache it to functions array
557 $functions[$id_start_ci] = $id_start;
558 }
559 }
560 }
561 $code .= $id_start.$id_name.'(';
562 $state = $lastState;
563 break;
564 case T_VARIABLE:
565 case T_DOUBLE_COLON:
566 if ($id_start !== '\\') {
567 if ($id_start_ci === 'self' || $id_start_ci === 'parent') {
568 if (! $inside_structure) {
569 $isUsingScope = true;
570 }
571 } elseif ($id_start_ci === 'static') {
572 if (! $inside_structure) {
573 $isUsingScope = $token[0] === T_DOUBLE_COLON;
574 }
575 } elseif (! (\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))) {
576 if ($classes === null) {
577 $classes = $this->getClasses();
578 }
579 if (isset($classes[$id_start_ci])) {
580 $id_start = $classes[$id_start_ci];
581 }
582 if ($id_start[0] !== '\\') {
583 $id_start = $nsf.'\\'.$id_start;
584 }
585 }
586 }
587
588 $code .= $id_start.$id_name.$token[1];
589 $state = $token[0] === T_DOUBLE_COLON ? 'ignore_next' : $lastState;
590 break;
591 default:
592 if ($id_start !== '\\' && ! defined($id_start)) {
593 if ($constants === null) {
594 $constants = $this->getConstants();
595 }
596 if (isset($constants[$id_start])) {
597 $id_start = $constants[$id_start];
598 } elseif ($context === 'new') {
599 if (in_array($id_start_ci, $class_keywords)) {
600 if (! $inside_structure) {
601 $isUsingScope = true;
602 }
603 } else {
604 if ($classes === null) {
605 $classes = $this->getClasses();
606 }
607 if (isset($classes[$id_start_ci])) {
608 $id_start = $classes[$id_start_ci];
609 }
610 if ($id_start[0] !== '\\') {
611 $id_start = $nsf.'\\'.$id_start;
612 }
613 }
614 } elseif ($context === 'use' ||
615 $context === 'instanceof' ||
616 $context === 'args' ||
617 $context === 'return_type' ||
618 $context === 'extends' ||
619 $context === 'root'
620 ) {
621 if (in_array($id_start_ci, $class_keywords)) {
622 if (! $inside_structure && ! $id_start_ci === 'static') {
623 $isUsingScope = true;
624 }
625 } elseif (! (\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))) {
626 if ($classes === null) {
627 $classes = $this->getClasses();
628 }
629 if (isset($classes[$id_start_ci])) {
630 $id_start = $classes[$id_start_ci];
631 }
632 if ($id_start[0] !== '\\') {
633 $id_start = $nsf.'\\'.$id_start;
634 }
635 }
636 }
637 }
638 $code .= $id_start.$id_name;
639 $state = $lastState;
640 $i--; //reprocess last token
641 }
642 break;
643 case 'anonymous':
644 switch ($token[0]) {
645 case T_NS_SEPARATOR:
646 case T_STRING:
647 $id_start = $token[1];
648 $id_start_ci = strtolower($id_start);
649 $id_name = '';
650 $state = 'id_name';
651 $context = 'extends';
652 $lastState = 'anonymous';
653 break;
654 case '{':
655 $state = 'closure';
656 if (! $inside_structure) {
657 $inside_structure = true;
658 $inside_structure_mark = $open;
659 }
660 $i--;
661 break;
662 default:
663 $code .= is_array($token) ? $token[1] : $token;
664 }
665 break;
666 }
667 }
668
669 if ($isShortClosure) {
670 $this->useVariables = $this->getStaticVariables();
671 } else {
672 $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use));
673 }
674
675 $this->isShortClosure = $isShortClosure;
676 $this->isBindingRequired = $isUsingThisObject;
677 $this->isScopeRequired = $isUsingScope;
678
679 if (PHP_VERSION_ID >= 80100) {
680 $attributesCode = array_map(function ($attribute) {
681 $arguments = $attribute->getArguments();
682
683 $name = $attribute->getName();
684 $arguments = implode(', ', array_map(function ($argument, $key) {
685 $argument = sprintf("'%s'", str_replace("'", "\\'", $argument));
686
687 if (is_string($key)) {
688 $argument = sprintf('%s: %s', $key, $argument);
689 }
690
691 return $argument;
692 }, $arguments, array_keys($arguments)));
693
694 return "#[$name($arguments)]";
695 }, $this->getAttributes());
696
697 if (! empty($attributesCode)) {
698 $code = implode("\n", array_merge($attributesCode, [$code]));
699 }
700 }
701
702 $this->code = $code;
703
704 return $this->code;
705 }
706
707 /**
708 * Get PHP native built in types.
709 *
710 * @return array
711 */
712 protected static function getBuiltinTypes()
713 {
714 // PHP 8.1
715 if (PHP_VERSION_ID >= 80100) {
716 return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object', 'mixed', 'false', 'null', 'never'];
717 }
718
719 // PHP 8
720 if (\PHP_MAJOR_VERSION === 8) {
721 return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object', 'mixed', 'false', 'null'];
722 }
723
724 // PHP 7
725 switch (\PHP_MINOR_VERSION) {
726 case 0:
727 return ['array', 'callable', 'string', 'int', 'bool', 'float'];
728 case 1:
729 return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void'];
730 default:
731 return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object'];
732 }
733 }
734
735 /**
736 * Gets the use variables by the closure.
737 *
738 * @return array
739 */
740 public function getUseVariables()
741 {
742 if ($this->useVariables !== null) {
743 return $this->useVariables;
744 }
745
746 $tokens = $this->getTokens();
747 $use = [];
748 $state = 'start';
749
750 foreach ($tokens as &$token) {
751 $is_array = is_array($token);
752
753 switch ($state) {
754 case 'start':
755 if ($is_array && $token[0] === T_USE) {
756 $state = 'use';
757 }
758 break;
759 case 'use':
760 if ($is_array) {
761 if ($token[0] === T_VARIABLE) {
762 $use[] = substr($token[1], 1);
763 }
764 } elseif ($token == ')') {
765 break 2;
766 }
767 break;
768 }
769 }
770
771 $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use));
772
773 return $this->useVariables;
774 }
775
776 /**
777 * Checks if binding is required.
778 *
779 * @return bool
780 */
781 public function isBindingRequired()
782 {
783 if ($this->isBindingRequired === null) {
784 $this->getCode();
785 }
786
787 return $this->isBindingRequired;
788 }
789
790 /**
791 * Checks if access to the scope is required.
792 *
793 * @return bool
794 */
795 public function isScopeRequired()
796 {
797 if ($this->isScopeRequired === null) {
798 $this->getCode();
799 }
800
801 return $this->isScopeRequired;
802 }
803
804 /**
805 * The the hash of the current file name.
806 *
807 * @return string
808 */
809 protected function getHashedFileName()
810 {
811 if ($this->hashedName === null) {
812 $this->hashedName = sha1($this->getFileName());
813 }
814
815 return $this->hashedName;
816 }
817
818 /**
819 * Get the file tokens.
820 *
821 * @return array
822 */
823 protected function getFileTokens()
824 {
825 $key = $this->getHashedFileName();
826
827 if (! isset(static::$files[$key])) {
828 static::$files[$key] = token_get_all(file_get_contents($this->getFileName()));
829 }
830
831 return static::$files[$key];
832 }
833
834 /**
835 * Get the tokens.
836 *
837 * @return array
838 */
839 protected function getTokens()
840 {
841 if ($this->tokens === null) {
842 $tokens = $this->getFileTokens();
843 $startLine = $this->getStartLine();
844 $endLine = $this->getEndLine();
845 $results = [];
846 $start = false;
847
848 foreach ($tokens as &$token) {
849 if (! is_array($token)) {
850 if ($start) {
851 $results[] = $token;
852 }
853
854 continue;
855 }
856
857 $line = $token[2];
858
859 if ($line <= $endLine) {
860 if ($line >= $startLine) {
861 $start = true;
862 $results[] = $token;
863 }
864
865 continue;
866 }
867
868 break;
869 }
870
871 $this->tokens = $results;
872 }
873
874 return $this->tokens;
875 }
876
877 /**
878 * Get the classes.
879 *
880 * @return array
881 */
882 protected function getClasses()
883 {
884 $key = $this->getHashedFileName();
885
886 if (! isset(static::$classes[$key])) {
887 $this->fetchItems();
888 }
889
890 return static::$classes[$key];
891 }
892
893 /**
894 * Get the functions.
895 *
896 * @return array
897 */
898 protected function getFunctions()
899 {
900 $key = $this->getHashedFileName();
901
902 if (! isset(static::$functions[$key])) {
903 $this->fetchItems();
904 }
905
906 return static::$functions[$key];
907 }
908
909 /**
910 * Gets the constants.
911 *
912 * @return array
913 */
914 protected function getConstants()
915 {
916 $key = $this->getHashedFileName();
917
918 if (! isset(static::$constants[$key])) {
919 $this->fetchItems();
920 }
921
922 return static::$constants[$key];
923 }
924
925 /**
926 * Get the structures.
927 *
928 * @return array
929 */
930 protected function getStructures()
931 {
932 $key = $this->getHashedFileName();
933
934 if (! isset(static::$structures[$key])) {
935 $this->fetchItems();
936 }
937
938 return static::$structures[$key];
939 }
940
941 /**
942 * Fetch the items.
943 *
944 * @return void.
945 */
946 protected function fetchItems()
947 {
948 $key = $this->getHashedFileName();
949
950 $classes = [];
951 $functions = [];
952 $constants = [];
953 $structures = [];
954 $tokens = $this->getFileTokens();
955
956 $open = 0;
957 $state = 'start';
958 $lastState = '';
959 $prefix = '';
960 $name = '';
961 $alias = '';
962 $isFunc = $isConst = false;
963
964 $startLine = $endLine = 0;
965 $structType = $structName = '';
966 $structIgnore = false;
967
968 foreach ($tokens as $token) {
969 switch ($state) {
970 case 'start':
971 switch ($token[0]) {
972 case T_CLASS:
973 case T_INTERFACE:
974 case T_TRAIT:
975 $state = 'before_structure';
976 $startLine = $token[2];
977 $structType = $token[0] == T_CLASS
978 ? 'class'
979 : ($token[0] == T_INTERFACE ? 'interface' : 'trait');
980 break;
981 case T_USE:
982 $state = 'use';
983 $prefix = $name = $alias = '';
984 $isFunc = $isConst = false;
985 break;
986 case T_FUNCTION:
987 $state = 'structure';
988 $structIgnore = true;
989 break;
990 case T_NEW:
991 $state = 'new';
992 break;
993 case T_OBJECT_OPERATOR:
994 case T_DOUBLE_COLON:
995 $state = 'invoke';
996 break;
997 }
998 break;
999 case 'use':
1000 switch ($token[0]) {
1001 case T_FUNCTION:
1002 $isFunc = true;
1003 break;
1004 case T_CONST:
1005 $isConst = true;
1006 break;
1007 case T_NS_SEPARATOR:
1008 $name .= $token[1];
1009 break;
1010 case T_STRING:
1011 $name .= $token[1];
1012 $alias = $token[1];
1013 break;
1014 case T_NAME_QUALIFIED:
1015 $name .= $token[1];
1016 $pieces = explode('\\', $token[1]);
1017 $alias = end($pieces);
1018 break;
1019 case T_AS:
1020 $lastState = 'use';
1021 $state = 'alias';
1022 break;
1023 case '{':
1024 $prefix = $name;
1025 $name = $alias = '';
1026 $state = 'use-group';
1027 break;
1028 case ',':
1029 case ';':
1030 if ($name === '' || $name[0] !== '\\') {
1031 $name = '\\'.$name;
1032 }
1033
1034 if ($alias !== '') {
1035 if ($isFunc) {
1036 $functions[strtolower($alias)] = $name;
1037 } elseif ($isConst) {
1038 $constants[$alias] = $name;
1039 } else {
1040 $classes[strtolower($alias)] = $name;
1041 }
1042 }
1043 $name = $alias = '';
1044 $state = $token === ';' ? 'start' : 'use';
1045 break;
1046 }
1047 break;
1048 case 'use-group':
1049 switch ($token[0]) {
1050 case T_NS_SEPARATOR:
1051 $name .= $token[1];
1052 break;
1053 case T_NAME_QUALIFIED:
1054 $name .= $token[1];
1055 $pieces = explode('\\', $token[1]);
1056 $alias = end($pieces);
1057 break;
1058 case T_STRING:
1059 $name .= $token[1];
1060 $alias = $token[1];
1061 break;
1062 case T_AS:
1063 $lastState = 'use-group';
1064 $state = 'alias';
1065 break;
1066 case ',':
1067 case '}':
1068
1069 if ($prefix === '' || $prefix[0] !== '\\') {
1070 $prefix = '\\'.$prefix;
1071 }
1072
1073 if ($alias !== '') {
1074 if ($isFunc) {
1075 $functions[strtolower($alias)] = $prefix.$name;
1076 } elseif ($isConst) {
1077 $constants[$alias] = $prefix.$name;
1078 } else {
1079 $classes[strtolower($alias)] = $prefix.$name;
1080 }
1081 }
1082 $name = $alias = '';
1083 $state = $token === '}' ? 'use' : 'use-group';
1084 break;
1085 }
1086 break;
1087 case 'alias':
1088 if ($token[0] === T_STRING) {
1089 $alias = $token[1];
1090 $state = $lastState;
1091 }
1092 break;
1093 case 'new':
1094 switch ($token[0]) {
1095 case T_WHITESPACE:
1096 case T_COMMENT:
1097 case T_DOC_COMMENT:
1098 break 2;
1099 case T_CLASS:
1100 $state = 'structure';
1101 $structIgnore = true;
1102 break;
1103 default:
1104 $state = 'start';
1105 }
1106 break;
1107 case 'invoke':
1108 switch ($token[0]) {
1109 case T_WHITESPACE:
1110 case T_COMMENT:
1111 case T_DOC_COMMENT:
1112 break 2;
1113 default:
1114 $state = 'start';
1115 }
1116 break;
1117 case 'before_structure':
1118 if ($token[0] == T_STRING) {
1119 $structName = $token[1];
1120 $state = 'structure';
1121 }
1122 break;
1123 case 'structure':
1124 switch ($token[0]) {
1125 case '{':
1126 case T_CURLY_OPEN:
1127 case T_DOLLAR_OPEN_CURLY_BRACES:
1128 $open++;
1129 break;
1130 case '}':
1131 if (--$open == 0) {
1132 if (! $structIgnore) {
1133 $structures[] = [
1134 'type' => $structType,
1135 'name' => $structName,
1136 'start' => $startLine,
1137 'end' => $endLine,
1138 ];
1139 }
1140 $structIgnore = false;
1141 $state = 'start';
1142 }
1143 break;
1144 default:
1145 if (is_array($token)) {
1146 $endLine = $token[2];
1147 }
1148 }
1149 break;
1150 }
1151 }
1152
1153 static::$classes[$key] = $classes;
1154 static::$functions[$key] = $functions;
1155 static::$constants[$key] = $constants;
1156 static::$structures[$key] = $structures;
1157 }
1158
1159 /**
1160 * Returns the namespace associated to the closure.
1161 *
1162 * @return string
1163 */
1164 protected function getClosureNamespaceName()
1165 {
1166 $ns = $this->getNamespaceName();
1167
1168 // First class callables...
1169 if ($this->getName() !== '{closure}' && empty($ns) && ! is_null($this->getClosureScopeClass())) {
1170 $ns = $this->getClosureScopeClass()->getNamespaceName();
1171 }
1172
1173 return $ns;
1174 }
1175
1176 /**
1177 * Parse the given token.
1178 *
1179 * @param string $token
1180 * @return array
1181 */
1182 protected function parseNameQualified($token)
1183 {
1184 $pieces = explode('\\', $token);
1185
1186 $id_start = array_shift($pieces);
1187
1188 $id_start_ci = strtolower($id_start);
1189
1190 $id_name = '\\'.implode('\\', $pieces);
1191
1192 return [$id_start, $id_start_ci, $id_name];
1193 }
1194 }
1195