Skip to content

Commit 90a5a15

Browse files
committed
[FEATURE] Add rule to check for private services
1 parent b73c577 commit 90a5a15

12 files changed

+302
-2
lines changed

composer.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
"typo3/cms-core": "^11.5 || ^12.4 || ^13.0",
1818
"typo3/cms-extbase": "^11.5 || ^12.4 || ^13.0",
1919
"bnf/phpstan-psr-container": "^1.0",
20-
"composer/semver": "^3.3"
20+
"composer/semver": "^3.3",
21+
"ssch/typo3-debug-dump-pass": "^0.0.2",
22+
"ext-simplexml": "*"
2123
},
2224
"require-dev": {
2325
"consistence-community/coding-standard": "^3.11.1",
@@ -27,7 +29,8 @@
2729
"phing/phing": "^2.17.4",
2830
"phpstan/phpstan-strict-rules": "^1.5.1",
2931
"phpunit/phpunit": "^9.6.16",
30-
"symfony/polyfill-php80": "^1.28.0"
32+
"symfony/polyfill-php80": "^1.28.0",
33+
"phpstan/phpstan-phpunit": "^1.3"
3134
},
3235
"autoload": {
3336
"psr-4": {

extension.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ parameters:
109109
bootstrapFiles:
110110
- phpstan.bootstrap.php
111111
typo3:
112+
containerXmlPath: null
112113
contextApiGetAspectMapping:
113114
date: TYPO3\CMS\Core\Context\DateTimeAspect
114115
visibility: TYPO3\CMS\Core\Context\VisibilityAspect
@@ -194,6 +195,7 @@ parameters:
194195
- pageErrorHandler
195196
parametersSchema:
196197
typo3: structure([
198+
containerXmlPath: schema(string(), nullable())
197199
contextApiGetAspectMapping: arrayOf(string())
198200
requestGetAttributeMapping: arrayOf(string())
199201
siteGetAttributeMapping: arrayOf(string())
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace SaschaEgerer\PhpstanTypo3\Contract;
5+
6+
interface ServiceMapFactoryInterface
7+
{
8+
public function create(): ServiceMapInterface;
9+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace SaschaEgerer\PhpstanTypo3\Contract;
5+
6+
use SaschaEgerer\PhpstanTypo3\Service\ServiceDefinition;
7+
8+
interface ServiceMapInterface
9+
{
10+
/**
11+
* @return ServiceDefinition[]
12+
*/
13+
public function getServiceDefinitions(): array;
14+
15+
public function getServiceDefinitionById(string $id): ?ServiceDefinition;
16+
}

src/Service/FakeServiceMap.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace SaschaEgerer\PhpstanTypo3\Service;
5+
6+
use SaschaEgerer\PhpstanTypo3\Contract\ServiceMapInterface;
7+
8+
final class FakeServiceMap implements ServiceMapInterface
9+
{
10+
11+
public function getServiceDefinitions(): array
12+
{
13+
return [];
14+
}
15+
16+
public function getServiceDefinitionById(string $id): ?ServiceDefinition
17+
{
18+
return null;
19+
}
20+
}

src/Service/ServiceDefinition.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace SaschaEgerer\PhpstanTypo3\Service;
5+
6+
final class ServiceDefinition
7+
{
8+
private string $id;
9+
private ?string $class;
10+
private bool $public;
11+
private bool $synthetic;
12+
private ?string $alias;
13+
14+
public function __construct(
15+
string $id,
16+
?string $class,
17+
bool $public,
18+
bool $synthetic,
19+
?string $alias
20+
)
21+
{
22+
23+
$this->id = $id;
24+
$this->class = $class;
25+
$this->public = $public;
26+
$this->synthetic = $synthetic;
27+
$this->alias = $alias;
28+
}
29+
30+
public function getId(): string
31+
{
32+
return $this->id;
33+
}
34+
35+
public function getClass(): ?string
36+
{
37+
return $this->class;
38+
}
39+
40+
public function isPublic(): bool
41+
{
42+
return $this->public;
43+
}
44+
45+
public function isSynthetic(): bool
46+
{
47+
return $this->synthetic;
48+
}
49+
50+
public function getAlias(): ?string
51+
{
52+
return $this->alias;
53+
}
54+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace SaschaEgerer\PhpstanTypo3\Service;
5+
6+
final class ServiceDefinitionFileException extends \InvalidArgumentException
7+
{
8+
public static function notFound(string $file): self
9+
{
10+
$message = sprintf('File "%s" does not exist', $file);
11+
12+
return new self($message);
13+
}
14+
15+
public static function parseError(string $file): self
16+
{
17+
$message = sprintf('File "%s" could not be parsed correctly', $file);
18+
19+
return new self($message);
20+
}
21+
}

src/Service/ServiceMap.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace SaschaEgerer\PhpstanTypo3\Service;
5+
6+
use SaschaEgerer\PhpstanTypo3\Contract\ServiceMapInterface;
7+
8+
final class ServiceMap implements ServiceMapInterface
9+
{
10+
/**
11+
* @var ServiceDefinition[]
12+
*/
13+
private array $serviceDefinitions;
14+
15+
/**
16+
* @param ServiceDefinition[] $serviceDefinitions
17+
*/
18+
public function __construct(array $serviceDefinitions)
19+
{
20+
$this->serviceDefinitions = $serviceDefinitions;
21+
}
22+
23+
public function getServiceDefinitions(): array
24+
{
25+
return $this->serviceDefinitions;
26+
}
27+
28+
public function getServiceDefinitionById(string $id): ?ServiceDefinition
29+
{
30+
return $this->serviceDefinitions[$id] ?? null;
31+
}
32+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace SaschaEgerer\PhpstanTypo3\Service;
5+
6+
use SaschaEgerer\PhpstanTypo3\Contract\ServiceMapFactoryInterface;
7+
use SaschaEgerer\PhpstanTypo3\Contract\ServiceMapInterface;
8+
use SimpleXMLElement;
9+
10+
final class XmlServiceMapFactory implements ServiceMapFactoryInterface
11+
{
12+
private ?string $containerXmlPath;
13+
14+
public function __construct(?string $containerXmlPath)
15+
{
16+
$this->containerXmlPath = $containerXmlPath;
17+
}
18+
19+
public function create(): ServiceMapInterface
20+
{
21+
if ($this->containerXmlPath === null) {
22+
return new FakeServiceMap();
23+
}
24+
25+
if(!file_exists($this->containerXmlPath)) {
26+
throw ServiceDefinitionFileException::notFound($this->containerXmlPath);
27+
}
28+
29+
$xml = @simplexml_load_file($this->containerXmlPath);
30+
31+
if($xml === false) {
32+
throw ServiceDefinitionFileException::parseError($this->containerXmlPath);
33+
}
34+
35+
/** @var ServiceDefinition[] $serviceDefinitions */
36+
$serviceDefinitions = [];
37+
/** @var ServiceDefinition[] $aliases */
38+
$aliases = [];
39+
foreach ($xml->services->service as $def) {
40+
/** @var SimpleXMLElement $attrs */
41+
$attrs = $def->attributes();
42+
if (!isset($attrs->id)) {
43+
continue;
44+
}
45+
46+
$serviceDefinition = new ServiceDefinition(
47+
strpos((string) $attrs->id, '.') === 0 ? substr((string) $attrs->id, 1) : (string) $attrs->id,
48+
isset($attrs->class) ? (string) $attrs->class : null,
49+
isset($attrs->public) && (string) $attrs->public === 'true',
50+
isset($attrs->synthetic) && (string) $attrs->synthetic === 'true',
51+
isset($attrs->alias) ? (string) $attrs->alias : null
52+
);
53+
54+
if ($serviceDefinition->getAlias() !== null) {
55+
$aliases[] = $serviceDefinition;
56+
} else {
57+
$serviceDefinitions[$serviceDefinition->getId()] = $serviceDefinition;
58+
}
59+
}
60+
foreach ($aliases as $serviceDefinition) {
61+
$alias = $serviceDefinition->getAlias();
62+
if ($alias !== null && !isset($serviceDefinitions[$alias])) {
63+
continue;
64+
}
65+
$id = $serviceDefinition->getId();
66+
$serviceDefinitions[$id] = new ServiceDefinition(
67+
$id,
68+
$serviceDefinitions[$alias]->getClass(),
69+
$serviceDefinition->isPublic(),
70+
$serviceDefinition->isSynthetic(),
71+
$alias
72+
);
73+
}
74+
75+
return new ServiceMap($serviceDefinitions);
76+
}
77+
}

tests/Unit/Fixtures/container.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
3+
<services>
4+
<service></service><!-- without id -->
5+
<service id="withoutClass"></service>
6+
<service id="withClass" class="Foo"></service>
7+
<service id="withoutPublic" class="Foo"></service>
8+
<service id="publicNotTrue" class="Foo" public="false"></service>
9+
<service id="public" class="Foo" public="true"></service>
10+
<service id="synthetic" class="Foo" synthetic="true"></service>
11+
<service id="alias" class="Bar" alias="withClass"></service>
12+
</services>
13+
</container>

0 commit comments

Comments
 (0)