From 899065dfcf186aeb4edc290d05e0860a4197dca9 Mon Sep 17 00:00:00 2001 From: Jeremy Lindblom Date: Fri, 24 May 2013 19:34:29 -0700 Subject: [PATCH] Updates to docs and tests --- phpunit.xml.dist | 8 ++--- .../SuperClosure/ClosureFinderVisitor.php | 28 ++++++++++++---- src/Jeremeamia/SuperClosure/ClosureParser.php | 33 +++++++++---------- .../SuperClosure/SerializableClosure.php | 4 +++ .../Test/ClosureFinderVisitorTest.php | 22 +++++++++++++ .../Test/SerializableClosureTest.php | 22 ++++++++----- 6 files changed, 81 insertions(+), 36 deletions(-) create mode 100644 tests/Jeremeamia/SuperClosure/Test/ClosureFinderVisitorTest.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 522fc73..299507c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -23,12 +23,12 @@ - - - - + + + diff --git a/src/Jeremeamia/SuperClosure/ClosureFinderVisitor.php b/src/Jeremeamia/SuperClosure/ClosureFinderVisitor.php index ebb8bd5..1e9f4f1 100644 --- a/src/Jeremeamia/SuperClosure/ClosureFinderVisitor.php +++ b/src/Jeremeamia/SuperClosure/ClosureFinderVisitor.php @@ -2,24 +2,38 @@ namespace Jeremeamia\SuperClosure; -use PHPParser_Node; -use PHPParser_Node_Expr_Closure; -use PHPParser_NodeVisitorAbstract; - -class ClosureFinderVisitor extends PHPParser_NodeVisitorAbstract +/** + * This is a visitor that extends the nikic/php-parser library and looks for a closure node. + */ +class ClosureFinderVisitor extends \PHPParser_NodeVisitorAbstract { + /** + * @var \ReflectionFunction + */ protected $reflection; + /** + * @var \PHPParser_Node_Expr_Closure + */ protected $closureNode; + /** + * @param \ReflectionFunction $reflection + */ public function __construct(\ReflectionFunction $reflection) { $this->reflection = $reflection; } - public function leaveNode(PHPParser_Node $node) + /** + * Identifies Closure nodes and holds onto the first closure it finds that matches the line number of the closure + * specified in the constructor by its reflection + * + * {@inheritdoc} + */ + public function leaveNode(\PHPParser_Node $node) { - if (!$this->closureNode && $node instanceof PHPParser_Node_Expr_Closure) { + if (!$this->closureNode && $node instanceof \PHPParser_Node_Expr_Closure) { $nodeStartLine = $node->getAttribute('startLine'); $closureStartLine = $this->reflection->getStartLine(); if ($nodeStartLine == $closureStartLine) { diff --git a/src/Jeremeamia/SuperClosure/ClosureParser.php b/src/Jeremeamia/SuperClosure/ClosureParser.php index c060a8c..8b14dd1 100644 --- a/src/Jeremeamia/SuperClosure/ClosureParser.php +++ b/src/Jeremeamia/SuperClosure/ClosureParser.php @@ -2,16 +2,10 @@ namespace Jeremeamia\SuperClosure; -use PHPParser_Error; -use PHPParser_Lexer_Emulative; -use PHPParser_Parser; -use PHPParser_PrettyPrinter_Default; -use PHPParser_Node; -use PHPParser_Node_Expr_Closure; -use PHPParser_Node_Name; -use PHPParser_NodeTraverser; -use PHPParser_NodeVisitor_NameResolver; - +/** + * Parses a closure from its reflection such that the code and used (closed upon) variables are accessible. The + * ClosureParser uses the fabulous nikic/php-parser library which creates abstract syntax trees (AST) of the code. + */ class ClosureParser { /** @@ -63,21 +57,22 @@ public function getReflection() public function getClosureAbstractSyntaxTree() { if (!$this->abstractSyntaxTree) { - $parser = new PHPParser_Parser(new PHPParser_Lexer_Emulative); - $traverser = new PHPParser_NodeTraverser(); + // Setup the parser and traverser objects + $parser = new \PHPParser_Parser(new \PHPParser_Lexer_Emulative); + $traverser = new \PHPParser_NodeTraverser(); $closureFinder = new ClosureFinderVisitor($this->reflection); - $traverser->addVisitor(new PHPParser_NodeVisitor_NameResolver); + $traverser->addVisitor(new \PHPParser_NodeVisitor_NameResolver); $traverser->addVisitor($closureFinder); try { - // Use the PHP parser and lexer to get an AST of the file containing the closure + // Parse the code from the file containing the closure and create an AST with FQCN resolved $statements = $parser->parse(file_get_contents($this->reflection->getFileName())); $traverser->traverse($statements); - } catch (PHPParser_Error $e) { + } catch (\PHPParser_Error $e) { throw new \InvalidArgumentException('There was an error parsing the file containing the closure.'); } - // Find only the first closure in the AST on the line where the closure is located + // Find the first closure defined in the AST that is on the line where the closure is located $this->abstractSyntaxTree = $closureFinder->getClosureNode(); if (!$this->abstractSyntaxTree) { throw new \InvalidArgumentException('The closure was not found within the abstract syntax tree.'); @@ -93,12 +88,15 @@ public function getClosureAbstractSyntaxTree() public function getUsedVariables() { if (!$this->usedVariables) { + // Get the variable names defined in the AST $usedVarNames = array_map(function ($usedVar) { return $usedVar->var; }, $this->getClosureAbstractSyntaxTree()->uses); + // Get the variable names and values using reflection $usedVarValues = $this->reflection->getStaticVariables(); + // Combine the two arrays to create a canonical hash of variable names and values $this->usedVariables = array(); foreach ($usedVarNames as $name) { if (isset($usedVarValues[$name])) { @@ -116,7 +114,8 @@ public function getUsedVariables() public function getCode() { if (!$this->code) { - $printer = new PHPParser_PrettyPrinter_Default(); + // Use the pretty printer to print the closure code from the AST + $printer = new \PHPParser_PrettyPrinter_Default(); $this->code = $printer->prettyPrint(array($this->getClosureAbstractSyntaxTree())); } diff --git a/src/Jeremeamia/SuperClosure/SerializableClosure.php b/src/Jeremeamia/SuperClosure/SerializableClosure.php index 46da5d2..5b586a3 100644 --- a/src/Jeremeamia/SuperClosure/SerializableClosure.php +++ b/src/Jeremeamia/SuperClosure/SerializableClosure.php @@ -2,6 +2,10 @@ namespace Jeremeamia\SuperClosure; +/** + * This class allows you to do the impossible – serializing closures! With the combined power of the nikic/php-parser + * library, the Reflection API, and eval, you can serialize a closure. + */ class SerializableClosure implements \Serializable { /** diff --git a/tests/Jeremeamia/SuperClosure/Test/ClosureFinderVisitorTest.php b/tests/Jeremeamia/SuperClosure/Test/ClosureFinderVisitorTest.php new file mode 100644 index 0000000..3194bf3 --- /dev/null +++ b/tests/Jeremeamia/SuperClosure/Test/ClosureFinderVisitorTest.php @@ -0,0 +1,22 @@ + 14)); + $closureFinder->leaveNode($closureNode); + + $this->assertSame($closureNode, $closureFinder->getClosureNode()); + } +} diff --git a/tests/Jeremeamia/SuperClosure/Test/SerializableClosureTest.php b/tests/Jeremeamia/SuperClosure/Test/SerializableClosureTest.php index fafcd2d..f30558b 100644 --- a/tests/Jeremeamia/SuperClosure/Test/SerializableClosureTest.php +++ b/tests/Jeremeamia/SuperClosure/Test/SerializableClosureTest.php @@ -4,6 +4,9 @@ use Jeremeamia\SuperClosure\SerializableClosure; +/** + * @covers \Jeremeamia\SuperClosure\SerializableClosure + */ class SerializableClosureTest extends \PHPUnit_Framework_TestCase { /** @@ -27,20 +30,23 @@ public function setup() $this->serializableClosure = new SerializableClosure($exp); } - public function testGetClosureReturnsTheOriginalClosure() + public function testClosureInvokesTheOriginalClosure() { $this->assertInstanceOf('\Closure', $this->serializableClosure->getClosure()); $this->assertSame($this->originalClosure, $this->serializableClosure->getClosure()); + $this->assertEquals( + call_user_func($this->originalClosure, 4), + call_user_func($this->serializableClosure, 4) + ); } - public function testInvokeActuallyInvokesTheOriginalClosure() + public function testClosureBehavesTheSameAfterSerializationProcess() { - $originalClosure = $this->originalClosure; - $serializableClosure = $this->serializableClosure; + $originalReturnValue = call_user_func($this->serializableClosure, 4); + $serializedClosure = serialize($this->serializableClosure); + $unserializedClosure = unserialize($serializedClosure); + $finalReturnValue = call_user_func($unserializedClosure, 4); - $originalClosureResult = $originalClosure(4); - $serializableClosureResult = $serializableClosure(4); - - $this->assertEquals($originalClosureResult, $serializableClosureResult); + $this->assertEquals($originalReturnValue, $finalReturnValue); } }