Skip to content

Commit 00f6c9a

Browse files
committed
HeaderBag::get() return type extension
1 parent 9e83865 commit 00f6c9a

File tree

5 files changed

+115
-1
lines changed

5 files changed

+115
-1
lines changed

extension.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,8 @@ services:
4040
-
4141
class: PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension
4242
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
43+
44+
# HeaderBag::get() return type
45+
-
46+
class: PHPStan\Type\Symfony\HeaderBagDynamicReturnTypeExtension
47+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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\ArrayType;
10+
use PHPStan\Type\Constant\ConstantBooleanType;
11+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
12+
use PHPStan\Type\IntegerType;
13+
use PHPStan\Type\NullType;
14+
use PHPStan\Type\StringType;
15+
use PHPStan\Type\Type;
16+
use PHPStan\Type\TypeCombinator;
17+
18+
final class HeaderBagDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
19+
{
20+
21+
public function getClass(): string
22+
{
23+
return 'Symfony\Component\HttpFoundation\HeaderBag';
24+
}
25+
26+
public function isMethodSupported(MethodReflection $methodReflection): bool
27+
{
28+
return $methodReflection->getName() === 'get';
29+
}
30+
31+
public function getTypeFromMethodCall(
32+
MethodReflection $methodReflection,
33+
MethodCall $methodCall,
34+
Scope $scope
35+
): Type
36+
{
37+
$firstArgType = isset($methodCall->args[2]) ? $scope->getType($methodCall->args[2]->value) : new ConstantBooleanType(true);
38+
$isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($firstArgType);
39+
$isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($firstArgType);
40+
$compareTypes = $isTrueType->compareTo($isFalseType);
41+
42+
$defaultArgType = isset($methodCall->args[1]) ? $scope->getType($methodCall->args[1]->value) : new NullType();
43+
44+
if ($compareTypes === $isTrueType) {
45+
return TypeCombinator::union($defaultArgType, new StringType());
46+
}
47+
if ($compareTypes === $isFalseType) {
48+
return TypeCombinator::union($defaultArgType, new ArrayType(new IntegerType(), new StringType()));
49+
}
50+
51+
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
52+
}
53+
54+
}

tests/Symfony/NeonTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function testExtensionNeon(): void
4040
], $container->getParameters());
4141

4242
self::assertCount(2, $container->findByTag('phpstan.rules.rule'));
43-
self::assertCount(4, $container->findByTag('phpstan.broker.dynamicMethodReturnTypeExtension'));
43+
self::assertCount(5, $container->findByTag('phpstan.broker.dynamicMethodReturnTypeExtension'));
4444
self::assertCount(3, $container->findByTag('phpstan.typeSpecifier.methodTypeSpecifyingExtension'));
4545
self::assertInstanceOf(ServiceMap::class, $container->getByType(ServiceMap::class));
4646
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony;
4+
5+
use Iterator;
6+
7+
final class HeaderBagDynamicReturnTypeExtensionTest extends ExtensionTestCase
8+
{
9+
10+
/**
11+
* @dataProvider getProvider
12+
*/
13+
public function testGet(string $expression, string $type): void
14+
{
15+
$this->processFile(
16+
__DIR__ . '/header_bag_get.php',
17+
$expression,
18+
$type,
19+
new HeaderBagDynamicReturnTypeExtension()
20+
);
21+
}
22+
23+
public function getProvider(): Iterator
24+
{
25+
yield ['$test1', 'string|null'];
26+
yield ['$test2', 'string|null'];
27+
yield ['$test3', 'string'];
28+
yield ['$test4', 'array<int, string>|string'];
29+
yield ['$test5', 'string|null'];
30+
yield ['$test6', 'string'];
31+
yield ['$test7', 'array<int, string>|string'];
32+
yield ['$test8', 'array<int, string>|null'];
33+
yield ['$test9', 'array<int, string>|string'];
34+
yield ['$test10', 'array<int, string>'];
35+
}
36+
37+
}

tests/Type/Symfony/header_bag_get.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php declare(strict_types = 1);
2+
3+
$bag = new \Symfony\Component\HttpFoundation\HeaderBag(['foo' => ['bar']]);
4+
5+
$test1 = $bag->get('foo');
6+
$test2 = $bag->get('foo', null);
7+
$test3 = $bag->get('foo', 'baz');
8+
$test4 = $bag->get('foo', ['baz']);
9+
10+
$test5 = $bag->get('foo', null, true);
11+
$test6 = $bag->get('foo', 'baz', true);
12+
$test7 = $bag->get('foo', ['baz'], true);
13+
14+
$test8 = $bag->get('foo', null, false);
15+
$test9 = $bag->get('foo', 'baz', false);
16+
$test10 = $bag->get('foo', ['baz'], false);
17+
18+
die;

0 commit comments

Comments
 (0)