1<?php 2/* =========================================================================== 3 * Copyright (c) 2018-2021 Zindex Software 4 * 5 * Licensed under the MIT License 6 * =========================================================================== */ 7 8namespace Opis\Closure; 9 10defined('T_NAME_QUALIFIED') || define('T_NAME_QUALIFIED', -4); 11defined('T_NAME_FULLY_QUALIFIED') || define('T_NAME_FULLY_QUALIFIED', -5); 12defined('T_FN') || define('T_FN', -6); 13 14use Closure; 15use ReflectionFunction; 16 17class ReflectionClosure extends ReflectionFunction 18{ 19 protected $code; 20 protected $tokens; 21 protected $hashedName; 22 protected $useVariables; 23 protected $isStaticClosure; 24 protected $isScopeRequired; 25 protected $isBindingRequired; 26 protected $isShortClosure; 27 28 protected static $files = array(); 29 protected static $classes = array(); 30 protected static $functions = array(); 31 protected static $constants = array(); 32 protected static $structures = array(); 33 34 35 /** 36 * ReflectionClosure constructor. 37 * @param Closure $closure 38 * @param string|null $code This is ignored. Do not use it 39 * @throws \ReflectionException 40 */ 41 public function __construct(Closure $closure, $code = null) 42 { 43 parent::__construct($closure); 44 } 45 46 /** 47 * @return bool 48 */ 49 public function isStatic() 50 { 51 if ($this->isStaticClosure === null) { 52 $this->isStaticClosure = strtolower(substr($this->getCode(), 0, 6)) === 'static'; 53 } 54 55 return $this->isStaticClosure; 56 } 57 58 public function isShortClosure() 59 { 60 if ($this->isShortClosure === null) { 61 $code = $this->getCode(); 62 if ($this->isStatic()) { 63 $code = substr($code, 6); 64 } 65 $this->isShortClosure = strtolower(substr(trim($code), 0, 2)) === 'fn'; 66 } 67 68 return $this->isShortClosure; 69 } 70 71 /** 72 * @return string 73 */ 74 public function getCode() 75 { 76 if($this->code !== null){ 77 return $this->code; 78 } 79 80 $fileName = $this->getFileName(); 81 $line = $this->getStartLine() - 1; 82 83 $className = null; 84 85 if (null !== $className = $this->getClosureScopeClass()) { 86 $className = '\\' . trim($className->getName(), '\\'); 87 } 88 89 $builtin_types = self::getBuiltinTypes(); 90 $class_keywords = ['self', 'static', 'parent']; 91 92 $ns = $this->getNamespaceName(); 93 $nsf = $ns == '' ? '' : ($ns[0] == '\\' ? $ns : '\\' . $ns); 94 95 $_file = var_export($fileName, true); 96 $_dir = var_export(dirname($fileName), true); 97 $_namespace = var_export($ns, true); 98 $_class = var_export(trim($className, '\\'), true); 99 $_function = $ns . ($ns == '' ? '' : '\\') . '{closure}'; 100 $_method = ($className == '' ? '' : trim($className, '\\') . '::') . $_function; 101 $_function = var_export($_function, true); 102 $_method = var_export($_method, true); 103 $_trait = null; 104 105 $tokens = $this->getTokens(); 106 $state = $lastState = 'start'; 107 $inside_structure = false; 108 $isShortClosure = false; 109 $inside_structure_mark = 0; 110 $open = 0; 111 $code = ''; 112 $id_start = $id_start_ci = $id_name = $context = ''; 113 $classes = $functions = $constants = null; 114 $use = array(); 115 $lineAdd = 0; 116 $isUsingScope = false; 117 $isUsingThisObject = false; 118 119 for($i = 0, $l = count($tokens); $i < $l; $i++) { 120 $token = $tokens[$i]; 121 switch ($state) { 122 case 'start': 123 if ($token[0] === T_FUNCTION || $token[0] === T_STATIC) { 124 $code .= $token[1]; 125 $state = $token[0] === T_FUNCTION ? 'function' : 'static'; 126 } elseif ($token[0] === T_FN) { 127 $isShortClosure = true; 128 $code .= $token[1]; 129 $state = 'closure_args'; 130 } 131 break; 132 case 'static': 133 if ($token[0] === T_WHITESPACE || $token[0] === T_COMMENT || $token[0] === T_FUNCTION) { 134 $code .= $token[1]; 135 if ($token[0] === T_FUNCTION) { 136 $state = 'function'; 137 } 138 } elseif ($token[0] === T_FN) { 139 $isShortClosure = true; 140 $code .= $token[1]; 141 $state = 'closure_args'; 142 } else { 143 $code = ''; 144 $state = 'start'; 145 } 146 break; 147 case 'function': 148 switch ($token[0]){ 149 case T_STRING: 150 $code = ''; 151 $state = 'named_function'; 152 break; 153 case '(': 154 $code .= '('; 155 $state = 'closure_args'; 156 break; 157 default: 158 $code .= is_array($token) ? $token[1] : $token; 159 } 160 break; 161 case 'named_function': 162 if($token[0] === T_FUNCTION || $token[0] === T_STATIC){ 163 $code = $token[1]; 164 $state = $token[0] === T_FUNCTION ? 'function' : 'static'; 165 } elseif ($token[0] === T_FN) { 166 $isShortClosure = true; 167 $code .= $token[1]; 168 $state = 'closure_args'; 169 } 170 break; 171 case 'closure_args': 172 switch ($token[0]){ 173 case T_NAME_QUALIFIED: 174 list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]); 175 $context = 'args'; 176 $state = 'id_name'; 177 $lastState = 'closure_args'; 178 break; 179 case T_NS_SEPARATOR: 180 case T_STRING: 181 $id_start = $token[1]; 182 $id_start_ci = strtolower($id_start); 183 $id_name = ''; 184 $context = 'args'; 185 $state = 'id_name'; 186 $lastState = 'closure_args'; 187 break; 188 case T_USE: 189 $code .= $token[1]; 190 $state = 'use'; 191 break; 192 case T_DOUBLE_ARROW: 193 $code .= $token[1]; 194 if ($isShortClosure) { 195 $state = 'closure'; 196 } 197 break; 198 case ':': 199 $code .= ':'; 200 $state = 'return'; 201 break; 202 case '{': 203 $code .= '{'; 204 $state = 'closure'; 205 $open++; 206 break; 207 default: 208 $code .= is_array($token) ? $token[1] : $token; 209 } 210 break; 211 case 'use': 212 switch ($token[0]){ 213 case T_VARIABLE: 214 $use[] = substr($token[1], 1); 215 $code .= $token[1]; 216 break; 217 case '{': 218 $code .= '{'; 219 $state = 'closure'; 220 $open++; 221 break; 222 case ':': 223 $code .= ':'; 224 $state = 'return'; 225 break; 226 default: 227 $code .= is_array($token) ? $token[1] : $token; 228 break; 229 } 230 break; 231 case 'return': 232 switch ($token[0]){ 233 case T_WHITESPACE: 234 case T_COMMENT: 235 case T_DOC_COMMENT: 236 $code .= $token[1]; 237 break; 238 case T_NS_SEPARATOR: 239 case T_STRING: 240 $id_start = $token[1]; 241 $id_start_ci = strtolower($id_start); 242 $id_name = ''; 243 $context = 'return_type'; 244 $state = 'id_name'; 245 $lastState = 'return'; 246 break 2; 247 case T_NAME_QUALIFIED: 248 list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]); 249 $context = 'return_type'; 250 $state = 'id_name'; 251 $lastState = 'return'; 252 break 2; 253 case T_DOUBLE_ARROW: 254 $code .= $token[1]; 255 if ($isShortClosure) { 256 $state = 'closure'; 257 } 258 break; 259 case '{': 260 $code .= '{'; 261 $state = 'closure'; 262 $open++; 263 break; 264 default: 265 $code .= is_array($token) ? $token[1] : $token; 266 break; 267 } 268 break; 269 case 'closure': 270 switch ($token[0]){ 271 case T_CURLY_OPEN: 272 case T_DOLLAR_OPEN_CURLY_BRACES: 273 case '{': 274 $code .= is_array($token) ? $token[1] : $token; 275 $open++; 276 break; 277 case '}': 278 $code .= '}'; 279 if(--$open === 0 && !$isShortClosure){ 280 break 3; 281 } elseif ($inside_structure) { 282 $inside_structure = !($open === $inside_structure_mark); 283 } 284 break; 285 case '(': 286 case '[': 287 $code .= $token[0]; 288 if ($isShortClosure) { 289 $open++; 290 } 291 break; 292 case ')': 293 case ']': 294 if ($isShortClosure) { 295 if ($open === 0) { 296 break 3; 297 } 298 --$open; 299 } 300 $code .= $token[0]; 301 break; 302 case ',': 303 case ';': 304 if ($isShortClosure && $open === 0) { 305 break 3; 306 } 307 $code .= $token[0]; 308 break; 309 case T_LINE: 310 $code .= $token[2] - $line + $lineAdd; 311 break; 312 case T_FILE: 313 $code .= $_file; 314 break; 315 case T_DIR: 316 $code .= $_dir; 317 break; 318 case T_NS_C: 319 $code .= $_namespace; 320 break; 321 case T_CLASS_C: 322 $code .= $inside_structure ? $token[1] : $_class; 323 break; 324 case T_FUNC_C: 325 $code .= $inside_structure ? $token[1] : $_function; 326 break; 327 case T_METHOD_C: 328 $code .= $inside_structure ? $token[1] : $_method; 329 break; 330 case T_COMMENT: 331 if (substr($token[1], 0, 8) === '#trackme') { 332 $timestamp = time(); 333 $code .= '/**' . PHP_EOL; 334 $code .= '* Date : ' . date(DATE_W3C, $timestamp) . PHP_EOL; 335 $code .= '* Timestamp : ' . $timestamp . PHP_EOL; 336 $code .= '* Line : ' . ($line + 1) . PHP_EOL; 337 $code .= '* File : ' . $_file . PHP_EOL . '*/' . PHP_EOL; 338 $lineAdd += 5; 339 } else { 340 $code .= $token[1]; 341 } 342 break; 343 case T_VARIABLE: 344 if($token[1] == '$this' && !$inside_structure){ 345 $isUsingThisObject = true; 346 } 347 $code .= $token[1]; 348 break; 349 case T_STATIC: 350 case T_NS_SEPARATOR: 351 case T_STRING: 352 $id_start = $token[1]; 353 $id_start_ci = strtolower($id_start); 354 $id_name = ''; 355 $context = 'root'; 356 $state = 'id_name'; 357 $lastState = 'closure'; 358 break 2; 359 case T_NAME_QUALIFIED: 360 list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]); 361 $context = 'root'; 362 $state = 'id_name'; 363 $lastState = 'closure'; 364 break 2; 365 case T_NEW: 366 $code .= $token[1]; 367 $context = 'new'; 368 $state = 'id_start'; 369 $lastState = 'closure'; 370 break 2; 371 case T_USE: 372 $code .= $token[1]; 373 $context = 'use'; 374 $state = 'id_start'; 375 $lastState = 'closure'; 376 break; 377 case T_INSTANCEOF: 378 case T_INSTEADOF: 379 $code .= $token[1]; 380 $context = 'instanceof'; 381 $state = 'id_start'; 382 $lastState = 'closure'; 383 break; 384 case T_OBJECT_OPERATOR: 385 case T_DOUBLE_COLON: 386 $code .= $token[1]; 387 $lastState = 'closure'; 388 $state = 'ignore_next'; 389 break; 390 case T_FUNCTION: 391 $code .= $token[1]; 392 $state = 'closure_args'; 393 if (!$inside_structure) { 394 $inside_structure = true; 395 $inside_structure_mark = $open; 396 } 397 break; 398 case T_TRAIT_C: 399 if ($_trait === null) { 400 $startLine = $this->getStartLine(); 401 $endLine = $this->getEndLine(); 402 $structures = $this->getStructures(); 403 404 $_trait = ''; 405 406 foreach ($structures as &$struct) { 407 if ($struct['type'] === 'trait' && 408 $struct['start'] <= $startLine && 409 $struct['end'] >= $endLine 410 ) { 411 $_trait = ($ns == '' ? '' : $ns . '\\') . $struct['name']; 412 break; 413 } 414 } 415 416 $_trait = var_export($_trait, true); 417 } 418 419 $code .= $_trait; 420 break; 421 default: 422 $code .= is_array($token) ? $token[1] : $token; 423 } 424 break; 425 case 'ignore_next': 426 switch ($token[0]){ 427 case T_WHITESPACE: 428 case T_COMMENT: 429 case T_DOC_COMMENT: 430 $code .= $token[1]; 431 break; 432 case T_CLASS: 433 case T_NEW: 434 case T_STATIC: 435 case T_VARIABLE: 436 case T_STRING: 437 case T_CLASS_C: 438 case T_FILE: 439 case T_DIR: 440 case T_METHOD_C: 441 case T_FUNC_C: 442 case T_FUNCTION: 443 case T_INSTANCEOF: 444 case T_LINE: 445 case T_NS_C: 446 case T_TRAIT_C: 447 case T_USE: 448 $code .= $token[1]; 449 $state = $lastState; 450 break; 451 default: 452 $state = $lastState; 453 $i--; 454 } 455 break; 456 case 'id_start': 457 switch ($token[0]){ 458 case T_WHITESPACE: 459 case T_COMMENT: 460 case T_DOC_COMMENT: 461 $code .= $token[1]; 462 break; 463 case T_NS_SEPARATOR: 464 case T_NAME_FULLY_QUALIFIED: 465 case T_STRING: 466 case T_STATIC: 467 $id_start = $token[1]; 468 $id_start_ci = strtolower($id_start); 469 $id_name = ''; 470 $state = 'id_name'; 471 break 2; 472 case T_NAME_QUALIFIED: 473 list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]); 474 $state = 'id_name'; 475 break 2; 476 case T_VARIABLE: 477 $code .= $token[1]; 478 $state = $lastState; 479 break; 480 case T_CLASS: 481 $code .= $token[1]; 482 $state = 'anonymous'; 483 break; 484 default: 485 $i--;//reprocess last 486 $state = 'id_name'; 487 } 488 break; 489 case 'id_name': 490 switch ($token[0]){ 491 case T_NAME_QUALIFIED: 492 case T_NS_SEPARATOR: 493 case T_STRING: 494 case T_WHITESPACE: 495 case T_COMMENT: 496 case T_DOC_COMMENT: 497 $id_name .= $token[1]; 498 break; 499 case '(': 500 if ($isShortClosure) { 501 $open++; 502 } 503 if($context === 'new' || false !== strpos($id_name, '\\')){ 504 if($id_start_ci === 'self' || $id_start_ci === 'static') { 505 if (!$inside_structure) { 506 $isUsingScope = true; 507 } 508 } elseif ($id_start !== '\\' && !in_array($id_start_ci, $class_keywords)) { 509 if ($classes === null) { 510 $classes = $this->getClasses(); 511 } 512 if (isset($classes[$id_start_ci])) { 513 $id_start = $classes[$id_start_ci]; 514 } 515 if($id_start[0] !== '\\'){ 516 $id_start = $nsf . '\\' . $id_start; 517 } 518 } 519 } else { 520 if($id_start !== '\\'){ 521 if($functions === null){ 522 $functions = $this->getFunctions(); 523 } 524 if(isset($functions[$id_start_ci])){ 525 $id_start = $functions[$id_start_ci]; 526 } elseif ($nsf !== '\\' && function_exists($nsf . '\\' . $id_start)) { 527 $id_start = $nsf . '\\' . $id_start; 528 // Cache it to functions array 529 $functions[$id_start_ci] = $id_start; 530 } 531 } 532 } 533 $code .= $id_start . $id_name . '('; 534 $state = $lastState; 535 break; 536 case T_VARIABLE: 537 case T_DOUBLE_COLON: 538 if($id_start !== '\\') { 539 if($id_start_ci === 'self' || $id_start_ci === 'parent'){ 540 if (!$inside_structure) { 541 $isUsingScope = true; 542 } 543 } elseif ($id_start_ci === 'static') { 544 if (!$inside_structure) { 545 $isUsingScope = $token[0] === T_DOUBLE_COLON; 546 } 547 } elseif (!(\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))){ 548 if ($classes === null) { 549 $classes = $this->getClasses(); 550 } 551 if (isset($classes[$id_start_ci])) { 552 $id_start = $classes[$id_start_ci]; 553 } 554 if($id_start[0] !== '\\'){ 555 $id_start = $nsf . '\\' . $id_start; 556 } 557 } 558 } 559 560 $code .= $id_start . $id_name . $token[1]; 561 $state = $token[0] === T_DOUBLE_COLON ? 'ignore_next' : $lastState; 562 break; 563 default: 564 if($id_start !== '\\' && !defined($id_start)){ 565 if($constants === null){ 566 $constants = $this->getConstants(); 567 } 568 if(isset($constants[$id_start])){ 569 $id_start = $constants[$id_start]; 570 } elseif($context === 'new'){ 571 if(in_array($id_start_ci, $class_keywords)) { 572 if (!$inside_structure) { 573 $isUsingScope = true; 574 } 575 } else { 576 if ($classes === null) { 577 $classes = $this->getClasses(); 578 } 579 if (isset($classes[$id_start_ci])) { 580 $id_start = $classes[$id_start_ci]; 581 } 582 if ($id_start[0] !== '\\') { 583 $id_start = $nsf . '\\' . $id_start; 584 } 585 } 586 } elseif($context === 'use' || 587 $context === 'instanceof' || 588 $context === 'args' || 589 $context === 'return_type' || 590 $context === 'extends' || 591 $context === 'root' 592 ){ 593 if(in_array($id_start_ci, $class_keywords)){ 594 if (!$inside_structure && !$id_start_ci === 'static') { 595 $isUsingScope = true; 596 } 597 } elseif (!(\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))){ 598 if($classes === null){ 599 $classes = $this->getClasses(); 600 } 601 if(isset($classes[$id_start_ci])){ 602 $id_start = $classes[$id_start_ci]; 603 } 604 if($id_start[0] !== '\\'){ 605 $id_start = $nsf . '\\' . $id_start; 606 } 607 } 608 } 609 } 610 $code .= $id_start . $id_name; 611 $state = $lastState; 612 $i--;//reprocess last token 613 } 614 break; 615 case 'anonymous': 616 switch ($token[0]) { 617 case T_NS_SEPARATOR: 618 case T_STRING: 619 $id_start = $token[1]; 620 $id_start_ci = strtolower($id_start); 621 $id_name = ''; 622 $state = 'id_name'; 623 $context = 'extends'; 624 $lastState = 'anonymous'; 625 break; 626 case '{': 627 $state = 'closure'; 628 if (!$inside_structure) { 629 $inside_structure = true; 630 $inside_structure_mark = $open; 631 } 632 $i--; 633 break; 634 default: 635 $code .= is_array($token) ? $token[1] : $token; 636 } 637 break; 638 } 639 } 640 641 if ($isShortClosure) { 642 $this->useVariables = $this->getStaticVariables(); 643 } else { 644 $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use)); 645 } 646 647 $this->isShortClosure = $isShortClosure; 648 $this->isBindingRequired = $isUsingThisObject; 649 $this->isScopeRequired = $isUsingScope; 650 $this->code = $code; 651 652 return $this->code; 653 } 654 655 /** 656 * @return array 657 */ 658 private static function getBuiltinTypes() 659 { 660 // PHP 5 661 if (\PHP_MAJOR_VERSION === 5) { 662 return ['array', 'callable']; 663 } 664 665 // PHP 8 666 if (\PHP_MAJOR_VERSION === 8) { 667 return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object', 'mixed', 'false', 'null']; 668 } 669 670 // PHP 7 671 switch (\PHP_MINOR_VERSION) { 672 case 0: 673 return ['array', 'callable', 'string', 'int', 'bool', 'float']; 674 case 1: 675 return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void']; 676 default: 677 return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object']; 678 } 679 } 680 681 /** 682 * @return array 683 */ 684 public function getUseVariables() 685 { 686 if($this->useVariables !== null){ 687 return $this->useVariables; 688 } 689 690 $tokens = $this->getTokens(); 691 $use = array(); 692 $state = 'start'; 693 694 foreach ($tokens as &$token) { 695 $is_array = is_array($token); 696 697 switch ($state) { 698 case 'start': 699 if ($is_array && $token[0] === T_USE) { 700 $state = 'use'; 701 } 702 break; 703 case 'use': 704 if ($is_array) { 705 if ($token[0] === T_VARIABLE) { 706 $use[] = substr($token[1], 1); 707 } 708 } elseif ($token == ')') { 709 break 2; 710 } 711 break; 712 } 713 } 714 715 $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use)); 716 717 return $this->useVariables; 718 } 719 720 /** 721 * return bool 722 */ 723 public function isBindingRequired() 724 { 725 if($this->isBindingRequired === null){ 726 $this->getCode(); 727 } 728 729 return $this->isBindingRequired; 730 } 731 732 /** 733 * return bool 734 */ 735 public function isScopeRequired() 736 { 737 if($this->isScopeRequired === null){ 738 $this->getCode(); 739 } 740 741 return $this->isScopeRequired; 742 } 743 744 /** 745 * @return string 746 */ 747 protected function getHashedFileName() 748 { 749 if ($this->hashedName === null) { 750 $this->hashedName = sha1($this->getFileName()); 751 } 752 753 return $this->hashedName; 754 } 755 756 /** 757 * @return array 758 */ 759 protected function getFileTokens() 760 { 761 $key = $this->getHashedFileName(); 762 763 if (!isset(static::$files[$key])) { 764 static::$files[$key] = token_get_all(file_get_contents($this->getFileName())); 765 } 766 767 return static::$files[$key]; 768 } 769 770 /** 771 * @return array 772 */ 773 protected function getTokens() 774 { 775 if ($this->tokens === null) { 776 $tokens = $this->getFileTokens(); 777 $startLine = $this->getStartLine(); 778 $endLine = $this->getEndLine(); 779 $results = array(); 780 $start = false; 781 782 foreach ($tokens as &$token) { 783 if (!is_array($token)) { 784 if ($start) { 785 $results[] = $token; 786 } 787 788 continue; 789 } 790 791 $line = $token[2]; 792 793 if ($line <= $endLine) { 794 if ($line >= $startLine) { 795 $start = true; 796 $results[] = $token; 797 } 798 799 continue; 800 } 801 802 break; 803 } 804 805 $this->tokens = $results; 806 } 807 808 return $this->tokens; 809 } 810 811 /** 812 * @return array 813 */ 814 protected function getClasses() 815 { 816 $key = $this->getHashedFileName(); 817 818 if (!isset(static::$classes[$key])) { 819 $this->fetchItems(); 820 } 821 822 return static::$classes[$key]; 823 } 824 825 /** 826 * @return array 827 */ 828 protected function getFunctions() 829 { 830 $key = $this->getHashedFileName(); 831 832 if (!isset(static::$functions[$key])) { 833 $this->fetchItems(); 834 } 835 836 return static::$functions[$key]; 837 } 838 839 /** 840 * @return array 841 */ 842 protected function getConstants() 843 { 844 $key = $this->getHashedFileName(); 845 846 if (!isset(static::$constants[$key])) { 847 $this->fetchItems(); 848 } 849 850 return static::$constants[$key]; 851 } 852 853 /** 854 * @return array 855 */ 856 protected function getStructures() 857 { 858 $key = $this->getHashedFileName(); 859 860 if (!isset(static::$structures[$key])) { 861 $this->fetchItems(); 862 } 863 864 return static::$structures[$key]; 865 } 866 867 protected function fetchItems() 868 { 869 $key = $this->getHashedFileName(); 870 871 $classes = array(); 872 $functions = array(); 873 $constants = array(); 874 $structures = array(); 875 $tokens = $this->getFileTokens(); 876 877 $open = 0; 878 $state = 'start'; 879 $lastState = ''; 880 $prefix = ''; 881 $name = ''; 882 $alias = ''; 883 $isFunc = $isConst = false; 884 885 $startLine = $endLine = 0; 886 $structType = $structName = ''; 887 $structIgnore = false; 888 889 foreach ($tokens as $token) { 890 891 switch ($state) { 892 case 'start': 893 switch ($token[0]) { 894 case T_CLASS: 895 case T_INTERFACE: 896 case T_TRAIT: 897 $state = 'before_structure'; 898 $startLine = $token[2]; 899 $structType = $token[0] == T_CLASS 900 ? 'class' 901 : ($token[0] == T_INTERFACE ? 'interface' : 'trait'); 902 break; 903 case T_USE: 904 $state = 'use'; 905 $prefix = $name = $alias = ''; 906 $isFunc = $isConst = false; 907 break; 908 case T_FUNCTION: 909 $state = 'structure'; 910 $structIgnore = true; 911 break; 912 case T_NEW: 913 $state = 'new'; 914 break; 915 case T_OBJECT_OPERATOR: 916 case T_DOUBLE_COLON: 917 $state = 'invoke'; 918 break; 919 } 920 break; 921 case 'use': 922 switch ($token[0]) { 923 case T_FUNCTION: 924 $isFunc = true; 925 break; 926 case T_CONST: 927 $isConst = true; 928 break; 929 case T_NS_SEPARATOR: 930 $name .= $token[1]; 931 break; 932 case T_STRING: 933 $name .= $token[1]; 934 $alias = $token[1]; 935 break; 936 case T_NAME_QUALIFIED: 937 $name .= $token[1]; 938 $pieces = explode('\\', $token[1]); 939 $alias = end($pieces); 940 break; 941 case T_AS: 942 $lastState = 'use'; 943 $state = 'alias'; 944 break; 945 case '{': 946 $prefix = $name; 947 $name = $alias = ''; 948 $state = 'use-group'; 949 break; 950 case ',': 951 case ';': 952 if ($name === '' || $name[0] !== '\\') { 953 $name = '\\' . $name; 954 } 955 956 if ($alias !== '') { 957 if ($isFunc) { 958 $functions[strtolower($alias)] = $name; 959 } elseif ($isConst) { 960 $constants[$alias] = $name; 961 } else { 962 $classes[strtolower($alias)] = $name; 963 } 964 } 965 $name = $alias = ''; 966 $state = $token === ';' ? 'start' : 'use'; 967 break; 968 } 969 break; 970 case 'use-group': 971 switch ($token[0]) { 972 case T_NS_SEPARATOR: 973 $name .= $token[1]; 974 break; 975 case T_NAME_QUALIFIED: 976 $name .= $token[1]; 977 $pieces = explode('\\', $token[1]); 978 $alias = end($pieces); 979 break; 980 case T_STRING: 981 $name .= $token[1]; 982 $alias = $token[1]; 983 break; 984 case T_AS: 985 $lastState = 'use-group'; 986 $state = 'alias'; 987 break; 988 case ',': 989 case '}': 990 991 if ($prefix === '' || $prefix[0] !== '\\') { 992 $prefix = '\\' . $prefix; 993 } 994 995 if ($alias !== '') { 996 if ($isFunc) { 997 $functions[strtolower($alias)] = $prefix . $name; 998 } elseif ($isConst) { 999 $constants[$alias] = $prefix . $name; 1000 } else { 1001 $classes[strtolower($alias)] = $prefix . $name; 1002 } 1003 } 1004 $name = $alias = ''; 1005 $state = $token === '}' ? 'use' : 'use-group'; 1006 break; 1007 } 1008 break; 1009 case 'alias': 1010 if ($token[0] === T_STRING) { 1011 $alias = $token[1]; 1012 $state = $lastState; 1013 } 1014 break; 1015 case 'new': 1016 switch ($token[0]) { 1017 case T_WHITESPACE: 1018 case T_COMMENT: 1019 case T_DOC_COMMENT: 1020 break 2; 1021 case T_CLASS: 1022 $state = 'structure'; 1023 $structIgnore = true; 1024 break; 1025 default: 1026 $state = 'start'; 1027 } 1028 break; 1029 case 'invoke': 1030 switch ($token[0]) { 1031 case T_WHITESPACE: 1032 case T_COMMENT: 1033 case T_DOC_COMMENT: 1034 break 2; 1035 default: 1036 $state = 'start'; 1037 } 1038 break; 1039 case 'before_structure': 1040 if ($token[0] == T_STRING) { 1041 $structName = $token[1]; 1042 $state = 'structure'; 1043 } 1044 break; 1045 case 'structure': 1046 switch ($token[0]) { 1047 case '{': 1048 case T_CURLY_OPEN: 1049 case T_DOLLAR_OPEN_CURLY_BRACES: 1050 $open++; 1051 break; 1052 case '}': 1053 if (--$open == 0) { 1054 if(!$structIgnore){ 1055 $structures[] = array( 1056 'type' => $structType, 1057 'name' => $structName, 1058 'start' => $startLine, 1059 'end' => $endLine, 1060 ); 1061 } 1062 $structIgnore = false; 1063 $state = 'start'; 1064 } 1065 break; 1066 default: 1067 if (is_array($token)) { 1068 $endLine = $token[2]; 1069 } 1070 } 1071 break; 1072 } 1073 } 1074 1075 static::$classes[$key] = $classes; 1076 static::$functions[$key] = $functions; 1077 static::$constants[$key] = $constants; 1078 static::$structures[$key] = $structures; 1079 } 1080 1081 private function parseNameQualified($token) 1082 { 1083 $pieces = explode('\\', $token); 1084 1085 $id_start = array_shift($pieces); 1086 1087 $id_start_ci = strtolower($id_start); 1088 1089 $id_name = '\\' . implode('\\', $pieces); 1090 1091 return [$id_start, $id_start_ci, $id_name]; 1092 } 1093} 1094