update v1.0.4

This commit is contained in:
sujitprasad
2016-01-04 18:05:45 +05:30
parent 372485336b
commit 4864e5a3f1
529 changed files with 20956 additions and 8178 deletions

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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.'
);
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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'