Completed
Pull Request — master (#682)
by Bilge
02:41
created

MockConfiguration::getTargetInterfaces()   C

Complexity

Conditions 13
Paths 11

Size

Total Lines 48
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 13.0069

Importance

Changes 0
Metric Value
dl 0
loc 48
ccs 28
cts 29
cp 0.9655
rs 5.0877
c 0
b 0
f 0
cc 13
eloc 29
nc 11
nop 0
crap 13.0069

How to fix   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
 * Mockery
4
 *
5
 * LICENSE
6
 *
7
 * This source file is subject to the new BSD license that is bundled
8
 * with this package in the file LICENSE.txt.
9
 * It is also available through the world-wide-web at this URL:
10
 * http://github.com/padraic/mockery/blob/master/LICENSE
11
 * If you did not receive a copy of the license and are unable to
12
 * obtain it through the world-wide-web, please send an email
13
 * to [email protected] so we can send you a copy immediately.
14
 *
15
 * @category   Mockery
16
 * @package    Mockery
17
 * @copyright  Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
18
 * @license    http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
19
 */
20
21
namespace Mockery\Generator;
22
23
/**
24
 * This class describes the configuration of mocks and hides away some of the
25
 * reflection implementation
26
 */
27
class MockConfiguration
28
{
29
    protected static $mockCounter = 0;
30
31
    /**
32
     * A class that we'd like to mock
33
     */
34
    protected $targetClass;
35
    protected $targetClassName;
36
37
    /**
38
     * A number of interfaces we'd like to mock, keyed by name to attempt to
39
     * keep unique
40
     */
41
    protected $targetInterfaces = array();
42
    protected $targetInterfaceNames = array();
43
44
    /**
45
     * An object we'd like our mock to proxy to
46
     */
47
    protected $targetObject;
48
49
    /**
50
     * The class name we'd like to use for a generated mock
51
     */
52
    protected $name;
53
54
    /**
55
     * Methods that should specifically not be mocked
56
     *
57
     * This is currently populated with stuff we don't know how to deal with,
58
     * should really be somewhere else
59
     */
60
    protected $blackListedMethods = array();
61
62
    /**
63
     * If not empty, only these methods will be mocked
64
     */
65
    protected $whiteListedMethods = array();
66
67
    /**
68
     * An instance mock is where we override the original class before it's
69
     * autoloaded
70
     */
71
    protected $instanceMock = false;
72
73
    /**
74
     * Param overrides
75
     */
76
    protected $parameterOverrides = array();
77
78
    /**
79
     * Instance cache of all methods
80
     */
81
    protected $allMethods;
82
83
    /**
84
     * If true, overrides original class destructor
85
     */
86
    protected $mockOriginalDestructor = false;
87
88 472
    public function __construct(
89
        array $targets = array(),
90
        array $blackListedMethods = array(),
91
        array $whiteListedMethods = array(),
92
        $name = null,
93
        $instanceMock = false,
94
        array $parameterOverrides = array(),
95
        $mockOriginalDestructor = false
96
    ) {
97 472
        $this->addTargets($targets);
98 472
        $this->blackListedMethods = $blackListedMethods;
99 472
        $this->whiteListedMethods = $whiteListedMethods;
100 472
        $this->name = $name;
101 472
        $this->instanceMock = $instanceMock;
102 472
        $this->parameterOverrides = $parameterOverrides;
103 472
        $this->mockOriginalDestructor = $mockOriginalDestructor;
104 472
    }
105
106
    /**
107
     * Attempt to create a hash of the configuration, in order to allow caching
108
     *
109
     * @TODO workout if this will work
110
     *
111
     * @return string
112
     */
113 452
    public function getHash()
114
    {
115
        $vars = array(
116 452
            'targetClassName'        => $this->targetClassName,
117 452
            'targetInterfaceNames'   => $this->targetInterfaceNames,
118 452
            'name'                   => $this->name,
119 452
            'blackListedMethods'     => $this->blackListedMethods,
120 452
            'whiteListedMethod'      => $this->whiteListedMethods,
121 452
            'instanceMock'           => $this->instanceMock,
122 452
            'parameterOverrides'     => $this->parameterOverrides,
123 452
            'mockOriginalDestructor' => $this->mockOriginalDestructor
124
        );
125
126 452
        return md5(serialize($vars));
127
    }
128
129
    /**
130
     * Gets a list of methods from the classes, interfaces and objects and
131
     * filters them appropriately. Lot's of filtering going on, perhaps we could
132
     * have filter classes to iterate through
133
     */
134 427
    public function getMethodsToMock()
135
    {
136 427
        $methods = $this->getAllMethods();
137
138 427
        foreach ($methods as $key => $method) {
139 155
            if ($method->isFinal()) {
140 155
                unset($methods[$key]);
141
            }
142
        }
143
144
        /**
145
         * Whitelist trumps everything else
146
         */
147 427
        if (count($this->getWhiteListedMethods())) {
148 19
            $whitelist = array_map('strtolower', $this->getWhiteListedMethods());
149
            $methods = array_filter($methods, function ($method) use ($whitelist) {
150 19
                return $method->isAbstract() || in_array(strtolower($method->getName()), $whitelist);
151 19
            });
152
153 19
            return $methods;
154
        }
155
156
        /**
157
         * Remove blacklisted methods
158
         */
159 409
        if (count($this->getBlackListedMethods())) {
160 407
            $blacklist = array_map('strtolower', $this->getBlackListedMethods());
161
            $methods = array_filter($methods, function ($method) use ($blacklist) {
162 134
                return !in_array(strtolower($method->getName()), $blacklist);
163 407
            });
164
        }
165
166
        /**
167
         * Internal objects can not be instantiated with newInstanceArgs and if
168
         * they implement Serializable, unserialize will have to be called. As
169
         * such, we can't mock it and will need a pass to add a dummy
170
         * implementation
171
         */
172 409
        if ($this->getTargetClass()
173 409
            && $this->getTargetClass()->implementsInterface("Serializable")
174 409
            && $this->getTargetClass()->hasInternalAncestor()) {
175
            $methods = array_filter($methods, function ($method) {
176 1
                return $method->getName() !== "unserialize";
177 1
            });
178
        }
179
180 409
        return array_values($methods);
181
    }
182
183
    /**
184
     * We declare the __call method to handle undefined stuff, if the class
185
     * we're mocking has also defined it, we need to comply with their interface
186
     */
187 420
    public function requiresCallTypeHintRemoval()
188
    {
189 420
        foreach ($this->getAllMethods() as $method) {
190 147
            if ("__call" === $method->getName()) {
191 6
                $params = $method->getParameters();
192 147
                return !$params[1]->isArray();
193
            }
194
        }
195
196 414
        return false;
197
    }
198
199
    /**
200
     * We declare the __callStatic method to handle undefined stuff, if the class
201
     * we're mocking has also defined it, we need to comply with their interface
202
     */
203 419
    public function requiresCallStaticTypeHintRemoval()
204
    {
205 419
        foreach ($this->getAllMethods() as $method) {
206 147
            if ("__callStatic" === $method->getName()) {
207 1
                $params = $method->getParameters();
208 147
                return !$params[1]->isArray();
209
            }
210
        }
211
212 418
        return false;
213
    }
214
215 419
    public function rename($className)
216
    {
217 419
        $targets = array();
218
219 419
        if ($this->targetClassName) {
220 399
            $targets[] = $this->targetClassName;
221
        }
222
223 419
        if ($this->targetInterfaceNames) {
224 19
            $targets = array_merge($targets, $this->targetInterfaceNames);
225
        }
226
227 419
        if ($this->targetObject) {
228 14
            $targets[] = $this->targetObject;
229
        }
230
231 419
        return new self(
232
            $targets,
233 419
            $this->blackListedMethods,
234 419
            $this->whiteListedMethods,
235
            $className,
236 419
            $this->instanceMock,
237 419
            $this->parameterOverrides,
238 419
            $this->mockOriginalDestructor
239
        );
240
    }
241
242 448
    protected function addTarget($target)
243
    {
244 448
        if (is_object($target)) {
245 14
            $this->setTargetObject($target);
246 14
            $this->setTargetClassName(get_class($target));
247 14
            return $this;
248
        }
249
250 448
        if ($target[0] !== "\\") {
251 445
            $target = "\\" . $target;
252
        }
253
254 448
        if (class_exists($target)) {
255 422
            $this->setTargetClassName($target);
256 422
            return $this;
257
        }
258
259 40
        if (interface_exists($target)) {
260 24
            $this->addTargetInterfaceName($target);
261 24
            return $this;
262
        }
263
264
        /**
265
         * Default is to set as class, or interface if class already set
266
         *
267
         * Don't like this condition, can't remember what the default
268
         * targetClass is for
269
         */
270 17
        if ($this->getTargetClassName()) {
271 1
            $this->addTargetInterfaceName($target);
272 1
            return $this;
273
        }
274
275 17
        $this->setTargetClassName($target);
276 17
    }
277
278 472
    protected function addTargets($interfaces)
279
    {
280 472
        foreach ($interfaces as $interface) {
281 448
            $this->addTarget($interface);
282
        }
283 472
    }
284
285 17
    public function getTargetClassName()
286
    {
287 17
        return $this->targetClassName;
288
    }
289
290 430
    public function getTargetClass()
291
    {
292 430
        if ($this->targetClass) {
293 406
            return $this->targetClass;
294
        }
295
296 430
        if (!$this->targetClassName) {
297 27
            return null;
298
        }
299
300 408
        if (class_exists($this->targetClassName)) {
301 395
            $dtc = DefinedTargetClass::factory($this->targetClassName);
302
303 395
            if ($this->getTargetObject() == false && $dtc->isFinal()) {
304 2
                throw new \Mockery\Exception(
305 2
                    'The class ' . $this->targetClassName . ' is marked final and its methods'
306 2
                    . ' cannot be replaced. Classes marked final can be passed in'
307 2
                    . ' to \Mockery::mock() as instantiated objects to create a'
308 2
                    . ' partial mock, but only if the mock is not subject to type'
309 2
                    . ' hinting checks.'
310
                );
311
            }
312
313 393
            $this->targetClass = $dtc;
314
        } else {
315 17
            $this->targetClass = UndefinedTargetClass::factory($this->targetClassName);
316
        }
317
318 406
        return $this->targetClass;
319
    }
320
321 432
    public function getTargetInterfaces()
322
    {
323 432
        if (!empty($this->targetInterfaces)) {
324 19
            return $this->targetInterfaces;
325
        }
326
327 432
        foreach ($this->targetInterfaceNames as $targetInterface) {
328 24
            if (!interface_exists($targetInterface)) {
329 1
                $this->targetInterfaces[] = UndefinedTargetClass::factory($targetInterface);
330 1
                return;
331
            }
332
333 24
            $dtc = DefinedTargetClass::factory($targetInterface);
334 24
            $extendedInterfaces = array_keys($dtc->getInterfaces());
335 24
            $extendedInterfaces[] = $targetInterface;
336
337 24
            $traversableFound = false;
338 24
            $iteratorShiftedToFront = false;
339 24
            foreach ($extendedInterfaces as $interface) {
340 24
                if (!$traversableFound && preg_match("/^\\?Iterator(|Aggregate)$/i", $interface)) {
341
                    break;
342
                }
343
344 24
                if (preg_match("/^\\\\?IteratorAggregate$/i", $interface)) {
345 3
                    $this->targetInterfaces[] = DefinedTargetClass::factory("\\IteratorAggregate");
346 3
                    $iteratorShiftedToFront = true;
347 24
                } elseif (preg_match("/^\\\\?Iterator$/i", $interface)) {
348 3
                    $this->targetInterfaces[] = DefinedTargetClass::factory("\\Iterator");
349 3
                    $iteratorShiftedToFront = true;
350 24
                } elseif (preg_match("/^\\\\?Traversable$/i", $interface)) {
351 24
                    $traversableFound = true;
352
                }
353
            }
354
355 24
            if ($traversableFound && !$iteratorShiftedToFront) {
356 5
                $this->targetInterfaces[] = DefinedTargetClass::factory("\\IteratorAggregate");
357
            }
358
359
            /**
360
             * We never straight up implement Traversable
361
             */
362 24
            if (!preg_match("/^\\\\?Traversable$/i", $targetInterface)) {
363 24
                $this->targetInterfaces[] = $dtc;
364
            }
365
        }
366 431
        $this->targetInterfaces = array_unique($this->targetInterfaces); // just in case
367 431
        return $this->targetInterfaces;
368
    }
369
370 459
    public function getTargetObject()
371
    {
372 459
        return $this->targetObject;
373
    }
374
375 458
    public function getName()
376
    {
377 458
        return $this->name;
378
    }
379
380
    /**
381
     * Generate a suitable name based on the config
382
     */
383 400
    public function generateName()
384
    {
385 400
        $name = 'Mockery_' . static::$mockCounter++;
386
387 400
        if ($this->getTargetObject()) {
388 14
            $name .= "_" . str_replace("\\", "_", get_class($this->getTargetObject()));
389
        }
390
391 400
        if ($this->getTargetClass()) {
392 382
            $name .= "_" . str_replace("\\", "_", $this->getTargetClass()->getName());
393
        }
394
395 399
        if ($this->getTargetInterfaces()) {
396
            $name .= array_reduce($this->getTargetInterfaces(), function ($tmpname, $i) {
397 18
                $tmpname .= '_' . str_replace("\\", "_", $i->getName());
398 18
                return $tmpname;
399 18
            }, '');
400
        }
401
402 399
        return $name;
403
    }
404
405 423
    public function getShortName()
406
    {
407 423
        $parts = explode("\\", $this->getName());
408 423
        return array_pop($parts);
409
    }
410
411 423
    public function getNamespaceName()
412
    {
413 423
        $parts = explode("\\", $this->getName());
414 423
        array_pop($parts);
415
416 423
        if (count($parts)) {
417 22
            return implode("\\", $parts);
418
        }
419
420 401
        return "";
421
    }
422
423 409
    public function getBlackListedMethods()
424
    {
425 409
        return $this->blackListedMethods;
426
    }
427
428 427
    public function getWhiteListedMethods()
429
    {
430 427
        return $this->whiteListedMethods;
431
    }
432
433 420
    public function isInstanceMock()
434
    {
435 420
        return $this->instanceMock;
436
    }
437
438 131
    public function getParameterOverrides()
439
    {
440 131
        return $this->parameterOverrides;
441
    }
442
443 399
    public function isMockOriginalDestructor()
444
    {
445 399
        return $this->mockOriginalDestructor;
446
    }
447
448 432
    protected function setTargetClassName($targetClassName)
449
    {
450 432
        $this->targetClassName = $targetClassName;
451 432
    }
452
453 428
    protected function getAllMethods()
454
    {
455 428
        if ($this->allMethods) {
456 147
            return $this->allMethods;
457
        }
458
459 428
        $classes = $this->getTargetInterfaces();
460
461 428
        if ($this->getTargetClass()) {
462 406
            $classes[] = $this->getTargetClass();
463
        }
464
465 428
        $methods = array();
466 428
        foreach ($classes as $class) {
467 418
            $methods = array_merge($methods, $class->getMethods());
468
        }
469
470 428
        $names = array();
471 428
        $methods = array_filter($methods, function ($method) use (&$names) {
472 155
            if (in_array($method->getName(), $names)) {
473 3
                return false;
474
            }
475
476 155
            $names[] = $method->getName();
477 155
            return true;
478 428
        });
479
480 428
        return $this->allMethods = $methods;
481
    }
482
483
    /**
484
     * If we attempt to implement Traversable, we must ensure we are also
485
     * implementing either Iterator or IteratorAggregate, and that whichever one
486
     * it is comes before Traversable in the list of implements.
487
     */
488 24
    protected function addTargetInterfaceName($targetInterface)
489
    {
490 24
        $this->targetInterfaceNames[] = $targetInterface;
491 24
    }
492
493
494 14
    protected function setTargetObject($object)
495
    {
496 14
        $this->targetObject = $object;
497 14
    }
498
}
499