Completed
Pull Request — master (#566)
by
unknown
02:43
created

Expectation::isExpected()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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