Completed
Pull Request — master (#646)
by Alex
05:03 queued 02:04
created

Expectation::_matchArg()   C

Complexity

Conditions 15
Paths 17

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 15.0185

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 22
cts 23
cp 0.9565
rs 5.0504
c 0
b 0
f 0
cc 15
eloc 20
nc 17
nop 2
crap 15.0185

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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