MockConfiguration::getWhiteListedMethods()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
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
     * A number of traits we'd like to mock, keyed by name to attempt to
46
     * keep unique
47
     */
48
    protected $targetTraits = array();
49
    protected $targetTraitNames = array();
50
51
    /**
52
     * An object we'd like our mock to proxy to
53
     */
54
    protected $targetObject;
55
56
    /**
57
     * The class name we'd like to use for a generated mock
58
     */
59
    protected $name;
60
61
    /**
62
     * Methods that should specifically not be mocked
63
     *
64
     * This is currently populated with stuff we don't know how to deal with,
65
     * should really be somewhere else
66
     */
67
    protected $blackListedMethods = array();
68
69
    /**
70
     * If not empty, only these methods will be mocked
71
     */
72
    protected $whiteListedMethods = array();
73
74
    /**
75
     * An instance mock is where we override the original class before it's
76
     * autoloaded
77
     */
78
    protected $instanceMock = false;
79
80
    /**
81
     * Param overrides
82
     */
83
    protected $parameterOverrides = array();
84
85
    /**
86
     * Instance cache of all methods
87
     */
88
    protected $allMethods;
89
90
    /**
91
     * If true, overrides original class destructor
92
     */
93
    protected $mockOriginalDestructor = false;
94
95 444
    public function __construct(
96
        array $targets = array(),
97
        array $blackListedMethods = array(),
98
        array $whiteListedMethods = array(),
99
        $name = null,
100
        $instanceMock = false,
101
        array $parameterOverrides = array(),
102
        $mockOriginalDestructor = false
103
    ) {
104 444
        $this->addTargets($targets);
105 444
        $this->blackListedMethods = $blackListedMethods;
106 444
        $this->whiteListedMethods = $whiteListedMethods;
107 444
        $this->name = $name;
108 444
        $this->instanceMock = $instanceMock;
109 444
        $this->parameterOverrides = $parameterOverrides;
110 444
        $this->mockOriginalDestructor = $mockOriginalDestructor;
111 444
    }
112
113
    /**
114
     * Attempt to create a hash of the configuration, in order to allow caching
115
     *
116
     * @TODO workout if this will work
117
     *
118
     * @return string
119
     */
120 424
    public function getHash()
121
    {
122
        $vars = array(
123 424
            'targetClassName'        => $this->targetClassName,
124 424
            'targetInterfaceNames'   => $this->targetInterfaceNames,
125 424
            'targetTraitNames'       => $this->targetTraitNames,
126 424
            'name'                   => $this->name,
127 424
            'blackListedMethods'     => $this->blackListedMethods,
128 424
            'whiteListedMethod'      => $this->whiteListedMethods,
129 424
            'instanceMock'           => $this->instanceMock,
130 424
            'parameterOverrides'     => $this->parameterOverrides,
131 424
            'mockOriginalDestructor' => $this->mockOriginalDestructor
132 424
        );
133
134 424
        return md5(serialize($vars));
135
    }
136
137
    /**
138
     * Gets a list of methods from the classes, interfaces and objects and
139
     * filters them appropriately. Lot's of filtering going on, perhaps we could
140
     * have filter classes to iterate through
141
     */
142 405
    public function getMethodsToMock()
143
    {
144 405
        $methods = $this->getAllMethods();
145
146 405
        foreach ($methods as $key => $method) {
147 131
            if ($method->isFinal()) {
148 12
                unset($methods[$key]);
149 12
            }
150 405
        }
151
152
        /**
153
         * Whitelist trumps everything else
154
         */
155 405
        if (count($this->getWhiteListedMethods())) {
156 19
            $whitelist = array_map('strtolower', $this->getWhiteListedMethods());
157
            $methods = array_filter($methods, function ($method) use ($whitelist) {
158 19
                return $method->isAbstract() || in_array(strtolower($method->getName()), $whitelist);
159 19
            });
160
161 19
            return $methods;
162
        }
163
164
        /**
165
         * Remove blacklisted methods
166
         */
167 387
        if (count($this->getBlackListedMethods())) {
168 385
            $blacklist = array_map('strtolower', $this->getBlackListedMethods());
169
            $methods = array_filter($methods, function ($method) use ($blacklist) {
170 110
                return !in_array(strtolower($method->getName()), $blacklist);
171 385
            });
172 385
        }
173
174
        /**
175
         * Internal objects can not be instantiated with newInstanceArgs and if
176
         * they implement Serializable, unserialize will have to be called. As
177
         * such, we can't mock it and will need a pass to add a dummy
178
         * implementation
179
         */
180 387
        if ($this->getTargetClass()
181 387
            && $this->getTargetClass()->implementsInterface("Serializable")
182 387
            && $this->getTargetClass()->hasInternalAncestor()) {
183
            $methods = array_filter($methods, function ($method) {
184 1
                return $method->getName() !== "unserialize";
185 1
            });
186 1
        }
187
188 387
        return array_values($methods);
189
    }
