Vendor lock
This commit is contained in:
18
vendor/jeremeamia/superclosure/LICENSE.md
vendored
Normal file
18
vendor/jeremeamia/superclosure/LICENSE.md
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
# MIT License
|
||||
|
||||
Copyright (c) 2010-2015 Jeremy Lindblom
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
|
||||
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.**
|
39
vendor/jeremeamia/superclosure/composer.json
vendored
Normal file
39
vendor/jeremeamia/superclosure/composer.json
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "jeremeamia/superclosure",
|
||||
"type": "library",
|
||||
"description": "Serialize Closure objects, including their context and binding",
|
||||
"keywords": ["closure", "serialize", "serializable", "function", "parser", "tokenizer", "lambda"],
|
||||
"homepage": "https://github.com/jeremeamia/super_closure",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jeremy Lindblom",
|
||||
"email": "jeremeamia@gmail.com",
|
||||
"homepage": "https://github.com/jeremeamia",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.4",
|
||||
"nikic/php-parser": "^1.2|^2.0|^3.0|^4.0",
|
||||
"symfony/polyfill-php56": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.0|^5.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SuperClosure\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"SuperClosure\\Test\\": "tests/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.4-dev"
|
||||
}
|
||||
}
|
||||
}
|
148
vendor/jeremeamia/superclosure/src/Analyzer/AstAnalyzer.php
vendored
Normal file
148
vendor/jeremeamia/superclosure/src/Analyzer/AstAnalyzer.php
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
<?php namespace SuperClosure\Analyzer;
|
||||
|
||||
use SuperClosure\Analyzer\Visitor\ThisDetectorVisitor;
|
||||
use SuperClosure\Exception\ClosureAnalysisException;
|
||||
use SuperClosure\Analyzer\Visitor\ClosureLocatorVisitor;
|
||||
use SuperClosure\Analyzer\Visitor\MagicConstantVisitor;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\PrettyPrinter\Standard as NodePrinter;
|
||||
use PhpParser\Error as ParserError;
|
||||
use PhpParser\Node\Expr\Variable as VariableNode;
|
||||
use PhpParser\NodeVisitor\NameResolver;
|
||||
use PhpParser\Parser as CodeParser;
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\Lexer\Emulative as EmulativeLexer;
|
||||
|
||||
/**
|
||||
* This is the AST based analyzer.
|
||||
*
|
||||
* We're using reflection and AST-based code parser to analyze a closure and
|
||||
* determine its code and context using the nikic/php-parser library. The AST
|
||||
* based analyzer and has more capabilities than the token analyzer, but is,
|
||||
* unfortunately, about 25 times slower.
|
||||
*/
|
||||
class AstAnalyzer extends ClosureAnalyzer
|
||||
{
|
||||
protected function determineCode(array &$data)
|
||||
{
|
||||
// Find the closure by traversing through a AST of the code.
|
||||
// Note: This also resolves class names to their FQCNs while traversing.
|
||||
$this->locateClosure($data);
|
||||
|
||||
// Make a second pass through the AST, but only through the closure's
|
||||
// nodes, to resolve any magic constants to literal values.
|
||||
$traverser = new NodeTraverser;
|
||||
$traverser->addVisitor(new MagicConstantVisitor($data['location']));
|
||||
$traverser->addVisitor($thisDetector = new ThisDetectorVisitor);
|
||||
$data['ast'] = $traverser->traverse([$data['ast']])[0];
|
||||
$data['hasThis'] = $thisDetector->detected;
|
||||
|
||||
// Bounce the updated AST down to a string representation of the code.
|
||||
$data['code'] = (new NodePrinter)->prettyPrint([$data['ast']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the closure's code and produces an abstract syntax tree (AST).
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @throws ClosureAnalysisException if there is an issue finding the closure
|
||||
*/
|
||||
private function locateClosure(array &$data)
|
||||
{
|
||||
try {
|
||||
$locator = new ClosureLocatorVisitor($data['reflection']);
|
||||
$fileAst = $this->getFileAst($data['reflection']);
|
||||
|
||||
$fileTraverser = new NodeTraverser;
|
||||
$fileTraverser->addVisitor(new NameResolver);
|
||||
$fileTraverser->addVisitor($locator);
|
||||
$fileTraverser->traverse($fileAst);
|
||||
} catch (ParserError $e) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new ClosureAnalysisException(
|
||||
'There was an error analyzing the closure code.', 0, $e
|
||||
);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$data['ast'] = $locator->closureNode;
|
||||
if (!$data['ast']) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new ClosureAnalysisException(
|
||||
'The closure was not found within the abstract syntax tree.'
|
||||
);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$data['location'] = $locator->location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the variables that in the "use" clause of the closure definition.
|
||||
* These are referred to as the "used variables", "static variables", or
|
||||
* "closed upon variables", "context" of the closure.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
protected function determineContext(array &$data)
|
||||
{
|
||||
// Get the variable names defined in the AST
|
||||
$refs = 0;
|
||||
$vars = array_map(function ($node) use (&$refs) {
|
||||
if ($node->byRef) {
|
||||
$refs++;
|
||||
}
|
||||
if ($node->var instanceof VariableNode) {
|
||||
// For PHP-Parser >=4.0
|
||||
return $node->var->name;
|
||||
} else {
|
||||
// For PHP-Parser <4.0
|
||||
return $node->var;
|
||||
}
|
||||
}, $data['ast']->uses);
|
||||
$data['hasRefs'] = ($refs > 0);
|
||||
|
||||
// Get the variable names and values using reflection
|
||||
$values = $data['reflection']->getStaticVariables();
|
||||
|
||||
// Combine the names and values to create the canonical context.
|
||||
foreach ($vars as $name) {
|
||||
if (isset($values[$name])) {
|
||||
$data['context'][$name] = $values[$name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \ReflectionFunction $reflection
|
||||
*
|
||||
* @throws ClosureAnalysisException
|
||||
*
|
||||
* @return \PhpParser\Node[]
|
||||
*/
|
||||
private function getFileAst(\ReflectionFunction $reflection)
|
||||
{
|
||||
$fileName = $reflection->getFileName();
|
||||
if (!file_exists($fileName)) {
|
||||
throw new ClosureAnalysisException(
|
||||
"The file containing the closure, \"{$fileName}\" did not exist."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return $this->getParser()->parse(file_get_contents($fileName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CodeParser
|
||||
*/
|
||||
private function getParser()
|
||||
{
|
||||
if (class_exists('PhpParser\ParserFactory')) {
|
||||
return (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||
}
|
||||
|
||||
return new CodeParser(new EmulativeLexer);
|
||||
}
|
||||
}
|
68
vendor/jeremeamia/superclosure/src/Analyzer/ClosureAnalyzer.php
vendored
Normal file
68
vendor/jeremeamia/superclosure/src/Analyzer/ClosureAnalyzer.php
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
<?php namespace SuperClosure\Analyzer;
|
||||
|
||||
use SuperClosure\Exception\ClosureAnalysisException;
|
||||
|
||||
abstract class ClosureAnalyzer
|
||||
{
|
||||
/**
|
||||
* Analyzer a given closure.
|
||||
*
|
||||
* @param \Closure $closure
|
||||
*
|
||||
* @throws ClosureAnalysisException
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function analyze(\Closure $closure)
|
||||
{
|
||||
$data = [
|
||||
'reflection' => new \ReflectionFunction($closure),
|
||||
'code' => null,
|
||||
'hasThis' => false,
|
||||
'context' => [],
|
||||
'hasRefs' => false,
|
||||
'binding' => null,
|
||||
'scope' => null,
|
||||
'isStatic' => $this->isClosureStatic($closure),
|
||||
];
|
||||
|
||||
$this->determineCode($data);
|
||||
$this->determineContext($data);
|
||||
$this->determineBinding($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
abstract protected function determineCode(array &$data);
|
||||
|
||||
/**
|
||||
* Returns the variables that are in the "use" clause of the closure.
|
||||
*
|
||||
* These variables are referred to as the "used variables", "static
|
||||
* variables", "closed upon variables", or "context" of the closure.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
abstract protected function determineContext(array &$data);
|
||||
|
||||
private function determineBinding(array &$data)
|
||||
{
|
||||
$data['binding'] = $data['reflection']->getClosureThis();
|
||||
if ($scope = $data['reflection']->getClosureScopeClass()) {
|
||||
$data['scope'] = $scope->getName();
|
||||
}
|
||||
}
|
||||
|
||||
private function isClosureStatic(\Closure $closure)
|
||||
{
|
||||
$closure = @$closure->bindTo(new \stdClass);
|
||||
|
||||
if ($closure === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$rebound = new \ReflectionFunction($closure);
|
||||
|
||||
return $rebound->getClosureThis() === null;
|
||||
}
|
||||
}
|
70
vendor/jeremeamia/superclosure/src/Analyzer/Token.php
vendored
Normal file
70
vendor/jeremeamia/superclosure/src/Analyzer/Token.php
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
<?php namespace SuperClosure\Analyzer;
|
||||
|
||||
/**
|
||||
* A Token object represents and individual token parsed from PHP code.
|
||||
*
|
||||
* Each Token object is a normalized token created from the result of the
|
||||
* `get_token_all()`. function, which is part of PHP's tokenizer.
|
||||
*
|
||||
* @link http://us2.php.net/manual/en/tokens.php
|
||||
*/
|
||||
class Token
|
||||
{
|
||||
/**
|
||||
* @var string The token name. Always null for literal tokens.
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var int|null The token's integer value. Always null for literal tokens.
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* @var string The PHP code of the token.
|
||||
*/
|
||||
public $code;
|
||||
|
||||
/**
|
||||
* @var int|null The line number of the token in the original code.
|
||||
*/
|
||||
public $line;
|
||||
|
||||
/**
|
||||
* Constructs a token object.
|
||||
*
|
||||
* @param string $code
|
||||
* @param int|null $value
|
||||
* @param int|null $line
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($code, $value = null, $line = null)
|
||||
{
|
||||
if (is_array($code)) {
|
||||
list($value, $code, $line) = array_pad($code, 3, null);
|
||||
}
|
||||
|
||||
$this->code = $code;
|
||||
$this->value = $value;
|
||||
$this->line = $line;
|
||||
$this->name = $value ? token_name($value) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the token's value/code is equal to the specified value.
|
||||
*
|
||||
* @param mixed $value The value to check.
|
||||
*
|
||||
* @return bool True if the token is equal to the value.
|
||||
*/
|
||||
public function is($value)
|
||||
{
|
||||
return ($this->code === $value || $this->value === $value);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
}
|
118
vendor/jeremeamia/superclosure/src/Analyzer/TokenAnalyzer.php
vendored
Normal file
118
vendor/jeremeamia/superclosure/src/Analyzer/TokenAnalyzer.php
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
<?php namespace SuperClosure\Analyzer;
|
||||
|
||||
use SuperClosure\Exception\ClosureAnalysisException;
|
||||
|
||||
/**
|
||||
* This is the token based analyzer.
|
||||
*
|
||||
* We're using Uses reflection and tokenization to analyze a closure and
|
||||
* determine its code and context. This is much faster than the AST based
|
||||
* implementation.
|
||||
*/
|
||||
class TokenAnalyzer extends ClosureAnalyzer
|
||||
{
|
||||
public function determineCode(array &$data)
|
||||
{
|
||||
$this->determineTokens($data);
|
||||
$data['code'] = implode('', $data['tokens']);
|
||||
$data['hasThis'] = (strpos($data['code'], '$this') !== false);
|
||||
}
|
||||
|
||||
private function determineTokens(array &$data)
|
||||
{
|
||||
$potential = $this->determinePotentialTokens($data['reflection']);
|
||||
$braceLevel = $index = $step = $insideUse = 0;
|
||||
$data['tokens'] = $data['context'] = [];
|
||||
|
||||
foreach ($potential as $token) {
|
||||
$token = new Token($token);
|
||||
switch ($step) {
|
||||
// Handle tokens before the function declaration.
|
||||
case 0:
|
||||
if ($token->is(T_FUNCTION)) {
|
||||
$data['tokens'][] = $token;
|
||||
$step++;
|
||||
}
|
||||
break;
|
||||
// Handle tokens inside the function signature.
|
||||
case 1:
|
||||
$data['tokens'][] = $token;
|
||||
if ($insideUse) {
|
||||
if ($token->is(T_VARIABLE)) {
|
||||
$varName = trim($token, '$ ');
|
||||
$data['context'][$varName] = null;
|
||||
} elseif ($token->is('&')) {
|
||||
$data['hasRefs'] = true;
|
||||
}
|
||||
} elseif ($token->is(T_USE)) {
|
||||
$insideUse++;
|
||||
}
|
||||
if ($token->is('{')) {
|
||||
$step++;
|
||||
$braceLevel++;
|
||||
}
|
||||
break;
|
||||
// Handle tokens inside the function body.
|
||||
case 2:
|
||||
$data['tokens'][] = $token;
|
||||
if ($token->is('{')) {
|
||||
$braceLevel++;
|
||||
} elseif ($token->is('}')) {
|
||||
$braceLevel--;
|
||||
if ($braceLevel === 0) {
|
||||
$step++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// Handle tokens after the function declaration.
|
||||
case 3:
|
||||
if ($token->is(T_FUNCTION)) {
|
||||
throw new ClosureAnalysisException('Multiple closures '
|
||||
. 'were declared on the same line of code. Could not '
|
||||
. 'determine which closure was the intended target.'
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function determinePotentialTokens(\ReflectionFunction $reflection)
|
||||
{
|
||||
// Load the file containing the code for the function.
|
||||
$fileName = $reflection->getFileName();
|
||||
if (!is_readable($fileName)) {
|
||||
throw new ClosureAnalysisException(
|
||||
"Cannot read the file containing the closure: \"{$fileName}\"."
|
||||
);
|
||||
}
|
||||
|
||||
$code = '';
|
||||
$file = new \SplFileObject($fileName);
|
||||
$file->seek($reflection->getStartLine() - 1);
|
||||
while ($file->key() < $reflection->getEndLine()) {
|
||||
$code .= $file->current();
|
||||
$file->next();
|
||||
}
|
||||
|
||||
$code = trim($code);
|
||||
if (strpos($code, '<?php') !== 0) {
|
||||
$code = "<?php\n" . $code;
|
||||
}
|
||||
|
||||
return token_get_all($code);
|
||||
}
|
||||
|
||||
protected function determineContext(array &$data)
|
||||
{
|
||||
// Get the values of the variables that are closed upon in "use".
|
||||
$values = $data['reflection']->getStaticVariables();
|
||||
|
||||
// Construct the context by combining the variable names and values.
|
||||
foreach ($data['context'] as $name => &$value) {
|
||||
if (isset($values[$name])) {
|
||||
$value = $values[$name];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
120
vendor/jeremeamia/superclosure/src/Analyzer/Visitor/ClosureLocatorVisitor.php
vendored
Normal file
120
vendor/jeremeamia/superclosure/src/Analyzer/Visitor/ClosureLocatorVisitor.php
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
<?php namespace SuperClosure\Analyzer\Visitor;
|
||||
|
||||
use SuperClosure\Exception\ClosureAnalysisException;
|
||||
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
|
||||
use PhpParser\Node\Stmt\Trait_ as TraitNode;
|
||||
use PhpParser\Node\Stmt\Class_ as ClassNode;
|
||||
use PhpParser\Node\Expr\Closure as ClosureNode;
|
||||
use PhpParser\Node as AstNode;
|
||||
use PhpParser\NodeVisitorAbstract as NodeVisitor;
|
||||
|
||||
/**
|
||||
* This is a visitor that extends the nikic/php-parser library and looks for a
|
||||
* closure node and its location.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ClosureLocatorVisitor extends NodeVisitor
|
||||
{
|
||||
/**
|
||||
* @var \ReflectionFunction
|
||||
*/
|
||||
private $reflection;
|
||||
|
||||
/**
|
||||
* @var ClosureNode
|
||||
*/
|
||||
public $closureNode;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $location;
|
||||
|
||||
/**
|
||||
* @param \ReflectionFunction $reflection
|
||||
*/
|
||||
public function __construct($reflection)
|
||||
{
|
||||
$this->reflection = $reflection;
|
||||
$this->location = [
|
||||
'class' => null,
|
||||
'directory' => dirname($this->reflection->getFileName()),
|
||||
'file' => $this->reflection->getFileName(),
|
||||
'function' => $this->reflection->getName(),
|
||||
'line' => $this->reflection->getStartLine(),
|
||||
'method' => null,
|
||||
'namespace' => null,
|
||||
'trait' => null,
|
||||
];
|
||||
}
|
||||
|
||||
public function enterNode(AstNode $node)
|
||||
{
|
||||
// Determine information about the closure's location
|
||||
if (!$this->closureNode) {
|
||||
if ($node instanceof NamespaceNode) {
|
||||
$namespace = $node->name !== null
|
||||
? $node->name->toString()
|
||||
: null;
|
||||
$this->location['namespace'] = $namespace;
|
||||
}
|
||||
if ($node instanceof TraitNode) {
|
||||
$this->location['trait'] = (string) $node->name;
|
||||
$this->location['class'] = null;
|
||||
} elseif ($node instanceof ClassNode) {
|
||||
$this->location['class'] = (string) $node->name;
|
||||
$this->location['trait'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Locate the node of the closure
|
||||
if ($node instanceof ClosureNode) {
|
||||
if ($node->getAttribute('startLine') == $this->location['line']) {
|
||||
if ($this->closureNode) {
|
||||
$line = $this->location['file'] . ':' . $node->getAttribute('startLine');
|
||||
throw new ClosureAnalysisException("Two closures were "
|
||||
. "declared on the same line ({$line}) of code. Cannot "
|
||||
. "determine which closure was the intended target.");
|
||||
} else {
|
||||
$this->closureNode = $node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function leaveNode(AstNode $node)
|
||||
{
|
||||
// Determine information about the closure's location
|
||||
if (!$this->closureNode) {
|
||||
if ($node instanceof NamespaceNode) {
|
||||
$this->location['namespace'] = null;
|
||||
}
|
||||
if ($node instanceof TraitNode) {
|
||||
$this->location['trait'] = null;
|
||||
} elseif ($node instanceof ClassNode) {
|
||||
$this->location['class'] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function afterTraverse(array $nodes)
|
||||
{
|
||||
if ($this->location['class']) {
|
||||
$this->location['class'] = $this->location['namespace'] . '\\' . $this->location['class'];
|
||||
$this->location['method'] = "{$this->location['class']}::{$this->location['function']}";
|
||||
} elseif ($this->location['trait']) {
|
||||
$this->location['trait'] = $this->location['namespace'] . '\\' . $this->location['trait'];
|
||||
$this->location['method'] = "{$this->location['trait']}::{$this->location['function']}";
|
||||
|
||||
// If the closure was declared in a trait, then we will do a best
|
||||
// effort guess on the name of the class that used the trait. It's
|
||||
// actually impossible at this point to know for sure what it is.
|
||||
if ($closureScope = $this->reflection->getClosureScopeClass()) {
|
||||
$this->location['class'] = $closureScope ? $closureScope->getName() : null;
|
||||
} elseif ($closureThis = $this->reflection->getClosureThis()) {
|
||||
$this->location['class'] = get_class($closureThis);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
50
vendor/jeremeamia/superclosure/src/Analyzer/Visitor/MagicConstantVisitor.php
vendored
Normal file
50
vendor/jeremeamia/superclosure/src/Analyzer/Visitor/MagicConstantVisitor.php
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
<?php namespace SuperClosure\Analyzer\Visitor;
|
||||
|
||||
use PhpParser\Node\Scalar\LNumber as NumberNode;
|
||||
use PhpParser\Node\Scalar\String_ as StringNode;
|
||||
use PhpParser\Node as AstNode;
|
||||
use PhpParser\NodeVisitorAbstract as NodeVisitor;
|
||||
|
||||
/**
|
||||
* This is a visitor that resolves magic constants (e.g., __FILE__) to their
|
||||
* intended values within a closure's AST.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MagicConstantVisitor extends NodeVisitor
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $location;
|
||||
|
||||
/**
|
||||
* @param array $location
|
||||
*/
|
||||
public function __construct(array $location)
|
||||
{
|
||||
$this->location = $location;
|
||||
}
|
||||
|
||||
public function leaveNode(AstNode $node)
|
||||
{
|
||||
switch ($node->getType()) {
|
||||
case 'Scalar_MagicConst_Class' :
|
||||
return new StringNode($this->location['class'] ?: '');
|
||||
case 'Scalar_MagicConst_Dir' :
|
||||
return new StringNode($this->location['directory'] ?: '');
|
||||
case 'Scalar_MagicConst_File' :
|
||||
return new StringNode($this->location['file'] ?: '');
|
||||
case 'Scalar_MagicConst_Function' :
|
||||
return new StringNode($this->location['function'] ?: '');
|
||||
case 'Scalar_MagicConst_Line' :
|
||||
return new NumberNode($node->getAttribute('startLine') ?: 0);
|
||||
case 'Scalar_MagicConst_Method' :
|
||||
return new StringNode($this->location['method'] ?: '');
|
||||
case 'Scalar_MagicConst_Namespace' :
|
||||
return new StringNode($this->location['namespace'] ?: '');
|
||||
case 'Scalar_MagicConst_Trait' :
|
||||
return new StringNode($this->location['trait'] ?: '');
|
||||
}
|
||||
}
|
||||
}
|
27
vendor/jeremeamia/superclosure/src/Analyzer/Visitor/ThisDetectorVisitor.php
vendored
Normal file
27
vendor/jeremeamia/superclosure/src/Analyzer/Visitor/ThisDetectorVisitor.php
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
<?php namespace SuperClosure\Analyzer\Visitor;
|
||||
|
||||
use PhpParser\Node as AstNode;
|
||||
use PhpParser\Node\Expr\Variable as VariableNode;
|
||||
use PhpParser\NodeVisitorAbstract as NodeVisitor;
|
||||
|
||||
/**
|
||||
* Detects if the closure's AST contains a $this variable.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ThisDetectorVisitor extends NodeVisitor
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $detected = false;
|
||||
|
||||
public function leaveNode(AstNode $node)
|
||||
{
|
||||
if ($node instanceof VariableNode) {
|
||||
if ($node->name === 'this') {
|
||||
$this->detected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
vendor/jeremeamia/superclosure/src/Exception/ClosureAnalysisException.php
vendored
Normal file
9
vendor/jeremeamia/superclosure/src/Exception/ClosureAnalysisException.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php namespace SuperClosure\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown when there is a problem analyzing a closure.
|
||||
*/
|
||||
class ClosureAnalysisException extends \RuntimeException implements SuperClosureException
|
||||
{
|
||||
//
|
||||
}
|
9
vendor/jeremeamia/superclosure/src/Exception/ClosureSerializationException.php
vendored
Normal file
9
vendor/jeremeamia/superclosure/src/Exception/ClosureSerializationException.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php namespace SuperClosure\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown when there is a problem serializing a closure.
|
||||
*/
|
||||
class ClosureSerializationException extends \RuntimeException implements SuperClosureException
|
||||
{
|
||||
//
|
||||
}
|
9
vendor/jeremeamia/superclosure/src/Exception/ClosureUnserializationException.php
vendored
Normal file
9
vendor/jeremeamia/superclosure/src/Exception/ClosureUnserializationException.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php namespace SuperClosure\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown when there is a problem unserializing a closure.
|
||||
*/
|
||||
class ClosureUnserializationException extends \RuntimeException implements SuperClosureException
|
||||
{
|
||||
//
|
||||
}
|
9
vendor/jeremeamia/superclosure/src/Exception/SuperClosureException.php
vendored
Normal file
9
vendor/jeremeamia/superclosure/src/Exception/SuperClosureException.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php namespace SuperClosure\Exception;
|
||||
|
||||
/**
|
||||
* This is a marker exception for the SuperClosure library.
|
||||
*/
|
||||
interface SuperClosureException
|
||||
{
|
||||
//
|
||||
}
|
217
vendor/jeremeamia/superclosure/src/SerializableClosure.php
vendored
Normal file
217
vendor/jeremeamia/superclosure/src/SerializableClosure.php
vendored
Normal file
@ -0,0 +1,217 @@
|
||||
<?php namespace SuperClosure;
|
||||
|
||||
use Closure;
|
||||
use SuperClosure\Exception\ClosureUnserializationException;
|
||||
|
||||
/**
|
||||
* This class acts as a wrapper for a closure, and allows it to be serialized.
|
||||
*
|
||||
* With the combined power of the Reflection API, code parsing, and the infamous
|
||||
* `eval()` function, you can serialize a closure, unserialize it somewhere
|
||||
* else (even a different PHP process), and execute it.
|
||||
*/
|
||||
class SerializableClosure implements \Serializable
|
||||
{
|
||||
/**
|
||||
* The closure being wrapped for serialization.
|
||||
*
|
||||
* @var Closure
|
||||
*/
|
||||
private $closure;
|
||||
|
||||
/**
|
||||
* The serializer doing the serialization work.
|
||||
*
|
||||
* @var SerializerInterface
|
||||
*/
|
||||
private $serializer;
|
||||
|
||||
/**
|
||||
* The data from unserialization.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* Create a new serializable closure instance.
|
||||
*
|
||||
* @param Closure $closure
|
||||
* @param SerializerInterface|null $serializer
|
||||
*/
|
||||
public function __construct(
|
||||
\Closure $closure,
|
||||
SerializerInterface $serializer = null
|
||||
) {
|
||||
$this->closure = $closure;
|
||||
$this->serializer = $serializer ?: new Serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the original closure object.
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
public function getClosure()
|
||||
{
|
||||
return $this->closure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates the closure invocation to the actual closure object.
|
||||
*
|
||||
* Important Notes:
|
||||
*
|
||||
* - `ReflectionFunction::invokeArgs()` should not be used here, because it
|
||||
* does not work with closure bindings.
|
||||
* - Args passed-by-reference lose their references when proxied through
|
||||
* `__invoke()`. This is an unfortunate, but understandable, limitation
|
||||
* of PHP that will probably never change.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __invoke()
|
||||
{
|
||||
return call_user_func_array($this->closure, func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the SerializableClosure with a new bound object and class scope.
|
||||
*
|
||||
* The method is essentially a wrapped proxy to the Closure::bindTo method.
|
||||
*
|
||||
* @param mixed $newthis The object to which the closure should be bound,
|
||||
* or NULL for the closure to be unbound.
|
||||
* @param mixed $newscope The class scope to which the closure is to be
|
||||
* associated, or 'static' to keep the current one.
|
||||
* If an object is given, the type of the object will
|
||||
* be used instead. This determines the visibility of
|
||||
* protected and private methods of the bound object.
|
||||
*
|
||||
* @return SerializableClosure
|
||||
* @link http://www.php.net/manual/en/closure.bindto.php
|
||||
*/
|
||||
public function bindTo($newthis, $newscope = 'static')
|
||||
{
|
||||
return new self(
|
||||
$this->closure->bindTo($newthis, $newscope),
|
||||
$this->serializer
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the code, context, and binding of the closure.
|
||||
*
|
||||
* @return string|null
|
||||
* @link http://php.net/manual/en/serializable.serialize.php
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
try {
|
||||
$this->data = $this->data ?: $this->serializer->getData($this->closure, true);
|
||||
return serialize($this->data);
|
||||
} catch (\Exception $e) {
|
||||
trigger_error(
|
||||
'Serialization of closure failed: ' . $e->getMessage(),
|
||||
E_USER_NOTICE
|
||||
);
|
||||
// Note: The serialize() method of Serializable must return a string
|
||||
// or null and cannot throw exceptions.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserializes the closure.
|
||||
*
|
||||
* Unserializes the closure's data and recreates the closure using a
|
||||
* simulation of its original context. The used variables (context) are
|
||||
* extracted into a fresh scope prior to redefining the closure. The
|
||||
* closure is also rebound to its former object and scope.
|
||||
*
|
||||
* @param string $serialized
|
||||
*
|
||||
* @throws ClosureUnserializationException
|
||||
* @link http://php.net/manual/en/serializable.unserialize.php
|
||||
*/
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
// Unserialize the closure data and reconstruct the closure object.
|
||||
$this->data = unserialize($serialized);
|
||||
$this->closure = __reconstruct_closure($this->data);
|
||||
|
||||
// Throw an exception if the closure could not be reconstructed.
|
||||
if (!$this->closure instanceof Closure) {
|
||||
throw new ClosureUnserializationException(
|
||||
'The closure is corrupted and cannot be unserialized.'
|
||||
);
|
||||
}
|
||||
|
||||
// Rebind the closure to its former binding and scope.
|
||||
if ($this->data['binding'] || $this->data['isStatic']) {
|
||||
$this->closure = $this->closure->bindTo(
|
||||
$this->data['binding'],
|
||||
$this->data['scope']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns closure data for `var_dump()`.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __debugInfo()
|
||||
{
|
||||
return $this->data ?: $this->serializer->getData($this->closure, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstruct a closure.
|
||||
*
|
||||
* HERE BE DRAGONS!
|
||||
*
|
||||
* The infamous `eval()` is used in this method, along with the error
|
||||
* suppression operator, and variable variables (i.e., double dollar signs) to
|
||||
* perform the unserialization logic. I'm sorry, world!
|
||||
*
|
||||
* This is also done inside a plain function instead of a method so that the
|
||||
* binding and scope of the closure are null.
|
||||
*
|
||||
* @param array $__data Unserialized closure data.
|
||||
*
|
||||
* @return Closure|null
|
||||
* @internal
|
||||
*/
|
||||
function __reconstruct_closure(array $__data)
|
||||
{
|
||||
// Simulate the original context the closure was created in.
|
||||
foreach ($__data['context'] as $__var_name => &$__value) {
|
||||
if ($__value instanceof SerializableClosure) {
|
||||
// Unbox any SerializableClosures in the context.
|
||||
$__value = $__value->getClosure();
|
||||
} elseif ($__value === Serializer::RECURSION) {
|
||||
// Track recursive references (there should only be one).
|
||||
$__recursive_reference = $__var_name;
|
||||
}
|
||||
|
||||
// Import the variable into this scope.
|
||||
${$__var_name} = $__value;
|
||||
}
|
||||
|
||||
// Evaluate the code to recreate the closure.
|
||||
try {
|
||||
if (isset($__recursive_reference)) {
|
||||
// Special handling for recursive closures.
|
||||
@eval("\${$__recursive_reference} = {$__data['code']};");
|
||||
$__closure = ${$__recursive_reference};
|
||||
} else {
|
||||
@eval("\$__closure = {$__data['code']};");
|
||||
}
|
||||
} catch (\ParseError $e) {
|
||||
// Discard the parse error.
|
||||
}
|
||||
|
||||
return isset($__closure) ? $__closure : null;
|
||||
}
|
221
vendor/jeremeamia/superclosure/src/Serializer.php
vendored
Normal file
221
vendor/jeremeamia/superclosure/src/Serializer.php
vendored
Normal file
@ -0,0 +1,221 @@
|
||||
<?php namespace SuperClosure;
|
||||
|
||||
use SuperClosure\Analyzer\AstAnalyzer as DefaultAnalyzer;
|
||||
use SuperClosure\Analyzer\ClosureAnalyzer;
|
||||
use SuperClosure\Exception\ClosureSerializationException;
|
||||
use SuperClosure\Exception\ClosureUnserializationException;
|
||||
|
||||
/**
|
||||
* This is the serializer class used for serializing Closure objects.
|
||||
*
|
||||
* We're abstracting away all the details, impossibilities, and scary things
|
||||
* that happen within.
|
||||
*/
|
||||
class Serializer implements SerializerInterface
|
||||
{
|
||||
/**
|
||||
* The special value marking a recursive reference to a closure.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const RECURSION = "{{RECURSION}}";
|
||||
|
||||
/**
|
||||
* The keys of closure data required for serialization.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $dataToKeep = [
|
||||
'code' => true,
|
||||
'context' => true,
|
||||
'binding' => true,
|
||||
'scope' => true,
|
||||
'isStatic' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* The closure analyzer instance.
|
||||
*
|
||||
* @var ClosureAnalyzer
|
||||
*/
|
||||
private $analyzer;
|
||||
|
||||
/**
|
||||
* The HMAC key to sign serialized closures.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $signingKey;
|
||||
|
||||
/**
|
||||
* Create a new serializer instance.
|
||||
*
|
||||
* @param ClosureAnalyzer|null $analyzer Closure analyzer instance.
|
||||
* @param string|null $signingKey HMAC key to sign closure data.
|
||||
*/
|
||||
public function __construct(
|
||||
ClosureAnalyzer $analyzer = null,
|
||||
$signingKey = null
|
||||
) {
|
||||
$this->analyzer = $analyzer ?: new DefaultAnalyzer;
|
||||
$this->signingKey = $signingKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function serialize(\Closure $closure)
|
||||
{
|
||||
$serialized = serialize(new SerializableClosure($closure, $this));
|
||||
|
||||
if ($serialized === null) {
|
||||
throw new ClosureSerializationException(
|
||||
'The closure could not be serialized.'
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->signingKey) {
|
||||
$signature = $this->calculateSignature($serialized);
|
||||
$serialized = '%' . base64_encode($signature) . $serialized;
|
||||
}
|
||||
|
||||
return $serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
// Strip off the signature from the front of the string.
|
||||
$signature = null;
|
||||
if ($serialized[0] === '%') {
|
||||
$signature = base64_decode(substr($serialized, 1, 44));
|
||||
$serialized = substr($serialized, 45);
|
||||
}
|
||||
|
||||
// If a key was provided, then verify the signature.
|
||||
if ($this->signingKey) {
|
||||
$this->verifySignature($signature, $serialized);
|
||||
}
|
||||
|
||||
set_error_handler(function () {});
|
||||
$unserialized = unserialize($serialized);
|
||||
restore_error_handler();
|
||||
if ($unserialized === false) {
|
||||
throw new ClosureUnserializationException(
|
||||
'The closure could not be unserialized.'
|
||||
);
|
||||
} elseif (!$unserialized instanceof SerializableClosure) {
|
||||
throw new ClosureUnserializationException(
|
||||
'The closure did not unserialize to a SuperClosure.'
|
||||
);
|
||||
}
|
||||
|
||||
return $unserialized->getClosure();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getData(\Closure $closure, $forSerialization = false)
|
||||
{
|
||||
// Use the closure analyzer to get data about the closure.
|
||||
$data = $this->analyzer->analyze($closure);
|
||||
|
||||
// If the closure data is getting retrieved solely for the purpose of
|
||||
// serializing the closure, then make some modifications to the data.
|
||||
if ($forSerialization) {
|
||||
// If there is no reference to the binding, don't serialize it.
|
||||
if (!$data['hasThis']) {
|
||||
$data['binding'] = null;
|
||||
}
|
||||
|
||||
// Remove data about the closure that does not get serialized.
|
||||
$data = array_intersect_key($data, self::$dataToKeep);
|
||||
|
||||
// Wrap any other closures within the context.
|
||||
foreach ($data['context'] as &$value) {
|
||||
if ($value instanceof \Closure) {
|
||||
$value = ($value === $closure)
|
||||
? self::RECURSION
|
||||
: new SerializableClosure($value, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively traverses and wraps all Closure objects within the value.
|
||||
*
|
||||
* NOTE: THIS MAY NOT WORK IN ALL USE CASES, SO USE AT YOUR OWN RISK.
|
||||
*
|
||||
* @param mixed $data Any variable that contains closures.
|
||||
* @param SerializerInterface $serializer The serializer to use.
|
||||
*/
|
||||
public static function wrapClosures(&$data, SerializerInterface $serializer)
|
||||
{
|
||||
if ($data instanceof \Closure) {
|
||||
// Handle and wrap closure objects.
|
||||
$reflection = new \ReflectionFunction($data);
|
||||
if ($binding = $reflection->getClosureThis()) {
|
||||
self::wrapClosures($binding, $serializer);
|
||||
$scope = $reflection->getClosureScopeClass();
|
||||
$scope = $scope ? $scope->getName() : 'static';
|
||||
$data = $data->bindTo($binding, $scope);
|
||||
}
|
||||
$data = new SerializableClosure($data, $serializer);
|
||||
} elseif (is_array($data) || $data instanceof \stdClass || $data instanceof \Traversable) {
|
||||
// Handle members of traversable values.
|
||||
foreach ($data as &$value) {
|
||||
self::wrapClosures($value, $serializer);
|
||||
}
|
||||
} elseif (is_object($data) && !$data instanceof \Serializable) {
|
||||
// Handle objects that are not already explicitly serializable.
|
||||
$reflection = new \ReflectionObject($data);
|
||||
if (!$reflection->hasMethod('__sleep')) {
|
||||
foreach ($reflection->getProperties() as $property) {
|
||||
if ($property->isPrivate() || $property->isProtected()) {
|
||||
$property->setAccessible(true);
|
||||
}
|
||||
$value = $property->getValue($data);
|
||||
self::wrapClosures($value, $serializer);
|
||||
$property->setValue($data, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a signature for a closure's serialized data.
|
||||
*
|
||||
* @param string $data Serialized closure data.
|
||||
*
|
||||
* @return string Signature of the closure's data.
|
||||
*/
|
||||
private function calculateSignature($data)
|
||||
{
|
||||
return hash_hmac('sha256', $data, $this->signingKey, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the signature for a closure's serialized data.
|
||||
*
|
||||
* @param string $signature The provided signature of the data.
|
||||
* @param string $data The data for which to verify the signature.
|
||||
*
|
||||
* @throws ClosureUnserializationException if the signature is invalid.
|
||||
*/
|
||||
private function verifySignature($signature, $data)
|
||||
{
|
||||
// Verify that the provided signature matches the calculated signature.
|
||||
if (!hash_equals($signature, $this->calculateSignature($data))) {
|
||||
throw new ClosureUnserializationException('The signature of the'
|
||||
. ' closure\'s data is invalid, which means the serialized '
|
||||
. 'closure has been modified and is unsafe to unserialize.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
45
vendor/jeremeamia/superclosure/src/SerializerInterface.php
vendored
Normal file
45
vendor/jeremeamia/superclosure/src/SerializerInterface.php
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
<?php namespace SuperClosure;
|
||||
|
||||
use SuperClosure\Exception\ClosureUnserializationException;
|
||||
|
||||
/**
|
||||
* Interface for a serializer that is used to serialize Closure objects.
|
||||
*/
|
||||
interface SerializerInterface
|
||||
{
|
||||
/**
|
||||
* Takes a Closure object, decorates it with a SerializableClosure object,
|
||||
* then performs the serialization.
|
||||
*
|
||||
* @param \Closure $closure Closure to serialize.
|
||||
*
|
||||
* @return string Serialized closure.
|
||||
*/
|
||||
public function serialize(\Closure $closure);
|
||||
|
||||
/**
|
||||
* Takes a serialized closure, performs the unserialization, and then
|
||||
* extracts and returns a the Closure object.
|
||||
*
|
||||
* @param string $serialized Serialized closure.
|
||||
*
|
||||
* @throws ClosureUnserializationException if unserialization fails.
|
||||
* @return \Closure Unserialized closure.
|
||||
*/
|
||||
public function unserialize($serialized);
|
||||
|
||||
/**
|
||||
* Retrieves data about a closure including its code, context, and binding.
|
||||
*
|
||||
* The data returned is dependant on the `ClosureAnalyzer` implementation
|
||||
* used and whether the `$forSerialization` parameter is set to true. If
|
||||
* `$forSerialization` is true, then only data relevant to serializing the
|
||||
* closure is returned.
|
||||
*
|
||||
* @param \Closure $closure Closure to analyze.
|
||||
* @param bool $forSerialization Include only serialization data.
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
public function getData(\Closure $closure, $forSerialization = false);
|
||||
}
|
Reference in New Issue
Block a user