Pull merge.
[yaffs-website] / vendor / phpunit / phpunit-mock-objects / src / Framework / MockObject / Generator.php
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 }