Skip to content

Feature/type for controller result factory #338

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/features.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
@@ -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:
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

/*
* This file is part of the phpstan-magento package.
*
* (c) bitExpert AG
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);

namespace bitExpert\PHPStan\Magento\Type;

use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;

/**
* \Magento\Framework\Controller\ResultFactory returns result type based on first parameter
*/
class ControllerResultFactoryReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
/** @see \Magento\Framework\Controller\ResultFactory */
private const TYPE_MAP = [
'TYPE_JSON' => \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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

/*
* This file is part of the phpstan-magento package.
*
* (c) bitExpert AG
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);

namespace bitExpert\PHPStan\Magento\Type;

use PhpParser\Node\Arg;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Testing\PHPStanTestCase;
use PHPStan\Type\ObjectType;

class ControllerResultFactoryReturnTypeExtensionUnitTest extends PHPStanTestCase
{
/**
* @var ControllerResultFactoryReturnTypeExtension
*/
private $extension;

protected function setUp(): void
{
$this->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());
}
}