From 0bbf5e05d92bb3af03fd58fa25070ac21a80b52f Mon Sep 17 00:00:00 2001 From: Jeremy Lindblom Date: Tue, 4 Feb 2014 22:02:54 -0800 Subject: [PATCH] Massive updates due to feedback. Travis stuff should work now as well. --- .scrutinizer.yml | 20 +++++ .travis.yml | 18 ++++- README.md | 2 +- autoload.php | 28 ------- composer.json | 6 +- demo/factorial.php | 2 +- demo/hello-world.php | 2 +- src/ClosureParser/ClosureParserFactory.php | 75 ------------------- src/ClosureParser/ClosureParserInterface.php | 21 ------ src/{ => SuperClosure}/ClosureBinding.php | 25 +++++-- .../ClosureParser/AbstractClosureContext.php | 4 +- .../ClosureParser/AbstractClosureParser.php | 19 +++-- .../ClosureParser/Ast/AstClosureContext.php | 8 +- .../ClosureParser/Ast/AstParser.php | 36 +++++---- .../Ast/Visitor/ClosureLocatorVisitor.php | 6 +- .../Ast/Visitor/MagicConstantVisitor.php | 4 +- .../ClosureParser/ClosureContextInterface.php | 4 +- .../ClosureParser/ClosureLocation.php | 18 ++--- .../ClosureParser/ClosureParserFactory.php | 65 ++++++++++++++++ .../ClosureParser/ClosureParserInterface.php | 16 ++++ .../ClosureParser/ClosureParsingException.php | 2 +- src/SuperClosure/ClosureParser/Options.php | 71 ++++++++++++++++++ .../ClosureParser/Token/Token.php | 27 +++---- .../Token/TokenClosureContext.php | 6 +- .../ClosureParser/Token/TokenParser.php | 27 ++++--- .../SerializableClosure.php | 10 +-- src/{ => SuperClosure}/SuperClosure.php | 2 +- src/functions.php | 32 ++++---- .../Integration/ClosureSerializationTest.php | 10 +-- tests/Integration/test-cases-php53.php | 2 +- tests/Integration/test-cases-php54.php | 2 +- tests/bootstrap.php | 21 +----- 32 files changed, 324 insertions(+), 267 deletions(-) create mode 100644 .scrutinizer.yml delete mode 100644 autoload.php delete mode 100644 src/ClosureParser/ClosureParserFactory.php delete mode 100644 src/ClosureParser/ClosureParserInterface.php rename src/{ => SuperClosure}/ClosureBinding.php (67%) rename src/{ => SuperClosure}/ClosureParser/AbstractClosureContext.php (91%) rename src/{ => SuperClosure}/ClosureParser/AbstractClosureParser.php (64%) rename src/{ => SuperClosure}/ClosureParser/Ast/AstClosureContext.php (81%) rename src/{ => SuperClosure}/ClosureParser/Ast/AstParser.php (80%) rename src/{ => SuperClosure}/ClosureParser/Ast/Visitor/ClosureLocatorVisitor.php (95%) rename src/{ => SuperClosure}/ClosureParser/Ast/Visitor/MagicConstantVisitor.php (92%) rename src/{ => SuperClosure}/ClosureParser/ClosureContextInterface.php (74%) rename src/{ => SuperClosure}/ClosureParser/ClosureLocation.php (71%) create mode 100644 src/SuperClosure/ClosureParser/ClosureParserFactory.php create mode 100644 src/SuperClosure/ClosureParser/ClosureParserInterface.php rename src/{ => SuperClosure}/ClosureParser/ClosureParsingException.php (78%) create mode 100644 src/SuperClosure/ClosureParser/Options.php rename src/{ => SuperClosure}/ClosureParser/Token/Token.php (83%) rename src/{ => SuperClosure}/ClosureParser/Token/TokenClosureContext.php (79%) rename src/{ => SuperClosure}/ClosureParser/Token/TokenParser.php (89%) rename src/{ => SuperClosure}/SerializableClosure.php (90%) rename src/{ => SuperClosure}/SuperClosure.php (98%) diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..bc186a0 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,20 @@ +filter: + paths: + - src/ + +before_commands: + - 'composer install --no-interaction --prefer-source --dev' + - 'cp phpunit.xml.dist phpunit.xml' + +tools: + sensiolabs_security_checker: true + php_code_coverage: + test_command: vendor/bin/phpunit + config_path: phpunit.xml + php_hhvm: + config: + unknown_class: true + unknown_base_class: true + unknown_function: true + unknown_object_method: true + unknown_trait: true diff --git a/.travis.yml b/.travis.yml index afa9172..c5c7fee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,20 @@ language: php + php: - 5.3 - 5.4 - 5.5 + - hhvm + before_script: -- sh -c 'if [ $(php -r "echo PHP_MINOR_VERSION;") -le 4 ]; then echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' -- cp phpunit.xml.dist phpunit.xml -- composer install --dev -script: vendor/bin/phpunit --coverage-text + - composer self-update + - composer install --no-interaction --prefer-source --dev + - cp phpunit.xml.dist phpunit.xml + +script: + - vendor/bin/phpunit + +matrix: + allow_failures: + - php: hhvm + fast_finish: true diff --git a/README.md b/README.md index f681ac2..bf1a88a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ I'm not joking, *you really can serialize a PHP closure*! require 'vendor/autoload.php'; -use Jeremeamia\SuperClosure\SerializableClosure; +use SuperClosure\SerializableClosure; $greeting = 'Hello'; $helloWorld = new SerializableClosure(function ($name = 'World') use ($greeting) { diff --git a/autoload.php b/autoload.php deleted file mode 100644 index b31b958..0000000 --- a/autoload.php +++ /dev/null @@ -1,28 +0,0 @@ - Hello, Jeremy! -$serialized = serialize_closure($helloWorld); +$serialized = SuperClosure\serialize($helloWorld); $unserialized = unserialize($serialized); $unserialized(); diff --git a/src/ClosureParser/ClosureParserFactory.php b/src/ClosureParser/ClosureParserFactory.php deleted file mode 100644 index cc46499..0000000 --- a/src/ClosureParser/ClosureParserFactory.php +++ /dev/null @@ -1,75 +0,0 @@ - 'Jeremeamia\SuperClosure\ClosureParser\Token\TokenParser', - self::AST_PARSER => 'Jeremeamia\SuperClosure\ClosureParser\Ast\AstParser', - ); - - protected $defaultOptions = array( - self::PARSER_CLASS => null, - self::TURBO_MODE => false, - Parser::HANDLE_CLOSURE_BINDINGS => true, - Parser::HANDLE_MAGIC_CONSTANTS => true, - Parser::HANDLE_CLASS_NAMES => true, - Parser::VALIDATE_TOKENS => true, - ); - - /** - * @param array $options - * - * @return \Jeremeamia\SuperClosure\ClosureParser\AbstractClosureParser - * @throws \InvalidArgumentException - */ - public function create(array $options = array()) - { - $options = $options + $this->defaultOptions; - - // Just turn on turbo mode to make it go fast - if ($options[self::TURBO_MODE]) { - return new TurboParser(array( - Parser::HANDLE_CLOSURE_BINDINGS => false, - Parser::HANDLE_MAGIC_CONSTANTS => false, - Parser::HANDLE_CLASS_NAMES => false, - Parser::VALIDATE_TOKENS => false, - )); - } - - // Use a particular parser class, if specified - if ($options[self::PARSER_CLASS]) { - if (isset($this->parserClasses[$options[self::PARSER_CLASS]])) { - $parserClass = $this->parserClasses[$options[self::PARSER_CLASS]]; - } elseif (class_exists($options[self::PARSER_CLASS])) { - $parserClass = $options[self::PARSER_CLASS]; - } else { - throw new \InvalidArgumentException('The parser class you specified does not exist.'); - } - // Use the AST parser if requiring features that only the AST parser provides - } elseif ($options[Parser::HANDLE_MAGIC_CONSTANTS] || $options[Parser::HANDLE_CLASS_NAMES]) { - $parserClass = $this->parserClasses[self::AST_PARSER]; - // Otherwise, use the token parser, because it's faster - } else { - $parserClass = $this->parserClasses[self::TOKEN_PARSER]; - } - - return new $parserClass($options); - } - - public function setDefaultOptions(array $options = array()) - { - $this->defaultOptions = $options; - - return $this; - } -} diff --git a/src/ClosureParser/ClosureParserInterface.php b/src/ClosureParser/ClosureParserInterface.php deleted file mode 100644 index 5463082..0000000 --- a/src/ClosureParser/ClosureParserInterface.php +++ /dev/null @@ -1,21 +0,0 @@ -getClosureScopeClass()) { - $scope = $scope->getName(); - } + } - return new self($reflection->getClosureThis(), $scope); + /** @var \ReflectionFunction $scope */ + if ($scope = $reflection->getClosureScopeClass()) { + $scope = $scope->getName(); } + + return new self($reflection->getClosureThis(), $scope); } /** diff --git a/src/ClosureParser/AbstractClosureContext.php b/src/SuperClosure/ClosureParser/AbstractClosureContext.php similarity index 91% rename from src/ClosureParser/AbstractClosureContext.php rename to src/SuperClosure/ClosureParser/AbstractClosureContext.php index 8207975..8717472 100644 --- a/src/ClosureParser/AbstractClosureContext.php +++ b/src/SuperClosure/ClosureParser/AbstractClosureContext.php @@ -1,8 +1,8 @@ options = $options + $this->getDefaultOptions(); + $this->options = $this->getDefaultOptions(); + if ($options) { + $this->options->merge($options); + } } /** @@ -28,7 +31,7 @@ public function __construct(array $options = array()) protected function prepareClosure($closure) { if ($closure instanceof \Closure) { - $closure = new SuperClosure($closure); + return new SuperClosure($closure); } if ($closure instanceof SuperClosure) { @@ -39,7 +42,7 @@ protected function prepareClosure($closure) } /** - * @return array + * @return Options */ abstract protected function getDefaultOptions(); } diff --git a/src/ClosureParser/Ast/AstClosureContext.php b/src/SuperClosure/ClosureParser/Ast/AstClosureContext.php similarity index 81% rename from src/ClosureParser/Ast/AstClosureContext.php rename to src/SuperClosure/ClosureParser/Ast/AstClosureContext.php index 281397b..539c586 100644 --- a/src/ClosureParser/Ast/AstClosureContext.php +++ b/src/SuperClosure/ClosureParser/Ast/AstClosureContext.php @@ -1,10 +1,10 @@ true, - Parser::HANDLE_MAGIC_CONSTANTS => true, - Parser::HANDLE_CLASS_NAMES => true, - ); + return new Options(array( + Options::HANDLE_CLOSURE_BINDINGS => true, + Options::HANDLE_MAGIC_CONSTANTS => true, + Options::HANDLE_CLASS_NAMES => true, + )); } public function parse($closure) @@ -38,7 +38,7 @@ public function parse($closure) } $closureLocation = $closureLocator->getLocation(); - if ($this->options[Parser::HANDLE_MAGIC_CONSTANTS]) { + if ($this->options[Options::HANDLE_MAGIC_CONSTANTS]) { // Resolve additional nodes by making a second pass through just the closure's nodes $closureTraverser = new \PHPParser_NodeTraverser(); $closureTraverser->addVisitor(new MagicConstantVisitor($closureLocation)); @@ -50,7 +50,7 @@ public function parse($closure) $astPrinter = new \PHPParser_PrettyPrinter_Default(); $closureCode = $astPrinter->prettyPrint(array($closureAst)); $closureVariables = $this->determineVariables($closureAst, $closureReflection); - $closureBinding = $this->options[Parser::HANDLE_CLOSURE_BINDINGS] ? $closure->getBinding() : null; + $closureBinding = $this->options[Options::HANDLE_CLOSURE_BINDINGS] ? $closure->getBinding() : null; return new AstClosureContext($closureCode, $closureVariables, $closureAst, $closureLocation, $closureBinding); } @@ -70,7 +70,7 @@ private function locateClosure(\ReflectionFunction $closureReflection) $fileAst = $this->getFileAst($closureReflection); $fileTraverser = new \PHPParser_NodeTraverser(); - if ($this->options[Parser::HANDLE_CLASS_NAMES]) { + if ($this->options[Options::HANDLE_CLASS_NAMES]) { $fileTraverser->addVisitor(new \PHPParser_NodeVisitor_NameResolver); } $fileTraverser->addVisitor($closureLocator); @@ -87,11 +87,17 @@ private function locateClosure(\ReflectionFunction $closureReflection) /** * @param \ReflectionFunction $closureReflection * + * @throws ClosureParsingException * @return \PHPParser_Node[] */ private function getFileAst(\ReflectionFunction $closureReflection) { - $fileContents = file_get_contents($closureReflection->getFileName()); + $fileName = $closureReflection->getFileName(); + if (!file_exists($fileName)) { + throw new ClosureParsingException("The file containing the closure, \"{$fileName}\" did not exist."); + } + + $fileContents = file_get_contents($fileName); $parser = new \PHPParser_Parser(new \PHPParser_Lexer_Emulative); $fileAst = $parser->parse($fileContents); diff --git a/src/ClosureParser/Ast/Visitor/ClosureLocatorVisitor.php b/src/SuperClosure/ClosureParser/Ast/Visitor/ClosureLocatorVisitor.php similarity index 95% rename from src/ClosureParser/Ast/Visitor/ClosureLocatorVisitor.php rename to src/SuperClosure/ClosureParser/Ast/Visitor/ClosureLocatorVisitor.php index 2d459de..214c60f 100644 --- a/src/ClosureParser/Ast/Visitor/ClosureLocatorVisitor.php +++ b/src/SuperClosure/ClosureParser/Ast/Visitor/ClosureLocatorVisitor.php @@ -1,9 +1,9 @@ true, + Options::HANDLE_MAGIC_CONSTANTS => true, + Options::HANDLE_CLASS_NAMES => true, + Options::VALIDATE_TOKENS => true, + ); + + /** + * @param array $options + * + * @return \SuperClosure\ClosureParser\AbstractClosureParser + */ + public function create(array $options = array()) + { + // Build an options array from the default options and provided options + $options = $options + $this->defaultOptions; + + // Handle the turbo mode option specially + if (isset($options[Options::TURBO_MODE]) && $options[Options::TURBO_MODE]) { + $options = array( + Options::HANDLE_CLOSURE_BINDINGS => false, + Options::HANDLE_MAGIC_CONSTANTS => false, + Options::HANDLE_CLASS_NAMES => false, + Options::VALIDATE_TOKENS => false, + ); + } + + // Instantiate an options object + $options = new Options($options); + + // Use the AST parser if requiring features that only the AST parser provides + if ($options[Options::HANDLE_MAGIC_CONSTANTS] || $options[Options::HANDLE_CLASS_NAMES]) { + $parser = new AstParser($options); + // Otherwise, use the token parser, because it's faster + } else { + $parser = new TokenParser($options); + } + + return $parser; + } + + /** + * @param array $options + * + * @return $this + */ + public function setDefaultOptions(array $options = array()) + { + $this->defaultOptions = $options; + + return $this; + } +} diff --git a/src/SuperClosure/ClosureParser/ClosureParserInterface.php b/src/SuperClosure/ClosureParser/ClosureParserInterface.php new file mode 100644 index 0000000..6dd8520 --- /dev/null +++ b/src/SuperClosure/ClosureParser/ClosureParserInterface.php @@ -0,0 +1,16 @@ + true, + self::HANDLE_MAGIC_CONSTANTS => true, + self::HANDLE_CLASS_NAMES => true, + self::VALIDATE_TOKENS => true, + ); + + /** + * @var array The internal array holding the options + */ + protected $options; + + /** + * @param array $options + */ + public function __construct(array $options = array()) + { + $this->options = $options + self::$defaultOptions; + } + + /** + * @param Options $options + */ + public function merge(Options $options) + { + $this->options = $options->toArray() + $this->options; + } + + public function offsetExists($key) + { + return isset($this->options[$key]); + } + + public function offsetGet($key) + { + return isset($this->options[$key]) ? $this->options[$key] : null; + } + + public function offsetSet($key, $value) + { + $this->options[$key] = $value; + } + + public function offsetUnset($key) + { + $this->options[$key] = null; + } + + /** + * @return array + */ + public function toArray() + { + return $this->options; + } +} diff --git a/src/ClosureParser/Token/Token.php b/src/SuperClosure/ClosureParser/Token/Token.php similarity index 83% rename from src/ClosureParser/Token/Token.php rename to src/SuperClosure/ClosureParser/Token/Token.php index e4932f6..54fa9ab 100644 --- a/src/ClosureParser/Token/Token.php +++ b/src/SuperClosure/ClosureParser/Token/Token.php @@ -1,6 +1,6 @@ true, - Parser::HANDLE_CLOSURE_BINDINGS => true, - ); + return new Options(array( + Options::VALIDATE_TOKENS => true, + Options::HANDLE_CLOSURE_BINDINGS => true, + )); } public function parse($closure) @@ -27,13 +28,13 @@ public function parse($closure) $closureTokens = $this->fetchTokens($closureReflection); // Only validate the tokens if configured to do so - if ($this->options[Parser::VALIDATE_TOKENS]) { + if ($this->options[Options::VALIDATE_TOKENS]) { $closureTokens = $this->validateTokens($closureTokens); } $closureCode = implode('', $closureTokens); $closureVariables = $this->determineVariables($closureReflection, $closureTokens); - $closureBinding = $this->options[Parser::HANDLE_CLOSURE_BINDINGS] ? $closure->getBinding() : null; + $closureBinding = $this->options[Options::HANDLE_CLOSURE_BINDINGS] ? $closure->getBinding() : null; return new TokenClosureContext($closureCode, $closureVariables, $closureTokens, $closureBinding); } @@ -49,7 +50,11 @@ public function parse($closure) protected function fetchTokens(\ReflectionFunction $closureReflection) { // Load the file containing the code for the function - $file = new \SplFileObject($closureReflection->getFileName()); + $fileName = $closureReflection->getFileName(); + if (!file_exists($fileName)) { + throw new ClosureParsingException("The file containing the closure, \"{$fileName}\" did not exist."); + } + $file = new \SplFileObject($fileName); // Identify the first and last lines of the code for the function $firstLine = $closureReflection->getStartLine(); diff --git a/src/SerializableClosure.php b/src/SuperClosure/SerializableClosure.php similarity index 90% rename from src/SerializableClosure.php rename to src/SuperClosure/SerializableClosure.php index 13037d0..d08a584 100644 --- a/src/SerializableClosure.php +++ b/src/SuperClosure/SerializableClosure.php @@ -1,9 +1,9 @@ fetchSerializableData(); - return serialize(array($this->code, $this->variables, $this->binding)); + return \serialize(array($this->code, $this->variables, $this->binding)); } /** @@ -81,7 +81,7 @@ public function serialize() public function unserialize($serialized) { // Unserialize the data we need to reconstruct the SuperClosure - list($this->code, $this->variables, $this->binding) = unserialize($serialized); + list($this->code, $this->variables, $this->binding) = \unserialize($serialized); // Simulate the original context the Closure was created in extract($this->variables); diff --git a/src/SuperClosure.php b/src/SuperClosure/SuperClosure.php similarity index 98% rename from src/SuperClosure.php rename to src/SuperClosure/SuperClosure.php index 456d8a0..188c09e 100644 --- a/src/SuperClosure.php +++ b/src/SuperClosure/SuperClosure.php @@ -1,6 +1,6 @@ add('SuperClosure\\Test\\', __DIR__);