1<?php 2/* 3 * This file is part of the PHPUnit_MockObject package. 4 * 5 * (c) Sebastian Bergmann <sebastian@phpunit.de> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11use Doctrine\Instantiator\Instantiator; 12use Doctrine\Instantiator\Exception\InvalidArgumentException as InstantiatorInvalidArgumentException; 13use Doctrine\Instantiator\Exception\UnexpectedValueException as InstantiatorUnexpectedValueException; 14 15if (!function_exists('trait_exists')) { 16 function trait_exists($traitname, $autoload = true) 17 { 18 return false; 19 } 20} 21 22/** 23 * Mock Object Code Generator 24 * 25 * @since Class available since Release 1.0.0 26 */ 27class PHPUnit_Framework_MockObject_Generator 28{ 29 /** 30 * @var array 31 */ 32 private static $cache = array(); 33 34 /** 35 * @var array 36 */ 37 protected $blacklistedMethodNames = array( 38 '__CLASS__' => true, 39 '__DIR__' => true, 40 '__FILE__' => true, 41 '__FUNCTION__' => true, 42 '__LINE__' => true, 43 '__METHOD__' => true, 44 '__NAMESPACE__' => true, 45 '__TRAIT__' => true, 46 '__clone' => true, 47 '__halt_compiler' => true, 48 'abstract' => true, 49 'and' => true, 50 'array' => true, 51 'as' => true, 52 'break' => true, 53 'callable' => true, 54 'case' => true, 55 'catch' => true, 56 'class' => true, 57 'clone' => true, 58 'const' => true, 59 'continue' => true, 60 'declare' => true, 61 'default' => true, 62 'die' => true, 63 'do' => true, 64 'echo' => true, 65 'else' => true, 66 'elseif' => true, 67 'empty' => true, 68 'enddeclare' => true, 69 'endfor' => true, 70 'endforeach' => true, 71 'endif' => true, 72 'endswitch' => true, 73 'endwhile' => true, 74 'eval' => true, 75 'exit' => true, 76 'expects' => true, 77 'extends' => true, 78 'final' => true, 79 'for' => true, 80 'foreach' => true, 81 'function' => true, 82 'global' => true, 83 'goto' => true, 84 'if' => true, 85 'implements' => true, 86 'include' => true, 87 'include_once' => true, 88 'instanceof' => true, 89 'insteadof' => true, 90 'interface' => true, 91 'isset' => true, 92 'list' => true, 93 'namespace' => true, 94 'new' => true, 95 'or' => true, 96 'print' => true, 97 'private' => true, 98 'protected' => true, 99 'public' => true, 100 'require' => true, 101 'require_once' => true, 102 'return' => true, 103 'static' => true, 104 'switch' => true, 105 'throw' => true, 106 'trait' => true, 107 'try' => true, 108 'unset' => true, 109 'use' => true, 110 'var' => true, 111 'while' => true, 112 'xor' => true 113 ); 114 115 /** 116 * Returns a mock object for the specified class. 117 * 118 * @param array|string $type 119 * @param array $methods 120 * @param array $arguments 121 * @param string $mockClassName 122 * @param bool $callOriginalConstructor 123 * @param bool $callOriginalClone 124 * @param bool $callAutoload 125 * @param bool $cloneArguments 126 * @param bool $callOriginalMethods 127 * @param object $proxyTarget 128 * @return object 129 * @throws InvalidArgumentException 130 * @throws PHPUnit_Framework_Exception 131 * @throws PHPUnit_Framework_MockObject_RuntimeException 132 * @since Method available since Release 1.0.0 133 */ 134 public function getMock($type, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false, $proxyTarget = null) 135 { 136 if (!is_array($type) && !is_string($type)) { 137 throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'array or string'); 138 } 139 140 if (!is_string($mockClassName)) { 141 throw PHPUnit_Util_InvalidArgumentHelper::factory(4, 'string'); 142 } 143 144 if (!is_array($methods) && !is_null($methods)) { 145 throw new InvalidArgumentException; 146 } 147 148 if ($type === 'Traversable' || $type === '\\Traversable') { 149 $type = 'Iterator'; 150 } 151 152 if (is_array($type)) { 153 $type = array_unique(array_map( 154 function ($type) { 155 if ($type === 'Traversable' || 156 $type === '\\Traversable' || 157 $type === '\\Iterator') { 158 return 'Iterator'; 159 } 160 161 return $type; 162 }, 163 $type 164 )); 165 } 166 167 if (null !== $methods) { 168 foreach ($methods as $method) { 169 if (!preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*~', $method)) { 170 throw new PHPUnit_Framework_Exception( 171 sprintf( 172 'Cannot stub or mock method with invalid name "%s"', 173 $method 174 ) 175 ); 176 } 177 } 178 179 if ($methods != array_unique($methods)) { 180 throw new PHPUnit_Framework_MockObject_RuntimeException( 181 sprintf( 182 'Cannot stub or mock using a method list that contains duplicates: "%s"', 183 implode(', ', $methods) 184 ) 185 ); 186 } 187 } 188 189 if ($mockClassName != '' && class_exists($mockClassName, false)) { 190 $reflect = new ReflectionClass($mockClassName); 191 192 if (!$reflect->implementsInterface('PHPUnit_Framework_MockObject_MockObject')) { 193 throw new PHPUnit_Framework_MockObject_RuntimeException( 194 sprintf( 195 'Class "%s" already exists.', 196 $mockClassName 197 ) 198 ); 199 } 200 } 201 202 $mock = $this->generate( 203 $type, 204 $methods, 205 $mockClassName, 206 $callOriginalClone, 207 $callAutoload, 208 $cloneArguments, 209 $callOriginalMethods 210 ); 211 212 return $this->getObject( 213 $mock['code'], 214 $mock['mockClassName'], 215 $type, 216 $callOriginalConstructor, 217 $callAutoload, 218 $arguments, 219 $callOriginalMethods, 220 $proxyTarget 221 ); 222 } 223 224 /** 225 * @param string $code 226 * @param string $className 227 * @param array|string $type 228 * @param bool $callOriginalConstructor 229 * @param bool $callAutoload 230 * @param array $arguments 231 * @param bool $callOriginalMethods 232 * @param object $proxyTarget 233 * @return object 234 */ 235 protected function getObject($code, $className, $type = '', $callOriginalConstructor = false, $callAutoload = false, array $arguments = array(), $callOriginalMethods = false, $proxyTarget = null) 236 { 237 $this->evalClass($code, $className); 238 239 if ($callOriginalConstructor && 240 is_string($type) && 241 !interface_exists($type, $callAutoload)) { 242 if (count($arguments) == 0) { 243 $object = new $className; 244 } else { 245 $class = new ReflectionClass($className); 246 $object = $class->newInstanceArgs($arguments); 247 } 248 } else { 249 try { 250 $instantiator = new Instantiator; 251 $object = $instantiator->instantiate($className); 252 } catch (InstantiatorUnexpectedValueException $exception) { 253 if ($exception->getPrevious()) { 254 $exception = $exception->getPrevious(); 255 } 256 257 throw new PHPUnit_Framework_MockObject_RuntimeException( 258 $exception->getMessage() 259 ); 260 } catch (InstantiatorInvalidArgumentException $exception) { 261 throw new PHPUnit_Framework_MockObject_RuntimeException( 262 $exception->getMessage() 263 ); 264 } 265 } 266 267 if ($callOriginalMethods) { 268 if (!is_object($proxyTarget)) { 269 if (count($arguments) == 0) { 270 $proxyTarget = new $type; 271 } else { 272 $class = new ReflectionClass($type); 273 $proxyTarget = $class->newInstanceArgs($arguments); 274 } 275 } 276 277 $object->__phpunit_setOriginalObject($proxyTarget); 278 } 279 280 return $object; 281 } 282 283 /** 284 * @param string $code 285 * @param string $className 286 */ 287 protected function evalClass($code, $className) 288 { 289 if (!class_exists($className, false)) { 290 eval($code); 291 } 292 } 293 294 /** 295 * Returns a mock object for the specified abstract class with all abstract 296 * methods of the class mocked. Concrete methods to mock can be specified with 297 * the last parameter 298 * 299 * @param string $originalClassName 300 * @param array $arguments 301 * @param string $mockClassName 302 * @param bool $callOriginalConstructor 303 * @param bool $callOriginalClone 304 * @param bool $callAutoload 305 * @param array $mockedMethods 306 * @param bool $cloneArguments 307 * @return object 308 * @since Method available since Release 1.0.0 309 * @throws PHPUnit_Framework_MockObject_RuntimeException 310 * @throws PHPUnit_Framework_Exception 311 */ 312 public function getMockForAbstractClass($originalClassName, array $arguments = array(), $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = array(), $cloneArguments = true) 313 { 314 if (!is_string($originalClassName)) { 315 throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); 316 } 317 318 if (!is_string($mockClassName)) { 319 throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string'); 320 } 321 322 if (class_exists($originalClassName, $callAutoload) || 323 interface_exists($originalClassName, $callAutoload)) { 324 $reflector = new ReflectionClass($originalClassName); 325 $methods = $mockedMethods; 326 327 foreach ($reflector->getMethods() as $method) { 328 if ($method->isAbstract() && !in_array($method->getName(), $methods)) { 329 $methods[] = $method->getName(); 330 } 331 } 332 333 if (empty($methods)) { 334 $methods = null; 335 } 336 337 return $this->getMock( 338 $originalClassName, 339 $methods, 340 $arguments, 341 $mockClassName, 342 $callOriginalConstructor, 343 $callOriginalClone, 344 $callAutoload, 345 $cloneArguments 346 ); 347 } else { 348 throw new PHPUnit_Framework_MockObject_RuntimeException( 349 sprintf('Class "%s" does not exist.', $originalClassName) 350 ); 351 } 352 } 353 354 /** 355 * Returns a mock object for the specified trait with all abstract methods 356 * of the trait mocked. Concrete methods to mock can be specified with the 357 * `$mockedMethods` parameter. 358 * 359 * @param string $traitName 360 * @param array $arguments 361 * @param string $mockClassName 362 * @param bool $callOriginalConstructor 363 * @param bool $callOriginalClone 364 * @param bool $callAutoload 365 * @param array $mockedMethods 366 * @param bool $cloneArguments 367 * @return object 368 * @since Method available since Release 1.2.3 369 * @throws PHPUnit_Framework_MockObject_RuntimeException 370 * @throws PHPUnit_Framework_Exception 371 */ 372 public function getMockForTrait($traitName, array $arguments = array(), $mockClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = array(), $cloneArguments = true) 373 { 374 if (!is_string($traitName)) { 375 throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); 376 } 377 378 if (!is_string($mockClassName)) { 379 throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string'); 380 } 381 382 if (!trait_exists($traitName, $callAutoload)) { 383 throw new PHPUnit_Framework_MockObject_RuntimeException( 384 sprintf( 385 'Trait "%s" does not exist.', 386 $traitName 387 ) 388 ); 389 } 390 391 $className = $this->generateClassName( 392 $traitName, 393 '', 394 'Trait_' 395 ); 396 397 $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' . 398 DIRECTORY_SEPARATOR; 399 $classTemplate = new Text_Template( 400 $templateDir . 'trait_class.tpl' 401 ); 402 403 $classTemplate->setVar( 404 array( 405 'prologue' => 'abstract ', 406 'class_name' => $className['className'], 407 'trait_name' => $traitName 408 ) 409 ); 410 411 $this->evalClass( 412 $classTemplate->render(), 413 $className['className'] 414 ); 415 416 return $this->getMockForAbstractClass($className['className'], $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $mockedMethods, $cloneArguments); 417 } 418 419 /** 420 * Returns an object for the specified trait. 421 * 422 * @param string $traitName 423 * @param array $arguments 424 * @param string $traitClassName 425 * @param bool $callOriginalConstructor 426 * @param bool $callOriginalClone 427 * @param bool $callAutoload 428 * @return object 429 * @since Method available since Release 1.1.0 430 * @throws PHPUnit_Framework_MockObject_RuntimeException 431 * @throws PHPUnit_Framework_Exception 432 */ 433 public function getObjectForTrait($traitName, array $arguments = array(), $traitClassName = '', $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true) 434 { 435 if (!is_string($traitName)) { 436 throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string'); 437 } 438 439 if (!is_string($traitClassName)) { 440 throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string'); 441 } 442 443 if (!trait_exists($traitName, $callAutoload)) { 444 throw new PHPUnit_Framework_MockObject_RuntimeException( 445 sprintf( 446 'Trait "%s" does not exist.', 447 $traitName 448 ) 449 ); 450 } 451 452 $className = $this->generateClassName( 453 $traitName, 454 $traitClassName, 455 'Trait_' 456 ); 457 458 $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' . 459 DIRECTORY_SEPARATOR; 460 $classTemplate = new Text_Template( 461 $templateDir . 'trait_class.tpl' 462 ); 463 464 $classTemplate->setVar( 465 array( 466 'prologue' => '', 467 'class_name' => $className['className'], 468 'trait_name' => $traitName 469 ) 470 ); 471 472 return $this->getObject( 473 $classTemplate->render(), 474 $className['className'] 475 ); 476 } 477 478 /** 479 * @param array|string $type 480 * @param array $methods 481 * @param string $mockClassName 482 * @param bool $callOriginalClone 483 * @param bool $callAutoload 484 * @param bool $cloneArguments 485 * @param bool $callOriginalMethods 486 * @return array 487 */ 488 public function generate($type, array $methods = null, $mockClassName = '', $callOriginalClone = true, $callAutoload = true, $cloneArguments = true, $callOriginalMethods = false) 489 { 490 if (is_array($type)) { 491 sort($type); 492 } 493 494 if ($mockClassName == '') { 495 $key = md5( 496 is_array($type) ? implode('_', $type) : $type . 497 serialize($methods) . 498 serialize($callOriginalClone) . 499 serialize($cloneArguments) . 500 serialize($callOriginalMethods) 501 ); 502 503 if (isset(self::$cache[$key])) { 504 return self::$cache[$key]; 505 } 506 } 507 508 $mock = $this->generateMock( 509 $type, 510 $methods, 511 $mockClassName, 512 $callOriginalClone, 513 $callAutoload, 514 $cloneArguments, 515 $callOriginalMethods 516 ); 517 518 if (isset($key)) { 519 self::$cache[$key] = $mock; 520 } 521 522 return $mock; 523 } 524 525 /** 526 * @param string $wsdlFile 527 * @param string $className 528 * @param array $methods 529 * @param array $options 530 * @return string 531 * @throws PHPUnit_Framework_MockObject_RuntimeException 532 */ 533 public function generateClassFromWsdl($wsdlFile, $className, array $methods = array(), array $options = array()) 534 { 535 if (!extension_loaded('soap')) { 536 throw new PHPUnit_Framework_MockObject_RuntimeException( 537 'The SOAP extension is required to generate a mock object from WSDL.' 538 ); 539 } 540 541 $options = array_merge($options, array('cache_wsdl' => WSDL_CACHE_NONE)); 542 $client = new SoapClient($wsdlFile, $options); 543 $_methods = array_unique($client->__getFunctions()); 544 unset($client); 545 546 sort($_methods); 547 548 $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' . DIRECTORY_SEPARATOR; 549 $methodTemplate = new Text_Template($templateDir . 'wsdl_method.tpl'); 550 $methodsBuffer = ''; 551 552 foreach ($_methods as $method) { 553 $nameStart = strpos($method, ' ') + 1; 554 $nameEnd = strpos($method, '('); 555 $name = substr($method, $nameStart, $nameEnd - $nameStart); 556 557 if (empty($methods) || in_array($name, $methods)) { 558 $args = explode( 559 ',', 560 substr( 561 $method, 562 $nameEnd + 1, 563 strpos($method, ')') - $nameEnd - 1 564 ) 565 ); 566 $numArgs = count($args); 567 568 for ($i = 0; $i < $numArgs; $i++) { 569 $args[$i] = substr($args[$i], strpos($args[$i], '$')); 570 } 571 572 $methodTemplate->setVar( 573 array( 574 'method_name' => $name, 575 'arguments' => implode(', ', $args) 576 ) 577 ); 578 579 $methodsBuffer .= $methodTemplate->render(); 580 } 581 } 582 583 $optionsBuffer = 'array('; 584 585 foreach ($options as $key => $value) { 586 $optionsBuffer .= $key . ' => ' . $value; 587 } 588 589 $optionsBuffer .= ')'; 590 591 $classTemplate = new Text_Template($templateDir . 'wsdl_class.tpl'); 592 $namespace = ''; 593 594 if (strpos($className, '\\') !== false) { 595 $parts = explode('\\', $className); 596 $className = array_pop($parts); 597 $namespace = 'namespace ' . implode('\\', $parts) . ';' . "\n\n"; 598 } 599 600 $classTemplate->setVar( 601 array( 602 'namespace' => $namespace, 603 'class_name' => $className, 604 'wsdl' => $wsdlFile, 605 'options' => $optionsBuffer, 606 'methods' => $methodsBuffer 607 ) 608 ); 609 610 return $classTemplate->render(); 611 } 612 613 /** 614 * @param array|string $type 615 * @param array|null $methods 616 * @param string $mockClassName 617 * @param bool $callOriginalClone 618 * @param bool $callAutoload 619 * @param bool $cloneArguments 620 * @param bool $callOriginalMethods 621 * @return array 622 * @throws PHPUnit_Framework_Exception 623 */ 624 protected function generateMock($type, $methods, $mockClassName, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods) 625 { 626 $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' . 627 DIRECTORY_SEPARATOR; 628 $classTemplate = new Text_Template( 629 $templateDir . 'mocked_class.tpl' 630 ); 631 632 $additionalInterfaces = array(); 633 $cloneTemplate = ''; 634 $isClass = false; 635 $isInterface = false; 636 637 $mockClassName = $this->generateClassName( 638 $type, 639 $mockClassName, 640 'Mock_' 641 ); 642 643 if (is_array($type)) { 644 foreach ($type as $_type) { 645 if (!interface_exists($_type, $callAutoload)) { 646 throw new PHPUnit_Framework_Exception( 647 sprintf( 648 'Interface "%s" does not exist.', 649 $_type 650 ) 651 ); 652 } 653 654 $additionalInterfaces[] = $_type; 655 656 foreach ($this->getClassMethods($_type) as $method) { 657 if (in_array($method, $methods)) { 658 throw new PHPUnit_Framework_Exception( 659 sprintf( 660 'Duplicate method "%s" not allowed.', 661 $method 662 ) 663 ); 664 } 665 666 $methods[] = $method; 667 } 668 } 669 } 670 671 if (class_exists($mockClassName['fullClassName'], $callAutoload)) { 672 $isClass = true; 673 } else { 674 if (interface_exists($mockClassName['fullClassName'], $callAutoload)) { 675 $isInterface = true; 676 } 677 } 678 679 if (!class_exists($mockClassName['fullClassName'], $callAutoload) && 680 !interface_exists($mockClassName['fullClassName'], $callAutoload)) { 681 $prologue = 'class ' . $mockClassName['originalClassName'] . "\n{\n}\n\n"; 682 683 if (!empty($mockClassName['namespaceName'])) { 684 $prologue = 'namespace ' . $mockClassName['namespaceName'] . 685 " {\n\n" . $prologue . "}\n\n" . 686 "namespace {\n\n"; 687 688 $epilogue = "\n\n}"; 689 } 690 691 $cloneTemplate = new Text_Template( 692 $templateDir . 'mocked_clone.tpl' 693 ); 694 } else { 695 $class = new ReflectionClass($mockClassName['fullClassName']); 696 697 if ($class->isFinal()) { 698 throw new PHPUnit_Framework_Exception( 699 sprintf( 700 'Class "%s" is declared "final" and cannot be mocked.', 701 $mockClassName['fullClassName'] 702 ) 703 ); 704 } 705 706 if ($class->hasMethod('__clone')) { 707 $cloneMethod = $class->getMethod('__clone'); 708 709 if (!$cloneMethod->isFinal()) { 710 if ($callOriginalClone && !$isInterface) { 711 $cloneTemplate = new Text_Template( 712 $templateDir . 'unmocked_clone.tpl' 713 ); 714 } else { 715 $cloneTemplate = new Text_Template( 716 $templateDir . 'mocked_clone.tpl' 717 ); 718 } 719 } 720 } else { 721 $cloneTemplate = new Text_Template( 722 $templateDir . 'mocked_clone.tpl' 723 ); 724 } 725 } 726 727 if (is_object($cloneTemplate)) { 728 $cloneTemplate = $cloneTemplate->render(); 729 } 730 731 if (is_array($methods) && empty($methods) && 732 ($isClass || $isInterface)) { 733 $methods = $this->getClassMethods($mockClassName['fullClassName']); 734 } 735 736 if (!is_array($methods)) { 737 $methods = array(); 738 } 739 740 $mockedMethods = ''; 741 742 if (isset($class)) { 743 // https://github.com/sebastianbergmann/phpunit-mock-objects/issues/103 744 if ($isInterface && $class->implementsInterface('Traversable') && 745 !$class->implementsInterface('Iterator') && 746 !$class->implementsInterface('IteratorAggregate')) { 747 $additionalInterfaces[] = 'Iterator'; 748 $methods = array_merge($methods, $this->getClassMethods('Iterator')); 749 } 750 751 foreach ($methods as $methodName) { 752 try { 753 $method = $class->getMethod($methodName); 754 755 if ($this->canMockMethod($method)) { 756 $mockedMethods .= $this->generateMockedMethodDefinitionFromExisting( 757 $templateDir, 758 $method, 759 $cloneArguments, 760 $callOriginalMethods 761 ); 762 } 763 } catch (ReflectionException $e) { 764 $mockedMethods .= $this->generateMockedMethodDefinition( 765 $templateDir, 766 $mockClassName['fullClassName'], 767 $methodName, 768 $cloneArguments 769 ); 770 } 771 } 772 } else { 773 foreach ($methods as $methodName) { 774 $mockedMethods .= $this->generateMockedMethodDefinition( 775 $templateDir, 776 $mockClassName['fullClassName'], 777 $methodName, 778 $cloneArguments 779 ); 780 } 781 } 782 783 $method = ''; 784 785 if (!in_array('method', $methods)) { 786 $methodTemplate = new Text_Template( 787 $templateDir . 'mocked_class_method.tpl' 788 ); 789 790 $method = $methodTemplate->render(); 791 } 792 793 $classTemplate->setVar( 794 array( 795 'prologue' => isset($prologue) ? $prologue : '', 796 'epilogue' => isset($epilogue) ? $epilogue : '', 797 'class_declaration' => $this->generateMockClassDeclaration( 798 $mockClassName, 799 $isInterface, 800 $additionalInterfaces 801 ), 802 'clone' => $cloneTemplate, 803 'mock_class_name' => $mockClassName['className'], 804 'mocked_methods' => $mockedMethods, 805 'method' => $method 806 ) 807 ); 808 809 return array( 810 'code' => $classTemplate->render(), 811 'mockClassName' => $mockClassName['className'] 812 ); 813 } 814 815 /** 816 * @param array|string $type 817 * @param string $className 818 * @param string $prefix 819 * @return array 820 */ 821 protected function generateClassName($type, $className, $prefix) 822 { 823 if (is_array($type)) { 824 $type = implode('_', $type); 825 } 826 827 if ($type[0] == '\\') { 828 $type = substr($type, 1); 829 } 830 831 $classNameParts = explode('\\', $type); 832 833 if (count($classNameParts) > 1) { 834 $type = array_pop($classNameParts); 835 $namespaceName = implode('\\', $classNameParts); 836 $fullClassName = $namespaceName . '\\' . $type; 837 } else { 838 $namespaceName = ''; 839 $fullClassName = $type; 840 } 841 842 if ($className == '') { 843 do { 844 $className = $prefix . $type . '_' . 845 substr(md5(microtime()), 0, 8); 846 } while (class_exists($className, false)); 847 } 848 849 return array( 850 'className' => $className, 851 'originalClassName' => $type, 852 'fullClassName' => $fullClassName, 853 'namespaceName' => $namespaceName 854 ); 855 } 856 857 /** 858 * @param array $mockClassName 859 * @param bool $isInterface 860 * @param array $additionalInterfaces 861 * @return array 862 */ 863 protected function generateMockClassDeclaration(array $mockClassName, $isInterface, array $additionalInterfaces = array()) 864 { 865 $buffer = 'class '; 866 867 $additionalInterfaces[] = 'PHPUnit_Framework_MockObject_MockObject'; 868 $interfaces = implode(', ', $additionalInterfaces); 869 870 if ($isInterface) { 871 $buffer .= sprintf( 872 '%s implements %s', 873 $mockClassName['className'], 874 $interfaces 875 ); 876 877 if (!in_array($mockClassName['originalClassName'], $additionalInterfaces)) { 878 $buffer .= ', '; 879 880 if (!empty($mockClassName['namespaceName'])) { 881 $buffer .= $mockClassName['namespaceName'] . '\\'; 882 } 883 884 $buffer .= $mockClassName['originalClassName']; 885 } 886 } else { 887 $buffer .= sprintf( 888 '%s extends %s%s implements %s', 889 $mockClassName['className'], 890 !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '', 891 $mockClassName['originalClassName'], 892 $interfaces 893 ); 894 } 895 896 return $buffer; 897 } 898 899 /** 900 * @param string $templateDir 901 * @param ReflectionMethod $method 902 * @param bool $cloneArguments 903 * @param bool $callOriginalMethods 904 * @return string 905 */ 906 protected function generateMockedMethodDefinitionFromExisting($templateDir, ReflectionMethod $method, $cloneArguments, $callOriginalMethods) 907 { 908 if ($method->isPrivate()) { 909 $modifier = 'private'; 910 } elseif ($method->isProtected()) { 911 $modifier = 'protected'; 912 } else { 913 $modifier = 'public'; 914 } 915 916 if ($method->isStatic()) { 917 $modifier .= ' static'; 918 } 919 920 if ($method->returnsReference()) { 921 $reference = '&'; 922 } else { 923 $reference = ''; 924 } 925 926 return $this->generateMockedMethodDefinition( 927 $templateDir, 928 $method->getDeclaringClass()->getName(), 929 $method->getName(), 930 $cloneArguments, 931 $modifier, 932 $this->getMethodParameters($method), 933 $this->getMethodParameters($method, true), 934 $reference, 935 $callOriginalMethods, 936 $method->isStatic() 937 ); 938 } 939 940 /** 941 * @param string $templateDir 942 * @param string $className 943 * @param string $methodName 944 * @param bool $cloneArguments 945 * @param string $modifier 946 * @param string $arguments_decl 947 * @param string $arguments_call 948 * @param string $reference 949 * @param bool $callOriginalMethods 950 * @param bool $static 951 * @return string 952 */ 953 protected function generateMockedMethodDefinition($templateDir, $className, $methodName, $cloneArguments = true, $modifier = 'public', $arguments_decl = '', $arguments_call = '', $reference = '', $callOriginalMethods = false, $static = false) 954 { 955 if ($static) { 956 $templateFile = 'mocked_static_method.tpl'; 957 } else { 958 $templateFile = sprintf( 959 '%s_method.tpl', 960 $callOriginalMethods ? 'proxied' : 'mocked' 961 ); 962 } 963 964 $template = new Text_Template($templateDir . $templateFile); 965 966 $template->setVar( 967 array( 968 'arguments_decl' => $arguments_decl, 969 'arguments_call' => $arguments_call, 970 'arguments_count' => !empty($arguments_call) ? count(explode(',', $arguments_call)) : 0, 971 'class_name' => $className, 972 'method_name' => $methodName, 973 'modifier' => $modifier, 974 'reference' => $reference, 975 'clone_arguments' => $cloneArguments ? 'TRUE' : 'FALSE' 976 ) 977 ); 978 979 return $template->render(); 980 } 981 982 /** 983 * @param ReflectionMethod $method 984 * @return bool 985 */ 986 protected function canMockMethod(ReflectionMethod $method) 987 { 988 if ($method->isConstructor() || 989 $method->isFinal() || 990 $method->isPrivate() || 991 isset($this->blacklistedMethodNames[$method->getName()])) { 992 return false; 993 } 994 995 return true; 996 } 997 998 /** 999 * Returns the parameters of a function or method. 1000 * 1001 * @param ReflectionMethod $method 1002 * @param bool $forCall 1003 * @return string 1004 * @throws PHPUnit_Framework_MockObject_RuntimeException 1005 * @since Method available since Release 2.0.0 1006 */ 1007 protected function getMethodParameters(ReflectionMethod $method, $forCall = false) 1008 { 1009 $parameters = array(); 1010 1011 foreach ($method->getParameters() as $i => $parameter) { 1012 $name = '$' . $parameter->getName(); 1013 1014 /* Note: PHP extensions may use empty names for reference arguments 1015 * or "..." for methods taking a variable number of arguments. 1016 */ 1017 if ($name === '$' || $name === '$...') { 1018 $name = '$arg' . $i; 1019 } 1020 1021 if ($this->isVariadic($parameter)) { 1022 if ($forCall) { 1023 continue; 1024 } else { 1025 $name = '...' . $name; 1026 } 1027 } 1028 1029 $default = ''; 1030 $reference = ''; 1031 $typeDeclaration = ''; 1032 1033 if (!$forCall) { 1034 if ($this->hasType($parameter)) { 1035 $typeDeclaration = (string) $parameter->getType() . ' '; 1036 } elseif ($parameter->isArray()) { 1037 $typeDeclaration = 'array '; 1038 } elseif ((defined('HHVM_VERSION') || version_compare(PHP_VERSION, '5.4.0', '>=')) 1039 && $parameter->isCallable()) { 1040 $typeDeclaration = 'callable '; 1041 } else { 1042 try { 1043 $class = $parameter->getClass(); 1044 } catch (ReflectionException $e) { 1045 throw new PHPUnit_Framework_MockObject_RuntimeException( 1046 sprintf( 1047 'Cannot mock %s::%s() because a class or ' . 1048 'interface used in the signature is not loaded', 1049 $method->getDeclaringClass()->getName(), 1050 $method->getName() 1051 ), 1052 0, 1053 $e 1054 ); 1055 } 1056 1057 if ($class !== null) { 1058 $typeDeclaration = $class->getName() . ' '; 1059 } 1060 } 1061 1062 if (!$this->isVariadic($parameter)) { 1063 if ($parameter->isDefaultValueAvailable()) { 1064 $value = $parameter->getDefaultValue(); 1065 $default = ' = ' . var_export($value, true); 1066 } elseif ($parameter->isOptional()) { 1067 $default = ' = null'; 1068 } 1069 } 1070 } 1071 1072 if ($parameter->isPassedByReference()) { 1073 $reference = '&'; 1074 } 1075 1076 $parameters[] = $typeDeclaration . $reference . $name . $default; 1077 } 1078 1079 return implode(', ', $parameters); 1080 } 1081 1082 /** 1083 * @param ReflectionParameter $parameter 1084 * @return bool 1085 * @since Method available since Release 2.2.1 1086 */ 1087 private function isVariadic(ReflectionParameter $parameter) 1088 { 1089 return method_exists('ReflectionParameter', 'isVariadic') && $parameter->isVariadic(); 1090 } 1091 1092 /** 1093 * @param ReflectionParameter $parameter 1094 * @return bool 1095 * @since Method available since Release 2.3.4 1096 */ 1097 private function hasType(ReflectionParameter $parameter) 1098 { 1099 return method_exists('ReflectionParameter', 'hasType') && $parameter->hasType(); 1100 } 1101 1102 /** 1103 * @param string $className 1104 * @return array 1105 * @since Method available since Release 2.3.2 1106 */ 1107 private function getClassMethods($className) 1108 { 1109 $class = new ReflectionClass($className); 1110 $methods = array(); 1111 1112 foreach ($class->getMethods() as $method) { 1113 if (($method->isPublic() || $method->isAbstract()) && !in_array($method->getName(), $methods)) { 1114 $methods[] = $method->getName(); 1115 } 1116 } 1117 1118 return $methods; 1119 } 1120} 1121