Completed
Push — master ( 0b23f7...de5feb )
by Dave
8s
created

Expectation::isMultiArgumentClosureExpectation()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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