PluginProbe
The Innovative Form Builder – IvyForms / 0.1.2
The Innovative Form Builder – IvyForms v0.1.2
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / backend / vendor / laravel / serializable-closure / src / Support / ReflectionClosure.php

ReflectionClosure.php in The Innovative Form Builder – IvyForms 0.1.2, at backend/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