190
191
    /**
192
     * We declare the __call method to handle undefined stuff, if the class
193
     * we're mocking has also defined it, we need to comply with their interface
194
     */
195 398
    public function requiresCallTypeHintRemoval()
196
    {
197 398
        foreach ($this->getAllMethods() as $method) {
198 123
            if ("__call" === $method->getName()) {
199 6
                $params = $method->getParameters();
200 6
                return !$params[1]->isArray();
201
            }
202 392
        }
203
204 392
        return false;
205
    }
206
207
    /**
208
     * We declare the __callStatic method to handle undefined stuff, if the class
209
     * we're mocking has also defined it, we need to comply with their interface
210
     */
211 397
    public function requiresCallStaticTypeHintRemoval()
212
    {
213 397
        foreach ($this->getAllMethods() as $method) {
214 123
            if ("__callStatic" === $method->getName()) {
215 1
                $params = $method->getParameters();
216 1
                return !$params[1]->isArray();
217
            }
218 396
        }
219
220 396
        return false;
221
    }
222
223 397
    public function rename($className)
224
    {
225 397
        $targets = array();
226
227 397
        if ($this->targetClassName) {
228 373
            $targets[] = $this->targetClassName;
229 373
        }
230
231 397
        if ($this->targetInterfaceNames) {
232 19
            $targets = array_merge($targets, $this->targetInterfaceNames);
233 19
        }
234
235 397
        if ($this->targetTraitNames) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->targetTraitNames of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
236 3
            $targets = array_merge($targets, $this->targetTraitNames);
237 3
        }
238
239 397
        if ($this->targetObject) {
240 14
            $targets[] = $this->targetObject;
241 14
        }
242
243 397
        return new self(
244 397
            $targets,
245 397
            $this->blackListedMethods,
246 397
            $this->whiteListedMethods,
247 397
            $className,
248 397
            $this->instanceMock,
249 397
            $this->parameterOverrides,
250 397
            $this->mockOriginalDestructor
251 397
        );
252
    }
253
254 411
    protected function addTarget($target)
255
    {
256 411
        if (is_object($target)) {
257 14
            $this->setTargetObject($target);
258 14
            $this->setTargetClassName(get_class($target));
259 14
            return $this;
260
        }
261
262 411
        if ($target[0] !== "\\") {
263 408
            $target = "\\" . $target;
264 408
        }
265
266 411
        if (class_exists($target)) {
267 382
            $this->setTargetClassName($target);
268 382
            return $this;
269
        }
270
271 43
        if (interface_exists($target)) {
272 24
            $this->addTargetInterfaceName($target);
273 24
            return $this;
274
        }
275
276 20
        if (trait_exists($target)) {
277 3
            $this->addTargetTraitName($target);
278 3
            return $this;
279
        }
280
281
        /**
282
         * Default is to set as class, or interface if class already set
283
         *
284
         * Don't like this condition, can't remember what the default
285
         * targetClass is for
286
         */
287 17
        if ($this->getTargetClassName()) {
288 1
            $this->addTargetInterfaceName($target);
289 1
            return $this;
290
        }
291
292 17
        $this->setTargetClassName($target);
293 17
    }
294
295 444
    protected function addTargets($interfaces)
