Skip to content

Commit 4e4353e

Browse files
Kocalondrejmirtes
authored andcommitted
Understand types returned by Container::getParameter() and similar methods
1 parent 2dad4de commit 4e4353e

21 files changed

+1020
-9
lines changed

Diff for: README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ This extension provides following features:
1010

1111
* Provides correct return type for `ContainerInterface::get()` and `::has()` methods.
1212
* Provides correct return type for `Controller::get()` and `::has()` methods.
13+
* Provides correct return type for `AbstractController::get()` and `::has()` methods.
14+
* Provides correct return type for `ContainerInterface::getParameter()` and `::hasParameter()` methods.
15+
* Provides correct return type for `ParameterBagInterface::get()` and `::has()` methods.
16+
* Provides correct return type for `Controller::getParameter()` method.
17+
* Provides correct return type for `AbstractController::getParameter()` method.
1318
* Provides correct return type for `Request::getContent()` method based on the `$asResource` parameter.
1419
* Provides correct return type for `HeaderBag::get()` method based on the `$first` parameter.
1520
* 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
5762
parameters:
5863
symfony:
5964
container_xml_path: var/cache/dev/srcDevDebugProjectContainer.xml
60-
# or with Symfony 4.2+
65+
# or with Symfony 4.2+
6166
container_xml_path: var/cache/dev/srcApp_KernelDevDebugContainer.xml
6267
# or with Symfony 5+
6368
container_xml_path: var/cache/dev/App_KernelDevDebugContainer.xml

Diff for: composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
"phpstan/phpstan-phpunit": "^0.12.16",
2727
"phpstan/phpstan-strict-rules": "^0.12.5",
2828
"phpunit/phpunit": "^7.5.20",
29-
"symfony/console": "^4.0 || ^5.0",
3029
"symfony/config": "^4.2 || ^5.0",
31-
"symfony/framework-bundle": "^4.0 || ^5.0",
30+
"symfony/console": "^4.0 || ^5.0",
31+
"symfony/framework-bundle": "^4.4 || ^5.0",
3232
"symfony/http-foundation": "^4.0 || ^5.0",
3333
"symfony/messenger": "^4.2 || ^5.0",
3434
"symfony/serializer": "^4.0 || ^5.0"

Diff for: extension.neon

+25
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ services:
6363
-
6464
factory: @symfony.serviceMapFactory::create()
6565

66+
# parameter map
67+
symfony.parameterMapFactory:
68+
class: PHPStan\Symfony\ParameterMapFactory
69+
factory: PHPStan\Symfony\XmlParameterMapFactory(%symfony.container_xml_path%)
70+
-
71+
factory: @symfony.parameterMapFactory::create()
72+
6673
# ControllerTrait::get()/has() return type
6774
-
6875
factory: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, %symfony.constant_hassers%)
@@ -215,3 +222,21 @@ services:
215222
-
216223
class: PHPStan\Type\Symfony\KernelInterfaceDynamicReturnTypeExtension
217224
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
225+
226+
# ParameterBagInterface::get()/has() return type
227+
-
228+
factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface, 'get', 'has', %symfony.constant_hassers%)
229+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
230+
231+
# ContainerInterface::getParameter()/hasParameter() return type
232+
-
233+
factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, 'getParameter', 'hasParameter', %symfony.constant_hassers%)
234+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
235+
236+
# (Abstract)Controller::getParameter() return type
237+
-
238+
factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController, 'getParameter', null, %symfony.constant_hassers%)
239+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
240+
-
241+
factory: PHPStan\Type\Symfony\ParameterDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller, 'getParameter', null, %symfony.constant_hassers%)
242+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]

Diff for: src/Symfony/DefaultParameterMap.php

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Symfony;
4+
5+
use PhpParser\Node\Expr;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Type\TypeUtils;
8+
use function count;
9+
10+
final class DefaultParameterMap implements ParameterMap
11+
{
12+
13+
/** @var \PHPStan\Symfony\ParameterDefinition[] */
14+
private $parameters;
15+
16+
/**
17+
* @param \PHPStan\Symfony\ParameterDefinition[] $parameters
18+
*/
19+
public function __construct(array $parameters)
20+
{
21+
$this->parameters = $parameters;
22+
}
23+
24+
/**
25+
* @return \PHPStan\Symfony\ParameterDefinition[]
26+
*/
27+
public function getParameters(): array
28+
{
29+
return $this->parameters;
30+
}
31+
32+
public function getParameter(string $key): ?ParameterDefinition
33+
{
34+
return $this->parameters[$key] ?? null;
35+
}
36+
37+
public static function getParameterKeyFromNode(Expr $node, Scope $scope): ?string
38+
{
39+
$strings = TypeUtils::getConstantStrings($scope->getType($node));
40+
return count($strings) === 1 ? $strings[0]->getValue() : null;
41+
}
42+
43+
}

Diff for: src/Symfony/FakeParameterMap.php

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Symfony;
4+
5+
use PhpParser\Node\Expr;
6+
use PHPStan\Analyser\Scope;
7+
8+
final class FakeParameterMap implements ParameterMap
9+
{
10+
11+
/**
12+
* @return \PHPStan\Symfony\ParameterDefinition[]
13+
*/
14+
public function getParameters(): array
15+
{
16+
return [];
17+
}
18+
19+
public function getParameter(string $key): ?ParameterDefinition
20+
{
21+
return null;
22+
}
23+
24+
public static function getParameterKeyFromNode(Expr $node, Scope $scope): ?string
25+
{
26+
return null;
27+
}
28+
29+
}

