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.9
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(
105 'implements' => TRUE,
107 'include_once' => TRUE,
108 'instanceof' => TRUE,
120 'require_once' => TRUE,
136 protected static $soapLoaded = NULL;
139 * Returns a mock object for the specified class.
141 * @param string $originalClassName
142 * @param array $methods
143 * @param array $arguments
144 * @param string $mockClassName
145 * @param boolean $callOriginalConstructor
146 * @param boolean $callOriginalClone
147 * @param boolean $callAutoload
149 * @throws InvalidArgumentException
150 * @since Method available since Release 1.0.0
152 public static function getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE)
154 if (!is_string($originalClassName)) {
155 throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
158 if (!is_string($mockClassName)) {
159 throw PHPUnit_Util_InvalidArgumentHelper::factory(4, 'string');
162 if (!is_array($methods) && !is_null($methods)) {
163 throw new InvalidArgumentException;
166 if ($mockClassName != '' && class_exists($mockClassName, FALSE)) {
167 throw new PHPUnit_Framework_Exception(
169 'Class "%s" already exists.',
175 $mock = self::generate(
183 if (!class_exists($mock['mockClassName'], FALSE)) {
187 if ($callOriginalConstructor &&
188 !interface_exists($originalClassName, $callAutoload)) {
189 if (count($arguments) == 0) {
190 $mockObject = new $mock['mockClassName'];
192 $mockClass = new ReflectionClass($mock['mockClassName']);
193 $mockObject = $mockClass->newInstanceArgs($arguments);
196 // Use a trick to create a new object of a class
197 // without invoking its constructor.
198 $mockObject = unserialize(
201 strlen($mock['mockClassName']), $mock['mockClassName']
210 * Returns a mock object for the specified abstract class with all abstract
211 * methods of the class mocked. Concrete methods are not mocked.
213 * @param string $originalClassName
214 * @param array $arguments
215 * @param string $mockClassName
216 * @param boolean $callOriginalConstructor
217 * @param boolean $callOriginalClone
218 * @param boolean $callAutoload
220 * @since Method available since Release 1.0.0
221 * @throws InvalidArgumentException
223 public static function getMockForAbstractClass($originalClassName, array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE)
225 if (!is_string($originalClassName)) {
226 throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
229 if (!is_string($mockClassName)) {
230 throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string');
233 if (class_exists($originalClassName, $callAutoload)) {
235 $reflector = new ReflectionClass($originalClassName);
237 foreach ($reflector->getMethods() as $method) {
238 if ($method->isAbstract()) {
239 $methods[] = $method->getName();
243 if (empty($methods)) {
247 return self::getMock(
252 $callOriginalConstructor,
257 throw new PHPUnit_Framework_Exception(
259 'Class "%s" does not exist.',
267 * @param string $originalClassName
268 * @param array $methods
269 * @param string $mockClassName
270 * @param boolean $callOriginalClone
271 * @param boolean $callAutoload
274 public static function generate($originalClassName, array $methods = NULL, $mockClassName = '', $callOriginalClone = TRUE, $callAutoload = TRUE)
276 if ($mockClassName == '') {
279 serialize($methods) .
280 serialize($callOriginalClone)
283 if (isset(self::$cache[$key])) {
284 return self::$cache[$key];
288 $mock = self::generateMock(
297 self::$cache[$key] = $mock;
304 * @param string $wsdlFile
305 * @param string $originalClassName
306 * @param array $methods
309 public static function generateClassFromWsdl($wsdlFile, $originalClassName, array $methods = array())
311 if (self::$soapLoaded === NULL) {
312 self::$soapLoaded = extension_loaded('soap');
315 if (self::$soapLoaded) {
316 $client = new SOAPClient($wsdlFile);
317 $_methods = array_unique($client->__getFunctions());
320 $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR .
321 'Generator' . DIRECTORY_SEPARATOR;
322 $methodTemplate = new Text_Template(
323 $templateDir . 'wsdl_method.tpl'
327 foreach ($_methods as $method) {
328 $nameStart = strpos($method, ' ') + 1;
329 $nameEnd = strpos($method, '(');
330 $name = substr($method, $nameStart, $nameEnd - $nameStart);
332 if (empty($methods) || in_array($name, $methods)) {
338 strpos($method, ')') - $nameEnd - 1
341 $numArgs = count($args);
343 for ($i = 0; $i < $numArgs; $i++) {
344 $args[$i] = substr($args[$i], strpos($args[$i], '$'));
347 $methodTemplate->setVar(
349 'method_name' => $name,
350 'arguments' => join(', ', $args)
354 $methodsBuffer .= $methodTemplate->render();
358 $classTemplate = new Text_Template(
359 $templateDir . 'wsdl_class.tpl'
362 $classTemplate->setVar(
364 'class_name' => $originalClassName,
366 'methods' => $methodsBuffer
370 return $classTemplate->render();
372 throw new PHPUnit_Framework_Exception(
373 'The SOAP extension is required to generate a mock object ' .
380 * @param string $originalClassName
381 * @param array|null $methods
382 * @param string $mockClassName
383 * @param boolean $callOriginalClone
384 * @param boolean $callAutoload
387 protected static function generateMock($originalClassName, $methods, $mockClassName, $callOriginalClone, $callAutoload)
389 $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' .
391 $classTemplate = new Text_Template(
392 $templateDir . 'mocked_class.tpl'
396 $isInterface = FALSE;
398 $mockClassName = self::generateMockClassName(
399 $originalClassName, $mockClassName
402 if (class_exists($mockClassName['fullClassName'], $callAutoload)) {
405 if (interface_exists($mockClassName['fullClassName'], $callAutoload)) {
410 if (!class_exists($mockClassName['fullClassName'], $callAutoload) &&
411 !interface_exists($mockClassName['fullClassName'], $callAutoload)) {
412 $prologue = 'class ' . $mockClassName['className'] . "\n{\n}\n\n";
414 if (!empty($mockClassName['namespaceName'])) {
415 $prologue = 'namespace ' . $mockClassName['namespaceName'] .
416 " {\n\n" . $prologue . "}\n\n" .
422 $cloneTemplate = new Text_Template(
423 $templateDir . 'mocked_clone.tpl'
426 $class = new ReflectionClass($mockClassName['fullClassName']);
428 if ($class->isFinal()) {
429 throw new PHPUnit_Framework_Exception(
431 'Class "%s" is declared "final" and cannot be mocked.',
432 $mockClassName['fullClassName']
437 if ($class->hasMethod('__clone')) {
438 $cloneMethod = $class->getMethod('__clone');
440 if (!$cloneMethod->isFinal()) {
441 if ($callOriginalClone && !$isInterface) {
442 $cloneTemplate = new Text_Template(
443 $templateDir . 'unmocked_clone.tpl'
446 $cloneTemplate = new Text_Template(
447 $templateDir . 'mocked_clone.tpl'
452 $cloneTemplate = new Text_Template(
453 $templateDir . 'mocked_clone.tpl'
458 if (is_object($cloneTemplate)) {
459 $cloneTemplate = $cloneTemplate->render();
462 if (is_array($methods) && empty($methods) &&
463 ($isClass || $isInterface)) {
464 $methods = get_class_methods($mockClassName['fullClassName']);
467 if (!is_array($methods)) {
475 if ($class->hasMethod('__construct')) {
476 $constructor = $class->getMethod('__construct');
479 else if ($class->hasMethod($originalClassName)) {
480 $constructor = $class->getMethod($originalClassName);
483 foreach ($methods as $methodName) {
485 $method = $class->getMethod($methodName);
487 if (self::canMockMethod($method)) {
488 $mockedMethods .= self::generateMockedMethodDefinitionFromExisting(
489 $templateDir, $method
494 catch (ReflectionException $e) {
495 $mockedMethods .= self::generateMockedMethodDefinition(
496 $templateDir, $mockClassName['fullClassName'], $methodName
501 foreach ($methods as $methodName) {
502 $mockedMethods .= self::generateMockedMethodDefinition(
503 $templateDir, $mockClassName['fullClassName'], $methodName
508 $classTemplate->setVar(
510 'prologue' => isset($prologue) ? $prologue : '',
511 'epilogue' => isset($epilogue) ? $epilogue : '',
512 'class_declaration' => self::generateMockClassDeclaration(
513 $mockClassName, $isInterface
515 'clone' => $cloneTemplate,
516 'mock_class_name' => $mockClassName['mockClassName'],
517 'mocked_methods' => $mockedMethods
522 'code' => $classTemplate->render(),
523 'mockClassName' => $mockClassName['mockClassName']
528 * @param string $originalClassName
529 * @param string $mockClassName
532 protected static function generateMockClassName($originalClassName, $mockClassName)
534 if ($originalClassName[0] == '\\') {
535 $originalClassName = substr($originalClassName, 1);
538 $classNameParts = explode('\\', $originalClassName);
540 if (count($classNameParts) > 1) {
541 $originalClassName = array_pop($classNameParts);
542 $namespaceName = join('\\', $classNameParts);
543 $fullClassName = $namespaceName . '\\' . $originalClassName;
546 $fullClassName = $originalClassName;
549 if ($mockClassName == '') {
551 $mockClassName = 'Mock_' . $originalClassName . '_' .
552 substr(md5(microtime()), 0, 8);
554 while (class_exists($mockClassName, FALSE));
558 'mockClassName' => $mockClassName,
559 'className' => $originalClassName,
560 'fullClassName' => $fullClassName,
561 'namespaceName' => $namespaceName
566 * @param array $mockClassName
567 * @param boolean $isInterface
570 protected static function generateMockClassDeclaration(array $mockClassName, $isInterface)
576 "%s implements PHPUnit_Framework_MockObject_MockObject, %s%s",
577 $mockClassName['mockClassName'],
578 !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '',
579 $mockClassName['className']
583 "%s extends %s%s implements PHPUnit_Framework_MockObject_MockObject",
584 $mockClassName['mockClassName'],
585 !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '',
586 $mockClassName['className']
594 * @param string $templateDir
595 * @param ReflectionMethod $method
598 protected static function generateMockedMethodDefinitionFromExisting($templateDir, ReflectionMethod $method)
600 if ($method->isPrivate()) {
601 $modifier = 'private';
604 else if ($method->isProtected()) {
605 $modifier = 'protected';
609 $modifier = 'public';
612 if ($method->isStatic()) {
618 if ($method->returnsReference()) {
624 return self::generateMockedMethodDefinition(
626 $method->getDeclaringClass()->getName(),
629 PHPUnit_Util_Class::getMethodParameters($method),
630 PHPUnit_Util_Class::getMethodParameters($method, TRUE),
637 * @param string $templateDir
638 * @param string $className
639 * @param string $methodName
640 * @param string $modifier
641 * @param string $arguments_decl
642 * @param string $arguments_call
643 * @param string $reference
644 * @param boolean $static
647 protected static function generateMockedMethodDefinition($templateDir, $className, $methodName, $modifier = 'public', $arguments_decl = '', $arguments_call = '', $reference = '', $static = FALSE)
650 $template = new Text_Template(
651 $templateDir . 'mocked_static_method.tpl'
654 $template = new Text_Template(
655 $templateDir . 'mocked_object_method.tpl'
661 'arguments_decl' => $arguments_decl,
662 'arguments_call' => $arguments_call,
663 'arguments_count' => !empty($arguments_call) ? count(explode(',', $arguments_call)) : 0,
664 'class_name' => $className,
665 'method_name' => $methodName,
666 'modifier' => $modifier,
667 'reference' => $reference
671 return $template->render();
675 * @param ReflectionMethod $method
678 protected static function canMockMethod(ReflectionMethod $method)
680 if ($method->isConstructor() || $method->isFinal() ||
681 isset(self::$blacklistedMethodNames[$method->getName()])) {