296
    {
297 444
        foreach ($interfaces as $interface) {
298 411
            $this->addTarget($interface);
299 444
        }
300 444
    }
301
302 17
    public function getTargetClassName()
303
    {
304 17
        return $this->targetClassName;
305
    }
306
307 408
    public function getTargetClass()
308
    {
309 408
        if ($this->targetClass) {
310 380
            return $this->targetClass;
311
        }
312
313 408
        if (!$this->targetClassName) {
314 31
            return null;
315
        }
316
317 382
        if (class_exists($this->targetClassName)) {
318 369
            $dtc = DefinedTargetClass::factory($this->targetClassName);
319
320 369
            if ($this->getTargetObject() == false && $dtc->isFinal()) {
321 2
                throw new \Mockery\Exception(
322 2
                    'The class ' . $this->targetClassName . ' is marked final and its methods'
323 2
                    . ' cannot be replaced. Classes marked final can be passed in'
324 2
                    . ' to \Mockery::mock() as instantiated objects to create a'
325 2
                    . ' partial mock, but only if the mock is not subject to type'
326 2
                    . ' hinting checks.'
327 2
                );
328
            }
329
330 367
            $this->targetClass = $dtc;
331 367
        } else {
332 17
            $this->targetClass = UndefinedTargetClass::factory($this->targetClassName);
333
        }
334
335 380
        return $this->targetClass;
336
    }
337
338 406
    public function getTargetTraits()
339
    {
340 406
        if (!empty($this->targetTraits)) {
341 3
            return $this->targetTraits;
342
        }
343
344 406
        foreach ($this->targetTraitNames as $targetTrait) {
345 3
            $this->targetTraits[] = DefinedTargetClass::factory($targetTrait);
346 406
        }
347
348 406
        $this->targetTraits = array_unique($this->targetTraits); // just in case
349 406
        return $this->targetTraits;
350
    }
351
352 410
    public function getTargetInterfaces()
353
    {
354 410
        if (!empty($this->targetInterfaces)) {
355 19
            return $this->targetInterfaces;
356
        }
357
358 410
        foreach ($this->targetInterfaceNames as $targetInterface) {
359 24
            if (!interface_exists($targetInterface)) {
360 1
                $this->targetInterfaces[] = UndefinedTargetClass::factory($targetInterface);
361 1
                return;
362
            }
363
364 24
            $dtc = DefinedTargetClass::factory($targetInterface);
365 24
            $extendedInterfaces = array_keys($dtc->getInterfaces());
366 24
            $extendedInterfaces[] = $targetInterface;
367
368 24
            $traversableFound = false;
369 24
            $iteratorShiftedToFront = false;
370 24
            foreach ($extendedInterfaces as $interface) {
371 24
                if (!$traversableFound && preg_match("/^\\?Iterator(|Aggregate)$/i", $interface)) {
372
                    break;
373
                }
374
375 24
                if (preg_match("/^\\\\?IteratorAggregate$/i", $interface)) {
376 3
                    $this->targetInterfaces[] = DefinedTargetClass::factory("\\IteratorAggregate");
377 3
                    $iteratorShiftedToFront = true;
378 24
                } elseif (preg_match("/^\\\\?Iterator$/i", $interface)) {
379 3
                    $this->targetInterfaces[] = DefinedTargetClass::factory("\\Iterator");
380 3
                    $iteratorShiftedToFront = true;
381 24
                } elseif (preg_match("/^\\\\?Traversable$/i", $interface)) {
382 11
                    $traversableFound = true;
383 11
                }
384 24
            }
385
386 24
            if ($traversableFound && !$iteratorShiftedToFront) {
387 5
                $this->targetInterfaces[] = DefinedTargetClass::factory("\\IteratorAggregate");
388 5
            }
389
390
            /**
391
             * We never straight up implement Traversable
392
             */
393 24
            if (!preg_match("/^\\\\?Traversable$/i", $targetInterface)) {
394 23
                $this->targetInterfaces[] = $dtc;
395 23
            }
396 410
        }
397 409
        $this->targetInterfaces = array_unique($this->targetInterfaces); // just in case
398 409
        return $this->targetInterfaces;
399
    }