Diff for: src/Symfony/Parameter.php

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Symfony;
4+
5+
final class Parameter implements ParameterDefinition
6+
{
7+
8+
/** @var string */
9+
private $key;
10+
11+
/** @var array<mixed>|bool|float|int|string */
12+
private $value;
13+
14+
/**
15+
* @param string $key
16+
* @param array<mixed>|bool|float|int|string $value
17+
*/
18+
public function __construct(
19+
string $key,
20+
$value
21+
)
22+
{
23+
$this->key = $key;
24+
$this->value = $value;
25+
}
26+
27+
public function getKey(): string
28+
{
29+
return $this->key;
30+
}
31+
32+
/**
33+
* @return array<mixed>|bool|float|int|string
34+
*/
35+
public function getValue()
36+
{
37+
return $this->value;
38+
}
39+
40+
}

Diff for: src/Symfony/ParameterDefinition.php

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Symfony;
4+
5+
interface ParameterDefinition
6+
{
7+
8+
public function getKey(): string;
9+
10+
/**
11+
* @return array<mixed>|bool|float|int|string
12+
*/
13+
public function getValue();
14+
15+
}

Diff for: src/Symfony/ParameterMap.php

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Symfony;
4+
5+
use PhpParser\Node\Expr;
6+
use PHPStan\Analyser\Scope;
7+
8+
interface ParameterMap
9+
{
10+
11+
/**
12+
* @return \PHPStan\Symfony\ParameterDefinition[]
13+
*/
14+
public function getParameters(): array;
15+
16+
public function getParameter(string $key): ?ParameterDefinition;
17+
18+
public static function getParameterKeyFromNode(Expr $node, Scope $scope): ?string;
19+
20+
}

Diff for: src/Symfony/ParameterMapFactory.php

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Symfony;
4+
5+
interface ParameterMapFactory
6+
{
7+
8+
public function create(): ParameterMap;
9+
10+
}

Diff for: src/Symfony/XmlParameterMapFactory.php

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Symfony;
4+
5+
use function sprintf;
6+
7+
final class XmlParameterMapFactory implements ParameterMapFactory
8+
{
9+
10+
/** @var string|null */
11+
private $containerXml;
12+
13+
public function __construct(?string $containerXml)
14+
{
15+
$this->containerXml = $containerXml;
16+
}
17+
18+
public function create(): ParameterMap
19+
{
20+
if ($this->containerXml === null) {
21+
return new FakeParameterMap();
22+
}
23+
24+
$fileContents = file_get_contents($this->containerXml);
25+
if ($fileContents === false) {
26+
throw new XmlContainerNotExistsException(sprintf('Container %s does not exist', $this->containerXml));
27+
}
28+
29+
$xml = @simplexml_load_string($fileContents);
30+
if ($xml === false) {
31+
throw new XmlContainerNotExistsException(sprintf('Container %s cannot be parsed', $this->containerXml));
32+
}
33+
34+
/** @var \PHPStan\Symfony\Parameter[] $parameters */
35+
$parameters = [];
36+
foreach ($xml->parameters->parameter as $def) {
37+
/** @var \SimpleXMLElement $attrs */
38+
$attrs = $def->attributes();
39+
40+
$parameter = new Parameter(
41+
(string) $attrs->key,
42+
$this->getNodeValue($def)
43+
);
44+
45+
$parameters[$parameter->getKey()] = $parameter;
46+
}
47+
48+
return new DefaultParameterMap($parameters);
49+
}
50+
51+
/**
52+
* @return array<mixed>|bool|float|int|string
53+
*/
54+
private function getNodeValue(\SimpleXMLElement $def)
55+
{
56+
/** @var \SimpleXMLElement $attrs */
57+
$attrs = $def->attributes();
58+
59+
$value = null;
60+
switch ((string) $attrs->type) {
61+
case 'collection':
62+
$value = [];
63+
foreach ($def->children() as $child) {
64+
/** @var \SimpleXMLElement $childAttrs */
65+
$childAttrs = $child->attributes();
66+
67+
if (isset($childAttrs->key)) {
68+
$value[(string) $childAttrs->key] = $this->getNodeValue($child);
69+
} else {
70+
$value[] = $this->getNodeValue($child);
71+
}
72+
}
73+
break;
74+
75+
case 'string':
76+
$value = (string) $def;
77+
break;
78+
79+
case 'binary':
80+
$value = base64_decode((string) $def, true);
81+
if ($value === false) {
82+
throw new \InvalidArgumentException(sprintf('Parameter "%s" of binary type is not valid base64 encoded string.', (string) $attrs->key));
83+
}
84+
85+
break;
86+
87+
default:
88+
$value = (string) $def;
89+
90+
if (is_numeric($value)) {
91+
if (strpos($value, '.') !== false) {
92+
$value = (float) $value;
93+
} else {
94+
$value = (int) $value;
95+
}
96+
} elseif ($value === 'true') {
97+
$value = true;
98+
} elseif ($value === 'false') {
99+
$value = false;
100+
}
101+
}
102+
103+
return $value;
104+
}
105+
106+
}

0 commit comments

Comments
 (0)