Completed
Pull Request — master (#668)
by Dave
02:39
created

Expectation::andReturns()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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 306
    public function __construct(\Mockery\MockInterface $mock, $name)
150
    {
151 306
        $this->_mock = $mock;
152 306
        $this->_name = $name;
153 306
    }
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 45
    public function __toString()
163
    {
164 45
        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 235
    public function verifyCall(array $args)
175
    {
176 235
        $this->validateOrder();
177 235
        $this->_actualCount++;
178 235
        if (true === $this->_passthru) {
179 3
            return $this->_mock->mockery_callSubjectMethod($this->_name, $args);
180
        }
181
182 233
        $return = $this->_getReturnValue($args);
183 233
        $this->throwAsNecessary($return);
184 227
        $this->_setValues();
185
186 227
        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 233
    private function throwAsNecessary($return)
196
    {
197 233
        if (!$this->_throw) {
198 227
            return;
199
        }
200
201 7
        $type = version_compare(PHP_VERSION, '7.0.0') >= 0
202 7
            ? "\Throwable"
203 7
            : "\Exception";
204
205 7
        if ($return instanceof $type) {
206 7
            throw $return;
207
        }
208
209
        return;
0 ignored issues
show
Coding Style introduced by
Empty return statement not required here
Loading history...
210
    }
211
212
    /**
213
     * Sets public properties with queued values to the mock object
214
     *
215
     * @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
     * @return mixed
217
     */
218 227
    protected function _setValues()
219
    {
220 227
        foreach ($this->_setQueue as $name => &$values) {
221 8
            if (count($values) > 0) {
222 8
                $value = array_shift($values);
223 8
                $this->_mock->{$name} = $value;
224 8
            }
225 227
        }
226 227
    }
227
228
    /**
229
     * Fetch the return value for the matching args
230
     *
231
     * @param array $args
232
     * @return mixed
233
     */
234 233
    protected function _getReturnValue(array $args)
235
    {
236 233
        if (count($this->_closureQueue) > 1) {
237
            return call_user_func_array(array_shift($this->_closureQueue), $args);
238 233
        } elseif (count($this->_closureQueue) > 0) {
239 1
            return call_user_func_array(current($this->_closureQueue), $args);
240 232
        } elseif (count($this->_returnQueue) > 1) {
241 5
            return array_shift($this->_returnQueue);
242 231
        } elseif (count($this->_returnQueue) > 0) {
243 93
            return current($this->_returnQueue);
244
        }
245
246 139
        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 273
    public function isEligible()
255
    {
256 273
        foreach ($this->_countValidators as $validator) {
257 139
            if (!$validator->isEligible($this->_actualCount)) {
258 22
                return false;
259
            }
260 270
        }
261 270
        return true;
262
    }
263
264
    /**
265
     * 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
    }
273
274
    /**
275
     * Verify call order
276
     *
277
     * @return void
278
     */
279 235
    public function validateOrder()
280
    {
281 235
        if ($this->_orderNumber) {
282 15
            $this->_mock->mockery_validateOrder((string) $this, $this->_orderNumber, $this->_mock);
283 15
        }
284 235
        if ($this->_globalOrderNumber) {
285 1
            $this->_mock->mockery_getContainer()
286 1
                ->mockery_validateOrder((string) $this, $this->_globalOrderNumber, $this->_mock);
287 1
        }
288 235
    }
289
290
    /**
291
     * Verify this expectation
292
     *
293
     * @return bool
294
     */
295 144
    public function verify()
296
    {
297 144
        foreach ($this->_countValidators as $validator) {
298 121
            $validator->validate($this->_actualCount);
299 129
        }
300 126
    }
301
302
    /**
303
     * Check if the registered expectation is a MultiArgumentClosureExpectation.
304
     * @return bool
305
     */
306 116
    private function isMultiArgumentClosureExpectation()
307
    {
308 116
        return (count($this->_expectedArgs) === 1 && ($this->_expectedArgs[0] instanceof \Mockery\Matcher\MultiArgumentClosure));
309
    }
310
311
    /**
312
     * Check if passed arguments match an argument expectation
313
     *
314
     * @param array $args
315
     * @return bool
316
     */
317 279
    public function matchArgs(array $args)
318
    {
319 279
        if (empty($this->_expectedArgs) && !$this->_noArgsExpectation) {
320 167
            return true;
321
        }
322 116
        if ($this->isMultiArgumentClosureExpectation()) {
323 6
            return $this->_matchArg($this->_expectedArgs[0], $args);
324
        }
325 110
        $argCount = count($args);
326 110
        if ($argCount !== count($this->_expectedArgs)) {
327 8
            return false;
328
        }
329 104
        for ($i=0; $i<$argCount; $i++) {
330 102
            $param =& $args[$i];
331 102
            if (!$this->_matchArg($this->_expectedArgs[$i], $param)) {
332 45
                return false;
333
            }
334 66
        }
335
336 68
        return true;
337
    }
338
339
    /**
340
     * Check if passed argument matches an argument expectation
341
     *
342
     * @param mixed $expected
343
     * @param mixed &$actual
344
     * @return bool
345
     */
346 108
    protected function _matchArg($expected, &$actual)
347
    {
348 108
        if ($expected === $actual) {
349 30
            return true;
350
        }
351 90
        if (!is_object($expected) && !is_object($actual) && $expected == $actual) {
352
            return true;
353
        }
354 90
        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 6
            set_error_handler(function () {
357 6
            });
358 6
            $result = preg_match($expected, (string) $actual);
359 6
            restore_error_handler();
360
361 6
            if ($result) {
362 3
                return true;
363
            }
364 3
        }
365 89
        if (is_string($expected) && is_object($actual)) {
366 1
            $result = $actual instanceof $expected;
367 1
            if ($result) {
368 1
                return true;
369
            }
370
        }
371 88
        if ($expected instanceof \Mockery\Matcher\MatcherAbstract) {
372 67
            return $expected->match($actual);
373
        }
374 21
        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 3
            return $expected->matches($actual);
376
        }
377 18
        return false;
378
    }
379
380
    /**
381
     * Expected argument setter for the expectation
382
     *
383
     * @param mixed ...
384
     * @return self
385
     */
386 130
    public function with()
387
    {
388 130
        return $this->withArgs(func_get_args());
389
    }
390
391
    /**
392
     * Expected arguments for the expectation passed as an array
393
     *
394
     * @param array $arguments
395
     * @return self
396
     */
397 139
    private function withArgsInArray(array $arguments)
398
    {
399 139
        if (empty($arguments)) {
400 3
            return $this->withNoArgs();
401
        }
402 136
        $this->_expectedArgs = $arguments;
403 136
        $this->_noArgsExpectation = false;
404 136
        return $this;
405
    }
406
407
    /**
408
     * Expected arguments have to be matched by the given closure.
409
     *
410
     * @param Closure $closure
411
     * @return self
412
     */
413 6
    private function withArgsMatchedByClosure(Closure $closure)
414
    {
415 6
        $this->_expectedArgs = [new MultiArgumentClosure($closure)];
416 6
        $this->_noArgsExpectation = false;
417 6
        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
     * each function call.
423
     *
424
     * @param array|Closure $argsOrClosure
425
     * @return self
426
     */
427 146
    public function withArgs($argsOrClosure)
428
    {
429 146
        if (is_array($argsOrClosure)) {
430 139
            $this->withArgsInArray($argsOrClosure);
431 146
        } elseif ($argsOrClosure instanceof Closure) {
432 6
            $this->withArgsMatchedByClosure($argsOrClosure);
433 6
        } else {
434 1
            throw new \InvalidArgumentException(sprintf('Call to %s with an invalid argument (%s), only array and '.
435 1
                'closure are allowed', __METHOD__, $argsOrClosure));
436
        }
437 145
        return $this;
438
    }
439
440
    /**
441
     * Set with() as no arguments expected
442
     *
443
     * @return self
444
     */
445 7
    public function withNoArgs()
446
    {
447 7
        $this->_noArgsExpectation = true;
448 7
        $this->_expectedArgs = [];
449 7
        return $this;
450
    }
451
452
    /**
453
     * Set expectation that any arguments are acceptable
454
     *
455
     * @return self
456
     */
457 3
    public function withAnyArgs()
458
    {
459 3
        $this->_noArgsExpectation = false;
460 3
        $this->_expectedArgs = array();
461 3
        return $this;
462
    }
463
464
    /**
465
     * Set a return value, or sequential queue of return values
466
     *
467
     * @param mixed ...
468
     * @return self
469
     */
470 106
    public function andReturn()
471
    {
472 106
        $this->_returnQueue = func_get_args();
473 106
        return $this;
474
    }
475
476
    /**
477
     * Set a return value, or sequential queue of return values
478
     *
479
     * @param mixed ...
480
     * @return self
481
     */
482
    public function andReturns()
483
    {
484
        return call_user_func_array([$this, 'andReturn'], func_get_args());
485
    }
486
487
    /**
488
     * Return this mock, like a fluent interface
489
     *
490
     * @return self
491
     */
492 1
    public function andReturnSelf()
493
    {
494 1
        return $this->andReturn($this->_mock);
495
    }
496
497
    /**
498
     * Set a sequential queue of return values with an array
499
     *
500
     * @param array $values
501
     * @return self
502
     */
503 2
    public function andReturnValues(array $values)
504
    {
505 2
        call_user_func_array(array($this, 'andReturn'), $values);
506 2
        return $this;
507
    }
508
509
    /**
510
     * Set a closure or sequence of closures with which to generate return
511
     * values. The arguments passed to the expected method are passed to the
512
     * closures as parameters.
513
     *
514
     * @param callable ...
515
     * @return self
516
     */
517 1
    public function andReturnUsing()
518
    {
519 1
        $this->_closureQueue = func_get_args();
520 1
        return $this;
521
    }
522
523
    /**
524
     * Return a self-returning black hole object.
525
     *
526
     * @return self
527
     */
528 1
    public function andReturnUndefined()
529
    {
530 1
        $this->andReturn(new \Mockery\Undefined);
531 1
        return $this;
532
    }
533
534
    /**
535
     * Return null. This is merely a language construct for Mock describing.
536
     *
537
     * @return self
538
     */
539 1
    public function andReturnNull()
540
    {
541 1
        return $this;
542
    }
543
544 1
    public function andReturnFalse()
545
    {
546 1
        return $this->andReturn(false);
547
    }
548
549 1
    public function andReturnTrue()
550
    {
551 1
        return $this->andReturn(true);
552
    }
553
554
    /**
555
     * Set Exception class and arguments to that class to be thrown
556
     *
557
     * @param string|\Exception $exception
558
     * @param string $message
559
     * @param int $code
560
     * @param \Exception $previous
561
     * @return self
562
     */
563 6
    public function andThrow($exception, $message = '', $code = 0, \Exception $previous = null)
564
    {
565 6
        $this->_throw = true;
566 6
        if (is_object($exception)) {
567 2
            $this->andReturn($exception);
568 2
        } else {
569 4
            $this->andReturn(new $exception($message, $code, $previous));
570
        }
571 6
        return $this;
572
    }
573
574
    /**
575
     * Set Exception classes to be thrown
576
     *
577
     * @param array $exceptions
578
     * @return self
579
     */
580 2
    public function andThrowExceptions(array $exceptions)
581
    {
582 2
        $this->_throw = true;
583 2
        foreach ($exceptions as $exception) {
584 2
            if (!is_object($exception)) {
585 1
                throw new Exception('You must pass an array of exception objects to andThrowExceptions');
586
            }
587 1
        }
588 1
        return $this->andReturnValues($exceptions);
589
    }
590
591
    /**
592
     * Register values to be set to a public property each time this expectation occurs
593
     *
594
     * @param string $name
595
     * @param mixed $value
596
     * @return self
597
     */
598 8
    public function andSet($name, $value)
599
    {
600 8
        $values = func_get_args();
601 8
        array_shift($values);
602 8
        $this->_setQueue[$name] = $values;
603 8
        return $this;
604
    }
605
606
    /**
607
     * Alias to andSet(). Allows the natural English construct
608
     * - set('foo', 'bar')->andReturn('bar')
609
     *
610
     * @param string $name
611
     * @param mixed $value
612
     * @return self
613
     */
614 3
    public function set($name, $value)
615
    {
616 3
        return call_user_func_array(array($this, 'andSet'), func_get_args());
617
    }
618
619
    /**
620
     * Indicates this expectation should occur zero or more times
621
     *
622
     * @return self
623
     */
624 2
    public function zeroOrMoreTimes()
625
    {
626 2
        $this->atLeast()->never();
627 2
    }
628
629
    /**
630
     * Indicates the number of times this expectation should occur
631
     *
632
     * @param int $limit
633
     * @throws InvalidArgumentException
634
     * @return self
635
     */
636 158
    public function times($limit = null)
637
    {
638 158
        if (is_null($limit)) {
639
            return $this;
640
        }
641 158
        if (!is_int($limit)) {
642 1
            throw new \InvalidArgumentException('The passed Times limit should be an integer value');
643
        }
644 157
        $this->_countValidators[] = new $this->_countValidatorClass($this, $limit);
645 157
        $this->_countValidatorClass = 'Mockery\CountValidator\Exact';
646 157
        return $this;
647
    }
648
649
    /**
650
     * Indicates that this expectation is never expected to be called
651
     *
652
     * @return self
653
     */
654 38
    public function never()
655
    {
656 38
        return $this->times(0);
657
    }
658
659
    /**
660
     * Indicates that this expectation is expected exactly once
661
     *
662
     * @return self
663
     */
664 108
    public function once()
665
    {
666 108
        return $this->times(1);
667
    }
668
669
    /**
670
     * Indicates that this expectation is expected exactly twice
671
     *
672
     * @return self
673
     */
674 19
    public function twice()
675
    {
676 19
        return $this->times(2);
677
    }
678
679
    /**
680
     * Sets next count validator to the AtLeast instance
681
     *
682
     * @return self
683
     */
684 14
    public function atLeast()
685
    {
686 14
        $this->_countValidatorClass = 'Mockery\CountValidator\AtLeast';
687 14
        return $this;
688
    }
689
690
    /**
691
     * Sets next count validator to the AtMost instance
692
     *
693
     * @return self
694
     */
695 7
    public function atMost()
696
    {
697 7
        $this->_countValidatorClass = 'Mockery\CountValidator\AtMost';
698 7
        return $this;
699
    }
700
701
    /**
702
     * Shorthand for setting minimum and maximum constraints on call counts
703
     *
704
     * @param int $minimum
705
     * @param int $maximum
706
     */
707
    public function between($minimum, $maximum)
708
    {
709
        return $this->atLeast()->times($minimum)->atMost()->times($maximum);
710
    }
711
712
    /**
713
     * Indicates that this expectation must be called in a specific given order
714
     *
715
     * @param string $group Name of the ordered group
716
     * @return self
717
     */
718 17
    public function ordered($group = null)
719
    {
720 17
        if ($this->_globally) {
721 1
            $this->_globalOrderNumber = $this->_defineOrdered($group, $this->_mock->mockery_getContainer());
722 1
        } else {
723 16
            $this->_orderNumber = $this->_defineOrdered($group, $this->_mock);
724
        }
725 17
        $this->_globally = false;
726 17
        return $this;
727
    }
728
729
    /**
730
     * Indicates call order should apply globally
731
     *
732
     * @return self
733
     */
734 1
    public function globally()
735
    {
736 1
        $this->_globally = true;
737 1
        return $this;
738
    }
739
740
    /**
741
     * Setup the ordering tracking on the mock or mock container
742
     *
743
     * @param string $group
744
     * @param object $ordering
745
     * @return int
746
     */
747 17
    protected function _defineOrdered($group, $ordering)
748
    {
749 17
        $groups = $ordering->mockery_getGroups();
750 17
        if (is_null($group)) {
751 16
            $result = $ordering->mockery_allocateOrder();
752 17
        } elseif (isset($groups[$group])) {
753 2
            $result = $groups[$group];
754 2
        } else {
755 4
            $result = $ordering->mockery_allocateOrder();
756 4
            $ordering->mockery_setGroup($group, $result);
757
        }
758 17
        return $result;
759
    }
760
761
    /**
762
     * Return order number
763
     *
764
     * @return int
765
     */
766 1
    public function getOrderNumber()
767
    {
768 1
        return $this->_orderNumber;
769
    }
770
771
    /**
772
     * Mark this expectation as being a default
773
     *
774
     * @return self
775
     */
776 25
    public function byDefault()
777
    {
778 25
        $director = $this->_mock->mockery_getExpectationsFor($this->_name);
779 25
        if (!empty($director)) {
780 25
            $director->makeExpectationDefault($this);
781 24
        }
782 24
        return $this;
783
    }
784
785
    /**
786
     * Return the parent mock of the expectation
787
     *
788
     * @return \Mockery\MockInterface
789
     */
790 29
    public function getMock()
791
    {
792 29
        return $this->_mock;
793
    }
794
795
    /**
796
     * Flag this expectation as calling the original class method with the
797
     * any provided arguments instead of using a return value queue.
798
     *
799
     * @return self
800
     */
801 3
    public function passthru()
802
    {
803 3
        if ($this->_mock instanceof Mock) {
804
            throw new Exception(
805
                'Mock Objects not created from a loaded/existing class are '
806
                . 'incapable of passing method calls through to a parent class'
807
            );
808
        }
809 3
        $this->_passthru = true;
810 3
        return $this;
811
    }
812
813
    /**
814
     * Cloning logic
815
     *
816
     */
817 9
    public function __clone()
818
    {
819 9
        $newValidators = array();
820 9
        $countValidators = $this->_countValidators;
821 9
        foreach ($countValidators as $validator) {
822 6
            $newValidators[] = clone $validator;
823 9
        }
824 9
        $this->_countValidators = $newValidators;
825 9
    }
826
827 6
    public function getName()
828
    {
829 6
        return $this->_name;
830
    }
831
}
832