From 4e4353ef28765f1b5e0ebab1bced58f087f2044b Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Sun, 2 May 2021 11:02:01 +0200 Subject: [PATCH] Understand types returned by Container::getParameter() and similar methods --- README.md | 7 +- composer.json | 4 +- extension.neon | 25 +++ src/Symfony/DefaultParameterMap.php | 43 ++++++ src/Symfony/FakeParameterMap.php | 29 ++++ src/Symfony/Parameter.php | 40 +++++ src/Symfony/ParameterDefinition.php | 15 ++ src/Symfony/ParameterMap.php | 20 +++ src/Symfony/ParameterMapFactory.php | 10 ++ src/Symfony/XmlParameterMapFactory.php | 106 +++++++++++++ .../ParameterDynamicReturnTypeExtension.php | 127 +++++++++++++++ tests/Symfony/DefaultParameterMapTest.php | 144 ++++++++++++++++++ tests/Symfony/container.xml | 32 ++++ .../Symfony/containers/bugfix%2Fcontainer.xml | 32 ++++ tests/Type/Symfony/ExtensionTest.php | 4 + .../Symfony/ExtensionTestWithoutContainer.php | 24 ++- tests/Type/Symfony/container.xml | 32 ++++ .../data/ExampleAbstractController.php | 96 ++++++++++++ ...mpleAbstractControllerWithoutContainer.php | 95 ++++++++++++ tests/Type/Symfony/data/ExampleController.php | 72 +++++++++ .../ExampleControllerWithoutContainer.php | 72 +++++++++ 21 files changed, 1020 insertions(+), 9 deletions(-) create mode 100644 src/Symfony/DefaultParameterMap.php create mode 100644 src/Symfony/FakeParameterMap.php create mode 100644 src/Symfony/Parameter.php create mode 100644 src/Symfony/ParameterDefinition.php create mode 100644 src/Symfony/ParameterMap.php create mode 100644 src/Symfony/ParameterMapFactory.php create mode 100644 src/Symfony/XmlParameterMapFactory.php create mode 100644 src/Type/Symfony/ParameterDynamicReturnTypeExtension.php create mode 100644 tests/Symfony/DefaultParameterMapTest.php create mode 100644 tests/Type/Symfony/data/ExampleAbstractController.php create mode 100644 tests/Type/Symfony/data/ExampleAbstractControllerWithoutContainer.php diff --git a/README.md b/README.md index 4cb627e4..b853b3fa 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,11 @@ This extension provides following features: * Provides correct return type for `ContainerInterface::get()` and `::has()` methods. * Provides correct return type for `Controller::get()` and `::has()` methods. +* Provides correct return type for `AbstractController::get()` and `::has()` methods. +* Provides correct return type for `ContainerInterface::getParameter()` and `::hasParameter()` methods. +* Provides correct return type for `ParameterBagInterface::get()` and `::has()` methods. +* Provides correct return type for `Controller::getParameter()` method. +* Provides correct return type for `AbstractController::getParameter()` method. * Provides correct return type for `Request::getContent()` method based on the `$asResource` parameter. * Provides correct return type for `HeaderBag::get()` method based on the `$first` parameter. * Provides correct return type for `Envelope::all()` method based on the `$stampFqcn` parameter. @@ -57,7 +62,7 @@ You have to provide a path to `srcDevDebugProjectContainer.xml` or similar XML f parameters: symfony: container_xml_path: var/cache/dev/srcDevDebugProjectContainer.xml - # or with Symfony 4.2+ + # or with Symfony 4.2+ container_xml_path: var/cache/dev/srcApp_KernelDevDebugContainer.xml # or with Symfony 5+ container_xml_path: var/cache/dev/App_KernelDevDebugContainer.xml diff --git a/composer.json b/composer.json index 20f2fce2..23f4f7a9 100644 --- a/composer.json +++ b/composer.json @@ -26,9 +26,9 @@ "phpstan/phpstan-phpunit": "^0.12.16", "phpstan/phpstan-strict-rules": "^0.12.5", "phpunit/phpunit": "^7.5.20", - "symfony/console": "^4.0 || ^5.0", "symfony/config": "^4.2 || ^5.0", - "symfony/framework-bundle": "^4.0 || ^5.0", + "symfony/console": "^4.0 || ^5.0", + "symfony/framework-bundle": "^4.4 || ^5.0", "symfony/http-foundation": "^4.0 || ^5.0", "symfony/messenger": "^4.2 || ^5.0", "symfony/serializer": "^4.0 || ^5.0" diff --git a/extension.neon b/extension.neon index 002b98a4..1b74bc46 100644 --- a/extension.neon +++ b/extension.neon @@ -63,6 +63,13 @@ services: - factory: @symfony.serviceMapFactory::create() + # parameter map + symfony.parameterMapFactory: + class: PHPStan\Symfony\ParameterMapFactory + factory: PHPStan\Symfony\XmlParameterMapFactory(%symfony.container_xml_path%) + - + factory: @symfony.parameterMapFactory::create() + # ControllerTrait::get()/has() return type - factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, %symfony.constant_hassers%) @@ -215,3 +222,21 @@ services: - class: PHPStan\Type\Symfony\KernelInterfaceDynamicReturnTypeExtension tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + + # ParameterBagInterface::get()/has() return type + - + factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface, 'get', 'has', %symfony.constant_hassers%) + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + + # ContainerInterface::getParameter()/hasParameter() return type + - + factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, 'getParameter', 'hasParameter', %symfony.constant_hassers%) + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + + # (Abstract)Controller::getParameter() return type + - + factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController, 'getParameter', null, %symfony.constant_hassers%) + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + - + factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller, 'getParameter', null, %symfony.constant_hassers%) + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] diff --git a/src/Symfony/DefaultParameterMap.php b/src/Symfony/DefaultParameterMap.php new file mode 100644 index 00000000..a58ed9fc --- /dev/null +++ b/src/Symfony/DefaultParameterMap.php @@ -0,0 +1,43 @@ +parameters = $parameters; + } + + /** + * @return \PHPStan\Symfony\ParameterDefinition[] + */ + public function getParameters(): array + { + return $this->parameters; + } + + public function getParameter(string $key): ?ParameterDefinition + { + return $this->parameters[$key] ?? null; + } + + public static function getParameterKeyFromNode(Expr $node, Scope $scope): ?string + { + $strings = TypeUtils::getConstantStrings($scope->getType($node)); + return count($strings) === 1 ? $strings[0]->getValue() : null; + } + +} diff --git a/src/Symfony/FakeParameterMap.php b/src/Symfony/FakeParameterMap.php new file mode 100644 index 00000000..7e723411 --- /dev/null +++ b/src/Symfony/FakeParameterMap.php @@ -0,0 +1,29 @@ +|bool|float|int|string */ + private $value; + + /** + * @param string $key + * @param array|bool|float|int|string $value + */ + public function __construct( + string $key, + $value + ) + { + $this->key = $key; + $this->value = $value; + } + + public function getKey(): string + { + return $this->key; + } + + /** + * @return array|bool|float|int|string + */ + public function getValue() + { + return $this->value; + } + +} diff --git a/src/Symfony/ParameterDefinition.php b/src/Symfony/ParameterDefinition.php new file mode 100644 index 00000000..e1aa2eaa --- /dev/null +++ b/src/Symfony/ParameterDefinition.php @@ -0,0 +1,15 @@ +|bool|float|int|string + */ + public function getValue(); + +} diff --git a/src/Symfony/ParameterMap.php b/src/Symfony/ParameterMap.php new file mode 100644 index 00000000..fc14fb2c --- /dev/null +++ b/src/Symfony/ParameterMap.php @@ -0,0 +1,20 @@ +containerXml = $containerXml; + } + + public function create(): ParameterMap + { + if ($this->containerXml === null) { + return new FakeParameterMap(); + } + + $fileContents = file_get_contents($this->containerXml); + if ($fileContents === false) { + throw new XmlContainerNotExistsException(sprintf('Container %s does not exist', $this->containerXml)); + } + + $xml = @simplexml_load_string($fileContents); + if ($xml === false) { + throw new XmlContainerNotExistsException(sprintf('Container %s cannot be parsed', $this->containerXml)); + } + + /** @var \PHPStan\Symfony\Parameter[] $parameters */ + $parameters = []; + foreach ($xml->parameters->parameter as $def) { + /** @var \SimpleXMLElement $attrs */ + $attrs = $def->attributes(); + + $parameter = new Parameter( + (string) $attrs->key, + $this->getNodeValue($def) + ); + + $parameters[$parameter->getKey()] = $parameter; + } + + return new DefaultParameterMap($parameters); + } + + /** + * @return array|bool|float|int|string + */ + private function getNodeValue(\SimpleXMLElement $def) + { + /** @var \SimpleXMLElement $attrs */ + $attrs = $def->attributes(); + + $value = null; + switch ((string) $attrs->type) { + case 'collection': + $value = []; + foreach ($def->children() as $child) { + /** @var \SimpleXMLElement $childAttrs */ + $childAttrs = $child->attributes(); + + if (isset($childAttrs->key)) { + $value[(string) $childAttrs->key] = $this->getNodeValue($child); + } else { + $value[] = $this->getNodeValue($child); + } + } + break; + + case 'string': + $value = (string) $def; + break; + + case 'binary': + $value = base64_decode((string) $def, true); + if ($value === false) { + throw new \InvalidArgumentException(sprintf('Parameter "%s" of binary type is not valid base64 encoded string.', (string) $attrs->key)); + } + + break; + + default: + $value = (string) $def; + + if (is_numeric($value)) { + if (strpos($value, '.') !== false) { + $value = (float) $value; + } else { + $value = (int) $value; + } + } elseif ($value === 'true') { + $value = true; + } elseif ($value === 'false') { + $value = false; + } + } + + return $value; + } + +} diff --git a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php new file mode 100644 index 00000000..adce58fb --- /dev/null +++ b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php @@ -0,0 +1,127 @@ +className = $className; + $this->methodGet = $methodGet; + $this->methodHas = $methodHas; + $this->constantHassers = $constantHassers; + $this->parameterMap = $symfonyParameterMap; + } + + public function getClass(): string + { + return $this->className; + } + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + $methods = array_filter([$this->methodGet, $this->methodHas], function (?string $method): bool { + return $method !== null; + }); + + return in_array($methodReflection->getName(), $methods, true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + switch ($methodReflection->getName()) { + case $this->methodGet: + return $this->getGetTypeFromMethodCall($methodReflection, $methodCall, $scope); + case $this->methodHas: + return $this->getHasTypeFromMethodCall($methodReflection, $methodCall, $scope); + } + throw new ShouldNotHappenException(); + } + + private function getGetTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type + { + // We don't use the method's return type because this won't work properly with lowest and + // highest versions of Symfony ("mixed" for lowest, "array|bool|float|integer|string|null" for highest). + $returnType = new UnionType([ + new ArrayType(new MixedType(), new MixedType()), + new BooleanType(), + new FloatType(), + new IntegerType(), + new StringType(), + new NullType(), + ]); + if (!isset($methodCall->args[0])) { + return $returnType; + } + + $parameterKey = $this->parameterMap::getParameterKeyFromNode($methodCall->args[0]->value, $scope); + if ($parameterKey !== null) { + $parameter = $this->parameterMap->getParameter($parameterKey); + if ($parameter !== null) { + return $scope->getTypeFromValue($parameter->getValue()); + } + } + + return $returnType; + } + + private function getHasTypeFromMethodCall( + MethodReflection $methodReflection, + MethodCall $methodCall, + Scope $scope + ): Type + { + $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + if (!isset($methodCall->args[0]) || !$this->constantHassers) { + return $returnType; + } + + $parameterKey = $this->parameterMap::getParameterKeyFromNode($methodCall->args[0]->value, $scope); + if ($parameterKey !== null) { + $parameter = $this->parameterMap->getParameter($parameterKey); + return new ConstantBooleanType($parameter !== null); + } + + return $returnType; + } + +} diff --git a/tests/Symfony/DefaultParameterMapTest.php b/tests/Symfony/DefaultParameterMapTest.php new file mode 100644 index 00000000..0b97d078 --- /dev/null +++ b/tests/Symfony/DefaultParameterMapTest.php @@ -0,0 +1,144 @@ +create()->getParameter($key)); + } + + public function testGetParameterEscapedPath(): void + { + $factory = new XmlParameterMapFactory(__DIR__ . '/containers/bugfix%2Fcontainer.xml'); + $serviceMap = $factory->create(); + + self::assertNotNull($serviceMap->getParameter('app.string')); + } + + /** + * @return \Iterator + */ + public function getParameterProvider(): Iterator + { + yield [ + 'unknown', + function (?Parameter $parameter): void { + self::assertNull($parameter); + }, + ]; + yield [ + 'app.string', + function (?Parameter $parameter): void { + self::assertNotNull($parameter); + self::assertSame('app.string', $parameter->getKey()); + self::assertSame('abcdef', $parameter->getValue()); + }, + ]; + yield [ + 'app.int', + function (?Parameter $parameter): void { + self::assertNotNull($parameter); + self::assertSame('app.int', $parameter->getKey()); + self::assertSame(123, $parameter->getValue()); + }, + ]; + yield [ + 'app.int_as_string', + function (?Parameter $parameter): void { + self::assertNotNull($parameter); + self::assertSame('app.int_as_string', $parameter->getKey()); + self::assertSame('123', $parameter->getValue()); + }, + ]; + yield [ + 'app.float', + function (?Parameter $parameter): void { + self::assertNotNull($parameter); + self::assertSame('app.float', $parameter->getKey()); + self::assertSame(123.45, $parameter->getValue()); + }, + ]; + yield [ + 'app.float_as_string', + function (?Parameter $parameter): void { + self::assertNotNull($parameter); + self::assertSame('app.float_as_string', $parameter->getKey()); + self::assertSame('123.45', $parameter->getValue()); + }, + ]; + yield [ + 'app.boolean', + function (?Parameter $parameter): void { + self::assertNotNull($parameter); + self::assertSame('app.boolean', $parameter->getKey()); + self::assertTrue($parameter->getValue()); + }, + ]; + yield [ + 'app.boolean_as_string', + function (?Parameter $parameter): void { + self::assertNotNull($parameter); + self::assertSame('app.boolean_as_string', $parameter->getKey()); + self::assertSame('true', $parameter->getValue()); + }, + ]; + yield [ + 'app.list', + function (?Parameter $parameter): void { + self::assertNotNull($parameter); + self::assertSame('app.list', $parameter->getKey()); + self::assertEquals(['en', 'es', 'fr'], $parameter->getValue()); + }, + ]; + yield [ + 'app.list_of_list', + function (?Parameter $parameter): void { + self::assertNotNull($parameter); + self::assertSame('app.list_of_list', $parameter->getKey()); + self::assertEquals([ + ['name' => 'the name', 'value' => 'the value'], + ['name' => 'another name', 'value' => 'another value'], + ], $parameter->getValue()); + }, + ]; + yield [ + 'app.map', + function (?Parameter $parameter): void { + self::assertNotNull($parameter); + self::assertSame('app.map', $parameter->getKey()); + self::assertEquals([ + 'a' => 'value of a', + 'b' => 'value of b', + 'c' => 'value of c', + ], $parameter->getValue()); + }, + ]; + yield [ + 'app.binary', + function (?Parameter $parameter): void { + self::assertNotNull($parameter); + self::assertSame('app.binary', $parameter->getKey()); + self::assertSame('This is a Bell char ', $parameter->getValue()); + }, + ]; + yield [ + 'app.constant', + function (?Parameter $parameter): void { + self::assertNotNull($parameter); + self::assertSame('app.constant', $parameter->getKey()); + self::assertSame('Y-m-d\TH:i:sP', $parameter->getValue()); + }, + ]; + } + +} diff --git a/tests/Symfony/container.xml b/tests/Symfony/container.xml index 8c8fbd55..5bed7715 100644 --- a/tests/Symfony/container.xml +++ b/tests/Symfony/container.xml @@ -1,5 +1,37 @@ + + abcdef + 123 + 123 + 123.45 + 123.45 + true + true + + en + es + fr + + + + the name + the value + + + another name + another value + + + + value of a + value of b + value of c + + VGhpcyBpcyBhIEJlbGwgY2hhciAH + Y-m-d\TH:i:sP + + diff --git a/tests/Symfony/containers/bugfix%2Fcontainer.xml b/tests/Symfony/containers/bugfix%2Fcontainer.xml index 8c8fbd55..5bed7715 100644 --- a/tests/Symfony/containers/bugfix%2Fcontainer.xml +++ b/tests/Symfony/containers/bugfix%2Fcontainer.xml @@ -1,5 +1,37 @@ + + abcdef + 123 + 123 + 123.45 + 123.45 + true + true + + en + es + fr + + + + the name + the value + + + another name + another value + + + + value of a + value of b + value of c + + VGhpcyBpcyBhIEJlbGwgY2hhciAH + Y-m-d\TH:i:sP + + diff --git a/tests/Type/Symfony/ExtensionTest.php b/tests/Type/Symfony/ExtensionTest.php index 2c6a0762..a2de90ec 100644 --- a/tests/Type/Symfony/ExtensionTest.php +++ b/tests/Type/Symfony/ExtensionTest.php @@ -37,6 +37,10 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/ExampleController.php'); } + if (class_exists('Symfony\Bundle\FrameworkBundle\Controller\AbstractController')) { + yield from $this->gatherAssertTypes(__DIR__ . '/data/ExampleAbstractController.php'); + } + yield from $this->gatherAssertTypes(__DIR__ . '/data/serializer.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/denormalizer.php'); } diff --git a/tests/Type/Symfony/ExtensionTestWithoutContainer.php b/tests/Type/Symfony/ExtensionTestWithoutContainer.php index 85b52315..997b7e02 100644 --- a/tests/Type/Symfony/ExtensionTestWithoutContainer.php +++ b/tests/Type/Symfony/ExtensionTestWithoutContainer.php @@ -8,13 +8,28 @@ class ExtensionTestWithoutContainer extends TypeInferenceTestCase { /** @return mixed[] */ - public function dataFileAsserts(): iterable + public function dataExampleController(): iterable { - yield from $this->gatherAssertTypes(__DIR__ . '/data/ExampleControllerWithoutContainer.php'); + if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) { + return; + } + + yield from $this->gatherAssertTypes(__DIR__ . '/data/ExampleController.php'); + } + + /** @return mixed[] */ + public function dataAbstractController(): iterable + { + if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\AbstractController')) { + return; + } + + yield from $this->gatherAssertTypes(__DIR__ . '/data/ExampleAbstractController.php'); } /** - * @dataProvider dataFileAsserts + * @dataProvider dataExampleController + * @dataProvider dataAbstractController * @param string $assertType * @param string $file * @param mixed ...$args @@ -25,9 +40,6 @@ public function testFileAsserts( ...$args ): void { - if (!class_exists('Symfony\Bundle\FrameworkBundle\Controller\Controller')) { - self::markTestSkipped('Needs class Symfony\Bundle\FrameworkBundle\Controller\Controller'); - } $this->assertFileAsserts($assertType, $file, ...$args); } diff --git a/tests/Type/Symfony/container.xml b/tests/Type/Symfony/container.xml index 978519d4..af82fd19 100644 --- a/tests/Type/Symfony/container.xml +++ b/tests/Type/Symfony/container.xml @@ -1,5 +1,37 @@ + + abcdef + 123 + 123 + 123.45 + 123.45 + true + true + + en + es + fr + + + + the name + the value + + + another name + another value + + + + value of a + value of b + value of c + + VGhpcyBpcyBhIEJlbGwgY2hhciAH + Y-m-d\TH:i:sP + + diff --git a/tests/Type/Symfony/data/ExampleAbstractController.php b/tests/Type/Symfony/data/ExampleAbstractController.php new file mode 100644 index 00000000..2bff7543 --- /dev/null +++ b/tests/Type/Symfony/data/ExampleAbstractController.php @@ -0,0 +1,96 @@ +get('foo')); + assertType('object', $this->get('bar')); + assertType('object', $this->get(doFoo())); + assertType('object', $this->get()); + + assertType('true', $this->has('foo')); + assertType('false', $this->has('bar')); + assertType('bool', $this->has(doFoo())); + assertType('bool', $this->has()); + } + + public function parameters(ContainerInterface $container, ParameterBagInterface $parameterBag): void + { + assertType('array|bool|float|int|string|null', $container->getParameter('unknown')); + assertType('array|bool|float|int|string|null', $parameterBag->get('unknown')); + assertType('array|bool|float|int|string|null', $this->getParameter('unknown')); + assertType("'abcdef'", $container->getParameter('app.string')); + assertType("'abcdef'", $parameterBag->get('app.string')); + assertType("'abcdef'", $this->getParameter('app.string')); + assertType('123', $container->getParameter('app.int')); + assertType('123', $parameterBag->get('app.int')); + assertType('123', $this->getParameter('app.int')); + assertType("'123'", $container->getParameter('app.int_as_string')); + assertType("'123'", $parameterBag->get('app.int_as_string')); + assertType("'123'", $this->getParameter('app.int_as_string')); + assertType('123.45', $container->getParameter('app.float')); + assertType('123.45', $parameterBag->get('app.float')); + assertType('123.45', $this->getParameter('app.float')); + assertType("'123.45'", $container->getParameter('app.float_as_string')); + assertType("'123.45'", $parameterBag->get('app.float_as_string')); + assertType("'123.45'", $this->getParameter('app.float_as_string')); + assertType('true', $container->getParameter('app.boolean')); + assertType('true', $parameterBag->get('app.boolean')); + assertType('true', $this->getParameter('app.boolean')); + assertType("'true'", $container->getParameter('app.boolean_as_string')); + assertType("'true'", $parameterBag->get('app.boolean_as_string')); + assertType("'true'", $this->getParameter('app.boolean_as_string')); + assertType("array('en', 'es', 'fr')", $container->getParameter('app.list')); + assertType("array('en', 'es', 'fr')", $parameterBag->get('app.list')); + assertType("array('en', 'es', 'fr')", $this->getParameter('app.list')); + assertType("array(array('name' => 'the name', 'value' => 'the value'), array('name' => 'another name', 'value' => 'another value'))", $container->getParameter('app.list_of_list')); + assertType("array(array('name' => 'the name', 'value' => 'the value'), array('name' => 'another name', 'value' => 'another value'))", $parameterBag->get('app.list_of_list')); + assertType("array(array('name' => 'the name', 'value' => 'the value'), array('name' => 'another name', 'value' => 'another value'))", $this->getParameter('app.list_of_list')); + assertType("array('a' => 'value of a', 'b' => 'value of b', 'c' => 'value of c')", $container->getParameter('app.map')); + assertType("array('a' => 'value of a', 'b' => 'value of b', 'c' => 'value of c')", $parameterBag->get('app.map')); + assertType("array('a' => 'value of a', 'b' => 'value of b', 'c' => 'value of c')", $this->getParameter('app.map')); + assertType("'This is a Bell char '", $container->getParameter('app.binary')); + assertType("'This is a Bell char '", $parameterBag->get('app.binary')); + assertType("'This is a Bell char '", $this->getParameter('app.binary')); + assertType("'Y-m-d\\\\TH:i:sP'", $container->getParameter('app.constant')); + assertType("'Y-m-d\\\\TH:i:sP'", $parameterBag->get('app.constant')); + assertType("'Y-m-d\\\\TH:i:sP'", $this->getParameter('app.constant')); + + assertType('false', $container->hasParameter('unknown')); + assertType('false', $parameterBag->has('unknown')); + assertType('true', $container->hasParameter('app.string')); + assertType('true', $parameterBag->has('app.string')); + assertType('true', $container->hasParameter('app.int')); + 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.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.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.list')); + assertType('true', $parameterBag->has('app.list')); + assertType('true', $container->hasParameter('app.list_of_list')); + assertType('true', $parameterBag->has('app.list_of_list')); + assertType('true', $container->hasParameter('app.map')); + assertType('true', $parameterBag->has('app.map')); + assertType('true', $container->hasParameter('app.binary')); + assertType('true', $parameterBag->has('app.binary')); + assertType('true', $container->hasParameter('app.constant')); + assertType('true', $parameterBag->has('app.constant')); + } + +} diff --git a/tests/Type/Symfony/data/ExampleAbstractControllerWithoutContainer.php b/tests/Type/Symfony/data/ExampleAbstractControllerWithoutContainer.php new file mode 100644 index 00000000..28773829 --- /dev/null +++ b/tests/Type/Symfony/data/ExampleAbstractControllerWithoutContainer.php @@ -0,0 +1,95 @@ +get('foo')); + assertType('object', $this->get('bar')); + assertType('object', $this->get(doFoo())); + assertType('object', $this->get()); + + assertType('bool', $this->has('foo')); + assertType('bool', $this->has('bar')); + assertType('bool', $this->has(doFoo())); + assertType('bool', $this->has()); + } + + public function parameters(ContainerInterface $container, ParameterBagInterface $parameterBag): void + { + assertType('array|bool|float|int|string|null', $container->getParameter('unknown')); + assertType('array|bool|float|int|string|null', $parameterBag->get('unknown')); + assertType('array|bool|float|int|string|null', $this->getParameter('unknown')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.string')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.string')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.string')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.int')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.int')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.int')); + 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.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.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.list')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.list')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.list')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.list_of_list')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.list_of_list')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.list_of_list')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.map')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.map')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.map')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.binary')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.binary')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.binary')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.constant')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.constant')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.constant')); + + assertType('bool', $container->hasParameter('unknown')); + assertType('bool', $parameterBag->has('unknown')); + assertType('bool', $container->hasParameter('app.string')); + assertType('bool', $parameterBag->has('app.string')); + assertType('bool', $container->hasParameter('app.int')); + 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.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.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.list')); + assertType('bool', $parameterBag->has('app.list')); + assertType('bool', $container->hasParameter('app.list_of_list')); + assertType('bool', $parameterBag->has('app.list_of_list')); + assertType('bool', $container->hasParameter('app.map')); + assertType('bool', $parameterBag->has('app.map')); + assertType('bool', $container->hasParameter('app.binary')); + assertType('bool', $parameterBag->has('app.binary')); + assertType('bool', $container->hasParameter('app.constant')); + assertType('bool', $parameterBag->has('app.constant')); + } + +} diff --git a/tests/Type/Symfony/data/ExampleController.php b/tests/Type/Symfony/data/ExampleController.php index ce22ac07..db761227 100644 --- a/tests/Type/Symfony/data/ExampleController.php +++ b/tests/Type/Symfony/data/ExampleController.php @@ -3,6 +3,8 @@ namespace PHPStan\Type\Symfony; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use function PHPStan\Testing\assertType; final class ExampleController extends Controller @@ -21,4 +23,74 @@ public function services(): void assertType('bool', $this->has()); } + public function parameters(ContainerInterface $container, ParameterBagInterface $parameterBag): void + { + assertType('array|bool|float|int|string|null', $container->getParameter('unknown')); + assertType('array|bool|float|int|string|null', $parameterBag->get('unknown')); + assertType('array|bool|float|int|string|null', $this->getParameter('unknown')); + assertType("'abcdef'", $container->getParameter('app.string')); + assertType("'abcdef'", $parameterBag->get('app.string')); + assertType("'abcdef'", $this->getParameter('app.string')); + assertType('123', $container->getParameter('app.int')); + assertType('123', $parameterBag->get('app.int')); + assertType('123', $this->getParameter('app.int')); + assertType("'123'", $container->getParameter('app.int_as_string')); + assertType("'123'", $parameterBag->get('app.int_as_string')); + assertType("'123'", $this->getParameter('app.int_as_string')); + assertType('123.45', $container->getParameter('app.float')); + assertType('123.45', $parameterBag->get('app.float')); + assertType('123.45', $this->getParameter('app.float')); + assertType("'123.45'", $container->getParameter('app.float_as_string')); + assertType("'123.45'", $parameterBag->get('app.float_as_string')); + assertType("'123.45'", $this->getParameter('app.float_as_string')); + assertType('true', $container->getParameter('app.boolean')); + assertType('true', $parameterBag->get('app.boolean')); + assertType('true', $this->getParameter('app.boolean')); + assertType("'true'", $container->getParameter('app.boolean_as_string')); + assertType("'true'", $parameterBag->get('app.boolean_as_string')); + assertType("'true'", $this->getParameter('app.boolean_as_string')); + assertType("array('en', 'es', 'fr')", $container->getParameter('app.list')); + assertType("array('en', 'es', 'fr')", $parameterBag->get('app.list')); + assertType("array('en', 'es', 'fr')", $this->getParameter('app.list')); + assertType("array(array('name' => 'the name', 'value' => 'the value'), array('name' => 'another name', 'value' => 'another value'))", $container->getParameter('app.list_of_list')); + assertType("array(array('name' => 'the name', 'value' => 'the value'), array('name' => 'another name', 'value' => 'another value'))", $parameterBag->get('app.list_of_list')); + assertType("array(array('name' => 'the name', 'value' => 'the value'), array('name' => 'another name', 'value' => 'another value'))", $this->getParameter('app.list_of_list')); + assertType("array('a' => 'value of a', 'b' => 'value of b', 'c' => 'value of c')", $container->getParameter('app.map')); + assertType("array('a' => 'value of a', 'b' => 'value of b', 'c' => 'value of c')", $parameterBag->get('app.map')); + assertType("array('a' => 'value of a', 'b' => 'value of b', 'c' => 'value of c')", $this->getParameter('app.map')); + assertType("'This is a Bell char '", $container->getParameter('app.binary')); + assertType("'This is a Bell char '", $parameterBag->get('app.binary')); + assertType("'This is a Bell char '", $this->getParameter('app.binary')); + assertType("'Y-m-d\\\\TH:i:sP'", $container->getParameter('app.constant')); + assertType("'Y-m-d\\\\TH:i:sP'", $parameterBag->get('app.constant')); + assertType("'Y-m-d\\\\TH:i:sP'", $this->getParameter('app.constant')); + + assertType('false', $container->hasParameter('unknown')); + assertType('false', $parameterBag->has('unknown')); + assertType('true', $container->hasParameter('app.string')); + assertType('true', $parameterBag->has('app.string')); + assertType('true', $container->hasParameter('app.int')); + 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.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.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.list')); + assertType('true', $parameterBag->has('app.list')); + assertType('true', $container->hasParameter('app.list_of_list')); + assertType('true', $parameterBag->has('app.list_of_list')); + assertType('true', $container->hasParameter('app.map')); + assertType('true', $parameterBag->has('app.map')); + assertType('true', $container->hasParameter('app.binary')); + assertType('true', $parameterBag->has('app.binary')); + assertType('true', $container->hasParameter('app.constant')); + assertType('true', $parameterBag->has('app.constant')); + } + } diff --git a/tests/Type/Symfony/data/ExampleControllerWithoutContainer.php b/tests/Type/Symfony/data/ExampleControllerWithoutContainer.php index f127d976..2adfd7d5 100644 --- a/tests/Type/Symfony/data/ExampleControllerWithoutContainer.php +++ b/tests/Type/Symfony/data/ExampleControllerWithoutContainer.php @@ -3,6 +3,8 @@ namespace PHPStan\Type\Symfony; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; final class ExampleControllerWithoutContainer extends Controller { @@ -20,4 +22,74 @@ public function services(): void assertType('bool', $this->has()); } + public function parameters(ContainerInterface $container, ParameterBagInterface $parameterBag): void + { + assertType('array|bool|float|int|string|null', $container->getParameter('unknown')); + assertType('array|bool|float|int|string|null', $parameterBag->get('unknown')); + assertType('array|bool|float|int|string|null', $this->getParameter('unknown')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.string')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.string')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.string')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.int')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.int')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.int')); + 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.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.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.list')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.list')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.list')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.list_of_list')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.list_of_list')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.list_of_list')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.map')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.map')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.map')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.binary')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.binary')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.binary')); + assertType('array|bool|float|int|string|null', $container->getParameter('app.constant')); + assertType('array|bool|float|int|string|null', $parameterBag->get('app.constant')); + assertType('array|bool|float|int|string|null', $this->getParameter('app.constant')); + + assertType('bool', $container->hasParameter('unknown')); + assertType('bool', $parameterBag->has('unknown')); + assertType('bool', $container->hasParameter('app.string')); + assertType('bool', $parameterBag->has('app.string')); + assertType('bool', $container->hasParameter('app.int')); + 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.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.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.list')); + assertType('bool', $parameterBag->has('app.list')); + assertType('bool', $container->hasParameter('app.list_of_list')); + assertType('bool', $parameterBag->has('app.list_of_list')); + assertType('bool', $container->hasParameter('app.map')); + assertType('bool', $parameterBag->has('app.map')); + assertType('bool', $container->hasParameter('app.binary')); + assertType('bool', $parameterBag->has('app.binary')); + assertType('bool', $container->hasParameter('app.constant')); + assertType('bool', $parameterBag->has('app.constant')); + } + }