diff options
Diffstat (limited to 'vendor/phpspec/prophecy/src/Prophecy/Doubler')
23 files changed, 2287 insertions, 0 deletions
diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/CachedDoubler.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/CachedDoubler.php new file mode 100644 index 0000000..d6b6b1a --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/CachedDoubler.php @@ -0,0 +1,68 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler; + +use ReflectionClass; + +/** + * Cached class doubler. + * Prevents mirroring/creation of the same structure twice. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class CachedDoubler extends Doubler +{ + private $classes = array(); + + /** + * {@inheritdoc} + */ + public function registerClassPatch(ClassPatch\ClassPatchInterface $patch) + { + $this->classes[] = array(); + + parent::registerClassPatch($patch); + } + + /** + * {@inheritdoc} + */ + protected function createDoubleClass(ReflectionClass $class = null, array $interfaces) + { + $classId = $this->generateClassId($class, $interfaces); + if (isset($this->classes[$classId])) { + return $this->classes[$classId]; + } + + return $this->classes[$classId] = parent::createDoubleClass($class, $interfaces); + } + + /** + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces + * + * @return string + */ + private function generateClassId(ReflectionClass $class = null, array $interfaces) + { + $parts = array(); + if (null !== $class) { + $parts[] = $class->getName(); + } + foreach ($interfaces as $interface) { + $parts[] = $interface->getName(); + } + sort($parts); + + return md5(implode('', $parts)); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ClassPatchInterface.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ClassPatchInterface.php new file mode 100644 index 0000000..d6d1968 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ClassPatchInterface.php @@ -0,0 +1,48 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; + +/** + * Class patch interface. + * Class patches extend doubles functionality or help + * Prophecy to avoid some internal PHP bugs. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +interface ClassPatchInterface +{ + /** + * Checks if patch supports specific class node. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node); + + /** + * Applies patch to the specific class node. + * + * @param ClassNode $node + * @return void + */ + public function apply(ClassNode $node); + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority(); +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php new file mode 100644 index 0000000..61998fc --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php @@ -0,0 +1,72 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; + +/** + * Disable constructor. + * Makes all constructor arguments optional. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class DisableConstructorPatch implements ClassPatchInterface +{ + /** + * Checks if class has `__construct` method. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node) + { + return true; + } + + /** + * Makes all class constructor arguments optional. + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + if (!$node->hasMethod('__construct')) { + $node->addMethod(new MethodNode('__construct', '')); + + return; + } + + $constructor = $node->getMethod('__construct'); + foreach ($constructor->getArguments() as $argument) { + $argument->setDefault(null); + } + + $constructor->setCode(<<<PHP +if (0 < func_num_args()) { + call_user_func_array(array('parent', '__construct'), func_get_args()); +} +PHP + ); + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() + { + return 100; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.php new file mode 100644 index 0000000..fa38fc0 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/HhvmExceptionPatch.php @@ -0,0 +1,63 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; + +/** + * Exception patch for HHVM to remove the stubs from special methods + * + * @author Christophe Coevoet <stof@notk.org> + */ +class HhvmExceptionPatch implements ClassPatchInterface +{ + /** + * Supports exceptions on HHVM. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node) + { + if (!defined('HHVM_VERSION')) { + return false; + } + + return 'Exception' === $node->getParentClass() || is_subclass_of($node->getParentClass(), 'Exception'); + } + + /** + * Removes special exception static methods from the doubled methods. + * + * @param ClassNode $node + * + * @return void + */ + public function apply(ClassNode $node) + { + if ($node->hasMethod('setTraceOptions')) { + $node->getMethod('setTraceOptions')->useParentCode(); + } + if ($node->hasMethod('getTraceOptions')) { + $node->getMethod('getTraceOptions')->useParentCode(); + } + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return -50; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/KeywordPatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/KeywordPatch.php new file mode 100644 index 0000000..41ea2fc --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/KeywordPatch.php @@ -0,0 +1,140 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; + +/** + * Remove method functionality from the double which will clash with php keywords. + * + * @author Milan Magudia <milan@magudia.com> + */ +class KeywordPatch implements ClassPatchInterface +{ + /** + * Support any class + * + * @param ClassNode $node + * + * @return boolean + */ + public function supports(ClassNode $node) + { + return true; + } + + /** + * Remove methods that clash with php keywords + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + $methodNames = array_keys($node->getMethods()); + $methodsToRemove = array_intersect($methodNames, $this->getKeywords()); + foreach ($methodsToRemove as $methodName) { + $node->removeMethod($methodName); + } + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() + { + return 49; + } + + /** + * Returns array of php keywords. + * + * @return array + */ + private function getKeywords() + { + if (\PHP_VERSION_ID >= 70000) { + return array('__halt_compiler'); + } + + return array( + '__halt_compiler', + 'abstract', + 'and', + 'array', + 'as', + 'break', + 'callable', + 'case', + 'catch', + 'class', + 'clone', + 'const', + 'continue', + 'declare', + 'default', + 'die', + 'do', + 'echo', + 'else', + 'elseif', + 'empty', + 'enddeclare', + 'endfor', + 'endforeach', + 'endif', + 'endswitch', + 'endwhile', + 'eval', + 'exit', + 'extends', + 'final', + 'finally', + 'for', + 'foreach', + 'function', + 'global', + 'goto', + 'if', + 'implements', + 'include', + 'include_once', + 'instanceof', + 'insteadof', + 'interface', + 'isset', + 'list', + 'namespace', + 'new', + 'or', + 'print', + 'private', + 'protected', + 'public', + 'require', + 'require_once', + 'return', + 'static', + 'switch', + 'throw', + 'trait', + 'try', + 'unset', + 'use', + 'var', + 'while', + 'xor', + 'yield', + ); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/MagicCallPatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/MagicCallPatch.php new file mode 100644 index 0000000..9ff49cd --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/MagicCallPatch.php @@ -0,0 +1,94 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; +use Prophecy\PhpDocumentor\ClassAndInterfaceTagRetriever; +use Prophecy\PhpDocumentor\MethodTagRetrieverInterface; + +/** + * Discover Magical API using "@method" PHPDoc format. + * + * @author Thomas Tourlourat <thomas@tourlourat.com> + * @author Kévin Dunglas <dunglas@gmail.com> + * @author Théo FIDRY <theo.fidry@gmail.com> + */ +class MagicCallPatch implements ClassPatchInterface +{ + private $tagRetriever; + + public function __construct(MethodTagRetrieverInterface $tagRetriever = null) + { + $this->tagRetriever = null === $tagRetriever ? new ClassAndInterfaceTagRetriever() : $tagRetriever; + } + + /** + * Support any class + * + * @param ClassNode $node + * + * @return boolean + */ + public function supports(ClassNode $node) + { + return true; + } + + /** + * Discover Magical API + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + $types = array_filter($node->getInterfaces(), function ($interface) { + return 0 !== strpos($interface, 'Prophecy\\'); + }); + $types[] = $node->getParentClass(); + + foreach ($types as $type) { + $reflectionClass = new \ReflectionClass($type); + + while ($reflectionClass) { + $tagList = $this->tagRetriever->getTagList($reflectionClass); + + foreach ($tagList as $tag) { + $methodName = $tag->getMethodName(); + + if (empty($methodName)) { + continue; + } + + if (!$reflectionClass->hasMethod($methodName)) { + $methodNode = new MethodNode($methodName); + $methodNode->setStatic($tag->isStatic()); + $node->addMethod($methodNode); + } + } + + $reflectionClass = $reflectionClass->getParentClass(); + } + } + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return integer Priority number (higher - earlier) + */ + public function getPriority() + { + return 50; + } +} + diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php new file mode 100644 index 0000000..081dea8 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php @@ -0,0 +1,104 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; +use Prophecy\Doubler\Generator\Node\ArgumentNode; + +/** + * Add Prophecy functionality to the double. + * This is a core class patch for Prophecy. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class ProphecySubjectPatch implements ClassPatchInterface +{ + /** + * Always returns true. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node) + { + return true; + } + + /** + * Apply Prophecy functionality to class node. + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + $node->addInterface('Prophecy\Prophecy\ProphecySubjectInterface'); + $node->addProperty('objectProphecy', 'private'); + + foreach ($node->getMethods() as $name => $method) { + if ('__construct' === strtolower($name)) { + continue; + } + + if ($method->getReturnType() === 'void') { + $method->setCode( + '$this->getProphecy()->makeProphecyMethodCall(__FUNCTION__, func_get_args());' + ); + } else { + $method->setCode( + 'return $this->getProphecy()->makeProphecyMethodCall(__FUNCTION__, func_get_args());' + ); + } + } + + $prophecySetter = new MethodNode('setProphecy'); + $prophecyArgument = new ArgumentNode('prophecy'); + $prophecyArgument->setTypeHint('Prophecy\Prophecy\ProphecyInterface'); + $prophecySetter->addArgument($prophecyArgument); + $prophecySetter->setCode('$this->objectProphecy = $prophecy;'); + + $prophecyGetter = new MethodNode('getProphecy'); + $prophecyGetter->setCode('return $this->objectProphecy;'); + + if ($node->hasMethod('__call')) { + $__call = $node->getMethod('__call'); + } else { + $__call = new MethodNode('__call'); + $__call->addArgument(new ArgumentNode('name')); + $__call->addArgument(new ArgumentNode('arguments')); + + $node->addMethod($__call, true); + } + + $__call->setCode(<<<PHP +throw new \Prophecy\Exception\Doubler\MethodNotFoundException( + sprintf('Method `%s::%s()` not found.', get_class(\$this), func_get_arg(0)), + \$this->getProphecy(), func_get_arg(0) +); +PHP + ); + + $node->addMethod($prophecySetter, true); + $node->addMethod($prophecyGetter, true); + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() + { + return 0; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.php new file mode 100644 index 0000000..9166aee --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ReflectionClassNewInstancePatch.php @@ -0,0 +1,57 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; + +/** + * ReflectionClass::newInstance patch. + * Makes first argument of newInstance optional, since it works but signature is misleading + * + * @author Florian Klein <florian.klein@free.fr> + */ +class ReflectionClassNewInstancePatch implements ClassPatchInterface +{ + /** + * Supports ReflectionClass + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node) + { + return 'ReflectionClass' === $node->getParentClass(); + } + + /** + * Updates newInstance's first argument to make it optional + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + foreach ($node->getMethod('newInstance')->getArguments() as $argument) { + $argument->setDefault(null); + } + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher = earlier) + */ + public function getPriority() + { + return 50; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php new file mode 100644 index 0000000..ceee94a --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/SplFileInfoPatch.php @@ -0,0 +1,123 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; + +/** + * SplFileInfo patch. + * Makes SplFileInfo and derivative classes usable with Prophecy. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class SplFileInfoPatch implements ClassPatchInterface +{ + /** + * Supports everything that extends SplFileInfo. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node) + { + if (null === $node->getParentClass()) { + return false; + } + return 'SplFileInfo' === $node->getParentClass() + || is_subclass_of($node->getParentClass(), 'SplFileInfo') + ; + } + + /** + * Updated constructor code to call parent one with dummy file argument. + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + if ($node->hasMethod('__construct')) { + $constructor = $node->getMethod('__construct'); + } else { + $constructor = new MethodNode('__construct'); + $node->addMethod($constructor); + } + + if ($this->nodeIsDirectoryIterator($node)) { + $constructor->setCode('return parent::__construct("' . __DIR__ . '");'); + + return; + } + + if ($this->nodeIsSplFileObject($node)) { + $filePath = str_replace('\\','\\\\',__FILE__); + $constructor->setCode('return parent::__construct("' . $filePath .'");'); + + return; + } + + if ($this->nodeIsSymfonySplFileInfo($node)) { + $filePath = str_replace('\\','\\\\',__FILE__); + $constructor->setCode('return parent::__construct("' . $filePath .'", "", "");'); + + return; + } + + $constructor->useParentCode(); + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() + { + return 50; + } + + /** + * @param ClassNode $node + * @return boolean + */ + private function nodeIsDirectoryIterator(ClassNode $node) + { + $parent = $node->getParentClass(); + + return 'DirectoryIterator' === $parent + || is_subclass_of($parent, 'DirectoryIterator'); + } + + /** + * @param ClassNode $node + * @return boolean + */ + private function nodeIsSplFileObject(ClassNode $node) + { + $parent = $node->getParentClass(); + + return 'SplFileObject' === $parent + || is_subclass_of($parent, 'SplFileObject'); + } + + /** + * @param ClassNode $node + * @return boolean + */ + private function nodeIsSymfonySplFileInfo(ClassNode $node) + { + $parent = $node->getParentClass(); + + return 'Symfony\\Component\\Finder\\SplFileInfo' === $parent; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ThrowablePatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ThrowablePatch.php new file mode 100644 index 0000000..b98e943 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ThrowablePatch.php @@ -0,0 +1,95 @@ +<?php + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Exception\Doubler\ClassCreatorException; + +class ThrowablePatch implements ClassPatchInterface +{ + /** + * Checks if patch supports specific class node. + * + * @param ClassNode $node + * @return bool + */ + public function supports(ClassNode $node) + { + return $this->implementsAThrowableInterface($node) && $this->doesNotExtendAThrowableClass($node); + } + + /** + * @param ClassNode $node + * @return bool + */ + private function implementsAThrowableInterface(ClassNode $node) + { + foreach ($node->getInterfaces() as $type) { + if (is_a($type, 'Throwable', true)) { + return true; + } + } + + return false; + } + + /** + * @param ClassNode $node + * @return bool + */ + private function doesNotExtendAThrowableClass(ClassNode $node) + { + return !is_a($node->getParentClass(), 'Throwable', true); + } + + /** + * Applies patch to the specific class node. + * + * @param ClassNode $node + * + * @return void + */ + public function apply(ClassNode $node) + { + $this->checkItCanBeDoubled($node); + $this->setParentClassToException($node); + } + + private function checkItCanBeDoubled(ClassNode $node) + { + $className = $node->getParentClass(); + if ($className !== 'stdClass') { + throw new ClassCreatorException( + sprintf( + 'Cannot double concrete class %s as well as implement Traversable', + $className + ), + $node + ); + } + } + + private function setParentClassToException(ClassNode $node) + { + $node->setParentClass('Exception'); + + $node->removeMethod('getMessage'); + $node->removeMethod('getCode'); + $node->removeMethod('getFile'); + $node->removeMethod('getLine'); + $node->removeMethod('getTrace'); + $node->removeMethod('getPrevious'); + $node->removeMethod('getNext'); + $node->removeMethod('getTraceAsString'); + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() + { + return 100; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/TraversablePatch.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/TraversablePatch.php new file mode 100644 index 0000000..eea0202 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/TraversablePatch.php @@ -0,0 +1,83 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\ClassPatch; + +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; + +/** + * Traversable interface patch. + * Forces classes that implement interfaces, that extend Traversable to also implement Iterator. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class TraversablePatch implements ClassPatchInterface +{ + /** + * Supports nodetree, that implement Traversable, but not Iterator or IteratorAggregate. + * + * @param ClassNode $node + * + * @return bool + */ + public function supports(ClassNode $node) + { + if (in_array('Iterator', $node->getInterfaces())) { + return false; + } + if (in_array('IteratorAggregate', $node->getInterfaces())) { + return false; + } + + foreach ($node->getInterfaces() as $interface) { + if ('Traversable' !== $interface && !is_subclass_of($interface, 'Traversable')) { + continue; + } + if ('Iterator' === $interface || is_subclass_of($interface, 'Iterator')) { + continue; + } + if ('IteratorAggregate' === $interface || is_subclass_of($interface, 'IteratorAggregate')) { + continue; + } + + return true; + } + + return false; + } + + /** + * Forces class to implement Iterator interface. + * + * @param ClassNode $node + */ + public function apply(ClassNode $node) + { + $node->addInterface('Iterator'); + + $node->addMethod(new MethodNode('current')); + $node->addMethod(new MethodNode('key')); + $node->addMethod(new MethodNode('next')); + $node->addMethod(new MethodNode('rewind')); + $node->addMethod(new MethodNode('valid')); + } + + /** + * Returns patch priority, which determines when patch will be applied. + * + * @return int Priority number (higher - earlier) + */ + public function getPriority() + { + return 100; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/DoubleInterface.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/DoubleInterface.php new file mode 100644 index 0000000..699be3a --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/DoubleInterface.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler; + +/** + * Core double interface. + * All doubled classes will implement this one. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +interface DoubleInterface +{ +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php new file mode 100644 index 0000000..a378ae2 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Doubler.php @@ -0,0 +1,146 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler; + +use Doctrine\Instantiator\Instantiator; +use Prophecy\Doubler\ClassPatch\ClassPatchInterface; +use Prophecy\Doubler\Generator\ClassMirror; +use Prophecy\Doubler\Generator\ClassCreator; +use Prophecy\Exception\InvalidArgumentException; +use ReflectionClass; + +/** + * Cached class doubler. + * Prevents mirroring/creation of the same structure twice. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class Doubler +{ + private $mirror; + private $creator; + private $namer; + + /** + * @var ClassPatchInterface[] + */ + private $patches = array(); + + /** + * @var \Doctrine\Instantiator\Instantiator + */ + private $instantiator; + + /** + * Initializes doubler. + * + * @param ClassMirror $mirror + * @param ClassCreator $creator + * @param NameGenerator $namer + */ + public function __construct(ClassMirror $mirror = null, ClassCreator $creator = null, + NameGenerator $namer = null) + { + $this->mirror = $mirror ?: new ClassMirror; + $this->creator = $creator ?: new ClassCreator; + $this->namer = $namer ?: new NameGenerator; + } + + /** + * Returns list of registered class patches. + * + * @return ClassPatchInterface[] + */ + public function getClassPatches() + { + return $this->patches; + } + + /** + * Registers new class patch. + * + * @param ClassPatchInterface $patch + */ + public function registerClassPatch(ClassPatchInterface $patch) + { + $this->patches[] = $patch; + + @usort($this->patches, function (ClassPatchInterface $patch1, ClassPatchInterface $patch2) { + return $patch2->getPriority() - $patch1->getPriority(); + }); + } + + /** + * Creates double from specific class or/and list of interfaces. + * + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces Array of ReflectionClass instances + * @param array $args Constructor arguments + * + * @return DoubleInterface + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function double(ReflectionClass $class = null, array $interfaces, array $args = null) + { + foreach ($interfaces as $interface) { + if (!$interface instanceof ReflectionClass) { + throw new InvalidArgumentException(sprintf( + "[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n". + "a second argument to `Doubler::double(...)`, but got %s.", + is_object($interface) ? get_class($interface).' class' : gettype($interface) + )); + } + } + + $classname = $this->createDoubleClass($class, $interfaces); + $reflection = new ReflectionClass($classname); + + if (null !== $args) { + return $reflection->newInstanceArgs($args); + } + if ((null === $constructor = $reflection->getConstructor()) + || ($constructor->isPublic() && !$constructor->isFinal())) { + return $reflection->newInstance(); + } + + if (!$this->instantiator) { + $this->instantiator = new Instantiator(); + } + + return $this->instantiator->instantiate($classname); + } + + /** + * Creates double class and returns its FQN. + * + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces + * + * @return string + */ + protected function createDoubleClass(ReflectionClass $class = null, array $interfaces) + { + $name = $this->namer->name($class, $interfaces); + $node = $this->mirror->reflect($class, $interfaces); + + foreach ($this->patches as $patch) { + if ($patch->supports($node)) { + $patch->apply($node); + } + } + + $this->creator->create($name, $node); + + return $name; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php new file mode 100644 index 0000000..891faa8 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php @@ -0,0 +1,129 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator; + +/** + * Class code creator. + * Generates PHP code for specific class node tree. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class ClassCodeGenerator +{ + /** + * @var TypeHintReference + */ + private $typeHintReference; + + public function __construct(TypeHintReference $typeHintReference = null) + { + $this->typeHintReference = $typeHintReference ?: new TypeHintReference(); + } + + /** + * Generates PHP code for class node. + * + * @param string $classname + * @param Node\ClassNode $class + * + * @return string + */ + public function generate($classname, Node\ClassNode $class) + { + $parts = explode('\\', $classname); + $classname = array_pop($parts); + $namespace = implode('\\', $parts); + + $code = sprintf("class %s extends \%s implements %s {\n", + $classname, $class->getParentClass(), implode(', ', + array_map(function ($interface) {return '\\'.$interface;}, $class->getInterfaces()) + ) + ); + + foreach ($class->getProperties() as $name => $visibility) { + $code .= sprintf("%s \$%s;\n", $visibility, $name); + } + $code .= "\n"; + + foreach ($class->getMethods() as $method) { + $code .= $this->generateMethod($method)."\n"; + } + $code .= "\n}"; + + return sprintf("namespace %s {\n%s\n}", $namespace, $code); + } + + private function generateMethod(Node\MethodNode $method) + { + $php = sprintf("%s %s function %s%s(%s)%s {\n", + $method->getVisibility(), + $method->isStatic() ? 'static' : '', + $method->returnsReference() ? '&':'', + $method->getName(), + implode(', ', $this->generateArguments($method->getArguments())), + $this->getReturnType($method) + ); + $php .= $method->getCode()."\n"; + + return $php.'}'; + } + + /** + * @return string + */ + private function getReturnType(Node\MethodNode $method) + { + if (version_compare(PHP_VERSION, '7.1', '>=')) { + if ($method->hasReturnType()) { + return $method->hasNullableReturnType() + ? sprintf(': ?%s', $method->getReturnType()) + : sprintf(': %s', $method->getReturnType()); + } + } + + if (version_compare(PHP_VERSION, '7.0', '>=')) { + return $method->hasReturnType() && $method->getReturnType() !== 'void' + ? sprintf(': %s', $method->getReturnType()) + : ''; + } + + return ''; + } + + private function generateArguments(array $arguments) + { + $typeHintReference = $this->typeHintReference; + return array_map(function (Node\ArgumentNode $argument) use ($typeHintReference) { + $php = ''; + + if (version_compare(PHP_VERSION, '7.1', '>=')) { + $php .= $argument->isNullable() ? '?' : ''; + } + + if ($hint = $argument->getTypeHint()) { + $php .= $typeHintReference->isBuiltInParamTypeHint($hint) ? $hint : '\\'.$hint; + } + + $php .= ' '.($argument->isPassedByReference() ? '&' : ''); + + $php .= $argument->isVariadic() ? '...' : ''; + + $php .= '$'.$argument->getName(); + + if ($argument->isOptional() && !$argument->isVariadic()) { + $php .= ' = '.var_export($argument->getDefault(), true); + } + + return $php; + }, $arguments); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.php new file mode 100644 index 0000000..882a4a4 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.php @@ -0,0 +1,67 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator; + +use Prophecy\Exception\Doubler\ClassCreatorException; + +/** + * Class creator. + * Creates specific class in current environment. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class ClassCreator +{ + private $generator; + + /** + * Initializes creator. + * + * @param ClassCodeGenerator $generator + */ + public function __construct(ClassCodeGenerator $generator = null) + { + $this->generator = $generator ?: new ClassCodeGenerator; + } + + /** + * Creates class. + * + * @param string $classname + * @param Node\ClassNode $class + * + * @return mixed + * + * @throws \Prophecy\Exception\Doubler\ClassCreatorException + */ + public function create($classname, Node\ClassNode $class) + { + $code = $this->generator->generate($classname, $class); + $return = eval($code); + + if (!class_exists($classname, false)) { + if (count($class->getInterfaces())) { + throw new ClassCreatorException(sprintf( + 'Could not double `%s` and implement interfaces: [%s].', + $class->getParentClass(), implode(', ', $class->getInterfaces()) + ), $class); + } + + throw new ClassCreatorException( + sprintf('Could not double `%s`.', $class->getParentClass()), + $class + ); + } + + return $return; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php new file mode 100644 index 0000000..c5f9a5c --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php @@ -0,0 +1,260 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator; + +use Prophecy\Exception\InvalidArgumentException; +use Prophecy\Exception\Doubler\ClassMirrorException; +use ReflectionClass; +use ReflectionMethod; +use ReflectionParameter; + +/** + * Class mirror. + * Core doubler class. Mirrors specific class and/or interfaces into class node tree. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class ClassMirror +{ + private static $reflectableMethods = array( + '__construct', + '__destruct', + '__sleep', + '__wakeup', + '__toString', + '__call', + '__invoke' + ); + + /** + * Reflects provided arguments into class node. + * + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces + * + * @return Node\ClassNode + * + * @throws \Prophecy\Exception\InvalidArgumentException + */ + public function reflect(ReflectionClass $class = null, array $interfaces) + { + $node = new Node\ClassNode; + + if (null !== $class) { + if (true === $class->isInterface()) { + throw new InvalidArgumentException(sprintf( + "Could not reflect %s as a class, because it\n". + "is interface - use the second argument instead.", + $class->getName() + )); + } + + $this->reflectClassToNode($class, $node); + } + + foreach ($interfaces as $interface) { + if (!$interface instanceof ReflectionClass) { + throw new InvalidArgumentException(sprintf( + "[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n". + "a second argument to `ClassMirror::reflect(...)`, but got %s.", + is_object($interface) ? get_class($interface).' class' : gettype($interface) + )); + } + if (false === $interface->isInterface()) { + throw new InvalidArgumentException(sprintf( + "Could not reflect %s as an interface, because it\n". + "is class - use the first argument instead.", + $interface->getName() + )); + } + + $this->reflectInterfaceToNode($interface, $node); + } + + $node->addInterface('Prophecy\Doubler\Generator\ReflectionInterface'); + + return $node; + } + + private function reflectClassToNode(ReflectionClass $class, Node\ClassNode $node) + { + if (true === $class->isFinal()) { + throw new ClassMirrorException(sprintf( + 'Could not reflect class %s as it is marked final.', $class->getName() + ), $class); + } + + $node->setParentClass($class->getName()); + + foreach ($class->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) { + if (false === $method->isProtected()) { + continue; + } + + $this->reflectMethodToNode($method, $node); + } + + foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + if (0 === strpos($method->getName(), '_') + && !in_array($method->getName(), self::$reflectableMethods)) { + continue; + } + + if (true === $method->isFinal()) { + $node->addUnextendableMethod($method->getName()); + continue; + } + + $this->reflectMethodToNode($method, $node); + } + } + + private function reflectInterfaceToNode(ReflectionClass $interface, Node\ClassNode $node) + { + $node->addInterface($interface->getName()); + + foreach ($interface->getMethods() as $method) { + $this->reflectMethodToNode($method, $node); + } + } + + private function reflectMethodToNode(ReflectionMethod $method, Node\ClassNode $classNode) + { + $node = new Node\MethodNode($method->getName()); + + if (true === $method->isProtected()) { + $node->setVisibility('protected'); + } + + if (true === $method->isStatic()) { + $node->setStatic(); + } + + if (true === $method->returnsReference()) { + $node->setReturnsReference(); + } + + if (version_compare(PHP_VERSION, '7.0', '>=') && $method->hasReturnType()) { + $returnType = (string) $method->getReturnType(); + $returnTypeLower = strtolower($returnType); + + if ('self' === $returnTypeLower) { + $returnType = $method->getDeclaringClass()->getName(); + } + if ('parent' === $returnTypeLower) { + $returnType = $method->getDeclaringClass()->getParentClass()->getName(); + } + + $node->setReturnType($returnType); + + if (version_compare(PHP_VERSION, '7.1', '>=') && $method->getReturnType()->allowsNull()) { + $node->setNullableReturnType(true); + } + } + + if (is_array($params = $method->getParameters()) && count($params)) { + foreach ($params as $param) { + $this->reflectArgumentToNode($param, $node); + } + } + + $classNode->addMethod($node); + } + + private function reflectArgumentToNode(ReflectionParameter $parameter, Node\MethodNode $methodNode) + { + $name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName(); + $node = new Node\ArgumentNode($name); + + $node->setTypeHint($this->getTypeHint($parameter)); + + if ($this->isVariadic($parameter)) { + $node->setAsVariadic(); + } + + if ($this->hasDefaultValue($parameter)) { + $node->setDefault($this->getDefaultValue($parameter)); + } + + if ($parameter->isPassedByReference()) { + $node->setAsPassedByReference(); + } + + $node->setAsNullable($this->isNullable($parameter)); + + $methodNode->addArgument($node); + } + + private function hasDefaultValue(ReflectionParameter $parameter) + { + if ($this->isVariadic($parameter)) { + return false; + } + + if ($parameter->isDefaultValueAvailable()) { + return true; + } + + return $parameter->isOptional() || $this->isNullable($parameter); + } + + private function getDefaultValue(ReflectionParameter $parameter) + { + if (!$parameter->isDefaultValueAvailable()) { + return null; + } + + return $parameter->getDefaultValue(); + } + + private function getTypeHint(ReflectionParameter $parameter) + { + if (null !== $className = $this->getParameterClassName($parameter)) { + return $className; + } + + if (true === $parameter->isArray()) { + return 'array'; + } + + if (version_compare(PHP_VERSION, '5.4', '>=') && true === $parameter->isCallable()) { + return 'callable'; + } + + if (version_compare(PHP_VERSION, '7.0', '>=') && true === $parameter->hasType()) { + return (string) $parameter->getType(); + } + + return null; + } + + private function isVariadic(ReflectionParameter $parameter) + { + return PHP_VERSION_ID >= 50600 && $parameter->isVariadic(); + } + + private function isNullable(ReflectionParameter $parameter) + { + return $parameter->allowsNull() && null !== $this->getTypeHint($parameter); + } + + private function getParameterClassName(ReflectionParameter $parameter) + { + try { + return $parameter->getClass() ? $parameter->getClass()->getName() : null; + } catch (\ReflectionException $e) { + preg_match('/\[\s\<\w+?>\s([\w,\\\]+)/s', $parameter, $matches); + + return isset($matches[1]) ? $matches[1] : null; + } + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentNode.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentNode.php new file mode 100644 index 0000000..dd29b68 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentNode.php @@ -0,0 +1,102 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator\Node; + +/** + * Argument node. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class ArgumentNode +{ + private $name; + private $typeHint; + private $default; + private $optional = false; + private $byReference = false; + private $isVariadic = false; + private $isNullable = false; + + /** + * @param string $name + */ + public function __construct($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + + public function getTypeHint() + { + return $this->typeHint; + } + + public function setTypeHint($typeHint = null) + { + $this->typeHint = $typeHint; + } + + public function hasDefault() + { + return $this->isOptional() && !$this->isVariadic(); + } + + public function getDefault() + { + return $this->default; + } + + public function setDefault($default = null) + { + $this->optional = true; + $this->default = $default; + } + + public function isOptional() + { + return $this->optional; + } + + public function setAsPassedByReference($byReference = true) + { + $this->byReference = $byReference; + } + + public function isPassedByReference() + { + return $this->byReference; + } + + public function setAsVariadic($isVariadic = true) + { + $this->isVariadic = $isVariadic; + } + + public function isVariadic() + { + return $this->isVariadic; + } + + public function isNullable() + { + return $this->isNullable; + } + + public function setAsNullable($isNullable = true) + { + $this->isNullable = $isNullable; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ClassNode.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ClassNode.php new file mode 100644 index 0000000..f7bd285 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ClassNode.php @@ -0,0 +1,169 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator\Node; + +use Prophecy\Exception\Doubler\MethodNotExtendableException; +use Prophecy\Exception\InvalidArgumentException; + +/** + * Class node. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class ClassNode +{ + private $parentClass = 'stdClass'; + private $interfaces = array(); + private $properties = array(); + private $unextendableMethods = array(); + + /** + * @var MethodNode[] + */ + private $methods = array(); + + public function getParentClass() + { + return $this->parentClass; + } + + /** + * @param string $class + */ + public function setParentClass($class) + { + $this->parentClass = $class ?: 'stdClass'; + } + + /** + * @return string[] + */ + public function getInterfaces() + { + return $this->interfaces; + } + + /** + * @param string $interface + */ + public function addInterface($interface) + { + if ($this->hasInterface($interface)) { + return; + } + + array_unshift($this->interfaces, $interface); + } + + /** + * @param string $interface + * + * @return bool + */ + public function hasInterface($interface) + { + return in_array($interface, $this->interfaces); + } + + public function getProperties() + { + return $this->properties; + } + + public function addProperty($name, $visibility = 'public') + { + $visibility = strtolower($visibility); + + if (!in_array($visibility, array('public', 'private', 'protected'))) { + throw new InvalidArgumentException(sprintf( + '`%s` property visibility is not supported.', $visibility + )); + } + + $this->properties[$name] = $visibility; + } + + /** + * @return MethodNode[] + */ + public function getMethods() + { + return $this->methods; + } + + public function addMethod(MethodNode $method, $force = false) + { + if (!$this->isExtendable($method->getName())){ + $message = sprintf( + 'Method `%s` is not extendable, so can not be added.', $method->getName() + ); + throw new MethodNotExtendableException($message, $this->getParentClass(), $method->getName()); + } + + if ($force || !isset($this->methods[$method->getName()])) { + $this->methods[$method->getName()] = $method; + } + } + + public function removeMethod($name) + { + unset($this->methods[$name]); + } + + /** + * @param string $name + * + * @return MethodNode|null + */ + public function getMethod($name) + { + return $this->hasMethod($name) ? $this->methods[$name] : null; + } + + /** + * @param string $name + * + * @return bool + */ + public function hasMethod($name) + { + return isset($this->methods[$name]); + } + + /** + * @return string[] + */ + public function getUnextendableMethods() + { + return $this->unextendableMethods; + } + + /** + * @param string $unextendableMethod + */ + public function addUnextendableMethod($unextendableMethod) + { + if (!$this->isExtendable($unextendableMethod)){ + return; + } + $this->unextendableMethods[] = $unextendableMethod; + } + + /** + * @param string $method + * @return bool + */ + public function isExtendable($method) + { + return !in_array($method, $this->unextendableMethods); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/MethodNode.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/MethodNode.php new file mode 100644 index 0000000..c74b483 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/MethodNode.php @@ -0,0 +1,198 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator\Node; + +use Prophecy\Doubler\Generator\TypeHintReference; +use Prophecy\Exception\InvalidArgumentException; + +/** + * Method node. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class MethodNode +{ + private $name; + private $code; + private $visibility = 'public'; + private $static = false; + private $returnsReference = false; + private $returnType; + private $nullableReturnType = false; + + /** + * @var ArgumentNode[] + */ + private $arguments = array(); + + /** + * @var TypeHintReference + */ + private $typeHintReference; + + /** + * @param string $name + * @param string $code + */ + public function __construct($name, $code = null, TypeHintReference $typeHintReference = null) + { + $this->name = $name; + $this->code = $code; + $this->typeHintReference = $typeHintReference ?: new TypeHintReference(); + } + + public function getVisibility() + { + return $this->visibility; + } + + /** + * @param string $visibility + */ + public function setVisibility($visibility) + { + $visibility = strtolower($visibility); + + if (!in_array($visibility, array('public', 'private', 'protected'))) { + throw new InvalidArgumentException(sprintf( + '`%s` method visibility is not supported.', $visibility + )); + } + + $this->visibility = $visibility; + } + + public function isStatic() + { + return $this->static; + } + + public function setStatic($static = true) + { + $this->static = (bool) $static; + } + + public function returnsReference() + { + return $this->returnsReference; + } + + public function setReturnsReference() + { + $this->returnsReference = true; + } + + public function getName() + { + return $this->name; + } + + public function addArgument(ArgumentNode $argument) + { + $this->arguments[] = $argument; + } + + /** + * @return ArgumentNode[] + */ + public function getArguments() + { + return $this->arguments; + } + + public function hasReturnType() + { + return null !== $this->returnType; + } + + /** + * @param string $type + */ + public function setReturnType($type = null) + { + if ($type === '' || $type === null) { + $this->returnType = null; + return; + } + $typeMap = array( + 'double' => 'float', + 'real' => 'float', + 'boolean' => 'bool', + 'integer' => 'int', + ); + if (isset($typeMap[$type])) { + $type = $typeMap[$type]; + } + $this->returnType = $this->typeHintReference->isBuiltInReturnTypeHint($type) ? + $type : + '\\' . ltrim($type, '\\'); + } + + public function getReturnType() + { + return $this->returnType; + } + + /** + * @param bool $bool + */ + public function setNullableReturnType($bool = true) + { + $this->nullableReturnType = (bool) $bool; + } + + /** + * @return bool + */ + public function hasNullableReturnType() + { + return $this->nullableReturnType; + } + + /** + * @param string $code + */ + public function setCode($code) + { + $this->code = $code; + } + + public function getCode() + { + if ($this->returnsReference) + { + return "throw new \Prophecy\Exception\Doubler\ReturnByReferenceException('Returning by reference not supported', get_class(\$this), '{$this->name}');"; + } + + return (string) $this->code; + } + + public function useParentCode() + { + $this->code = sprintf( + 'return parent::%s(%s);', $this->getName(), implode(', ', + array_map(array($this, 'generateArgument'), $this->arguments) + ) + ); + } + + private function generateArgument(ArgumentNode $arg) + { + $argument = '$'.$arg->getName(); + + if ($arg->isVariadic()) { + $argument = '...'.$argument; + } + + return $argument; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ReflectionInterface.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ReflectionInterface.php new file mode 100644 index 0000000..d720b15 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ReflectionInterface.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler\Generator; + +/** + * Reflection interface. + * All reflected classes implement this interface. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +interface ReflectionInterface +{ +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/TypeHintReference.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/TypeHintReference.php new file mode 100644 index 0000000..ce95202 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/TypeHintReference.php @@ -0,0 +1,46 @@ +<?php + +namespace Prophecy\Doubler\Generator; + +/** + * Tells whether a keyword refers to a class or to a built-in type for the + * current version of php + */ +final class TypeHintReference +{ + public function isBuiltInParamTypeHint($type) + { + switch ($type) { + case 'self': + case 'array': + return true; + + case 'callable': + return PHP_VERSION_ID >= 50400; + + case 'bool': + case 'float': + case 'int': + case 'string': + return PHP_VERSION_ID >= 70000; + + case 'iterable': + return PHP_VERSION_ID >= 70100; + + case 'object': + return PHP_VERSION_ID >= 70200; + + default: + return false; + } + } + + public function isBuiltInReturnTypeHint($type) + { + if ($type === 'void') { + return PHP_VERSION_ID >= 70100; + } + + return $this->isBuiltInParamTypeHint($type); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/LazyDouble.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/LazyDouble.php new file mode 100644 index 0000000..8a99c4c --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/LazyDouble.php @@ -0,0 +1,127 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler; + +use Prophecy\Exception\Doubler\DoubleException; +use Prophecy\Exception\Doubler\ClassNotFoundException; +use Prophecy\Exception\Doubler\InterfaceNotFoundException; +use ReflectionClass; + +/** + * Lazy double. + * Gives simple interface to describe double before creating it. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class LazyDouble +{ + private $doubler; + private $class; + private $interfaces = array(); + private $arguments = null; + private $double; + + /** + * Initializes lazy double. + * + * @param Doubler $doubler + */ + public function __construct(Doubler $doubler) + { + $this->doubler = $doubler; + } + + /** + * Tells doubler to use specific class as parent one for double. + * + * @param string|ReflectionClass $class + * + * @throws \Prophecy\Exception\Doubler\ClassNotFoundException + * @throws \Prophecy\Exception\Doubler\DoubleException + */ + public function setParentClass($class) + { + if (null !== $this->double) { + throw new DoubleException('Can not extend class with already instantiated double.'); + } + + if (!$class instanceof ReflectionClass) { + if (!class_exists($class)) { + throw new ClassNotFoundException(sprintf('Class %s not found.', $class), $class); + } + + $class = new ReflectionClass($class); + } + + $this->class = $class; + } + + /** + * Tells doubler to implement specific interface with double. + * + * @param string|ReflectionClass $interface + * + * @throws \Prophecy\Exception\Doubler\InterfaceNotFoundException + * @throws \Prophecy\Exception\Doubler\DoubleException + */ + public function addInterface($interface) + { + if (null !== $this->double) { + throw new DoubleException( + 'Can not implement interface with already instantiated double.' + ); + } + + if (!$interface instanceof ReflectionClass) { + if (!interface_exists($interface)) { + throw new InterfaceNotFoundException( + sprintf('Interface %s not found.', $interface), + $interface + ); + } + + $interface = new ReflectionClass($interface); + } + + $this->interfaces[] = $interface; + } + + /** + * Sets constructor arguments. + * + * @param array $arguments + */ + public function setArguments(array $arguments = null) + { + $this->arguments = $arguments; + } + + /** + * Creates double instance or returns already created one. + * + * @return DoubleInterface + */ + public function getInstance() + { + if (null === $this->double) { + if (null !== $this->arguments) { + return $this->double = $this->doubler->double( + $this->class, $this->interfaces, $this->arguments + ); + } + + $this->double = $this->doubler->double($this->class, $this->interfaces); + } + + return $this->double; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/NameGenerator.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/NameGenerator.php new file mode 100644 index 0000000..d67ec6a --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/NameGenerator.php @@ -0,0 +1,52 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <ever.zet@gmail.com> + * Marcello Duarte <marcello.duarte@gmail.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prophecy\Doubler; + +use ReflectionClass; + +/** + * Name generator. + * Generates classname for double. + * + * @author Konstantin Kudryashov <ever.zet@gmail.com> + */ +class NameGenerator +{ + private static $counter = 1; + + /** + * Generates name. + * + * @param ReflectionClass $class + * @param ReflectionClass[] $interfaces + * + * @return string + */ + public function name(ReflectionClass $class = null, array $interfaces) + { + $parts = array(); + + if (null !== $class) { + $parts[] = $class->getName(); + } else { + foreach ($interfaces as $interface) { + $parts[] = $interface->getShortName(); + } + } + + if (!count($parts)) { + $parts[] = 'stdClass'; + } + + return sprintf('Double\%s\P%d', implode('\\', $parts), self::$counter++); + } +} |