Skip to content

Commit cc9d9ac

Browse files
authored
Add full support for Config/TreeBuilder
1 parent 6f27071 commit cc9d9ac

13 files changed

+495
-52
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ This extension provides following features:
1313
* Provides correct return type for `Request::getContent()` method based on the `$asResource` parameter.
1414
* Provides correct return type for `HeaderBag::get()` method based on the `$first` parameter.
1515
* Provides correct return type for `Envelope::all()` method based on the `$stampFqcn` parameter.
16+
* Provides correct return types for `TreeBuilder` and `NodeDefinition` objects.
1617
* Notifies you when you try to get an unregistered service from the container.
1718
* Notifies you when you try to get a private service from the container.
1819
* Optionally correct return types for `InputInterface::getArgument()`, `::getOption`, `::hasArgument`, and `::hasOption`.

extension.neon

+49-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ services:
4444
# console resolver
4545
-
4646
factory: PHPStan\Symfony\ConsoleApplicationResolver
47-
arguments: [%symfony.console_application_loader%]
47+
arguments:
48+
consoleApplicationLoader: %symfony.console_application_loader%
4849

4950
# service map
5051
symfony.serviceMapFactory:
@@ -146,12 +147,57 @@ services:
146147
factory: PHPStan\Type\Symfony\InputInterfaceHasOptionDynamicReturnTypeExtension
147148
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
148149

150+
# ArrayNodeDefinition::*prototype() return type
151+
-
152+
factory: PHPStan\Type\Symfony\Config\ArrayNodeDefinitionPrototypeDynamicReturnTypeExtension
153+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
154+
155+
# ExprBuilder::end() return type
156+
-
157+
factory: PHPStan\Type\Symfony\Config\ReturnParentDynamicReturnTypeExtension
158+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
159+
arguments:
160+
className: Symfony\Component\Config\Definition\Builder\ExprBuilder
161+
methods: [end]
162+
163+
# NodeBuilder::*node() return type
164+
-
165+
factory: PHPStan\Type\Symfony\Config\PassParentObjectDynamicReturnTypeExtension
166+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
167+
arguments:
168+
className: Symfony\Component\Config\Definition\Builder\NodeBuilder
169+
methods: [arrayNode, scalarNode, booleanNode, integerNode, floatNode, enumNode, variableNode]
170+
171+
# NodeBuilder::end() return type
172+
-
173+
factory: PHPStan\Type\Symfony\Config\ReturnParentDynamicReturnTypeExtension
174+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
175+
arguments:
176+
className: Symfony\Component\Config\Definition\Builder\NodeBuilder
177+
methods: [end]
178+
179+
# NodeDefinition::children() return type
180+
-
181+
factory: PHPStan\Type\Symfony\Config\PassParentObjectDynamicReturnTypeExtension
182+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
183+
arguments:
184+
className: Symfony\Component\Config\Definition\Builder\NodeDefinition
185+
methods: [children, validate]
186+
187+
# NodeDefinition::end() return type
188+
-
189+
factory: PHPStan\Type\Symfony\Config\ReturnParentDynamicReturnTypeExtension
190+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
191+
arguments:
192+
className: Symfony\Component\Config\Definition\Builder\NodeDefinition
193+
methods: [end]
194+
149195
# new TreeBuilder() return type
150196
-
151-
factory: PHPStan\Type\Symfony\TreeBuilderDynamicReturnTypeExtension
197+
factory: PHPStan\Type\Symfony\Config\TreeBuilderDynamicReturnTypeExtension
152198
tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension]
153199

