Skip to content

Commit 298e1cc

Browse files
committed
Services and tests refactoring
1 parent d7d1624 commit 298e1cc

36 files changed

+799
-779
lines changed

Diff for: README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ includes:
2929
- vendor/phpstan/phpstan-symfony/extension.neon
3030
parameters:
3131
symfony:
32-
container_xml_path: %rootDir%/../../../var/cache/dev/appDevDebugProjectContainer.xml # or srcDevDebugProjectContainer.xml for Symfony 4+
32+
container_xml_path: %rootDir%/../../../var/cache/dev/srcDevDebugProjectContainer.xml
3333
```
3434

3535
## Limitations
3636

37-
You have to provide a path to `appDevDebugProjectContainer.xml` or similar xml file describing your container.
37+
You have to provide a path to `srcDevDebugProjectContainer.xml` or similar xml file describing your container.

Diff for: extension.neon

+29-24
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,38 @@
1+
rules:
2+
- PHPStan\Rules\Symfony\ContainerInterfacePrivateServiceRule
3+
- PHPStan\Rules\Symfony\ContainerInterfaceUnknownServiceRule
4+
15
services:
6+
# service map
7+
symfony.serviceMapFactory:
8+
class: PHPStan\Symfony\ServiceMapFactory
9+
factory: PHPStan\Symfony\XmlServiceMapFactory(%symfony.container_xml_path%)
210
-
3-
class: PHPStan\Type\Symfony\ContainerInterfaceDynamicReturnTypeExtension
4-
tags:
5-
- phpstan.broker.dynamicMethodReturnTypeExtension
11+
class: PHPStan\Symfony\ServiceMap(@symfony.serviceMapFactory::create())
12+
13+
# ControllerTrait::get()/has() return type
614
-
7-
class: PHPStan\Type\Symfony\ControllerDynamicReturnTypeExtension
8-
tags:
9-
- phpstan.broker.dynamicMethodReturnTypeExtension
15+
class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface)
16+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
1017
-
11-
class: PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension
12-
tags:
13-
- phpstan.broker.dynamicMethodReturnTypeExtension
18+
class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller)
19+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
1420
-
15-
class: PHPStan\Rules\Symfony\ContainerInterfacePrivateServiceRule
16-
tags:
17-
- phpstan.rules.rule
21+
class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController)
22+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
23+
24+
# ControllerTrait::has() type specification
1825
-
19-
class: PHPStan\Rules\Symfony\ContainerInterfaceUnknownServiceRule
20-
tags:
21-
- phpstan.rules.rule
26+
class: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Component\DependencyInjection\ContainerInterface)
27+
tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]
2228
-
23-
class: PHPStan\Type\Symfony\ContainerInterfaceMethodTypeSpecifyingExtension
24-
tags:
25-
- phpstan.typeSpecifier.methodTypeSpecifyingExtension
29+
class: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller)
30+
tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]
2631
-
27-
class: PHPStan\Type\Symfony\ControllerMethodTypeSpecifyingExtension
28-
tags:
29-
- phpstan.typeSpecifier.methodTypeSpecifyingExtension
32+
class: PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController)
33+
tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]
34+
35+
# Request::getContent() return type
3036
-
31-
class: PHPStan\Symfony\ServiceMap
32-
arguments:
33-
containerXml: %symfony.container_xml_path%
37+
class: PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension
38+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]

Diff for: phpcs.xml

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedExceptions"/>
88
<exclude name="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly"/>
99
<exclude name="Consistence.Exceptions.ExceptionDeclaration"/>
10+
<exclude name="Squiz.Commenting.FunctionComment.MissingParamTag"/>
1011
</rule>
1112
<rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses">
1213
<properties>
@@ -39,5 +40,6 @@
3940
<property name="rootNamespaces" type="array" value="src=>PHPStan,tests=>PHPStan"/>
4041
</properties>
4142
</rule>
42-
<exclude-pattern>tests/*/data</exclude-pattern>
43+
<exclude-pattern>tests/tmp</exclude-pattern>
44+
<exclude-pattern>tests/Symfony/ExampleContainer.php</exclude-pattern>
4345
</ruleset>

Diff for: phpstan.neon

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,7 @@ includes:
55

66
parameters:
77
excludes_analyse:
8-
- */tests/*/data/*
8+
- */tests/tmp/*
9+
- */tests/*/ExampleContainer.php
10+
- */tests/*/ExampleController.php
11+
- */tests/*/request_get_content.php

Diff for: src/Rules/Symfony/ContainerInterfacePrivateServiceRule.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PHPStan\Rules\Rule;
99
use PHPStan\Symfony\ServiceMap;
1010
use PHPStan\Type\ObjectType;
11+
use function sprintf;
1112

1213
final class ContainerInterfacePrivateServiceRule implements Rule
1314
{
@@ -42,9 +43,10 @@ public function processNode(Node $node, Scope $scope): array
4243

4344
$argType = $scope->getType($node->var);
4445
$isControllerType = (new ObjectType('Symfony\Bundle\FrameworkBundle\Controller\Controller'))->isSuperTypeOf($argType);
46+
$isAbstractControllerType = (new ObjectType('Symfony\Bundle\FrameworkBundle\Controller\AbstractController'))->isSuperTypeOf($argType);
4547
$isContainerType = (new ObjectType('Symfony\Component\DependencyInjection\ContainerInterface'))->isSuperTypeOf($argType);
4648
$isTestContainerType = (new ObjectType('Symfony\Bundle\FrameworkBundle\Test\TestContainer'))->isSuperTypeOf($argType);
47-
if ($isTestContainerType->yes() || (!$isControllerType->yes() && !$isContainerType->yes())) {
49+
if ($isTestContainerType->yes() || (!$isControllerType->yes() && !$isAbstractControllerType->yes() && !$isContainerType->yes())) {
4850
return [];
4951
}
5052

Diff for: src/Rules/Symfony/ContainerInterfaceUnknownServiceRule.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ public function processNode(Node $node, Scope $scope): array
4848

4949
$argType = $scope->getType($node->var);
5050
$isControllerType = (new ObjectType('Symfony\Bundle\FrameworkBundle\Controller\Controller'))->isSuperTypeOf($argType);
51+
$isAbstractControllerType = (new ObjectType('Symfony\Bundle\FrameworkBundle\Controller\AbstractController'))->isSuperTypeOf($argType);
5152
$isContainerType = (new ObjectType('Symfony\Component\DependencyInjection\ContainerInterface'))->isSuperTypeOf($argType);
52-
if (!$isControllerType->yes() && !$isContainerType->yes()) {
53+
if (!$isControllerType->yes() && !$isAbstractControllerType->yes() && !$isContainerType->yes()) {
5354
return [];
5455
}
5556

Diff for: src/Symfony/Service.php

+2-12
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace PHPStan\Symfony;
44

5-
final class Service
5+
final class Service implements ServiceDefinition
66
{
77

88
/** @var string */
@@ -20,24 +20,19 @@ final class Service
2020
/** @var string|null */
2121
private $alias;
2222

23-
/** @var bool */
24-
private $hidden;
25-
2623
public function __construct(
2724
string $id,
2825
?string $class,
2926
bool $public,
3027
bool $synthetic,
31-
?string $alias,
32-
bool $hidden
28+
?string $alias
3329
)
3430
{
3531
$this->id = $id;
3632
$this->class = $class;
3733
$this->public = $public;
3834
$this->synthetic = $synthetic;
3935
$this->alias = $alias;
40-
$this->hidden = $hidden;
4136
}
4237

4338
public function getId(): string
@@ -65,9 +60,4 @@ public function getAlias(): ?string
6560
return $this->alias;
6661
}
6762

68-
public function isHidden(): bool
69-
{
70-
return $this->hidden;
71-
}
72-
7363
}

Diff for: src/Symfony/ServiceDefinition.php

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Symfony;
4+
5+
interface ServiceDefinition
6+
{
7+
8+
public function getId(): string;
9+
10+
public function getClass(): ?string;
11+
12+
public function isPublic(): bool;
13+
14+
public function isSynthetic(): bool;
15+
16+
public function getAlias(): ?string;
17+
18+
}

Diff for: src/Symfony/ServiceMap.php

+17-43
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,31 @@
55
use PhpParser\Node\Expr;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Type\TypeUtils;
8+
use function count;
89

910
final class ServiceMap
1011
{
1112

12-
/** @var Service[] */
13-
private $services = [];
13+
/** @var \PHPStan\Symfony\ServiceDefinition[] */
14+
private $services;
1415

15-
public function __construct(string $containerXml)
16+
/**
17+
* @param \PHPStan\Symfony\ServiceDefinition[] $services
18+
*/
19+
public function __construct(array $services)
1620
{
17-
/** @var Service[] $aliases */
18-
$aliases = [];
19-
/** @var \SimpleXMLElement|false $xml */
20-
$xml = @simplexml_load_file($containerXml);
21-
if ($xml === false) {
22-
throw new \PHPStan\Symfony\XmlContainerNotExistsException(sprintf('Container %s not exists', $containerXml));
23-
}
24-
foreach ($xml->services->service as $def) {
25-
$attrs = $def->attributes();
26-
if (!isset($attrs->id)) {
27-
continue;
28-
}
29-
$service = new Service(
30-
strpos((string) $attrs->id, '.') === 0 ? substr((string) $attrs->id, 1) : (string) $attrs->id,
31-
isset($attrs->class) ? (string) $attrs->class : null,
32-
!isset($attrs->public) || (string) $attrs->public !== 'false',
33-
isset($attrs->synthetic) && (string) $attrs->synthetic === 'true',
34-
isset($attrs->alias) ? (string) $attrs->alias : null,
35-
strpos((string) $attrs->id, '.') === 0
36-
);
37-
if ($service->getAlias() !== null) {
38-
$aliases[] = $service;
39-
} else {
40-
$this->services[$service->getId()] = $service;
41-
}
42-
}
43-
foreach ($aliases as $service) {
44-
if ($service->getAlias() !== null && !array_key_exists($service->getAlias(), $this->services)) {
45-
continue;
46-
}
47-
$this->services[$service->getId()] = new Service(
48-
$service->getId(),
49-
$this->services[$service->getAlias()]->getClass(),
50-
$service->isPublic(),
51-
$service->isSynthetic(),
52-
$service->getAlias(),
53-
$service->isHidden()
54-
);
55-
}
21+
$this->services = $services;
5622
}
5723

58-
public function getService(string $id): ?Service
24+
/**
25+
* @return \PHPStan\Symfony\ServiceDefinition[]
26+
*/
27+
public function getServices(): array
28+
{
29+
return $this->services;
30+
}
31+
32+
public function getService(string $id): ?ServiceDefinition
5933
{
6034
return $this->services[$id] ?? null;
6135
}

Diff for: src/Symfony/ServiceMapFactory.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 ServiceMapFactory
6+
{
7+
8+
public function create(): ServiceMap;
9+
10+
}

Diff for: src/Symfony/XmlServiceMapFactory.php

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Symfony;
4+
5+
use function simplexml_load_file;
6+
use function sprintf;
7+
use function strpos;
8+
use function substr;
9+
10+
final class XmlServiceMapFactory implements ServiceMapFactory
11+
{
12+
13+
/** @var string */
14+
private $containerXml;
15+
16+
public function __construct(string $containerXml)
17+
{
18+
$this->containerXml = $containerXml;
19+
}
20+
21+
public function create(): ServiceMap
22+
{
23+
$xml = @simplexml_load_file($this->containerXml);
24+
if ($xml === false) {
25+
throw new XmlContainerNotExistsException(sprintf('Container %s not exists', $this->containerXml));
26+
}
27+
28+
/** @var \PHPStan\Symfony\Service[] $services */
29+
$services = [];
30+
/** @var \PHPStan\Symfony\Service[] $aliases */
31+
$aliases = [];
32+
foreach ($xml->services->service as $def) {
33+
/** @var \SimpleXMLElement $attrs */
34+
$attrs = $def->attributes();
35+
if (!isset($attrs->id)) {
36+
continue;
37+
}
38+
39+
$service = new Service(
40+
strpos((string) $attrs->id, '.') === 0 ? substr((string) $attrs->id, 1) : (string) $attrs->id,
41+
isset($attrs->class) ? (string) $attrs->class : null,
42+
!isset($attrs->public) || (string) $attrs->public !== 'false',
43+
isset($attrs->synthetic) && (string) $attrs->synthetic === 'true',
44+
isset($attrs->alias) ? (string) $attrs->alias : null
45+
);
46+
47+
if ($service->getAlias() !== null) {
48+
$aliases[] = $service;
49+
} else {
50+
$services[$service->getId()] = $service;
51+
}
52+
}
53+
foreach ($aliases as $service) {
54+
$alias = $service->getAlias();
55+
if ($alias !== null && !isset($services[$alias])) {
56+
continue;
57+
}
58+
$id = $service->getId();
59+
$services[$id] = new Service(
60+
$id,
61+
$services[$alias]->getClass(),
62+
$service->isPublic(),
63+
$service->isSynthetic(),
64+
$alias
65+
);
66+
}
67+
68+
return new ServiceMap($services);
69+
}
70+
71+
}

0 commit comments

Comments
 (0)