Skip to content

Commit 6f27071

Browse files
authored
Support for TreeBuilder::getRootNode()
1 parent f051037 commit 6f27071

17 files changed

+222
-16
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"phpstan/phpstan-strict-rules": "^0.12.5",
2828
"phpunit/phpunit": "^7.5.20",
2929
"symfony/console": "^4.0",
30+
"symfony/config": "^4.2",
3031
"symfony/framework-bundle": "^4.0",
3132
"symfony/http-foundation": "^4.0",
3233
"symfony/messenger": "^4.2",

extension.neon

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,13 @@ services:
145145
-
146146
factory: PHPStan\Type\Symfony\InputInterfaceHasOptionDynamicReturnTypeExtension
147147
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
148+
149+
# new TreeBuilder() return type
150+
-
151+
factory: PHPStan\Type\Symfony\TreeBuilderDynamicReturnTypeExtension
152+
tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension]
153+
154+
# TreeBuilder::getRootNode() return type
155+
-
156+
factory: PHPStan\Type\Symfony\TreeBuilderGetRootNodeDynamicReturnTypeExtension
157+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony;
4+
5+
use PhpParser\Node\Expr\StaticCall;
6+
use PhpParser\Node\Name;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Reflection\MethodReflection;
9+
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
10+
use PHPStan\Type\Type;
11+
use PHPStan\Type\TypeUtils;
12+
13+
final class TreeBuilderDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
14+
{
15+
16+
private const MAPPING = [
17+
'variable' => 'Symfony\Component\Config\Definition\Builder\VariableNodeDefinition',
18+
'scalar' => 'Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition',
19+
'boolean' => 'Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition',
20+
'integer' => 'Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition',
21+
'float' => 'Symfony\Component\Config\Definition\Builder\FloatNodeDefinition',
22+
'array' => 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition',
23+
'enum' => 'Symfony\Component\Config\Definition\Builder\EnumNodeDefinition',
24+
];
25+
26+
public function getClass(): string
27+
{
28+
return 'Symfony\Component\Config\Definition\Builder\TreeBuilder';
29+
}
30+
31+
public function isStaticMethodSupported(MethodReflection $methodReflection): bool
32+
{
33+
return $methodReflection->getName() === '__construct';
34+
}
35+
36+
public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type
37+
{
38+
if (!$methodCall->class instanceof Name) {
39+
throw new \PHPStan\ShouldNotHappenException();
40+
}
41+
42+
$className = $scope->resolveName($methodCall->class);
43+
44+
$type = 'array';
45+
46+
if (isset($methodCall->args[1])) {
47+
$argStrings = TypeUtils::getConstantStrings($scope->getType($methodCall->args[1]->value));
48+
if (count($argStrings) === 1 && isset(self::MAPPING[$argStrings[0]->getValue()])) {
49+
$type = $argStrings[0]->getValue();
50+
}
51+
}
52+
53+
return new TreeBuilderType($className, self::MAPPING[$type]);
54+
}
55+
56+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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\Type\DynamicMethodReturnTypeExtension;
9+
use PHPStan\Type\ObjectType;
10+
use PHPStan\Type\Type;
11+
12+
final class TreeBuilderGetRootNodeDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
13+
{
14+
15+
public function getClass(): string
16+
{
17+
return 'Symfony\Component\Config\Definition\Builder\TreeBuilder';
18+
}
19+
20+
public function isMethodSupported(MethodReflection $methodReflection): bool
21+
{
22+
return $methodReflection->getName() === 'getRootNode';
23+
}
24+
25+
public function getTypeFromMethodCall(
26+
MethodReflection $methodReflection,
27+
MethodCall $methodCall,
28+
Scope $scope
29+
): Type
30+
{
31+
$calledOnType = $scope->getType($methodCall->var);
32+
if ($calledOnType instanceof TreeBuilderType) {
33+
return new ObjectType($calledOnType->getRootNodeClassName());
34+
}
35+
36+
return $methodReflection->getVariants()[0]->getReturnType();
37+
}
38+
39+
}

src/Type/Symfony/TreeBuilderType.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony;
4+
5+
use PHPStan\Type\ObjectType;
6+
7+
class TreeBuilderType extends ObjectType
8+
{
9+
10+
/** @var string */
11+
private $rootNodeClassName;
12+
13+
public function __construct(string $className, string $rootNodeClassName)
14+
{
15+
parent::__construct($className);
16+
17+
$this->rootNodeClassName = $rootNodeClassName;
18+
}
19+
20+
public function getRootNodeClassName(): string
21+
{
22+
return $this->rootNodeClassName;
23+
}
24+
25+
}

tests/Type/Symfony/EnvelopeReturnTypeExtensionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public function testAll(string $expression, string $type): void
1818
__DIR__ . '/envelope_all.php',
1919
$expression,
2020
$type,
21-
new EnvelopeReturnTypeExtension()
21+
[new EnvelopeReturnTypeExtension()]
2222
);
2323
}
2424

