Skip to content

Commit

Permalink
Understand types returned by Container::getParameter() and similar me…
Browse files Browse the repository at this point in the history
…thods
  • Loading branch information
Kocal authored and ondrejmirtes committed May 2, 2021
1 parent 2dad4de commit 4e4353e
Show file tree
Hide file tree
Showing 21 changed files with 1,020 additions and 9 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
25 changes: 25 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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%)
Expand Down Expand Up @@ -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]
43 changes: 43 additions & 0 deletions src/Symfony/DefaultParameterMap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php declare(strict_types = 1);

namespace PHPStan\Symfony;

use PhpParser\Node\Expr;
use PHPStan\Analyser\Scope;
use PHPStan\Type\TypeUtils;
use function count;

final class DefaultParameterMap implements ParameterMap
{

/** @var \PHPStan\Symfony\ParameterDefinition[] */
private $parameters;

/**
* @param \PHPStan\Symfony\ParameterDefinition[] $parameters
*/
public function __construct(array $parameters)
{
$this->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;
}

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

namespace PHPStan\Symfony;

use PhpParser\Node\Expr;
use PHPStan\Analyser\Scope;

final class FakeParameterMap implements ParameterMap
{

/**
* @return \PHPStan\Symfony\ParameterDefinition[]
*/
public function getParameters(): array
{
return [];
}

public function getParameter(string $key): ?ParameterDefinition
{
return null;
}

public static function getParameterKeyFromNode(Expr $node, Scope $scope): ?string
{
return null;
}

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

namespace PHPStan\Symfony;

final class Parameter implements ParameterDefinition
{

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

/** @var array<mixed>|bool|float|int|string */
private $value;

/**
* @param string $key
* @param array<mixed>|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<mixed>|bool|float|int|string
*/
public function getValue()
{
return $this->value;
}

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

namespace PHPStan\Symfony;

interface ParameterDefinition
{

public function getKey(): string;

/**
* @return array<mixed>|bool|float|int|string
*/
public function getValue();

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

namespace PHPStan\Symfony;

use PhpParser\Node\Expr;
use PHPStan\Analyser\Scope;

interface ParameterMap
{

/**
* @return \PHPStan\Symfony\ParameterDefinition[]
*/
public function getParameters(): array;

public function getParameter(string $key): ?ParameterDefinition;

public static function getParameterKeyFromNode(Expr $node, Scope $scope): ?string;

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

namespace PHPStan\Symfony;

interface ParameterMapFactory
{

public function create(): ParameterMap;

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

namespace PHPStan\Symfony;

use function sprintf;

final class XmlParameterMapFactory implements ParameterMapFactory
{

/** @var string|null */
private $containerXml;

public function __construct(?string $containerXml)
{
$this->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<mixed>|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;
}

}
Loading

0 comments on commit 4e4353e

Please sign in to comment.