Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: use Doctrine\Instantiator\Instantiator; Chris@0: use Doctrine\Instantiator\Exception\InvalidArgumentException as InstantiatorInvalidArgumentException; Chris@0: use Doctrine\Instantiator\Exception\UnexpectedValueException as InstantiatorUnexpectedValueException; Chris@0: Chris@0: if (!function_exists('trait_exists')) { Chris@0: function trait_exists($traitname, $autoload = true) Chris@0: { Chris@0: return false; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Mock Object Code Generator Chris@0: * Chris@0: * @since Class available since Release 1.0.0 Chris@0: */ Chris@0: class PHPUnit_Framework_MockObject_Generator Chris@0: { Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@0: private static $cache = array(); Chris@0: Chris@0: /** Chris@0: * @var array Chris@0: */ Chris@0: protected $blacklistedMethodNames = array( Chris@0: '__CLASS__' => true, Chris@0: '__DIR__' => true, Chris@0: '__FILE__' => true, Chris@0: '__FUNCTION__' => true, Chris@0: '__LINE__' => true, Chris@0: '__METHOD__' => true, Chris@0: '__NAMESPACE__' => true, Chris@0: '__TRAIT__' => true, Chris@0: '__clone' => true, Chris@0: '__halt_compiler' => true, Chris@0: 'abstract' => true, Chris@0: 'and' => true, Chris@0: 'array' => true, Chris@0: 'as' => true, Chris@0: 'break' => true, Chris@0: 'callable' => true, Chris@0: 'case' => true, Chris@0: 'catch' => true, Chris@0: 'class' => true, Chris@0: 'clone' => true, Chris@0: 'const' => true, Chris@0: 'continue' => true, Chris@0: 'declare' => true, Chris@0: 'default' => true, Chris@0: 'die' => true, Chris@0: 'do' => true, Chris@0: 'echo' => true, Chris@0: 'else' => true, Chris@0: 'elseif' => true, Chris@0: 'empty' => true, Chris@0: 'enddeclare' => true, Chris@0: 'endfor' => true, Chris@0: 'endforeach' => true, Chris@0: 'endif' => true, Chris@0: 'endswitch' => true, Chris@0: 'endwhile' => true, Chris@0: 'eval' => true, Chris@0: 'exit' => true, Chris@0: 'expects' => true, Chris@0: 'extends' => true, Chris@0: 'final' => true, Chris@0: 'for' => true, Chris@0: 'foreach' => true, Chris@0: 'function' => true, Chris@0: 'global' => true, Chris@0: 'goto' => true, Chris@0: 'if' => true, Chris@0: 'implements' => true, Chris@0: 'include' => true, Chris@0: 'include_once' => true, Chris@0: 'instanceof' => true, Chris@0: 'insteadof' => true, Chris@0: 'interface' => true, Chris@0: 'isset' => true, Chris@0: 'list' => true, Chris@0: 'namespace' => true, Chris@0: 'new' => true, Chris@0: 'or' => true, Chris@0: 'print' => true, Chris@0: 'private' => true, Chris@0: 'protected' => true, Chris@0: 'public' => true, Chris@0: 'require' => true, Chris@0: 'require_once' => true, Chris@0: 'return' => true, Chris@0: 'static' => true, Chris@0: 'switch' => true, Chris@0: 'throw' => true, Chris@0: 'trait' => true, Chris@0: 'try' => true, Chris@0: 'unset' => true, Chris@0: 'use' => true, Chris@0: 'var' => true, Chris@0: 'while' => true, Chris@0: 'xor' => true Chris@0: ); Chris@0: Chris@0: /** Chris@0: * Returns a mock object for the specified class. Chris@0: * Chris@0: * @param array|string $type Chris@0: * @param array $methods Chris@0: * @param array $arguments Chris@0: * @param string $mockClassName Chris@0: * @param bool $callOriginalConstructor Chris@0: * @param bool $callOriginalClone Chris@0: * @param bool $callAutoload Chris@0: * @param bool $cloneArguments Chris@0: * @param bool $callOriginalMethods Chris@0: * @param object $proxyTarget Chris@0: * @return object Chris@0: * @throws InvalidArgumentException Chris@0: * @throws PHPUnit_Framework_Exception Chris@0: * @throws PHPUnit_Framework_MockObject_RuntimeException Chris@0: * @since Method available since Release 1.0.0 Chris@0: */ Chris@0: public function getMock($type, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false, $proxyTarget = null) Chris@0: { Chris@0: if (!is_array($type) && !is_string($type)) { Chris@0: throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'array or string'); Chris@0: } Chris@0: Chris@0: if (!is_string($mockClassName)) { Chris@0: throw PHPUnit_Util_InvalidArgumentHelper::factory(4, 'string'); Chris@0: } Chris@0: Chris@0: if (!is_array($methods) && !is_null($methods)) { Chris@0: throw new InvalidArgumentException; Chris@0: } Chris@0: Chris@0: if ($type === 'Traversable' || $type === '\\Traversable') { Chris@0: $type = 'Iterator'; Chris@0: } Chris@0: Chris@0: if (is_array($type)) { Chris@0: $type = array_unique(array_map( Chris@0: function ($type) { Chris@0: if ($type === 'Traversable' || Chris@0: $type === '\\Traversable' || Chris@0: $type === '\\Iterator') { Chris@0: return 'Iterator'; Chris@0: } Chris@0: Chris@0: return $type; Chris@0: }, Chris@0: $type Chris@0: )); Chris@0: } Chris@0: Chris@0: if (null !== $methods) { Chris@0: foreach ($methods as $method) { Chris@0: if (!preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', $method)) { Chris@0: throw new PHPUnit_Framework_Exception( Chris@0: sprintf( Chris@0: 'Cannot stub or mock method with invalid name "%s"', Chris@0: $method Chris@0: ) Chris@0: ); Chris@0: } Chris@0: } Chris@0: Chris@0: if ($methods != array_unique($methods)) { Chris@0: throw new PHPUnit_Framework_MockObject_RuntimeException( Chris@0: sprintf( Chris@0: 'Cannot stub or mock using a method list that contains duplicates: "%s"', Chris@0: implode(', ', $methods) Chris@0: ) Chris@0: ); Chris@0: } Chris@0: } Chris@0: Chris@0: if ($mockClassName != '' && class_exists($mockClassName, false)) { Chris@0: $reflect = new ReflectionClass($mockClassName); Chris@0: Chris@0: if (!$reflect->implementsInterface('PHPUnit_Framework_MockObject_MockObject')) { Chris@0: throw new PHPUnit_Framework_MockObject_RuntimeException( Chris@0: sprintf( Chris@0: 'Class "%s" already exists.', Chris@0: $mockClassName Chris@0: ) Chris@0: ); Chris@0: } Chris@0: } Chris@0: Chris@0: $mock = $this->generate( Chris@0: $type, Chris@0: $methods, Chris@0: $mockClassName, Chris@0: $callOriginalClone, Chris@0: $callAutoload, Chris@0: $cloneArguments, Chris@0: $callOriginalMethods Chris@0: ); Chris@0: Chris@0: return $this->getObject( Chris@0: $mock['code'], Chris@0: $mock['mockClassName'], Chris@0: $type, Chris@0: $callOriginalConstructor, Chris@0: $callAutoload, Chris@0: $arguments, Chris@0: $callOriginalMethods, Chris@0: $proxyTarget Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param string $code Chris@0: * @param string $className Chris@0: * @param array|string $type Chris@0: * @param bool $callOriginalConstructor Chris@0: * @param bool $callAutoload Chris@0: * @param array $arguments Chris@0: * @param bool $callOriginalMethods Chris@0: * @param object $proxyTarget Chris@0: * @return object Chris@0: */ Chris@0: protected function getObject($code, $className, $type = '', $callOriginalConstructor = false, $callAutoload = false, array $arguments = array(), $callOriginalMethods = false, $proxyTarget = null) Chris@0: { Chris@0: $this->evalClass($code, $className); Chris@0: Chris@0: if ($callOriginalConstructor && Chris@0: is_string($type) && Chris@0: !interface_exists($type, $callAutoload)) { Chris@0: if (count($arguments) == 0) { Chris@0: $object = new $className; Chris@0: } else { Chris@0: $class = new ReflectionClass($className); Chris@0: $object = $class->newInstanceArgs($arguments); Chris@0: } Chris@0: } else { Chris@0: try { Chris@0: $instantiator = new Instantiator; Chris@0: $object = $instantiator->instantiate($className); Chris@0: } catch (InstantiatorUnexpectedValueException $exception) { Chris@0: if ($exception->getPrevious()) { Chris@0: $exception = $exception->getPrevious(); Chris@0: } Chris@0: Chris@0: throw new PHPUnit_Framework_MockObject_RuntimeException( Chris@0: $exception->getMessage() Chris@0: ); Chris@0: } catch (InstantiatorInvalidArgumentException $exception) { Chris@0: throw new PHPUnit_Framework_MockObject_RuntimeException( Chris@0: $exception->getMessage() Chris@0: ); Chris@0: } Chris@0: } Chris@0: Chris@0: if ($callOriginalMethods) { Chris@0: if (!is_object($proxyTarget)) { Chris@0: if (count($arguments) == 0) { Chris@0: $proxyTarget = new $type; Chris@0: } else { Chris@0: $class = new ReflectionClass($type); Chris@0: $proxyTarget = $class->newInstanceArgs($arguments); Chris@0: } Chris@0: } Chris@0: Chris@0: $object->__phpunit_setOriginalObject($proxyTarget); Chris@0: } Chris@0: Chris@0: return $object; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param string $code Chris@0: * @param string $className Chris@0: */ Chris@0: protected function evalClass($code, $className) Chris@0: { Chris@0: if (!class_exists($className, false)) { Chris@0: eval($code); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns a mock object for the specified abstract class with all abstract Chris@0: * methods of the class mocked. Concrete methods to mock can be specified with Chris@0: * the last parameter Chris@0: * Chris@0: * @param string $originalClassName Chris@0: * @param array $arguments Chris@0: * @param string $mockClassName Chris@0: * @param bool $callOriginalConstructor Chris@0: * @param bool $callOriginalClone Chris@0: * @param bool $callAutoload Chris@0: * @param array $mockedMethods Chris@0: * @param bool $cloneArguments Chris@0: * @return object Chris@0: * @since Method available since Release 1.0.0 Chris@0: * @throws PHPUnit_Framework_MockObject_RuntimeException Chris@0: * @throws PHPUnit_Framework_Exception Chris@0: */ Chris@0: public function getMockForAbstractClass($originalClassName, array $arguments = array(), $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = array(), $cloneArguments = true) Chris@0: { Chris@0: if (!is_string($originalClassName)) { Chris@0: throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); Chris@0: } Chris@0: Chris@0: if (!is_string($mockClassName)) { Chris@0: throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string'); Chris@0: } Chris@0: Chris@0: if (class_exists($originalClassName, $callAutoload) || Chris@0: interface_exists($originalClassName, $callAutoload)) { Chris@0: $reflector = new ReflectionClass($originalClassName); Chris@0: $methods = $mockedMethods; Chris@0: Chris@0: foreach ($reflector->getMethods() as $method) { Chris@0: if ($method->isAbstract() && !in_array($method->getName(), $methods)) { Chris@0: $methods[] = $method->getName(); Chris@0: } Chris@0: } Chris@0: Chris@0: if (empty($methods)) { Chris@0: $methods = null; Chris@0: } Chris@0: Chris@0: return $this->getMock( Chris@0: $originalClassName, Chris@0: $methods, Chris@0: $arguments, Chris@0: $mockClassName, Chris@0: $callOriginalConstructor, Chris@0: $callOriginalClone, Chris@0: $callAutoload, Chris@0: $cloneArguments Chris@0: ); Chris@0: } else { Chris@0: throw new PHPUnit_Framework_MockObject_RuntimeException( Chris@0: sprintf('Class "%s" does not exist.', $originalClassName) Chris@0: ); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns a mock object for the specified trait with all abstract methods Chris@0: * of the trait mocked. Concrete methods to mock can be specified with the Chris@0: * `$mockedMethods` parameter. Chris@0: * Chris@0: * @param string $traitName Chris@0: * @param array $arguments Chris@0: * @param string $mockClassName Chris@0: * @param bool $callOriginalConstructor Chris@0: * @param bool $callOriginalClone Chris@0: * @param bool $callAutoload Chris@0: * @param array $mockedMethods Chris@0: * @param bool $cloneArguments Chris@0: * @return object Chris@0: * @since Method available since Release 1.2.3 Chris@0: * @throws PHPUnit_Framework_MockObject_RuntimeException Chris@0: * @throws PHPUnit_Framework_Exception Chris@0: */ Chris@0: public function getMockForTrait($traitName, array $arguments = array(), $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = array(), $cloneArguments = true) Chris@0: { Chris@0: if (!is_string($traitName)) { Chris@0: throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); Chris@0: } Chris@0: Chris@0: if (!is_string($mockClassName)) { Chris@0: throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string'); Chris@0: } Chris@0: Chris@0: if (!trait_exists($traitName, $callAutoload)) { Chris@0: throw new PHPUnit_Framework_MockObject_RuntimeException( Chris@0: sprintf( Chris@0: 'Trait "%s" does not exist.', Chris@0: $traitName Chris@0: ) Chris@0: ); Chris@0: } Chris@0: Chris@0: $className = $this->generateClassName( Chris@0: $traitName, Chris@0: '', Chris@0: 'Trait_' Chris@0: ); Chris@0: Chris@0: $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' . Chris@0: DIRECTORY_SEPARATOR; Chris@0: $classTemplate = new Text_Template( Chris@0: $templateDir . 'trait_class.tpl' Chris@0: ); Chris@0: Chris@0: $classTemplate->setVar( Chris@0: array( Chris@0: 'prologue' => 'abstract ', Chris@0: 'class_name' => $className['className'], Chris@0: 'trait_name' => $traitName Chris@0: ) Chris@0: ); Chris@0: Chris@0: $this->evalClass( Chris@0: $classTemplate->render(), Chris@0: $className['className'] Chris@0: ); Chris@0: Chris@0: return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns an object for the specified trait. Chris@0: * Chris@0: * @param string $traitName Chris@0: * @param array $arguments Chris@0: * @param string $traitClassName Chris@0: * @param bool $callOriginalConstructor Chris@0: * @param bool $callOriginalClone Chris@0: * @param bool $callAutoload Chris@0: * @return object Chris@0: * @since Method available since Release 1.1.0 Chris@0: * @throws PHPUnit_Framework_MockObject_RuntimeException Chris@0: * @throws PHPUnit_Framework_Exception Chris@0: */ Chris@0: public function getObjectForTrait($traitName, array $arguments = array(), $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true) Chris@0: { Chris@0: if (!is_string($traitName)) { Chris@0: throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); Chris@0: } Chris@0: Chris@0: if (!is_string($traitClassName)) { Chris@0: throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string'); Chris@0: } Chris@0: Chris@0: if (!trait_exists($traitName, $callAutoload)) { Chris@0: throw new PHPUnit_Framework_MockObject_RuntimeException( Chris@0: sprintf( Chris@0: 'Trait "%s" does not exist.', Chris@0: $traitName Chris@0: ) Chris@0: ); Chris@0: } Chris@0: Chris@0: $className = $this->generateClassName( Chris@0: $traitName, Chris@0: $traitClassName, Chris@0: 'Trait_' Chris@0: ); Chris@0: Chris@0: $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' . Chris@0: DIRECTORY_SEPARATOR; Chris@0: $classTemplate = new Text_Template( Chris@0: $templateDir . 'trait_class.tpl' Chris@0: ); Chris@0: Chris@0: $classTemplate->setVar( Chris@0: array( Chris@0: 'prologue' => '', Chris@0: 'class_name' => $className['className'], Chris@0: 'trait_name' => $traitName Chris@0: ) Chris@0: ); Chris@0: Chris@0: return $this->getObject( Chris@0: $classTemplate->render(), Chris@0: $className['className'] Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param array|string $type Chris@0: * @param array $methods Chris@0: * @param string $mockClassName Chris@0: * @param bool $callOriginalClone Chris@0: * @param bool $callAutoload Chris@0: * @param bool $cloneArguments Chris@0: * @param bool $callOriginalMethods Chris@0: * @return array Chris@0: */ Chris@0: public function generate($type, array $methods = null, $mockClassName = '', $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false) Chris@0: { Chris@0: if (is_array($type)) { Chris@0: sort($type); Chris@0: } Chris@0: Chris@0: if ($mockClassName == '') { Chris@0: $key = md5( Chris@0: is_array($type) ? implode('_', $type) : $type . Chris@0: serialize($methods) . Chris@0: serialize($callOriginalClone) . Chris@0: serialize($cloneArguments) . Chris@0: serialize($callOriginalMethods) Chris@0: ); Chris@0: Chris@0: if (isset(self::$cache[$key])) { Chris@0: return self::$cache[$key]; Chris@0: } Chris@0: } Chris@0: Chris@0: $mock = $this->generateMock( Chris@0: $type, Chris@0: $methods, Chris@0: $mockClassName, Chris@0: $callOriginalClone, Chris@0: $callAutoload, Chris@0: $cloneArguments, Chris@0: $callOriginalMethods Chris@0: ); Chris@0: Chris@0: if (isset($key)) { Chris@0: self::$cache[$key] = $mock; Chris@0: } Chris@0: Chris@0: return $mock; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param string $wsdlFile Chris@0: * @param string $className Chris@0: * @param array $methods Chris@0: * @param array $options Chris@0: * @return string Chris@0: * @throws PHPUnit_Framework_MockObject_RuntimeException Chris@0: */ Chris@0: public function generateClassFromWsdl($wsdlFile, $className, array $methods = array(), array $options = array()) Chris@0: { Chris@0: if (!extension_loaded('soap')) { Chris@0: throw new PHPUnit_Framework_MockObject_RuntimeException( Chris@0: 'The SOAP extension is required to generate a mock object from WSDL.' Chris@0: ); Chris@0: } Chris@0: Chris@0: $options = array_merge($options, array('cache_wsdl' => WSDL_CACHE_NONE)); Chris@0: $client = new SoapClient($wsdlFile, $options); Chris@0: $_methods = array_unique($client->__getFunctions()); Chris@0: unset($client); Chris@0: Chris@0: sort($_methods); Chris@0: Chris@0: $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR; Chris@0: $methodTemplate = new Text_Template($templateDir . 'wsdl_method.tpl'); Chris@0: $methodsBuffer = ''; Chris@0: Chris@0: foreach ($_methods as $method) { Chris@0: $nameStart = strpos($method, ' ') + 1; Chris@0: $nameEnd = strpos($method, '('); Chris@0: $name = substr($method, $nameStart, $nameEnd - $nameStart); Chris@0: Chris@0: if (empty($methods) || in_array($name, $methods)) { Chris@0: $args = explode( Chris@0: ',', Chris@0: substr( Chris@0: $method, Chris@0: $nameEnd + 1, Chris@0: strpos($method, ')') - $nameEnd - 1 Chris@0: ) Chris@0: ); Chris@0: $numArgs = count($args); Chris@0: Chris@0: for ($i = 0; $i < $numArgs; $i++) { Chris@0: $args[$i] = substr($args[$i], strpos($args[$i], '$')); Chris@0: } Chris@0: Chris@0: $methodTemplate->setVar( Chris@0: array( Chris@0: 'method_name' => $name, Chris@0: 'arguments' => implode(', ', $args) Chris@0: ) Chris@0: ); Chris@0: Chris@0: $methodsBuffer .= $methodTemplate->render(); Chris@0: } Chris@0: } Chris@0: Chris@0: $optionsBuffer = 'array('; Chris@0: Chris@0: foreach ($options as $key => $value) { Chris@0: $optionsBuffer .= $key . ' => ' . $value; Chris@0: } Chris@0: Chris@0: $optionsBuffer .= ')'; Chris@0: Chris@0: $classTemplate = new Text_Template($templateDir . 'wsdl_class.tpl'); Chris@0: $namespace = ''; Chris@0: Chris@0: if (strpos($className, '\\') !== false) { Chris@0: $parts = explode('\\', $className); Chris@0: $className = array_pop($parts); Chris@0: $namespace = 'namespace ' . implode('\\', $parts) . ';' . "\n\n"; Chris@0: } Chris@0: Chris@0: $classTemplate->setVar( Chris@0: array( Chris@0: 'namespace' => $namespace, Chris@0: 'class_name' => $className, Chris@0: 'wsdl' => $wsdlFile, Chris@0: 'options' => $optionsBuffer, Chris@0: 'methods' => $methodsBuffer Chris@0: ) Chris@0: ); Chris@0: Chris@0: return $classTemplate->render(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param array|string $type Chris@0: * @param array|null $methods Chris@0: * @param string $mockClassName Chris@0: * @param bool $callOriginalClone Chris@0: * @param bool $callAutoload Chris@0: * @param bool $cloneArguments Chris@0: * @param bool $callOriginalMethods Chris@0: * @return array Chris@0: * @throws PHPUnit_Framework_Exception Chris@0: */ Chris@0: protected function generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods) Chris@0: { Chris@0: $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' . Chris@0: DIRECTORY_SEPARATOR; Chris@0: $classTemplate = new Text_Template( Chris@0: $templateDir . 'mocked_class.tpl' Chris@0: ); Chris@0: Chris@0: $additionalInterfaces = array(); Chris@0: $cloneTemplate = ''; Chris@0: $isClass = false; Chris@0: $isInterface = false; Chris@0: Chris@0: $mockClassName = $this->generateClassName( Chris@0: $type, Chris@0: $mockClassName, Chris@0: 'Mock_' Chris@0: ); Chris@0: Chris@0: if (is_array($type)) { Chris@0: foreach ($type as $_type) { Chris@0: if (!interface_exists($_type, $callAutoload)) { Chris@0: throw new PHPUnit_Framework_Exception( Chris@0: sprintf( Chris@0: 'Interface "%s" does not exist.', Chris@0: $_type Chris@0: ) Chris@0: ); Chris@0: } Chris@0: Chris@0: $additionalInterfaces[] = $_type; Chris@0: Chris@0: foreach ($this->getClassMethods($_type) as $method) { Chris@0: if (in_array($method, $methods)) { Chris@0: throw new PHPUnit_Framework_Exception( Chris@0: sprintf( Chris@0: 'Duplicate method "%s" not allowed.', Chris@0: $method Chris@0: ) Chris@0: ); Chris@0: } Chris@0: Chris@0: $methods[] = $method; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if (class_exists($mockClassName['fullClassName'], $callAutoload)) { Chris@0: $isClass = true; Chris@0: } else { Chris@0: if (interface_exists($mockClassName['fullClassName'], $callAutoload)) { Chris@0: $isInterface = true; Chris@0: } Chris@0: } Chris@0: Chris@0: if (!class_exists($mockClassName['fullClassName'], $callAutoload) && Chris@0: !interface_exists($mockClassName['fullClassName'], $callAutoload)) { Chris@0: $prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n"; Chris@0: Chris@0: if (!empty($mockClassName['namespaceName'])) { Chris@0: $prologue = 'namespace ' . $mockClassName['namespaceName'] . Chris@0: " {\n\n" . $prologue . "}\n\n" . Chris@0: "namespace {\n\n"; Chris@0: Chris@0: $epilogue = "\n\n}"; Chris@0: } Chris@0: Chris@0: $cloneTemplate = new Text_Template( Chris@0: $templateDir . 'mocked_clone.tpl' Chris@0: ); Chris@0: } else { Chris@0: $class = new ReflectionClass($mockClassName['fullClassName']); Chris@0: Chris@0: if ($class->isFinal()) { Chris@0: throw new PHPUnit_Framework_Exception( Chris@0: sprintf( Chris@0: 'Class "%s" is declared "final" and cannot be mocked.', Chris@0: $mockClassName['fullClassName'] Chris@0: ) Chris@0: ); Chris@0: } Chris@0: Chris@0: if ($class->hasMethod('__clone')) { Chris@0: $cloneMethod = $class->getMethod('__clone'); Chris@0: Chris@0: if (!$cloneMethod->isFinal()) { Chris@0: if ($callOriginalClone && !$isInterface) { Chris@0: $cloneTemplate = new Text_Template( Chris@0: $templateDir . 'unmocked_clone.tpl' Chris@0: ); Chris@0: } else { Chris@0: $cloneTemplate = new Text_Template( Chris@0: $templateDir . 'mocked_clone.tpl' Chris@0: ); Chris@0: } Chris@0: } Chris@0: } else { Chris@0: $cloneTemplate = new Text_Template( Chris@0: $templateDir . 'mocked_clone.tpl' Chris@0: ); Chris@0: } Chris@0: } Chris@0: Chris@0: if (is_object($cloneTemplate)) { Chris@0: $cloneTemplate = $cloneTemplate->render(); Chris@0: } Chris@0: Chris@0: if (is_array($methods) && empty($methods) && Chris@0: ($isClass || $isInterface)) { Chris@0: $methods = $this->getClassMethods($mockClassName['fullClassName']); Chris@0: } Chris@0: Chris@0: if (!is_array($methods)) { Chris@0: $methods = array(); Chris@0: } Chris@0: Chris@0: $mockedMethods = ''; Chris@0: Chris@0: if (isset($class)) { Chris@0: // https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 Chris@0: if ($isInterface && $class->implementsInterface('Traversable') && Chris@0: !$class->implementsInterface('Iterator') && Chris@0: !$class->implementsInterface('IteratorAggregate')) { Chris@0: $additionalInterfaces[] = 'Iterator'; Chris@0: $methods = array_merge($methods, $this->getClassMethods('Iterator')); Chris@0: } Chris@0: Chris@0: foreach ($methods as $methodName) { Chris@0: try { Chris@0: $method = $class->getMethod($methodName); Chris@0: Chris@0: if ($this->canMockMethod($method)) { Chris@0: $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting( Chris@0: $templateDir, Chris@0: $method, Chris@0: $cloneArguments, Chris@0: $callOriginalMethods Chris@0: ); Chris@0: } Chris@0: } catch (ReflectionException $e) { Chris@0: $mockedMethods .= $this->generateMockedMethodDefinition( Chris@0: $templateDir, Chris@0: $mockClassName['fullClassName'], Chris@0: $methodName, Chris@0: $cloneArguments Chris@0: ); Chris@0: } Chris@0: } Chris@0: } else { Chris@0: foreach ($methods as $methodName) { Chris@0: $mockedMethods .= $this->generateMockedMethodDefinition( Chris@0: $templateDir, Chris@0: $mockClassName['fullClassName'], Chris@0: $methodName, Chris@0: $cloneArguments Chris@0: ); Chris@0: } Chris@0: } Chris@0: Chris@0: $method = ''; Chris@0: Chris@0: if (!in_array('method', $methods)) { Chris@0: $methodTemplate = new Text_Template( Chris@0: $templateDir . 'mocked_class_method.tpl' Chris@0: ); Chris@0: Chris@0: $method = $methodTemplate->render(); Chris@0: } Chris@0: Chris@0: $classTemplate->setVar( Chris@0: array( Chris@0: 'prologue' => isset($prologue) ? $prologue : '', Chris@0: 'epilogue' => isset($epilogue) ? $epilogue : '', Chris@0: 'class_declaration' => $this->generateMockClassDeclaration( Chris@0: $mockClassName, Chris@0: $isInterface, Chris@0: $additionalInterfaces Chris@0: ), Chris@0: 'clone' => $cloneTemplate, Chris@0: 'mock_class_name' => $mockClassName['className'], Chris@0: 'mocked_methods' => $mockedMethods, Chris@0: 'method' => $method Chris@0: ) Chris@0: ); Chris@0: Chris@0: return array( Chris@0: 'code' => $classTemplate->render(), Chris@0: 'mockClassName' => $mockClassName['className'] Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param array|string $type Chris@0: * @param string $className Chris@0: * @param string $prefix Chris@0: * @return array Chris@0: */ Chris@0: protected function generateClassName($type, $className, $prefix) Chris@0: { Chris@0: if (is_array($type)) { Chris@0: $type = implode('_', $type); Chris@0: } Chris@0: Chris@0: if ($type[0] == '\\') { Chris@0: $type = substr($type, 1); Chris@0: } Chris@0: Chris@0: $classNameParts = explode('\\', $type); Chris@0: Chris@0: if (count($classNameParts) > 1) { Chris@0: $type = array_pop($classNameParts); Chris@0: $namespaceName = implode('\\', $classNameParts); Chris@0: $fullClassName = $namespaceName . '\\' . $type; Chris@0: } else { Chris@0: $namespaceName = ''; Chris@0: $fullClassName = $type; Chris@0: } Chris@0: Chris@0: if ($className == '') { Chris@0: do { Chris@0: $className = $prefix . $type . '_' . Chris@0: substr(md5(microtime()), 0, 8); Chris@0: } while (class_exists($className, false)); Chris@0: } Chris@0: Chris@0: return array( Chris@0: 'className' => $className, Chris@0: 'originalClassName' => $type, Chris@0: 'fullClassName' => $fullClassName, Chris@0: 'namespaceName' => $namespaceName Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param array $mockClassName Chris@0: * @param bool $isInterface Chris@0: * @param array $additionalInterfaces Chris@0: * @return array Chris@0: */ Chris@0: protected function generateMockClassDeclaration(array $mockClassName, $isInterface, array $additionalInterfaces = array()) Chris@0: { Chris@0: $buffer = 'class '; Chris@0: Chris@0: $additionalInterfaces[] = 'PHPUnit_Framework_MockObject_MockObject'; Chris@0: $interfaces = implode(', ', $additionalInterfaces); Chris@0: Chris@0: if ($isInterface) { Chris@0: $buffer .= sprintf( Chris@0: '%s implements %s', Chris@0: $mockClassName['className'], Chris@0: $interfaces Chris@0: ); Chris@0: Chris@0: if (!in_array($mockClassName['originalClassName'], $additionalInterfaces)) { Chris@0: $buffer .= ', '; Chris@0: Chris@0: if (!empty($mockClassName['namespaceName'])) { Chris@0: $buffer .= $mockClassName['namespaceName'] . '\\'; Chris@0: } Chris@0: Chris@0: $buffer .= $mockClassName['originalClassName']; Chris@0: } Chris@0: } else { Chris@0: $buffer .= sprintf( Chris@0: '%s extends %s%s implements %s', Chris@0: $mockClassName['className'], Chris@0: !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', Chris@0: $mockClassName['originalClassName'], Chris@0: $interfaces Chris@0: ); Chris@0: } Chris@0: Chris@0: return $buffer; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param string $templateDir Chris@0: * @param ReflectionMethod $method Chris@0: * @param bool $cloneArguments Chris@0: * @param bool $callOriginalMethods Chris@0: * @return string Chris@0: */ Chris@0: protected function generateMockedMethodDefinitionFromExisting($templateDir, ReflectionMethod $method, $cloneArguments, $callOriginalMethods) Chris@0: { Chris@0: if ($method->isPrivate()) { Chris@0: $modifier = 'private'; Chris@0: } elseif ($method->isProtected()) { Chris@0: $modifier = 'protected'; Chris@0: } else { Chris@0: $modifier = 'public'; Chris@0: } Chris@0: Chris@0: if ($method->isStatic()) { Chris@0: $modifier .= ' static'; Chris@0: } Chris@0: Chris@0: if ($method->returnsReference()) { Chris@0: $reference = '&'; Chris@0: } else { Chris@0: $reference = ''; Chris@0: } Chris@0: Chris@0: return $this->generateMockedMethodDefinition( Chris@0: $templateDir, Chris@0: $method->getDeclaringClass()->getName(), Chris@0: $method->getName(), Chris@0: $cloneArguments, Chris@0: $modifier, Chris@0: $this->getMethodParameters($method), Chris@0: $this->getMethodParameters($method, true), Chris@0: $reference, Chris@0: $callOriginalMethods, Chris@0: $method->isStatic() Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param string $templateDir Chris@0: * @param string $className Chris@0: * @param string $methodName Chris@0: * @param bool $cloneArguments Chris@0: * @param string $modifier Chris@0: * @param string $arguments_decl Chris@0: * @param string $arguments_call Chris@0: * @param string $reference Chris@0: * @param bool $callOriginalMethods Chris@0: * @param bool $static Chris@0: * @return string Chris@0: */ Chris@0: protected function generateMockedMethodDefinition($templateDir, $className, $methodName, $cloneArguments = true, $modifier = 'public', $arguments_decl = '', $arguments_call = '', $reference = '', $callOriginalMethods = false, $static = false) Chris@0: { Chris@0: if ($static) { Chris@0: $templateFile = 'mocked_static_method.tpl'; Chris@0: } else { Chris@0: $templateFile = sprintf( Chris@0: '%s_method.tpl', Chris@0: $callOriginalMethods ? 'proxied' : 'mocked' Chris@0: ); Chris@0: } Chris@0: Chris@0: $template = new Text_Template($templateDir . $templateFile); Chris@0: Chris@0: $template->setVar( Chris@0: array( Chris@0: 'arguments_decl' => $arguments_decl, Chris@0: 'arguments_call' => $arguments_call, Chris@0: 'arguments_count' => !empty($arguments_call) ? count(explode(',', $arguments_call)) : 0, Chris@0: 'class_name' => $className, Chris@0: 'method_name' => $methodName, Chris@0: 'modifier' => $modifier, Chris@0: 'reference' => $reference, Chris@0: 'clone_arguments' => $cloneArguments ? 'TRUE' : 'FALSE' Chris@0: ) Chris@0: ); Chris@0: Chris@0: return $template->render(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param ReflectionMethod $method Chris@0: * @return bool Chris@0: */ Chris@0: protected function canMockMethod(ReflectionMethod $method) Chris@0: { Chris@0: if ($method->isConstructor() || Chris@0: $method->isFinal() || Chris@0: $method->isPrivate() || Chris@0: isset($this->blacklistedMethodNames[$method->getName()])) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the parameters of a function or method. Chris@0: * Chris@0: * @param ReflectionMethod $method Chris@0: * @param bool $forCall Chris@0: * @return string Chris@0: * @throws PHPUnit_Framework_MockObject_RuntimeException Chris@0: * @since Method available since Release 2.0.0 Chris@0: */ Chris@0: protected function getMethodParameters(ReflectionMethod $method, $forCall = false) Chris@0: { Chris@0: $parameters = array(); Chris@0: Chris@0: foreach ($method->getParameters() as $i => $parameter) { Chris@0: $name = '$' . $parameter->getName(); Chris@0: Chris@0: /* Note: PHP extensions may use empty names for reference arguments Chris@0: * or "..." for methods taking a variable number of arguments. Chris@0: */ Chris@0: if ($name === '$' || $name === '$...') { Chris@0: $name = '$arg' . $i; Chris@0: } Chris@0: Chris@0: if ($this->isVariadic($parameter)) { Chris@0: if ($forCall) { Chris@0: continue; Chris@0: } else { Chris@0: $name = '...' . $name; Chris@0: } Chris@0: } Chris@0: Chris@0: $default = ''; Chris@0: $reference = ''; Chris@0: $typeDeclaration = ''; Chris@0: Chris@0: if (!$forCall) { Chris@0: if ($this->hasType($parameter)) { Chris@0: $typeDeclaration = (string) $parameter->getType() . ' '; Chris@0: } elseif ($parameter->isArray()) { Chris@0: $typeDeclaration = 'array '; Chris@0: } elseif ((defined('HHVM_VERSION') || version_compare(PHP_VERSION, '5.4.0', '>=')) Chris@0: && $parameter->isCallable()) { Chris@0: $typeDeclaration = 'callable '; Chris@0: } else { Chris@0: try { Chris@0: $class = $parameter->getClass(); Chris@0: } catch (ReflectionException $e) { Chris@0: throw new PHPUnit_Framework_MockObject_RuntimeException( Chris@0: sprintf( Chris@0: 'Cannot mock %s::%s() because a class or ' . Chris@0: 'interface used in the signature is not loaded', Chris@0: $method->getDeclaringClass()->getName(), Chris@0: $method->getName() Chris@0: ), Chris@0: 0, Chris@0: $e Chris@0: ); Chris@0: } Chris@0: Chris@0: if ($class !== null) { Chris@0: $typeDeclaration = $class->getName() . ' '; Chris@0: } Chris@0: } Chris@0: Chris@0: if (!$this->isVariadic($parameter)) { Chris@0: if ($parameter->isDefaultValueAvailable()) { Chris@0: $value = $parameter->getDefaultValue(); Chris@0: $default = ' = ' . var_export($value, true); Chris@0: } elseif ($parameter->isOptional()) { Chris@0: $default = ' = null'; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if ($parameter->isPassedByReference()) { Chris@0: $reference = '&'; Chris@0: } Chris@0: Chris@0: $parameters[] = $typeDeclaration . $reference . $name . $default; Chris@0: } Chris@0: Chris@0: return implode(', ', $parameters); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param ReflectionParameter $parameter Chris@0: * @return bool Chris@0: * @since Method available since Release 2.2.1 Chris@0: */ Chris@0: private function isVariadic(ReflectionParameter $parameter) Chris@0: { Chris@0: return method_exists('ReflectionParameter', 'isVariadic') && $parameter->isVariadic(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param ReflectionParameter $parameter Chris@0: * @return bool Chris@0: * @since Method available since Release 2.3.4 Chris@0: */ Chris@0: private function hasType(ReflectionParameter $parameter) Chris@0: { Chris@0: return method_exists('ReflectionParameter', 'hasType') && $parameter->hasType(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param string $className Chris@0: * @return array Chris@0: * @since Method available since Release 2.3.2 Chris@0: */ Chris@0: private function getClassMethods($className) Chris@0: { Chris@0: $class = new ReflectionClass($className); Chris@0: $methods = array(); Chris@0: Chris@0: foreach ($class->getMethods() as $method) { Chris@0: if (($method->isPublic() || $method->isAbstract()) && !in_array($method->getName(), $methods)) { Chris@0: $methods[] = $method->getName(); Chris@0: } Chris@0: } Chris@0: Chris@0: return $methods; Chris@0: } Chris@0: }