tests/Type/Symfony/ExtensionTestCase.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,28 @@
1717
use PHPStan\PhpDoc\PhpDocStringResolver;
1818
use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider;
1919
use PHPStan\Testing\TestCase;
20-
use PHPStan\Type\DynamicMethodReturnTypeExtension;
2120
use PHPStan\Type\FileTypeMapper;
2221
use PHPStan\Type\VerbosityLevel;
2322

2423
abstract class ExtensionTestCase extends TestCase
2524
{
2625

26+
/**
27+
* @param string $file
28+
* @param string $expression
29+
* @param string $type
30+
* @param \PHPStan\Type\DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions
31+
* @param \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions
32+
*/
2733
protected function processFile(
2834
string $file,
2935
string $expression,
3036
string $type,
31-
DynamicMethodReturnTypeExtension $extension
37+
array $dynamicMethodReturnTypeExtensions = [],
38+
array $dynamicStaticMethodReturnTypeExtensions = []
3239
): void
3340
{
34-
$broker = $this->createBroker([$extension]);
41+
$broker = $this->createBroker($dynamicMethodReturnTypeExtensions, $dynamicStaticMethodReturnTypeExtensions);
3542
$parser = $this->getParser();
3643
$currentWorkingDirectory = $this->getCurrentWorkingDirectory();
3744
$fileHelper = new FileHelper($currentWorkingDirectory);

tests/Type/Symfony/HeaderBagDynamicReturnTypeExtensionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public function testGet(string $expression, string $type): void
1616
__DIR__ . '/header_bag_get.php',
1717
$expression,
1818
$type,
19-
new HeaderBagDynamicReturnTypeExtension()
19+
[new HeaderBagDynamicReturnTypeExtension()]
2020
);
2121
}
2222

tests/Type/Symfony/InputBagDynamicReturnTypeExtensionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public function testGet(string $expression, string $type): void
2020
__DIR__ . '/input_bag_get.php',
2121
$expression,
2222
$type,
23-
new InputBagDynamicReturnTypeExtension()
23+
[new InputBagDynamicReturnTypeExtension()]
2424
);
2525
}
2626

tests/Type/Symfony/InputInterfaceGetArgumentDynamicReturnTypeExtensionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public function testArgumentTypes(string $expression, string $type): void
1717
__DIR__ . '/ExampleBaseCommand.php',
1818
$expression,
1919
$type,
20-
new InputInterfaceGetArgumentDynamicReturnTypeExtension(new ConsoleApplicationResolver(__DiR__ . '/console_application_loader.php'))
20+
[new InputInterfaceGetArgumentDynamicReturnTypeExtension(new ConsoleApplicationResolver(__DiR__ . '/console_application_loader.php'))]
2121
);
2222
}
2323

0 commit comments

Comments
 (0)