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

+1
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

+10
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]
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+
}
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

+25
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

+1-1
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

+10-3
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

+1-1
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

+1-1
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

+1-1
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

tests/Type/Symfony/InputInterfaceGetOptionDynamicReturnTypeExtensionTest.php

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

tests/Type/Symfony/KernelInterfaceDynamicReturnTypeExtensionTest.php

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

tests/Type/Symfony/RequestDynamicReturnTypeExtensionTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public function testGetContent(string $expression, string $type): void
1616
__DIR__ . '/request_get_content.php',
1717
$expression,
1818
$type,
19-
new RequestDynamicReturnTypeExtension()
19+
[new RequestDynamicReturnTypeExtension()]
2020
);
2121
}
2222

tests/Type/Symfony/SerializerDynamicReturnTypeExtensionTest.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ public function testSerializerInterface(string $expression, string $type): void
1616
__DIR__ . '/serializer.php',
1717
$expression,
1818
$type,
19-
new SerializerDynamicReturnTypeExtension(
19+
[new SerializerDynamicReturnTypeExtension(
2020
'Symfony\Component\Serializer\SerializerInterface',
2121
'deserialize'
22-
)
22+
)]
2323
);
2424
}
2525

@@ -32,10 +32,10 @@ public function testDenormalizerInterface(string $expression, string $type): voi
3232
__DIR__ . '/denormalizer.php',
3333
$expression,
3434
$type,
35-
new SerializerDynamicReturnTypeExtension(
35+
[new SerializerDynamicReturnTypeExtension(
3636
'Symfony\Component\Serializer\Normalizer\DenormalizerInterface',
3737
'denormalize'
38-
)
38+
)]
3939
);
4040
}
4141

tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public function testServices(string $expression, string $type, ?string $containe
1818
__DIR__ . '/ExampleController.php',
1919
$expression,
2020
$type,
21-
new ServiceDynamicReturnTypeExtension(Controller::class, true, (new XmlServiceMapFactory($container))->create())
21+
[new ServiceDynamicReturnTypeExtension(Controller::class, true, (new XmlServiceMapFactory($container))->create())]
2222
);
2323
}
2424

@@ -55,7 +55,7 @@ public function testConstantHassersOff(string $expression, string $type, ?string
5555
__DIR__ . '/ExampleController.php',
5656
$expression,
5757
$type,
58-
new ServiceDynamicReturnTypeExtension(Controller::class, false, (new XmlServiceMapFactory($container))->create())
58+
[new ServiceDynamicReturnTypeExtension(Controller::class, false, (new XmlServiceMapFactory($container))->create())]
5959
);
6060
}
6161

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 Iterator;
6+
7+
final class TreeBuilderDynamicReturnTypeExtensionTest extends ExtensionTestCase
8+
{
9+
10+
/**
11+
* @dataProvider getProvider
12+
*/
13+
public function testGet(string $expression, string $type): void
14+
{
15+
$this->processFile(
16+
__DIR__ . '/tree_builder.php',
17+
$expression,
18+
$type,
19+
[new TreeBuilderGetRootNodeDynamicReturnTypeExtension()],
20+
[new TreeBuilderDynamicReturnTypeExtension()]
21+
);
22+
}
23+
24+
/**
25+
* @return \Iterator<array{string, string}>
26+
*/
27+
public function getProvider(): Iterator
28+
{
29+
yield ['$treeRootNode', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
30+
yield ['$variableRootNode', 'Symfony\Component\Config\Definition\Builder\VariableNodeDefinition'];
31+
yield ['$scalarRootNode', 'Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition'];
32+
yield ['$booleanRootNode', 'Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition'];
33+
yield ['$integerRootNode', 'Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition'];
34+
yield ['$floatRootNode', 'Symfony\Component\Config\Definition\Builder\FloatNodeDefinition'];
35+
yield ['$arrayRootNode', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
36+
yield ['$enumRootNode', 'Symfony\Component\Config\Definition\Builder\EnumNodeDefinition'];
37+
}
38+
39+
}

tests/Type/Symfony/tree_builder.php

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php declare(strict_types = 1);
2+
3+
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
4+
5+
$treeBuilder = new TreeBuilder('my_tree');
6+
$treeRootNode = $treeBuilder->getRootNode();
7+
8+
$variableTreeBuilder = new TreeBuilder('my_tree', 'variable');
9+
$variableRootNode = $variableTreeBuilder->getRootNode();
10+
11+
$scalarTreeBuilder = new TreeBuilder('my_tree', 'scalar');
12+
$scalarRootNode = $scalarTreeBuilder->getRootNode();
13+
14+
$booleanTreeBuilder = new TreeBuilder('my_tree', 'boolean');
15+
$booleanRootNode = $booleanTreeBuilder->getRootNode();
16+
17+
$integerTreeBuilder = new TreeBuilder('my_tree', 'integer');
18+
$integerRootNode = $integerTreeBuilder->getRootNode();
19+
20+
$floatTreeBuilder = new TreeBuilder('my_tree', 'float');
21+
$floatRootNode = $floatTreeBuilder->getRootNode();
22+
23+
$arrayTreeBuilder = new TreeBuilder('my_tree', 'array');
24+
$arrayRootNode = $arrayTreeBuilder->getRootNode();
25+
26+
$enumTreeBuilder = new TreeBuilder('my_tree', 'enum');
27+
$enumRootNode = $enumTreeBuilder->getRootNode();
28+
29+
die;

0 commit comments

Comments
 (0)