Mercurial > hg > isophonics-drupal-site
comparison vendor/phpunit/phpunit-mock-objects/src/Framework/MockObject/Generator.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
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 | |
11 use Doctrine\Instantiator\Instantiator; | |
12 use Doctrine\Instantiator\Exception\InvalidArgumentException as InstantiatorInvalidArgumentException; | |
13 use Doctrine\Instantiator\Exception\UnexpectedValueException as InstantiatorUnexpectedValueException; | |
14 | |
15 if (!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 */ | |
27 class 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 } |