3
3
namespace ShipMonk \PHPStan \DeadCode \Provider ;
4
4
5
5
use Composer \InstalledVersions ;
6
+ use PhpParser \Node ;
7
+ use PhpParser \Node \Stmt \Return_ ;
8
+ use PHPStan \Analyser \Scope ;
9
+ use PHPStan \Node \InClassNode ;
10
+ use PHPStan \Reflection \ExtendedMethodReflection ;
11
+ use PHPStan \Reflection \MethodReflection ;
6
12
use ReflectionClass ;
7
13
use ReflectionMethod ;
14
+ use ShipMonk \PHPStan \DeadCode \Graph \ClassMethodRef ;
15
+ use ShipMonk \PHPStan \DeadCode \Graph \ClassMethodUsage ;
8
16
use const PHP_VERSION_ID ;
9
17
10
- class DoctrineUsageProvider extends ReflectionBasedMemberUsageProvider
18
+ class DoctrineUsageProvider implements MemberUsageProvider
11
19
{
12
20
13
21
private bool $ enabled ;
@@ -17,28 +25,112 @@ public function __construct(?bool $enabled)
17
25
$ this ->enabled = $ enabled ?? $ this ->isDoctrineInstalled ();
18
26
}
19
27
20
- public function shouldMarkMethodAsUsed ( ReflectionMethod $ method ): bool
28
+ public function getUsages ( Node $ node , Scope $ scope ): array
21
29
{
22
30
if (!$ this ->enabled ) {
23
- return false ;
31
+ return [];
32
+ }
33
+
34
+ $ usages = [];
35
+
36
+ if ($ node instanceof InClassNode) { // @phpstan-ignore phpstanApi.instanceofAssumption
37
+ $ usages = [
38
+ ...$ usages ,
39
+ ...$ this ->getUsagesFromReflection ($ node ),
40
+ ];
41
+ }
42
+
43
+ if ($ node instanceof Return_) {
44
+ $ usages = [
45
+ ...$ usages ,
46
+ ...$ this ->getUsagesOfEventSubscriber ($ node , $ scope ),
47
+ ];
24
48
}
25
49
50
+ return $ usages ;
51
+ }
52
+
53
+ /**
54
+ * @return list<ClassMethodUsage>
55
+ */
56
+ private function getUsagesFromReflection (InClassNode $ node ): array
57
+ {
58
+ $ classReflection = $ node ->getClassReflection ();
59
+ $ nativeReflection = $ classReflection ->getNativeReflection ();
60
+
61
+ $ usages = [];
62
+
63
+ foreach ($ nativeReflection ->getMethods () as $ method ) {
64
+ if ($ method ->getDeclaringClass ()->getName () !== $ nativeReflection ->getName ()) {
65
+ continue ;
66
+ }
67
+
68
+ if ($ this ->shouldMarkMethodAsUsed ($ method )) {
69
+ $ usages [] = $ this ->createMethodUsage ($ classReflection ->getNativeMethod ($ method ->getName ()));
70
+ }
71
+ }
72
+
73
+ return $ usages ;
74
+ }
75
+
76
+ /**
77
+ * @return list<ClassMethodUsage>
78
+ */
79
+ private function getUsagesOfEventSubscriber (Return_ $ node , Scope $ scope ): array
80
+ {
81
+ if ($ node ->expr === null ) {
82
+ return [];
83
+ }
84
+
85
+ if (!$ scope ->isInClass ()) {
86
+ return [];
87
+ }
88
+
89
+ if (!$ scope ->getFunction () instanceof MethodReflection) {
90
+ return [];
91
+ }
92
+
93
+ if ($ scope ->getFunction ()->getName () !== 'getSubscribedEvents ' ) {
94
+ return [];
95
+ }
96
+
97
+ if (!$ scope ->getClassReflection ()->implementsInterface ('Doctrine\Common\EventSubscriber ' )) {
98
+ return [];
99
+ }
100
+
101
+ $ className = $ scope ->getClassReflection ()->getName ();
102
+
103
+ $ usages = [];
104
+
105
+ foreach ($ scope ->getType ($ node ->expr )->getConstantArrays () as $ rootArray ) {
106
+ foreach ($ rootArray ->getValuesArray ()->getValueTypes () as $ eventConfig ) {
107
+ foreach ($ eventConfig ->getConstantStrings () as $ subscriberMethodString ) {
108
+ $ usages [] = new ClassMethodUsage (
109
+ null ,
110
+ new ClassMethodRef (
111
+ $ className ,
112
+ $ subscriberMethodString ->getValue (),
113
+ true ,
114
+ ),
115
+ );
116
+ }
117
+ }
118
+ }
119
+
120
+ return $ usages ;
121
+ }
122
+
123
+ protected function shouldMarkMethodAsUsed (ReflectionMethod $ method ): bool
124
+ {
26
125
$ methodName = $ method ->getName ();
27
126
$ class = $ method ->getDeclaringClass ();
28
127
29
- return $ this ->isEventSubscriberMethod ($ method )
30
- || $ this ->isLifecycleEventMethod ($ method )
128
+ return $ this ->isLifecycleEventMethod ($ method )
31
129
|| $ this ->isEntityRepositoryConstructor ($ class , $ method )
32
130
|| $ this ->isPartOfAsEntityListener ($ class , $ methodName )
33
131
|| $ this ->isProbablyDoctrineListener ($ methodName );
34
132
}
35
133
36
- protected function isEventSubscriberMethod (ReflectionMethod $ method ): bool
37
- {
38
- // this is simplification, we should deduce that from AST of getSubscribedEvents() method
39
- return $ method ->getDeclaringClass ()->implementsInterface ('Doctrine\Common\EventSubscriber ' );
40
- }
41
-
42
134
protected function isLifecycleEventMethod (ReflectionMethod $ method ): bool
43
135
{
44
136
return $ this ->hasAttribute ($ method , 'Doctrine\ORM\Mapping\PostLoad ' )
@@ -119,4 +211,16 @@ private function isDoctrineInstalled(): bool
119
211
|| InstalledVersions::isInstalled ('doctrine/doctrine-bundle ' );
120
212
}
121
213
214
+ private function createMethodUsage (ExtendedMethodReflection $ methodReflection ): ClassMethodUsage
215
+ {
216
+ return new ClassMethodUsage (
217
+ null ,
218
+ new ClassMethodRef (
219
+ $ methodReflection ->getDeclaringClass ()->getName (),
220
+ $ methodReflection ->getName (),
221
+ false ,
222
+ ),
223
+ );
224
+ }
225
+
122
226
}
0 commit comments