4
4
5
5
use Composer \InstalledVersions ;
6
6
use PhpParser \Node ;
7
+ use PhpParser \Node \Stmt \Return_ ;
7
8
use PHPStan \Analyser \Scope ;
8
9
use PHPStan \BetterReflection \Reflector \Exception \IdentifierNotFound ;
9
10
use PHPStan \Node \InClassNode ;
10
11
use PHPStan \Reflection \ExtendedMethodReflection ;
12
+ use PHPStan \Reflection \MethodReflection ;
11
13
use PHPStan \Symfony \ServiceMapFactory ;
12
14
use ReflectionAttribute ;
13
15
use ReflectionClass ;
@@ -41,10 +43,115 @@ public function __construct(
41
43
42
44
public function getUsages (Node $ node , Scope $ scope ): array
43
45
{
44
- if (!$ this ->enabled || ! $ node instanceof InClassNode ) { // @phpstan-ignore phpstanApi.instanceofAssumption
46
+ if (!$ this ->enabled ) {
45
47
return [];
46
48
}
47
49
50
+ $ usages = [];
51
+
52
+ if ($ node instanceof InClassNode) { // @phpstan-ignore phpstanApi.instanceofAssumption
53
+ $ usages = [
54
+ ...$ usages ,
55
+ ...$ this ->getUsagesFromReflection ($ node ),
56
+ ];
57
+ }
58
+
59
+ if ($ node instanceof Return_) {
60
+ $ usages = [
61
+ ...$ usages ,
62
+ ...$ this ->getUsagesOfEventSubscriber ($ node , $ scope ),
63
+ ];
64
+ }
65
+
66
+ return $ usages ;
67
+ }
68
+
69
+ /**
70
+ * @return list<ClassMethodUsage>
71
+ */
72
+ private function getUsagesOfEventSubscriber (Return_ $ node , Scope $ scope ): array
73
+ {
74
+ if ($ node ->expr === null ) {
75
+ return [];
76
+ }
77
+
78
+ if (!$ scope ->isInClass ()) {
79
+ return [];
80
+ }
81
+
82
+ if (!$ scope ->getFunction () instanceof MethodReflection) {
83
+ return [];
84
+ }
85
+
86
+ if ($ scope ->getFunction ()->getName () !== 'getSubscribedEvents ' ) {
87
+ return [];
88
+ }
89
+
90
+ if (!$ scope ->getClassReflection ()->implementsInterface ('Symfony\Component\EventDispatcher\EventSubscriberInterface ' )) {
91
+ return [];
92
+ }
93
+
94
+ $ className = $ scope ->getClassReflection ()->getName ();
95
+
96
+ $ usages = [];
97
+
98
+ // phpcs:disable Squiz.PHP.CommentedOutCode.Found
99
+ foreach ($ scope ->getType ($ node ->expr )->getConstantArrays () as $ rootArray ) {
100
+ foreach ($ rootArray ->getValuesArray ()->getValueTypes () as $ eventConfig ) {
101
+ // ['eventName' => 'methodName']
102
+ foreach ($ eventConfig ->getConstantStrings () as $ subscriberMethodString ) {
103
+ $ usages [] = new ClassMethodUsage (
104
+ null ,
105
+ new ClassMethodRef (
106
+ $ className ,
107
+ $ subscriberMethodString ->getValue (),
108
+ true ,
109
+ ),
110
+ );
111
+ }
112
+
113
+ // ['eventName' => ['methodName', $priority]]
114
+ foreach ($ eventConfig ->getConstantArrays () as $ subscriberMethodArray ) {
115
+ foreach ($ subscriberMethodArray ->getFirstIterableValueType ()->getConstantStrings () as $ subscriberMethodString ) {
116
+ $ usages [] = new ClassMethodUsage (
117
+ null ,
118
+ new ClassMethodRef (
119
+ $ className ,
120
+ $ subscriberMethodString ->getValue (),
121
+ true ,
122
+ ),
123
+ );
124
+ }
125
+ }
126
+
127
+ // ['eventName' => [['methodName', $priority], ['methodName', $priority]]]
128
+ foreach ($ eventConfig ->getConstantArrays () as $ subscriberMethodArray ) {
129
+ foreach ($ subscriberMethodArray ->getIterableValueType ()->getConstantArrays () as $ innerArray ) {
130
+ foreach ($ innerArray ->getFirstIterableValueType ()->getConstantStrings () as $ subscriberMethodString ) {
131
+ $ usages [] = new ClassMethodUsage (
132
+ null ,
133
+ new ClassMethodRef (
134
+ $ className ,
135
+ $ subscriberMethodString ->getValue (),
136
+ true ,
137
+ ),
138
+ );
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ // phpcs:enable // phpcs:disable Squiz.PHP.CommentedOutCode.Found
146
+
147
+ return $ usages ;
148
+ }
149
+
150
+ /**
151
+ * @return list<ClassMethodUsage>
152
+ */
153
+ private function getUsagesFromReflection (InClassNode $ node ): array
154
+ {
48
155
$ classReflection = $ node ->getClassReflection ();
49
156
$ nativeReflection = $ classReflection ->getNativeReflection ();
50
157
$ className = $ classReflection ->getName ();
@@ -70,8 +177,7 @@ public function getUsages(Node $node, Scope $scope): array
70
177
71
178
protected function shouldMarkAsUsed (ReflectionMethod $ method ): bool
72
179
{
73
- return $ this ->isEventSubscriberMethod ($ method )
74
- || $ this ->isBundleConstructor ($ method )
180
+ return $ this ->isBundleConstructor ($ method )
75
181
|| $ this ->isEventListenerMethodWithAsEventListenerAttribute ($ method )
76
182
|| $ this ->isAutowiredWithRequiredAttribute ($ method )
77
183
|| $ this ->isConstructorWithAsCommandAttribute ($ method )
@@ -93,12 +199,6 @@ protected function fillDicClasses(ServiceMapFactory $serviceMapFactory): void
93
199
}
94
200
}
95
201
96
- protected function isEventSubscriberMethod (ReflectionMethod $ method ): bool
97
- {
98
- // this is simplification, we should deduce that from AST of getSubscribedEvents() method
99
- return $ method ->getDeclaringClass ()->implementsInterface ('Symfony\Component\EventDispatcher\EventSubscriberInterface ' );
100
- }
101
-
102
202
protected function isBundleConstructor (ReflectionMethod $ method ): bool
103
203
{
104
204
return $ method ->isConstructor () && $ method ->getDeclaringClass ()->isSubclassOf ('Symfony\Component\HttpKernel\Bundle\Bundle ' );
0 commit comments