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