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

Mockery::noMoreElementsInChain()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
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
use Mockery\ExpectationInterface;
22
use Mockery\Generator\CachingGenerator;
23
use Mockery\Generator\Generator;
24
use Mockery\Generator\MockConfigurationBuilder;
25
use Mockery\Generator\StringManipulation\Pass\RemoveDestructorPass;
26
use Mockery\Generator\StringManipulationGenerator;
27
use Mockery\Generator\StringManipulation\Pass\CallTypeHintPass;
28
use Mockery\Generator\StringManipulation\Pass\ClassNamePass;
29
use Mockery\Generator\StringManipulation\Pass\ClassPass;
30
use Mockery\Generator\StringManipulation\Pass\InstanceMockPass;
31
use Mockery\Generator\StringManipulation\Pass\InterfacePass;
32
use Mockery\Generator\StringManipulation\Pass\MethodDefinitionPass;
33
use Mockery\Generator\StringManipulation\Pass\RemoveBuiltinMethodsThatAreFinalPass;
34
use Mockery\Generator\StringManipulation\Pass\RemoveUnserializeForInternalSerializableClassesPass;
35
use Mockery\Loader\EvalLoader;
36
use Mockery\Loader\Loader;
37
38
class Mockery
39
{
40
    const BLOCKS = 'Mockery_Forward_Blocks';
41
42
    /**
43
     * Global container to hold all mocks for the current unit test running.
44
     *
45
     * @var \Mockery\Container
46
     */
47
    protected static $_container = null;
48
49
    /**
50
     * Global configuration handler containing configuration options.
51
     *
52
     * @var \Mockery\Configuration
53
     */
54
    protected static $_config = null;
55
56
    /**
57
     * @var \Mockery\Generator\Generator
58
     */
59
    protected static $_generator;
60
61
    /**
62
     * @var \Mockery\Loader\Loader
63
     */
64
    protected static $_loader;
65
66
    /**
67
     * @var array
68
     */
69
    private static $_filesToCleanUp = [];
70
71
    /**
72
     * Static shortcut to \Mockery\Container::mock().
73
     *
74
     * @return \Mockery\MockInterface
75
     */
76 24
    public static function mock()
77
    {
78 24
        $args = func_get_args();
79
80 24
        return call_user_func_array(array(self::getContainer(), 'mock'), $args);
81
    }
82
83
    /**
84
     * @return \Mockery\MockInterface
85
     */
86 7
    public static function spy()
87
    {
88 7
        $args = func_get_args();
89 7
        return call_user_func_array(array(self::getContainer(), 'mock'), $args)->shouldIgnoreMissing();
90
    }
91
92
    /**
93
     * @return \Mockery\MockInterface
94
     */
95
    public static function instanceMock()
96
    {
97
        $args = func_get_args();
98
99
        return call_user_func_array(array(self::getContainer(), 'mock'), $args);
100
    }
101
102
    /**
103
     * Static shortcut to \Mockery\Container::mock(), first argument names the mock.
104
     *
105
     * @return \Mockery\MockInterface
106
     */
107 4
    public static function namedMock()
108
    {
109 4
        $args = func_get_args();
110 4
        $name = array_shift($args);
111
112 4
        $builder = new MockConfigurationBuilder();
113 4
        $builder->setName($name);
114
115 4
        array_unshift($args, $builder);
116
117 4
        return call_user_func_array(array(self::getContainer(), 'mock'), $args);
118
    }
119
120
    /**
121
     * Static shortcut to \Mockery\Container::self().
122
     *
123
     * @throws LogicException
124
     *
125
     * @return \Mockery\MockInterface
126
     */
127
    public static function self()
128
    {
129
        if (is_null(self::$_container)) {
130
            throw new \LogicException('You have not declared any mocks yet');
131
        }
132
133
        return self::$_container->self();
134
    }
135
136
    /**
137
     * Static shortcut to closing up and verifying all mocks in the global
138
     * container, and resetting the container static variable to null.
139
     *
140
     * @return void
141
     */
142 404
    public static function close()
143
    {
144 404
        foreach (self::$_filesToCleanUp as $fileName) {
145 402
            @unlink($fileName);
146 404
        }
147
148 404
        if (is_null(self::$_container)) {
149
            return;
150
        }
151
152 404
        self::$_container->mockery_teardown();
153 404
        self::$_container->mockery_close();
154 404
        self::$_container = null;
155 404
    }
156
157
    /**
158
     * Static fetching of a mock associated with a name or explicit class poser.
159
     *
160
     * @param $name
161
     *
162
     * @return \Mockery\Mock
163
     */
164 11
    public static function fetchMock($name)
165
    {
166 11
        return self::$_container->fetchMock($name);
167
    }
168
169
    /**
170
     * Lazy loader and getter for
171
     * the container property.
172
     *
173
     * @return Mockery\Container
174
     */
175 419
    public static function getContainer()
176
    {
177 419
        if (is_null(self::$_container)) {
178 413
            self::$_container = new Mockery\Container(self::getGenerator(), self::getLoader());
179 413
        }
180
181 419
        return self::$_container;
182
    }
183
184
    /**
185
     * @param \Mockery\Generator\Generator $generator
186
     */
187
    public static function setGenerator(Generator $generator)
188
    {
189
        self::$_generator = $generator;
190
    }
191
192
    /**
193
     * Lazy loader method and getter for
194
     * the generator property.
195
     *
196
     * @return Generator
197
     */
198 413
    public static function getGenerator()
199
    {
200 413
        if (is_null(self::$_generator)) {
201 1
            self::$_generator = self::getDefaultGenerator();
202 1
        }
203
204 413
        return self::$_generator;
205
    }
206
207
    /**
208
     * Creates and returns a default generator
209
     * used inside this class.
210
     *
211
     * @return CachingGenerator
212
     */
213 400
    public static function getDefaultGenerator()
214
    {
215 400
        $generator = new StringManipulationGenerator(array(
216 400
            new CallTypeHintPass(),
217 400
            new ClassPass(),
218 400
            new ClassNamePass(),
219 400
            new InstanceMockPass(),
220 400
            new InterfacePass(),
221 400
            new MethodDefinitionPass(),
222 400
            new RemoveUnserializeForInternalSerializableClassesPass(),
223 400
            new RemoveBuiltinMethodsThatAreFinalPass(),
224 400
            new RemoveDestructorPass(),
225 400
        ));
226
227 400
        return new CachingGenerator($generator);
228
    }
229
230
    /**
231
     * @param Loader $loader
232
     */
233
    public static function setLoader(Loader $loader)
234
    {
235
        self::$_loader = $loader;
236
    }
237
238
    /**
239
     * @return Loader
240
     */
241 413
    public static function getLoader()
242
    {
243 413
        if (is_null(self::$_loader)) {
244 1
            self::$_loader = self::getDefaultLoader();
245 1
        }
246
247 413
        return self::$_loader;
248
    }
249
250
    /**
251
     * @return EvalLoader
252
     */
253 274
    public static function getDefaultLoader()
254
    {
255 274
        return new EvalLoader();
256
    }
257
258
    /**
259
     * Set the container.
260
     *
261
     * @param \Mockery\Container $container
262
     *
263
     * @return \Mockery\Container
264
     */
265 18
    public static function setContainer(Mockery\Container $container)
266
    {
267 18
        return self::$_container = $container;
268
    }
269
270
    /**
271
     * Reset the container to null.
272
     *
273
     * @return void
274
     */
275 15
    public static function resetContainer()
276
    {
277 15
        self::$_container = null;
278 15
    }
279
280
    /**
281
     * Return instance of ANY matcher.
282
     *
283
     * @return \Mockery\Matcher\Any
284
     */
285 5
    public static function any()
286
    {
287 5
        return new \Mockery\Matcher\Any();
288
    }
289
290
    /**
291
     * Return instance of TYPE matcher.
292
     *
293
     * @param $expected
294
     *
295
     * @return \Mockery\Matcher\Type
296
     */
297 48
    public static function type($expected)
298
    {
299 48
        return new \Mockery\Matcher\Type($expected);
300
    }
301
302
    /**
303
     * Return instance of DUCKTYPE matcher.
304
     *
305
     * @return \Mockery\Matcher\Ducktype
306
     */
307 3
    public static function ducktype()
308
    {
309 3
        return new \Mockery\Matcher\Ducktype(func_get_args());
310
    }
311
312
    /**
313
     * Return instance of SUBSET matcher.
314
     *
315
     * @param array $part
316
     *
317
     * @return \Mockery\Matcher\Subset
318
     */
319 3
    public static function subset(array $part)
320
    {
321 3
        return new \Mockery\Matcher\Subset($part);
322
    }
323
324
    /**
325
     * Return instance of CONTAINS matcher.
326
     *
327
     * @return \Mockery\Matcher\Contains
328
     */
329 3
    public static function contains()
330
    {
331 3
        return new \Mockery\Matcher\Contains(func_get_args());
332
    }
333
334
    /**
335
     * Return instance of HASKEY matcher.
336
     *
337
     * @param $key
338
     *
339
     * @return \Mockery\Matcher\HasKey
340
     */
341 3
    public static function hasKey($key)
342
    {
343 3
        return new \Mockery\Matcher\HasKey($key);
344
    }
345
346
    /**
347
     * Return instance of HASVALUE matcher.
348
     *
349
     * @param $val
350
     *
351
     * @return \Mockery\Matcher\HasValue
352
     */
353 3
    public static function hasValue($val)
354
    {
355 3
        return new \Mockery\Matcher\HasValue($val);
356
    }
357
358
    /**
359
     * Return instance of CLOSURE matcher.
360
     *
361
     * @param $closure
362
     *
363
     * @return \Mockery\Matcher\Closure
364
     */
365 7
    public static function on($closure)
366
    {
367 7
        return new \Mockery\Matcher\Closure($closure);
368
    }
369
370
    /**
371
     * Return instance of MUSTBE matcher.
372
     *
373
     * @param $expected
374
     *
375
     * @return \Mockery\Matcher\MustBe
376
     */
377 9
    public static function mustBe($expected)
378
    {
379 9
        return new \Mockery\Matcher\MustBe($expected);
380
    }
381
382
    /**
383
     * Return instance of NOT matcher.
384
     *
385
     * @param $expected
386
     *
387
     * @return \Mockery\Matcher\Not
388
     */
389 3
    public static function not($expected)
390
    {
391 3
        return new \Mockery\Matcher\Not($expected);
392
    }
393
394
    /**
395
     * Return instance of ANYOF matcher.
396
     *
397
     * @return \Mockery\Matcher\AnyOf
398
     */
399 3
    public static function anyOf()
400
    {
401 3
        return new \Mockery\Matcher\AnyOf(func_get_args());
402
    }
403
404
    /**
405
     * Return instance of NOTANYOF matcher.
406
     *
407
     * @return \Mockery\Matcher\NotAnyOf
408
     */
409 3
    public static function notAnyOf()
410
    {
411 3
        return new \Mockery\Matcher\NotAnyOf(func_get_args());
412
    }
413
414
    /**
415
     * Lazy loader and Getter for the global
416
     * configuration container.
417
     *
418
     * @return \Mockery\Configuration
419
     */
420 412
    public static function getConfiguration()
421
    {
422 412
        if (is_null(self::$_config)) {
423 1
            self::$_config = new \Mockery\Configuration();
424 1
        }
425
426 412
        return self::$_config;
427
    }
428
429
    /**
430
     * Utility method to format method name and arguments into a string.
431
     *
432
     * @param string $method
433
     * @param array $arguments
434
     *
435
     * @return string
436
     */
437 94
    public static function formatArgs($method, array $arguments = null)
438
    {
439 94
        if (is_null($arguments)) {
440 2
            return $method . '()';
441
        }
442
443 93
        $formattedArguments = array();
444 93
        foreach ($arguments as $argument) {
445 60
            $formattedArguments[] = self::formatArgument($argument);
446 93
        }
447
448 93
        return $method . '(' . implode(', ', $formattedArguments) . ')';
449
    }
450
451
    /**
452
     * Gets the string representation
453
     * of any passed argument.
454
     *
455
     * @param $argument
456
     * @param $depth
457
     *
458
     * @return string
459
     */
460 60
    private static function formatArgument($argument, $depth = 0)
461
    {
462 60
        if (is_object($argument)) {
463 10
            return 'object(' . get_class($argument) . ')';
464
        }
465
466 54
        if (is_int($argument) || is_float($argument)) {
467 36
            return $argument;
468
        }
469
470 27
        if (is_array($argument)) {
471 13
            if ($depth === 1) {
472 2
                $argument = '[...]';
473 2
            } else {
474 13
                $sample = array();
475 13
                foreach ($argument as $key => $value) {
476 12
                    $key = is_int($key) ? $key : "'$key'";
477 12
                    $value = self::formatArgument($value, $depth + 1);
478 12
                    $sample[] = "$key => $value";
479 13
                }
480
481 13
                $argument = "[".implode(", ", $sample)."]";
482
            }
483
484 13
            return ((strlen($argument) > 1000) ? substr($argument, 0, 1000).'...]' : $argument);
485
        }
486
487 18
        if (is_bool($argument)) {
488 1
            return $argument ? 'true' : 'false';
489
        }
490
491 17
        if (is_resource($argument)) {
492 2
            return 'resource(...)';
493
        }
494
495 15
        if (is_null($argument)) {
496 1
            return 'NULL';
497
        }
498
499 14
        return "'".(string) $argument."'";
500
    }
501
502
    /**
503
     * Utility function to format objects to printable arrays.
504
     *
505
     * @param array $objects
506
     *
507
     * @return string
508
     */
509 54
    public static function formatObjects(array $objects = null)
510
    {
511 54
        static $formatting;
512
513 54
        if ($formatting) {
514 1
            return '[Recursion]';
515
        }
516
517 54
        if (is_null($objects)) {
518
            return '';
519
        }
520
521 54
        $objects = array_filter($objects, 'is_object');
522 54
        if (empty($objects)) {
523 47
            return '';
524
        }
525
526 7
        $formatting = true;
527 7
        $parts = array();
528
529 7
        foreach ($objects as $object) {
530 7
            $parts[get_class($object)] = self::objectToArray($object);
531 7
        }
532
533 7
        $formatting = false;
534
535 7
        return 'Objects: ( ' . var_export($parts, true) . ')';
536
    }
537
538
    /**
539
     * Utility function to turn public properties and public get* and is* method values into an array.
540
     *
541
     * @param     $object
542
     * @param int $nesting
543
     *
544
     * @return array
545
     */
546 7
    private static function objectToArray($object, $nesting = 3)
547
    {
548 7
        if ($nesting == 0) {
549
            return array('...');
550
        }
551
552
        return array(
553 7
            'class' => get_class($object),
554 7
            'properties' => self::extractInstancePublicProperties($object, $nesting),
555 7
            'getters' => self::extractGetters($object, $nesting)
556 7
        );
557
    }
558
559
    /**
560
     * Returns all public instance properties.
561
     *
562
     * @param $object
563
     * @param $nesting
564
     *
565
     * @return array
566
     */
567 7
    private static function extractInstancePublicProperties($object, $nesting)
568
    {
569 7
        $reflection = new \ReflectionClass(get_class($object));
570 7
        $properties = $reflection->getProperties(\ReflectionProperty::IS_PUBLIC);
571 7
        $cleanedProperties = array();
572
573 7
        foreach ($properties as $publicProperty) {
574 1
            if (!$publicProperty->isStatic()) {
575
                $name = $publicProperty->getName();
576
                $cleanedProperties[$name] = self::cleanupNesting($object->$name, $nesting);
577
            }
578 7
        }
579
580 7
        return $cleanedProperties;
581
    }
582
583
    /**
584
     * Returns all object getters.
585
     *
586
     * @param $object
587
     * @param $nesting
588
     *
589
     * @return array
590
     */
591 7
    private static function extractGetters($object, $nesting)
592
    {
593 7
        $reflection = new \ReflectionClass(get_class($object));
594 7
        $publicMethods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
595 7
        $getters = array();
596
597 7
        foreach ($publicMethods as $publicMethod) {
598 5
            $name = $publicMethod->getName();
599 5
            $irrelevantName = (substr($name, 0, 3) !== 'get' && substr($name, 0, 2) !== 'is');
600 5
            $isStatic = $publicMethod->isStatic();
601 5
            $numberOfParameters = $publicMethod->getNumberOfParameters();
602
603 5
            if ($irrelevantName || $numberOfParameters != 0 || $isStatic) {
604 5
                continue;
605
            }
606
607
            try {
608 2
                $getters[$name] = self::cleanupNesting($object->$name(), $nesting);
609 2
            } catch (\Exception $e) {
610 1
                $getters[$name] = '!! ' . get_class($e) . ': ' . $e->getMessage() . ' !!';
611
            }
612 7
        }
613
614 7
        return $getters;
615
    }
616
617
    /**
618
     * Utility method used for recursively generating
619
     * an object or array representation.
620
     *
621
     * @param $argument
622
     * @param $nesting
623
     *
624
     * @return mixed
625
     */
626 1
    private static function cleanupNesting($argument, $nesting)
627
    {
628 1
        if (is_object($argument)) {
629
            $object = self::objectToArray($argument, $nesting - 1);
630
            $object['class'] = get_class($argument);
631
632
            return $object;
633
        }
634
635 1
        if (is_array($argument)) {
636 1
            return self::cleanupArray($argument, $nesting - 1);
637
        }
638
639 1
        return $argument;
640
    }
641
642
    /**
643
     * Utility method for recursively
644
     * gerating a representation
645
     * of the given array.
646
     *
647
     * @param array $argument
648
     * @param int $nesting
649
     *
650
     * @return mixed
651
     */
652 1
    private static function cleanupArray($argument, $nesting = 3)
653
    {
654 1
        if ($nesting == 0) {
655 1
            return '...';
656
        }
657
658 1
        foreach ($argument as $key => $value) {
659 1
            if (is_array($value)) {
660 1
                $argument[$key] = self::cleanupArray($value, $nesting - 1);
661 1
            } elseif (is_object($value)) {
662
                $argument[$key] = self::objectToArray($value, $nesting - 1);
663
            }
664 1
        }
665
666 1
        return $argument;
667
    }
668
669
    /**
670
     * Utility function to parse shouldReceive() arguments and generate
671
     * expectations from such as needed.
672
     *
673
     * @param Mockery\MockInterface $mock
674
     * @param array $args
675
     * @param callable $add
676
     * @return \Mockery\CompositeExpectation
677
     */
678 307
    public static function parseShouldReturnArgs(\Mockery\MockInterface $mock, $args, $add)
679
    {
680 307
        $composite = new \Mockery\CompositeExpectation();
681
682 307
        foreach ($args as $arg) {
683 307
            if (is_array($arg)) {
684 16
                foreach ($arg as $k => $v) {
685 16
                    $expectation = self::buildDemeterChain($mock, $k, $add)->andReturn($v);
686 16
                    $composite->add($expectation);
687 16
                }
688 307
            } elseif (is_string($arg)) {
689 295
                $expectation = self::buildDemeterChain($mock, $arg, $add);
690 289
                $composite->add($expectation);
691 289
            }
692 301
        }
693
694 301
        return $composite;
695
    }
696
697
    /**
698
     * Sets up expectations on the members of the CompositeExpectation and
699
     * builds up any demeter chain that was passed to shouldReceive.
700
     *
701
     * @param \Mockery\MockInterface $mock
702
     * @param string $arg
703
     * @param callable $add
704
     * @throws Mockery\Exception
705
     * @return \Mockery\ExpectationDirector
706
     */
707 307
    protected static function buildDemeterChain(\Mockery\MockInterface $mock, $arg, $add)
708
    {
709
        /** @var Mockery\Container $container */
710 307
        $container = $mock->mockery_getContainer();
711 307
        $methodNames = explode('->', $arg);
712 307
        reset($methodNames);
713
714 307
        if (!\Mockery::getConfiguration()->mockingNonExistentMethodsAllowed()
715 307
            && !$mock->mockery_isAnonymous()
716 307
            && !in_array(current($methodNames), $mock->mockery_getMockableMethods())
717 307
        ) {
718 4
            throw new \Mockery\Exception(
719
                'Mockery\'s configuration currently forbids mocking the method '
720 4
                . current($methodNames) . ' as it does not exist on the class or object '
721 4
                . 'being mocked'
722 4
            );
723
        }
724
725
        /** @var ExpectationInterface|null $expectations */
726 303
        $expectations = null;
727
728
        /** @var Callable $nextExp */
729
        $nextExp = function ($method) use ($add) {
730 303
            return $add($method);
731 303
        };
732
733 303
        while (true) {
734 303
            $method = array_shift($methodNames);
735 303
            $expectations = $mock->mockery_getExpectationsFor($method);
736
737 303
            if (is_null($expectations) || self::noMoreElementsInChain($methodNames)) {
738 303
                $expectations = $nextExp($method);
739 301
                if (self::noMoreElementsInChain($methodNames)) {
740 301
                    break;
741
                }
742
743 11
                $mock = self::getNewDemeterMock($container, $method, $expectations);
744 11
            } else {
745 5
                $demeterMockKey = $container->getKeyOfDemeterMockFor($method);
746 5
                if ($demeterMockKey) {
747 5
                    $mock = self::getExistingDemeterMock($container, $demeterMockKey);
748 5
                }
749
            }
750
751 11
            $nextExp = function ($n) use ($mock) {
752 11
                return $mock->shouldReceive($n);
753 11
            };
754 11
        }
755
756 301
        return $expectations;
757
    }
758
759
    /**
760
     * @param \Mockery\Container $container
761
     * @param string $method
762
     * @param Mockery\ExpectationInterface $exp
763
     *
764
     * @return \Mockery\Mock
765
     */
766 11
    private static function getNewDemeterMock(Mockery\Container $container,
767
        $method,
768
        Mockery\ExpectationInterface $exp
769
    ) {
770 11
        $mock = $container->mock('demeter_' . $method);
771 11
        $exp->andReturn($mock);
772
773 11
        return $mock;
774
    }
775
776
    /**
777
     * @param \Mockery\Container $container
778
     * @param string $demeterMockKey
779
     *
780
     * @return mixed
781
     */
782 5
    private static function getExistingDemeterMock(Mockery\Container $container, $demeterMockKey)
783
    {
784 5
        $mocks = $container->getMocks();
785 5
        $mock = $mocks[$demeterMockKey];
786
787 5
        return $mock;
788
    }
789
790
    /**
791
     * @param array $methodNames
792
     *
793
     * @return bool
794
     */
795 301
    private static function noMoreElementsInChain(array $methodNames)
796
    {
797 301
        return empty($methodNames);
798
    }
799
800
    /**
801
     * Register a file to be deleted on tearDown.
802
     *
803
     * @param string $fileName
804
     */
805 17
    public static function registerFileForCleanUp($fileName)
806
    {
807 17
        self::$_filesToCleanUp[] = $fileName;
808 17
    }
809
}
810