PageRenderTime 49ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Generator.php

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