PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

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

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