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;
22
 
23
use Mockery\Generator\Generator;
24
use Mockery\Generator\MockConfigurationBuilder;
25
use Mockery\Loader\Loader as LoaderInterface;
26
 
27
class Container
28
{
29
    const BLOCKS = \Mockery::BLOCKS;
30
 
31
    /**
32
     * Store of mock objects
33
     *
34
     * @var array
35
     */
36
    protected $_mocks = array();
37
 
38
    /**
39
     * Order number of allocation
40
     *
41
     * @var int
42
     */
43
    protected $_allocatedOrder = 0;
44
 
45
    /**
46
     * Current ordered number
47
     *
48
     * @var int
49
     */
50
    protected $_currentOrder = 0;
51
 
52
    /**
53
     * Ordered groups
54
     *
55
     * @var array
56
     */
57
    protected $_groups = array();
58
 
59
    /**
60
     * @var Generator
61
     */
62
    protected $_generator;
63
 
64
    /**
65
     * @var LoaderInterface
66
     */
67
    protected $_loader;
68
 
69
    /**
70
     * @var array
71
     */
72
    protected $_namedMocks = array();
73
 
74
    public function __construct(Generator $generator = null, LoaderInterface $loader = null)
75
    {
76
        $this->_generator = $generator ?: \Mockery::getDefaultGenerator();
77
        $this->_loader = $loader ?: \Mockery::getDefaultLoader();
78
    }
79
 
80
    /**
81
     * Generates a new mock object for this container
82
     *
83
     * I apologies in advance for this. A God Method just fits the API which
84
     * doesn't require differentiating between classes, interfaces, abstracts,
85
     * names or partials - just so long as it's something that can be mocked.
86
     * I'll refactor it one day so it's easier to follow.
87
     *
88
     * @param array ...$args
89
     *
90
     * @return Mock
91
     * @throws Exception\RuntimeException
92
     */
93
    public function mock(...$args)
94
    {
95
        $expectationClosure = null;
96
        $quickdefs = array();
97
        $constructorArgs = null;
98
        $blocks = array();
99
        $class = null;
100
 
101
        if (count($args) > 1) {
102
            $finalArg = end($args);
103
            reset($args);
104
            if (is_callable($finalArg) && is_object($finalArg)) {
105
                $expectationClosure = array_pop($args);
106
            }
107
        }
108
 
109
        $builder = new MockConfigurationBuilder();
110
 
111
        foreach ($args as $k => $arg) {
112
            if ($arg instanceof MockConfigurationBuilder) {
113
                $builder = $arg;
114
                unset($args[$k]);
115
            }
116
        }
117
        reset($args);
118
 
119
        $builder->setParameterOverrides(\Mockery::getConfiguration()->getInternalClassMethodParamMaps());
120
        $builder->setConstantsMap(\Mockery::getConfiguration()->getConstantsMap());
121
 
122
        while (count($args) > 0) {
123
            $arg = array_shift($args);
124
            // check for multiple interfaces
125
            if (is_string($arg)) {
126
                foreach (explode('|', $arg) as $type) {
127
                    if ($arg === 'null') {
128
                        // skip PHP 8 'null's
129
                    } elseif (strpos($type, ',') && !strpos($type, ']')) {
130
                        $interfaces = explode(',', str_replace(' ', '', $type));
131
                        $builder->addTargets($interfaces);
132
                    } elseif (substr($type, 0, 6) == 'alias:') {
133
                        $type = str_replace('alias:', '', $type);
134
                        $builder->addTarget('stdClass');
135
                        $builder->setName($type);
136
                    } elseif (substr($type, 0, 9) == 'overload:') {
137
                        $type = str_replace('overload:', '', $type);
138
                        $builder->setInstanceMock(true);
139
                        $builder->addTarget('stdClass');
140
                        $builder->setName($type);
141
                    } elseif (substr($type, strlen($type)-1, 1) == ']') {
142
                        $parts = explode('[', $type);
143
                        if (!class_exists($parts[0], true) && !interface_exists($parts[0], true)) {
144
                            throw new \Mockery\Exception('Can only create a partial mock from'
145
                            . ' an existing class or interface');
146
                        }
147
                        $class = $parts[0];
148
                        $parts[1] = str_replace(' ', '', $parts[1]);
149
                        $partialMethods = array_filter(explode(',', strtolower(rtrim($parts[1], ']'))));
150
                        $builder->addTarget($class);
151
                        foreach ($partialMethods as $partialMethod) {
152
                            if ($partialMethod[0] === '!') {
153
                                $builder->addBlackListedMethod(substr($partialMethod, 1));
154
                                continue;
155
                            }
156
                            $builder->addWhiteListedMethod($partialMethod);
157
                        }
158
                    } elseif (class_exists($type, true) || interface_exists($type, true) || trait_exists($type, true)) {
159
                        $builder->addTarget($type);
160
                    } elseif (!\Mockery::getConfiguration()->mockingNonExistentMethodsAllowed() && (!class_exists($type, true) && !interface_exists($type, true))) {
161
                        throw new \Mockery\Exception("Mockery can't find '$type' so can't mock it");
162
                    } else {
163
                        if (!$this->isValidClassName($type)) {
164
                            throw new \Mockery\Exception('Class name contains invalid characters');
165
                        }
166
                        $builder->addTarget($type);
167
                    }
168
                    break; // unions are "sum" types and not "intersections", and so we must only process the first part
169
                }
170
            } elseif (is_object($arg)) {
171
                $builder->addTarget($arg);
172
            } elseif (is_array($arg)) {
173
                if (!empty($arg) && array_keys($arg) !== range(0, count($arg) - 1)) {
174
                    // if associative array
175
                    if (array_key_exists(self::BLOCKS, $arg)) {
176
                        $blocks = $arg[self::BLOCKS];
177
                    }
178
                    unset($arg[self::BLOCKS]);
179
                    $quickdefs = $arg;
180
                } else {
181
                    $constructorArgs = $arg;
182
                }
183
            } else {
184
                throw new \Mockery\Exception(
185
                    'Unable to parse arguments sent to '
186
                    . get_class($this) . '::mock()'
187
                );
188
            }
189
        }
190
 
191
        $builder->addBlackListedMethods($blocks);
192
 
193
        if (!is_null($constructorArgs)) {
194
            $builder->addBlackListedMethod("__construct"); // we need to pass through
195
        } else {
196
            $builder->setMockOriginalDestructor(true);
197
        }
198
 
199
        if (!empty($partialMethods) && $constructorArgs === null) {
200
            $constructorArgs = array();
201
        }
202
 
203
        $config = $builder->getMockConfiguration();
204
 
205
        $this->checkForNamedMockClashes($config);
206
 
207
        $def = $this->getGenerator()->generate($config);
208
 
209
        if (class_exists($def->getClassName(), $attemptAutoload = false)) {
210
            $rfc = new \ReflectionClass($def->getClassName());
211
            if (!$rfc->implementsInterface("Mockery\LegacyMockInterface")) {
212
                throw new \Mockery\Exception\RuntimeException("Could not load mock {$def->getClassName()}, class already exists");
213
            }
214
        }
215
 
216
        $this->getLoader()->load($def);
217
 
218
        $mock = $this->_getInstance($def->getClassName(), $constructorArgs);
219
        $mock->mockery_init($this, $config->getTargetObject(), $config->isInstanceMock());
220
 
221
        if (!empty($quickdefs)) {
222
            if (\Mockery::getConfiguration()->getQuickDefinitions()->shouldBeCalledAtLeastOnce()) {
223
                $mock->shouldReceive($quickdefs)->atLeast()->once();
224
            } else {
225
                $mock->shouldReceive($quickdefs)->byDefault();
226
            }
227
        }
228
        if (!empty($expectationClosure)) {
229
            $expectationClosure($mock);
230
        }
231
        $this->rememberMock($mock);
232
        return $mock;
233
    }
234
 
235
    public function instanceMock()
236
    {
237
    }
238
 
239
    public function getLoader()
240
    {
241
        return $this->_loader;
242
    }
243
 
244
    public function getGenerator()
245
    {
246
        return $this->_generator;
247
    }
248
 
249
    /**
250
     * @param string $method
251
     * @param string $parent
252
     * @return string|null
253
     */
254
    public function getKeyOfDemeterMockFor($method, $parent)
255
    {
256
        $keys = array_keys($this->_mocks);
257
        $match = preg_grep("/__demeter_" . md5($parent) . "_{$method}$/", $keys);
258
        if (count($match) == 1) {
259
            $res = array_values($match);
260
            if (count($res) > 0) {
261
                return $res[0];
262
            }
263
        }
264
        return null;
265
    }
266
 
267
    /**
268
     * @return array
269
     */
270
    public function getMocks()
271
    {
272
        return $this->_mocks;
273
    }
274
 
275
    /**
276
     *  Tear down tasks for this container
277
     *
278
     * @throws \Exception
279
     * @return void
280
     */
281
    public function mockery_teardown()
282
    {
283
        try {
284
            $this->mockery_verify();
285
        } catch (\Exception $e) {
286
            $this->mockery_close();
287
            throw $e;
288
        }
289
    }
290
 
291
    /**
292
     * Verify the container mocks
293
     *
294
     * @return void
295
     */
296
    public function mockery_verify()
297
    {
298
        foreach ($this->_mocks as $mock) {
299
            $mock->mockery_verify();
300
        }
301
    }
302
 
303
    /**
304
     * Retrieves all exceptions thrown by mocks
305
     *
306
     * @return array
307
     */
308
    public function mockery_thrownExceptions()
309
    {
310
        $e = [];
311
 
312
        foreach ($this->_mocks as $mock) {
313
            $e = array_merge($e, $mock->mockery_thrownExceptions());
314
        }
315
 
316
        return $e;
317
    }
318
 
319
    /**
320
     * Reset the container to its original state
321
     *
322
     * @return void
323
     */
324
    public function mockery_close()
325
    {
326
        foreach ($this->_mocks as $mock) {
327
            $mock->mockery_teardown();
328
        }
329
        $this->_mocks = array();
330
    }
331
 
332
    /**
333
     * Fetch the next available allocation order number
334
     *
335
     * @return int
336
     */
337
    public function mockery_allocateOrder()
338
    {
339
        $this->_allocatedOrder += 1;
340
        return $this->_allocatedOrder;
341
    }
342
 
343
    /**
344
     * Set ordering for a group
345
     *
346
     * @param mixed $group
347
     * @param int $order
348
     */
349
    public function mockery_setGroup($group, $order)
350
    {
351
        $this->_groups[$group] = $order;
352
    }
353
 
354
    /**
355
     * Fetch array of ordered groups
356
     *
357
     * @return array
358
     */
359
    public function mockery_getGroups()
360
    {
361
        return $this->_groups;
362
    }
363
 
364
    /**
365
     * Set current ordered number
366
     *
367
     * @param int $order
368
     * @return int The current order number that was set
369
     */
370
    public function mockery_setCurrentOrder($order)
371
    {
372
        $this->_currentOrder = $order;
373
        return $this->_currentOrder;
374
    }
375
 
376
    /**
377
     * Get current ordered number
378
     *
379
     * @return int
380
     */
381
    public function mockery_getCurrentOrder()
382
    {
383
        return $this->_currentOrder;
384
    }
385
 
386
    /**
387
     * Validate the current mock's ordering
388
     *
389
     * @param string $method
390
     * @param int $order
391
     * @throws \Mockery\Exception
392
     * @return void
393
     */
394
    public function mockery_validateOrder($method, $order, \Mockery\LegacyMockInterface $mock)
395
    {
396
        if ($order < $this->_currentOrder) {
397
            $exception = new \Mockery\Exception\InvalidOrderException(
398
                'Method ' . $method . ' called out of order: expected order '
399
                . $order . ', was ' . $this->_currentOrder
400
            );
401
            $exception->setMock($mock)
402
                ->setMethodName($method)
403
                ->setExpectedOrder($order)
404
                ->setActualOrder($this->_currentOrder);
405
            throw $exception;
406
        }
407
        $this->mockery_setCurrentOrder($order);
408
    }
409
 
410
    /**
411
     * Gets the count of expectations on the mocks
412
     *
413
     * @return int
414
     */
415
    public function mockery_getExpectationCount()
416
    {
417
        $count = 0;
418
        foreach ($this->_mocks as $mock) {
419
            $count += $mock->mockery_getExpectationCount();
420
        }
421
        return $count;
422
    }
423
 
424
    /**
425
     * Store a mock and set its container reference
426
     *
427
     * @param \Mockery\Mock $mock
428
     * @return \Mockery\LegacyMockInterface|\Mockery\MockInterface
429
     */
430
    public function rememberMock(\Mockery\LegacyMockInterface $mock)
431
    {
432
        if (!isset($this->_mocks[get_class($mock)])) {
433
            $this->_mocks[get_class($mock)] = $mock;
434
        } else {
435
            /**
436
             * This condition triggers for an instance mock where origin mock
437
             * is already remembered
438
             */
439
            $this->_mocks[] = $mock;
440
        }
441
        return $mock;
442
    }
443
 
444
    /**
445
     * Retrieve the last remembered mock object, which is the same as saying
446
     * retrieve the current mock being programmed where you have yet to call
447
     * mock() to change it - thus why the method name is "self" since it will be
448
     * be used during the programming of the same mock.
449
     *
450
     * @return \Mockery\Mock
451
     */
452
    public function self()
453
    {
454
        $mocks = array_values($this->_mocks);
455
        $index = count($mocks) - 1;
456
        return $mocks[$index];
457
    }
458
 
459
    /**
460
     * Return a specific remembered mock according to the array index it
461
     * was stored to in this container instance
462
     *
463
     * @return \Mockery\Mock
464
     */
465
    public function fetchMock($reference)
466
    {
467
        if (isset($this->_mocks[$reference])) {
468
            return $this->_mocks[$reference];
469
        }
470
    }
471
 
472
    protected function _getInstance($mockName, $constructorArgs = null)
473
    {
474
        if ($constructorArgs !== null) {
475
            $r = new \ReflectionClass($mockName);
476
            return $r->newInstanceArgs($constructorArgs);
477
        }
478
 
479
        try {
480
            $instantiator = new Instantiator();
481
            $instance = $instantiator->instantiate($mockName);
482
        } catch (\Exception $ex) {
483
            $internalMockName = $mockName . '_Internal';
484
 
485
            if (!class_exists($internalMockName)) {
486
                eval("class $internalMockName extends $mockName {" .
487
                        'public function __construct() {}' .
488
                    '}');
489
            }
490
 
491
            $instance = new $internalMockName();
492
        }
493
 
494
        return $instance;
495
    }
496
 
497
    protected function checkForNamedMockClashes($config)
498
    {
499
        $name = $config->getName();
500
 
501
        if (!$name) {
502
            return;
503
        }
504
 
505
        $hash = $config->getHash();
506
 
507
        if (isset($this->_namedMocks[$name])) {
508
            if ($hash !== $this->_namedMocks[$name]) {
509
                throw new \Mockery\Exception(
510
                    "The mock named '$name' has been already defined with a different mock configuration"
511
                );
512
            }
513
        }
514
 
515
        $this->_namedMocks[$name] = $hash;
516
    }
517
 
518
    /**
519
     * see http://php.net/manual/en/language.oop5.basic.php
520
     * @param string $className
521
     * @return bool
522
     */
523
    public function isValidClassName($className)
524
    {
525
        $pos = strpos($className, '\\');
526
        if ($pos === 0) {
527
            $className = substr($className, 1); // remove the first backslash
528
        }
529
        // all the namespaces and class name should match the regex
530
        $invalidNames = array_filter(explode('\\', $className), function ($name) {
531
            return !preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $name);
532
        });
533
        return empty($invalidNames);
534
    }
535
}