PageRenderTime 47ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/PHPUnit/PHPUnit/Framework/MockObject/Generator.php

https://bitbucket.org/cviolette/sugarcrm
PHP | 687 lines | 470 code | 73 blank | 144 comment | 73 complexity | 04c479291f1d8e745f4aec1da9c152ba MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
  1. <?php
  2. /**
  3. * PHPUnit
  4. *
  5. * Copyright (c) 2010-2011, Sebastian Bergmann <sb@sebastian-bergmann.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 <sb@sebastian-bergmann.de>
  39. * @copyright 2010-2011 Sebastian Bergmann <sb@sebastian-bergmann.de>
  40. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  41. * @link http://github.com/sebastianbergmann/phpunit-mock-objects
  42. * @since File available since Release 1.0.0
  43. */
  44. require_once 'Text/Template.php';
  45. /**
  46. * Mock Object Code Generator
  47. *
  48. * @package PHPUnit_MockObject
  49. * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
  50. * @copyright 2010-2011 Sebastian Bergmann <sb@sebastian-bergmann.de>
  51. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  52. * @version Release: 1.0.9
  53. * @link http://github.com/sebastianbergmann/phpunit-mock-objects
  54. * @since Class available since Release 1.0.0
  55. */
  56. class PHPUnit_Framework_MockObject_Generator
  57. {
  58. /**
  59. * @var array
  60. */
  61. protected static $cache = array();
  62. /**
  63. * @var array
  64. */
  65. protected static $blacklistedMethodNames = array(
  66. '__clone' => TRUE,
  67. 'abstract' => TRUE,
  68. 'and' => TRUE,
  69. 'array' => TRUE,
  70. 'as' => TRUE,
  71. 'break' => TRUE,
  72. 'case' => TRUE,
  73. 'catch' => TRUE,
  74. 'class' => TRUE,
  75. 'clone' => TRUE,
  76. 'const' => TRUE,
  77. 'continue' => TRUE,
  78. 'declare' => TRUE,
  79. 'default' => TRUE,
  80. 'die' => TRUE,
  81. 'do' => TRUE,
  82. 'echo' => TRUE,
  83. 'else' => TRUE,
  84. 'elseif' => TRUE,
  85. 'empty' => TRUE,
  86. 'enddeclare' => TRUE,
  87. 'endfor' => TRUE,
  88. 'endforeach' => TRUE,
  89. 'endif' => TRUE,
  90. 'endswitch' => TRUE,
  91. 'endwhile' => TRUE,
  92. 'eval' => TRUE,
  93. 'exit' => TRUE,
  94. 'extends' => TRUE,
  95. 'final' => TRUE,
  96. 'for' => TRUE,
  97. 'foreach' => TRUE,
  98. 'function' => TRUE,
  99. 'global' => TRUE,
  100. 'goto' => TRUE,
  101. 'if' => TRUE,
  102. 'implements' => TRUE,
  103. 'include' => TRUE,
  104. 'include_once' => TRUE,
  105. 'instanceof' => TRUE,
  106. 'interface' => TRUE,
  107. 'isset' => TRUE,
  108. 'list' => TRUE,
  109. 'namespace' => TRUE,
  110. 'new' => TRUE,
  111. 'or' => TRUE,
  112. 'print' => TRUE,
  113. 'private' => TRUE,
  114. 'protected' => TRUE,
  115. 'public' => TRUE,
  116. 'require' => TRUE,
  117. 'require_once' => TRUE,
  118. 'return' => TRUE,
  119. 'static' => TRUE,
  120. 'switch' => TRUE,
  121. 'throw' => TRUE,
  122. 'try' => TRUE,
  123. 'unset' => TRUE,
  124. 'use' => TRUE,
  125. 'var' => TRUE,
  126. 'while' => TRUE,
  127. 'xor' => TRUE
  128. );
  129. /**
  130. * @var boolean
  131. */
  132. protected static $soapLoaded = NULL;
  133. /**
  134. * Returns a mock object for the specified class.
  135. *
  136. * @param string $originalClassName
  137. * @param array $methods
  138. * @param array $arguments
  139. * @param string $mockClassName
  140. * @param boolean $callOriginalConstructor
  141. * @param boolean $callOriginalClone
  142. * @param boolean $callAutoload
  143. * @return object
  144. * @throws InvalidArgumentException
  145. * @since Method available since Release 1.0.0
  146. */
  147. public static function getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE)
  148. {
  149. if (!is_string($originalClassName)) {
  150. throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
  151. }
  152. if (!is_string($mockClassName)) {
  153. throw PHPUnit_Util_InvalidArgumentHelper::factory(4, 'string');
  154. }
  155. if (!is_array($methods) && !is_null($methods)) {
  156. throw new InvalidArgumentException;
  157. }
  158. if ($mockClassName != '' && class_exists($mockClassName, FALSE)) {
  159. throw new PHPUnit_Framework_Exception(
  160. sprintf(
  161. 'Class "%s" already exists.',
  162. $mockClassName
  163. )
  164. );
  165. }
  166. $mock = self::generate(
  167. $originalClassName,
  168. $methods,
  169. $mockClassName,
  170. $callOriginalClone,
  171. $callAutoload
  172. );
  173. if (!class_exists($mock['mockClassName'], FALSE)) {
  174. eval($mock['code']);
  175. }
  176. if ($callOriginalConstructor &&
  177. !interface_exists($originalClassName, $callAutoload)) {
  178. if (count($arguments) == 0) {
  179. $mockObject = new $mock['mockClassName'];
  180. } else {
  181. $mockClass = new ReflectionClass($mock['mockClassName']);
  182. $mockObject = $mockClass->newInstanceArgs($arguments);
  183. }
  184. } else {
  185. // Use a trick to create a new object of a class
  186. // without invoking its constructor.
  187. $mockObject = unserialize(
  188. sprintf(
  189. 'O:%d:"%s":0:{}',
  190. strlen($mock['mockClassName']), $mock['mockClassName']
  191. )
  192. );
  193. }
  194. return $mockObject;
  195. }
  196. /**
  197. * Returns a mock object for the specified abstract class with all abstract
  198. * methods of the class mocked. Concrete methods are not mocked.
  199. *
  200. * @param string $originalClassName
  201. * @param array $arguments
  202. * @param string $mockClassName
  203. * @param boolean $callOriginalConstructor
  204. * @param boolean $callOriginalClone
  205. * @param boolean $callAutoload
  206. * @return object
  207. * @since Method available since Release 1.0.0
  208. * @throws InvalidArgumentException
  209. */
  210. public static function getMockForAbstractClass($originalClassName, array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE)
  211. {
  212. if (!is_string($originalClassName)) {
  213. throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'string');
  214. }
  215. if (!is_string($mockClassName)) {
  216. throw PHPUnit_Util_InvalidArgumentHelper::factory(3, 'string');
  217. }
  218. if (class_exists($originalClassName, $callAutoload)) {
  219. $methods = array();
  220. $reflector = new ReflectionClass($originalClassName);
  221. foreach ($reflector->getMethods() as $method) {
  222. if ($method->isAbstract()) {
  223. $methods[] = $method->getName();
  224. }
  225. }
  226. if (empty($methods)) {
  227. $methods = NULL;
  228. }
  229. return self::getMock(
  230. $originalClassName,
  231. $methods,
  232. $arguments,
  233. $mockClassName,
  234. $callOriginalConstructor,
  235. $callOriginalClone,
  236. $callAutoload
  237. );
  238. } else {
  239. throw new PHPUnit_Framework_Exception(
  240. sprintf(
  241. 'Class "%s" does not exist.',
  242. $originalClassName
  243. )
  244. );
  245. }
  246. }
  247. /**
  248. * @param string $originalClassName
  249. * @param array $methods
  250. * @param string $mockClassName
  251. * @param boolean $callOriginalClone
  252. * @param boolean $callAutoload
  253. * @return array
  254. */
  255. public static function generate($originalClassName, array $methods = NULL, $mockClassName = '', $callOriginalClone = TRUE, $callAutoload = TRUE)
  256. {
  257. if ($mockClassName == '') {
  258. $key = md5(
  259. $originalClassName .
  260. serialize($methods) .
  261. serialize($callOriginalClone)
  262. );
  263. if (isset(self::$cache[$key])) {
  264. return self::$cache[$key];
  265. }
  266. }
  267. $mock = self::generateMock(
  268. $originalClassName,
  269. $methods,
  270. $mockClassName,
  271. $callOriginalClone,
  272. $callAutoload
  273. );
  274. if (isset($key)) {
  275. self::$cache[$key] = $mock;
  276. }
  277. return $mock;
  278. }
  279. /**
  280. * @param string $wsdlFile
  281. * @param string $originalClassName
  282. * @param array $methods
  283. * @return array
  284. */
  285. public static function generateClassFromWsdl($wsdlFile, $originalClassName, array $methods = array())
  286. {
  287. if (self::$soapLoaded === NULL) {
  288. self::$soapLoaded = extension_loaded('soap');
  289. }
  290. if (self::$soapLoaded) {
  291. $client = new SOAPClient($wsdlFile);
  292. $_methods = array_unique($client->__getFunctions());
  293. unset($client);
  294. $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR .
  295. 'Generator' . DIRECTORY_SEPARATOR;
  296. $methodTemplate = new Text_Template(
  297. $templateDir . 'wsdl_method.tpl'
  298. );
  299. $methodsBuffer = '';
  300. foreach ($_methods as $method) {
  301. $nameStart = strpos($method, ' ') + 1;
  302. $nameEnd = strpos($method, '(');
  303. $name = substr($method, $nameStart, $nameEnd - $nameStart);
  304. if (empty($methods) || in_array($name, $methods)) {
  305. $args = explode(
  306. ',',
  307. substr(
  308. $method,
  309. $nameEnd + 1,
  310. strpos($method, ')') - $nameEnd - 1
  311. )
  312. );
  313. $numArgs = count($args);
  314. for ($i = 0; $i < $numArgs; $i++) {
  315. $args[$i] = substr($args[$i], strpos($args[$i], '$'));
  316. }
  317. $methodTemplate->setVar(
  318. array(
  319. 'method_name' => $name,
  320. 'arguments' => join(', ', $args)
  321. )
  322. );
  323. $methodsBuffer .= $methodTemplate->render();
  324. }
  325. }
  326. $classTemplate = new Text_Template(
  327. $templateDir . 'wsdl_class.tpl'
  328. );
  329. $classTemplate->setVar(
  330. array(
  331. 'class_name' => $originalClassName,
  332. 'wsdl' => $wsdlFile,
  333. 'methods' => $methodsBuffer
  334. )
  335. );
  336. return $classTemplate->render();
  337. } else {
  338. throw new PHPUnit_Framework_Exception(
  339. 'The SOAP extension is required to generate a mock object ' .
  340. 'from WSDL.'
  341. );
  342. }
  343. }
  344. /**
  345. * @param string $originalClassName
  346. * @param array|null $methods
  347. * @param string $mockClassName
  348. * @param boolean $callOriginalClone
  349. * @param boolean $callAutoload
  350. * @return array
  351. */
  352. protected static function generateMock($originalClassName, $methods, $mockClassName, $callOriginalClone, $callAutoload)
  353. {
  354. $templateDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Generator' .
  355. DIRECTORY_SEPARATOR;
  356. $classTemplate = new Text_Template(
  357. $templateDir . 'mocked_class.tpl'
  358. );
  359. $cloneTemplate = '';
  360. $isClass = FALSE;
  361. $isInterface = FALSE;
  362. $mockClassName = self::generateMockClassName(
  363. $originalClassName, $mockClassName
  364. );
  365. if (class_exists($mockClassName['fullClassName'], $callAutoload)) {
  366. $isClass = TRUE;
  367. } else {
  368. if (interface_exists($mockClassName['fullClassName'], $callAutoload)) {
  369. $isInterface = TRUE;
  370. }
  371. }
  372. if (!class_exists($mockClassName['fullClassName'], $callAutoload) &&
  373. !interface_exists($mockClassName['fullClassName'], $callAutoload)) {
  374. $prologue = 'class ' . $mockClassName['className'] . "\n{\n}\n\n";
  375. if (!empty($mockClassName['namespaceName'])) {
  376. $prologue = 'namespace ' . $mockClassName['namespaceName'] .
  377. " {\n\n" . $prologue . "}\n\n" .
  378. "namespace {\n\n";
  379. $epilogue = "\n\n}";
  380. }
  381. $cloneTemplate = new Text_Template(
  382. $templateDir . 'mocked_clone.tpl'
  383. );
  384. } else {
  385. $class = new ReflectionClass($mockClassName['fullClassName']);
  386. if ($class->isFinal()) {
  387. throw new PHPUnit_Framework_Exception(
  388. sprintf(
  389. 'Class "%s" is declared "final" and cannot be mocked.',
  390. $mockClassName['fullClassName']
  391. )
  392. );
  393. }
  394. if ($class->hasMethod('__clone')) {
  395. $cloneMethod = $class->getMethod('__clone');
  396. if (!$cloneMethod->isFinal()) {
  397. if ($callOriginalClone && !$isInterface) {
  398. $cloneTemplate = new Text_Template(
  399. $templateDir . 'unmocked_clone.tpl'
  400. );
  401. } else {
  402. $cloneTemplate = new Text_Template(
  403. $templateDir . 'mocked_clone.tpl'
  404. );
  405. }
  406. }
  407. } else {
  408. $cloneTemplate = new Text_Template(
  409. $templateDir . 'mocked_clone.tpl'
  410. );
  411. }
  412. }
  413. if (is_object($cloneTemplate)) {
  414. $cloneTemplate = $cloneTemplate->render();
  415. }
  416. if (is_array($methods) && empty($methods) &&
  417. ($isClass || $isInterface)) {
  418. $methods = get_class_methods($mockClassName['fullClassName']);
  419. }
  420. if (!is_array($methods)) {
  421. $methods = array();
  422. }
  423. $constructor = NULL;
  424. $mockedMethods = '';
  425. if (isset($class)) {
  426. if ($class->hasMethod('__construct')) {
  427. $constructor = $class->getMethod('__construct');
  428. }
  429. else if ($class->hasMethod($originalClassName)) {
  430. $constructor = $class->getMethod($originalClassName);
  431. }
  432. foreach ($methods as $methodName) {
  433. try {
  434. $method = $class->getMethod($methodName);
  435. if (self::canMockMethod($method)) {
  436. $mockedMethods .= self::generateMockedMethodDefinitionFromExisting(
  437. $templateDir, $method
  438. );
  439. }
  440. }
  441. catch (ReflectionException $e) {
  442. $mockedMethods .= self::generateMockedMethodDefinition(
  443. $templateDir, $mockClassName['fullClassName'], $methodName
  444. );
  445. }
  446. }
  447. } else {
  448. foreach ($methods as $methodName) {
  449. $mockedMethods .= self::generateMockedMethodDefinition(
  450. $templateDir, $mockClassName['fullClassName'], $methodName
  451. );
  452. }
  453. }
  454. $classTemplate->setVar(
  455. array(
  456. 'prologue' => isset($prologue) ? $prologue : '',
  457. 'epilogue' => isset($epilogue) ? $epilogue : '',
  458. 'class_declaration' => self::generateMockClassDeclaration(
  459. $mockClassName, $isInterface
  460. ),
  461. 'clone' => $cloneTemplate,
  462. 'mock_class_name' => $mockClassName['mockClassName'],
  463. 'mocked_methods' => $mockedMethods
  464. )
  465. );
  466. return array(
  467. 'code' => $classTemplate->render(),
  468. 'mockClassName' => $mockClassName['mockClassName']
  469. );
  470. }
  471. /**
  472. * @param string $originalClassName
  473. * @param string $mockClassName
  474. * @return array
  475. */
  476. protected static function generateMockClassName($originalClassName, $mockClassName)
  477. {
  478. if ($originalClassName[0] == '\\') {
  479. $originalClassName = substr($originalClassName, 1);
  480. }
  481. $classNameParts = explode('\\', $originalClassName);
  482. if (count($classNameParts) > 1) {
  483. $originalClassName = array_pop($classNameParts);
  484. $namespaceName = join('\\', $classNameParts);
  485. $fullClassName = $namespaceName . '\\' . $originalClassName;
  486. } else {
  487. $namespaceName = '';
  488. $fullClassName = $originalClassName;
  489. }
  490. if ($mockClassName == '') {
  491. do {
  492. $mockClassName = 'Mock_' . $originalClassName . '_' .
  493. substr(md5(microtime()), 0, 8);
  494. }
  495. while (class_exists($mockClassName, FALSE));
  496. }
  497. return array(
  498. 'mockClassName' => $mockClassName,
  499. 'className' => $originalClassName,
  500. 'fullClassName' => $fullClassName,
  501. 'namespaceName' => $namespaceName
  502. );
  503. }
  504. /**
  505. * @param array $mockClassName
  506. * @param boolean $isInterface
  507. * @return array
  508. */
  509. protected static function generateMockClassDeclaration(array $mockClassName, $isInterface)
  510. {
  511. $buffer = 'class ';
  512. if ($isInterface) {
  513. $buffer .= sprintf(
  514. "%s implements PHPUnit_Framework_MockObject_MockObject, %s%s",
  515. $mockClassName['mockClassName'],
  516. !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '',
  517. $mockClassName['className']
  518. );
  519. } else {
  520. $buffer .= sprintf(
  521. "%s extends %s%s implements PHPUnit_Framework_MockObject_MockObject",
  522. $mockClassName['mockClassName'],
  523. !empty($mockClassName['namespaceName']) ? $mockClassName['namespaceName'] . '\\' : '',
  524. $mockClassName['className']
  525. );
  526. }
  527. return $buffer;
  528. }
  529. /**
  530. * @param string $templateDir
  531. * @param ReflectionMethod $method
  532. * @return string
  533. */
  534. protected static function generateMockedMethodDefinitionFromExisting($templateDir, ReflectionMethod $method)
  535. {
  536. if ($method->isPrivate()) {
  537. $modifier = 'private';
  538. }
  539. else if ($method->isProtected()) {
  540. $modifier = 'protected';
  541. }
  542. else {
  543. $modifier = 'public';
  544. }
  545. if ($method->isStatic()) {
  546. $static = TRUE;
  547. } else {
  548. $static = FALSE;
  549. }
  550. if ($method->returnsReference()) {
  551. $reference = '&';
  552. } else {
  553. $reference = '';
  554. }
  555. return self::generateMockedMethodDefinition(
  556. $templateDir,
  557. $method->getDeclaringClass()->getName(),
  558. $method->getName(),
  559. $modifier,
  560. PHPUnit_Util_Class::getMethodParameters($method),
  561. PHPUnit_Util_Class::getMethodParameters($method, TRUE),
  562. $reference,
  563. $static
  564. );
  565. }
  566. /**
  567. * @param string $templateDir
  568. * @param string $className
  569. * @param string $methodName
  570. * @param string $modifier
  571. * @param string $arguments_decl
  572. * @param string $arguments_call
  573. * @param string $reference
  574. * @param boolean $static
  575. * @return string
  576. */
  577. protected static function generateMockedMethodDefinition($templateDir, $className, $methodName, $modifier = 'public', $arguments_decl = '', $arguments_call = '', $reference = '', $static = FALSE)
  578. {
  579. if ($static) {
  580. $template = new Text_Template(
  581. $templateDir . 'mocked_static_method.tpl'
  582. );
  583. } else {
  584. $template = new Text_Template(
  585. $templateDir . 'mocked_object_method.tpl'
  586. );
  587. }
  588. $template->setVar(
  589. array(
  590. 'arguments_decl' => $arguments_decl,
  591. 'arguments_call' => $arguments_call,
  592. 'arguments_count' => !empty($arguments_call) ? count(explode(',', $arguments_call)) : 0,
  593. 'class_name' => $className,
  594. 'method_name' => $methodName,
  595. 'modifier' => $modifier,
  596. 'reference' => $reference
  597. )
  598. );
  599. return $template->render();
  600. }
  601. /**
  602. * @param ReflectionMethod $method
  603. * @return boolean
  604. */
  605. protected static function canMockMethod(ReflectionMethod $method)
  606. {
  607. if ($method->isConstructor() || $method->isFinal() ||
  608. isset(self::$blacklistedMethodNames[$method->getName()])) {
  609. return FALSE;
  610. }
  611. return TRUE;
  612. }
  613. }