Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"phpstan/phpstan-strict-rules": "^0.12.5",
"phpunit/phpunit": "^7.5.20",
"symfony/console": "^4.0",
"symfony/config": "^4.2",
"symfony/framework-bundle": "^4.0",
"symfony/http-foundation": "^4.0",
"symfony/messenger": "^4.2",
Expand Down
10 changes: 10 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,13 @@ services:
-
factory: PHPStan\Type\Symfony\InputInterfaceHasOptionDynamicReturnTypeExtension
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]

# new TreeBuilder() return type
-
factory: PHPStan\Type\Symfony\TreeBuilderDynamicReturnTypeExtension
tags: [phpstan.broker.dynamicStaticMethodReturnTypeExtension]

# TreeBuilder::getRootNode() return type
-
factory: PHPStan\Type\Symfony\TreeBuilderGetRootNodeDynamicReturnTypeExtension
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
56 changes: 56 additions & 0 deletions src/Type/Symfony/TreeBuilderDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Symfony;

use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;

final class TreeBuilderDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
{

private const MAPPING = [
'variable' => 'Symfony\Component\Config\Definition\Builder\VariableNodeDefinition',
'scalar' => 'Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition',
'boolean' => 'Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition',
'integer' => 'Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition',
'float' => 'Symfony\Component\Config\Definition\Builder\FloatNodeDefinition',
'array' => 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition',
'enum' => 'Symfony\Component\Config\Definition\Builder\EnumNodeDefinition',
];

public function getClass(): string
{
return 'Symfony\Component\Config\Definition\Builder\TreeBuilder';
}

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

public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type
{
if (!$methodCall->class instanceof Name) {
throw new \PHPStan\ShouldNotHappenException();
}

$className = $scope->resolveName($methodCall->class);

$type = 'array';

if (isset($methodCall->args[1])) {
$argStrings = TypeUtils::getConstantStrings($scope->getType($methodCall->args[1]->value));
if (count($argStrings) === 1 && isset(self::MAPPING[$argStrings[0]->getValue()])) {
$type = $argStrings[0]->getValue();
}
}

return new TreeBuilderType($className, self::MAPPING[$type]);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Symfony;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;

final class TreeBuilderGetRootNodeDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{

public function getClass(): string
{
return 'Symfony\Component\Config\Definition\Builder\TreeBuilder';
}

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

public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): Type
{
$calledOnType = $scope->getType($methodCall->var);
if ($calledOnType instanceof TreeBuilderType) {
return new ObjectType($calledOnType->getRootNodeClassName());
}

return $methodReflection->getVariants()[0]->getReturnType();
}

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

namespace PHPStan\Type\Symfony;

use PHPStan\Type\ObjectType;

class TreeBuilderType extends ObjectType
{

/** @var string */
private $rootNodeClassName;

public function __construct(string $className, string $rootNodeClassName)
{
parent::__construct($className);

$this->rootNodeClassName = $rootNodeClassName;
}

public function getRootNodeClassName(): string
{
return $this->rootNodeClassName;
}

}
2 changes: 1 addition & 1 deletion tests/Type/Symfony/EnvelopeReturnTypeExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function testAll(string $expression, string $type): void
__DIR__ . '/envelope_all.php',
$expression,
$type,
new EnvelopeReturnTypeExtension()
[new EnvelopeReturnTypeExtension()]
);
}

Expand Down
13 changes: 10 additions & 3 deletions tests/Type/Symfony/ExtensionTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,28 @@
use PHPStan\PhpDoc\PhpDocStringResolver;
use PHPStan\Reflection\ReflectionProvider\DirectReflectionProviderProvider;
use PHPStan\Testing\TestCase;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\FileTypeMapper;
use PHPStan\Type\VerbosityLevel;

