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