Completed
Push — master ( eeeb37...208730 )
by Dave
8s
created

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