diff options
Diffstat (limited to 'vendor/phpspec/prophecy/src/Prophecy/Doubler')
26 files changed, 2419 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 000000000..2b875211a --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/CachedDoubler.php @@ -0,0 +1,66 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +class CachedDoubler extends Doubler +{ + private static $classes = array(); + + /** + * {@inheritdoc} + */ + protected function createDoubleClass(ReflectionClass $class = null, array $interfaces) + { + $classId = $this->generateClassId($class, $interfaces); + if (isset(self::$classes[$classId])) { + return self::$classes[$classId]; + } + + return self::$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(); + } + foreach ($this->getClassPatches() as $patch) { + $parts[] = get_class($patch); + } + sort($parts); + + return md5(implode('', $parts)); + } + + public function resetCache() + { + self::$classes = array(); + } +} 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 000000000..d6d196850 --- /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 <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +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 000000000..9d843099d --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php @@ -0,0 +1,76 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +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->isExtendable('__construct')) { + return; + } + + 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 000000000..fa38fc0d1 --- /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 <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +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 000000000..ab99f74be --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/KeywordPatch.php @@ -0,0 +1,68 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +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() + { + return ['__halt_compiler']; + } +} 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 000000000..a545eeff5 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/MagicCallPatch.php @@ -0,0 +1,105 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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\ArgumentNode; +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 <[email protected]> + * @author Kévin Dunglas <[email protected]> + * @author Théo FIDRY <[email protected]> + */ +class MagicCallPatch implements ClassPatchInterface +{ + const MAGIC_METHODS_WITH_ARGUMENTS = ['__call', '__callStatic', '__get', '__isset', '__set', '__set_state', '__unserialize', '__unset']; + + 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); + + // only magic methods can have a contract that needs to be enforced + if (in_array($methodName, self::MAGIC_METHODS_WITH_ARGUMENTS)) { + foreach($tag->getArguments() as $argument) { + $argumentNode = new ArgumentNode($argument['name']); + $methodNode->addArgument($argumentNode); + } + } + + $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 000000000..7573ca50e --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/ProphecySubjectPatch.php @@ -0,0 +1,113 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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\ArgumentTypeNode; +use Prophecy\Doubler\Generator\Node\ClassNode; +use Prophecy\Doubler\Generator\Node\MethodNode; +use Prophecy\Doubler\Generator\Node\ArgumentNode; +use Prophecy\Doubler\Generator\Node\ReturnTypeNode; + +/** + * Add Prophecy functionality to the double. + * This is a core class patch for Prophecy. + * + * @author Konstantin Kudryashov <[email protected]> + */ +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('objectProphecyClosure', 'private'); + + foreach ($node->getMethods() as $name => $method) { + if ('__construct' === strtolower($name)) { + continue; + } + + if (!$method->getReturnTypeNode()->hasReturnStatement()) { + $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->setTypeNode(new ArgumentTypeNode('Prophecy\Prophecy\ProphecyInterface')); + $prophecySetter->addArgument($prophecyArgument); + $prophecySetter->setCode(<<<PHP +if (null === \$this->objectProphecyClosure) { + \$this->objectProphecyClosure = static function () use (\$prophecy) { + return \$prophecy; + }; +} +PHP + ); + + $prophecyGetter = new MethodNode('getProphecy'); + $prophecyGetter->setCode('return \call_user_func($this->objectProphecyClosure);'); + + 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)), + get_class(\$this), 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 000000000..9166aeefa --- /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 <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +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 000000000..ceee94a2e --- /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 <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +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 000000000..b98e94327 --- /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 000000000..0e2e04700 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/ClassPatch/TraversablePatch.php @@ -0,0 +1,98 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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\ReturnTypeNode; + +/** + * Traversable interface patch. + * Forces classes that implement interfaces, that extend Traversable to also implement Iterator. + * + * @author Konstantin Kudryashov <[email protected]> + */ +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'); + + $currentMethod = new MethodNode('current'); + (\PHP_VERSION_ID >= 80100) && $currentMethod->setReturnTypeNode(new ReturnTypeNode('mixed')); + $node->addMethod($currentMethod); + + $keyMethod = new MethodNode('key'); + (\PHP_VERSION_ID >= 80100) && $keyMethod->setReturnTypeNode(new ReturnTypeNode('mixed')); + $node->addMethod($keyMethod); + + $nextMethod = new MethodNode('next'); + (\PHP_VERSION_ID >= 80100) && $nextMethod->setReturnTypeNode(new ReturnTypeNode('void')); + $node->addMethod($nextMethod); + + $rewindMethod = new MethodNode('rewind'); + (\PHP_VERSION_ID >= 80100) && $rewindMethod->setReturnTypeNode(new ReturnTypeNode('void')); + $node->addMethod($rewindMethod); + + $validMethod = new MethodNode('valid'); + (\PHP_VERSION_ID >= 80100) && $validMethod->setReturnTypeNode(new ReturnTypeNode('bool')); + $node->addMethod($validMethod); + } + + /** + * 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 000000000..699be3a2a --- /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 <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +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 000000000..a378ae279 --- /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 <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +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 000000000..52e5e0455 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php @@ -0,0 +1,110 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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\Doubler\Generator\Node\ReturnTypeNode; +use Prophecy\Doubler\Generator\Node\TypeNodeAbstract; + +/** + * Class code creator. + * Generates PHP code for specific class node tree. + * + * @author Konstantin Kudryashov <[email protected]> + */ +class ClassCodeGenerator +{ + public function __construct(TypeHintReference $typeHintReference = null) + { + } + + /** + * 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())), + ($ret = $this->generateTypes($method->getReturnTypeNode())) ? ': '.$ret : '' + ); + $php .= $method->getCode()."\n"; + + return $php.'}'; + } + + private function generateTypes(TypeNodeAbstract $typeNode): string + { + if (!$typeNode->getTypes()) { + return ''; + } + + // When we require PHP 8 we can stop generating ?foo nullables and remove this first block + if ($typeNode->canUseNullShorthand()) { + return sprintf( '?%s', $typeNode->getNonNullTypes()[0]); + } else { + return join('|', $typeNode->getTypes()); + } + } + + private function generateArguments(array $arguments) + { + return array_map(function (Node\ArgumentNode $argument){ + + $php = $this->generateTypes($argument->getTypeNode()); + + $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 000000000..882a4a4b7 --- /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 <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +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 000000000..5d9cd2d20 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassMirror.php @@ -0,0 +1,254 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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\Doubler\Generator\Node\ArgumentTypeNode; +use Prophecy\Doubler\Generator\Node\ReturnTypeNode; +use Prophecy\Exception\InvalidArgumentException; +use Prophecy\Exception\Doubler\ClassMirrorException; +use ReflectionClass; +use ReflectionIntersectionType; +use ReflectionMethod; +use ReflectionNamedType; +use ReflectionParameter; +use ReflectionType; +use ReflectionUnionType; + +/** + * Class mirror. + * Core doubler class. Mirrors specific class and/or interfaces into class node tree. + * + * @author Konstantin Kudryashov <[email protected]> + */ +class ClassMirror +{ + private static $reflectableMethods = array( + '__construct', + '__destruct', + '__sleep', + '__wakeup', + '__toString', + '__call', + '__invoke' + ); + + /** + * Reflects provided arguments into class node. + * + * @param ReflectionClass|null $class + * @param ReflectionClass[] $interfaces + * + * @return Node\ClassNode + * + */ + public function reflect(?ReflectionClass $class, 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 ($method->hasReturnType()) { + $returnTypes = $this->getTypeHints($method->getReturnType(), $method->getDeclaringClass(), $method->getReturnType()->allowsNull()); + $node->setReturnTypeNode(new ReturnTypeNode(...$returnTypes)); + } + elseif (method_exists($method, 'hasTentativeReturnType') && $method->hasTentativeReturnType()) { + $returnTypes = $this->getTypeHints($method->getTentativeReturnType(), $method->getDeclaringClass(), $method->getTentativeReturnType()->allowsNull()); + $node->setReturnTypeNode(new ReturnTypeNode(...$returnTypes)); + } + + 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); + + $typeHints = $this->getTypeHints($parameter->getType(), $parameter->getDeclaringClass(), $parameter->allowsNull()); + + $node->setTypeNode(new ArgumentTypeNode(...$typeHints)); + + if ($parameter->isVariadic()) { + $node->setAsVariadic(); + } + + if ($this->hasDefaultValue($parameter)) { + $node->setDefault($this->getDefaultValue($parameter)); + } + + if ($parameter->isPassedByReference()) { + $node->setAsPassedByReference(); + } + + + $methodNode->addArgument($node); + } + + private function hasDefaultValue(ReflectionParameter $parameter) + { + if ($parameter->isVariadic()) { + return false; + } + + if ($parameter->isDefaultValueAvailable()) { + return true; + } + + return $parameter->isOptional() || ($parameter->allowsNull() && $parameter->getType() && \PHP_VERSION_ID < 80100); + } + + private function getDefaultValue(ReflectionParameter $parameter) + { + if (!$parameter->isDefaultValueAvailable()) { + return null; + } + + return $parameter->getDefaultValue(); + } + + private function getTypeHints(?ReflectionType $type, ?ReflectionClass $class, bool $allowsNull) : array + { + $types = []; + + if ($type instanceof ReflectionNamedType) { + $types = [$type->getName()]; + + } + elseif ($type instanceof ReflectionUnionType) { + $types = $type->getTypes(); + } + elseif ($type instanceof ReflectionIntersectionType) { + throw new ClassMirrorException('Doubling intersection types is not supported', $class); + } + elseif(is_object($type)) { + throw new ClassMirrorException('Unknown reflection type ' . get_class($type), $class); + } + + $types = array_map( + function(string $type) use ($class) { + if ($type === 'self') { + return $class->getName(); + } + if ($type === 'parent') { + return $class->getParentClass()->getName(); + } + + return $type; + }, + $types + ); + + if ($types && $types != ['mixed'] && $allowsNull) { + $types[] = 'null'; + } + + return $types; + } +} 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 000000000..da7fed4e1 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentNode.php @@ -0,0 +1,133 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +class ArgumentNode +{ + private $name; + private $default; + private $optional = false; + private $byReference = false; + private $isVariadic = false; + + /** @var ArgumentTypeNode */ + private $typeNode; + + /** + * @param string $name + */ + public function __construct($name) + { + $this->name = $name; + $this->typeNode = new ArgumentTypeNode(); + } + + public function getName() + { + return $this->name; + } + + public function setTypeNode(ArgumentTypeNode $typeNode) + { + $this->typeNode = $typeNode; + } + + public function getTypeNode() : ArgumentTypeNode + { + return $this->typeNode; + } + + 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; + } + + /** + * @deprecated use getArgumentTypeNode instead + * @return string|null + */ + public function getTypeHint() + { + $type = $this->typeNode->getNonNullTypes() ? $this->typeNode->getNonNullTypes()[0] : null; + + return $type ? ltrim($type, '\\') : null; + } + + /** + * @deprecated use setArgumentTypeNode instead + * @param string|null $typeHint + */ + public function setTypeHint($typeHint = null) + { + $this->typeNode = ($typeHint === null) ? new ArgumentTypeNode() : new ArgumentTypeNode($typeHint); + } + + /** + * @deprecated use getArgumentTypeNode instead + * @return bool + */ + public function isNullable() + { + return $this->typeNode->canUseNullShorthand(); + } + + /** + * @deprecated use getArgumentTypeNode instead + * @param bool $isNullable + */ + public function setAsNullable($isNullable = true) + { + $nonNullTypes = $this->typeNode->getNonNullTypes(); + $this->typeNode = $isNullable ? new ArgumentTypeNode('null', ...$nonNullTypes) : new ArgumentTypeNode(...$nonNullTypes); + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentTypeNode.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentTypeNode.php new file mode 100644 index 000000000..0a18b91e1 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ArgumentTypeNode.php @@ -0,0 +1,10 @@ +<?php + +namespace Prophecy\Doubler\Generator\Node; + +use Prophecy\Exception\Doubler\DoubleException; + +class ArgumentTypeNode extends TypeNodeAbstract +{ + +} 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 000000000..f7bd2857a --- /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 <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +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 000000000..ece652f9f --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/MethodNode.php @@ -0,0 +1,210 @@ +<?php + +/* + * This file is part of the Prophecy. + * (c) Konstantin Kudryashov <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +class MethodNode +{ + private $name; + private $code; + private $visibility = 'public'; + private $static = false; + private $returnsReference = false; + + /** @var ReturnTypeNode */ + private $returnTypeNode; + + /** + * @var ArgumentNode[] + */ + private $arguments = array(); + + /** + * @param string $name + * @param string $code + */ + public function __construct($name, $code = null, TypeHintReference $typeHintReference = null) + { + $this->name = $name; + $this->code = $code; + $this->returnTypeNode = new ReturnTypeNode(); + } + + 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; + } + + /** + * @deprecated use getReturnTypeNode instead + * @return bool + */ + public function hasReturnType() + { + return (bool) $this->returnTypeNode->getNonNullTypes(); + } + + public function setReturnTypeNode(ReturnTypeNode $returnTypeNode): void + { + $this->returnTypeNode = $returnTypeNode; + } + + /** + * @deprecated use setReturnTypeNode instead + * @param string $type + */ + public function setReturnType($type = null) + { + $this->returnTypeNode = ($type === '' || $type === null) ? new ReturnTypeNode() : new ReturnTypeNode($type); + } + + /** + * @deprecated use setReturnTypeNode instead + * @param bool $bool + */ + public function setNullableReturnType($bool = true) + { + if ($bool) { + $this->returnTypeNode = new ReturnTypeNode('null', ...$this->returnTypeNode->getTypes()); + } + else { + $this->returnTypeNode = new ReturnTypeNode(...$this->returnTypeNode->getNonNullTypes()); + } + } + + /** + * @deprecated use getReturnTypeNode instead + * @return string|null + */ + public function getReturnType() + { + if ($types = $this->returnTypeNode->getNonNullTypes()) + { + return $types[0]; + } + + return null; + } + + public function getReturnTypeNode() : ReturnTypeNode + { + return $this->returnTypeNode; + } + + /** + * @deprecated use getReturnTypeNode instead + * @return bool + */ + public function hasNullableReturnType() + { + return $this->returnTypeNode->canUseNullShorthand(); + } + + /** + * @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/Node/ReturnTypeNode.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ReturnTypeNode.php new file mode 100644 index 000000000..5b5824988 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/ReturnTypeNode.php @@ -0,0 +1,45 @@ +<?php + +namespace Prophecy\Doubler\Generator\Node; + +use Prophecy\Exception\Doubler\DoubleException; + +final class ReturnTypeNode extends TypeNodeAbstract +{ + protected function getRealType(string $type): string + { + switch ($type) { + case 'void': + case 'never': + return $type; + default: + return parent::getRealType($type); + } + } + + protected function guardIsValidType() + { + if (isset($this->types['void']) && count($this->types) !== 1) { + throw new DoubleException('void cannot be part of a union'); + } + if (isset($this->types['never']) && count($this->types) !== 1) { + throw new DoubleException('never cannot be part of a union'); + } + + parent::guardIsValidType(); + } + + /** + * @deprecated use hasReturnStatement + */ + public function isVoid() + { + return $this->types == ['void' => 'void']; + } + + public function hasReturnStatement(): bool + { + return $this->types !== ['void' => 'void'] + && $this->types !== ['never' => 'never']; + } +} diff --git a/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php new file mode 100644 index 000000000..97fc54978 --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php @@ -0,0 +1,97 @@ +<?php + +namespace Prophecy\Doubler\Generator\Node; + +use Prophecy\Exception\Doubler\DoubleException; + +abstract class TypeNodeAbstract +{ + /** @var string[] */ + protected $types = []; + + public function __construct(string ...$types) + { + foreach ($types as $type) { + $type = $this->getRealType($type); + $this->types[$type] = $type; + } + + $this->guardIsValidType(); + } + + public function canUseNullShorthand(): bool + { + return isset($this->types['null']) && count($this->types) <= 2; + } + + public function getTypes(): array + { + return array_values($this->types); + } + + public function getNonNullTypes(): array + { + $nonNullTypes = $this->types; + unset($nonNullTypes['null']); + + return array_values($nonNullTypes); + } + + protected function prefixWithNsSeparator(string $type): string + { + return '\\' . ltrim($type, '\\'); + } + + protected function getRealType(string $type): string + { + switch ($type) { + // type aliases + case 'double': + case 'real': + return 'float'; + case 'boolean': + return 'bool'; + case 'integer': + return 'int'; + + // built in types + case 'self': + case 'static': + case 'array': + case 'callable': + case 'bool': + case 'false': + case 'float': + case 'int': + case 'string': + case 'iterable': + case 'object': + case 'null': + return $type; + case 'mixed': + return \PHP_VERSION_ID < 80000 ? $this->prefixWithNsSeparator($type) : $type; + + default: + return $this->prefixWithNsSeparator($type); + } + } + + protected function guardIsValidType() + { + if ($this->types == ['null' => 'null']) { + throw new DoubleException('Type cannot be standalone null'); + } + + if ($this->types == ['false' => 'false']) { + throw new DoubleException('Type cannot be standalone false'); + } + + if ($this->types == ['false' => 'false', 'null' => 'null']) { + throw new DoubleException('Type cannot be nullable false'); + } + + if (\PHP_VERSION_ID >= 80000 && isset($this->types['mixed']) && count($this->types) !== 1) { + throw new DoubleException('mixed cannot be part of a union'); + } + } +} 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 000000000..d720b1515 --- /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 <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +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 000000000..5e8aa303d --- /dev/null +++ b/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/TypeHintReference.php @@ -0,0 +1,43 @@ +<?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 + * + * @deprecated in favour of Node\TypeNodeAbstract + */ +final class TypeHintReference +{ + public function isBuiltInParamTypeHint($type) + { + switch ($type) { + case 'self': + case 'array': + case 'callable': + case 'bool': + case 'float': + case 'int': + case 'string': + case 'iterable': + case 'object': + return true; + + case 'mixed': + return PHP_VERSION_ID >= 80000; + + default: + return false; + } + } + + public function isBuiltInReturnTypeHint($type) + { + if ($type === 'void') { + return true; + } + + 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 000000000..8a99c4ce8 --- /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 <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +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 000000000..d67ec6a4d --- /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 <[email protected]> + * Marcello Duarte <[email protected]> + * + * 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 <[email protected]> + */ +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++); + } +} |