Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -66,6 +66,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 @@ -218,3 +225,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