update v1.0.4
This commit is contained in:
@@ -9,6 +9,7 @@ use PhpParser\PrettyPrinter\Standard as NodePrinter;
|
||||
use PhpParser\Error as ParserError;
|
||||
use PhpParser\NodeVisitor\NameResolver;
|
||||
use PhpParser\Parser as CodeParser;
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\Lexer\Emulative as EmulativeLexer;
|
||||
|
||||
/**
|
||||
@@ -122,8 +123,19 @@ class AstAnalyzer extends ClosureAnalyzer
|
||||
);
|
||||
}
|
||||
|
||||
$parser = new CodeParser(new EmulativeLexer);
|
||||
|
||||
return $parser->parse(file_get_contents($fileName));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,13 @@ abstract class ClosureAnalyzer
|
||||
|
||||
private function isClosureStatic(\Closure $closure)
|
||||
{
|
||||
$rebound = new \ReflectionFunction(@$closure->bindTo(new \stdClass));
|
||||
$closure = @$closure->bindTo(new \stdClass);
|
||||
|
||||
if ($closure === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$rebound = new \ReflectionFunction($closure);
|
||||
|
||||
return $rebound->getClosureThis() === null;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class TokenAnalyzer extends ClosureAnalyzer
|
||||
case 3:
|
||||
if ($token->is(T_FUNCTION)) {
|
||||
throw new ClosureAnalysisException('Multiple closures '
|
||||
. 'were declared on the same line of code. Could '
|
||||
. 'were declared on the same line of code. Could not '
|
||||
. 'determine which closure was the intended target.'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -106,12 +106,15 @@ final class ClosureLocatorVisitor extends NodeVisitor
|
||||
} elseif ($this->location['trait']) {
|
||||
$this->location['trait'] = $this->location['namespace'] . '\\' . $this->location['trait'];
|
||||
$this->location['method'] = "{$this->location['trait']}::{$this->location['function']}";
|
||||
}
|
||||
|
||||
if (!$this->location['class']) {
|
||||
/** @var \ReflectionClass $closureScopeClass */
|
||||
$closureScopeClass = $this->reflection->getClosureScopeClass();
|
||||
$this->location['class'] = $closureScopeClass ? $closureScopeClass->getName() : null;
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php namespace SuperClosure\Analyzer\Visitor;
|
||||
|
||||
use PhpParser\Node\Scalar\LNumber as NumberNode;
|
||||
use PhpParser\Node\Scalar\String as StringNode;
|
||||
use PhpParser\Node\Scalar\String_ as StringNode;
|
||||
use PhpParser\Node as AstNode;
|
||||
use PhpParser\NodeVisitorAbstract as NodeVisitor;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php namespace SuperClosure;
|
||||
|
||||
use Closure;
|
||||
use SuperClosure\Exception\ClosureUnserializationException;
|
||||
|
||||
/**
|
||||
@@ -14,7 +15,7 @@ class SerializableClosure implements \Serializable
|
||||
/**
|
||||
* The closure being wrapped for serialization.
|
||||
*
|
||||
* @var \Closure
|
||||
* @var Closure
|
||||
*/
|
||||
private $closure;
|
||||
|
||||
@@ -35,7 +36,7 @@ class SerializableClosure implements \Serializable
|
||||
/**
|
||||
* Create a new serializable closure instance.
|
||||
*
|
||||
* @param \Closure $closure
|
||||
* @param Closure $closure
|
||||
* @param SerializerInterface|null $serializer
|
||||
*/
|
||||
public function __construct(
|
||||
@@ -49,7 +50,7 @@ class SerializableClosure implements \Serializable
|
||||
/**
|
||||
* Return the original closure object.
|
||||
*
|
||||
* @return \Closure
|
||||
* @return Closure
|
||||
*/
|
||||
public function getClosure()
|
||||
{
|
||||
@@ -64,7 +65,7 @@ class SerializableClosure implements \Serializable
|
||||
* - `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 is an unfortunate, but understandable, limitation
|
||||
* `__invoke()`. This is an unfortunate, but understandable, limitation
|
||||
* of PHP that will probably never change.
|
||||
*
|
||||
* @return mixed
|
||||
@@ -74,12 +75,35 @@ class SerializableClosure implements \Serializable
|
||||
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.
|
||||
*
|
||||
* @see http://php.net/manual/en/serializable.serialize.php
|
||||
*
|
||||
* @return string|null
|
||||
* @link http://php.net/manual/en/serializable.serialize.php
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
@@ -105,25 +129,26 @@ class SerializableClosure implements \Serializable
|
||||
* extracted into a fresh scope prior to redefining the closure. The
|
||||
* closure is also rebound to its former object and scope.
|
||||
*
|
||||
* @see http://php.net/manual/en/serializable.unserialize.php
|
||||
*
|
||||
* @param string $serialized
|
||||
*
|
||||
* @throws ClosureUnserializationException
|
||||
* @link http://php.net/manual/en/serializable.unserialize.php
|
||||
*/
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
// Unserialize the data and reconstruct the SuperClosure.
|
||||
// Unserialize the closure data and reconstruct the closure object.
|
||||
$this->data = unserialize($serialized);
|
||||
$this->reconstructClosure();
|
||||
if (!$this->closure instanceof \Closure) {
|
||||
$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, if it's not static.
|
||||
if (!$this->data['isStatic']) {
|
||||
// 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']
|
||||
@@ -131,29 +156,6 @@ class SerializableClosure implements \Serializable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconstruct the closure.
|
||||
*
|
||||
* HERE BE DRAGONS!
|
||||
*
|
||||
* The infamous `eval()` is used in this method, along with `extract()`,
|
||||
* the error suppression operator, and variable variables (i.e., double
|
||||
* dollar signs) to perform the unserialization work. I'm sorry, world!
|
||||
*/
|
||||
private function reconstructClosure()
|
||||
{
|
||||
// Simulate the original context the closure was created in.
|
||||
extract($this->data['context'], EXTR_OVERWRITE);
|
||||
|
||||
// Evaluate the code to recreate the closure.
|
||||
if ($_fn = array_search(Serializer::RECURSION, $this->data['context'], true)) {
|
||||
@eval("\${$_fn} = {$this->data['code']};");
|
||||
$this->closure = $$_fn;
|
||||
} else {
|
||||
@eval("\$this->closure = {$this->data['code']};");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns closure data for `var_dump()`.
|
||||
*
|
||||
@@ -164,3 +166,52 @@ class SerializableClosure implements \Serializable
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -92,8 +92,18 @@ class Serializer implements SerializerInterface
|
||||
$this->verifySignature($signature, $serialized);
|
||||
}
|
||||
|
||||
/** @var SerializableClosure $unserialized */
|
||||
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();
|
||||
}
|
||||
@@ -193,13 +203,6 @@ class Serializer implements SerializerInterface
|
||||
*/
|
||||
private function verifySignature($signature, $data)
|
||||
{
|
||||
// Ensure that hash_equals() is available.
|
||||
static $hashEqualsFnExists = false;
|
||||
if (!$hashEqualsFnExists) {
|
||||
require __DIR__ . '/hash_equals.php';
|
||||
$hashEqualsFnExists = true;
|
||||
}
|
||||
|
||||
// Verify that the provided signature matches the calculated signature.
|
||||
if (!hash_equals($signature, $this->calculateSignature($data))) {
|
||||
throw new ClosureUnserializationException('The signature of the'
|
||||
|
||||
Reference in New Issue
Block a user