commitall

This commit is contained in:
Sampanna Rimal
2024-07-10 18:28:19 +05:45
parent 140abda4e6
commit 9cd05ef3cb
15723 changed files with 4818733 additions and 0 deletions

View File

@ -0,0 +1,66 @@
<?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 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();
}
}

View File

@ -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();
}

View File

@ -0,0 +1,76 @@
<?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->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;
}
}

View File

@ -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;
}
}

View File

@ -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',
);
}
}

View File

@ -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;
}
}

View File

@ -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('objectProphecyClosure', '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->objectProphecyClosure = function () use ($prophecy) { return $prophecy; };');
$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)),
\$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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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
{
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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 = PHP_VERSION_ID >= 70100 ? $method->getReturnType()->getName() : (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 PHP_VERSION_ID >= 70100 ? $parameter->getType()->getName() : (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;
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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
{
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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++);
}
}