From fe10d57b75d66cf0a089679dc2a83969be62423e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 8 Oct 2021 12:06:30 +0200 Subject: [PATCH 1/4] Add support for env processor --- .../ParameterDynamicReturnTypeExtension.php | 55 ++++++++++++++++++- tests/Type/Symfony/container.xml | 13 +++++ .../data/ExampleAbstractController.php | 11 ++++ ...mpleAbstractControllerWithoutContainer.php | 15 +++++ tests/Type/Symfony/data/ExampleController.php | 15 +++++ .../ExampleControllerWithoutContainer.php | 15 +++++ 6 files changed, 123 insertions(+), 1 deletion(-) diff --git a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php index 29b1a883..f1e3eb48 100644 --- a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php +++ b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php @@ -22,6 +22,7 @@ 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 function in_array; @@ -102,13 +103,65 @@ 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; } + 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 ( + is_string($value) + && preg_match('/%env\((.*)\:.*\)%/U', $value, $matches) === 1 + && strlen($matches[0]) === strlen($value) + ) { + switch ($matches[1]) { + case 'base64': + case 'file': + case 'resolve': + case 'string': + case 'trim': + return new StringType(); + case 'bool': + return new BooleanType(); + case 'int': + return new IntegerType(); + case 'float': + return new FloatType(); + case 'csv': + case 'json': + case 'url': + case 'query_string': + return new ArrayType(new MixedType(), new MixedType()); + default: + return new UnionType([ + new ArrayType(new MixedType(), new MixedType()), + new BooleanType(), + new FloatType(), + new IntegerType(), + new StringType(), + ]); + } + } + + 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')); From 5bf02f79ee68ac07a116a7bd53960573303165a0 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Fri, 8 Oct 2021 16:33:21 +0200 Subject: [PATCH 2/4] Cs --- src/Type/Symfony/ParameterDynamicReturnTypeExtension.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php index f1e3eb48..c6e58367 100644 --- a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php +++ b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php @@ -110,6 +110,10 @@ private function getGetTypeFromMethodCall( return $returnType; } + /** + * @param Scope $scope + * @param array|bool|float|int|string $value + */ private function generalizeTypeFromValue(Scope $scope, $value): Type { if (is_array($value) && $value !== []) { From b5a937301f171e720d5e3d31fceee380bc8a637a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 22 Jan 2022 10:33:58 +0100 Subject: [PATCH 3/4] Use type string resolver --- .../ParameterDynamicReturnTypeExtension.php | 45 +++++++------------ 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php index c6e58367..41fdf699 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; @@ -25,6 +26,7 @@ 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 @@ -45,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 @@ -134,33 +147,9 @@ private function generalizeTypeFromValue(Scope $scope, $value): Type && preg_match('/%env\((.*)\:.*\)%/U', $value, $matches) === 1 && strlen($matches[0]) === strlen($value) ) { - switch ($matches[1]) { - case 'base64': - case 'file': - case 'resolve': - case 'string': - case 'trim': - return new StringType(); - case 'bool': - return new BooleanType(); - case 'int': - return new IntegerType(); - case 'float': - return new FloatType(); - case 'csv': - case 'json': - case 'url': - case 'query_string': - return new ArrayType(new MixedType(), new MixedType()); - default: - return new UnionType([ - new ArrayType(new MixedType(), new MixedType()), - new BooleanType(), - new FloatType(), - new IntegerType(), - new StringType(), - ]); - } + $providedTypes = EnvVarProcessor::getProvidedTypes(); + + return $this->typeStringResolver->resolve($providedTypes[$matches[1]] ?? 'bool|int|float|string|array'); } return $this->generalizeType($scope->getTypeFromValue($value)); From 58b5a974852369901a6e0bd0889697a3b38a227f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 22 Jan 2022 10:36:04 +0100 Subject: [PATCH 4/4] Add class_exists check --- src/Type/Symfony/ParameterDynamicReturnTypeExtension.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php index 41fdf699..0de91de1 100644 --- a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php +++ b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php @@ -143,7 +143,8 @@ private function generalizeTypeFromValue(Scope $scope, $value): Type } if ( - is_string($value) + class_exists(EnvVarProcessor::class) + && is_string($value) && preg_match('/%env\((.*)\:.*\)%/U', $value, $matches) === 1 && strlen($matches[0]) === strlen($value) ) {