From d75b284013787497d1adc4a49b9eaa8e82071dcd Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Tue, 20 Jun 2023 17:08:04 +0200 Subject: [PATCH] Improve error handling for unsupported return types --- phpstan-baseline.neon | 5 -- spec/Prophecy/Prophecy/MethodProphecySpec.php | 54 +++++++++++++++++++ src/Prophecy/Prophecy/MethodProphecy.php | 13 ++++- 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6134c84da..ff18eff5f 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -35,11 +35,6 @@ parameters: count: 1 path: src/Prophecy/Doubler/Doubler.php - - - message: "#^Parameter \\#1 \\$classOrInterface of method Prophecy\\\\Prophet\\:\\:prophesize\\(\\) expects class\\-string\\\\|null, string given\\.$#" - count: 1 - path: src/Prophecy/Prophecy/MethodProphecy.php - - message: "#^Unable to resolve the template type T in call to method Prophecy\\\\Prophet\\:\\:prophesize\\(\\)$#" count: 1 diff --git a/spec/Prophecy/Prophecy/MethodProphecySpec.php b/spec/Prophecy/Prophecy/MethodProphecySpec.php index 4db9204eb..9b5bde6cd 100644 --- a/spec/Prophecy/Prophecy/MethodProphecySpec.php +++ b/spec/Prophecy/Prophecy/MethodProphecySpec.php @@ -7,6 +7,7 @@ use Prophecy\Argument; use Prophecy\Argument\ArgumentsWildcard; use Prophecy\Call\Call; +use Prophecy\Exception\Prophecy\MethodProphecyException; use Prophecy\Prediction\PredictionInterface; use Prophecy\Promise\CallbackPromise; use Prophecy\Promise\PromiseInterface; @@ -557,6 +558,59 @@ function it_returns_object_prophecy_for_object_scalar_union(ObjectProphecy $obje $return->shouldImplement(ProphecySubjectInterface::class); } + function it_returns_object_prophecy_for_object_return_type(ObjectProphecy $objectProphecy, ArgumentsWildcard $argumentsWildcard) + { + $this->generateMethodProphecyWithReturnValue($objectProphecy, 'foo', 'object'); + $this->beConstructedWith($objectProphecy, 'foo', $argumentsWildcard); + + $return = $this->getPromise()->execute([], $objectProphecy, $this); + $return->shouldImplement(ProphecySubjectInterface::class); + } + + function it_throws_for_non_existent_class_return_type(ObjectProphecy $objectProphecy, ArgumentsWildcard $argumentsWildcard) + { + $this->generateMethodProphecyWithReturnValue($objectProphecy, 'foo', 'NonExistentClass'); + $this->beConstructedWith($objectProphecy, 'foo', $argumentsWildcard); + + $return = $this->getPromise()->shouldThrow(MethodProphecyException::class)->during('execute', [[], $objectProphecy, $this]); + } + + function it_returns_true_prophecy_for_true_return_type(ObjectProphecy $objectProphecy, ArgumentsWildcard $argumentsWildcard) + { + if (\PHP_VERSION_ID < 80200) { + return; + } + + $this->generateMethodProphecyWithReturnValue($objectProphecy, 'foo', 'true'); + $this->beConstructedWith($objectProphecy, 'foo', $argumentsWildcard); + + $this->getPromise()->execute([], $objectProphecy, $this)->shouldBe(true); + } + + function it_returns_false_prophecy_for_false_return_type(ObjectProphecy $objectProphecy, ArgumentsWildcard $argumentsWildcard) + { + if (\PHP_VERSION_ID < 80200) { + return; + } + + $this->generateMethodProphecyWithReturnValue($objectProphecy, 'foo', 'false'); + $this->beConstructedWith($objectProphecy, 'foo', $argumentsWildcard); + + $this->getPromise()->execute([], $objectProphecy, $this)->shouldBe(false); + } + + function it_returns_null_prophecy_for_null_return_type(ObjectProphecy $objectProphecy, ArgumentsWildcard $argumentsWildcard) + { + if (\PHP_VERSION_ID < 80200) { + return; + } + + $this->generateMethodProphecyWithReturnValue($objectProphecy, 'foo', 'null'); + $this->beConstructedWith($objectProphecy, 'foo', $argumentsWildcard); + + $this->getPromise()->execute([], $objectProphecy, $this)->shouldBe(null); + } + private function generateMethodProphecyWithReturnValue($objectProphecy, string $methodName, string $returnType): void { $objectProphecy->reveal()->willReturn( diff --git a/src/Prophecy/Prophecy/MethodProphecy.php b/src/Prophecy/Prophecy/MethodProphecy.php index 96fc6b946..3dfd82de2 100644 --- a/src/Prophecy/Prophecy/MethodProphecy.php +++ b/src/Prophecy/Prophecy/MethodProphecy.php @@ -155,7 +155,7 @@ static function(string $type1, string $type2) { $this->voidReturnType = true; } - $this->will(function () use ($defaultType) { + $this->will(function ($args, ObjectProphecy $object, MethodProphecy $method) use ($defaultType) { switch ($defaultType) { case 'void': return; case 'string': return ''; @@ -163,6 +163,9 @@ static function(string $type1, string $type2) { case 'int': return 0; case 'bool': return false; case 'array': return array(); + case 'true': return true; + case 'false': return false; + case 'null': return null; case 'callable': case 'Closure': @@ -172,7 +175,15 @@ static function(string $type1, string $type2) { case 'Generator': return (function () { yield; })(); + case 'object': + $prophet = new Prophet; + return $prophet->prophesize()->reveal(); + default: + if (!class_exists($defaultType) && !interface_exists($defaultType)) { + throw new MethodProphecyException(sprintf('Cannot create a return value for the method as the type "%s" is not supported. Configure an explicit return value instead.', $defaultType), $method); + } + $prophet = new Prophet; return $prophet->prophesize($defaultType)->reveal(); }