5
5
use PhpParser \Node ;
6
6
use PhpParser \Node \Expr \MethodCall ;
7
7
use PHPStan \Analyser \Scope ;
8
+ use PHPStan \BetterReflection \Reflection \Adapter \FakeReflectionAttribute ;
9
+ use PHPStan \BetterReflection \Reflection \ReflectionAttribute ;
10
+ use PHPStan \Reflection \ClassReflection ;
11
+ use PHPStan \Reflection \Php \PhpPropertyReflection ;
12
+ use PHPStan \Reflection \PropertyReflection ;
8
13
use PHPStan \Rules \Rule ;
9
14
use PHPStan \Rules \RuleErrorBuilder ;
15
+ use PHPStan \Symfony \ServiceDefinition ;
10
16
use PHPStan \Symfony \ServiceMap ;
11
17
use PHPStan \TrinaryLogic ;
12
18
use PHPStan \Type \ObjectType ;
13
19
use PHPStan \Type \Type ;
20
+ use function get_class ;
14
21
use function sprintf ;
15
22
16
23
/**
@@ -66,15 +73,29 @@ public function processNode(Node $node, Scope $scope): array
66
73
}
67
74
68
75
$ serviceId = $ this ->serviceMap ::getServiceIdFromNode ($ node ->getArgs ()[0 ]->value , $ scope );
69
- if ($ serviceId !== null ) {
70
- $ service = $ this ->serviceMap ->getService ($ serviceId );
71
- if ($ service !== null && !$ service ->isPublic ()) {
72
- return [
73
- RuleErrorBuilder::message (sprintf ('Service "%s" is private. ' , $ serviceId ))
74
- ->identifier ('symfonyContainer.privateService ' )
75
- ->build (),
76
- ];
77
- }
76
+ if ($ serviceId === null ) {
77
+ return [];
78
+ }
79
+
80
+ $ service = $ this ->serviceMap ->getService ($ serviceId );
81
+ if (!$ service instanceof ServiceDefinition) {
82
+ return [];
83
+ }
84
+
85
+ $ isContainerInterfaceType = $ isContainerType ->yes () || $ isPsrContainerType ->yes ();
86
+ if (
87
+ $ isContainerInterfaceType &&
88
+ $ this ->isAutowireLocator ($ node , $ scope , $ service )
89
+ ) {
90
+ return [];
91
+ }
92
+
93
+ if (!$ service ->isPublic ()) {
94
+ return [
95
+ RuleErrorBuilder::message (sprintf ('Service "%s" is private. ' , $ serviceId ))
96
+ ->identifier ('symfonyContainer.privateService ' )
97
+ ->build (),
98
+ ];
78
99
}
79
100
80
101
return [];
@@ -92,4 +113,85 @@ private function isServiceSubscriber(Type $containerType, Scope $scope): Trinary
92
113
return $ isContainerServiceSubscriber ->or ($ serviceSubscriberInterfaceType ->isSuperTypeOf ($ containedClassType ));
93
114
}
94
115
116
+ private function isAutowireLocator (Node $ node , Scope $ scope , ServiceDefinition $ service ): bool
117
+ {
118
+ if (
119
+ !$ node instanceof MethodCall
120
+ ) {
121
+ return false ;
122
+ }
123
+
124
+ $ nodeParentProperty = $ node ->var ;
125
+
126
+ if (!$ nodeParentProperty instanceof Node \Expr \PropertyFetch) {
127
+ return false ;
128
+ }
129
+
130
+ $ nodeParentPropertyName = $ nodeParentProperty ->name ;
131
+
132
+ if (!$ nodeParentPropertyName instanceof Node \Identifier) {
133
+ return false ;
134
+ }
135
+
136
+ $ containerInterfacePropertyName = $ nodeParentPropertyName ->name ;
137
+ $ scopeClassReflection = $ scope ->getClassReflection ();
138
+
139
+ if (!$ scopeClassReflection instanceof ClassReflection) {
140
+ return false ;
141
+ }
142
+
143
+ /** @var PropertyReflection $containerInterfacePropertyReflection */
144
+ $ containerInterfacePropertyReflection = $ scopeClassReflection
145
+ ->getProperty ($ containerInterfacePropertyName , $ scope );
146
+
147
+ if (!$ containerInterfacePropertyReflection instanceof PhpPropertyReflection) {
148
+ return false ;
149
+ }
150
+
151
+ $ classPropertyReflection = $ containerInterfacePropertyReflection ->getNativeReflection ();
152
+ /** @var class-string $autowireLocatorClassString */
153
+ $ autowireLocatorClassString = 'Symfony\Component\DependencyInjection\Attribute\AutowireLocator ' ;
154
+ $ autowireLocatorAttributes = $ classPropertyReflection ->getAttributes ($ autowireLocatorClassString );
155
+
156
+ return $ this ->isAutowireLocatorService ($ autowireLocatorAttributes , $ service );
157
+ }
158
+
159
+ /**
160
+ * @param array<int, FakeReflectionAttribute|ReflectionAttribute> $autowireLocatorAttributes
161
+ */
162
+ private function isAutowireLocatorService (array $ autowireLocatorAttributes , ServiceDefinition $ service ): bool
163
+ {
164
+ foreach ($ autowireLocatorAttributes as $ autowireLocatorAttribute ) {
165
+ foreach ($ autowireLocatorAttribute ->getArgumentsExpressions () as $ autowireLocatorServices ) {
166
+ if (!$ autowireLocatorServices instanceof Node \Expr \Array_) {
167
+ continue ;
168
+ }
169
+
170
+ foreach ($ autowireLocatorServices ->items as $ autowireLocatorServiceNode ) {
171
+ /** @var Node\Expr $autowireLocatorService */
172
+ $ autowireLocatorServiceExpr = $ autowireLocatorServiceNode ->value ;
173
+
174
+ switch (get_class ($ autowireLocatorServiceExpr )) {
175
+ case Node \Scalar \String_::class:
176
+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->value ;
177
+ break ;
178
+ case Node \Expr \ClassConstFetch::class:
179
+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->class instanceof Node \Name
180
+ ? $ autowireLocatorServiceExpr ->class ->toString ()
181
+ : null ;
182
+ break ;
183
+ default :
184
+ $ autowireLocatorServiceClass = null ;
185
+ }
186
+
187
+ if ($ service ->getId () === $ autowireLocatorServiceClass ) {
188
+ return true ;
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ return false ;
195
+ }
196
+
95
197
}
0 commit comments