Proyectos de Subversion Moodle

Rev

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