Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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