Completed
Pull Request — master (#684)
by Dave
02:32
created

MockConfiguration::getTargetInterfaces()   C

Complexity

Conditions 13
Paths 11

Size

Total Lines 48
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 13.0042

Importance

Changes 0
Metric Value
dl 0
loc 48
ccs 33
cts 34
cp 0.9706
rs 5.0877
c 0
b 0
f 0
cc 13
eloc 29
nc 11
nop 0
crap 13.0042

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 432
    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 432
        $this->addTargets($targets);
98 432
        $this->blackListedMethods = $blackListedMethods;
99 432
        $this->whiteListedMethods = $whiteListedMethods;
100 432
        $this->name = $name;
101 432
        $this->instanceMock = $instanceMock;
102 432
        $this->parameterOverrides = $parameterOverrides;
103 432
        $this->mockOriginalDestructor = $mockOriginalDestructor;
104 432
    }
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 412
    public function getHash()
114
    {
115
        $vars = array(
116 412
            'targetClassName'        => $this->targetClassName,
117 412
            'targetInterfaceNames'   => $this->targetInterfaceNames,
118 412
            'name'                   => $this->name,
119 412
            'blackListedMethods'     => $this->blackListedMethods,
120 412
            'whiteListedMethod'      => $this->whiteListedMethods,
121 412
            'instanceMock'           => $this->instanceMock,
122 412
            'parameterOverrides'     => $this->parameterOverrides,
123 412
            'mockOriginalDestructor' => $this->mockOriginalDestructor
124 412
        );
125
126 412
        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 399
    public function getMethodsToMock()
135
    {
136 399
        $methods = $this->getAllMethods();
137
138 399
        foreach ($methods as $key => $method) {
139 127
            if ($method->isFinal()) {
140 12
                unset($methods[$key]);
141 12
            }
142 399
        }
143
144
        /**
145
         * Whitelist trumps everything else
146
         */
147 399
        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 381
        if (count($this->getBlackListedMethods())) {
160 379
            $blacklist = array_map('strtolower', $this->getBlackListedMethods());
161
            $methods = array_filter($methods, function ($method) use ($blacklist) {
162 106
                return !in_array(strtolower($method->getName()), $blacklist);
163 379
            });
164 379
        }
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 381
        if ($this->getTargetClass()
173 381
            && $this->getTargetClass()->implementsInterface("Serializable")
174 381
            && $this->getTargetClass()->hasInternalAncestor()) {
175
            $methods = array_filter($methods, function ($method) {
176 1
                return $method->getName() !== "unserialize";
177 1
            });
178 1
        }
179
180 381
        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 392
    public function requiresCallTypeHintRemoval()
188
    {
189 392
        foreach ($this->getAllMethods() as $method) {
190 119
            if ("__call" === $method->getName()) {
191 6
                $params = $method->getParameters();
192 6
                return !$params[1]->isArray();
193
            }
194 386
        }
195
196 386
        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 391
    public function requiresCallStaticTypeHintRemoval()
204
    {
205 391
        foreach ($this->getAllMethods() as $method) {
206 119
            if ("__callStatic" === $method->getName()) {
207 1
                $params = $method->getParameters();
208 1
                return !$params[1]->isArray();
209
            }
210 390
        }
211
212 390
        return false;
213
    }
214
215 391
    public function rename($className)
216
    {
217 391
        $targets = array();
218
219 391
        if ($this->targetClassName) {
220 370
            $targets[] = $this->targetClassName;
221 370
        }
222
223 391
        if ($this->targetInterfaceNames) {
224 19
            $targets = array_merge($targets, $this->targetInterfaceNames);
225 19
        }
226
227 391
        if ($this->targetObject) {
228 14
            $targets[] = $this->targetObject;
229 14
        }
230
231 391
        return new self(
232 391
            $targets,
233 391
            $this->blackListedMethods,
234 391
            $this->whiteListedMethods,
235 391
            $className,
236 391
            $this->instanceMock,
237 391
            $this->parameterOverrides,
238 391
            $this->mockOriginalDestructor
239 391
        );
240
    }
241
242 405
    protected function addTarget($target)
243
    {
244 405
        if (is_object($target)) {
245 14
            $this->setTargetObject($target);
246 14
            $this->setTargetClassName(get_class($target));
247 14
            return $this;
248
        }
249
250 405
        if ($target[0] !== "\\") {
251 402
            $target = "\\" . $target;
252 402
        }
253
254 405
        if (class_exists($target)) {
255 379
            $this->setTargetClassName($target);
256 379
            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 432
    protected function addTargets($interfaces)
279
    {
280 432
        foreach ($interfaces as $interface) {
281 405
            $this->addTarget($interface);
282 432
        }
283 432
    }
284
285 17
    public function getTargetClassName()
286
    {
287 17
        return $this->targetClassName;
288
    }
289
290 402
    public function getTargetClass()
291
    {
292 402
        if ($this->targetClass) {
293 377
            return $this->targetClass;
294
        }
295
296 402
        if (!$this->targetClassName) {
297 28
            return null;
298
        }
299
300 379
        if (class_exists($this->targetClassName)) {
301 366
            $dtc = DefinedTargetClass::factory($this->targetClassName);
302
303 366
            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 2
                );
311
            }
312
313 364
            $this->targetClass = $dtc;
314 364
        } else {
315 17
            $this->targetClass = UndefinedTargetClass::factory($this->targetClassName);
316
        }
317
318 377
        return $this->targetClass;
319
    }
320
321 404
    public function getTargetInterfaces()
322
    {
323 404
        if (!empty($this->targetInterfaces)) {
324 19
            return $this->targetInterfaces;
325
        }
326
327 404
        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 11
                    $traversableFound = true;
352 11
                }
353 24
            }
354
355 24
            if ($traversableFound && !$iteratorShiftedToFront) {
356 5
                $this->targetInterfaces[] = DefinedTargetClass::factory("\\IteratorAggregate");
357 5
            }
358
359
            /**
360
             * We never straight up implement Traversable
361
             */
362 24
            if (!preg_match("/^\\\\?Traversable$/i", $targetInterface)) {
363 23
                $this->targetInterfaces[] = $dtc;
364 23
            }
365 404
        }
366 403
        $this->targetInterfaces = array_unique($this->targetInterfaces); // just in case
367 403
        return $this->targetInterfaces;
368
    }
