Completed
Pull Request — master (#476)
by Ciaran
04:26
created

MethodProphecy::__construct()   C

Complexity

Conditions 16
Paths 8

Size

Total Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 57
rs 5.5666
c 0
b 0
f 0
cc 16
nc 8
nop 3

How to fix   Long Method    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
/*
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 (true === $reflectedMethod->hasReturnType()) {
74
            $type = $reflectedMethod->getReturnType()->getName();
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
                        return (function () { yield; })();
96
97
                    default:
98
                        $prophet = new Prophet;
99
                        return $prophet->prophesize($type)->reveal();
100
                }
101
            });
102
        }
103
    }
104
105
    /**
106
     * Sets argument wildcard.
107
     *
108
     * @param array|Argument\ArgumentsWildcard $arguments
109
     *
110
     * @return $this
111
     *
112
     * @throws \Prophecy\Exception\InvalidArgumentException
113
     */
114
    public function withArguments($arguments)
115
    {
116
        if (is_array($arguments)) {
117
            $arguments = new Argument\ArgumentsWildcard($arguments);
118
        }
119
120
        if (!$arguments instanceof Argument\ArgumentsWildcard) {
121
            throw new InvalidArgumentException(sprintf(
122
                "Either an array or an instance of ArgumentsWildcard expected as\n".
123
                'a `MethodProphecy::withArguments()` argument, but got %s.',
124
                gettype($arguments)
125
            ));
126
        }
127
128
        $this->argumentsWildcard = $arguments;
129
130
        return $this;
131
    }
132
133
    /**
134
     * Sets custom promise to the prophecy.
135
     *
136
     * @param callable|Promise\PromiseInterface $promise
137
     *
138
     * @return $this
139
     *
140
     * @throws \Prophecy\Exception\InvalidArgumentException
141
     */
142 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...
143
    {
144
        if (is_callable($promise)) {
145
            $promise = new Promise\CallbackPromise($promise);
146
        }
147
148
        if (!$promise instanceof Promise\PromiseInterface) {
149
            throw new InvalidArgumentException(sprintf(
150
                'Expected callable or instance of PromiseInterface, but got %s.',
151
                gettype($promise)
152
            ));
153
        }
154
155
        $this->bindToObjectProphecy();
156
        $this->promise = $promise;
157
158
        return $this;
159
    }
160
161
    /**
162
     * Sets return promise to the prophecy.
163
     *
164
     * @see \Prophecy\Promise\ReturnPromise
165
     *
166
     * @return $this
167
     */
168
    public function willReturn()
169
    {
170
        if ($this->voidReturnType) {
171
            throw new MethodProphecyException(
172
                "The method \"$this->methodName\" has a void return type, and so cannot return anything",
173
                $this
174
            );
175
        }
176
177
        return $this->will(new Promise\ReturnPromise(func_get_args()));
178
    }
179
180
    /**
181
     * @param array $items
182
     *
183
     * @return $this
184
     *
185
     * @throws \Prophecy\Exception\InvalidArgumentException
186
     */
187
    public function willYield($items)
188
    {
189
        if ($this->voidReturnType) {
190
            throw new MethodProphecyException(
191
                "The method \"$this->methodName\" has a void return type, and so cannot yield anything",
192
                $this
193
            );
194
        }
195
196
        if (!is_array($items)) {
197
            throw new InvalidArgumentException(sprintf(
198
                'Expected array, but got %s.',
199
                gettype($items)
200
            ));
201
        }
202
203
        $generator =  function() use ($items) {
204
            foreach ($items as $key => $value) {
205
                yield $key => $value;
206
            }
207
        };
208
209
        return $this->will($generator);
210
    }
211
212
    /**
213
     * Sets return argument promise to the prophecy.
214
     *
215
     * @param int $index The zero-indexed number of the argument to return
216
     *
217
     * @see \Prophecy\Promise\ReturnArgumentPromise
218
     *
219
     * @return $this
220
     */
221
    public function willReturnArgument($index = 0)
222
    {
223
        if ($this->voidReturnType) {
224
            throw new MethodProphecyException("The method \"$this->methodName\" has a void return type", $this);
225
        }
226
227
        return $this->will(new Promise\ReturnArgumentPromise($index));
228
    }
229
230
    /**
231
     * Sets throw promise to the prophecy.
232
     *
233
     * @see \Prophecy\Promise\ThrowPromise
234
     *
235
     * @param string|\Exception $exception Exception class or instance
236
     *
237
     * @return $this
238
     */
239
    public function willThrow($exception)
240
    {
241
        return $this->will(new Promise\ThrowPromise($exception));
242
    }
243
244
    /**
245
     * Sets custom prediction to the prophecy.
246
     *
247
     * @param callable|Prediction\PredictionInterface $prediction
248
     *
249
     * @return $this
250
     *
251
     * @throws \Prophecy\Exception\InvalidArgumentException
252
     */
253 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...
254
    {
255
        if (is_callable($prediction)) {
256
            $prediction = new Prediction\CallbackPrediction($prediction);
257
        }
258
259
        if (!$prediction instanceof Prediction\PredictionInterface) {
260
            throw new InvalidArgumentException(sprintf(
261
                'Expected callable or instance of PredictionInterface, but got %s.',
262
                gettype($prediction)
263
            ));
264
        }
265
266
        $this->bindToObjectProphecy();
267
        $this->prediction = $prediction;
268
269
        return $this;
270
    }
271
272
    /**
273
     * Sets call prediction to the prophecy.
274
     *
275
     * @see \Prophecy\Prediction\CallPrediction
276
     *
277
     * @return $this
278
     */
279
    public function shouldBeCalled()
280
    {
281
        return $this->should(new Prediction\CallPrediction);
282
    }
283
284
    /**
285
     * Sets no calls prediction to the prophecy.
286
     *
287
     * @see \Prophecy\Prediction\NoCallsPrediction
288
     *
289
     * @return $this
290
     */
291
    public function shouldNotBeCalled()
292
    {
293
        return $this->should(new Prediction\NoCallsPrediction);
294
    }
295
296
    /**
297
     * Sets call times prediction to the prophecy.
298
     *
299
     * @see \Prophecy\Prediction\CallTimesPrediction
300
     *
301
     * @param $count
302
     *
303
     * @return $this
304
     */
305
    public function shouldBeCalledTimes($count)
306
    {
307
        return $this->should(new Prediction\CallTimesPrediction($count));
308
    }
309
310
    /**
311
     * Sets call times prediction to the prophecy.
312
     *
313
     * @see \Prophecy\Prediction\CallTimesPrediction
314
     *
315
     * @return $this
316
     */
317
    public function shouldBeCalledOnce()
318
    {
319
        return $this->shouldBeCalledTimes(1);
320
    }
321
322
    /**
323
     * Checks provided prediction immediately.
324
     *
325
     * @param callable|Prediction\PredictionInterface $prediction
326
     *
327
     * @return $this
328
     *
329
     * @throws \Prophecy\Exception\InvalidArgumentException
330
     */
331
    public function shouldHave($prediction)
332
    {
333
        if (is_callable($prediction)) {
334
            $prediction = new Prediction\CallbackPrediction($prediction);
335
        }
336
337
        if (!$prediction instanceof Prediction\PredictionInterface) {
338
            throw new InvalidArgumentException(sprintf(
339
                'Expected callable or instance of PredictionInterface, but got %s.',
340
                gettype($prediction)
341
            ));
342
        }
343
344
        if (null === $this->promise && !$this->voidReturnType) {
345
            $this->willReturn();
346
        }
347
348
        $calls = $this->getObjectProphecy()->findProphecyMethodCalls(
349
            $this->getMethodName(),
350
            $this->getArgumentsWildcard()
351
        );
352
353
        try {
354
            $prediction->check($calls, $this->getObjectProphecy(), $this);
355
            $this->checkedPredictions[] = $prediction;
356
        } catch (\Exception $e) {
357
            $this->checkedPredictions[] = $prediction;
358
359
            throw $e;
360
        }
361
362
        return $this;
363
    }
364
365
    /**
366
     * Checks call prediction.
367
     *
368
     * @see \Prophecy\Prediction\CallPrediction
369
     *
370
     * @return $this
371
     */
372
    public function shouldHaveBeenCalled()
373
    {
374
        return $this->shouldHave(new Prediction\CallPrediction);
375
    }
376
377
    /**
378
     * Checks no calls prediction.
379
     *
380
     * @see \Prophecy\Prediction\NoCallsPrediction
381
     *
382
     * @return $this
383
     */
384
    public function shouldNotHaveBeenCalled()
385
    {
386
        return $this->shouldHave(new Prediction\NoCallsPrediction);
387
    }
388
389
    /**
390
     * Checks no calls prediction.
391
     *
392
     * @see \Prophecy\Prediction\NoCallsPrediction
393
     * @deprecated
394
     *
395
     * @return $this
396
     */
397
    public function shouldNotBeenCalled()
398
    {
399
        return $this->shouldNotHaveBeenCalled();
400
    }
401
402
    /**
403
     * Checks call times prediction.
404
     *
405
     * @see \Prophecy\Prediction\CallTimesPrediction
406
     *
407
     * @param int $count
408
     *
409
     * @return $this
410
     */
411
    public function shouldHaveBeenCalledTimes($count)
412
    {
413
        return $this->shouldHave(new Prediction\CallTimesPrediction($count));
414
    }
415
416
    /**
417
     * Checks call times prediction.
418
     *
419
     * @see \Prophecy\Prediction\CallTimesPrediction
420
     *
421
     * @return $this
422
     */
423
    public function shouldHaveBeenCalledOnce()
424
    {
425
        return $this->shouldHaveBeenCalledTimes(1);
426
    }
427
428
    /**
429
     * Checks currently registered [with should(...)] prediction.
430
     */
431
    public function checkPrediction()
432
    {
433
        if (null === $this->prediction) {
434
            return;
435
        }
436
437
        $this->shouldHave($this->prediction);
438
    }
439
440
    /**
441
     * Returns currently registered promise.
442
     *
443
     * @return null|Promise\PromiseInterface
444
     */
445
    public function getPromise()
446
    {
447
        return $this->promise;
448
    }
449
450
    /**
451
     * Returns currently registered prediction.
452
     *
453
     * @return null|Prediction\PredictionInterface
454
     */
455
    public function getPrediction()
456
    {
457
        return $this->prediction;
458
    }
459
460
    /**
461
     * Returns predictions that were checked on this object.
462
     *
463
     * @return Prediction\PredictionInterface[]
464
     */
465
    public function getCheckedPredictions()
466
    {
467
        return $this->checkedPredictions;
468
    }
469
470
    /**
471
     * Returns object prophecy this method prophecy is tied to.
472
     *
473
     * @return ObjectProphecy
474
     */
475
    public function getObjectProphecy()
476
    {
477
        return $this->objectProphecy;
478
    }
479
480
    /**
481
     * Returns method name.
482
     *
483
     * @return string
484
     */
485
    public function getMethodName()
486
    {
487
        return $this->methodName;
488
    }
489
490
    /**
491
     * Returns arguments wildcard.
492
     *
493
     * @return Argument\ArgumentsWildcard
494
     */
495
    public function getArgumentsWildcard()
496
    {
497
        return $this->argumentsWildcard;
498
    }
499
500
    /**
501
     * @return bool
502
     */
503
    public function hasReturnVoid()
504
    {
505
        return $this->voidReturnType;
506
    }
507
508
    private function bindToObjectProphecy()
509
    {
510
        if ($this->bound) {
511
            return;
512
        }
513
514
        $this->getObjectProphecy()->addMethodProphecy($this);
515
        $this->bound = true;
516
    }
517
}
518