PluginProbe
WPIDE – File Manager & Code Editor / 3.5.9
WPIDE – File Manager & Code Editor v3.5.9
3.5.9 3.5.8 3.5.7 2.0.14 2.0.15 2.0.16 2.0.2 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1 2.2 2.3 2.3.1 2.3.2 2.4.0 2.5 2.6 3.0 3.1 3.2 3.3 All 55 releases
wpide / vendor / laravel / serializable-closure / src / Support / ReflectionClosure.php

ReflectionClosure.php in WPIDE – File Manager & Code Editor 3.5.9, at vendor/laravel/serializable-closure/src/Support/ReflectionClosure.php

1,199 lines 44.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace 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 case $token[0] === ':' && $context !== 'instanceof':
512 if ($lastState === 'closure' && $context === 'root') {
513 $state = 'closure';
514 $code .= $id_start.$token;
515 }
516
517 break;
518 case T_NAME_QUALIFIED:
519 case T_NS_SEPARATOR:
520 case T_STRING:
521 case T_WHITESPACE:
522 case T_COMMENT:
523 case T_DOC_COMMENT:
524 $id_name .= $token[1];
525 break;
526 case '(':
527 if ($isShortClosure) {
528 $open++;
529 }
530 if ($context === 'new' || false !== strpos($id_name, '\\')) {
531 if ($id_start_ci === 'self' || $id_start_ci === 'static') {
532 if (! $inside_structure) {
533 $isUsingScope = true;
534 }
535 } elseif ($id_start !== '\\' && ! in_array($id_start_ci, $class_keywords)) {
536 if ($classes === null) {
537 $classes = $this->getClasses();
538 }
539 if (isset($classes[$id_start_ci])) {
540 $id_start = $classes[$id_start_ci];
541 }
542 if ($id_start[0] !== '\\') {
543 $id_start = $nsf.'\\'.$id_start;
544 }
545 }
546 } else {
547 if ($id_start !== '\\') {
548 if ($functions === null) {
549 $functions = $this->getFunctions();
550 }
551 if (isset($functions[$id_start_ci])) {
552 $id_start = $functions[$id_start_ci];
553 } elseif ($nsf !== '\\' && function_exists($nsf.'\\'.$id_start)) {
554 $id_start = $nsf.'\\'.$id_start;
555 // Cache it to functions array
556 $functions[$id_start_ci] = $id_start;
557 }
558 }
559 }
560 $code .= $id_start.$id_name.'(';
561 $state = $lastState;
562 break;
563 case T_VARIABLE:
564 case T_DOUBLE_COLON:
565 if ($id_start !== '\\') {
566 if ($id_start_ci === 'self' || $id_start_ci === 'parent') {
567 if (! $inside_structure) {
568 $isUsingScope = true;
569 }
570 } elseif ($id_start_ci === 'static') {
571 if (! $inside_structure) {
572 $isUsingScope = $token[0] === T_DOUBLE_COLON;
573 }
574 } elseif (! (\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))) {
575 if ($classes === null) {
576 $classes = $this->getClasses();
577 }
578 if (isset($classes[$id_start_ci])) {
579 $id_start = $classes[$id_start_ci];
580 }
581 if ($id_start[0] !== '\\') {
582 $id_start = $nsf.'\\'.$id_start;
583 }
584 }
585 }
586
587 $code .= $id_start.$id_name.$token[1];
588 $state = $token[0] === T_DOUBLE_COLON ? 'ignore_next' : $lastState;
589 break;
590 default:
591 if ($id_start !== '\\' && ! defined($id_start)) {
592 if ($constants === null) {
593 $constants = $this->getConstants();
594 }
595 if (isset($constants[$id_start])) {
596 $id_start = $constants[$id_start];
597 } elseif ($context === 'new') {
598 if (in_array($id_start_ci, $class_keywords)) {
599 if (! $inside_structure) {
600 $isUsingScope = true;
601 }
602 } else {
603 if ($classes === null) {
604 $classes = $this->getClasses();
605 }
606 if (isset($classes[$id_start_ci])) {
607 $id_start = $classes[$id_start_ci];
608 }
609 if ($id_start[0] !== '\\') {
610 $id_start = $nsf.'\\'.$id_start;
611 }
612 }
613 } elseif ($context === 'use' ||
614 $context === 'instanceof' ||
615 $context === 'args' ||
616 $context === 'return_type' ||
617 $context === 'extends' ||
618 $context === 'root'
619 ) {
620 if (in_array($id_start_ci, $class_keywords)) {
621 if (! $inside_structure && ! $id_start_ci === 'static') {
622 $isUsingScope = true;
623 }
624 } elseif (! (\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))) {
625 if ($classes === null) {
626 $classes = $this->getClasses();
627 }
628 if (isset($classes[$id_start_ci])) {
629 $id_start = $classes[$id_start_ci];
630 }
631 if ($id_start[0] !== '\\') {
632 $id_start = $nsf.'\\'.$id_start;
633 }
634 }
635 }
636 }
637 $code .= $id_start.$id_name;
638 $state = $lastState;
639 $i--; //reprocess last token
640 }
641 break;
642 case 'anonymous':
643 switch ($token[0]) {
644 case T_NAME_QUALIFIED:
645 [$id_start, $id_start_ci, $id_name] = $this->parseNameQualified($token[1]);
646 $state = 'id_name';
647 $lastState = 'anonymous';
648 break 2;
649 case T_NS_SEPARATOR:
650 case T_STRING:
651 $id_start = $token[1];
652 $id_start_ci = strtolower($id_start);
653 $id_name = '';
654 $state = 'id_name';
655 $context = 'extends';
656 $lastState = 'anonymous';
657 break;
658 case '{':
659 $state = 'closure';
660 if (! $inside_structure) {
661 $inside_structure = true;
662 $inside_structure_mark = $open;
663 }
664 $i--;
665 break;
666 default:
667 $code .= is_array($token) ? $token[1] : $token;
668 }
669 break;
670 }
671 }
672
673 if ($isShortClosure) {
674 $this->useVariables = $this->getStaticVariables();
675 } else {
676 $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use));
677 }
678
679 $this->isShortClosure = $isShortClosure;
680 $this->isBindingRequired = $isUsingThisObject;
681 $this->isScopeRequired = $isUsingScope;
682
683 if (PHP_VERSION_ID >= 80100) {
684 $attributesCode = array_map(function ($attribute) {
685 $arguments = $attribute->getArguments();
686
687 $name = $attribute->getName();
688 $arguments = implode(', ', array_map(function ($argument, $key) {
689 $argument = sprintf("'%s'", str_replace("'", "\\'", $argument));
690
691 if (is_string($key)) {
692 $argument = sprintf('%s: %s', $key, $argument);
693 }
694
695 return $argument;
696 }, $arguments, array_keys($arguments)));
697
698 return "#[$name($arguments)]";
699 }, $this->getAttributes());
700
701 if (! empty($attributesCode)) {
702 $code = implode("\n", array_merge($attributesCode, [$code]));
703 }
704 }
705
706 $this->code = $code;
707
708 return $this->code;
709 }
710
711 /**
712 * Get PHP native built in types.
713 *
714 * @return array
715 */
716 protected static function getBuiltinTypes()
717 {
718 // PHP 8.1
719 if (PHP_VERSION_ID >= 80100) {
720 return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object', 'mixed', 'false', 'null', 'never'];
721 }
722
723 // PHP 8
724 if (\PHP_MAJOR_VERSION === 8) {
725 return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object', 'mixed', 'false', 'null'];
726 }
727
728 // PHP 7
729 switch (\PHP_MINOR_VERSION) {
730 case 0:
731 return ['array', 'callable', 'string', 'int', 'bool', 'float'];
732 case 1:
733 return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void'];
734 default:
735 return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object'];
736 }
737 }
738
739 /**
740 * Gets the use variables by the closure.
741 *
742 * @return array
743 */
744 public function getUseVariables()
745 {
746 if ($this->useVariables !== null) {
747 return $this->useVariables;
748 }
749
750 $tokens = $this->getTokens();
751 $use = [];
752 $state = 'start';
753
754 foreach ($tokens as &$token) {
755 $is_array = is_array($token);
756
757 switch ($state) {
758 case 'start':
759 if ($is_array && $token[0] === T_USE) {
760 $state = 'use';
761 }
762 break;
763 case 'use':
764 if ($is_array) {
765 if ($token[0] === T_VARIABLE) {
766 $use[] = substr($token[1], 1);
767 }
768 } elseif ($token == ')') {
769 break 2;
770 }
771 break;
772 }
773 }
774
775 $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use));
776
777 return $this->useVariables;
778 }
779
780 /**
781 * Checks if binding is required.
782 *
783 * @return bool
784 */
785 public function isBindingRequired()
786 {
787 if ($this->isBindingRequired === null) {
788 $this->getCode();
789 }
790
791 return $this->isBindingRequired;
792 }
793
794 /**
795 * Checks if access to the scope is required.
796 *
797 * @return bool
798 */
799 public function isScopeRequired()
800 {
801 if ($this->isScopeRequired === null) {
802 $this->getCode();
803 }
804
805 return $this->isScopeRequired;
806 }
807
808 /**
809 * The hash of the current file name.
810 *
811 * @return string
812 */
813 protected function getHashedFileName()
814 {
815 if ($this->hashedName === null) {
816 $this->hashedName = sha1($this->getFileName());
817 }
818
819 return $this->hashedName;
820 }
821
822 /**
823 * Get the file tokens.
824 *
825 * @return array
826 */
827 protected function getFileTokens()
828 {
829 $key = $this->getHashedFileName();
830
831 if (! isset(static::$files[$key])) {
832 static::$files[$key] = token_get_all(file_get_contents($this->getFileName()));
833 }
834
835 return static::$files[$key];
836 }
837
838 /**
839 * Get the tokens.
840 *
841 * @return array
842 */
843 protected function getTokens()
844 {
845 if ($this->tokens === null) {
846 $tokens = $this->getFileTokens();
847 $startLine = $this->getStartLine();
848 $endLine = $this->getEndLine();
849 $results = [];
850 $start = false;
851
852 foreach ($tokens as &$token) {
853 if (! is_array($token)) {
854 if ($start) {
855 $results[] = $token;
856 }
857
858 continue;
859 }
860
861 $line = $token[2];
862
863 if ($line <= $endLine) {
864 if ($line >= $startLine) {
865 $start = true;
866 $results[] = $token;
867 }
868
869 continue;
870 }
871
872 break;
873 }
874
875 $this->tokens = $results;
876 }
877
878 return $this->tokens;
879 }
880
881 /**
882 * Get the classes.
883 *
884 * @return array
885 */
886 protected function getClasses()
887 {
888 $key = $this->getHashedFileName();
889
890 if (! isset(static::$classes[$key])) {
891 $this->fetchItems();
892 }
893
894 return static::$classes[$key];
895 }
896
897 /**
898 * Get the functions.
899 *
900 * @return array
901 */
902 protected function getFunctions()
903 {
904 $key = $this->getHashedFileName();
905
906 if (! isset(static::$functions[$key])) {
907 $this->fetchItems();
908 }
909
910 return static::$functions[$key];
911 }
912
913 /**
914 * Gets the constants.
915 *
916 * @return array
917 */
918 protected function getConstants()
919 {
920 $key = $this->getHashedFileName();
921
922 if (! isset(static::$constants[$key])) {
923 $this->fetchItems();
924 }
925
926 return static::$constants[$key];
927 }
928
929 /**
930 * Get the structures.
931 *
932 * @return array
933 */
934 protected function getStructures()
935 {
936 $key = $this->getHashedFileName();
937
938 if (! isset(static::$structures[$key])) {
939 $this->fetchItems();
940 }
941
942 return static::$structures[$key];
943 }
944
945 /**
946 * Fetch the items.
947 *
948 * @return void.
949 */
950 protected function fetchItems()
951 {
952 $key = $this->getHashedFileName();
953
954 $classes = [];
955 $functions = [];
956 $constants = [];
957 $structures = [];
958 $tokens = $this->getFileTokens();
959
960 $open = 0;
961 $state = 'start';
962 $lastState = '';
963 $prefix = '';
964 $name = '';
965 $alias = '';
966 $isFunc = $isConst = false;
967
968 $startLine = $endLine = 0;
969 $structType = $structName = '';
970 $structIgnore = false;
971
972 foreach ($tokens as $token) {
973 switch ($state) {
974 case 'start':
975 switch ($token[0]) {
976 case T_CLASS:
977 case T_INTERFACE:
978 case T_TRAIT:
979 $state = 'before_structure';
980 $startLine = $token[2];
981 $structType = $token[0] == T_CLASS
982 ? 'class'
983 : ($token[0] == T_INTERFACE ? 'interface' : 'trait');
984 break;
985 case T_USE:
986 $state = 'use';
987 $prefix = $name = $alias = '';
988 $isFunc = $isConst = false;
989 break;
990 case T_FUNCTION:
991 $state = 'structure';
992 $structIgnore = true;
993 break;
994 case T_NEW:
995 $state = 'new';
996 break;
997 case T_OBJECT_OPERATOR:
998 case T_DOUBLE_COLON:
999 $state = 'invoke';
1000 break;
1001 }
1002 break;
1003 case 'use':
1004 switch ($token[0]) {
1005 case T_FUNCTION:
1006 $isFunc = true;
1007 break;
1008 case T_CONST:
1009 $isConst = true;
1010 break;
1011 case T_NS_SEPARATOR:
1012 $name .= $token[1];
1013 break;
1014 case T_STRING:
1015 $name .= $token[1];
1016 $alias = $token[1];
1017 break;
1018 case T_NAME_QUALIFIED:
1019 $name .= $token[1];
1020 $pieces = explode('\\', $token[1]);
1021 $alias = end($pieces);
1022 break;
1023 case T_AS:
1024 $lastState = 'use';
1025 $state = 'alias';
1026 break;
1027 case '{':
1028 $prefix = $name;
1029 $name = $alias = '';
1030 $state = 'use-group';
1031 break;
1032 case ',':
1033 case ';':
1034 if ($name === '' || $name[0] !== '\\') {
1035 $name = '\\'.$name;
1036 }
1037
1038 if ($alias !== '') {
1039 if ($isFunc) {
1040 $functions[strtolower($alias)] = $name;
1041 } elseif ($isConst) {
1042 $constants[$alias] = $name;
1043 } else {
1044 $classes[strtolower($alias)] = $name;
1045 }
1046 }
1047 $name = $alias = '';
1048 $state = $token === ';' ? 'start' : 'use';
1049 break;
1050 }
1051 break;
1052 case 'use-group':
1053 switch ($token[0]) {
1054 case T_NS_SEPARATOR:
1055 $name .= $token[1];
1056 break;
1057 case T_NAME_QUALIFIED:
1058 $name .= $token[1];
1059 $pieces = explode('\\', $token[1]);
1060 $alias = end($pieces);
1061 break;
1062 case T_STRING:
1063 $name .= $token[1];
1064 $alias = $token[1];
1065 break;
1066 case T_AS:
1067 $lastState = 'use-group';
1068 $state = 'alias';
1069 break;
1070 case ',':
1071 case '}':
1072
1073 if ($prefix === '' || $prefix[0] !== '\\') {
1074 $prefix = '\\'.$prefix;
1075 }
1076
1077 if ($alias !== '') {
1078 if ($isFunc) {
1079 $functions[strtolower($alias)] = $prefix.$name;
1080 } elseif ($isConst) {
1081 $constants[$alias] = $prefix.$name;
1082 } else {
1083 $classes[strtolower($alias)] = $prefix.$name;
1084 }
1085 }
1086 $name = $alias = '';
1087 $state = $token === '}' ? 'use' : 'use-group';
1088 break;
1089 }
1090 break;
1091 case 'alias':
1092 if ($token[0] === T_STRING) {
1093 $alias = $token[1];
1094 $state = $lastState;
1095 }
1096 break;
1097 case 'new':
1098 switch ($token[0]) {
1099 case T_WHITESPACE:
1100 case T_COMMENT:
1101 case T_DOC_COMMENT:
1102 break 2;
1103 case T_CLASS:
1104 $state = 'structure';
1105 $structIgnore = true;
1106 break;
1107 default:
1108 $state = 'start';
1109 }
1110 break;
1111 case 'invoke':
1112 switch ($token[0]) {
1113 case T_WHITESPACE:
1114 case T_COMMENT:
1115 case T_DOC_COMMENT:
1116 break 2;
1117 default:
1118 $state = 'start';
1119 }
1120 break;
1121 case 'before_structure':
1122 if ($token[0] == T_STRING) {
1123 $structName = $token[1];
1124 $state = 'structure';
1125 }
1126 break;
1127 case 'structure':
1128 switch ($token[0]) {
1129 case '{':
1130 case T_CURLY_OPEN:
1131 case T_DOLLAR_OPEN_CURLY_BRACES:
1132 $open++;
1133 break;
1134 case '}':
1135 if (--$open == 0) {
1136 if (! $structIgnore) {
1137 $structures[] = [
1138 'type' => $structType,
1139 'name' => $structName,
1140 'start' => $startLine,
1141 'end' => $endLine,
1142 ];
1143 }
1144 $structIgnore = false;
1145 $state = 'start';
1146 }
1147 break;
1148 default:
1149 if (is_array($token)) {
1150 $endLine = $token[2];
1151 }
1152 }
1153 break;
1154 }
1155 }
1156
1157 static::$classes[$key] = $classes;
1158 static::$functions[$key] = $functions;
1159 static::$constants[$key] = $constants;
1160 static::$structures[$key] = $structures;
1161 }
1162
1163 /**
1164 * Returns the namespace associated to the closure.
1165 *
1166 * @return string
1167 */
1168 protected function getClosureNamespaceName()
1169 {
1170 $ns = $this->getNamespaceName();
1171
1172 // First class callables...
1173 if ($this->getName() !== '{closure}' && empty($ns) && ! is_null($this->getClosureScopeClass())) {
1174 $ns = $this->getClosureScopeClass()->getNamespaceName();
1175 }
1176
1177 return $ns;
1178 }
1179
1180 /**
1181 * Parse the given token.
1182 *
1183 * @param string $token
1184 * @return array
1185 */
1186 protected function parseNameQualified($token)
1187 {
1188 $pieces = explode('\\', $token);
1189
1190 $id_start = array_shift($pieces);
1191
1192 $id_start_ci = strtolower($id_start);
1193
1194 $id_name = '\\'.implode('\\', $pieces);
1195
1196 return [$id_start, $id_start_ci, $id_name];
1197 }
1198 }
1199