abstract class ExtensionTestCase extends TestCase
{

/**
* @param string $file
* @param string $expression
* @param string $type
* @param \PHPStan\Type\DynamicMethodReturnTypeExtension[] $dynamicMethodReturnTypeExtensions
* @param \PHPStan\Type\DynamicStaticMethodReturnTypeExtension[] $dynamicStaticMethodReturnTypeExtensions
*/
protected function processFile(
string $file,
string $expression,
string $type,
DynamicMethodReturnTypeExtension $extension
array $dynamicMethodReturnTypeExtensions = [],
array $dynamicStaticMethodReturnTypeExtensions = []
): void
{
$broker = $this->createBroker([$extension]);
$broker = $this->createBroker($dynamicMethodReturnTypeExtensions, $dynamicStaticMethodReturnTypeExtensions);
$parser = $this->getParser();
$currentWorkingDirectory = $this->getCurrentWorkingDirectory();
$fileHelper = new FileHelper($currentWorkingDirectory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function testGet(string $expression, string $type): void
__DIR__ . '/header_bag_get.php',
$expression,
$type,
new HeaderBagDynamicReturnTypeExtension()
[new HeaderBagDynamicReturnTypeExtension()]
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function testGet(string $expression, string $type): void
__DIR__ . '/input_bag_get.php',
$expression,
$type,
new InputBagDynamicReturnTypeExtension()
[new InputBagDynamicReturnTypeExtension()]
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function testArgumentTypes(string $expression, string $type): void
__DIR__ . '/ExampleBaseCommand.php',
$expression,
$type,
new InputInterfaceGetArgumentDynamicReturnTypeExtension(new ConsoleApplicationResolver(__DiR__ . '/console_application_loader.php'))
[new InputInterfaceGetArgumentDynamicReturnTypeExtension(new ConsoleApplicationResolver(__DiR__ . '/console_application_loader.php'))]
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function testArgumentTypes(string $expression, string $type): void
__DIR__ . '/ExampleOptionCommand.php',
$expression,
$type,
new InputInterfaceGetOptionDynamicReturnTypeExtension(new ConsoleApplicationResolver(__DiR__ . '/console_application_loader.php'))
[new InputInterfaceGetOptionDynamicReturnTypeExtension(new ConsoleApplicationResolver(__DiR__ . '/console_application_loader.php'))]
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function testGet(string $expression, string $type): void
__DIR__ . '/kernel_interface.php',
$expression,
$type,
new KernelInterfaceDynamicReturnTypeExtension()
[new KernelInterfaceDynamicReturnTypeExtension()]
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function testGetContent(string $expression, string $type): void
__DIR__ . '/request_get_content.php',
$expression,
$type,
new RequestDynamicReturnTypeExtension()
[new RequestDynamicReturnTypeExtension()]
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ public function testSerializerInterface(string $expression, string $type): void
__DIR__ . '/serializer.php',
$expression,
$type,
new SerializerDynamicReturnTypeExtension(
[new SerializerDynamicReturnTypeExtension(
'Symfony\Component\Serializer\SerializerInterface',
'deserialize'
)
)]
);
}

Expand All @@ -32,10 +32,10 @@ public function testDenormalizerInterface(string $expression, string $type): voi
__DIR__ . '/denormalizer.php',
$expression,
$type,
new SerializerDynamicReturnTypeExtension(
[new SerializerDynamicReturnTypeExtension(
'Symfony\Component\Serializer\Normalizer\DenormalizerInterface',
'denormalize'
)
)]
);
}

Expand Down
4 changes: 2 additions & 2 deletions tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function testServices(string $expression, string $type, ?string $containe
__DIR__ . '/ExampleController.php',
$expression,
$type,
new ServiceDynamicReturnTypeExtension(Controller::class, true, (new XmlServiceMapFactory($container))->create())
[new ServiceDynamicReturnTypeExtension(Controller::class, true, (new XmlServiceMapFactory($container))->create())]
);
}

Expand Down Expand Up @@ -55,7 +55,7 @@ public function testConstantHassersOff(string $expression, string $type, ?string
__DIR__ . '/ExampleController.php',
$expression,
$type,
new ServiceDynamicReturnTypeExtension(Controller::class, false, (new XmlServiceMapFactory($container))->create())
[new ServiceDynamicReturnTypeExtension(Controller::class, false, (new XmlServiceMapFactory($container))->create())]
);
}

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

namespace PHPStan\Type\Symfony;

use Iterator;

final class TreeBuilderDynamicReturnTypeExtensionTest extends ExtensionTestCase
{

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

/**
* @return \Iterator<array{string, string}>
*/
public function getProvider(): Iterator
{
yield ['$treeRootNode', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
yield ['$variableRootNode', 'Symfony\Component\Config\Definition\Builder\VariableNodeDefinition'];
yield ['$scalarRootNode', 'Symfony\Component\Config\Definition\Builder\ScalarNodeDefinition'];
yield ['$booleanRootNode', 'Symfony\Component\Config\Definition\Builder\BooleanNodeDefinition'];
yield ['$integerRootNode', 'Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition'];
yield ['$floatRootNode', 'Symfony\Component\Config\Definition\Builder\FloatNodeDefinition'];
yield ['$arrayRootNode', 'Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition'];
yield ['$enumRootNode', 'Symfony\Component\Config\Definition\Builder\EnumNodeDefinition'];
}

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

use Symfony\Component\Config\Definition\Builder\TreeBuilder;

$treeBuilder = new TreeBuilder('my_tree');
$treeRootNode = $treeBuilder->getRootNode();

$variableTreeBuilder = new TreeBuilder('my_tree', 'variable');
$variableRootNode = $variableTreeBuilder->getRootNode();

$scalarTreeBuilder = new TreeBuilder('my_tree', 'scalar');
$scalarRootNode = $scalarTreeBuilder->getRootNode();

$booleanTreeBuilder = new TreeBuilder('my_tree', 'boolean');
$booleanRootNode = $booleanTreeBuilder->getRootNode();

$integerTreeBuilder = new TreeBuilder('my_tree', 'integer');
$integerRootNode = $integerTreeBuilder->getRootNode();

$floatTreeBuilder = new TreeBuilder('my_tree', 'float');
$floatRootNode = $floatTreeBuilder->getRootNode();

$arrayTreeBuilder = new TreeBuilder('my_tree', 'array');
$arrayRootNode = $arrayTreeBuilder->getRootNode();

$enumTreeBuilder = new TreeBuilder('my_tree', 'enum');
$enumRootNode = $enumTreeBuilder->getRootNode();

die;