369
370 419
    public function getTargetObject()
371
    {
372 419
        return $this->targetObject;
373
    }
374
375 418
    public function getName()
376
    {
377 418
        return $this->name;
378
    }
379
380
    /**
381
     * Generate a suitable name based on the config
382
     */
383 371
    public function generateName()
384
    {
385 371
        $name = 'Mockery_' . static::$mockCounter++;
386
387 371
        if ($this->getTargetObject()) {
388 14
            $name .= "_" . str_replace("\\", "_", get_class($this->getTargetObject()));
389 14
        }
390
391 371
        if ($this->getTargetClass()) {
392 353
            $name .= "_" . str_replace("\\", "_", $this->getTargetClass()->getName());
393 353
        }
394
395 370
        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 18
        }
401
402 370
        return $name;
403
    }
404
405 395
    public function getShortName()
406
    {
407 395
        $parts = explode("\\", $this->getName());
408 395
        return array_pop($parts);
409
    }
410
411 395
    public function getNamespaceName()
412
    {
413 395
        $parts = explode("\\", $this->getName());
414 395
        array_pop($parts);
415
416 395
        if (count($parts)) {
417 22
            return implode("\\", $parts);
418
        }
419
420 373
        return "";
421
    }
422
423 381
    public function getBlackListedMethods()
424
    {
425 381
        return $this->blackListedMethods;
426
    }
427
428 399
    public function getWhiteListedMethods()
429
    {
430 399
        return $this->whiteListedMethods;
431
    }
432
433 392
    public function isInstanceMock()
434
    {
435 392
        return $this->instanceMock;
436
    }
437
438 105
    public function getParameterOverrides()
439
    {
440 105
        return $this->parameterOverrides;
441
    }
442
443 370
    public function isMockOriginalDestructor()
444
    {
445 370
        return $this->mockOriginalDestructor;
446
    }
447
448 389
    protected function setTargetClassName($targetClassName)
449
    {
450 389
        $this->targetClassName = $targetClassName;
451 389
    }
452
453 400
    protected function getAllMethods()
454
    {
455 400
        if ($this->allMethods) {
456 119
            return $this->allMethods;
457
        }
458
459 400
        $classes = $this->getTargetInterfaces();
460
461 400
        if ($this->getTargetClass()) {
462 377
            $classes[] = $this->getTargetClass();
463 377
        }
464
465 400
        $methods = array();
466 400
        foreach ($classes as $class) {
467 389
            $methods = array_merge($methods, $class->getMethods());
468 400
        }
469
470 400
        $names = array();
471 400
        $methods = array_filter($methods, function ($method) use (&$names) {
472 127
            if (in_array($method->getName(), $names)) {
473 3
                return false;
474
            }
475
476 127
            $names[] = $method->getName();
477 127
            return true;
478 400
        });
479
480 400
        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