Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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