Completed
Push — master ( 0734f6...062627 )
by Ciaran
01:25
created

MethodProphecy::shouldHaveBeenCalledOnce()   A

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 = (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
     * Sets return argument promise to the prophecy.
185
     *
186
     * @param int $index The zero-indexed number of the argument to return
187
     *
188
     * @see \Prophecy\Promise\ReturnArgumentPromise
189
     *
190
     * @return $this
191
     */
192
    public function willReturnArgument($index = 0)
193
    {
194
        if ($this->voidReturnType) {
195
            throw new MethodProphecyException("The method \"$this->methodName\" has a void return type", $this);
196
        }
197
198
        return $this->will(new Promise\ReturnArgumentPromise($index));
199
    }
200
201
    /**
202
     * Sets throw promise to the prophecy.
203
     *
204
     * @see \Prophecy\Promise\ThrowPromise
205
     *
206
     * @param string|\Exception $exception Exception class or instance
207
     *
208
     * @return $this
209
     */
210
    public function willThrow($exception)
211
    {
212
        return $this->will(new Promise\ThrowPromise($exception));
213
    }
214
215
    /**
216
     * Sets custom prediction to the prophecy.
217
     *
218
     * @param callable|Prediction\PredictionInterface $prediction
219
     *
220
     * @return $this
221
     *
222
     * @throws \Prophecy\Exception\InvalidArgumentException
223
     */
224 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...
225
    {
226
        if (is_callable($prediction)) {
227
            $prediction = new Prediction\CallbackPrediction($prediction);
228
        }
229
230
        if (!$prediction instanceof Prediction\PredictionInterface) {
231
            throw new InvalidArgumentException(sprintf(
232
                'Expected callable or instance of PredictionInterface, but got %s.',
233
                gettype($prediction)
234
            ));
235
        }
236
237
        $this->bindToObjectProphecy();
238
        $this->prediction = $prediction;
239
240
        return $this;
241
    }
242
243
    /**
244
     * Sets call prediction to the prophecy.
245
     *
246
     * @see \Prophecy\Prediction\CallPrediction
247
     *
248
     * @return $this
249
     */
250
    public function shouldBeCalled()
251
    {
252
        return $this->should(new Prediction\CallPrediction);
253
    }
254
255
    /**
256
     * Sets no calls prediction to the prophecy.
257
     *
258
     * @see \Prophecy\Prediction\NoCallsPrediction
259
     *
260
     * @return $this
261
     */
262
    public function shouldNotBeCalled()
263
    {
264
        return $this->should(new Prediction\NoCallsPrediction);
265
    }
266
267
    /**
268
     * Sets call times prediction to the prophecy.
269
     *
270
     * @see \Prophecy\Prediction\CallTimesPrediction
271
     *
272
     * @param $count
273
     *
274
     * @return $this
275
     */
276
    public function shouldBeCalledTimes($count)
277
    {
278
        return $this->should(new Prediction\CallTimesPrediction($count));
279
    }
280
281
    /**
282
     * Sets call times prediction to the prophecy.
283
     *
284
     * @see \Prophecy\Prediction\CallTimesPrediction
285
     *
286
     * @return $this
287
     */
288
    public function shouldBeCalledOnce()
289
    {
290
        return $this->shouldBeCalledTimes(1);
291
    }
292
293
    /**
294
     * Checks provided prediction immediately.
295
     *
296
     * @param callable|Prediction\PredictionInterface $prediction
297
     *
298
     * @return $this
299
     *
300
     * @throws \Prophecy\Exception\InvalidArgumentException
301
     */
302
    public function shouldHave($prediction)
303
    {
304
        if (is_callable($prediction)) {
305
            $prediction = new Prediction\CallbackPrediction($prediction);
306
        }
307
308
        if (!$prediction instanceof Prediction\PredictionInterface) {
309
            throw new InvalidArgumentException(sprintf(
310
                'Expected callable or instance of PredictionInterface, but got %s.',
311
                gettype($prediction)
312
            ));
313
        }
314
315
        if (null === $this->promise && !$this->voidReturnType) {
316
            $this->willReturn();
317
        }
318
319
        $calls = $this->getObjectProphecy()->findProphecyMethodCalls(
320
            $this->getMethodName(),
321
            $this->getArgumentsWildcard()
322
        );
323
324
        try {
325
            $prediction->check($calls, $this->getObjectProphecy(), $this);
326
            $this->checkedPredictions[] = $prediction;
327
        } catch (\Exception $e) {
328
            $this->checkedPredictions[] = $prediction;
329
330
            throw $e;
331
        }
332
333
        return $this;
334
    }
335
336
    /**
337
     * Checks call prediction.
338
     *
339
     * @see \Prophecy\Prediction\CallPrediction
340
     *
341
     * @return $this
342
     */
343
    public function shouldHaveBeenCalled()
344
    {
345
        return $this->shouldHave(new Prediction\CallPrediction);
346
    }
347
348
    /**
349
     * Checks no calls prediction.
350
     *
351
     * @see \Prophecy\Prediction\NoCallsPrediction
352
     *
353
     * @return $this
354
     */
355
    public function shouldNotHaveBeenCalled()
356
    {
357
        return $this->shouldHave(new Prediction\NoCallsPrediction);
358
    }
359
360
    /**
361
     * Checks no calls prediction.
362
     *
363
     * @see \Prophecy\Prediction\NoCallsPrediction
364
     * @deprecated
365
     *
366
     * @return $this
367
     */
368
    public function shouldNotBeenCalled()
369
    {
370
        return $this->shouldNotHaveBeenCalled();
371
    }
372
373
    /**
374
     * Checks call times prediction.
375
     *
376
     * @see \Prophecy\Prediction\CallTimesPrediction
377
     *
378
     * @param int $count
379
     *
380
     * @return $this
381
     */
382
    public function shouldHaveBeenCalledTimes($count)
383
    {
384
        return $this->shouldHave(new Prediction\CallTimesPrediction($count));
385
    }
386
387
    /**
388
     * Checks call times prediction.
389
     *
390
     * @see \Prophecy\Prediction\CallTimesPrediction
391
     *
392
     * @return $this
393
     */
394
    public function shouldHaveBeenCalledOnce()
395
    {
396
        return $this->shouldHaveBeenCalledTimes(1);
397
    }
398
399
    /**
400
     * Checks currently registered [with should(...)] prediction.
401
     */
402
    public function checkPrediction()
403
    {
404
        if (null === $this->prediction) {
405
            return;
406
        }
407
408
        $this->shouldHave($this->prediction);
409
    }
410
411
    /**
412
     * Returns currently registered promise.
413
     *
414
     * @return null|Promise\PromiseInterface
415
     */
416
    public function getPromise()
417
    {
418
        return $this->promise;
419
    }
420
421
    /**
422
     * Returns currently registered prediction.
423
     *
424
     * @return null|Prediction\PredictionInterface
425
     */
426
    public function getPrediction()
427
    {
428
        return $this->prediction;
429
    }
430
431
    /**
432
     * Returns predictions that were checked on this object.
433
     *
434
     * @return Prediction\PredictionInterface[]
435
     */
436
    public function getCheckedPredictions()
437
    {
438
        return $this->checkedPredictions;
439
    }
440
441
    /**
442
     * Returns object prophecy this method prophecy is tied to.
443
     *
444
     * @return ObjectProphecy
445
     */
446
    public function getObjectProphecy()
447
    {
448
        return $this->objectProphecy;
449
    }
450
451
    /**
452
     * Returns method name.
453
     *
454
     * @return string
455
     */
456
    public function getMethodName()
457
    {
458
        return $this->methodName;
459
    }
460
461
    /**
462
     * Returns arguments wildcard.
463
     *
464
     * @return Argument\ArgumentsWildcard
465
     */
466
    public function getArgumentsWildcard()
467
    {
468
        return $this->argumentsWildcard;
469
    }
470
471
    /**
472
     * @return bool
473
     */
474
    public function hasReturnVoid()
475
    {
476
        return $this->voidReturnType;
477
    }
478
479
    private function bindToObjectProphecy()
480
    {
481
        if ($this->bound) {
482
            return;
483
        }
484
485
        $this->getObjectProphecy()->addMethodProphecy($this);
486
        $this->bound = true;
487
    }
488
}
489