Skip to content

Commit

Permalink
HeaderBag::get() return type extension
Browse files Browse the repository at this point in the history
  • Loading branch information
lookyman committed Nov 27, 2018
1 parent 9e83865 commit 00f6c9a
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 1 deletion.
5 changes: 5 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@ services:
-
class: PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]

# HeaderBag::get() return type
-
class: PHPStan\Type\Symfony\HeaderBagDynamicReturnTypeExtension
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
54 changes: 54 additions & 0 deletions src/Type/Symfony/HeaderBagDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Symfony;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\IntegerType;
use PHPStan\Type\NullType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

final class HeaderBagDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{

public function getClass(): string
{
return 'Symfony\Component\HttpFoundation\HeaderBag';
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'get';
}

public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): Type
{
$firstArgType = isset($methodCall->args[2]) ? $scope->getType($methodCall->args[2]->value) : new ConstantBooleanType(true);
$isTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($firstArgType);
$isFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($firstArgType);
$compareTypes = $isTrueType->compareTo($isFalseType);

$defaultArgType = isset($methodCall->args[1]) ? $scope->getType($methodCall->args[1]->value) : new NullType();

if ($compareTypes === $isTrueType) {
return TypeCombinator::union($defaultArgType, new StringType());
}
if ($compareTypes === $isFalseType) {
return TypeCombinator::union($defaultArgType, new ArrayType(new IntegerType(), new StringType()));
}

return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
}

}
2 changes: 1 addition & 1 deletion tests/Symfony/NeonTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function testExtensionNeon(): void
], $container->getParameters());

self::assertCount(2, $container->findByTag('phpstan.rules.rule'));
self::assertCount(4, $container->findByTag('phpstan.broker.dynamicMethodReturnTypeExtension'));
self::assertCount(5, $container->findByTag('phpstan.broker.dynamicMethodReturnTypeExtension'));
self::assertCount(3, $container->findByTag('phpstan.typeSpecifier.methodTypeSpecifyingExtension'));
self::assertInstanceOf(ServiceMap::class, $container->getByType(ServiceMap::class));
}
Expand Down
37 changes: 37 additions & 0 deletions tests/Type/Symfony/HeaderBagDynamicReturnTypeExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Symfony;

use Iterator;

final class HeaderBagDynamicReturnTypeExtensionTest extends ExtensionTestCase
{

/**
* @dataProvider getProvider
*/
public function testGet(string $expression, string $type): void
{
$this->processFile(
__DIR__ . '/header_bag_get.php',
$expression,
$type,
new HeaderBagDynamicReturnTypeExtension()
);
}

public function getProvider(): Iterator
{
yield ['$test1', 'string|null'];
yield ['$test2', 'string|null'];
yield ['$test3', 'string'];
yield ['$test4', 'array<int, string>|string'];
yield ['$test5', 'string|null'];
yield ['$test6', 'string'];
yield ['$test7', 'array<int, string>|string'];
yield ['$test8', 'array<int, string>|null'];
yield ['$test9', 'array<int, string>|string'];
yield ['$test10', 'array<int, string>'];
}

}
18 changes: 18 additions & 0 deletions tests/Type/Symfony/header_bag_get.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php declare(strict_types = 1);

$bag = new \Symfony\Component\HttpFoundation\HeaderBag(['foo' => ['bar']]);

$test1 = $bag->get('foo');
$test2 = $bag->get('foo', null);
$test3 = $bag->get('foo', 'baz');
$test4 = $bag->get('foo', ['baz']);

$test5 = $bag->get('foo', null, true);
$test6 = $bag->get('foo', 'baz', true);
$test7 = $bag->get('foo', ['baz'], true);

$test8 = $bag->get('foo', null, false);
$test9 = $bag->get('foo', 'baz', false);
$test10 = $bag->get('foo', ['baz'], false);

die;

0 comments on commit 00f6c9a

Please sign in to comment.