Completed
Pull Request — master (#679)
by Dave
02:17
created

Expectation::throwAsNecessary()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 8
cts 8
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 5
nop 1
crap 4
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 Closure;
24
use Mockery\Matcher\MultiArgumentClosure;
25
26
class Expectation implements ExpectationInterface
27
{
28
    /**
29
     * Mock object to which this expectation belongs
30
     *
31
     * @var object
32
     */
33
    protected $_mock = null;
34
35
    /**
36
     * Method name
37
     *
38
     * @var string
39
     */
40
    protected $_name = null;
41
42
    /**
43
     * Arguments expected by this expectation
44
     *
45
     * @var array
46
     */
47
    protected $_expectedArgs = array();
48
49
    /**
50
     * Count validator store
51
     *
52
     * @var array
53
     */
54
    protected $_countValidators = array();
55
56
    /**
57
     * The count validator class to use
58
     *
59
     * @var string
60
     */
61
    protected $_countValidatorClass = 'Mockery\CountValidator\Exact';
62
63
    /**
64
     * Actual count of calls to this expectation
65
     *
66
     * @var int
67
     */
68
    protected $_actualCount = 0;
69
70
    /**
71
     * Value to return from this expectation
72
     *
73
     * @var mixed
74
     */
75
    protected $_returnValue = null;
76
77
    /**
78
     * Array of return values as a queue for multiple return sequence
79
     *
80
     * @var array
81
     */
82
    protected $_returnQueue = array();
83
84
    /**
85
     * Array of closures executed with given arguments to generate a result
86
     * to be returned
87
     *
88
     * @var array
89
     */
90
    protected $_closureQueue = array();
91
92
    /**
93
     * Array of values to be set when this expectation matches
94
     *
95
     * @var array
96
     */
97
    protected $_setQueue = array();
98
99
    /**
100
     * Integer representing the call order of this expectation
101
     *
102
     * @var int
103
     */
104
    protected $_orderNumber = null;
105
106
    /**
107
     * Integer representing the call order of this expectation on a global basis
108
     *
109
     * @var int
110
     */
111
    protected $_globalOrderNumber = null;
112
113
    /**
114
     * Flag indicating that an exception is expected to be throw (not returned)
115
     *
116
     * @var bool
117
     */
118
    protected $_throw = false;
119
120
    /**
121
     * Flag indicating whether the order of calling is determined locally or
122
     * globally
123
     *
124
     * @var bool
125
     */
126
    protected $_globally = false;
127
128
    /**
129
     * Flag indicating we expect no arguments
130
     *
131
     * @var bool
132
     */
133
    protected $_noArgsExpectation = false;
134
135
    /**
136
     * Flag indicating if the return value should be obtained from the original
137
     * class method instead of returning predefined values from the return queue
138
     *
139
     * @var bool
140
     */
141
    protected $_passthru = false;
142
143
    /**
144
     * Constructor
145
     *
146
     * @param \Mockery\MockInterface $mock
147
     * @param string $name
148
     */
149 299
    public function __construct(\Mockery\MockInterface $mock, $name)
150
    {
151 299
        $this->_mock = $mock;
152 299
        $this->_name = $name;
153 299
    }
154
155
    /**
156
     * Return a string with the method name and arguments formatted
157
     *
158
     * @param string $name Name of the expected method
159
     * @param array $args List of arguments to the method
160
     * @return string
161
     */
162 43
    public function __toString()
163
    {
164 43
        return \Mockery::formatArgs($this->_name, $this->_expectedArgs);
165
    }
166
167
    /**
168
     * Verify the current call, i.e. that the given arguments match those
169
     * of this expectation
170
     *
171
     * @param array $args
172
     * @return mixed
173
     */
174 229
    public function verifyCall(array $args)
175
    {
176 229
        $this->validateOrder();
177 229
        $this->_actualCount++;
178 229
        if (true === $this->_passthru) {
179 3
            return $this->_mock->mockery_callSubjectMethod($this->_name, $args);
180
        }
181 227
182 227
        $return = $this->_getReturnValue($args);
183 7
        $this->throwAsNecessary($return);
184
        $this->_setValues();
185 221
186 221
        return $return;
187
    }
188
189
    /**
190
     * Throws an exception if the expectation has been configured to do so
191
     * 
192
     * @throws \Exception|\Throwable
193
     * @return void
194
     */
195 221
	private function throwAsNecessary($return)
196
    {
197 221
        if (!$this->_throw) {
198 8
            return;
199 8
        }
200 8
201 8
        $type = version_compare(PHP_VERSION, '7.0.0')
202 221
            ? "\Throwable"
203 221
            : "\Exception";
204
205
        if ($return instanceof $type) {
206
            throw $return;
207
        }
208
209
        return;
0 ignored issues
show
Coding Style introduced by
Empty return statement not required here
Loading history...
210
    }
211 227
212
    /**
213 227
     * Sets public properties with queued values to the mock object
214
     *
215 227
     * @param array $args
0 ignored issues
show
Bug introduced by
There is no parameter named $args. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
216 1
     * @return mixed
217 226
     */
218 5
    protected function _setValues()
219 225
    {
220 88
        foreach ($this->_setQueue as $name => &$values) {
221
            if (count($values) > 0) {
222
                $value = array_shift($values);
223 138
                $this->_mock->{$name} = $value;
224
            }
225
        }
226
    }
227
228
    /**
229
     * Fetch the return value for the matching args
230
     *
231 267
     * @param array $args
232
     * @return mixed
233 267
     */
234 138
    protected function _getReturnValue(array $args)
235 22
    {
236
        if (count($this->_closureQueue) > 1) {
237 264
            return call_user_func_array(array_shift($this->_closureQueue), $args);
238 264
        } elseif (count($this->_closureQueue) > 0) {
239
            return call_user_func_array(current($this->_closureQueue), $args);
240
        } elseif (count($this->_returnQueue) > 1) {
241
            return array_shift($this->_returnQueue);
242
        } elseif (count($this->_returnQueue) > 0) {
243
            return current($this->_returnQueue);
244
        }
245
246
        return $this->_mock->mockery_returnValueForMethod($this->_name);
247
    }
248
249
    /**
250
     * Checks if this expectation is eligible for additional calls
251
     *
252
     * @return bool
253
     */
254
    public function isEligible()
255
    {
256 229
        foreach ($this->_countValidators as $validator) {
257
            if (!$validator->isEligible($this->_actualCount)) {
258 229
                return false;
259 15
            }
260 15
        }
261 229
        return true;
262 1
    }
263 1
264 1
    /**
265 229
     * Check if there is a constraint on call count
266
     *
267
     * @return bool
268
     */
269
    public function isCallCountConstrained()
270
    {
271
        return (count($this->_countValidators) > 0);
272 141
    }
273
274 141
    /**
275 119
     * Verify call order
276 126
     *
277 124
     * @return void
278
     */
279
    public function validateOrder()
280
    {
281
        if ($this->_orderNumber) {
282
            $this->_mock->mockery_validateOrder((string) $this, $this->_orderNumber, $this->_mock);
283 114
        }
284
        if ($this->_globalOrderNumber) {
285 114
            $this->_mock->mockery_getContainer()
286
                ->mockery_validateOrder((string) $this, $this->_globalOrderNumber, $this->_mock);
287
        }
288
    }
289
290
    /**
291
     * Verify this expectation
292
     *
293
     * @return bool
294 273
     */
295
    public function verify()
296 273
    {
297 163
        foreach ($this->_countValidators as $validator) {
298
            $validator->validate($this->_actualCount);
299 114
        }
300 6
    }
301
302 108
    /**
303 108
     * Check if the registered expectation is a MultiArgumentClosureExpectation.
304 8
     * @return bool
305
     */
306 102
    private function isMultiArgumentClosureExpectation()
307 100
    {
308 100
        return (count($this->_expectedArgs) === 1 && ($this->_expectedArgs[0] instanceof \Mockery\Matcher\MultiArgumentClosure));
309 45
    }
310
311 64
    /**
312
     * Check if passed arguments match an argument expectation
313 66
     *
314
     * @param array $args
315
     * @return bool
316
     */
317
    public function matchArgs(array $args)
318
    {
319
        if (empty($this->_expectedArgs) && !$this->_noArgsExpectation) {
320
            return true;
321
        }
322
        if ($this->isMultiArgumentClosureExpectation()) {
323 106
            return $this->_matchArg($this->_expectedArgs[0], $args);
324
        }
325 106
        $argCount = count($args);
326 28
        if ($argCount !== count($this->_expectedArgs)) {
327
            return false;
328 90
        }
329
        for ($i=0; $i<$argCount; $i++) {
330
            $param =& $args[$i];
331 90
            if (!$this->_matchArg($this->_expectedArgs[$i], $param)) {
332
                return false;
333 6
            }
334 6
        }
335 6
336 6
        return true;
337
    }
338 6
339 3
    /**
340
     * Check if passed argument matches an argument expectation
341 3
     *
342 89
     * @param mixed $expected
343 1
     * @param mixed &$actual
344 1
     * @return bool
345 1
     */
346
    protected function _matchArg($expected, &$actual)
347
    {
348 88
        if ($expected === $actual) {
349 67
            return true;
350
        }
351 21
        if (!is_object($expected) && !is_object($actual) && $expected == $actual) {
352 3
            return true;
353
        }
354 18
        if (is_string($expected) && !is_array($actual) && !is_object($actual)) {
355
            # push/pop an error handler here to to make sure no error/exception thrown if $expected is not a regex
356
            set_error_handler(function () {
357
            });
358
            $result = preg_match($expected, (string) $actual);
359
            restore_error_handler();
360
361
            if ($result) {
362
                return true;
363 130
            }
364
        }
365 130
        if (is_string($expected) && is_object($actual)) {
366
            $result = $actual instanceof $expected;
367
            if ($result) {
368
                return true;
369
            }
370
        }
371
        if ($expected instanceof \Mockery\Matcher\MatcherAbstract) {
372
            return $expected->match($actual);
373
        }
374 135
        if ($expected instanceof \Hamcrest\Matcher || $expected instanceof \Hamcrest_Matcher) {
0 ignored issues
show
Bug introduced by
The class Hamcrest_Matcher does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
375
            return $expected->matches($actual);
376 135
        }
377 2
        return false;
378
    }
379 133
380 133
    /**
381 133
     * Expected argument setter for the expectation
382
     *
383
     * @param mixed ...
384
     * @return self
385
     */
386
    public function with()
387
    {
388
        return $this->withArgs(func_get_args());
389
    }
390 6
391
    /**
392 6
     * Expected arguments for the expectation passed as an array
393 6
     *
394 6
     * @param array $arguments
395
     * @return self
396
     */
397
    private function withArgsInArray(array $arguments)
398
    {
399
        if (empty($arguments)) {
400
            return $this->withNoArgs();
401
        }
402
        $this->_expectedArgs = $arguments;
403
        $this->_noArgsExpectation = false;
404 142
        return $this;
405
    }
406 142
407 135
    /**
408 142
     * Expected arguments have to be matched by the given closure.
409 6
     *
410 6
     * @param Closure $closure
411 1
     * @return self
412 1
     */
413
    private function withArgsMatchedByClosure(Closure $closure)
414 141
    {
415
        $this->_expectedArgs = [new MultiArgumentClosure($closure)];
416
        $this->_noArgsExpectation = false;
417
        return $this;
418
    }
419
420
    /**
421
     * Expected arguments for the expectation passed as an array or a closure that matches each passed argument on
422 6
     * each function call.
423
     *
424 6
     * @param array|Closure $argsOrClosure
425 6
     * @return self
426 6
     */
427
    public function withArgs($argsOrClosure)
428
    {
429
        if (is_array($argsOrClosure)) {
430
            $this->withArgsInArray($argsOrClosure);
431
        } elseif ($argsOrClosure instanceof Closure) {
432
            $this->withArgsMatchedByClosure($argsOrClosure);
433
        } else {
434 2
            throw new \InvalidArgumentException(sprintf('Call to %s with an invalid argument (%s), only array and '.
435
                'closure are allowed', __METHOD__, $argsOrClosure));
436 2
        }
437 2
        return $this;
438
    }
439
440
    /**
441
     * Set with() as no arguments expected
442
     *
443
     * @return self
444
     */
445
    public function withNoArgs()
446 101
    {
447
        $this->_noArgsExpectation = true;
448 101
        $this->_expectedArgs = [];
449 101
        return $this;
450
    }
451
452
    /**
453
     * Set expectation that any arguments are acceptable
454
     *
455
     * @return self
456
     */
457 1
    public function withAnyArgs()
458
    {
459 1
        $this->_expectedArgs = array();
460
        return $this;
461
    }
462
463
    /**
464
     * Set a return value, or sequential queue of return values
465
     *
466
     * @param mixed ...
467
     * @return self
468 2
     */
469
    public function andReturn()
470 2
    {
471 2
        $this->_returnQueue = func_get_args();
472
        return $this;
473
    }
474
475
    /**
476
     * Return this mock, like a fluent interface
477
     *
478
     * @return self
479
     */
480
    public function andReturnSelf()
481
    {
482 1
        return $this->andReturn($this->_mock);
483
    }
484 1
485 1
    /**
486
     * Set a sequential queue of return values with an array
487
     *
488
     * @param array $values
489
     * @return self
490
     */
491
    public function andReturnValues(array $values)
492
    {
493 1
        call_user_func_array(array($this, 'andReturn'), $values);
494
        return $this;
495 1
    }
496 1
497
    /**
498
     * Set a closure or sequence of closures with which to generate return
499
     * values. The arguments passed to the expected method are passed to the
500
     * closures as parameters.
501
     *
502
     * @param callable ...
503
     * @return self
504 1
     */
505
    public function andReturnUsing()
506 1
    {
507
        $this->_closureQueue = func_get_args();
508
        return $this;
509 1
    }
510
511 1
    /**
512
     * Return a self-returning black hole object.
513
     *
514 1
     * @return self
515
     */
516 1
    public function andReturnUndefined()
517
    {
518
        $this->andReturn(new \Mockery\Undefined);
519
        return $this;
520
    }
521
522
    /**
523
     * Return null. This is merely a language construct for Mock describing.
524
     *
525
     * @return self
526
     */
527
    public function andReturnNull()
528 6
    {
529
        return $this;
530 6
    }
531 6
532 2
    public function andReturnFalse()
533 2
    {
534 4
        return $this->andReturn(false);
535
    }
536 6
537
    public function andReturnTrue()
538
    {
539
        return $this->andReturn(true);
540
    }
541
542
    /**
543
     * Set Exception class and arguments to that class to be thrown
544
     *
545 2
     * @param string|\Exception $exception
546
     * @param string $message
547 2
     * @param int $code
548 2
     * @param \Exception $previous
549 2
     * @return self
550 1
     */
551
    public function andThrow($exception, $message = '', $code = 0, \Exception $previous = null)
552 1
    {
553 1
        $this->_throw = true;
554
        if (is_object($exception)) {
555
            $this->andReturn($exception);
556
        } else {
557
            $this->andReturn(new $exception($message, $code, $previous));
558
        }
559
        return $this;
560
    }
561
562
    /**
563 8
     * Set Exception classes to be thrown
564
     *
565 8
     * @param array $exceptions
566 8
     * @return self
567 8
     */
568 8
    public function andThrowExceptions(array $exceptions)
569
    {
570
        $this->_throw = true;
571
        foreach ($exceptions as $exception) {
572
            if (!is_object($exception)) {
573
                throw new Exception('You must pass an array of exception objects to andThrowExceptions');
574
            }
575
        }
576
        return $this->andReturnValues($exceptions);
577
    }
578
579 3
    /**
580
     * Register values to be set to a public property each time this expectation occurs
581 3
     *
582
     * @param string $name
583
     * @param mixed $value
584
     * @return self
585
     */
586
    public function andSet($name, $value)
587
    {
588
        $values = func_get_args();
589 2
        array_shift($values);
590
        $this->_setQueue[$name] = $values;
591 2
        return $this;
592 2
    }
593
594
    /**
595
     * Alias to andSet(). Allows the natural English construct
596
     * - set('foo', 'bar')->andReturn('bar')
597
     *
598
     * @param string $name
599
     * @param mixed $value
600
     * @return self
601 156
     */
602
    public function set($name, $value)
603 156
    {
604
        return call_user_func_array(array($this, 'andSet'), func_get_args());
605
    }
606 156
607 1
    /**
608
     * Indicates this expectation should occur zero or more times
609 155
     *
610 155
     * @return self
611 155
     */
612
    public function zeroOrMoreTimes()
613
    {
614
        $this->atLeast()->never();
615
    }
616
617
    /**
618
     * Indicates the number of times this expectation should occur
619 38
     *
620
     * @param int $limit
621 38
     * @throws InvalidArgumentException
622
     * @return self
623
     */
624
    public function times($limit = null)
625
    {
626
        if (is_null($limit)) {
627
            return $this;
628
        }
629 106
        if (!is_int($limit)) {
630
            throw new \InvalidArgumentException('The passed Times limit should be an integer value');
631 106
        }
632
        $this->_countValidators[] = new $this->_countValidatorClass($this, $limit);
633
        $this->_countValidatorClass = 'Mockery\CountValidator\Exact';
634
        return $this;
635
    }
636
637
    /**
638
     * Indicates that this expectation is never expected to be called
639 18
     *
640
     * @return self
641 18
     */
642
    public function never()
643
    {
644
        return $this->times(0);
645
    }
646
647
    /**
648
     * Indicates that this expectation is expected exactly once
649 14
     *
650
     * @return self
651 14
     */
652 14
    public function once()
653
    {
654
        return $this->times(1);
655
    }
656
657
    /**
658
     * Indicates that this expectation is expected exactly twice
659
     *
660 7
     * @return self
661
     */
662 7
    public function twice()
663 7
    {
664
        return $this->times(2);
665
    }
666
667
    /**
668
     * Sets next count validator to the AtLeast instance
669
     *
670
     * @return self
671
     */
672
    public function atLeast()
673
    {
674
        $this->_countValidatorClass = 'Mockery\CountValidator\AtLeast';
675
        return $this;
676
    }
677
678
    /**
679
     * Sets next count validator to the AtMost instance
680
     *
681
     * @return self
682
     */
683 17
    public function atMost()
684
    {
685 17
        $this->_countValidatorClass = 'Mockery\CountValidator\AtMost';
686 1
        return $this;
687 1
    }
688 16
689
    /**
690 17
     * Shorthand for setting minimum and maximum constraints on call counts
691 17
     *
692
     * @param int $minimum
693
     * @param int $maximum
694
     */
695
    public function between($minimum, $maximum)
696
    {
697
        return $this->atLeast()->times($minimum)->atMost()->times($maximum);
698
    }
699 1
700
    /**
701 1
     * Indicates that this expectation must be called in a specific given order
702 1
     *
703
     * @param string $group Name of the ordered group
704
     * @return self
705
     */
706
    public function ordered($group = null)
707
    {
708
        if ($this->_globally) {
709
            $this->_globalOrderNumber = $this->_defineOrdered($group, $this->_mock->mockery_getContainer());
710
        } else {
711
            $this->_orderNumber = $this->_defineOrdered($group, $this->_mock);
712 17
        }
713
        $this->_globally = false;
714 17
        return $this;
715 17
    }
716 16
717 17
    /**
718 2
     * Indicates call order should apply globally
719 2
     *
720 4
     * @return self
721 4
     */
722
    public function globally()
723 17
    {
724
        $this->_globally = true;
725
        return $this;
726
    }
727
728
    /**
729
     * Setup the ordering tracking on the mock or mock container
730
     *
731 1
     * @param string $group
732
     * @param object $ordering
733 1
     * @return int
734
     */
735
    protected function _defineOrdered($group, $ordering)
736
    {
737
        $groups = $ordering->mockery_getGroups();
738
        if (is_null($group)) {
739
            $result = $ordering->mockery_allocateOrder();
740
        } elseif (isset($groups[$group])) {
741 25
            $result = $groups[$group];
742
        } else {
743 25
            $result = $ordering->mockery_allocateOrder();
744 25
            $ordering->mockery_setGroup($group, $result);
745 25
        }
746 24
        return $result;
747 24
    }
748
749
    /**
750
     * Return order number
751
     *
752
     * @return int
753
     */
754
    public function getOrderNumber()
755 27
    {
756
        return $this->_orderNumber;
757 27
    }
758
759
    /**
760
     * Mark this expectation as being a default
761
     *
762
     * @return self
763
     */
764
    public function byDefault()
765
    {
766 3
        $director = $this->_mock->mockery_getExpectationsFor($this->_name);
767
        if (!empty($director)) {
768 3
            $director->makeExpectationDefault($this);
769
        }
770
        return $this;
771
    }
772
773
    /**
774 3
     * Return the parent mock of the expectation
775 3
     *
776
     * @return \Mockery\MockInterface
777
     */
778
    public function getMock()
779
    {
780
        return $this->_mock;
781
    }
782 9
783
    /**
784 9
     * Flag this expectation as calling the original class method with the
785 9
     * any provided arguments instead of using a return value queue.
786 9
     *
787 6
     * @return self
788 9
     */
789 9
    public function passthru()
790 9
    {
791
        if ($this->_mock instanceof Mock) {
792 6
            throw new Exception(
793
                'Mock Objects not created from a loaded/existing class are '
794 6
                . 'incapable of passing method calls through to a parent class'
795
            );
796
        }
797
        $this->_passthru = true;
798
        return $this;
799
    }
800
801
    /**
802
     * Cloning logic
803
     *
804
     */
805
    public function __clone()
806
    {
807
        $newValidators = array();
808
        $countValidators = $this->_countValidators;
809
        foreach ($countValidators as $validator) {
810
            $newValidators[] = clone $validator;
811
        }
812
        $this->_countValidators = $newValidators;
813
    }
814
815
    public function getName()
816
    {
817
        return $this->_name;
818
    }
819
}
820