Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/3rdparty.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'laravel/serializable-closure/src/Support/ReflectionClosure.php')
-rw-r--r--laravel/serializable-closure/src/Support/ReflectionClosure.php1195
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];
+ }
+}