Completed
Pull Request — master (#287)
by Sascha-Oliver
02:30
created

MethodProphecy::withArguments()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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