From 1ff0a619d64309f2c28c5de94a962a0c755a8e27 Mon Sep 17 00:00:00 2001 From: Piotr Markiewicz Date: Tue, 4 Mar 2025 14:56:25 +0100 Subject: [PATCH 1/2] Add extension for return type in Result Factory --- extension.neon | 4 + ...rollerResultFactoryReturnTypeExtension.php | 66 +++++++++++++ ...sultFactoryReturnTypeExtensionUnitTest.php | 98 +++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 src/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtension.php create mode 100644 tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php diff --git a/extension.neon b/extension.neon index a43e09a..aa90e9d 100644 --- a/extension.neon +++ b/extension.neon @@ -37,6 +37,10 @@ services: class: bitExpert\PHPStan\Magento\Type\TestFrameworkObjectManagerDynamicReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: bitExpert\PHPStan\Magento\Type\ControllerResultFactoryReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension - class: bitExpert\PHPStan\Magento\Reflection\Framework\Session\SessionManagerMagicMethodReflectionExtension tags: diff --git a/src/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtension.php b/src/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtension.php new file mode 100644 index 0000000..6047375 --- /dev/null +++ b/src/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtension.php @@ -0,0 +1,66 @@ + \Magento\Framework\Controller\Result\Json::class, + 'TYPE_RAW' => \Magento\Framework\Controller\Result\Raw::class, + 'TYPE_REDIRECT' => \Magento\Framework\Controller\Result\Redirect::class, + 'TYPE_FORWARD' => \Magento\Framework\Controller\Result\Forward::class, + 'TYPE_LAYOUT' => \Magento\Framework\View\Result\Layout::class, + 'TYPE_PAGE' => \Magento\Framework\View\Result\Page::class, + ]; + + public function getClass(): string + { + return \Magento\Framework\Controller\ResultFactory::class; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'create'; + } + + public function getTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): ?ObjectType { + $class = null; + if (\count($methodCall->getArgs()) > 0) { + $arg = $methodCall->getArgs()[0]; + $expr = $arg->value; + + if ($expr instanceof ClassConstFetch && $expr->name instanceof Identifier) { + $class = self::TYPE_MAP[$expr->name->toString()] ?? null; + } + } + + return $class !== null ? new ObjectType($class) : null; + } +} diff --git a/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php new file mode 100644 index 0000000..2ba08c5 --- /dev/null +++ b/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php @@ -0,0 +1,98 @@ +extension = new ControllerResultFactoryReturnTypeExtension(); + } + + /** + * @return mixed[] + */ + public function returnTypeDataProvider(): array + { + return [ + ['TYPE_JSON', 'Magento\Framework\Controller\Result\Json'], + ['TYPE_PAGE', 'Magento\Framework\View\Result\Page'], + ]; + } + + /** + * @return mixed[] + */ + public function isMethodSupportedDataProvider(): array + { + return [ + ['create', true], + ['get', false] + ]; + } + + /** + * @test + * @dataProvider isMethodSupportedDataProvider + * @param string $method + * @param bool $expectedResult + */ + public function checkIfMethodIsSupported(string $method, bool $expectedResult): void + { + $methodReflection = $this->createMock(MethodReflection::class); + $methodReflection->method('getName')->willReturn($method); + + self::assertSame($expectedResult, $this->extension->isMethodSupported($methodReflection)); + } + + /** + * @test + * @dataProvider returnTypeDataProvider + * @param string $param + * @param string $expectedResult + */ + public function returnValidResultType(string $param, string $expectedResult): void + { + $methodReflection = $this->createMock(MethodReflection::class); + $scope = $this->createMock(Scope::class); + + $identifier = $this->createConfiguredMock(Identifier::class, ['toString' => $param]); + + $expr = $this->createMock(ClassConstFetch::class); + $expr->name = $identifier; + + $arg = $this->createMock(Arg::class); + $arg->value = $expr; + + $methodCall = $this->createConfiguredMock(MethodCall::class, ['getArgs' => [$arg]]); + + $resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope); + + self::assertNotNull($resultType); + self::assertSame($expectedResult, $resultType->getClassName()); + } +} From 37e204c1f35571a74076ea2fca7b19fe261a7ba2 Mon Sep 17 00:00:00 2001 From: Piotr Markiewicz Date: Wed, 5 Mar 2025 12:36:28 +0100 Subject: [PATCH 2/2] Add explaination to docs --- docs/features.md | 3 +++ .../ControllerResultFactoryReturnTypeExtensionUnitTest.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/features.md b/docs/features.md index 32b5d15..735ccb2 100644 --- a/docs/features.md +++ b/docs/features.md @@ -24,6 +24,9 @@ Additionally, a PHPStan rule checks that only `Magento\Framework\Data\Collection ### ObjectManager type hints A type extension is provided so that `Magento\Framework\App\ObjectManager` method calls do return the correct return type. +### ResultFactory type hints +Correct type is returned from `Magento\Framework\Controller\ResultFactory` based on passed parameter. + ## Magic method calls For some classes like `Magento\Framework\DataObject` or `Magento\Framework\Session\SessionManager` PHPStan logic is provided to be able to let the magic method calls return proper types. diff --git a/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php index 2ba08c5..8587d98 100644 --- a/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php +++ b/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php @@ -40,7 +40,7 @@ public function returnTypeDataProvider(): array { return [ ['TYPE_JSON', 'Magento\Framework\Controller\Result\Json'], - ['TYPE_PAGE', 'Magento\Framework\View\Result\Page'], + ['TYPE_PAGE', 'Magento\Framework\View\Result\Page'] ]; }