MethodProphecy::shouldBeCalledOnce()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
/*
4
 * This file is part of the Prophecy.
5
 * (c) Konstantin Kudryashov <[email protected]>
6
 *     Marcello Duarte <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Prophecy\Prophecy;
13
14
use Prophecy\Argument;
15
use Prophecy\Prophet;
16
use Prophecy\Promise;
17
use Prophecy\Prediction;
18
use Prophecy\Exception\Doubler\MethodNotFoundException;
19
use Prophecy\Exception\InvalidArgumentException;
20
use Prophecy\Exception\Prophecy\MethodProphecyException;
21
22
/**
23
 * Method prophecy.
24
 *
25
 * @author Konstantin Kudryashov <[email protected]>
26
 */
27
class MethodProphecy
28
{
29
    private $objectProphecy;
30
    private $methodName;
31
    private $argumentsWildcard;
32
    private $promise;
33
    private $prediction;
34
    private $checkedPredictions = array();
35
    private $bound = false;
36
    private $voidReturnType = false;
37
38
    /**
39
     * Initializes method prophecy.
40
     *
41
     * @param ObjectProphecy                        $objectProphecy
42
     * @param string                                $methodName
43
     * @param null|Argument\ArgumentsWildcard|array $arguments
44
     *
45
     * @throws \Prophecy\Exception\Doubler\MethodNotFoundException If method not found
46
     */
47
    public function __construct(ObjectProphecy $objectProphecy, $methodName, $arguments = null)
48
    {
49
        $double = $objectProphecy->reveal();
50
        if (!method_exists($double, $methodName)) {
51
            throw new MethodNotFoundException(sprintf(
52
                'Method `%s::%s()` is not defined.', get_class($double), $methodName
53
            ), get_class($double), $methodName, $arguments);
0 ignored issues
show
Bug introduced by
It seems like $arguments defined by parameter $arguments on line 47 can also be of type object<Prophecy\Argument\ArgumentsWildcard>; however, Prophecy\Exception\Doubl...xception::__construct() does only seem to accept null|object<Prophecy\Exc...rgumentsWildcard>|array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
54
        }
55
56
        $this->objectProphecy = $objectProphecy;
57
        $this->methodName     = $methodName;
58
59
        $reflectedMethod = new \ReflectionMethod($double, $methodName);
60
        if ($reflectedMethod->isFinal()) {
61
            throw new MethodProphecyException(sprintf(
62
                "Can not add prophecy for a method `%s::%s()`\n".
63
                "as it is a final method.",
64
                get_class($double),
65
                $methodName
66
            ), $this);
67
        }
68
69
        if (null !== $arguments) {
70
            $this->withArguments($arguments);
71
        }
72
73
        if (version_compare(PHP_VERSION, '7.0', '>=') && true === $reflectedMethod->hasReturnType()) {
74
            $type = PHP_VERSION_ID >= 70100 ? $reflectedMethod->getReturnType()->getName() : (string) $reflectedMethod->getReturnType();
75
76
            if ('void' === $type) {
77
                $this->voidReturnType = true;
78
            }
79
80
            $this->will(function () use ($type) {
81
                switch ($type) {
82
                    case 'void': return;
83
                    case 'string': return '';
84
                    case 'float':  return 0.0;
85
                    case 'int':    return 0;
86
                    case 'bool':   return false;
87
                    case 'array':  return array();
88
89
                    case 'callable':
90
                    case 'Closure':
91
                        return function () {};
92
93
                    case 'Traversable':
94
                    case 'Generator':
95
                        // Remove eval() when minimum version >=5.5
96
                        /** @var callable $generator */
97
                        $generator = eval('return function () { yield; };');
98
                        return $generator();
99
100
                    default:
101
                        $prophet = new Prophet;
102
                        return $prophet->prophesize($type)->reveal();
103
                }
104
            });
105
        }
106
    }
107
108
    /**
109
     * Sets argument wildcard.
110
     *
111
     * @param array|Argument\ArgumentsWildcard $arguments
112
     *
113
     * @return $this
114
     *
115
     * @throws \Prophecy\Exception\InvalidArgumentException
116
     */
117
    public function withArguments($arguments)
118
    {
119
        if (is_array($arguments)) {
120
            $arguments = new Argument\ArgumentsWildcard($arguments);
121
        }
122
123
        if (!$arguments instanceof Argument\ArgumentsWildcard) {
124
            throw new InvalidArgumentException(sprintf(
125
                "Either an array or an instance of ArgumentsWildcard expected as\n".
126
                'a `MethodProphecy::withArguments()` argument, but got %s.',
127
                gettype($arguments)
128
            ));
129
        }
130
131
        $this->argumentsWildcard = $arguments;
132
133
        return $this;
134
    }
135
136
    /**
137
     * Sets custom promise to the prophecy.
138
     *
139
     * @param callable|Promise\PromiseInterface $promise
140
     *
141
     * @return $this
142
     *
143
     * @throws \Prophecy\Exception\InvalidArgumentException
144
     */
145 View Code Duplication
    public function will($promise)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146
    {
147
        if (is_callable($promise)) {
148
            $promise = new Promise\CallbackPromise($promise);
149
        }
150
151
        if (!$promise instanceof Promise\PromiseInterface) {
152
            throw new InvalidArgumentException(sprintf(
153
                'Expected callable or instance of PromiseInterface, but got %s.',
154
                gettype($promise)
155
            ));
156
        }
157
158
        $this->bindToObjectProphecy();
159
        $this->promise = $promise;
160
161
        return $this;
162
    }
163
164
    /**
165
     * Sets return promise to the prophecy.
166
     *
167
     * @see \Prophecy\Promise\ReturnPromise
168
     *
169
     * @return $this
170
     */
171
    public function willReturn()
172
    {
173
        if ($this->voidReturnType) {
174
            throw new MethodProphecyException(
175
                "The method \"$this->methodName\" has a void return type, and so cannot return anything",
176
                $this
177
            );
178
        }
179
180
        return $this->will(new Promise\ReturnPromise(func_get_args()));
181
    }
182
183
    /**
184
     * @param array $items
185
     *
186
     * @return $this
187
     *
188
     * @throws \Prophecy\Exception\InvalidArgumentException
189
     */
190
    public function willYield($items)
191
    {
192
        if ($this->voidReturnType) {
193
            throw new MethodProphecyException(
194
                "The method \"$this->methodName\" has a void return type, and so cannot yield anything",
195
                $this
196
            );
197
        }
198
199
        if (!is_array($items)) {
200
            throw new InvalidArgumentException(sprintf(
201
                'Expected array, but got %s.',
202
                gettype($items)
203
            ));
204
        }
205
206
        // Remove eval() when minimum version >=5.5
207
        /** @var callable $generator */
208
        $generator = eval('return function() use ($items) {
209
            foreach ($items as $key => $value) {
210
                yield $key => $value;
211
            }
212
        };');
213
214
        return $this->will($generator);
215
    }
216
217
    /**
218
     * Sets return argument promise to the prophecy.
219
     *
220
     * @param int $index The zero-indexed number of the argument to return
221
     *
222
     * @see \Prophecy\Promise\ReturnArgumentPromise
223
     *
224
     * @return $this
225
     */
226
    public function willReturnArgument($index = 0)
227
    {
228
        if ($this->voidReturnType) {
229
            throw new MethodProphecyException("The method \"$this->methodName\" has a void return type", $this);
230
        }
231
232
        return $this->will(new Promise\ReturnArgumentPromise($index));
233
    }
234
235
    /**
236
     * Sets throw promise to the prophecy.
237
     *
238
     * @see \Prophecy\Promise\ThrowPromise
239
     *
240
     * @param string|\Exception $exception Exception class or instance
241
     *
242
     * @return $this
243
     */
244
    public function willThrow($exception)
245
    {
246
        return $this->will(new Promise\ThrowPromise($exception));
247
    }
248
249
    /**
250
     * Sets custom prediction to the prophecy.
251
     *
252
     * @param callable|Prediction\PredictionInterface $prediction
253
     *
254
     * @return $this
255
     *
256
     * @throws \Prophecy\Exception\InvalidArgumentException
257
     */
258 View Code Duplication
    public function should($prediction)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
259
    {
260
        if (is_callable($prediction)) {
261
            $prediction = new Prediction\CallbackPrediction($prediction);
262
        }
263
264
        if (!$prediction instanceof Prediction\PredictionInterface) {
265
            throw new InvalidArgumentException(sprintf(
266
                'Expected callable or instance of PredictionInterface, but got %s.',
267
                gettype($prediction)
268
            ));
269
        }
270
271
        $this->bindToObjectProphecy();
272
        $this->prediction = $prediction;
273
274
        return $this;
275
    }
276
277
    /**
278
     * Sets call prediction to the prophecy.
279
     *
280
     * @see \Prophecy\Prediction\CallPrediction
281
     *
282
     * @return $this
283
     */
284
    public function shouldBeCalled()
285
    {
286
        return $this->should(new Prediction\CallPrediction);
287
    }
288
289
    /**
290
     * Sets no calls prediction to the prophecy.
291
     *
292
     * @see \Prophecy\Prediction\NoCallsPrediction
293
     *
294
     * @return $this
295
     */
296
    public function shouldNotBeCalled()
297
    {
298
        return $this->should(new Prediction\NoCallsPrediction);
299
    }
300
301
    /**
302
     * Sets call times prediction to the prophecy.
303
     *
304
     * @see \Prophecy\Prediction\CallTimesPrediction
305
     *
306
     * @param $count
307
     *
308
     * @return $this
309
     */
310
    public function shouldBeCalledTimes($count)
311
    {
312
        return $this->should(new Prediction\CallTimesPrediction($count));
313
    }
314
315
    /**
316
     * Sets call times prediction to the prophecy.
317
     *
318
     * @see \Prophecy\Prediction\CallTimesPrediction
319
     *
320
     * @return $this
321
     */
322
    public function shouldBeCalledOnce()
323
    {
324
        return $this->shouldBeCalledTimes(1);
325
    }
326
327
    /**
328
     * Checks provided prediction immediately.
329
     *
330
     * @param callable|Prediction\PredictionInterface $prediction
331
     *
332
     * @return $this
333
     *
334
     * @throws \Prophecy\Exception\InvalidArgumentException
335
     */
336
    public function shouldHave($prediction)
337
    {
338
        if (is_callable($prediction)) {
339
            $prediction = new Prediction\CallbackPrediction($prediction);
340
        }
341
342
        if (!$prediction instanceof Prediction\PredictionInterface) {
343
            throw new InvalidArgumentException(sprintf(
344
                'Expected callable or instance of PredictionInterface, but got %s.',
345
                gettype($prediction)
346
            ));
347
        }
348
349
        if (null === $this->promise && !$this->voidReturnType) {
350
            $this->willReturn();
351
        }
352
353
        $calls = $this->getObjectProphecy()->findProphecyMethodCalls(
354
            $this->getMethodName(),
355
            $this->getArgumentsWildcard()
356
        );
357
358
        try {
359
            $prediction->check($calls, $this->getObjectProphecy(), $this);
360
            $this->checkedPredictions[] = $prediction;
361
        } catch (\Exception $e) {
362
            $this->checkedPredictions[] = $prediction;
363
364
            throw $e;
365
        }
366
367
        return $this;
368
    }
369
370
    /**
371
     * Checks call prediction.
372
     *
373
     * @see \Prophecy\Prediction\CallPrediction
374
     *
375
     * @return $this
376
     */
377
    public function shouldHaveBeenCalled()
378
    {
379
        return $this->shouldHave(new Prediction\CallPrediction);
380
    }
381
382
    /**
383
     * Checks no calls prediction.
384
     *
385
     * @see \Prophecy\Prediction\NoCallsPrediction
386
     *
387
     * @return $this
388
     */
389
    public function shouldNotHaveBeenCalled()
390
    {
391
        return $this->shouldHave(new Prediction\NoCallsPrediction);
392
    }
393
394
    /**
395
     * Checks no calls prediction.
396
     *
397
     * @see \Prophecy\Prediction\NoCallsPrediction
398
     * @deprecated
399
     *
400
     * @return $this
401
     */
402
    public function shouldNotBeenCalled()
403
    {
404
        return $this->shouldNotHaveBeenCalled();
405
    }
406
407
    /**
408
     * Checks call times prediction.
409
     *
410
     * @see \Prophecy\Prediction\CallTimesPrediction
411
     *
412
     * @param int $count
413
     *
414
     * @return $this
415
     */
416
    public function shouldHaveBeenCalledTimes($count)
417
    {
418
        return $this->shouldHave(new Prediction\CallTimesPrediction($count));
419
    }
420
421
    /**
422
     * Checks call times prediction.
423
     *
424
     * @see \Prophecy\Prediction\CallTimesPrediction
425
     *
426
     * @return $this
427
     */
428
    public function shouldHaveBeenCalledOnce()
429
    {
430
        return $this->shouldHaveBeenCalledTimes(1);
431
    }
432
433
    /**
434
     * Checks currently registered [with should(...)] prediction.
435
     */
436
    public function checkPrediction()
437
    {
438
        if (null === $this->prediction) {
439
            return;
440
        }
441
442
        $this->shouldHave($this->prediction);
443
    }
444
445
    /**
446
     * Returns currently registered promise.
447
     *
448
     * @return null|Promise\PromiseInterface
449
     */
450
    public function getPromise()
451
    {
452
        return $this->promise;
453
    }
454
455
    /**
456
     * Returns currently registered prediction.
457
     *
458
     * @return null|Prediction\PredictionInterface
459
     */
460
    public function getPrediction()
461
    {
462
        return $this->prediction;
463
    }
464
465
    /**
466
     * Returns predictions that were checked on this object.
467
     *
468
     * @return Prediction\PredictionInterface[]
469
     */
470
    public function getCheckedPredictions()
471
    {
472
        return $this->checkedPredictions;
473
    }
474
475
    /**
476
     * Returns object prophecy this method prophecy is tied to.
477
     *
478
     * @return ObjectProphecy
479
     */
480
    public function getObjectProphecy()
481
    {
482
        return $this->objectProphecy;
483
    }
484
485
    /**
486
     * Returns method name.
487
     *
488
     * @return string
489
     */
490
    public function getMethodName()
491
    {
492
        return $this->methodName;
493
    }
494
495
    /**
496
     * Returns arguments wildcard.
497
     *
498
     * @return Argument\ArgumentsWildcard
499
     */
500
    public function getArgumentsWildcard()
501
    {
502
        return $this->argumentsWildcard;
503
    }
504
505
    /**
506
     * @return bool
507
     */
508
    public function hasReturnVoid()
509
    {
510
        return $this->voidReturnType;
511
    }
512
513
    private function bindToObjectProphecy()
514
    {
515
        if ($this->bound) {
516
            return;
517
        }
518
519
        $this->getObjectProphecy()->addMethodProphecy($this);
520
        $this->bound = true;
521
    }
522
}
523