Skip to content

Commit b56a301

Browse files
Merge pull request #152 from sascha-egerer/task/add-container-private-rule
[FEATURE] Add rule to check if service is private
2 parents 947b026 + 6188f32 commit b56a301

34 files changed

+1054
-6
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,14 @@ $myIntAttribute = $site->getAttribute('myIntAttribute');
143143
// PHPStan will now know that $myStringAttribute is of type string
144144
$myStringAttribute = $site->getAttribute('myStringAttribute');
145145
```
146+
147+
### Check for private Services
148+
You have to provide a path to App_KernelDevelopmentDebugContainer.xml or similar XML file describing your container.
149+
This is generated by [ssch/typo3-debug-dump-pass](https://github.com/sabbelasichon/typo3-debug-dump-pass) in your /var/cache/{TYPO3_CONTEXT}/ folder.
150+
151+
```NEON
152+
parameters:
153+
typo3:
154+
containerXmlPath: var/cache/development/App_KernelDevelopmentDebugContainer.xml
155+
```
156+

build.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
passthru="true"
6161
checkreturn="true"
6262
>
63+
<arg line="--memory-limit=-1"/>
6364
</exec>
6465
</target>
6566

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: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
includes:
2+
- rules.neon
3+
14
services:
25
-
36
class: SaschaEgerer\PhpstanTypo3\Reflection\RepositoryFindMethodsClassReflectionExtension
@@ -51,10 +54,6 @@ services:
5154
siteGetAttributeMapping: %typo3.siteGetAttributeMapping%
5255
tags:
5356
- phpstan.rules.rule
54-
-
55-
class: SaschaEgerer\PhpstanTypo3\Rule\ValidatorResolverOptionsRule
56-
tags:
57-
- phpstan.rules.rule
5857
-
5958
class: SaschaEgerer\PhpstanTypo3\Type\RepositoryQueryDynamicReturnTypeExtension
6059
tags:
@@ -105,10 +104,23 @@ services:
105104
class: SaschaEgerer\PhpstanTypo3\Type\DateTimeAspectGetDynamicReturnTypeExtension
106105
tags:
107106
- phpstan.broker.dynamicMethodReturnTypeExtension
107+
-
108+
class: SaschaEgerer\PhpstanTypo3\Service\PrivateServiceAnalyzer
109+
-
110+
class: SaschaEgerer\PhpstanTypo3\Service\PrototypeServiceDefinitionChecker
111+
# service map
112+
typo3.serviceMapFactory:
113+
class: SaschaEgerer\PhpstanTypo3\Contract\ServiceMapFactory
114+
factory: SaschaEgerer\PhpstanTypo3\Service\XmlServiceMapFactory
115+
arguments:
116+
containerXmlPath: %typo3.containerXmlPath%
117+
-
118+
factory: @typo3.serviceMapFactory::create()
108119
parameters:
109120
bootstrapFiles:
110121
- phpstan.bootstrap.php
111122
typo3:
123+
containerXmlPath: null
112124
contextApiGetAspectMapping:
113125
date: TYPO3\CMS\Core\Context\DateTimeAspect
114126
visibility: TYPO3\CMS\Core\Context\VisibilityAspect
@@ -194,6 +206,7 @@ parameters:
194206
- pageErrorHandler
195207
parametersSchema:
196208
typo3: structure([
209+
containerXmlPath: schema(string(), nullable())
197210
contextApiGetAspectMapping: arrayOf(string())
198211
requestGetAttributeMapping: arrayOf(string())
199212
siteGetAttributeMapping: arrayOf(string())

phpstan.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
includes:
22
- vendor/phpstan/phpstan-strict-rules/rules.neon
3+
- vendor/phpstan/phpstan-phpunit/extension.neon
34
- extension.neon
45
- tests/Unit/Type/data/context-get-aspect-return-types.neon
56
- tests/Unit/Type/data/request-get-attribute-return-types.neon
@@ -12,6 +13,7 @@ parameters:
1213
- tests
1314
reportUnmatchedIgnoredErrors: false
1415
excludePaths:
16+
- '*tests/*/Fixtures/*'
1517
- '*tests/*/Fixture/*'
1618
- '*tests/*/Source/*'
1719
- '*tests/*/data/*'

rules.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
rules:
2+
- SaschaEgerer\PhpstanTypo3\Rule\ValidatorResolverOptionsRule
3+
- SaschaEgerer\PhpstanTypo3\Rule\ContainerInterfacePrivateServiceRule
4+
- SaschaEgerer\PhpstanTypo3\Rule\GeneralUtilityMakeInstancePrivateServiceRule
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SaschaEgerer\PhpstanTypo3\Contract;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\StaticCall;
7+
use SaschaEgerer\PhpstanTypo3\Service\ServiceDefinition;
8+
9+
interface ServiceDefinitionChecker
10+
{
11+
12+
/**
13+
* @param Node\Expr\MethodCall|StaticCall $node
14+
*/
15+
public function isPrototype(ServiceDefinition $serviceDefinition, Node $node): bool;
16+
17+
}

src/Contract/ServiceMap.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SaschaEgerer\PhpstanTypo3\Contract;
4+
5+
use PhpParser\Node\Expr;
6+
use PHPStan\Analyser\Scope;
7+
use SaschaEgerer\PhpstanTypo3\Service\ServiceDefinition;
8+
9+
interface ServiceMap
10+
{
11+
12+
/**
13+
* @return ServiceDefinition[]
14+
*/
15+
public function getServiceDefinitions(): array;
16+
17+
public function getServiceDefinitionById(string $id): ?ServiceDefinition;
18+
19+
public function getServiceIdFromNode(Expr $node, Scope $scope): ?string;
20+
21+
}

src/Contract/ServiceMapFactory.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SaschaEgerer\PhpstanTypo3\Contract;
4+
5+
interface ServiceMapFactory
6+
{
7+
8+
public function create(): ServiceMap;
9+
10+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SaschaEgerer\PhpstanTypo3\Rule;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\MethodCall;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Type\ObjectType;
10+
use SaschaEgerer\PhpstanTypo3\Service\NullServiceDefinitionChecker;
11+
use SaschaEgerer\PhpstanTypo3\Service\PrivateServiceAnalyzer;
12+
13+
/**
14+
* @implements Rule<MethodCall>
15+
*/
16+
final class ContainerInterfacePrivateServiceRule implements Rule
17+
{
18+
19+
private PrivateServiceAnalyzer $privateServiceAnalyzer;
20+
21+
public function __construct(PrivateServiceAnalyzer $privateServiceAnalyzer)
22+
{
23+
$this->privateServiceAnalyzer = $privateServiceAnalyzer;
24+
}
25+
26+
public function getNodeType(): string
27+
{
28+
return MethodCall::class;
29+
}
30+
31+
public function processNode(Node $node, Scope $scope): array
32+
{
33+
if ($this->shouldSkip($node, $scope)) {
34+
return [];
35+
}
36+
37+
return $this->privateServiceAnalyzer->analyze($node, $scope, new NullServiceDefinitionChecker());
38+
}
39+
40+
private function shouldSkip(MethodCall $node, Scope $scope): bool
41+
{
42+
if (!$node->name instanceof Node\Identifier) {
43+
return true;
44+
}
45+
46+
$methodCallArguments = $node->getArgs();
47+
48+
if (!isset($methodCallArguments[0])) {
49+
return true;
50+
}
51+
52+
$methodCallName = $node->name->name;
53+
54+
if ($methodCallName !== 'get') {
55+
return true;
56+
}
57+
58+
$argType = $scope->getType($node->var);
59+
60+
$isPsrContainerType = (new ObjectType('Psr\Container\ContainerInterface'))->isSuperTypeOf($argType);
61+
$isTestCaseType = (new ObjectType('TYPO3\TestingFramework\Core\Functional\FunctionalTestCase'))->isSuperTypeOf($argType);
62+
63+
if ($isTestCaseType->yes()) {
64+
return true;
65+
}
66+
67+
return !$isPsrContainerType->yes();
68+
}
69+
70+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SaschaEgerer\PhpstanTypo3\Rule;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\StaticCall;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Rules\Rule;
9+
use SaschaEgerer\PhpstanTypo3\Service\PrivateServiceAnalyzer;
10+
use SaschaEgerer\PhpstanTypo3\Service\PrototypeServiceDefinitionChecker;
11+
use TYPO3\CMS\Core\Utility\GeneralUtility;
12+
13+
/**
14+
* @implements Rule<StaticCall>
15+
*/
16+
final class GeneralUtilityMakeInstancePrivateServiceRule implements Rule
17+
{
18+
19+
private PrivateServiceAnalyzer $privateServiceAnalyzer;
20+
21+
private PrototypeServiceDefinitionChecker $prototypeServiceDefinitionChecker;
22+
23+
public function __construct(PrivateServiceAnalyzer $privateServiceAnalyzer, PrototypeServiceDefinitionChecker $prototypeServiceDefinitionChecker)
24+
{
25+
$this->privateServiceAnalyzer = $privateServiceAnalyzer;
26+
$this->prototypeServiceDefinitionChecker = $prototypeServiceDefinitionChecker;
27+
}
28+
29+
public function getNodeType(): string
30+
{
31+
return StaticCall::class;
32+
}
33+
34+
public function processNode(Node $node, Scope $scope): array
35+
{
36+
if ($this->shouldSkip($node)) {
37+
return [];
38+
}
39+
40+
return $this->privateServiceAnalyzer->analyze($node, $scope, $this->prototypeServiceDefinitionChecker);
41+
}
42+
43+
private function shouldSkip(StaticCall $node): bool
44+
{
45+
if (!$node->name instanceof Node\Identifier) {
46+
return true;
47+
}
48+
49+
$methodCallArguments = $node->getArgs();
50+
51+
if (!isset($methodCallArguments[0])) {
52+
return true;
53+
}
54+
55+
$methodCallName = $node->name->name;
56+
57+
if ($methodCallName !== 'makeInstance') {
58+
return true;
59+
}
60+
61+
if (count($methodCallArguments) > 1) {
62+
return true;
63+
}
64+
65+
if (!$node->class instanceof Node\Name\FullyQualified) {
66+
return true;
67+
}
68+
69+
return $node->class->toString() !== GeneralUtility::class;
70+
}
71+
72+
}

src/Service/DefaultServiceMap.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SaschaEgerer\PhpstanTypo3\Service;
4+
5+
use PhpParser\Node\Expr;
6+
use PHPStan\Analyser\Scope;
7+
use SaschaEgerer\PhpstanTypo3\Contract\ServiceMap;
8+
9+
final class DefaultServiceMap implements ServiceMap
10+
{
11+
12+
/** @var ServiceDefinition[] */
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+
33+
public function getServiceIdFromNode(Expr $node, Scope $scope): ?string
34+
{
35+
$strings = $scope->getType($node)->getConstantStrings();
36+
return count($strings) === 1 ? $strings[0]->getValue() : null;
37+
}
38+
39+
}

src/Service/FakeServiceMap.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SaschaEgerer\PhpstanTypo3\Service;
4+
5+
use PhpParser\Node\Expr;
6+
use PHPStan\Analyser\Scope;
7+
use SaschaEgerer\PhpstanTypo3\Contract\ServiceMap;
8+
9+
final class FakeServiceMap implements ServiceMap
10+
{
11+
12+
public function getServiceDefinitions(): array
13+
{
14+
return [];
15+
}
16+
17+
public function getServiceDefinitionById(string $id): ?ServiceDefinition
18+
{
19+
return null;
20+
}
21+
22+
public function getServiceIdFromNode(Expr $node, Scope $scope): ?string
23+
{
24+
return null;
25+
}
26+
27+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SaschaEgerer\PhpstanTypo3\Service;
4+
5+
use PhpParser\Node;
6+
use SaschaEgerer\PhpstanTypo3\Contract\ServiceDefinitionChecker;
7+
8+
final class NullServiceDefinitionChecker implements ServiceDefinitionChecker
9+
{
10+
11+
public function isPrototype(ServiceDefinition $serviceDefinition, Node $node): bool
12+
{
13+
return false;
14+
}
15+
16+
}

0 commit comments

Comments
 (0)