Skip to content

Commit bb16f94

Browse files
committed
Dynamic return type for Request::getContent()
1 parent 180914d commit bb16f94

File tree

6 files changed

+172
-1
lines changed

6 files changed

+172
-1
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
/tests/tmp
12
/vendor
23
composer.lock

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This extension provides following features:
1010

1111
* Provides correct return type for `ContainerInterface::get()` method.
1212
* Provides correct return type for `Controller::get()` method.
13+
* Provides correct return type for `Request::getContent()` method based on the `$asResource` parameter.
1314
* Notifies you when you try to get an unregistered service from the container.
1415
* Notifies you when you try to get a private service from the container.
1516

composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"satooshi/php-coveralls": "^1.0",
3333
"slevomat/coding-standard": "^4.5.2",
3434
"phpstan/phpstan-phpunit": "^0.10",
35-
"symfony/framework-bundle": "^4.0"
35+
"symfony/framework-bundle": "^4.0",
36+
"symfony/http-foundation": "^4.0"
3637
},
3738
"autoload": {
3839
"psr-4": {

extension.neon

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ services:
77
class: PHPStan\Type\Symfony\ControllerDynamicReturnTypeExtension
88
tags:
99
- phpstan.broker.dynamicMethodReturnTypeExtension
10+
-
11+
class: PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension
12+
tags:
13+
- phpstan.broker.dynamicMethodReturnTypeExtension
1014
-
1115
class: PHPStan\Rules\Symfony\ContainerInterfacePrivateServiceRule
1216
tags:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony;
4+
5+
use PhpParser\Node\Expr\MethodCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\MethodReflection;
8+
use PHPStan\Reflection\ParametersAcceptorSelector;
9+
use PHPStan\Type\Constant\ConstantBooleanType;
10+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
11+
use PHPStan\Type\ResourceType;
12+
use PHPStan\Type\StringType;
13+
use PHPStan\Type\Type;
14+
15+
final class RequestDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
16+
{
17+
18+
public function getClass(): string
19+
{
20+
return 'Symfony\Component\HttpFoundation\Request';
21+
}
22+
23+
public function isMethodSupported(MethodReflection $methodReflection): bool
24+
{
25+
return $methodReflection->getName() === 'getContent';
26+
}
27+
28+
public function getTypeFromMethodCall(
29+
MethodReflection $methodReflection,
30+
MethodCall $methodCall,
31+
Scope $scope
32+
): Type
33+
{
34+
if (!isset($methodCall->args[0])) {
35+
return new StringType();
36+
}
37+
38+
$argType = $scope->getType($methodCall->args[0]->value);
39+
if (!$argType instanceof ConstantBooleanType) {
40+
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
41+
}
42+
43+
return $argType->getValue() ? new ResourceType() : new StringType();
44+
}
45+
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony;
4+
5+
use PhpParser\Node\Arg;
6+
use PhpParser\Node\Expr;
7+
use PhpParser\Node\Expr\MethodCall;
8+
use PHPStan\Analyser\Scope;
9+
use PHPStan\Reflection\MethodReflection;
10+
use PHPStan\Reflection\ParametersAcceptor;
11+
use PHPStan\Type\Constant\ConstantBooleanType;
12+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
13+
use PHPStan\Type\NeverType;
14+
use PHPStan\Type\ResourceType;
15+
use PHPStan\Type\StringType;
16+
use PHPStan\Type\Type;
17+
use PHPUnit\Framework\TestCase;
18+
19+
/**
20+
* @covers \PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension
21+
*/
22+
final class RequestDynamicReturnTypeExtensionTest extends TestCase
23+
{
24+
25+
public function testImplementsDynamicMethodReturnTypeExtension(): void
26+
{
27+
self::assertInstanceOf(
28+
DynamicMethodReturnTypeExtension::class,
29+
new RequestDynamicReturnTypeExtension()
30+
);
31+
}
32+
33+
public function testGetClass(): void
34+
{
35+
$extension = new RequestDynamicReturnTypeExtension();
36+
self::assertSame('Symfony\Component\HttpFoundation\Request', $extension->getClass());
37+
}
38+
39+
public function testIsMethodSupported(): void
40+
{
41+
$methodGetContent = $this->createMock(MethodReflection::class);
42+
$methodGetContent->expects(self::once())->method('getName')->willReturn('getContent');
43+
44+
$methodFoo = $this->createMock(MethodReflection::class);
45+
$methodFoo->expects(self::once())->method('getName')->willReturn('foo');
46+
47+
$extension = new RequestDynamicReturnTypeExtension();
48+
self::assertTrue($extension->isMethodSupported($methodGetContent));
49+
self::assertFalse($extension->isMethodSupported($methodFoo));
50+
}
51+
52+
/**
53+
* @dataProvider getTypeFromMethodCallProvider
54+
* @param MethodReflection $methodReflection
55+
* @param MethodCall $methodCall
56+
* @param Type $expectedType
57+
* @param Scope $scope
58+
*/
59+
public function testGetTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Type $expectedType, Scope $scope): void
60+
{
61+
$extension = new RequestDynamicReturnTypeExtension();
62+
$type = $extension->getTypeFromMethodCall(
63+
$methodReflection,
64+
$methodCall,
65+
$scope
66+
);
67+
self::assertEquals($expectedType, $type);
68+
}
69+
70+
/**
71+
* @return mixed[]
72+
*/
73+
public function getTypeFromMethodCallProvider(): array
74+
{
75+
$scopeAsString = $this->createMock(Scope::class);
76+
$scopeAsString->expects(self::once())->method('getType')->willReturn(new ConstantBooleanType(false));
77+
78+
$scopeAsResource = $this->createMock(Scope::class);
79+
$scopeAsResource->expects(self::once())->method('getType')->willReturn(new ConstantBooleanType(true));
80+
81+
$scopeUnknown = $this->createMock(Scope::class);
82+
$scopeUnknown->expects(self::once())->method('getType')->willReturn($this->createMock(Type::class));
83+
84+
$parametersAcceptorUnknown = $this->createMock(ParametersAcceptor::class);
85+
$parametersAcceptorUnknown->expects(self::once())->method('getReturnType')->willReturn(new NeverType());
86+
87+
$methodReflectionUnknown = $this->createMock(MethodReflection::class);
88+
$methodReflectionUnknown->expects(self::once())->method('getVariants')->willReturn([$parametersAcceptorUnknown]);
89+
90+
return [
91+
'noArgument' => [
92+
$this->createMock(MethodReflection::class),
93+
new MethodCall($this->createMock(Expr::class), ''),
94+
new StringType(),
95+
$this->createMock(Scope::class),
96+
],
97+
'asString' => [
98+
$this->createMock(MethodReflection::class),
99+
new MethodCall($this->createMock(Expr::class), '', [new Arg($this->createMock(Expr::class))]),
100+
new StringType(),
101+
$scopeAsString,
102+
],
103+
'asResource' => [
104+
$this->createMock(MethodReflection::class),
105+
new MethodCall($this->createMock(Expr::class), '', [new Arg($this->createMock(Expr::class))]),
106+
new ResourceType(),
107+
$scopeAsResource,
108+
],
109+
'unknown' => [
110+
$methodReflectionUnknown,
111+
new MethodCall($this->createMock(Expr::class), '', [new Arg($this->createMock(Expr::class))]),
112+
new NeverType(),
113+
$scopeUnknown,
114+
],
115+
];
116+
}
117+
118+
}

0 commit comments

Comments
 (0)