Skip to content

Commit 79c938c

Browse files
committed
Merge branch 'get-session-type-specifying' of git://github.com/plotek/phpstan-symfony into plotek-get-session-type-specifying
2 parents 59bb0e0 + 84a19b9 commit 79c938c

9 files changed

+180
-2
lines changed

composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
"squizlabs/php_codesniffer": "^3.3.2",
4343
"symfony/serializer": "^3.0 || ^4.0",
4444
"symfony/messenger": "^4.2",
45-
"symfony/console": "^3.0 || ^4.0"
45+
"symfony/console": "^3.0 || ^4.0",
46+
"symfony/http-foundation": "^3.0 || ^4.0"
4647
},
4748
"conflict": {
4849
"symfony/framework-bundle": "<3.0"

extension.neon

+5
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ services:
5959
factory: PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension
6060
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
6161

62+
# Request::getSession() type specification
63+
-
64+
factory: PHPStan\Type\Symfony\RequestTypeSpecifyingExtension
65+
tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]
66+
6267
# HeaderBag::get() return type
6368
-
6469
factory: PHPStan\Type\Symfony\HeaderBagDynamicReturnTypeExtension

phpstan.neon

+3
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@ parameters:
1414
- tests/*/header_bag_get.php
1515
- tests/*/kernel_interface.php
1616
- tests/*/request_get_content.php
17+
- tests/*/request_get_session.php
1718
- tests/*/serializer.php
19+
ignoreErrors:
20+
- '~^Parameter \#1 \$node \(.*\) of method .*Rule::processNode\(\) should be contravariant with parameter \$node \(PhpParser\\Node\) of method PHPStan\\Rules\\Rule::processNode\(\)$~'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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\Analyser\SpecifiedTypes;
8+
use PHPStan\Analyser\TypeSpecifier;
9+
use PHPStan\Analyser\TypeSpecifierAwareExtension;
10+
use PHPStan\Analyser\TypeSpecifierContext;
11+
use PHPStan\Broker\Broker;
12+
use PHPStan\Reflection\MethodReflection;
13+
use PHPStan\Reflection\ParametersAcceptorSelector;
14+
use PHPStan\Type\MethodTypeSpecifyingExtension;
15+
use PHPStan\Type\TypeCombinator;
16+
17+
final class RequestTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
18+
{
19+
20+
private const REQUEST_CLASS = 'Symfony\Component\HttpFoundation\Request';
21+
private const HAS_METHOD_NAME = 'hasSession';
22+
private const GET_METHOD_NAME = 'getSession';
23+
24+
/** @var Broker */
25+
private $broker;
26+
27+
/** @var TypeSpecifier */
28+
private $typeSpecifier;
29+
30+
public function __construct(Broker $broker)
31+
{
32+
$this->broker = $broker;
33+
}
34+
35+
public function getClass(): string
36+
{
37+
return self::REQUEST_CLASS;
38+
}
39+
40+
public function isMethodSupported(MethodReflection $methodReflection, MethodCall $node, TypeSpecifierContext $context): bool
41+
{
42+
return $methodReflection->getName() === self::HAS_METHOD_NAME && !$context->null();
43+
}
44+
45+
public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
46+
{
47+
$classReflection = $this->broker->getClass(self::REQUEST_CLASS);
48+
$methodVariants = $classReflection->getNativeMethod(self::GET_METHOD_NAME)->getVariants();
49+
50+
return $this->typeSpecifier->create(
51+
new MethodCall($node->var, self::GET_METHOD_NAME),
52+
TypeCombinator::removeNull(ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType()),
53+
$context
54+
);
55+
}
56+
57+
public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
58+
{
59+
$this->typeSpecifier = $typeSpecifier;
60+
}
61+
62+
}

tests/Symfony/NeonTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function testExtensionNeon(): void
4747

4848
self::assertCount(6, $container->findByTag('phpstan.rules.rule'));
4949
self::assertCount(12, $container->findByTag('phpstan.broker.dynamicMethodReturnTypeExtension'));
50-
self::assertCount(5, $container->findByTag('phpstan.typeSpecifier.methodTypeSpecifyingExtension'));
50+
self::assertCount(6, $container->findByTag('phpstan.typeSpecifier.methodTypeSpecifyingExtension'));
5151
self::assertInstanceOf(ServiceMap::class, $container->getByType(ServiceMap::class));
5252
}
5353

tests/Symfony/config.neon

+11
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,14 @@ parameters:
44

55
services:
66
- PhpParser\PrettyPrinter\Standard
7+
8+
-
9+
class: PHPStan\DependencyInjection\Container
10+
factory: PHPStan\DependencyInjection\Nette\NetteContainer
11+
12+
broker:
13+
class: PHPStan\Broker\Broker
14+
factory: @brokerFactory::create
15+
16+
brokerFactory:
17+
class: PHPStan\Broker\BrokerFactory
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony;
4+
5+
use PHPStan\Rules\Rule;
6+
use PHPStan\Testing\RuleTestCase;
7+
use PHPStan\Type\MethodTypeSpecifyingExtension;
8+
use Symfony\Component\HttpFoundation\Session\SessionInterface;
9+
10+
final class RequestTypeSpecifyingExtensionTest extends RuleTestCase
11+
{
12+
13+
protected function getRule(): Rule
14+
{
15+
return new VariableTypeReportingRule();
16+
}
17+
18+
/** @return MethodTypeSpecifyingExtension[] */
19+
protected function getMethodTypeSpecifyingExtensions(): array
20+
{
21+
return [
22+
new RequestTypeSpecifyingExtension($this->createBroker()),
23+
];
24+
}
25+
26+
public function testGetSession(): void
27+
{
28+
$this->analyse([__DIR__ . '/request_get_session.php'], [
29+
[
30+
'Variable $session1 is: ' . SessionInterface::class . '|null',
31+
7,
32+
],
33+
[
34+
'Variable $session2 is: ' . SessionInterface::class,
35+
11,
36+
],
37+
]);
38+
}
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\Variable;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Type\VerbosityLevel;
10+
11+
final class VariableTypeReportingRule implements Rule
12+
{
13+
14+
public function getNodeType(): string
15+
{
16+
return Variable::class;
17+
}
18+
19+
/**
20+
* @param \PhpParser\Node\Expr\Variable $node
21+
* @param \PHPStan\Analyser\Scope $scope
22+
* @return string[] errors
23+
*/
24+
public function processNode(Node $node, Scope $scope): array
25+
{
26+
if (!is_string($node->name)) {
27+
return [];
28+
}
29+
if (!$scope->isInFirstLevelStatement()) {
30+
return [];
31+
};
32+
if ($scope->isInExpressionAssign($node)) {
33+
return [];
34+
}
35+
return [
36+
sprintf(
37+
'Variable $%s is: %s',
38+
$node->name,
39+
$scope->getType($node)->describe(VerbosityLevel::value())
40+
),
41+
];
42+
}
43+
44+
}
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php declare(strict_types = 1);
2+
3+
/** @var \Symfony\Component\HttpFoundation\Request $request */
4+
$request = doRequest();
5+
6+
$session1 = $request->getSession();
7+
$session1;
8+
9+
if ($request->hasSession()) {
10+
$session2 = $request->getSession();
11+
$session2;
12+
}

0 commit comments

Comments
 (0)