Completed
Push — master ( 96df1a...267f86 )
by Naveen
09:52
created

generateMock()   F

Complexity

Conditions 34
Paths 2312

Size

Total Lines 190
Code Lines 118

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 190
rs 2
cc 34
eloc 118
nc 2312
nop 7

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * This file is part of the PHPUnit_MockObject package.
4
 *
5
 * (c) Sebastian Bergmann <[email protected]>
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)
0 ignored issues
show
Unused Code introduced by
The parameter $traitname is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $autoload is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
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 View Code Duplication
            if (count($arguments) == 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
            if (!is_object($proxyTarget)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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);
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
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)) {
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
329
                    $methods[] = $method->getName();
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
330
                }
331
            }
332
333
            if (empty($methods)) {
334
                $methods = null;
335
            }
336
337
            return $this->getMock(
338
                $originalClassName,
339
                $methods,
0 ignored issues
show
Bug introduced by
It seems like $methods defined by null on line 334 can also be of type null; however, PHPUnit_Framework_MockObject_Generator::getMock() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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)
0 ignored issues
show
Unused Code introduced by
The parameter $arguments is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $callOriginalConstructor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $callOriginalClone is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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(),
0 ignored issues
show
introduced by
Consider using $method->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
929
            $method->getName(),
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
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()])) {
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
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();
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
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(),
0 ignored issues
show
introduced by
Consider using $method->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
1050
                                $method->getName()
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
1051
                            ),
1052
                            0,
1053
                            $e
1054
                        );
1055
                    }
1056
1057
                    if ($class !== null) {
1058
                        $typeDeclaration = $class->getName() . ' ';
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
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)) {
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
1114
                $methods[] = $method->getName();
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
1115
            }
1116
        }
1117
1118
        return $methods;
1119
    }
1120
}
1121