154200
# TreeBuilder::getRootNode() return type
155201
-
156-
factory: PHPStan\Type\Symfony\TreeBuilderGetRootNodeDynamicReturnTypeExtension
202+
factory: PHPStan\Type\Symfony\Config\TreeBuilderGetRootNodeDynamicReturnTypeExtension
157203
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony\Config;
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\DynamicMethodReturnTypeExtension;
10+
use PHPStan\Type\Symfony\Config\ValueObject\ParentObjectType;
11+
use PHPStan\Type\Type;
12+
use PHPStan\Type\TypeUtils;
13+
use PHPStan\Type\VerbosityLevel;
14+
15+
final class ArrayNodeDefinitionPrototypeDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
16+
{
17+
18+
private const PROTOTYPE_METHODS = [
19+
'arrayPrototype',
20+
'scalarPrototype',
21+
'booleanPrototype',
22+
'integerPrototype',
23+
'floatPrototype',
24+
'enumPrototype',
25+
'variablePrototype',
26+
];
27+
28+
private const MAPPING = [
29+
'variable' => 'Symfony\Component\Config\Definition\Builder\VariableNodeDefinition',
30+
'scalar' => 'Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition',
31+
'boolean' => 'Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition',
32+
'integer' => 'Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition',
33+
'float' => 'Symfony\Component\Config\Definition\Builder\FloatNodeDefinition',
34+
'array' => 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition',
35+
'enum' => 'Symfony\Component\Config\Definition\Builder\EnumNodeDefinition',
36+
];
37+
38+
public function getClass(): string
39+
{
40+
return 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition';
41+
}
42+
43+
public function isMethodSupported(MethodReflection $methodReflection): bool
44+
{
45+
return $methodReflection->getName() === 'prototype' || in_array($methodReflection->getName(), self::PROTOTYPE_METHODS, true);
46+
}
47+
48+
public function getTypeFromMethodCall(
49+
MethodReflection $methodReflection,
50+
MethodCall $methodCall,
51+
Scope $scope
52+
): Type
53+
{
54+
$calledOnType = $scope->getType($methodCall->var);
55+
56+
$defaultType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
57+
58+
if ($methodReflection->getName() === 'prototype') {
59+
if (!isset($methodCall->args[0])) {
60+
return $defaultType;
61+
}
62+
63+
$argStrings = TypeUtils::getConstantStrings($scope->getType($methodCall->args[0]->value));
64+
if (count($argStrings) === 1 && isset(self::MAPPING[$argStrings[0]->getValue()])) {
65+
$type = $argStrings[0]->getValue();
66+
67+
return new ParentObjectType(self::MAPPING[$type], $calledOnType);
68+
}
69+
}
70+
71+
return new ParentObjectType(
72+
$defaultType->describe(VerbosityLevel::typeOnly()),
73+
$calledOnType
74+
);
75+
}
76+
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony\Config;
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\DynamicMethodReturnTypeExtension;
10+
use PHPStan\Type\Symfony\Config\ValueObject\ParentObjectType;
11+
use PHPStan\Type\Type;
12+
use PHPStan\Type\VerbosityLevel;
13+
14+
final class PassParentObjectDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
15+
{
16+
17+
/** @var string */
18+
private $className;
19+
20+
/** @var string[] */
21+
private $methods;
22+
23+
/**
24+
* @param string $className
25+
* @param string[] $methods
26+
*/
27+
public function __construct(string $className, array $methods)
28+
{
29+
$this->className = $className;
30+
$this->methods = $methods;
31+
}
32+
33+
public function getClass(): string
34+
{
35+
return $this->className;
36+
}
37+
38+
public function isMethodSupported(MethodReflection $methodReflection): bool
39+
{
40+
return in_array($methodReflection->getName(), $this->methods, true);
41+
}
42+
43+
public function getTypeFromMethodCall(
44+
MethodReflection $methodReflection,
45+
MethodCall $methodCall,
46+
Scope $scope
47+
): Type
48+
{
49+
$calledOnType = $scope->getType($methodCall->var);
50+
51+
$defaultType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
52+
53+
return new ParentObjectType($defaultType->describe(VerbosityLevel::typeOnly()), $calledOnType);
54+
}
55+
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony\Config;
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\DynamicMethodReturnTypeExtension;
10+
use PHPStan\Type\Symfony\Config\ValueObject\ParentObjectType;
11+
use PHPStan\Type\Type;
12+
13+
final class ReturnParentDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
14+
{
15+
16+
/** @var string */
17+
private $className;
18+
19+
/** @var string[] */
20+
private $methods;
21+
22+
/**
23+
* @param string $className
24+
* @param string[] $methods
25+
*/
26+
public function __construct(string $className, array $methods)
27+
{
28+
$this->className = $className;
29+
$this->methods = $methods;
30+
}
31+
32+
public function getClass(): string
33+
{
34+
return $this->className;
35+
}
36+
37+
public function isMethodSupported(MethodReflection $methodReflection): bool
38+
{
39+
return in_array($methodReflection->getName(), $this->methods, true);
40+
}
41+
42+
public function getTypeFromMethodCall(
43+
MethodReflection $methodReflection,
44+
MethodCall $methodCall,
45+
Scope $scope
46+
): Type
47+
{
48+
$calledOnType = $scope->getType($methodCall->var);
49+
if ($calledOnType instanceof ParentObjectType) {
50+
return $calledOnType->getParent();
51+
}
52+
53+
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
54+
}
55+
56+
}

src/Type/Symfony/TreeBuilderDynamicReturnTypeExtension.php renamed to src/Type/Symfony/Config/TreeBuilderDynamicReturnTypeExtension.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
<?php declare(strict_types = 1);
22

3-
namespace PHPStan\Type\Symfony;
3+
namespace PHPStan\Type\Symfony\Config;
44

55
use PhpParser\Node\Expr\StaticCall;
66
use PhpParser\Node\Name;
77
use PHPStan\Analyser\Scope;
88
use PHPStan\Reflection\MethodReflection;
99
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
10+
use PHPStan\Type\Symfony\Config\ValueObject\TreeBuilderType;
1011
use PHPStan\Type\Type;
1112
use PHPStan\Type\TypeUtils;
1213

src/Type/Symfony/TreeBuilderGetRootNodeDynamicReturnTypeExtension.php renamed to src/Type/Symfony/Config/TreeBuilderGetRootNodeDynamicReturnTypeExtension.php

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
<?php declare(strict_types = 1);
22

3-
namespace PHPStan\Type\Symfony;
3+
namespace PHPStan\Type\Symfony\Config;
44

55
use PhpParser\Node\Expr\MethodCall;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Reflection\MethodReflection;
8+
use PHPStan\Reflection\ParametersAcceptorSelector;
89
use PHPStan\Type\DynamicMethodReturnTypeExtension;
9-
use PHPStan\Type\ObjectType;
10+
use PHPStan\Type\Symfony\Config\ValueObject\ParentObjectType;
11+
use PHPStan\Type\Symfony\Config\ValueObject\TreeBuilderType;
1012
use PHPStan\Type\Type;
1113

1214
final class TreeBuilderGetRootNodeDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
@@ -29,11 +31,17 @@ public function getTypeFromMethodCall(
2931
): Type
3032
{
3133
$calledOnType = $scope->getType($methodCall->var);
34+
35+
$defaultType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
36+
3237
if ($calledOnType instanceof TreeBuilderType) {
33-
return new ObjectType($calledOnType->getRootNodeClassName());
38+
return new ParentObjectType(
39+
$calledOnType->getRootNodeClassName(),
40+
$calledOnType
41+
);
3442
}
3543

36-
return $methodReflection->getVariants()[0]->getReturnType();
44+
return $defaultType;
3745
}
3846

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

src/Type/Symfony/TreeBuilderType.php renamed to src/Type/Symfony/Config/ValueObject/TreeBuilderType.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php declare(strict_types = 1);
22

3-
namespace PHPStan\Type\Symfony;
3+
namespace PHPStan\Type\Symfony\Config\ValueObject;
44

55
use PHPStan\Type\ObjectType;
66

0 commit comments

Comments
 (0)