Completed
Pull Request — master (#668)
by Dave
03:50
created

Mock::expects()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
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 Mockery\HigherOrderMessage;
24
use Mockery\MockInterface;
25
use Mockery\ExpectsHigherOrderMessage;
26
27
class Mock implements MockInterface
28
{
29
    /**
30
     * Stores an array of all expectation directors for this mock
31
     *
32
     * @var array
33
     */
34
    protected $_mockery_expectations = array();
35
36
    /**
37
     * Stores an inital number of expectations that can be manipulated
38
     * while using the getter method.
39
     *
40
     * @var int
41
     */
42
    protected $_mockery_expectations_count = 0;
43
44
    /**
45
     * Flag to indicate whether we can ignore method calls missing from our
46
     * expectations
47
     *
48
     * @var bool
49
     */
50
    protected $_mockery_ignoreMissing = false;
51
52
    /**
53
     * Flag to indicate whether we can defer method calls missing from our
54
     * expectations
55
     *
56
     * @var bool
57
     */
58
    protected $_mockery_deferMissing = false;
59
60
    /**
61
     * Flag to indicate whether this mock was verified
62
     *
63
     * @var bool
64
     */
65
    protected $_mockery_verified = false;
66
67
    /**
68
     * Given name of the mock
69
     *
70
     * @var string
71
     */
72
    protected $_mockery_name = null;
73
74
    /**
75
     * Order number of allocation
76
     *
77
     * @var int
78
     */
79
    protected $_mockery_allocatedOrder = 0;
80
81
    /**
82
     * Current ordered number
83
     *
84
     * @var int
85
     */
86
    protected $_mockery_currentOrder = 0;
87
88
    /**
89
     * Ordered groups
90
     *
91
     * @var array
92
     */
93
    protected $_mockery_groups = array();
94
95
    /**
96
     * Mock container containing this mock object
97
     *
98
     * @var \Mockery\Container
99
     */
100
    protected $_mockery_container = null;
101
102
    /**
103
     * Instance of a core object on which methods are called in the event
104
     * it has been set, and an expectation for one of the object's methods
105
     * does not exist. This implements a simple partial mock proxy system.
106
     *
107
     * @var object
108
     */
109
    protected $_mockery_partial = null;
110
111
    /**
112
     * Flag to indicate we should ignore all expectations temporarily. Used
113
     * mainly to prevent expectation matching when in the middle of a mock
114
     * object recording session.
115
     *
116
     * @var bool
117
     */
118
    protected $_mockery_disableExpectationMatching = false;
119
120
    /**
121
     * Stores all stubbed public methods separate from any on-object public
122
     * properties that may exist.
123
     *
124
     * @var array
125
     */
126
    protected $_mockery_mockableProperties = array();
127
128
    /**
129
     * @var array
130
     */
131
    protected $_mockery_mockableMethods = array();
132
133
    /**
134
     * Just a local cache for this mock's target's methods
135
     *
136
     * @var ReflectionMethod[]
137
     */
138
    protected static $_mockery_methods;
139
140
    protected $_mockery_allowMockingProtectedMethods = false;
141
142
    protected $_mockery_receivedMethodCalls;
143
144
    /**
145
     * If shouldIgnoreMissing is called, this value will be returned on all calls to missing methods
146
     * @var mixed
147
     */
148
    protected $_mockery_defaultReturnValue = null;
149
150
    /**
151
     * We want to avoid constructors since class is copied to Generator.php
152
     * for inclusion on extending class definitions.
153
     *
154
     * @param \Mockery\Container $container
155
     * @param object $partialObject
156
     * @return void
157
     */
158
    public function mockery_init(\Mockery\Container $container = null, $partialObject = null)
159
    {
160
        if (is_null($container)) {
161
            $container = new \Mockery\Container;
162
        }
163
        $this->_mockery_container = $container;
164
        if (!is_null($partialObject)) {
165
            $this->_mockery_partial = $partialObject;
166
        }
167
168
        if (!\Mockery::getConfiguration()->mockingNonExistentMethodsAllowed()) {
169
            foreach ($this->mockery_getMethods() as $method) {
170
                if ($method->isPublic() && !$method->isStatic()) {
171
                    $this->_mockery_mockableMethods[] = $method->getName();
172
                }
173
            }
174
        }
175
    }
176
177
    /**
178
     * Set expected method calls
179
     *
180
     * @param null|string $methodName,... one or many methods that are expected to be called in this mock
181
     * @return \Mockery\ExpectationInterface|\Mockery\HigherOrderMessage
182
     */
183
    public function shouldReceive($methodName = null)
184
    {
185
        if ($methodName === null) {
186
            return new HigherOrderMessage($this, "shouldReceive");
187
        }
188
189
        foreach (func_get_args() as $method) {
190
            if ("" == $method) {
191
                throw new \InvalidArgumentException("Received empty method name");
192
            }
193
        }
194
195
        /** @var array $nonPublicMethods */
196
        $nonPublicMethods = $this->getNonPublicMethods();
197
198
        $self = $this;
199
        $allowMockingProtectedMethods = $this->_mockery_allowMockingProtectedMethods;
200
201
        $lastExpectation = \Mockery::parseShouldReturnArgs(
202
            $this, func_get_args(), function ($method) use ($self, $nonPublicMethods, $allowMockingProtectedMethods) {
203
                $rm = $self->mockery_getMethod($method);
204
                if ($rm) {
205
                    if ($rm->isPrivate()) {
206
                        throw new \InvalidArgumentException("$method() cannot be mocked as it is a private method");
207
                    }
208
                    if (!$allowMockingProtectedMethods && $rm->isProtected()) {
209
                        throw new \InvalidArgumentException("$method() cannot be mocked as it is a protected method and mocking protected methods is not enabled for the currently used mock object.");
210
                    }
211
                }
212
213
                $director = $self->mockery_getExpectationsFor($method);
214
                if (!$director) {
215
                    $director = new \Mockery\ExpectationDirector($method, $self);
216
                    $self->mockery_setExpectationsFor($method, $director);
217
                }
218
                $expectation = new \Mockery\Expectation($self, $method);
219
                $director->addExpectation($expectation);
220
                return $expectation;
221
            }
222
        );
223
        return $lastExpectation;
224
    }
225
226
    // start method allows
227
    /**
228
     * @return HigherOrderMessage
229
     */
230
    public function allows(array $stubs = [])
231
    {
232
        if (empty($stubs)) {
233
            return $this->shouldReceive();
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->shouldReceive(); of type Mockery\HigherOrderMessa...ry\CompositeExpectation adds the type Mockery\CompositeExpectation to the return on line 233 which is incompatible with the return type documented by Mockery\Mock::allows of type Mockery\HigherOrderMessage.
Loading history...
234
        }
235
236
        foreach ($stubs as $method => $returnValue) {
237
            $this->shouldReceive($method)->andReturn($returnValue);
0 ignored issues
show
Bug introduced by
The method andReturn does only exist in Mockery\CompositeExpectation, but not in Mockery\HigherOrderMessage.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
238
        }
239
240
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Mockery\Mock) is incompatible with the return type documented by Mockery\Mock::allows of type Mockery\HigherOrderMessage.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
241
    }
242
    // end method allows
243
244
    // start method expects
245
    /**
246
     * @return ExpectsHigherOrderMessage
247
     */
248
    public function expects()
249
    {
250
        return new ExpectsHigherOrderMessage($this);
251
    }
252
    // end method expects
253
     
254
     
255
    /**
256
     * Shortcut method for setting an expectation that a method should not be called.
257
     *
258
     * @param null|string $methodName,... one or many methods that are expected not to be called in this mock
259
     * @return \Mockery\Expectation|\Mockery\HigherOrderMessage
260
     */
261
    public function shouldNotReceive($methodName = null)
262
    {
263
        if ($methodName === null) {
264
            return new HigherOrderMessage($this, "shouldNotReceive");
265
        }
266
267
        $expectation = call_user_func_array(array($this, 'shouldReceive'), func_get_args());
268
        $expectation->never();
269
        return $expectation;
270
    }
271
272
    /**
273
     * Allows additional methods to be mocked that do not explicitly exist on mocked class
274
     * @param String $method name of the method to be mocked
275
     * @return Mock
276
     */
277
    public function shouldAllowMockingMethod($method)
278
    {
279
        $this->_mockery_mockableMethods[] = $method;
280
        return $this;
281
    }
282
283
    /**
284
     * Set mock to ignore unexpected methods and return Undefined class
285
     * @param mixed $returnValue the default return value for calls to missing functions on this mock
286
     * @return Mock
287
     */
288
    public function shouldIgnoreMissing($returnValue = null)
289
    {
290
        $this->_mockery_ignoreMissing = true;
291
        $this->_mockery_defaultReturnValue = $returnValue;
292
        return $this;
293
    }
294
295
    public function asUndefined()
296
    {
297
        $this->_mockery_ignoreMissing = true;
298
        $this->_mockery_defaultReturnValue = new \Mockery\Undefined;
299
        return $this;
300
    }
301
302
    /**
303
     * @return Mock
304
     */
305
    public function shouldAllowMockingProtectedMethods()
306
    {
307
        $this->_mockery_allowMockingProtectedMethods = true;
308
        return $this;
309
    }
310
311
312
    /**
313
     * Set mock to defer unexpected methods to it's parent
314
     *
315
     * This is particularly useless for this class, as it doesn't have a parent,
316
     * but included for completeness
317
     *
318
     * @return Mock
319
     */
320
    public function shouldDeferMissing()
321
    {
322
        $this->_mockery_deferMissing = true;
323
        return $this;
324
    }
325
326
    /**
327
     * Create an obviously worded alias to shouldDeferMissing()
328
     *
329
     * @return Mock
330
     */
331
    public function makePartial()
332
    {
333
        return $this->shouldDeferMissing();
334
    }
335
336
    /**
337
     * In the event shouldReceive() accepting one or more methods/returns,
338
     * this method will switch them from normal expectations to default
339
     * expectations
340
     *
341
     * @return self
342
     */
343
    public function byDefault()
344
    {
345
        foreach ($this->_mockery_expectations as $director) {
346
            $exps = $director->getExpectations();
347
            foreach ($exps as $exp) {
348
                $exp->byDefault();
349
            }
350
        }
351
        return $this;
352
    }
353
354
    /**
355
     * Capture calls to this mock
356
     */
357
    public function __call($method, array $args)
358
    {
359
        return $this->_mockery_handleMethodCall($method, $args);
360
    }
361
362
    public static function __callStatic($method, array $args)
363
    {
364
        return self::_mockery_handleStaticMethodCall($method, $args);
365
    }
366
367
    /**
368
     * Forward calls to this magic method to the __call method
369
     */
370
    public function __toString()
371
    {
372
        return $this->__call('__toString', array());
373
    }
374
375
    /**
376
     * Iterate across all expectation directors and validate each
377
     *
378
     * @throws \Mockery\CountValidator\Exception
379
     * @return void
380
     */
381
    public function mockery_verify()
382
    {
383
        if ($this->_mockery_verified) {
384
            return;
385
        }
386
        if (isset($this->_mockery_ignoreVerification)
387
            && $this->_mockery_ignoreVerification == true) {
388
            return;
389
        }
390
        $this->_mockery_verified = true;
391
        foreach ($this->_mockery_expectations as $director) {
392
            $director->verify();
393
        }
394
    }
395
396
    /**
397
     * Tear down tasks for this mock
398
     *
399
     * @return void
400
     */
401
    public function mockery_teardown()
402
    {
403
    }
404
405
    /**
406
     * Fetch the next available allocation order number
407
     *
408
     * @return int
409
     */
410
    public function mockery_allocateOrder()
411
    {
412
        $this->_mockery_allocatedOrder += 1;
413
        return $this->_mockery_allocatedOrder;
414
    }
415
416
    /**
417
     * Set ordering for a group
418
     *
419
     * @param mixed $group
420
     * @param int $order
421
     */
422
    public function mockery_setGroup($group, $order)
423
    {
424
        $this->_mockery_groups[$group] = $order;
425
    }
426
427
    /**
428
     * Fetch array of ordered groups
429
     *
430
     * @return array
431
     */
432
    public function mockery_getGroups()
433
    {
434
        return $this->_mockery_groups;
435
    }
436
437
    /**
438
     * Set current ordered number
439
     *
440
     * @param int $order
441
     */
442
    public function mockery_setCurrentOrder($order)
443
    {
444
        $this->_mockery_currentOrder = $order;
445
        return $this->_mockery_currentOrder;
446
    }
447
448
    /**
449
     * Get current ordered number
450
     *
451
     * @return int
452
     */
453
    public function mockery_getCurrentOrder()
454
    {
455
        return $this->_mockery_currentOrder;
456
    }
457
458
    /**
459
     * Validate the current mock's ordering
460
     *
461
     * @param string $method
462
     * @param int $order
463
     * @throws \Mockery\Exception
464
     * @return void
465
     */
466
    public function mockery_validateOrder($method, $order)
467
    {
468
        if ($order < $this->_mockery_currentOrder) {
469
            $exception = new \Mockery\Exception\InvalidOrderException(
470
                'Method ' . __CLASS__ . '::' . $method . '()'
471
                . ' called out of order: expected order '
472
                . $order . ', was ' . $this->_mockery_currentOrder
473
            );
474
            $exception->setMock($this)
475
                ->setMethodName($method)
476
                ->setExpectedOrder($order)
477
                ->setActualOrder($this->_mockery_currentOrder);
478
            throw $exception;
479
        }
480
        $this->mockery_setCurrentOrder($order);
481
    }
482
483
    /**
484
     * Gets the count of expectations for this mock
485
     *
486
     * @return int
487
     */
488
    public function mockery_getExpectationCount()
489
    {
490
        $count = $this->_mockery_expectations_count;
491
        foreach ($this->_mockery_expectations as $director) {
492
            $count += $director->getExpectationCount();
493
        }
494
        return $count;
495
    }
496
497
    /**
498
     * Return the expectations director for the given method
499
     *
500
     * @var string $method
501
     * @return \Mockery\ExpectationDirector|null
502
     */
503
    public function mockery_setExpectationsFor($method, \Mockery\ExpectationDirector $director)
504
    {
505
        $this->_mockery_expectations[$method] = $director;
506
    }
507
508
    /**
509
     * Return the expectations director for the given method
510
     *
511
     * @var string $method
512
     * @return \Mockery\ExpectationDirector|null
513
     */
514
    public function mockery_getExpectationsFor($method)
515
    {
516
        if (isset($this->_mockery_expectations[$method])) {
517
            return $this->_mockery_expectations[$method];
518
        }
519
    }
520
521
    /**
522
     * Find an expectation matching the given method and arguments
523
     *
524
     * @var string $method
525
     * @var array $args
526
     * @return \Mockery\Expectation|null
527
     */
528
    public function mockery_findExpectation($method, array $args)
529
    {
530
        if (!isset($this->_mockery_expectations[$method])) {
531
            return null;
532
        }
533
        $director = $this->_mockery_expectations[$method];
534
535
        return $director->findExpectation($args);
536
    }
537
538
    /**
539
     * Return the container for this mock
540
     *
541
     * @return \Mockery\Container
542
     */
543
    public function mockery_getContainer()
544
    {
545
        return $this->_mockery_container;
546
    }
547
548
    /**
549
     * Return the name for this mock
550
     *
551
     * @return string
552
     */
553
    public function mockery_getName()
554
    {
555
        return __CLASS__;
556
    }
557
558
    /**
559
     * @return array
560
     */
561
    public function mockery_getMockableProperties()
562
    {
563
        return $this->_mockery_mockableProperties;
564
    }
565
566
    public function __isset($name)
567
    {
568
        if (false === stripos($name, '_mockery_') && method_exists(get_parent_class($this), '__isset')) {
569
            return parent::__isset($name);
570
        }
571
572
        return false;
573
    }
574
575
    public function mockery_getExpectations()
576
    {
577
        return $this->_mockery_expectations;
578
    }
579
580
    /**
581
     * Calls a parent class method and returns the result. Used in a passthru
582
     * expectation where a real return value is required while still taking
583
     * advantage of expectation matching and call count verification.
584
     *
585
     * @param string $name
586
     * @param array $args
587
     * @return mixed
588
     */
589
    public function mockery_callSubjectMethod($name, array $args)
590
    {
591
        return call_user_func_array('parent::' . $name, $args);
592
    }
593
594
    /**
595
     * @return string[]
596
     */
597
    public function mockery_getMockableMethods()
598
    {
599
        return $this->_mockery_mockableMethods;
600
    }
601
602
    /**
603
     * @return bool
604
     */
605
    public function mockery_isAnonymous()
606
    {
607
        $rfc = new \ReflectionClass($this);
608
        $onlyImplementsMock = count($rfc->getInterfaces()) == 1;
609
        return (false === $rfc->getParentClass()) && $onlyImplementsMock;
610
    }
611
612
    public function __wakeup()
613
    {
614
        /**
615
         * This does not add __wakeup method support. It's a blind method and any
616
         * expected __wakeup work will NOT be performed. It merely cuts off
617
         * annoying errors where a __wakeup exists but is not essential when
618
         * mocking
619
         */
620
    }
621
622
    public function __destruct()
623
    {
624
        /**
625
         * Overrides real class destructor in case if class was created without original constructor
626
         */
627
    }
628
629
    public function mockery_getMethod($name)
630
    {
631
        foreach ($this->mockery_getMethods() as $method) {
632
            if ($method->getName() == $name) {
633
                return $method;
634
            }
635
        }
636
637
        return null;
638
    }
639
640
    /**
641
     * @param string $name Method name.
642
     *
643
     * @return mixed Generated return value based on the declared return value of the named method.
644
     */
645
    public function mockery_returnValueForMethod($name)
646
    {
647
        if (version_compare(PHP_VERSION, '7.0.0-dev') < 0) {
648
            return;
649
        }
650
651
        $rm = $this->mockery_getMethod($name);
652
        if (!$rm || !$rm->hasReturnType()) {
653
            return;
654
        }
655
656
        $type = (string) $rm->getReturnType();
657
        switch ($type) {
658
            case '':       return;
659
            case 'string': return '';
660
            case 'int':    return 0;
661
            case 'float':  return 0.0;
662
            case 'bool':   return false;
663
            case 'array':  return [];
664
665
            case 'callable':
666
            case 'Closure':
667
                return function () {
668
                };
669
670
            case 'Traversable':
671
            case 'Generator':
672
                // Remove eval() when minimum version >=5.5
673
                $generator = eval('return function () { yield; };');
674
                return $generator();
675
676
            case 'self':
677
                return \Mockery::mock($rm->getDeclaringClass()->getName());
678
679
            case 'void':
680
                return null;
681
682
            default:
683
                return \Mockery::mock($type);
684
        }
685
    }
686
687
    public function shouldHaveReceived($method = null, $args = null)
688
    {
689
        if ($method === null) {
690
            return new HigherOrderMessage($this, "shouldHaveReceived");
691
        }
692
693
        $expectation = new \Mockery\VerificationExpectation($this, $method);
694
        if (null !== $args) {
695
            $expectation->withArgs($args);
0 ignored issues
show
Documentation introduced by
$args is of type null, but the function expects a array|object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
696
        }
697
        $expectation->atLeast()->once();
698
        $director = new \Mockery\VerificationDirector($this->_mockery_getReceivedMethodCalls(), $expectation);
699
        $this->_mockery_expectations_count++;
700
        $director->verify();
701
        return $director;
702
    }
703
704
    public function shouldNotHaveReceived($method = null, $args = null)
705
    {
706
        if ($method === null) {
707
            return new HigherOrderMessage($this, "shouldNotHaveReceived");
708
        }
709
710
        $expectation = new \Mockery\VerificationExpectation($this, $method);
711
        if (null !== $args) {
712
            $expectation->withArgs($args);
0 ignored issues
show
Documentation introduced by
$args is of type null, but the function expects a array|object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
713
        }
714
        $expectation->never();
715
        $director = new \Mockery\VerificationDirector($this->_mockery_getReceivedMethodCalls(), $expectation);
716
        $this->_mockery_expectations_count++;
717
        $director->verify();
718
        return null;
719
    }
720
721
    protected static function _mockery_handleStaticMethodCall($method, array $args)
722
    {
723
        try {
724
            $associatedRealObject = \Mockery::fetchMock(__CLASS__);
725
            return $associatedRealObject->__call($method, $args);
726
        } catch (\BadMethodCallException $e) {
727
            throw new \BadMethodCallException(
728
                'Static method ' . $associatedRealObject->mockery_getName() . '::' . $method
729
                . '() does not exist on this mock object'
730
            );
731
        }
732
    }
733
734
    protected function _mockery_getReceivedMethodCalls()
735
    {
736
        return $this->_mockery_receivedMethodCalls ?: $this->_mockery_receivedMethodCalls = new \Mockery\ReceivedMethodCalls();
737
    }
738
739
    protected function _mockery_handleMethodCall($method, array $args)
740
    {
741
        $this->_mockery_getReceivedMethodCalls()->push(new \Mockery\MethodCall($method, $args));
742
743
        $rm = $this->mockery_getMethod($method);
744
        if ($rm && $rm->isProtected() && !$this->_mockery_allowMockingProtectedMethods) {
745
            if ($rm->isAbstract()) {
746
                return;
747
            }
748
749
            try {
750
                $prototype = $rm->getPrototype();
751
                if ($prototype->isAbstract()) {
752
                    return;
753
                }
754
            } catch (\ReflectionException $re) {
755
                // noop - there is no hasPrototype method
756
            }
757
758
            return call_user_func_array("parent::$method", $args);
759
        }
760
761
        if (isset($this->_mockery_expectations[$method])
762
        && !$this->_mockery_disableExpectationMatching) {
763
            $handler = $this->_mockery_expectations[$method];
764
765
            try {
766
                return $handler->call($args);
767
            } catch (\Mockery\Exception\NoMatchingExpectationException $e) {
768
                if (!$this->_mockery_ignoreMissing && !$this->_mockery_deferMissing) {
769
                    throw $e;
770
                }
771
            }
772
        }
773
774
        if (!is_null($this->_mockery_partial) && method_exists($this->_mockery_partial, $method)) {
775
            return call_user_func_array(array($this->_mockery_partial, $method), $args);
776
        } elseif ($this->_mockery_deferMissing && is_callable("parent::$method")
777
            && (!$this->hasMethodOverloadingInParentClass() || method_exists(get_parent_class($this), $method))) {
778
            return call_user_func_array("parent::$method", $args);
779
        } elseif ($method == '__toString') {
780
            // __toString is special because we force its addition to the class API regardless of the
781
            // original implementation.  Thus, we should always return a string rather than honor
782
            // _mockery_ignoreMissing and break the API with an error.
783
            return sprintf("%s#%s", __CLASS__, spl_object_hash($this));
784
        } elseif ($this->_mockery_ignoreMissing) {
785
            if (\Mockery::getConfiguration()->mockingNonExistentMethodsAllowed() || (method_exists($this->_mockery_partial, $method) || is_callable("parent::$method"))) {
786
                if ($this->_mockery_defaultReturnValue instanceof \Mockery\Undefined) {
787
                    return call_user_func_array(array($this->_mockery_defaultReturnValue, $method), $args);
788
                } elseif (null === $this->_mockery_defaultReturnValue) {
789
                    return $this->mockery_returnValueForMethod($method);
790
                } else {
791
                    return $this->_mockery_defaultReturnValue;
792
                }
793
            }
794
        }
795
796
        $message = 'Method ' . __CLASS__ . '::' . $method .
797
            '() does not exist on this mock object';
798
799
        if (!is_null($rm)) {
800
            $message = 'Received ' . __CLASS__ .
801
                '::' . $method . '(), but no expectations were specified';
802
        }
803
804
        throw new \BadMethodCallException(
805
            $message
806
        );
807
    }
808
809
    /**
810
     * Uses reflection to get the list of all
811
     * methods within the current mock object
812
     *
813
     * @return array
814
     */
815
    protected function mockery_getMethods()
816
    {
817
        if (static::$_mockery_methods) {
818
            return static::$_mockery_methods;
819
        }
820
821
        if (isset($this->_mockery_partial)) {
822
            $reflected = new \ReflectionObject($this->_mockery_partial);
823
        } else {
824
            $reflected = new \ReflectionClass($this);
825
        }
826
827
        return static::$_mockery_methods = $reflected->getMethods();
0 ignored issues
show
Documentation Bug introduced by
It seems like $reflected->getMethods() of type array<integer,object<ReflectionMethod>> is incompatible with the declared type array<integer,object<Mockery\ReflectionMethod>> of property $_mockery_methods.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
828
    }
829
830
    private function hasMethodOverloadingInParentClass()
831
    {
832
        // if there's __call any name would be callable
833
        return is_callable('parent::' . uniqid(__FUNCTION__));
834
    }
835
836
    /**
837
     * @return array
838
     */
839
    private function getNonPublicMethods()
840
    {
841
        return array_map(
842
            function ($method) {
843
                return $method->getName();
844
            },
845
            array_filter($this->mockery_getMethods(), function ($method) {
846
                return !$method->isPublic();
847
            })
848
        );
849
    }
850
}
851