Completed
Push — master ( a8acfc...5bbe1c )
by Konstantin
8s
created

MethodProphecy::hasReturnVoid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
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
                return;
79
            }
80
81
            $this->will(function () use ($type) {
82
                switch ($type) {
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
     * Checks provided prediction immediately.
283
     *
284
     * @param callable|Prediction\PredictionInterface $prediction
285
     *
286
     * @return $this
287
     *
288
     * @throws \Prophecy\Exception\InvalidArgumentException
289
     */
290
    public function shouldHave($prediction)
291
    {
292
        if (is_callable($prediction)) {
293
            $prediction = new Prediction\CallbackPrediction($prediction);
294
        }
295
296
        if (!$prediction instanceof Prediction\PredictionInterface) {
297
            throw new InvalidArgumentException(sprintf(
298
                'Expected callable or instance of PredictionInterface, but got %s.',
299
                gettype($prediction)
300
            ));
301
        }
302
303
        if (null === $this->promise && !$this->voidReturnType) {
304
            $this->willReturn();
305
        }
306
307
        $calls = $this->getObjectProphecy()->findProphecyMethodCalls(
308
            $this->getMethodName(),
309
            $this->getArgumentsWildcard()
310
        );
311
312
        try {
313
            $prediction->check($calls, $this->getObjectProphecy(), $this);
314
            $this->checkedPredictions[] = $prediction;
315
        } catch (\Exception $e) {
316
            $this->checkedPredictions[] = $prediction;
317
318
            throw $e;
319
        }
320
321
        return $this;
322
    }
323
324
    /**
325
     * Checks call prediction.
326
     *
327
     * @see Prophecy\Prediction\CallPrediction
328
     *
329
     * @return $this
330
     */
331
    public function shouldHaveBeenCalled()
332
    {
333
        return $this->shouldHave(new Prediction\CallPrediction);
334
    }
335
336
    /**
337
     * Checks no calls prediction.
338
     *
339
     * @see Prophecy\Prediction\NoCallsPrediction
340
     *
341
     * @return $this
342
     */
343
    public function shouldNotHaveBeenCalled()
344
    {
345
        return $this->shouldHave(new Prediction\NoCallsPrediction);
346
    }
347
348
    /**
349
     * Checks no calls prediction.
350
     *
351
     * @see Prophecy\Prediction\NoCallsPrediction
352
     * @deprecated
353
     *
354
     * @return $this
355
     */
356
    public function shouldNotBeenCalled()
357
    {
358
        return $this->shouldNotHaveBeenCalled();
359
    }
360
361
    /**
362
     * Checks call times prediction.
363
     *
364
     * @see Prophecy\Prediction\CallTimesPrediction
365
     *
366
     * @param int $count
367
     *
368
     * @return $this
369
     */
370
    public function shouldHaveBeenCalledTimes($count)
371
    {
372
        return $this->shouldHave(new Prediction\CallTimesPrediction($count));
373
    }
374
375
    /**
376
     * Checks currently registered [with should(...)] prediction.
377
     */
378
    public function checkPrediction()
379
    {
380
        if (null === $this->prediction) {
381
            return;
382
        }
383
384
        $this->shouldHave($this->prediction);
385
    }
386
387
    /**
388
     * Returns currently registered promise.
389
     *
390
     * @return null|Promise\PromiseInterface
391
     */
392
    public function getPromise()
393
    {
394
        return $this->promise;
395
    }
396
397
    /**
398
     * Returns currently registered prediction.
399
     *
400
     * @return null|Prediction\PredictionInterface
401
     */
402
    public function getPrediction()
403
    {
404
        return $this->prediction;
405
    }
406
407
    /**
408
     * Returns predictions that were checked on this object.
409
     *
410
     * @return Prediction\PredictionInterface[]
411
     */
412
    public function getCheckedPredictions()
413
    {
414
        return $this->checkedPredictions;
415
    }
416
417
    /**
418
     * Returns object prophecy this method prophecy is tied to.
419
     *
420
     * @return ObjectProphecy
421
     */
422
    public function getObjectProphecy()
423
    {
424
        return $this->objectProphecy;
425
    }
426
427
    /**
428
     * Returns method name.
429
     *
430
     * @return string
431
     */
432
    public function getMethodName()
433
    {
434
        return $this->methodName;
435
    }
436
437
    /**
438
     * Returns arguments wildcard.
439
     *
440
     * @return Argument\ArgumentsWildcard
441
     */
442
    public function getArgumentsWildcard()
443
    {
444
        return $this->argumentsWildcard;
445
    }
446
447
    /**
448
     * @return bool
449
     */
450
    public function hasReturnVoid()
451
    {
452
        return $this->voidReturnType;
453
    }
454
455
    private function bindToObjectProphecy()
456
    {
457
        if ($this->bound) {
458
            return;
459
        }
460
461
        $this->getObjectProphecy()->addMethodProphecy($this);
462
        $this->bound = true;
463
    }
464
}
465