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