diff --git a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php index 29b1a883..0de91de1 100644 --- a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php +++ b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php @@ -4,6 +4,7 @@ use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; +use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\ShouldNotHappenException; @@ -22,8 +23,10 @@ use PHPStan\Type\NullType; use PHPStan\Type\StringType; use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use PHPStan\Type\TypeTraverser; use PHPStan\Type\UnionType; +use Symfony\Component\DependencyInjection\EnvVarProcessor; use function in_array; final class ParameterDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension @@ -44,13 +47,24 @@ final class ParameterDynamicReturnTypeExtension implements DynamicMethodReturnTy /** @var \PHPStan\Symfony\ParameterMap */ private $parameterMap; - public function __construct(string $className, ?string $methodGet, ?string $methodHas, Configuration $configuration, ParameterMap $symfonyParameterMap) + /** @var \PHPStan\PhpDoc\TypeStringResolver */ + private $typeStringResolver; + + public function __construct( + string $className, + ?string $methodGet, + ?string $methodHas, + Configuration $configuration, + ParameterMap $symfonyParameterMap, + TypeStringResolver $typeStringResolver + ) { $this->className = $className; $this->methodGet = $methodGet; $this->methodHas = $methodHas; $this->constantHassers = $configuration->hasConstantHassers(); $this->parameterMap = $symfonyParameterMap; + $this->typeStringResolver = $typeStringResolver; } public function getClass(): string @@ -102,13 +116,46 @@ private function getGetTypeFromMethodCall( if ($parameterKey !== null) { $parameter = $this->parameterMap->getParameter($parameterKey); if ($parameter !== null) { - return $this->generalizeType($scope->getTypeFromValue($parameter->getValue())); + return $this->generalizeTypeFromValue($scope, $parameter->getValue()); } } return $returnType; } + /** + * @param Scope $scope + * @param array|bool|float|int|string $value + */ + private function generalizeTypeFromValue(Scope $scope, $value): Type + { + if (is_array($value) && $value !== []) { + return $this->generalizeType( + new ArrayType( + TypeCombinator::union(...array_map(function ($item) use ($scope): Type { + return $this->generalizeTypeFromValue($scope, $item); + }, array_keys($value))), + TypeCombinator::union(...array_map(function ($item) use ($scope): Type { + return $this->generalizeTypeFromValue($scope, $item); + }, array_values($value))) + ) + ); + } + + if ( + class_exists(EnvVarProcessor::class) + && is_string($value) + && preg_match('/%env\((.*)\:.*\)%/U', $value, $matches) === 1 + && strlen($matches[0]) === strlen($value) + ) { + $providedTypes = EnvVarProcessor::getProvidedTypes(); + + return $this->typeStringResolver->resolve($providedTypes[$matches[1]] ?? 'bool|int|float|string|array'); + } + + return $this->generalizeType($scope->getTypeFromValue($value)); + } + private function generalizeType(Type $type): Type { return TypeTraverser::map($type, function (Type $type, callable $traverse): Type { diff --git a/tests/Type/Symfony/container.xml b/tests/Type/Symfony/container.xml index f4240c3b..e6f8f03b 100644 --- a/tests/Type/Symfony/container.xml +++ b/tests/Type/Symfony/container.xml @@ -4,15 +4,28 @@ abcdef 123 123 + %env(int:APP_INT)% 123.45 123.45 + %env(float:APP_FLOAT)% true true + %env(bool:APP_BOOL)% en es fr + + 123 + 456 + 789 + + + %env(int:APP_INT)% + %env(int:APP_INT)% + %env(int:APP_INT)% + the name diff --git a/tests/Type/Symfony/data/ExampleAbstractController.php b/tests/Type/Symfony/data/ExampleAbstractController.php index 4dbaffa7..6dc6ef7e 100644 --- a/tests/Type/Symfony/data/ExampleAbstractController.php +++ b/tests/Type/Symfony/data/ExampleAbstractController.php @@ -39,6 +39,9 @@ public function parameters(ContainerInterface $container, ParameterBagInterface assertType("string", $container->getParameter('app.int_as_string')); assertType("string", $parameterBag->get('app.int_as_string')); assertType("string", $this->getParameter('app.int_as_string')); + assertType('int', $container->getParameter('app.int_as_processor')); + assertType('int', $parameterBag->get('app.int_as_processor')); + assertType('int', $this->getParameter('app.int_as_processor')); assertType('float', $container->getParameter('app.float')); assertType('float', $parameterBag->get('app.float')); assertType('float', $this->getParameter('app.float')); @@ -54,6 +57,12 @@ public function parameters(ContainerInterface $container, ParameterBagInterface assertType("array", $container->getParameter('app.list')); assertType("array", $parameterBag->get('app.list')); assertType("array", $this->getParameter('app.list')); + assertType("array", $container->getParameter('app.list_of_int')); + assertType("array", $parameterBag->get('app.list_of_int')); + assertType("array", $this->getParameter('app.list_of_int')); + assertType("array", $container->getParameter('app.list_of_int_as_processor')); + assertType("array", $parameterBag->get('app.list_of_int_as_processor')); + assertType("array", $this->getParameter('app.list_of_int_as_processor')); assertType("array>", $container->getParameter('app.list_of_list')); assertType("array>", $parameterBag->get('app.list_of_list')); assertType("array>", $this->getParameter('app.list_of_list')); @@ -77,6 +86,8 @@ public function parameters(ContainerInterface $container, ParameterBagInterface assertType('true', $parameterBag->has('app.int')); assertType('true', $container->hasParameter('app.int_as_string')); assertType('true', $parameterBag->has('app.int_as_string')); + assertType('true', $container->hasParameter('app.int_as_processor')); + assertType('true', $parameterBag->has('app.int_as_processor')); assertType('true', $container->hasParameter('app.float')); assertType('true', $parameterBag->has('app.float')); assertType('true', $container->hasParameter('app.float_as_string')); diff --git a/tests/Type/Symfony/data/ExampleAbstractControllerWithoutContainer.php b/tests/Type/Symfony/data/ExampleAbstractControllerWithoutContainer.php index 7ca06775..26d0f366 100644 --- a/tests/Type/Symfony/data/ExampleAbstractControllerWithoutContainer.php +++ b/tests/Type/Symfony/data/ExampleAbstractControllerWithoutContainer.php @@ -38,18 +38,27 @@ public function parameters(ContainerInterface $container, ParameterBagInterface assertType('array|bool|float|int|string|null', $container->getParameter('app.int_as_string')); assertType('array|bool|float|int|string|null', $parameterBag->get('app.int_as_string')); assertType('array|bool|float|int|string|null', $this->getParameter('app.int_as_string')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.int_as_processor')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.int_as_processor')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.int_as_processor')); assertType('array|bool|float|int|string|null', $container->getParameter('app.float')); assertType('array|bool|float|int|string|null', $parameterBag->get('app.float')); assertType('array|bool|float|int|string|null', $this->getParameter('app.float')); assertType('array|bool|float|int|string|null', $container->getParameter('app.float_as_string')); assertType('array|bool|float|int|string|null', $parameterBag->get('app.float_as_string')); assertType('array|bool|float|int|string|null', $this->getParameter('app.float_as_string')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.float_as_processor')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.float_as_processor')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.float_as_processor')); assertType('array|bool|float|int|string|null', $container->getParameter('app.boolean')); assertType('array|bool|float|int|string|null', $parameterBag->get('app.boolean')); assertType('array|bool|float|int|string|null', $this->getParameter('app.boolean')); assertType('array|bool|float|int|string|null', $container->getParameter('app.boolean_as_string')); assertType('array|bool|float|int|string|null', $parameterBag->get('app.boolean_as_string')); assertType('array|bool|float|int|string|null', $this->getParameter('app.boolean_as_string')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.boolean_as_processor')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.boolean_as_processor')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.boolean_as_processor')); assertType('array|bool|float|int|string|null', $container->getParameter('app.list')); assertType('array|bool|float|int|string|null', $parameterBag->get('app.list')); assertType('array|bool|float|int|string|null', $this->getParameter('app.list')); @@ -74,14 +83,20 @@ public function parameters(ContainerInterface $container, ParameterBagInterface assertType('bool', $parameterBag->has('app.int')); assertType('bool', $container->hasParameter('app.int_as_string')); assertType('bool', $parameterBag->has('app.int_as_string')); + assertType('bool', $container->hasParameter('app.int_as_processor')); + assertType('bool', $parameterBag->has('app.int_as_processor')); assertType('bool', $container->hasParameter('app.float')); assertType('bool', $parameterBag->has('app.float')); assertType('bool', $container->hasParameter('app.float_as_string')); assertType('bool', $parameterBag->has('app.float_as_string')); + assertType('bool', $container->hasParameter('app.float_as_processor')); + assertType('bool', $parameterBag->has('app.float_as_processor')); assertType('bool', $container->hasParameter('app.boolean')); assertType('bool', $parameterBag->has('app.boolean')); assertType('bool', $container->hasParameter('app.boolean_as_string')); assertType('bool', $parameterBag->has('app.boolean_as_string')); + assertType('bool', $container->hasParameter('app.boolean_as_processor')); + assertType('bool', $parameterBag->has('app.boolean_as_processor')); assertType('bool', $container->hasParameter('app.list')); assertType('bool', $parameterBag->has('app.list')); assertType('bool', $container->hasParameter('app.list_of_list')); diff --git a/tests/Type/Symfony/data/ExampleController.php b/tests/Type/Symfony/data/ExampleController.php index 1fe8c983..8cd0c93a 100644 --- a/tests/Type/Symfony/data/ExampleController.php +++ b/tests/Type/Symfony/data/ExampleController.php @@ -39,18 +39,27 @@ public function parameters(ContainerInterface $container, ParameterBagInterface assertType("string", $container->getParameter('app.int_as_string')); assertType("string", $parameterBag->get('app.int_as_string')); assertType("string", $this->getParameter('app.int_as_string')); + assertType('int', $container->getParameter('app.int_as_processor')); + assertType('int', $parameterBag->get('app.int_as_processor')); + assertType('int', $this->getParameter('app.int_as_processor')); assertType('float', $container->getParameter('app.float')); assertType('float', $parameterBag->get('app.float')); assertType('float', $this->getParameter('app.float')); assertType("string", $container->getParameter('app.float_as_string')); assertType("string", $parameterBag->get('app.float_as_string')); assertType("string", $this->getParameter('app.float_as_string')); + assertType('float', $container->getParameter('app.float_as_processor')); + assertType('float', $parameterBag->get('app.float_as_processor')); + assertType('float', $this->getParameter('app.float_as_processor')); assertType('bool', $container->getParameter('app.boolean')); assertType('bool', $parameterBag->get('app.boolean')); assertType('bool', $this->getParameter('app.boolean')); assertType("string", $container->getParameter('app.boolean_as_string')); assertType("string", $parameterBag->get('app.boolean_as_string')); assertType("string", $this->getParameter('app.boolean_as_string')); + assertType('bool', $container->getParameter('app.boolean_as_processor')); + assertType('bool', $parameterBag->get('app.boolean_as_processor')); + assertType('bool', $this->getParameter('app.boolean_as_processor')); assertType("array", $container->getParameter('app.list')); assertType("array", $parameterBag->get('app.list')); assertType("array", $this->getParameter('app.list')); @@ -75,14 +84,20 @@ public function parameters(ContainerInterface $container, ParameterBagInterface assertType('true', $parameterBag->has('app.int')); assertType('true', $container->hasParameter('app.int_as_string')); assertType('true', $parameterBag->has('app.int_as_string')); + assertType('true', $container->hasParameter('app.int_as_processor')); + assertType('true', $parameterBag->has('app.int_as_processor')); assertType('true', $container->hasParameter('app.float')); assertType('true', $parameterBag->has('app.float')); assertType('true', $container->hasParameter('app.float_as_string')); assertType('true', $parameterBag->has('app.float_as_string')); + assertType('true', $container->hasParameter('app.float_as_processor')); + assertType('true', $parameterBag->has('app.float_as_processor')); assertType('true', $container->hasParameter('app.boolean')); assertType('true', $parameterBag->has('app.boolean')); assertType('true', $container->hasParameter('app.boolean_as_string')); assertType('true', $parameterBag->has('app.boolean_as_string')); + assertType('true', $container->hasParameter('app.boolean_as_processor')); + assertType('true', $parameterBag->has('app.boolean_as_processor')); assertType('true', $container->hasParameter('app.list')); assertType('true', $parameterBag->has('app.list')); assertType('true', $container->hasParameter('app.list_of_list')); diff --git a/tests/Type/Symfony/data/ExampleControllerWithoutContainer.php b/tests/Type/Symfony/data/ExampleControllerWithoutContainer.php index 2e9b7ba2..fc48c0e5 100644 --- a/tests/Type/Symfony/data/ExampleControllerWithoutContainer.php +++ b/tests/Type/Symfony/data/ExampleControllerWithoutContainer.php @@ -38,18 +38,27 @@ public function parameters(ContainerInterface $container, ParameterBagInterface assertType('array|bool|float|int|string|null', $container->getParameter('app.int_as_string')); assertType('array|bool|float|int|string|null', $parameterBag->get('app.int_as_string')); assertType('array|bool|float|int|string|null', $this->getParameter('app.int_as_string')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.int_as_processor')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.int_as_processor')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.int_as_processor')); assertType('array|bool|float|int|string|null', $container->getParameter('app.float')); assertType('array|bool|float|int|string|null', $parameterBag->get('app.float')); assertType('array|bool|float|int|string|null', $this->getParameter('app.float')); assertType('array|bool|float|int|string|null', $container->getParameter('app.float_as_string')); assertType('array|bool|float|int|string|null', $parameterBag->get('app.float_as_string')); assertType('array|bool|float|int|string|null', $this->getParameter('app.float_as_string')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.float_as_processor')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.float_as_processor')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.float_as_processor')); assertType('array|bool|float|int|string|null', $container->getParameter('app.boolean')); assertType('array|bool|float|int|string|null', $parameterBag->get('app.boolean')); assertType('array|bool|float|int|string|null', $this->getParameter('app.boolean')); assertType('array|bool|float|int|string|null', $container->getParameter('app.boolean_as_string')); assertType('array|bool|float|int|string|null', $parameterBag->get('app.boolean_as_string')); assertType('array|bool|float|int|string|null', $this->getParameter('app.boolean_as_string')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.boolean_as_processor')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.boolean_as_processor')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.boolean_as_processor')); assertType('array|bool|float|int|string|null', $container->getParameter('app.list')); assertType('array|bool|float|int|string|null', $parameterBag->get('app.list')); assertType('array|bool|float|int|string|null', $this->getParameter('app.list')); @@ -74,14 +83,20 @@ public function parameters(ContainerInterface $container, ParameterBagInterface assertType('bool', $parameterBag->has('app.int')); assertType('bool', $container->hasParameter('app.int_as_string')); assertType('bool', $parameterBag->has('app.int_as_string')); + assertType('bool', $container->hasParameter('app.int_as_processor')); + assertType('bool', $parameterBag->has('app.int_as_processor')); assertType('bool', $container->hasParameter('app.float')); assertType('bool', $parameterBag->has('app.float')); assertType('bool', $container->hasParameter('app.float_as_string')); assertType('bool', $parameterBag->has('app.float_as_string')); + assertType('bool', $container->hasParameter('app.float_as_processor')); + assertType('bool', $parameterBag->has('app.float_as_processor')); assertType('bool', $container->hasParameter('app.boolean')); assertType('bool', $parameterBag->has('app.boolean')); assertType('bool', $container->hasParameter('app.boolean_as_string')); assertType('bool', $parameterBag->has('app.boolean_as_string')); + assertType('bool', $container->hasParameter('app.boolean_as_processor')); + assertType('bool', $parameterBag->has('app.boolean_as_processor')); assertType('bool', $container->hasParameter('app.list')); assertType('bool', $parameterBag->has('app.list')); assertType('bool', $container->hasParameter('app.list_of_list'));