Completed
Push — master ( 061cc9...6fc656 )
by Dave
02:45
created

Expectation::withArgs()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

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