Skip to content

Commit b233aa9

Browse files
authored
Merge pull request #338 from pmarki/feature/type-for-controller-result-factory
Feature/type for controller result factory
2 parents 09c680d + 37e204c commit b233aa9

File tree

4 files changed

+171
-0
lines changed

4 files changed

+171
-0
lines changed

docs/features.md

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ Additionally, a PHPStan rule checks that only `Magento\Framework\Data\Collection
2424
### ObjectManager type hints
2525
A type extension is provided so that `Magento\Framework\App\ObjectManager` method calls do return the correct return type.
2626

27+
### ResultFactory type hints
28+
Correct type is returned from `Magento\Framework\Controller\ResultFactory` based on passed parameter.
29+
2730
## Magic method calls
2831
For some classes like `Magento\Framework\DataObject` or `Magento\Framework\Session\SessionManager` PHPStan logic is provided
2932
to be able to let the magic method calls return proper types.

extension.neon

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ services:
3737
class: bitExpert\PHPStan\Magento\Type\TestFrameworkObjectManagerDynamicReturnTypeExtension
3838
tags:
3939
- phpstan.broker.dynamicMethodReturnTypeExtension
40+
-
41+
class: bitExpert\PHPStan\Magento\Type\ControllerResultFactoryReturnTypeExtension
42+
tags:
43+
- phpstan.broker.dynamicMethodReturnTypeExtension
4044
-
4145
class: bitExpert\PHPStan\Magento\Reflection\Framework\Session\SessionManagerMagicMethodReflectionExtension
4246
tags:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the phpstan-magento package.
5+
*
6+
* (c) bitExpert AG
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
declare(strict_types=1);
12+
13+
namespace bitExpert\PHPStan\Magento\Type;
14+
15+
use PhpParser\Node\Expr\ClassConstFetch;
16+
use PhpParser\Node\Expr\MethodCall;
17+
use PhpParser\Node\Identifier;
18+
use PHPStan\Analyser\Scope;
19+
use PHPStan\Reflection\MethodReflection;
20+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
21+
use PHPStan\Type\ObjectType;
22+
use PHPStan\Type\Type;
23+
24+
/**
25+
* \Magento\Framework\Controller\ResultFactory returns result type based on first parameter
26+
*/
27+
class ControllerResultFactoryReturnTypeExtension implements DynamicMethodReturnTypeExtension
28+
{
29+
/** @see \Magento\Framework\Controller\ResultFactory */
30+
private const TYPE_MAP = [
31+
'TYPE_JSON' => \Magento\Framework\Controller\Result\Json::class,
32+
'TYPE_RAW' => \Magento\Framework\Controller\Result\Raw::class,
33+
'TYPE_REDIRECT' => \Magento\Framework\Controller\Result\Redirect::class,
34+
'TYPE_FORWARD' => \Magento\Framework\Controller\Result\Forward::class,
35+
'TYPE_LAYOUT' => \Magento\Framework\View\Result\Layout::class,
36+
'TYPE_PAGE' => \Magento\Framework\View\Result\Page::class,
37+
];
38+
39+
public function getClass(): string
40+
{
41+
return \Magento\Framework\Controller\ResultFactory::class;
42+
}
43+
44+
public function isMethodSupported(MethodReflection $methodReflection): bool
45+
{
46+
return $methodReflection->getName() === 'create';
47+
}
48+
49+
public function getTypeFromMethodCall(
50+
MethodReflection $methodReflection,
51+
MethodCall $methodCall,
52+
Scope $scope
53+
): ?ObjectType {
54+
$class = null;
55+
if (\count($methodCall->getArgs()) > 0) {
56+
$arg = $methodCall->getArgs()[0];
57+
$expr = $arg->value;
58+
59+
if ($expr instanceof ClassConstFetch && $expr->name instanceof Identifier) {
60+
$class = self::TYPE_MAP[$expr->name->toString()] ?? null;
61+
}
62+
}
63+
64+
return $class !== null ? new ObjectType($class) : null;
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the phpstan-magento package.
5+
*
6+
* (c) bitExpert AG
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
declare(strict_types=1);
12+
13+
namespace bitExpert\PHPStan\Magento\Type;
14+
15+
use PhpParser\Node\Arg;
16+
use PhpParser\Node\Expr\ClassConstFetch;
17+
use PhpParser\Node\Expr\MethodCall;
18+
use PhpParser\Node\Identifier;
19+
use PHPStan\Analyser\Scope;
20+
use PHPStan\Reflection\MethodReflection;
21+
use PHPStan\Testing\PHPStanTestCase;
22+
use PHPStan\Type\ObjectType;
23+
24+
class ControllerResultFactoryReturnTypeExtensionUnitTest extends PHPStanTestCase
25+
{
26+
/**
27+
* @var ControllerResultFactoryReturnTypeExtension
28+
*/
29+
private $extension;
30+
31+
protected function setUp(): void
32+
{
33+
$this->extension = new ControllerResultFactoryReturnTypeExtension();
34+
}
35+
36+
/**
37+
* @return mixed[]
38+
*/
39+
public function returnTypeDataProvider(): array
40+
{
41+
return [
42+
['TYPE_JSON', 'Magento\Framework\Controller\Result\Json'],
43+
['TYPE_PAGE', 'Magento\Framework\View\Result\Page']
44+
];
45+
}
46+
47+
/**
48+
* @return mixed[]
49+
*/
50+
public function isMethodSupportedDataProvider(): array
51+
{
52+
return [
53+
['create', true],
54+
['get', false]
55+
];
56+
}
57+
58+
/**
59+
* @test
60+
* @dataProvider isMethodSupportedDataProvider
61+
* @param string $method
62+
* @param bool $expectedResult
63+
*/
64+
public function checkIfMethodIsSupported(string $method, bool $expectedResult): void
65+
{
66+
$methodReflection = $this->createMock(MethodReflection::class);
67+
$methodReflection->method('getName')->willReturn($method);
68+
69+
self::assertSame($expectedResult, $this->extension->isMethodSupported($methodReflection));
70+
}
71+
72+
/**
73+
* @test
74+
* @dataProvider returnTypeDataProvider
75+
* @param string $param
76+
* @param string $expectedResult
77+
*/
78+
public function returnValidResultType(string $param, string $expectedResult): void
79+
{
80+
$methodReflection = $this->createMock(MethodReflection::class);
81+
$scope = $this->createMock(Scope::class);
82+
83+
$identifier = $this->createConfiguredMock(Identifier::class, ['toString' => $param]);
84+
85+
$expr = $this->createMock(ClassConstFetch::class);
86+
$expr->name = $identifier;
87+
88+
$arg = $this->createMock(Arg::class);
89+
$arg->value = $expr;
90+
91+
$methodCall = $this->createConfiguredMock(MethodCall::class, ['getArgs' => [$arg]]);
92+
93+
$resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
94+
95+
self::assertNotNull($resultType);
96+
self::assertSame($expectedResult, $resultType->getClassName());
97+
}
98+
}

0 commit comments

Comments
 (0)