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 ;
8
11
use PHPStan \Rules \Rule ;
9
12
use PHPStan \Rules \RuleErrorBuilder ;
13
+ use PHPStan \Symfony \ServiceDefinition ;
10
14
use PHPStan \Symfony \ServiceMap ;
11
15
use PHPStan \TrinaryLogic ;
12
16
use PHPStan \Type \ObjectType ;
13
17
use PHPStan \Type \Type ;
18
+ use Symfony \Component \DependencyInjection \Attribute \AutowireLocator ;
19
+ use function class_exists ;
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,81 @@ 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 (!class_exists ('Symfony \\Component \\DependencyInjection \\Attribute \\AutowireLocator ' )) {
119
+ return false ;
120
+ }
121
+
122
+ if (
123
+ !$ node instanceof MethodCall
124
+ ) {
125
+ return false ;
126
+ }
127
+
128
+ $ nodeParentProperty = $ node ->var ;
129
+
130
+ if (!$ nodeParentProperty instanceof Node \Expr \PropertyFetch) {
131
+ return false ;
132
+ }
133
+
134
+ $ nodeParentPropertyName = $ nodeParentProperty ->name ;
135
+
136
+ if (!$ nodeParentPropertyName instanceof Node \Identifier) {
137
+ return false ;
138
+ }
139
+
140
+ $ containerInterfacePropertyName = $ nodeParentPropertyName ->name ;
141
+ $ scopeClassReflection = $ scope ->getClassReflection ();
142
+
143
+ if (!$ scopeClassReflection instanceof ClassReflection) {
144
+ return false ;
145
+ }
146
+
147
+ $ containerInterfacePropertyReflection = $ scopeClassReflection
148
+ ->getNativeProperty ($ containerInterfacePropertyName );
149
+ $ classPropertyReflection = $ containerInterfacePropertyReflection ->getNativeReflection ();
150
+ $ autowireLocatorAttributes = $ classPropertyReflection ->getAttributes (AutowireLocator::class);
151
+
152
+ return $ this ->isAutowireLocatorService ($ autowireLocatorAttributes , $ service );
153
+ }
154
+
155
+ /**
156
+ * @param array<int, FakeReflectionAttribute|ReflectionAttribute> $autowireLocatorAttributes
157
+ */
158
+ private function isAutowireLocatorService (array $ autowireLocatorAttributes , ServiceDefinition $ service ): bool
159
+ {
160
+ foreach ($ autowireLocatorAttributes as $ autowireLocatorAttribute ) {
161
+ foreach ($ autowireLocatorAttribute ->getArgumentsExpressions () as $ autowireLocatorServices ) {
162
+ if (!$ autowireLocatorServices instanceof Node \Expr \Array_) {
163
+ continue ;
164
+ }
165
+
166
+ foreach ($ autowireLocatorServices ->items as $ autowireLocatorServiceNode ) {
167
+ /** @var Node\Expr\ArrayItem $autowireLocatorServiceNode */
168
+ $ autowireLocatorServiceExpr = $ autowireLocatorServiceNode ->value ;
169
+
170
+ switch (get_class ($ autowireLocatorServiceExpr )) {
171
+ case Node \Scalar \String_::class:
172
+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->value ;
173
+ break ;
174
+ case Node \Expr \ClassConstFetch::class:
175
+ $ autowireLocatorServiceClass = $ autowireLocatorServiceExpr ->class instanceof Node \Name
176
+ ? $ autowireLocatorServiceExpr ->class ->toString ()
177
+ : null ;
178
+ break ;
179
+ default :
180
+ $ autowireLocatorServiceClass = null ;
181
+ }
182
+
183
+ if ($ service ->getId () === $ autowireLocatorServiceClass ) {
184
+ return true ;
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ return false ;
191
+ }
192
+
95
193
}
0 commit comments