Skip to content

Commit 173610b

Browse files
franmomuondrejmirtes
authored andcommitted
Specify type after calling InputBag::has
1 parent df5ce32 commit 173610b

File tree

3 files changed

+79
-0
lines changed

3 files changed

+79
-0
lines changed

Diff for: extension.neon

+5
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,8 @@ services:
278278
-
279279
factory: PHPStan\Type\Symfony\ResponseHeaderBagDynamicReturnTypeExtension
280280
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
281+
282+
# InputBag::get() type specification
283+
-
284+
factory: PHPStan\Type\Symfony\InputBagTypeSpecifyingExtension
285+
tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]

Diff for: src/Type/Symfony/InputBagTypeSpecifyingExtension.php

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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\Reflection\MethodReflection;
12+
use PHPStan\Reflection\ParametersAcceptorSelector;
13+
use PHPStan\Reflection\ReflectionProvider;
14+
use PHPStan\Type\MethodTypeSpecifyingExtension;
15+
use PHPStan\Type\TypeCombinator;
16+
use Symfony\Component\HttpFoundation\InputBag;
17+
18+
final class InputBagTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
19+
{
20+
21+
private const INPUT_BAG_CLASS = InputBag::class;
22+
private const HAS_METHOD_NAME = 'has';
23+
private const GET_METHOD_NAME = 'get';
24+
25+
/** @var ReflectionProvider */
26+
private $reflectionProvider;
27+
28+
/** @var TypeSpecifier */
29+
private $typeSpecifier;
30+
31+
public function __construct(ReflectionProvider $reflectionProvider)
32+
{
33+
$this->reflectionProvider = $reflectionProvider;
34+
}
35+
36+
public function getClass(): string
37+
{
38+
return self::INPUT_BAG_CLASS;
39+
}
40+
41+
public function isMethodSupported(MethodReflection $methodReflection, MethodCall $node, TypeSpecifierContext $context): bool
42+
{
43+
return $methodReflection->getName() === self::HAS_METHOD_NAME && !$context->null();
44+
}
45+
46+
public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
47+
{
48+
$classReflection = $this->reflectionProvider->getClass(self::INPUT_BAG_CLASS);
49+
$methodVariants = $classReflection->getNativeMethod(self::GET_METHOD_NAME)->getVariants();
50+
$returnType = ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType();
51+
52+
if (!TypeCombinator::containsNull($returnType)) {
53+
return new SpecifiedTypes();
54+
}
55+
56+
return $this->typeSpecifier->create(
57+
new MethodCall($node->var, self::GET_METHOD_NAME, $node->getArgs()),
58+
TypeCombinator::removeNull($returnType),
59+
$context
60+
);
61+
}
62+
63+
public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
64+
{
65+
$this->typeSpecifier = $typeSpecifier;
66+
}
67+
68+
}

Diff for: tests/Type/Symfony/data/input_bag.php

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
$bag = new \Symfony\Component\HttpFoundation\InputBag(['foo' => 'bar', 'bar' => ['x']]);
66

77
assertType('bool|float|int|string|null', $bag->get('foo'));
8+
9+
if ($bag->has('foo')) {
10+
assertType('bool|float|int|string', $bag->get('foo'));
11+
assertType('bool|float|int|string|null', $bag->get('bar'));
12+
}
13+
814
assertType('bool|float|int|string|null', $bag->get('foo', null));
915
assertType('bool|float|int|string', $bag->get('foo', ''));
1016
assertType('bool|float|int|string', $bag->get('foo', 'baz'));

0 commit comments

Comments
 (0)