Completed
Pull Request — master (#668)
by Dave
10:01
created

Expectation::andReturns()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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