You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1194 lines
44 KiB
1194 lines
44 KiB
<?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 = '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]; |
|
} |
|
}
|
|
|