Completed
Push — master ( c00c2f...af7e7e )
by Dave
8s
created

Expectation::withArgsInArray()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

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