Skip to content

Commit bf14f85

Browse files
committed
[TASK] Add check for potential prototype class
1 parent e18d10e commit bf14f85

File tree

6 files changed

+77
-7
lines changed

6 files changed

+77
-7
lines changed

extension.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ services:
106106
- phpstan.broker.dynamicMethodReturnTypeExtension
107107
-
108108
class: SaschaEgerer\PhpstanTypo3\Service\PrivateServiceAnalyzer
109+
-
110+
class: SaschaEgerer\PhpstanTypo3\Service\PrototypeServiceDefinitionChecker
109111
# service map
110112
typo3.serviceMapFactory:
111113
class: SaschaEgerer\PhpstanTypo3\Contract\ServiceMapFactory

src/Contract/ServiceDefinitionChecker.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22

33
namespace SaschaEgerer\PhpstanTypo3\Contract;
44

5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\StaticCall;
57
use SaschaEgerer\PhpstanTypo3\Service\ServiceDefinition;
68

79
interface ServiceDefinitionChecker
810
{
911

10-
public function isPrototype(ServiceDefinition $serviceDefinition): bool;
12+
/**
13+
* @param Node\Expr\MethodCall|StaticCall $node
14+
*/
15+
public function isPrototype(ServiceDefinition $serviceDefinition, Node $node): bool;
1116

1217
}

src/Rule/GeneralUtilityMakeInstancePrivateServiceRule.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ final class GeneralUtilityMakeInstancePrivateServiceRule implements Rule
1818

1919
private PrivateServiceAnalyzer $privateServiceAnalyzer;
2020

21-
public function __construct(PrivateServiceAnalyzer $privateServiceAnalyzer)
21+
private PrototypeServiceDefinitionChecker $prototypeServiceDefinitionChecker;
22+
23+
public function __construct(PrivateServiceAnalyzer $privateServiceAnalyzer, PrototypeServiceDefinitionChecker $prototypeServiceDefinitionChecker)
2224
{
2325
$this->privateServiceAnalyzer = $privateServiceAnalyzer;
26+
$this->prototypeServiceDefinitionChecker = $prototypeServiceDefinitionChecker;
2427
}
2528

2629
public function getNodeType(): string
@@ -34,7 +37,7 @@ public function processNode(Node $node, Scope $scope): array
3437
return [];
3538
}
3639

37-
return $this->privateServiceAnalyzer->analyze($node, $scope, new PrototypeServiceDefinitionChecker());
40+
return $this->privateServiceAnalyzer->analyze($node, $scope, $this->prototypeServiceDefinitionChecker);
3841
}
3942

4043
private function shouldSkip(StaticCall $node): bool

src/Service/NullServiceDefinitionChecker.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
namespace SaschaEgerer\PhpstanTypo3\Service;
44

5+
use PhpParser\Node;
56
use SaschaEgerer\PhpstanTypo3\Contract\ServiceDefinitionChecker;
67

78
final class NullServiceDefinitionChecker implements ServiceDefinitionChecker
89
{
910

10-
public function isPrototype(ServiceDefinition $serviceDefinition): bool
11+
public function isPrototype(ServiceDefinition $serviceDefinition, Node $node): bool
1112
{
1213
return false;
1314
}

src/Service/PrivateServiceAnalyzer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function analyze(Node $node, Scope $scope, ServiceDefinitionChecker $serv
4040
return [];
4141
}
4242

43-
if ($serviceDefinitionChecker->isPrototype($serviceDefinition)) {
43+
if ($serviceDefinitionChecker->isPrototype($serviceDefinition, $node)) {
4444
return [];
4545
}
4646

src/Service/PrototypeServiceDefinitionChecker.php

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,73 @@
22

33
namespace SaschaEgerer\PhpstanTypo3\Service;
44

5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\ClassConstFetch;
7+
use PhpParser\Node\Expr\StaticCall;
58
use SaschaEgerer\PhpstanTypo3\Contract\ServiceDefinitionChecker;
69

710
final class PrototypeServiceDefinitionChecker implements ServiceDefinitionChecker
811
{
912

10-
public function isPrototype(ServiceDefinition $serviceDefinition): bool
13+
public function isPrototype(ServiceDefinition $serviceDefinition, Node $node): bool
1114
{
12-
return !$serviceDefinition->isHasTags() && !$serviceDefinition->isHasMethodCalls() && !$serviceDefinition->isHasConstructorArguments();
15+
return !$serviceDefinition->isHasTags() && !$serviceDefinition->isHasMethodCalls() && !$serviceDefinition->isHasConstructorArguments() && !$this->canBePrototypeClass($node);
16+
}
17+
18+
private function extractFirstArgument(StaticCall $node): ?Node
19+
{
20+
if (!isset($node->args[0])) {
21+
return null;
22+
}
23+
24+
if (!$node->args[0] instanceof Node\Arg) {
25+
return null;
26+
}
27+
28+
return $node->args[0]->value;
29+
}
30+
31+
private function canBePrototypeClass(Node $node): bool
32+
{
33+
if (!$node instanceof StaticCall) {
34+
return false;
35+
}
36+
37+
$firstArgument = $this->extractFirstArgument($node);
38+
39+
if (!$firstArgument instanceof ClassConstFetch) {
40+
return false;
41+
}
42+
43+
if (!$firstArgument->class instanceof Node\Name) {
44+
return false;
45+
}
46+
47+
if ($firstArgument->class->isSpecialClassName()) {
48+
return false;
49+
}
50+
51+
/** @var class-string $className */
52+
$className = $firstArgument->class->toString();
53+
54+
$reflection = new \ReflectionClass($className);
55+
56+
$constructorMethod = $reflection->getConstructor();
57+
58+
if ($constructorMethod === null) {
59+
return true;
60+
}
61+
62+
$constructorParameters = $constructorMethod->getParameters();
63+
$hasRequiredParameter = false;
64+
foreach ($constructorParameters as $constructorParameter) {
65+
if ($constructorParameter->isOptional()) {
66+
continue;
67+
}
68+
$hasRequiredParameter = true;
69+
}
70+
71+
return $hasRequiredParameter === false;
1372
}
1473

1574
}

0 commit comments

Comments
 (0)