From 7da6080c3d7cf22705d6419c709d145b95172792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Fri, 27 Dec 2019 23:46:45 +0100 Subject: [PATCH] Enhancement: Add support for willImplement() --- CHANGELOG.md | 5 ++ ...ophecyRevealDynamicReturnTypeExtension.php | 11 +-- ...illImplementDynamicReturnTypeExtension.php | 68 +++++++++++++++++++ src/Type/ObjectProphecyType.php | 31 +++++++-- src/extension.neon | 5 ++ tests/Model/Bar.php | 10 +++ tests/Model/BaseModel.php | 5 ++ tests/Model/Foo.php | 10 +++ tests/Test/BaseModelTest.php | 18 +++++ 9 files changed, 152 insertions(+), 11 deletions(-) create mode 100644 src/Extension/ObjectProphecyWillImplementDynamicReturnTypeExtension.php create mode 100644 tests/Model/Bar.php create mode 100644 tests/Model/Foo.php diff --git a/CHANGELOG.md b/CHANGELOG.md index f5b417c..575456c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), For a full diff see [`0.5.1...master`][0.5.1...master]. +### Added + +* Support for `willImplement()` ([#92]), by [@localheinz] + ## [`0.5.1`][0.5.1] For a full diff see [`0.5.0...0.5.1`][0.5.0...0.5.1]. @@ -81,6 +85,7 @@ For a full diff see [`afd6fd9...0.1`][afd6fd9...0.1]. [#67]: https://github.com/Jan0707/phpstan-prophecy/pull/67 [#79]: https://github.com/Jan0707/phpstan-prophecy/pull/79 +[#92]: https://github.com/Jan0707/phpstan-prophecy/pull/92 [@localheinz]: https://github.com/localheinz [@PedroTroller]: https://github.com/PedroTroller diff --git a/src/Extension/ObjectProphecyRevealDynamicReturnTypeExtension.php b/src/Extension/ObjectProphecyRevealDynamicReturnTypeExtension.php index 2cf639f..3a4a336 100644 --- a/src/Extension/ObjectProphecyRevealDynamicReturnTypeExtension.php +++ b/src/Extension/ObjectProphecyRevealDynamicReturnTypeExtension.php @@ -34,9 +34,12 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return $parametersAcceptor->getReturnType(); } - return TypeCombinator::intersect( - new ObjectType($calledOnType->getProphesizedClass()), - $parametersAcceptor->getReturnType() - ); + $types = \array_map(static function (string $class): ObjectType { + return new ObjectType($class); + }, $calledOnType->getProphesizedClasses()); + + $types[] = $parametersAcceptor->getReturnType(); + + return TypeCombinator::intersect(...$types); } } diff --git a/src/Extension/ObjectProphecyWillImplementDynamicReturnTypeExtension.php b/src/Extension/ObjectProphecyWillImplementDynamicReturnTypeExtension.php new file mode 100644 index 0000000..eebcb2c --- /dev/null +++ b/src/Extension/ObjectProphecyWillImplementDynamicReturnTypeExtension.php @@ -0,0 +1,68 @@ +getName(); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants()); + + $calledOnType = $scope->getType($methodCall->var); + + $prophecyType = $parametersAcceptor->getReturnType(); + + if (!$calledOnType instanceof ObjectProphecyType) { + return $prophecyType; + } + + if (0 === \count($methodCall->args)) { + return $prophecyType; + } + + $argType = $scope->getType($methodCall->args[0]->value); + + if (!($argType instanceof ConstantStringType)) { + return $prophecyType; + } + + $class = $argType->getValue(); + + if (!($prophecyType instanceof TypeWithClassName)) { + throw new ShouldNotHappenException(); + } + + if ('static' === $class) { + return $prophecyType; + } + + if ('self' === $class && null !== $scope->getClassReflection()) { + $class = $scope->getClassReflection()->getName(); + } + + $calledOnType->addProphesizedClass($class); + + return $calledOnType; + } +} diff --git a/src/Type/ObjectProphecyType.php b/src/Type/ObjectProphecyType.php index 0b286af..eb32e78 100644 --- a/src/Type/ObjectProphecyType.php +++ b/src/Type/ObjectProphecyType.php @@ -9,29 +9,46 @@ class ObjectProphecyType extends ObjectType { /** - * @var string + * @var string[] */ - protected $prophesizedClass; + protected $prophesizedClasses; public function __construct(string $prophesizedClass) { - $this->prophesizedClass = $prophesizedClass; + $this->prophesizedClasses = [ + $prophesizedClass, + ]; parent::__construct('Prophecy\Prophecy\ObjectProphecy'); } public static function __set_state(array $properties): Type { - return new self($properties['prophesizedClass']); + return new self($properties['prophesizedClasses']); } public function describe(VerbosityLevel $level): string { - return \sprintf('%s<%s>', parent::describe($level), $this->prophesizedClass); + return \sprintf( + '%s<%s>', + parent::describe($level), + \implode( + '&', + $this->prophesizedClasses + ) + ); } - public function getProphesizedClass(): string + public function addProphesizedClass(string $prophesizedClass): void { - return $this->prophesizedClass; + $this->prophesizedClasses[] = $prophesizedClass; + } + + /** + * @return string[] + */ + public function getProphesizedClasses(): array + { + return $this->prophesizedClasses; } } diff --git a/src/extension.neon b/src/extension.neon index a5ab8e2..8da66d7 100644 --- a/src/extension.neon +++ b/src/extension.neon @@ -20,6 +20,11 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: JanGregor\Prophecy\Extension\ObjectProphecyWillImplementDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - class: JanGregor\Prophecy\Reflection\ProphecyMethodsClassReflectionExtension tags: diff --git a/tests/Model/Bar.php b/tests/Model/Bar.php new file mode 100644 index 0000000..73a3b45 --- /dev/null +++ b/tests/Model/Bar.php @@ -0,0 +1,10 @@ +bar(); + } } diff --git a/tests/Model/Foo.php b/tests/Model/Foo.php new file mode 100644 index 0000000..9b8c703 --- /dev/null +++ b/tests/Model/Foo.php @@ -0,0 +1,10 @@ +getFoo()); self::assertEquals(5, $subject->doubleTheNumber(2)); } + + public function testWillImplementWorks(): void + { + $fooThatAlsoBars = $this->prophesize(Foo::class); + + $fooThatAlsoBars->willImplement(Bar::class); + + $fooThatAlsoBars + ->bar() + ->shouldBeCalled() + ->willReturn('Oh'); + + $subject = new BaseModel(); + + self::assertSame('Oh', $subject->bar($fooThatAlsoBars->reveal())); + } }