5 * Copyright (c) 2010-2011, Sebastian Bergmann <sb@sebastian-bergmann.de>.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
20 * * Neither the name of Sebastian Bergmann nor the names of his
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
37 * @package PHPUnit_MockObject
38 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
39 * @copyright 2010-2011 Sebastian Bergmann <sb@sebastian-bergmann.de>
40 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
41 * @link http://github.com/sebastianbergmann/phpunit-mock-objects
42 * @since File available since Release 1.0.0
45 require_once 'Text/Template.php';
48 * Mock Object Code Generator
50 * @package PHPUnit_MockObject
51 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
52 * @copyright 2010-2011 Sebastian Bergmann <sb@sebastian-bergmann.de>
53 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
54 * @version Release: 1.0.8
55 * @link http://github.com/sebastianbergmann/phpunit-mock-objects
56 * @since Class available since Release 1.0.0
58 class PHPUnit_Framework_MockObject_Generator
63 protected static $cache = array();
68 protected static $blacklistedMethodNames = array(
100 'implements' => TRUE,
102 'instanceof' => TRUE,
122 protected static $soapLoaded = NULL;
125 * Returns a mock object for the specified class.
127 * @param string $originalClassName
128 * @param array $methods
129 * @param array $arguments
130 * @param string $mockClassName
131 * @param boolean $callOriginalConstructor
132 * @param boolean $callOriginalClone
133 * @param boolean $callAutoload
135 * @throws InvalidArgumentException
136 * @since Method available since Release 1.0.0
138 public static function getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE)
140 if (!is_string($originalClassName)) {
141 throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
144 if (!is_string($mockClassName)) {
145 throw PHPUnit_Util_InvalidArgumentHelper::factory(4, 'string');
148 if (!is_array($methods) && !is_null($methods)) {
149 throw new InvalidArgumentException;
152 if ($mockClassName != '' && class_exists($mockClassName, FALSE)) {
153 throw new PHPUnit_Framework_Exception(
155 'Class "%s" already exists.',
161 $mock = self::generate(
169 if (!class_exists($mock['mockClassName'], FALSE)) {
173 if ($callOriginalConstructor &&
174 !interface_exists($originalClassName, $callAutoload)) {
175 if (count($arguments) == 0) {
176 $mockObject = new $mock['mockClassName'];
178 $mockClass = new ReflectionClass($mock['mockClassName']);
179 $mockObject = $mockClass->newInstanceArgs($arguments);
182 // Use a trick to create a new object of a class
183 // without invoking its constructor.
184 $mockObject = unserialize(
187 strlen($mock['mockClassName']), $mock['mockClassName']
196 * Returns a mock object for the specified abstract class with all abstract
197 * methods of the class mocked. Concrete methods are not mocked.
199 * @param string $originalClassName
200 * @param array $arguments
201 * @param string $mockClassName
202 * @param boolean $callOriginalConstructor
203 * @param boolean $callOriginalClone
204 * @param boolean $callAutoload
206 * @since Method available since Release 1.0.0
207 * @throws InvalidArgumentException
209 public static function getMockForAbstractClass($originalClassName, array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE)
211 if (!is_string($originalClassName)) {
212 throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
215 if (!is_string($mockClassName)) {
216 throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string');
219 if (class_exists($originalClassName, $callAutoload)) {
221 $reflector = new ReflectionClass($originalClassName);
223 foreach ($reflector->getMethods() as $method) {
224 if ($method->isAbstract()) {
225 $methods[] = $method->getName();
229 if (empty($methods)) {
233 return self::getMock(
238 $callOriginalConstructor,
243 throw new PHPUnit_Framework_Exception(
245 'Class "%s" does not exist.',
253 * @param string $originalClassName
254 * @param array $methods
255 * @param string $mockClassName
256 * @param boolean $callOriginalClone
257 * @param boolean $callAutoload
260 public static function generate($originalClassName, array $methods = NULL, $mockClassName = '', $callOriginalClone = TRUE, $callAutoload = TRUE)
262 if ($mockClassName == '') {
265 serialize($methods) .
266 serialize($callOriginalClone)
269 if (isset(self::$cache[$key])) {
270 return self::$cache[$key];
274 $mock = self::generateMock(
283 self::$cache[$key] = $mock;
290 * @param string $wsdlFile
291 * @param string $originalClassName
292 * @param array $methods
295 public static function generateClassFromWsdl($wsdlFile, $originalClassName, array $methods = array())
297 if (self::$soapLoaded === NULL) {
298 self::$soapLoaded = extension_loaded('soap');
301 if (self::$soapLoaded) {
302 $client = new SOAPClient($wsdlFile);
303 $_methods = array_unique($client->__getFunctions());
306 $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR .
307 'Generator' . DIRECTORY_SEPARATOR;
308 $methodTemplate = new Text_Template(
309 $templateDir . 'wsdl_method.tpl'
313 foreach ($_methods as $method) {
314 $nameStart = strpos($method, ' ') + 1;
315 $nameEnd = strpos($method, '(');
316 $name = substr($method, $nameStart, $nameEnd - $nameStart);
318 if (empty($methods) || in_array($name, $methods)) {
324 strpos($method, ')') - $nameEnd - 1
327 $numArgs = count($args);
329 for ($i = 0; $i < $numArgs; $i++) {
330 $args[$i] = substr($args[$i], strpos($args[$i], '$'));
333 $methodTemplate->setVar(
335 'method_name' => $name,
336 'arguments' => join(', ', $args)
340 $methodsBuffer .= $methodTemplate->render();
344 $classTemplate = new Text_Template(
345 $templateDir . 'wsdl_class.tpl'
348 $classTemplate->setVar(
350 'class_name' => $originalClassName,
352 'methods' => $methodsBuffer
356 return $classTemplate->render();
358 throw new PHPUnit_Framework_Exception(
359 'The SOAP extension is required to generate a mock object ' .
366 * @param string $originalClassName
367 * @param array|null $methods
368 * @param string $mockClassName
369 * @param boolean $callOriginalClone
370 * @param boolean $callAutoload
373 protected static function generateMock($originalClassName, $methods, $mockClassName, $callOriginalClone, $callAutoload)
375 $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' .
377 $classTemplate = new Text_Template(
378 $templateDir . 'mocked_class.tpl'
382 $isInterface = FALSE;
384 $mockClassName = self::generateMockClassName(
385 $originalClassName, $mockClassName
388 if (class_exists($mockClassName['fullClassName'], $callAutoload)) {
391 if (interface_exists($mockClassName['fullClassName'], $callAutoload)) {
396 if (!class_exists($mockClassName['fullClassName'], $callAutoload) &&
397 !interface_exists($mockClassName['fullClassName'], $callAutoload)) {
398 $prologue = 'class ' . $mockClassName['className'] . "\n{\n}\n\n";
400 if (!empty($mockClassName['namespaceName'])) {
401 $prologue = 'namespace ' . $mockClassName['namespaceName'] .
405 $cloneTemplate = new Text_Template(
406 $templateDir . 'mocked_clone.tpl'
409 $class = new ReflectionClass($mockClassName['fullClassName']);
411 if ($class->isFinal()) {
412 throw new PHPUnit_Framework_Exception(
414 'Class "%s" is declared "final" and cannot be mocked.',
415 $mockClassName['fullClassName']
420 if ($class->hasMethod('__clone')) {
421 $cloneMethod = $class->getMethod('__clone');
423 if (!$cloneMethod->isFinal()) {
424 if ($callOriginalClone && !$isInterface) {
425 $cloneTemplate = new Text_Template(
426 $templateDir . 'unmocked_clone.tpl'
429 $cloneTemplate = new Text_Template(
430 $templateDir . 'mocked_clone.tpl'
435 $cloneTemplate = new Text_Template(
436 $templateDir . 'mocked_clone.tpl'
441 if (is_object($cloneTemplate)) {
442 $cloneTemplate = $cloneTemplate->render();
445 if (is_array($methods) && empty($methods) &&
446 ($isClass || $isInterface)) {
447 $methods = get_class_methods($mockClassName['fullClassName']);
450 if (!is_array($methods)) {
458 if ($class->hasMethod('__construct')) {
459 $constructor = $class->getMethod('__construct');
462 else if ($class->hasMethod($originalClassName)) {
463 $constructor = $class->getMethod($originalClassName);
466 foreach ($methods as $methodName) {
468 $method = $class->getMethod($methodName);
470 if (self::canMockMethod($method)) {
471 $mockedMethods .= self::generateMockedMethodDefinitionFromExisting(
472 $templateDir, $method
477 catch (ReflectionException $e) {
478 $mockedMethods .= self::generateMockedMethodDefinition(
479 $templateDir, $mockClassName['fullClassName'], $methodName
484 foreach ($methods as $methodName) {
485 $mockedMethods .= self::generateMockedMethodDefinition(
486 $templateDir, $mockClassName['fullClassName'], $methodName
491 $classTemplate->setVar(
493 'prologue' => isset($prologue) ? $prologue : '',
494 'class_declaration' => self::generateMockClassDeclaration(
495 $mockClassName, $isInterface
497 'clone' => $cloneTemplate,
498 'mock_class_name' => $mockClassName['mockClassName'],
499 'mocked_methods' => $mockedMethods
504 'code' => $classTemplate->render(),
505 'mockClassName' => $mockClassName['mockClassName']
510 * @param string $originalClassName
511 * @param string $mockClassName
514 protected static function generateMockClassName($originalClassName, $mockClassName)
516 $classNameParts = explode('\\', $originalClassName);
518 if (count($classNameParts) > 1) {
519 $originalClassName = array_pop($classNameParts);
520 $namespaceName = join('\\', $classNameParts);
521 $fullClassName = $namespaceName . '\\' . $originalClassName;
524 $fullClassName = $originalClassName;
527 if ($mockClassName == '') {
529 $mockClassName = 'Mock_' . $originalClassName . '_' .
530 substr(md5(microtime()), 0, 8);
532 while (class_exists($mockClassName, FALSE));
536 'mockClassName' => $mockClassName,
537 'className' => $originalClassName,
538 'fullClassName' => $fullClassName,
539 'namespaceName' => $namespaceName
544 * @param array $mockClassName
545 * @param boolean $isInterface
548 protected static function generateMockClassDeclaration(array $mockClassName, $isInterface)
554 "%s implements PHPUnit_Framework_MockObject_MockObject, %s%s",
555 $mockClassName['mockClassName'],
556 !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '',
557 $mockClassName['className']
561 "%s extends %s%s implements PHPUnit_Framework_MockObject_MockObject",
562 $mockClassName['mockClassName'],
563 !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '',
564 $mockClassName['className']
572 * @param string $templateDir
573 * @param ReflectionMethod $method
576 protected static function generateMockedMethodDefinitionFromExisting($templateDir, ReflectionMethod $method)
578 if ($method->isPrivate()) {
579 $modifier = 'private';
582 else if ($method->isProtected()) {
583 $modifier = 'protected';
587 $modifier = 'public';
590 if ($method->isStatic()) {
596 if ($method->returnsReference()) {
602 return self::generateMockedMethodDefinition(
604 $method->getDeclaringClass()->getName(),
607 PHPUnit_Util_Class::getMethodParameters($method),
608 PHPUnit_Util_Class::getMethodParameters($method, TRUE),
615 * @param string $templateDir
616 * @param string $className
617 * @param string $methodName
618 * @param string $modifier
619 * @param string $arguments_decl
620 * @param string $arguments_call
621 * @param string $reference
622 * @param boolean $static
625 protected static function generateMockedMethodDefinition($templateDir, $className, $methodName, $modifier = 'public', $arguments_decl = '', $arguments_call = '', $reference = '', $static = FALSE)
628 $template = new Text_Template(
629 $templateDir . 'mocked_static_method.tpl'
632 $template = new Text_Template(
633 $templateDir . 'mocked_object_method.tpl'
639 'arguments_decl' => $arguments_decl,
640 'arguments_call' => $arguments_call,
641 'arguments_count' => !empty($arguments_call) ? count(explode(',', $arguments_call)) : 0,
642 'class_name' => $className,
643 'method_name' => $methodName,
644 'modifier' => $modifier,
645 'reference' => $reference
649 return $template->render();
653 * @param ReflectionMethod $method
656 protected static function canMockMethod(ReflectionMethod $method)
658 if ($method->isConstructor() || $method->isFinal() ||
659 isset(self::$blacklistedMethodNames[$method->getName()])) {