Completed
Pull Request — master (#585)
by
unknown
02:28
created

Container::mockery_allocateOrder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

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