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