Completed
Pull Request — master (#435)
by Tibor
01:25
created

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