diff options
Diffstat (limited to 'laravel/serializable-closure/src/Support/ReflectionClosure.php')
-rw-r--r-- | laravel/serializable-closure/src/Support/ReflectionClosure.php | 1195 |
1 files changed, 1195 insertions, 0 deletions
diff --git a/laravel/serializable-closure/src/Support/ReflectionClosure.php b/laravel/serializable-closure/src/Support/ReflectionClosure.php new file mode 100644 index 00000000..a0d37075 --- /dev/null +++ b/laravel/serializable-closure/src/Support/ReflectionClosure.php @@ -0,0 +1,1195 @@ +<?php + +namespace Laravel\SerializableClosure\Support; + +defined('T_NAME_QUALIFIED') || define('T_NAME_QUALIFIED', -4); +defined('T_NAME_FULLY_QUALIFIED') || define('T_NAME_FULLY_QUALIFIED', -5); +defined('T_FN') || define('T_FN', -6); +defined('T_NULLSAFE_OBJECT_OPERATOR') || define('T_NULLSAFE_OBJECT_OPERATOR', -7); + +use Closure; +use ReflectionFunction; + +class ReflectionClosure extends ReflectionFunction +{ + protected $code; + protected $tokens; + protected $hashedName; + protected $useVariables; + protected $isStaticClosure; + protected $isScopeRequired; + protected $isBindingRequired; + protected $isShortClosure; + + protected static $files = []; + protected static $classes = []; + protected static $functions = []; + protected static $constants = []; + protected static $structures = []; + + /** + * Creates a new reflection closure instance. + * + * @param \Closure $closure + * @param string|null $code + * @return void + */ + public function __construct(Closure $closure, $code = null) + { + parent::__construct($closure); + } + + /** + * Checks if the closure is "static". + * + * @return bool + */ + public function isStatic(): bool + { + if ($this->isStaticClosure === null) { + $this->isStaticClosure = strtolower(substr($this->getCode(), 0, 6)) === 'static'; + } + + return $this->isStaticClosure; + } + + /** + * Checks if the closure is a "short closure". + * + * @return bool + */ + public function isShortClosure() + { + if ($this->isShortClosure === null) { + $code = $this->getCode(); + + if ($this->isStatic()) { + $code = substr($code, 6); + } + + $this->isShortClosure = strtolower(substr(trim($code), 0, 2)) === 'fn'; + } + + return $this->isShortClosure; + } + + /** + * Get the closure's code. + * + * @return string + */ + public function getCode() + { + if ($this->code !== null) { + return $this->code; + } + + $fileName = $this->getFileName(); + $line = $this->getStartLine() - 1; + + $className = null; + + if (null !== $className = $this->getClosureScopeClass()) { + $className = '\\'.trim($className->getName(), '\\'); + } + + $builtin_types = self::getBuiltinTypes(); + $class_keywords = ['self', 'static', 'parent']; + + $ns = $this->getClosureNamespaceName(); + $nsf = $ns == '' ? '' : ($ns[0] == '\\' ? $ns : '\\'.$ns); + + $_file = var_export($fileName, true); + $_dir = var_export(dirname($fileName), true); + $_namespace = var_export($ns, true); + $_class = var_export(trim($className ?: '', '\\'), true); + $_function = $ns.($ns == '' ? '' : '\\').'{closure}'; + $_method = ($className == '' ? '' : trim($className, '\\').'::').$_function; + $_function = var_export($_function, true); + $_method = var_export($_method, true); + $_trait = null; + + $tokens = $this->getTokens(); + $state = $lastState = 'start'; + $inside_structure = false; + $isFirstClassCallable = false; + $isShortClosure = false; + + $inside_structure_mark = 0; + $open = 0; + $code = ''; + $id_start = $id_start_ci = $id_name = $context = ''; + $classes = $functions = $constants = null; + $use = []; + $lineAdd = 0; + $isUsingScope = false; + $isUsingThisObject = false; + + for ($i = 0, $l = count($tokens); $i < $l; $i++) { + $token = $tokens[$i]; + + switch ($state) { + case 'start': + if ($token[0] === T_FUNCTION || $token[0] === T_STATIC) { + $code .= $token[1]; + + $state = $token[0] === T_FUNCTION ? 'function' : 'static'; + } elseif ($token[0] === T_FN) { + $isShortClosure = true; + $code .= $token[1]; + $state = 'closure_args'; + } elseif ($token[0] === T_PUBLIC || $token[0] === T_PROTECTED || $token[0] === T_PRIVATE) { + $code = ''; + $isFirstClassCallable = true; + } + break; + case 'static': + if ($token[0] === T_WHITESPACE || $token[0] === T_COMMENT || $token[0] === T_FUNCTION) { + $code .= $token[1]; + if ($token[0] === T_FUNCTION) { + $state = 'function'; + } + } elseif ($token[0] === T_FN) { + $isShortClosure = true; + $code .= $token[1]; + $state = 'closure_args'; + } else { + $code = ''; + $state = 'start'; + } + break; + case 'function': + switch ($token[0]) { + case T_STRING: + if ($isFirstClassCallable) { + $state = 'closure_args'; + break; + } + + $code = ''; + $state = 'named_function'; + break; + case '(': + $code .= '('; + $state = 'closure_args'; + break; + default: + $code .= is_array($token) ? $token[1] : $token; + } + break; + case 'named_function': + if ($token[0] === T_FUNCTION || $token[0] === T_STATIC) { + $code = $token[1]; + $state = $token[0] === T_FUNCTION ? 'function' : 'static'; + } elseif ($token[0] === T_FN) { + $isShortClosure = true; + $code .= $token[1]; + $state = 'closure_args'; + } + break; + case 'closure_args': + switch ($token[0]) { + case T_NAME_QUALIFIED: + [$id_start, $id_start_ci, $id_name] = $this->parseNameQualified($token[1]); + $context = 'args'; + $state = 'id_name'; + $lastState = 'closure_args'; + break; + case T_NS_SEPARATOR: + case T_STRING: + $id_start = $token[1]; + $id_start_ci = strtolower($id_start); + $id_name = ''; + $context = 'args'; + $state = 'id_name'; + $lastState = 'closure_args'; + break; + case T_USE: + $code .= $token[1]; + $state = 'use'; + break; + case T_DOUBLE_ARROW: + $code .= $token[1]; + if ($isShortClosure) { + $state = 'closure'; + } + break; + case ':': + $code .= ':'; + $state = 'return'; + break; + case '{': + $code .= '{'; + $state = 'closure'; + $open++; + break; + default: + $code .= is_array($token) ? $token[1] : $token; + } + break; + case 'use': + switch ($token[0]) { + case T_VARIABLE: + $use[] = substr($token[1], 1); + $code .= $token[1]; + break; + case '{': + $code .= '{'; + $state = 'closure'; + $open++; + break; + case ':': + $code .= ':'; + $state = 'return'; + break; + default: + $code .= is_array($token) ? $token[1] : $token; + break; + } + break; + case 'return': + switch ($token[0]) { + case T_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + $code .= $token[1]; + break; + case T_NS_SEPARATOR: + case T_STRING: + $id_start = $token[1]; + $id_start_ci = strtolower($id_start); + $id_name = ''; + $context = 'return_type'; + $state = 'id_name'; + $lastState = 'return'; + break 2; + case T_NAME_QUALIFIED: + [$id_start, $id_start_ci, $id_name] = $this->parseNameQualified($token[1]); + $context = 'return_type'; + $state = 'id_name'; + $lastState = 'return'; + break 2; + case T_DOUBLE_ARROW: + $code .= $token[1]; + if ($isShortClosure) { + $state = 'closure'; + } + break; + case '{': + $code .= '{'; + $state = 'closure'; + $open++; + break; + default: + $code .= is_array($token) ? $token[1] : $token; + break; + } + break; + case 'closure': + switch ($token[0]) { + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case '{': + $code .= is_array($token) ? $token[1] : $token; + $open++; + break; + case '}': + $code .= '}'; + if (--$open === 0 && ! $isShortClosure) { + break 3; + } elseif ($inside_structure) { + $inside_structure = ! ($open === $inside_structure_mark); + } + break; + case '(': + case '[': + $code .= $token[0]; + if ($isShortClosure) { + $open++; + } + break; + case ')': + case ']': + if ($isShortClosure) { + if ($open === 0) { + break 3; + } + $open--; + } + $code .= $token[0]; + break; + case ',': + case ';': + if ($isShortClosure && $open === 0) { + break 3; + } + $code .= $token[0]; + break; + case T_LINE: + $code .= $token[2] - $line + $lineAdd; + break; + case T_FILE: + $code .= $_file; + break; + case T_DIR: + $code .= $_dir; + break; + case T_NS_C: + $code .= $_namespace; + break; + case T_CLASS_C: + $code .= $inside_structure ? $token[1] : $_class; + break; + case T_FUNC_C: + $code .= $inside_structure ? $token[1] : $_function; + break; + case T_METHOD_C: + $code .= $inside_structure ? $token[1] : $_method; + break; + case T_COMMENT: + if (substr($token[1], 0, 8) === '#trackme') { + $timestamp = time(); + $code .= '/**'.PHP_EOL; + $code .= '* Date : '.date(DATE_W3C, $timestamp).PHP_EOL; + $code .= '* Timestamp : '.$timestamp.PHP_EOL; + $code .= '* Line : '.($line + 1).PHP_EOL; + $code .= '* File : '.$_file.PHP_EOL.'*/'.PHP_EOL; + $lineAdd += 5; + } else { + $code .= $token[1]; + } + break; + case T_VARIABLE: + if ($token[1] == '$this' && ! $inside_structure) { + $isUsingThisObject = true; + } + $code .= $token[1]; + break; + case T_STATIC: + case T_NS_SEPARATOR: + case T_STRING: + $id_start = $token[1]; + $id_start_ci = strtolower($id_start); + $id_name = ''; + $context = 'root'; + $state = 'id_name'; + $lastState = 'closure'; + break 2; + case T_NAME_QUALIFIED: + [$id_start, $id_start_ci, $id_name] = $this->parseNameQualified($token[1]); + $context = 'root'; + $state = 'id_name'; + $lastState = 'closure'; + break 2; + case T_NEW: + $code .= $token[1]; + $context = 'new'; + $state = 'id_start'; + $lastState = 'closure'; + break 2; + case T_USE: + $code .= $token[1]; + $context = 'use'; + $state = 'id_start'; + $lastState = 'closure'; + break; + case T_INSTANCEOF: + case T_INSTEADOF: + $code .= $token[1]; + $context = 'instanceof'; + $state = 'id_start'; + $lastState = 'closure'; + break; + case T_OBJECT_OPERATOR: + case T_NULLSAFE_OBJECT_OPERATOR: + case T_DOUBLE_COLON: + $code .= $token[1]; + $lastState = 'closure'; + $state = 'ignore_next'; + break; + case T_FUNCTION: + $code .= $token[1]; + $state = 'closure_args'; + if (! $inside_structure) { + $inside_structure = true; + $inside_structure_mark = $open; + } + break; + case T_TRAIT_C: + if ($_trait === null) { + $startLine = $this->getStartLine(); + $endLine = $this->getEndLine(); + $structures = $this->getStructures(); + + $_trait = ''; + + foreach ($structures as &$struct) { + if ($struct['type'] === 'trait' && + $struct['start'] <= $startLine && + $struct['end'] >= $endLine + ) { + $_trait = ($ns == '' ? '' : $ns.'\\').$struct['name']; + break; + } + } + + $_trait = var_export($_trait, true); + } + + $code .= $_trait; + break; + default: + $code .= is_array($token) ? $token[1] : $token; + } + break; + case 'ignore_next': + switch ($token[0]) { + case T_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + $code .= $token[1]; + break; + case T_CLASS: + case T_NEW: + case T_STATIC: + case T_VARIABLE: + case T_STRING: + case T_CLASS_C: + case T_FILE: + case T_DIR: + case T_METHOD_C: + case T_FUNC_C: + case T_FUNCTION: + case T_INSTANCEOF: + case T_LINE: + case T_NS_C: + case T_TRAIT_C: + case T_USE: + $code .= $token[1]; + $state = $lastState; + break; + default: + $state = $lastState; + $i--; + } + break; + case 'id_start': + switch ($token[0]) { + case T_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + $code .= $token[1]; + break; + case T_NS_SEPARATOR: + case T_NAME_FULLY_QUALIFIED: + case T_STRING: + case T_STATIC: + $id_start = $token[1]; + $id_start_ci = strtolower($id_start); + $id_name = ''; + $state = 'id_name'; + break 2; + case T_NAME_QUALIFIED: + [$id_start, $id_start_ci, $id_name] = $this->parseNameQualified($token[1]); + $state = 'id_name'; + break 2; + case T_VARIABLE: + $code .= $token[1]; + $state = $lastState; + break; + case T_CLASS: + $code .= $token[1]; + $state = 'anonymous'; + break; + default: + $i--; //reprocess last + $state = 'id_name'; + } + break; + case 'id_name': + switch ($token[0]) { + // named arguments... + case ':': + if ($lastState === 'closure' && $context === 'root') { + $state = 'ignore_next'; + $lastState = 'closure'; + $code .= $id_start.$token; + } + + break; + case T_NAME_QUALIFIED: + case T_NS_SEPARATOR: + case T_STRING: + case T_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + $id_name .= $token[1]; + break; + case '(': + if ($isShortClosure) { + $open++; + } + if ($context === 'new' || false !== strpos($id_name, '\\')) { + if ($id_start_ci === 'self' || $id_start_ci === 'static') { + if (! $inside_structure) { + $isUsingScope = true; + } + } elseif ($id_start !== '\\' && ! in_array($id_start_ci, $class_keywords)) { + if ($classes === null) { + $classes = $this->getClasses(); + } + if (isset($classes[$id_start_ci])) { + $id_start = $classes[$id_start_ci]; + } + if ($id_start[0] !== '\\') { + $id_start = $nsf.'\\'.$id_start; + } + } + } else { + if ($id_start !== '\\') { + if ($functions === null) { + $functions = $this->getFunctions(); + } + if (isset($functions[$id_start_ci])) { + $id_start = $functions[$id_start_ci]; + } elseif ($nsf !== '\\' && function_exists($nsf.'\\'.$id_start)) { + $id_start = $nsf.'\\'.$id_start; + // Cache it to functions array + $functions[$id_start_ci] = $id_start; + } + } + } + $code .= $id_start.$id_name.'('; + $state = $lastState; + break; + case T_VARIABLE: + case T_DOUBLE_COLON: + if ($id_start !== '\\') { + if ($id_start_ci === 'self' || $id_start_ci === 'parent') { + if (! $inside_structure) { + $isUsingScope = true; + } + } elseif ($id_start_ci === 'static') { + if (! $inside_structure) { + $isUsingScope = $token[0] === T_DOUBLE_COLON; + } + } elseif (! (\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))) { + if ($classes === null) { + $classes = $this->getClasses(); + } + if (isset($classes[$id_start_ci])) { + $id_start = $classes[$id_start_ci]; + } + if ($id_start[0] !== '\\') { + $id_start = $nsf.'\\'.$id_start; + } + } + } + + $code .= $id_start.$id_name.$token[1]; + $state = $token[0] === T_DOUBLE_COLON ? 'ignore_next' : $lastState; + break; + default: + if ($id_start !== '\\' && ! defined($id_start)) { + if ($constants === null) { + $constants = $this->getConstants(); + } + if (isset($constants[$id_start])) { + $id_start = $constants[$id_start]; + } elseif ($context === 'new') { + if (in_array($id_start_ci, $class_keywords)) { + if (! $inside_structure) { + $isUsingScope = true; + } + } else { + if ($classes === null) { + $classes = $this->getClasses(); + } + if (isset($classes[$id_start_ci])) { + $id_start = $classes[$id_start_ci]; + } + if ($id_start[0] !== '\\') { + $id_start = $nsf.'\\'.$id_start; + } + } + } elseif ($context === 'use' || + $context === 'instanceof' || + $context === 'args' || + $context === 'return_type' || + $context === 'extends' || + $context === 'root' + ) { + if (in_array($id_start_ci, $class_keywords)) { + if (! $inside_structure && ! $id_start_ci === 'static') { + $isUsingScope = true; + } + } elseif (! (\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))) { + if ($classes === null) { + $classes = $this->getClasses(); + } + if (isset($classes[$id_start_ci])) { + $id_start = $classes[$id_start_ci]; + } + if ($id_start[0] !== '\\') { + $id_start = $nsf.'\\'.$id_start; + } + } + } + } + $code .= $id_start.$id_name; + $state = $lastState; + $i--; //reprocess last token + } + break; + case 'anonymous': + switch ($token[0]) { + case T_NS_SEPARATOR: + case T_STRING: + $id_start = $token[1]; + $id_start_ci = strtolower($id_start); + $id_name = ''; + $state = 'id_name'; + $context = 'extends'; + $lastState = 'anonymous'; + break; + case '{': + $state = 'closure'; + if (! $inside_structure) { + $inside_structure = true; + $inside_structure_mark = $open; + } + $i--; + break; + default: + $code .= is_array($token) ? $token[1] : $token; + } + break; + } + } + + if ($isShortClosure) { + $this->useVariables = $this->getStaticVariables(); + } else { + $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use)); + } + + $this->isShortClosure = $isShortClosure; + $this->isBindingRequired = $isUsingThisObject; + $this->isScopeRequired = $isUsingScope; + + if (PHP_VERSION_ID >= 80100) { + $attributesCode = array_map(function ($attribute) { + $arguments = $attribute->getArguments(); + + $name = $attribute->getName(); + $arguments = implode(', ', array_map(function ($argument, $key) { + $argument = sprintf("'%s'", str_replace("'", "\\'", $argument)); + + if (is_string($key)) { + $argument = sprintf('%s: %s', $key, $argument); + } + + return $argument; + }, $arguments, array_keys($arguments))); + + return "#[$name($arguments)]"; + }, $this->getAttributes()); + + if (! empty($attributesCode)) { + $code = implode("\n", array_merge($attributesCode, [$code])); + } + } + + $this->code = $code; + + return $this->code; + } + + /** + * Get PHP native built in types. + * + * @return array + */ + protected static function getBuiltinTypes() + { + // PHP 8.1 + if (PHP_VERSION_ID >= 80100) { + return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object', 'mixed', 'false', 'null', 'never']; + } + + // PHP 8 + if (\PHP_MAJOR_VERSION === 8) { + return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object', 'mixed', 'false', 'null']; + } + + // PHP 7 + switch (\PHP_MINOR_VERSION) { + case 0: + return ['array', 'callable', 'string', 'int', 'bool', 'float']; + case 1: + return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void']; + default: + return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object']; + } + } + + /** + * Gets the use variables by the closure. + * + * @return array + */ + public function getUseVariables() + { + if ($this->useVariables !== null) { + return $this->useVariables; + } + + $tokens = $this->getTokens(); + $use = []; + $state = 'start'; + + foreach ($tokens as &$token) { + $is_array = is_array($token); + + switch ($state) { + case 'start': + if ($is_array && $token[0] === T_USE) { + $state = 'use'; + } + break; + case 'use': + if ($is_array) { + if ($token[0] === T_VARIABLE) { + $use[] = substr($token[1], 1); + } + } elseif ($token == ')') { + break 2; + } + break; + } + } + + $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use)); + + return $this->useVariables; + } + + /** + * Checks if binding is required. + * + * @return bool + */ + public function isBindingRequired() + { + if ($this->isBindingRequired === null) { + $this->getCode(); + } + + return $this->isBindingRequired; + } + + /** + * Checks if access to the scope is required. + * + * @return bool + */ + public function isScopeRequired() + { + if ($this->isScopeRequired === null) { + $this->getCode(); + } + + return $this->isScopeRequired; + } + + /** + * The the hash of the current file name. + * + * @return string + */ + protected function getHashedFileName() + { + if ($this->hashedName === null) { + $this->hashedName = sha1($this->getFileName()); + } + + return $this->hashedName; + } + + /** + * Get the file tokens. + * + * @return array + */ + protected function getFileTokens() + { + $key = $this->getHashedFileName(); + + if (! isset(static::$files[$key])) { + static::$files[$key] = token_get_all(file_get_contents($this->getFileName())); + } + + return static::$files[$key]; + } + + /** + * Get the tokens. + * + * @return array + */ + protected function getTokens() + { + if ($this->tokens === null) { + $tokens = $this->getFileTokens(); + $startLine = $this->getStartLine(); + $endLine = $this->getEndLine(); + $results = []; + $start = false; + + foreach ($tokens as &$token) { + if (! is_array($token)) { + if ($start) { + $results[] = $token; + } + + continue; + } + + $line = $token[2]; + + if ($line <= $endLine) { + if ($line >= $startLine) { + $start = true; + $results[] = $token; + } + + continue; + } + + break; + } + + $this->tokens = $results; + } + + return $this->tokens; + } + + /** + * Get the classes. + * + * @return array + */ + protected function getClasses() + { + $key = $this->getHashedFileName(); + + if (! isset(static::$classes[$key])) { + $this->fetchItems(); + } + + return static::$classes[$key]; + } + + /** + * Get the functions. + * + * @return array + */ + protected function getFunctions() + { + $key = $this->getHashedFileName(); + + if (! isset(static::$functions[$key])) { + $this->fetchItems(); + } + + return static::$functions[$key]; + } + + /** + * Gets the constants. + * + * @return array + */ + protected function getConstants() + { + $key = $this->getHashedFileName(); + + if (! isset(static::$constants[$key])) { + $this->fetchItems(); + } + + return static::$constants[$key]; + } + + /** + * Get the structures. + * + * @return array + */ + protected function getStructures() + { + $key = $this->getHashedFileName(); + + if (! isset(static::$structures[$key])) { + $this->fetchItems(); + } + + return static::$structures[$key]; + } + + /** + * Fetch the items. + * + * @return void. + */ + protected function fetchItems() + { + $key = $this->getHashedFileName(); + + $classes = []; + $functions = []; + $constants = []; + $structures = []; + $tokens = $this->getFileTokens(); + + $open = 0; + $state = 'start'; + $lastState = ''; + $prefix = ''; + $name = ''; + $alias = ''; + $isFunc = $isConst = false; + + $startLine = $endLine = 0; + $structType = $structName = ''; + $structIgnore = false; + + foreach ($tokens as $token) { + switch ($state) { + case 'start': + switch ($token[0]) { + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + $state = 'before_structure'; + $startLine = $token[2]; + $structType = $token[0] == T_CLASS + ? 'class' + : ($token[0] == T_INTERFACE ? 'interface' : 'trait'); + break; + case T_USE: + $state = 'use'; + $prefix = $name = $alias = ''; + $isFunc = $isConst = false; + break; + case T_FUNCTION: + $state = 'structure'; + $structIgnore = true; + break; + case T_NEW: + $state = 'new'; + break; + case T_OBJECT_OPERATOR: + case T_DOUBLE_COLON: + $state = 'invoke'; + break; + } + break; + case 'use': + switch ($token[0]) { + case T_FUNCTION: + $isFunc = true; + break; + case T_CONST: + $isConst = true; + break; + case T_NS_SEPARATOR: + $name .= $token[1]; + break; + case T_STRING: + $name .= $token[1]; + $alias = $token[1]; + break; + case T_NAME_QUALIFIED: + $name .= $token[1]; + $pieces = explode('\\', $token[1]); + $alias = end($pieces); + break; + case T_AS: + $lastState = 'use'; + $state = 'alias'; + break; + case '{': + $prefix = $name; + $name = $alias = ''; + $state = 'use-group'; + break; + case ',': + case ';': + if ($name === '' || $name[0] !== '\\') { + $name = '\\'.$name; + } + + if ($alias !== '') { + if ($isFunc) { + $functions[strtolower($alias)] = $name; + } elseif ($isConst) { + $constants[$alias] = $name; + } else { + $classes[strtolower($alias)] = $name; + } + } + $name = $alias = ''; + $state = $token === ';' ? 'start' : 'use'; + break; + } + break; + case 'use-group': + switch ($token[0]) { + case T_NS_SEPARATOR: + $name .= $token[1]; + break; + case T_NAME_QUALIFIED: + $name .= $token[1]; + $pieces = explode('\\', $token[1]); + $alias = end($pieces); + break; + case T_STRING: + $name .= $token[1]; + $alias = $token[1]; + break; + case T_AS: + $lastState = 'use-group'; + $state = 'alias'; + break; + case ',': + case '}': + + if ($prefix === '' || $prefix[0] !== '\\') { + $prefix = '\\'.$prefix; + } + + if ($alias !== '') { + if ($isFunc) { + $functions[strtolower($alias)] = $prefix.$name; + } elseif ($isConst) { + $constants[$alias] = $prefix.$name; + } else { + $classes[strtolower($alias)] = $prefix.$name; + } + } + $name = $alias = ''; + $state = $token === '}' ? 'use' : 'use-group'; + break; + } + break; + case 'alias': + if ($token[0] === T_STRING) { + $alias = $token[1]; + $state = $lastState; + } + break; + case 'new': + switch ($token[0]) { + case T_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + break 2; + case T_CLASS: + $state = 'structure'; + $structIgnore = true; + break; + default: + $state = 'start'; + } + break; + case 'invoke': + switch ($token[0]) { + case T_WHITESPACE: + case T_COMMENT: + case T_DOC_COMMENT: + break 2; + default: + $state = 'start'; + } + break; + case 'before_structure': + if ($token[0] == T_STRING) { + $structName = $token[1]; + $state = 'structure'; + } + break; + case 'structure': + switch ($token[0]) { + case '{': + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + $open++; + break; + case '}': + if (--$open == 0) { + if (! $structIgnore) { + $structures[] = [ + 'type' => $structType, + 'name' => $structName, + 'start' => $startLine, + 'end' => $endLine, + ]; + } + $structIgnore = false; + $state = 'start'; + } + break; + default: + if (is_array($token)) { + $endLine = $token[2]; + } + } + break; + } + } + + static::$classes[$key] = $classes; + static::$functions[$key] = $functions; + static::$constants[$key] = $constants; + static::$structures[$key] = $structures; + } + + /** + * Returns the namespace associated to the closure. + * + * @return string + */ + protected function getClosureNamespaceName() + { + $ns = $this->getNamespaceName(); + + // First class callables... + if ($this->getName() !== '{closure}' && empty($ns) && ! is_null($this->getClosureScopeClass())) { + $ns = $this->getClosureScopeClass()->getNamespaceName(); + } + + return $ns; + } + + /** + * Parse the given token. + * + * @param string $token + * @return array + */ + protected function parseNameQualified($token) + { + $pieces = explode('\\', $token); + + $id_start = array_shift($pieces); + + $id_start_ci = strtolower($id_start); + + $id_name = '\\'.implode('\\', $pieces); + + return [$id_start, $id_start_ci, $id_name]; + } +} |