Skip to content

Commit

Permalink
Updates to docs and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremeamia committed May 25, 2013
1 parent e32ab51 commit 899065d
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 36 deletions.
8 changes: 4 additions & 4 deletions phpunit.xml.dist
Expand Up @@ -23,12 +23,12 @@
</filter>

<logging>
<log type="coverage-html" target="./build/tests/coverage"
<log type="coverage-html" target="./build/artifacts/coverage"
yui="true" highlight="false" charset="UTF-8"
lowUpperBound="35" highLowerBound="70"/>
<log type="coverage-clover" target="./build/tests/coverage.xml"/>
<log type="junit" target="./build/tests/log.xml" logIncompleteSkipped="false"/>
<log type="testdox-html" target="./build/tests/testdox.html"/>
<log type="coverage-clover" target="./build/artifacts/coverage.xml"/>
<log type="junit" target="./build/artifacts/log.xml" logIncompleteSkipped="false"/>
<log type="testdox-html" target="./build/artifacts/testdox.html"/>
</logging>

</phpunit>
28 changes: 21 additions & 7 deletions src/Jeremeamia/SuperClosure/ClosureFinderVisitor.php
Expand Up @@ -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) {
Expand Down
33 changes: 16 additions & 17 deletions src/Jeremeamia/SuperClosure/ClosureParser.php
Expand Up @@ -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
{
/**
Expand Down Expand Up @@ -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.');
Expand All @@ -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])) {
Expand All @@ -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()));
}

Expand Down
4 changes: 4 additions & 0 deletions src/Jeremeamia/SuperClosure/SerializableClosure.php
Expand Up @@ -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
{
/**
Expand Down
22 changes: 22 additions & 0 deletions tests/Jeremeamia/SuperClosure/Test/ClosureFinderVisitorTest.php
@@ -0,0 +1,22 @@
<?php

namespace Jeremeamia\SuperClosure\Test;

use Jeremeamia\SuperClosure\ClosureFinderVisitor;

/**
* @covers Jeremeamia\SuperClosure\ClosureFinderVisitor
*/
class ClosureFinderVisitorTest extends \PHPUnit_Framework_TestCase
{
public function testClosureNodeIsDiscoveredByVisitor()
{
$closure = function(){}; // Take the line number here and set it as the "startLine"
$reflectedClosure = new \ReflectionFunction($closure);
$closureFinder = new ClosureFinderVisitor($reflectedClosure);
$closureNode = new \PHPParser_Node_Expr_Closure(array(), array('startLine' => 14));
$closureFinder->leaveNode($closureNode);

$this->assertSame($closureNode, $closureFinder->getClosureNode());
}
}
22 changes: 14 additions & 8 deletions tests/Jeremeamia/SuperClosure/Test/SerializableClosureTest.php
Expand Up @@ -4,6 +4,9 @@

use Jeremeamia\SuperClosure\SerializableClosure;

/**
* @covers \Jeremeamia\SuperClosure\SerializableClosure
*/
class SerializableClosureTest extends \PHPUnit_Framework_TestCase
{
/**
Expand All @@ -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);
}
}

0 comments on commit 899065d

Please sign in to comment.