Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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