400
401 431
    public function getTargetObject()
402
    {
403 431
        return $this->targetObject;
404
    }
405
406 430
    public function getName()
407
    {
408 430
        return $this->name;
409
    }
410
411
    /**
412
     * Generate a suitable name based on the config
413
     */
414 377
    public function generateName()
415
    {
416 377
        $name = 'Mockery_' . static::$mockCounter++;
417
418 377
        if ($this->getTargetObject()) {
419 14
            $name .= "_" . str_replace("\\", "_", get_class($this->getTargetObject()));
420 14
        }
421
422 377
        if ($this->getTargetClass()) {
423 356
            $name .= "_" . str_replace("\\", "_", $this->getTargetClass()->getName());
424 356
        }
425
426 376
        if ($this->getTargetInterfaces()) {
427
            $name .= array_reduce($this->getTargetInterfaces(), function ($tmpname, $i) {
428 18
                $tmpname .= '_' . str_replace("\\", "_", $i->getName());
429 18
                return $tmpname;
430 18
            }, '');
431 18
        }
432
433 376
        return $name;
434
    }
435
436 401
    public function getShortName()
437
    {
438 401
        $parts = explode("\\", $this->getName());
439 401
        return array_pop($parts);
440
    }
441
442 401
    public function getNamespaceName()
443
    {
444 401
        $parts = explode("\\", $this->getName());
445 401
        array_pop($parts);
446
447 401
        if (count($parts)) {
448 22
            return implode("\\", $parts);
449
        }
450
451 379
        return "";
452
    }
453
454 387
    public function getBlackListedMethods()
455
    {
456 387
        return $this->blackListedMethods;
457
    }
458
459 405
    public function getWhiteListedMethods()
460
    {
461 405
        return $this->whiteListedMethods;
462
    }
463
464 398
    public function isInstanceMock()
465
    {
466 398
        return $this->instanceMock;
467
    }
468
469 109
    public function getParameterOverrides()
470
    {
471 109
        return $this->parameterOverrides;
472
    }
473
474 373
    public function isMockOriginalDestructor()
475
    {
476 373
        return $this->mockOriginalDestructor;
477
    }
478
479 392
    protected function setTargetClassName($targetClassName)
480
    {
481 392
        $this->targetClassName = $targetClassName;
482 392
    }
483
484 406
    protected function getAllMethods()
485
    {
486 406
        if ($this->allMethods) {
487 123
            return $this->allMethods;
488
        }
489
490 406
        $classes = $this->getTargetInterfaces();
491
492 406
        if ($this->getTargetClass()) {
493 380
            $classes[] = $this->getTargetClass();
494 380
        }
495
496 406
        $methods = array();
497 406
        foreach ($classes as $class) {
498 392
            $methods = array_merge($methods, $class->getMethods());
499 406
        }
500
501 406
        foreach ($this->getTargetTraits() AS $trait) {
502 3
            foreach ($trait->getMethods() as $method) {
503 3
                if ($method->isAbstract()) {
504 2
                    $methods[] = $method;
505 2
                }
506 3
            }
507 406
        }
508
509 406
        $names = array();
510 406
        $methods = array_filter($methods, function ($method) use (&$names) {
511 131
            if (in_array($method->getName(), $names)) {
512 3
                return false;
513
            }
514
515 131
            $names[] = $method->getName();
516 131
            return true;
517 406
        });
518
519 406
        return $this->allMethods = $methods;
520
    }
521
522
    /**
523
     * If we attempt to implement Traversable, we must ensure we are also
524
     * implementing either Iterator or IteratorAggregate, and that whichever one
525
     * it is comes before Traversable in the list of implements.
526
     */
527 24
    protected function addTargetInterfaceName($targetInterface)
528
    {
529 24
        $this->targetInterfaceNames[] = $targetInterface;
530 24
    }
531
532 3
    protected function addTargetTraitName($targetTraitName)
533
    {
534 3
        $this->targetTraitNames[] = $targetTraitName;
535 3
    }
536
537 14
    protected function setTargetObject($object)
538
    {
539 14
        $this->targetObject = $object;
540 14
    }
541
}
542