123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182 |
- <?php
- /*
- * This file is part of the phpunit-mock-objects package.
- *
- * (c) Sebastian Bergmann <sebastian@phpunit.de>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace PHPUnit\Framework\MockObject;
- use Doctrine\Instantiator\Exception\ExceptionInterface as InstantiatorException;
- use Doctrine\Instantiator\Instantiator;
- use Iterator;
- use IteratorAggregate;
- use PHPUnit\Framework\Exception;
- use PHPUnit\Util\InvalidArgumentHelper;
- use ReflectionClass;
- use ReflectionException;
- use ReflectionMethod;
- use SoapClient;
- use Text_Template;
- use Traversable;
- /**
- * Mock Object Code Generator
- */
- class Generator
- {
- /**
- * @var array
- */
- private static $cache = [];
- /**
- * @var Text_Template[]
- */
- private static $templates = [];
- /**
- * @var array
- */
- private $blacklistedMethodNames = [
- '__CLASS__' => true,
- '__DIR__' => true,
- '__FILE__' => true,
- '__FUNCTION__' => true,
- '__LINE__' => true,
- '__METHOD__' => true,
- '__NAMESPACE__' => true,
- '__TRAIT__' => true,
- '__clone' => true,
- '__halt_compiler' => true,
- ];
- /**
- * Returns a mock object for the specified class.
- *
- * @param string|string[] $type
- * @param array $methods
- * @param array $arguments
- * @param string $mockClassName
- * @param bool $callOriginalConstructor
- * @param bool $callOriginalClone
- * @param bool $callAutoload
- * @param bool $cloneArguments
- * @param bool $callOriginalMethods
- * @param object $proxyTarget
- * @param bool $allowMockingUnknownTypes
- *
- * @return MockObject
- *
- * @throws Exception
- * @throws RuntimeException
- * @throws \PHPUnit\Framework\Exception
- * @throws \ReflectionException
- */
- public function getMock($type, $methods = [], array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false, $proxyTarget = null, $allowMockingUnknownTypes = true)
- {
- if (!\is_array($type) && !\is_string($type)) {
- throw InvalidArgumentHelper::factory(1, 'array or string');
- }
- if (!\is_string($mockClassName)) {
- throw InvalidArgumentHelper::factory(4, 'string');
- }
- if (!\is_array($methods) && null !== $methods) {
- throw InvalidArgumentHelper::factory(2, 'array', $methods);
- }
- if ($type === 'Traversable' || $type === '\\Traversable') {
- $type = 'Iterator';
- }
- if (\is_array($type)) {
- $type = \array_unique(
- \array_map(
- function ($type) {
- if ($type === 'Traversable' ||
- $type === '\\Traversable' ||
- $type === '\\Iterator') {
- return 'Iterator';
- }
- return $type;
- },
- $type
- )
- );
- }
- if (!$allowMockingUnknownTypes) {
- if (\is_array($type)) {
- foreach ($type as $_type) {
- if (!\class_exists($_type, $callAutoload) &&
- !\interface_exists($_type, $callAutoload)) {
- throw new RuntimeException(
- \sprintf(
- 'Cannot stub or mock class or interface "%s" which does not exist',
- $_type
- )
- );
- }
- }
- } else {
- if (!\class_exists($type, $callAutoload) &&
- !\interface_exists($type, $callAutoload)
- ) {
- throw new RuntimeException(
- \sprintf(
- 'Cannot stub or mock class or interface "%s" which does not exist',
- $type
- )
- );
- }
- }
- }
- if (null !== $methods) {
- foreach ($methods as $method) {
- if (!\preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', $method)) {
- throw new RuntimeException(
- \sprintf(
- 'Cannot stub or mock method with invalid name "%s"',
- $method
- )
- );
- }
- }
- if ($methods !== \array_unique($methods)) {
- throw new RuntimeException(
- \sprintf(
- 'Cannot stub or mock using a method list that contains duplicates: "%s" (duplicate: "%s")',
- \implode(', ', $methods),
- \implode(', ', \array_unique(\array_diff_assoc($methods, \array_unique($methods))))
- )
- );
- }
- }
- if ($mockClassName !== '' && \class_exists($mockClassName, false)) {
- $reflect = new ReflectionClass($mockClassName);
- if (!$reflect->implementsInterface(MockObject::class)) {
- throw new RuntimeException(
- \sprintf(
- 'Class "%s" already exists.',
- $mockClassName
- )
- );
- }
- }
- if ($callOriginalConstructor === false && $callOriginalMethods === true) {
- throw new RuntimeException(
- 'Proxying to original methods requires invoking the original constructor'
- );
- }
- $mock = $this->generate(
- $type,
- $methods,
- $mockClassName,
- $callOriginalClone,
- $callAutoload,
- $cloneArguments,
- $callOriginalMethods
- );
- return $this->getObject(
- $mock['code'],
- $mock['mockClassName'],
- $type,
- $callOriginalConstructor,
- $callAutoload,
- $arguments,
- $callOriginalMethods,
- $proxyTarget
- );
- }
- /**
- * @param string $code
- * @param string $className
- * @param array|string $type
- * @param bool $callOriginalConstructor
- * @param bool $callAutoload
- * @param array $arguments
- * @param bool $callOriginalMethods
- * @param object $proxyTarget
- *
- * @return MockObject
- *
- * @throws \ReflectionException
- * @throws RuntimeException
- */
- private function getObject($code, $className, $type = '', $callOriginalConstructor = false, $callAutoload = false, array $arguments = [], $callOriginalMethods = false, $proxyTarget = null)
- {
- $this->evalClass($code, $className);
- if ($callOriginalConstructor &&
- \is_string($type) &&
- !\interface_exists($type, $callAutoload)) {
- if (\count($arguments) === 0) {
- $object = new $className;
- } else {
- $class = new ReflectionClass($className);
- $object = $class->newInstanceArgs($arguments);
- }
- } else {
- try {
- $instantiator = new Instantiator;
- $object = $instantiator->instantiate($className);
- } catch (InstantiatorException $exception) {
- throw new RuntimeException($exception->getMessage());
- }
- }
- if ($callOriginalMethods) {
- if (!\is_object($proxyTarget)) {
- if (\count($arguments) === 0) {
- $proxyTarget = new $type;
- } else {
- $class = new ReflectionClass($type);
- $proxyTarget = $class->newInstanceArgs($arguments);
- }
- }
- $object->__phpunit_setOriginalObject($proxyTarget);
- }
- return $object;
- }
- /**
- * @param string $code
- * @param string $className
- */
- private function evalClass($code, $className)
- {
- if (!\class_exists($className, false)) {
- eval($code);
- }
- }
- /**
- * Returns a mock object for the specified abstract class with all abstract
- * methods of the class mocked. Concrete methods to mock can be specified with
- * the last parameter
- *
- * @param string $originalClassName
- * @param array $arguments
- * @param string $mockClassName
- * @param bool $callOriginalConstructor
- * @param bool $callOriginalClone
- * @param bool $callAutoload
- * @param array $mockedMethods
- * @param bool $cloneArguments
- *
- * @return MockObject
- *
- * @throws \ReflectionException
- * @throws RuntimeException
- * @throws Exception
- */
- public function getMockForAbstractClass($originalClassName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true)
- {
- if (!\is_string($originalClassName)) {
- throw InvalidArgumentHelper::factory(1, 'string');
- }
- if (!\is_string($mockClassName)) {
- throw InvalidArgumentHelper::factory(3, 'string');
- }
- if (\class_exists($originalClassName, $callAutoload) ||
- \interface_exists($originalClassName, $callAutoload)) {
- $reflector = new ReflectionClass($originalClassName);
- $methods = $mockedMethods;
- foreach ($reflector->getMethods() as $method) {
- if ($method->isAbstract() && !\in_array($method->getName(), $methods)) {
- $methods[] = $method->getName();
- }
- }
- if (empty($methods)) {
- $methods = null;
- }
- return $this->getMock(
- $originalClassName,
- $methods,
- $arguments,
- $mockClassName,
- $callOriginalConstructor,
- $callOriginalClone,
- $callAutoload,
- $cloneArguments
- );
- }
- throw new RuntimeException(
- \sprintf('Class "%s" does not exist.', $originalClassName)
- );
- }
- /**
- * Returns a mock object for the specified trait with all abstract methods
- * of the trait mocked. Concrete methods to mock can be specified with the
- * `$mockedMethods` parameter.
- *
- * @param string $traitName
- * @param array $arguments
- * @param string $mockClassName
- * @param bool $callOriginalConstructor
- * @param bool $callOriginalClone
- * @param bool $callAutoload
- * @param array $mockedMethods
- * @param bool $cloneArguments
- *
- * @return MockObject
- *
- * @throws \ReflectionException
- * @throws RuntimeException
- * @throws Exception
- */
- public function getMockForTrait($traitName, array $arguments = [], $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = [], $cloneArguments = true)
- {
- if (!\is_string($traitName)) {
- throw InvalidArgumentHelper::factory(1, 'string');
- }
- if (!\is_string($mockClassName)) {
- throw InvalidArgumentHelper::factory(3, 'string');
- }
- if (!\trait_exists($traitName, $callAutoload)) {
- throw new RuntimeException(
- \sprintf(
- 'Trait "%s" does not exist.',
- $traitName
- )
- );
- }
- $className = $this->generateClassName(
- $traitName,
- '',
- 'Trait_'
- );
- $classTemplate = $this->getTemplate('trait_class.tpl');
- $classTemplate->setVar(
- [
- 'prologue' => 'abstract ',
- 'class_name' => $className['className'],
- 'trait_name' => $traitName
- ]
- );
- $this->evalClass(
- $classTemplate->render(),
- $className['className']
- );
- return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments);
- }
- /**
- * Returns an object for the specified trait.
- *
- * @param string $traitName
- * @param array $arguments
- * @param string $traitClassName
- * @param bool $callOriginalConstructor
- * @param bool $callOriginalClone
- * @param bool $callAutoload
- *
- * @return object
- *
- * @throws \ReflectionException
- * @throws RuntimeException
- * @throws Exception
- */
- public function getObjectForTrait($traitName, array $arguments = [], $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true)
- {
- if (!\is_string($traitName)) {
- throw InvalidArgumentHelper::factory(1, 'string');
- }
- if (!\is_string($traitClassName)) {
- throw InvalidArgumentHelper::factory(3, 'string');
- }
- if (!\trait_exists($traitName, $callAutoload)) {
- throw new RuntimeException(
- \sprintf(
- 'Trait "%s" does not exist.',
- $traitName
- )
- );
- }
- $className = $this->generateClassName(
- $traitName,
- $traitClassName,
- 'Trait_'
- );
- $classTemplate = $this->getTemplate('trait_class.tpl');
- $classTemplate->setVar(
- [
- 'prologue' => '',
- 'class_name' => $className['className'],
- 'trait_name' => $traitName
- ]
- );
- return $this->getObject($classTemplate->render(), $className['className']);
- }
- /**
- * @param array|string $type
- * @param array $methods
- * @param string $mockClassName
- * @param bool $callOriginalClone
- * @param bool $callAutoload
- * @param bool $cloneArguments
- * @param bool $callOriginalMethods
- *
- * @return array
- *
- * @throws \ReflectionException
- * @throws \PHPUnit\Framework\MockObject\RuntimeException
- */
- public function generate($type, array $methods = null, $mockClassName = '', $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false)
- {
- if (\is_array($type)) {
- \sort($type);
- }
- if ($mockClassName === '') {
- $key = \md5(
- \is_array($type) ? \implode('_', $type) : $type .
- \serialize($methods) .
- \serialize($callOriginalClone) .
- \serialize($cloneArguments) .
- \serialize($callOriginalMethods)
- );
- if (isset(self::$cache[$key])) {
- return self::$cache[$key];
- }
- }
- $mock = $this->generateMock(
- $type,
- $methods,
- $mockClassName,
- $callOriginalClone,
- $callAutoload,
- $cloneArguments,
- $callOriginalMethods
- );
- if (isset($key)) {
- self::$cache[$key] = $mock;
- }
- return $mock;
- }
- /**
- * @param string $wsdlFile
- * @param string $className
- * @param array $methods
- * @param array $options
- *
- * @return string
- *
- * @throws RuntimeException
- */
- public function generateClassFromWsdl($wsdlFile, $className, array $methods = [], array $options = [])
- {
- if (!\extension_loaded('soap')) {
- throw new RuntimeException(
- 'The SOAP extension is required to generate a mock object from WSDL.'
- );
- }
- $options = \array_merge($options, ['cache_wsdl' => WSDL_CACHE_NONE]);
- $client = new SoapClient($wsdlFile, $options);
- $_methods = \array_unique($client->__getFunctions());
- unset($client);
- \sort($_methods);
- $methodTemplate = $this->getTemplate('wsdl_method.tpl');
- $methodsBuffer = '';
- foreach ($_methods as $method) {
- $nameStart = \strpos($method, ' ') + 1;
- $nameEnd = \strpos($method, '(');
- $name = \substr($method, $nameStart, $nameEnd - $nameStart);
- if (empty($methods) || \in_array($name, $methods)) {
- $args = \explode(
- ',',
- \substr(
- $method,
- $nameEnd + 1,
- \strpos($method, ')') - $nameEnd - 1
- )
- );
- foreach (\range(0, \count($args) - 1) as $i) {
- $args[$i] = \substr($args[$i], \strpos($args[$i], '$'));
- }
- $methodTemplate->setVar(
- [
- 'method_name' => $name,
- 'arguments' => \implode(', ', $args)
- ]
- );
- $methodsBuffer .= $methodTemplate->render();
- }
- }
- $optionsBuffer = 'array(';
- foreach ($options as $key => $value) {
- $optionsBuffer .= $key . ' => ' . $value;
- }
- $optionsBuffer .= ')';
- $classTemplate = $this->getTemplate('wsdl_class.tpl');
- $namespace = '';
- if (\strpos($className, '\\') !== false) {
- $parts = \explode('\\', $className);
- $className = \array_pop($parts);
- $namespace = 'namespace ' . \implode('\\', $parts) . ';' . "\n\n";
- }
- $classTemplate->setVar(
- [
- 'namespace' => $namespace,
- 'class_name' => $className,
- 'wsdl' => $wsdlFile,
- 'options' => $optionsBuffer,
- 'methods' => $methodsBuffer
- ]
- );
- return $classTemplate->render();
- }
- /**
- * @param array|string $type
- * @param array|null $methods
- * @param string $mockClassName
- * @param bool $callOriginalClone
- * @param bool $callAutoload
- * @param bool $cloneArguments
- * @param bool $callOriginalMethods
- *
- * @return array
- *
- * @throws \InvalidArgumentException
- * @throws \ReflectionException
- * @throws RuntimeException
- */
- private function generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods)
- {
- $methodReflections = [];
- $classTemplate = $this->getTemplate('mocked_class.tpl');
- $additionalInterfaces = [];
- $cloneTemplate = '';
- $isClass = false;
- $isInterface = false;
- $isMultipleInterfaces = false;
- if (\is_array($type)) {
- foreach ($type as $_type) {
- if (!\interface_exists($_type, $callAutoload)) {
- throw new RuntimeException(
- \sprintf(
- 'Interface "%s" does not exist.',
- $_type
- )
- );
- }
- $isMultipleInterfaces = true;
- $additionalInterfaces[] = $_type;
- $typeClass = new ReflectionClass($this->generateClassName(
- $_type,
- $mockClassName,
- 'Mock_'
- )['fullClassName']
- );
- foreach ($this->getClassMethods($_type) as $method) {
- if (\in_array($method, $methods)) {
- throw new RuntimeException(
- \sprintf(
- 'Duplicate method "%s" not allowed.',
- $method
- )
- );
- }
- $methodReflections[$method] = $typeClass->getMethod($method);
- $methods[] = $method;
- }
- }
- }
- $mockClassName = $this->generateClassName(
- $type,
- $mockClassName,
- 'Mock_'
- );
- if (\class_exists($mockClassName['fullClassName'], $callAutoload)) {
- $isClass = true;
- } elseif (\interface_exists($mockClassName['fullClassName'], $callAutoload)) {
- $isInterface = true;
- }
- if (!$isClass && !$isInterface) {
- $prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n";
- if (!empty($mockClassName['namespaceName'])) {
- $prologue = 'namespace ' . $mockClassName['namespaceName'] .
- " {\n\n" . $prologue . "}\n\n" .
- "namespace {\n\n";
- $epilogue = "\n\n}";
- }
- $cloneTemplate = $this->getTemplate('mocked_clone.tpl');
- } else {
- $class = new ReflectionClass($mockClassName['fullClassName']);
- if ($class->isFinal()) {
- throw new RuntimeException(
- \sprintf(
- 'Class "%s" is declared "final" and cannot be mocked.',
- $mockClassName['fullClassName']
- )
- );
- }
- if ($class->hasMethod('__clone')) {
- $cloneMethod = $class->getMethod('__clone');
- if (!$cloneMethod->isFinal()) {
- if ($callOriginalClone && !$isInterface) {
- $cloneTemplate = $this->getTemplate('unmocked_clone.tpl');
- } else {
- $cloneTemplate = $this->getTemplate('mocked_clone.tpl');
- }
- }
- } else {
- $cloneTemplate = $this->getTemplate('mocked_clone.tpl');
- }
- }
- if (\is_object($cloneTemplate)) {
- $cloneTemplate = $cloneTemplate->render();
- }
- if (\is_array($methods) && empty($methods) &&
- ($isClass || $isInterface)) {
- $methods = $this->getClassMethods($mockClassName['fullClassName']);
- }
- if (!\is_array($methods)) {
- $methods = [];
- }
- $mockedMethods = '';
- $configurable = [];
- foreach ($methods as $methodName) {
- if ($methodName !== '__construct' && $methodName !== '__clone') {
- $configurable[] = \strtolower($methodName);
- }
- }
- if (isset($class)) {
- // https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103
- if ($isInterface && $class->implementsInterface(Traversable::class) &&
- !$class->implementsInterface(Iterator::class) &&
- !$class->implementsInterface(IteratorAggregate::class)) {
- $additionalInterfaces[] = Iterator::class;
- $methods = \array_merge($methods, $this->getClassMethods(Iterator::class));
- }
- foreach ($methods as $methodName) {
- try {
- $method = $class->getMethod($methodName);
- if ($this->canMockMethod($method)) {
- $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting(
- $method,
- $cloneArguments,
- $callOriginalMethods
- );
- }
- } catch (ReflectionException $e) {
- $mockedMethods .= $this->generateMockedMethodDefinition(
- $mockClassName['fullClassName'],
- $methodName,
- $cloneArguments
- );
- }
- }
- } elseif ($isMultipleInterfaces) {
- foreach ($methods as $methodName) {
- if ($this->canMockMethod($methodReflections[$methodName])) {
- $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting(
- $methodReflections[$methodName],
- $cloneArguments,
- $callOriginalMethods
- );
- }
- }
- } else {
- foreach ($methods as $methodName) {
- $mockedMethods .= $this->generateMockedMethodDefinition(
- $mockClassName['fullClassName'],
- $methodName,
- $cloneArguments
- );
- }
- }
- $method = '';
- if (!\in_array('method', $methods) && (!isset($class) || !$class->hasMethod('method'))) {
- $methodTemplate = $this->getTemplate('mocked_class_method.tpl');
- $method = $methodTemplate->render();
- }
- $classTemplate->setVar(
- [
- 'prologue' => $prologue ?? '',
- 'epilogue' => $epilogue ?? '',
- 'class_declaration' => $this->generateMockClassDeclaration(
- $mockClassName,
- $isInterface,
- $additionalInterfaces
- ),
- 'clone' => $cloneTemplate,
- 'mock_class_name' => $mockClassName['className'],
- 'mocked_methods' => $mockedMethods,
- 'method' => $method,
- 'configurable' => '[' . \implode(', ', \array_map(function ($m) {
- return '\'' . $m . '\'';
- }, $configurable)) . ']'
- ]
- );
- return [
- 'code' => $classTemplate->render(),
- 'mockClassName' => $mockClassName['className']
- ];
- }
- /**
- * @param array|string $type
- * @param string $className
- * @param string $prefix
- *
- * @return array
- */
- private function generateClassName($type, $className, $prefix)
- {
- if (\is_array($type)) {
- $type = \implode('_', $type);
- }
- if ($type[0] === '\\') {
- $type = \substr($type, 1);
- }
- $classNameParts = \explode('\\', $type);
- if (\count($classNameParts) > 1) {
- $type = \array_pop($classNameParts);
- $namespaceName = \implode('\\', $classNameParts);
- $fullClassName = $namespaceName . '\\' . $type;
- } else {
- $namespaceName = '';
- $fullClassName = $type;
- }
- if ($className === '') {
- do {
- $className = $prefix . $type . '_' .
- \substr(\md5(\mt_rand()), 0, 8);
- } while (\class_exists($className, false));
- }
- return [
- 'className' => $className,
- 'originalClassName' => $type,
- 'fullClassName' => $fullClassName,
- 'namespaceName' => $namespaceName
- ];
- }
- /**
- * @param array $mockClassName
- * @param bool $isInterface
- * @param array $additionalInterfaces
- *
- * @return string
- */
- private function generateMockClassDeclaration(array $mockClassName, $isInterface, array $additionalInterfaces = [])
- {
- $buffer = 'class ';
- $additionalInterfaces[] = MockObject::class;
- $interfaces = \implode(', ', $additionalInterfaces);
- if ($isInterface) {
- $buffer .= \sprintf(
- '%s implements %s',
- $mockClassName['className'],
- $interfaces
- );
- if (!\in_array($mockClassName['originalClassName'], $additionalInterfaces)) {
- $buffer .= ', ';
- if (!empty($mockClassName['namespaceName'])) {
- $buffer .= $mockClassName['namespaceName'] . '\\';
- }
- $buffer .= $mockClassName['originalClassName'];
- }
- } else {
- $buffer .= \sprintf(
- '%s extends %s%s implements %s',
- $mockClassName['className'],
- !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '',
- $mockClassName['originalClassName'],
- $interfaces
- );
- }
- return $buffer;
- }
- /**
- * @param ReflectionMethod $method
- * @param bool $cloneArguments
- * @param bool $callOriginalMethods
- *
- * @return string
- *
- * @throws \PHPUnit\Framework\MockObject\RuntimeException
- */
- private function generateMockedMethodDefinitionFromExisting(ReflectionMethod $method, $cloneArguments, $callOriginalMethods)
- {
- if ($method->isPrivate()) {
- $modifier = 'private';
- } elseif ($method->isProtected()) {
- $modifier = 'protected';
- } else {
- $modifier = 'public';
- }
- if ($method->isStatic()) {
- $modifier .= ' static';
- }
- if ($method->returnsReference()) {
- $reference = '&';
- } else {
- $reference = '';
- }
- if ($method->hasReturnType()) {
- $returnType = (string) $method->getReturnType();
- } else {
- $returnType = '';
- }
- if (\preg_match('#\*[ \t]*+@deprecated[ \t]*+(.*?)\r?+\n[ \t]*+\*(?:[ \t]*+@|/$)#s', $method->getDocComment(), $deprecation)) {
- $deprecation = \trim(\preg_replace('#[ \t]*\r?\n[ \t]*+\*[ \t]*+#', ' ', $deprecation[1]));
- } else {
- $deprecation = false;
- }
- return $this->generateMockedMethodDefinition(
- $method->getDeclaringClass()->getName(),
- $method->getName(),
- $cloneArguments,
- $modifier,
- $this->getMethodParameters($method),
- $this->getMethodParameters($method, true),
- $returnType,
- $reference,
- $callOriginalMethods,
- $method->isStatic(),
- $deprecation,
- $method->hasReturnType() && PHP_VERSION_ID >= 70100 && $method->getReturnType()->allowsNull()
- );
- }
- /**
- * @param string $className
- * @param string $methodName
- * @param bool $cloneArguments
- * @param string $modifier
- * @param string $argumentsForDeclaration
- * @param string $argumentsForCall
- * @param string $returnType
- * @param string $reference
- * @param bool $callOriginalMethods
- * @param bool $static
- * @param bool|string $deprecation
- * @param bool $allowsReturnNull
- *
- * @return string
- *
- * @throws \InvalidArgumentException
- */
- private function generateMockedMethodDefinition($className, $methodName, $cloneArguments = true, $modifier = 'public', $argumentsForDeclaration = '', $argumentsForCall = '', $returnType = '', $reference = '', $callOriginalMethods = false, $static = false, $deprecation = false, $allowsReturnNull = false)
- {
- if ($static) {
- $templateFile = 'mocked_static_method.tpl';
- } else {
- if ($returnType === 'void') {
- $templateFile = \sprintf(
- '%s_method_void.tpl',
- $callOriginalMethods ? 'proxied' : 'mocked'
- );
- } else {
- $templateFile = \sprintf(
- '%s_method.tpl',
- $callOriginalMethods ? 'proxied' : 'mocked'
- );
- }
- }
- // Mocked interfaces returning 'self' must explicitly declare the
- // interface name as the return type. See
- // https://bugs.php.net/bug.php?id=70722
- if ($returnType === 'self') {
- $returnType = $className;
- }
- if (false !== $deprecation) {
- $deprecation = "The $className::$methodName method is deprecated ($deprecation).";
- $deprecationTemplate = $this->getTemplate('deprecation.tpl');
- $deprecationTemplate->setVar(
- [
- 'deprecation' => \var_export($deprecation, true),
- ]
- );
- $deprecation = $deprecationTemplate->render();
- }
- $template = $this->getTemplate($templateFile);
- $template->setVar(
- [
- 'arguments_decl' => $argumentsForDeclaration,
- 'arguments_call' => $argumentsForCall,
- 'return_delim' => $returnType ? ': ' : '',
- 'return_type' => $allowsReturnNull ? '?' . $returnType : $returnType,
- 'arguments_count' => !empty($argumentsForCall) ? \substr_count($argumentsForCall, ',') + 1 : 0,
- 'class_name' => $className,
- 'method_name' => $methodName,
- 'modifier' => $modifier,
- 'reference' => $reference,
- 'clone_arguments' => $cloneArguments ? 'true' : 'false',
- 'deprecation' => $deprecation
- ]
- );
- return $template->render();
- }
- /**
- * @param ReflectionMethod $method
- *
- * @return bool
- *
- * @throws \ReflectionException
- */
- private function canMockMethod(ReflectionMethod $method)
- {
- return !($method->isConstructor() || $method->isFinal() || $method->isPrivate() || $this->isMethodNameBlacklisted($method->getName()));
- }
- /**
- * Returns whether a method name is blacklisted
- *
- * @param string $name
- *
- * @return bool
- */
- private function isMethodNameBlacklisted($name)
- {
- return isset($this->blacklistedMethodNames[$name]);
- }
- /**
- * Returns the parameters of a function or method.
- *
- * @param ReflectionMethod $method
- * @param bool $forCall
- *
- * @return string
- *
- * @throws RuntimeException
- */
- private function getMethodParameters(ReflectionMethod $method, $forCall = false)
- {
- $parameters = [];
- foreach ($method->getParameters() as $i => $parameter) {
- $name = '$' . $parameter->getName();
- /* Note: PHP extensions may use empty names for reference arguments
- * or "..." for methods taking a variable number of arguments.
- */
- if ($name === '$' || $name === '$...') {
- $name = '$arg' . $i;
- }
- if ($parameter->isVariadic()) {
- if ($forCall) {
- continue;
- }
- $name = '...' . $name;
- }
- $nullable = '';
- $default = '';
- $reference = '';
- $typeDeclaration = '';
- if (!$forCall) {
- if (PHP_VERSION_ID >= 70100 && $parameter->hasType() && $parameter->allowsNull()) {
- $nullable = '?';
- }
- if ($parameter->hasType() && (string) $parameter->getType() !== 'self') {
- $typeDeclaration = (string) $parameter->getType() . ' ';
- } elseif ($parameter->isArray()) {
- $typeDeclaration = 'array ';
- } elseif ($parameter->isCallable()) {
- $typeDeclaration = 'callable ';
- } else {
- try {
- $class = $parameter->getClass();
- } catch (ReflectionException $e) {
- throw new RuntimeException(
- \sprintf(
- 'Cannot mock %s::%s() because a class or ' .
- 'interface used in the signature is not loaded',
- $method->getDeclaringClass()->getName(),
- $method->getName()
- ),
- 0,
- $e
- );
- }
- if ($class !== null) {
- $typeDeclaration = $class->getName() . ' ';
- }
- }
- if (!$parameter->isVariadic()) {
- if ($parameter->isDefaultValueAvailable()) {
- $value = $parameter->getDefaultValueConstantName();
- if ($value === null) {
- $value = \var_export($parameter->getDefaultValue(), true);
- } elseif (!\defined($value)) {
- $rootValue = \preg_replace('/^.*\\\\/', '', $value);
- $value = \defined($rootValue) ? $rootValue : $value;
- }
- $default = ' = ' . $value;
- } elseif ($parameter->isOptional()) {
- $default = ' = null';
- }
- }
- }
- if ($parameter->isPassedByReference()) {
- $reference = '&';
- }
- $parameters[] = $nullable . $typeDeclaration . $reference . $name . $default;
- }
- return \implode(', ', $parameters);
- }
- /**
- * @param string $className
- *
- * @return array
- *
- * @throws \ReflectionException
- */
- public function getClassMethods($className)
- {
- $class = new ReflectionClass($className);
- $methods = [];
- foreach ($class->getMethods() as $method) {
- if ($method->isPublic() || $method->isAbstract()) {
- $methods[] = $method->getName();
- }
- }
- return $methods;
- }
- /**
- * @param string $template
- *
- * @return Text_Template
- *
- * @throws \InvalidArgumentException
- */
- private function getTemplate($template)
- {
- $filename = __DIR__ . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR . $template;
- if (!isset(self::$templates[$filename])) {
- self::$templates[$filename] = new Text_Template($filename);
- }
- return self::$templates[$filename];
- }
- }
|