Mercurial > hg > isophonics-drupal-site
comparison vendor/symfony/dependency-injection/Compiler/AutowirePass.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 /* | |
4 * This file is part of the Symfony package. | |
5 * | |
6 * (c) Fabien Potencier <fabien@symfony.com> | |
7 * | |
8 * For the full copyright and license information, please view the LICENSE | |
9 * file that was distributed with this source code. | |
10 */ | |
11 | |
12 namespace Symfony\Component\DependencyInjection\Compiler; | |
13 | |
14 use Symfony\Component\DependencyInjection\Config\AutowireServiceResource; | |
15 use Symfony\Component\DependencyInjection\ContainerBuilder; | |
16 use Symfony\Component\DependencyInjection\Definition; | |
17 use Symfony\Component\DependencyInjection\Exception\RuntimeException; | |
18 use Symfony\Component\DependencyInjection\Reference; | |
19 | |
20 /** | |
21 * Guesses constructor arguments of services definitions and try to instantiate services if necessary. | |
22 * | |
23 * @author Kévin Dunglas <dunglas@gmail.com> | |
24 */ | |
25 class AutowirePass implements CompilerPassInterface | |
26 { | |
27 private $container; | |
28 private $reflectionClasses = array(); | |
29 private $definedTypes = array(); | |
30 private $types; | |
31 private $ambiguousServiceTypes = array(); | |
32 private $autowired = array(); | |
33 | |
34 /** | |
35 * {@inheritdoc} | |
36 */ | |
37 public function process(ContainerBuilder $container) | |
38 { | |
39 $throwingAutoloader = function ($class) { throw new \ReflectionException(sprintf('Class %s does not exist', $class)); }; | |
40 spl_autoload_register($throwingAutoloader); | |
41 | |
42 try { | |
43 $this->container = $container; | |
44 foreach ($container->getDefinitions() as $id => $definition) { | |
45 if ($definition->isAutowired()) { | |
46 $this->completeDefinition($id, $definition); | |
47 } | |
48 } | |
49 } finally { | |
50 spl_autoload_unregister($throwingAutoloader); | |
51 | |
52 // Free memory and remove circular reference to container | |
53 $this->reflectionClasses = array(); | |
54 $this->definedTypes = array(); | |
55 $this->types = null; | |
56 $this->ambiguousServiceTypes = array(); | |
57 $this->autowired = array(); | |
58 } | |
59 } | |
60 | |
61 /** | |
62 * Creates a resource to help know if this service has changed. | |
63 * | |
64 * @param \ReflectionClass $reflectionClass | |
65 * | |
66 * @return AutowireServiceResource | |
67 */ | |
68 public static function createResourceForClass(\ReflectionClass $reflectionClass) | |
69 { | |
70 $metadata = array(); | |
71 | |
72 if ($constructor = $reflectionClass->getConstructor()) { | |
73 $metadata['__construct'] = self::getResourceMetadataForMethod($constructor); | |
74 } | |
75 | |
76 foreach (self::getSetters($reflectionClass) as $reflectionMethod) { | |
77 $metadata[$reflectionMethod->name] = self::getResourceMetadataForMethod($reflectionMethod); | |
78 } | |
79 | |
80 return new AutowireServiceResource($reflectionClass->name, $reflectionClass->getFileName(), $metadata); | |
81 } | |
82 | |
83 /** | |
84 * Wires the given definition. | |
85 * | |
86 * @param string $id | |
87 * @param Definition $definition | |
88 * | |
89 * @throws RuntimeException | |
90 */ | |
91 private function completeDefinition($id, Definition $definition) | |
92 { | |
93 if ($definition->getFactory()) { | |
94 throw new RuntimeException(sprintf('Service "%s" can use either autowiring or a factory, not both.', $id)); | |
95 } | |
96 | |
97 if (!$reflectionClass = $this->getReflectionClass($id, $definition)) { | |
98 return; | |
99 } | |
100 | |
101 if ($this->container->isTrackingResources()) { | |
102 $this->container->addResource(static::createResourceForClass($reflectionClass)); | |
103 } | |
104 | |
105 if (!$constructor = $reflectionClass->getConstructor()) { | |
106 return; | |
107 } | |
108 $parameters = $constructor->getParameters(); | |
109 if (method_exists('ReflectionMethod', 'isVariadic') && $constructor->isVariadic()) { | |
110 array_pop($parameters); | |
111 } | |
112 | |
113 $arguments = $definition->getArguments(); | |
114 foreach ($parameters as $index => $parameter) { | |
115 if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) { | |
116 continue; | |
117 } | |
118 | |
119 try { | |
120 if (!$typeHint = $parameter->getClass()) { | |
121 if (isset($arguments[$index])) { | |
122 continue; | |
123 } | |
124 | |
125 // no default value? Then fail | |
126 if (!$parameter->isOptional()) { | |
127 throw new RuntimeException(sprintf('Unable to autowire argument index %d ($%s) for the service "%s". If this is an object, give it a type-hint. Otherwise, specify this argument\'s value explicitly.', $index, $parameter->name, $id)); | |
128 } | |
129 | |
130 // specifically pass the default value | |
131 $arguments[$index] = $parameter->getDefaultValue(); | |
132 | |
133 continue; | |
134 } | |
135 | |
136 if (isset($this->autowired[$typeHint->name])) { | |
137 $arguments[$index] = $this->autowired[$typeHint->name] ? new Reference($this->autowired[$typeHint->name]) : null; | |
138 continue; | |
139 } | |
140 | |
141 if (null === $this->types) { | |
142 $this->populateAvailableTypes(); | |
143 } | |
144 | |
145 if (isset($this->types[$typeHint->name])) { | |
146 $value = new Reference($this->types[$typeHint->name]); | |
147 } else { | |
148 try { | |
149 $value = $this->createAutowiredDefinition($typeHint, $id); | |
150 } catch (RuntimeException $e) { | |
151 if ($parameter->isDefaultValueAvailable()) { | |
152 $value = $parameter->getDefaultValue(); | |
153 } elseif ($parameter->allowsNull()) { | |
154 $value = null; | |
155 } else { | |
156 throw $e; | |
157 } | |
158 $this->autowired[$typeHint->name] = false; | |
159 } | |
160 } | |
161 } catch (\ReflectionException $e) { | |
162 // Typehint against a non-existing class | |
163 | |
164 if (!$parameter->isDefaultValueAvailable()) { | |
165 throw new RuntimeException(sprintf('Cannot autowire argument %s for %s because the type-hinted class does not exist (%s).', $index + 1, $definition->getClass(), $e->getMessage()), 0, $e); | |
166 } | |
167 | |
168 $value = $parameter->getDefaultValue(); | |
169 } | |
170 | |
171 $arguments[$index] = $value; | |
172 } | |
173 | |
174 if ($parameters && !isset($arguments[++$index])) { | |
175 while (0 <= --$index) { | |
176 $parameter = $parameters[$index]; | |
177 if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) { | |
178 break; | |
179 } | |
180 unset($arguments[$index]); | |
181 } | |
182 } | |
183 | |
184 // it's possible index 1 was set, then index 0, then 2, etc | |
185 // make sure that we re-order so they're injected as expected | |
186 ksort($arguments); | |
187 $definition->setArguments($arguments); | |
188 } | |
189 | |
190 /** | |
191 * Populates the list of available types. | |
192 */ | |
193 private function populateAvailableTypes() | |
194 { | |
195 $this->types = array(); | |
196 | |
197 foreach ($this->container->getDefinitions() as $id => $definition) { | |
198 $this->populateAvailableType($id, $definition); | |
199 } | |
200 } | |
201 | |
202 /** | |
203 * Populates the list of available types for a given definition. | |
204 * | |
205 * @param string $id | |
206 * @param Definition $definition | |
207 */ | |
208 private function populateAvailableType($id, Definition $definition) | |
209 { | |
210 // Never use abstract services | |
211 if ($definition->isAbstract()) { | |
212 return; | |
213 } | |
214 | |
215 foreach ($definition->getAutowiringTypes() as $type) { | |
216 $this->definedTypes[$type] = true; | |
217 $this->types[$type] = $id; | |
218 unset($this->ambiguousServiceTypes[$type]); | |
219 } | |
220 | |
221 if (!$reflectionClass = $this->getReflectionClass($id, $definition)) { | |
222 return; | |
223 } | |
224 | |
225 foreach ($reflectionClass->getInterfaces() as $reflectionInterface) { | |
226 $this->set($reflectionInterface->name, $id); | |
227 } | |
228 | |
229 do { | |
230 $this->set($reflectionClass->name, $id); | |
231 } while ($reflectionClass = $reflectionClass->getParentClass()); | |
232 } | |
233 | |
234 /** | |
235 * Associates a type and a service id if applicable. | |
236 * | |
237 * @param string $type | |
238 * @param string $id | |
239 */ | |
240 private function set($type, $id) | |
241 { | |
242 if (isset($this->definedTypes[$type])) { | |
243 return; | |
244 } | |
245 | |
246 // is this already a type/class that is known to match multiple services? | |
247 if (isset($this->ambiguousServiceTypes[$type])) { | |
248 $this->ambiguousServiceTypes[$type][] = $id; | |
249 | |
250 return; | |
251 } | |
252 | |
253 // check to make sure the type doesn't match multiple services | |
254 if (!isset($this->types[$type]) || $this->types[$type] === $id) { | |
255 $this->types[$type] = $id; | |
256 | |
257 return; | |
258 } | |
259 | |
260 // keep an array of all services matching this type | |
261 if (!isset($this->ambiguousServiceTypes[$type])) { | |
262 $this->ambiguousServiceTypes[$type] = array($this->types[$type]); | |
263 unset($this->types[$type]); | |
264 } | |
265 $this->ambiguousServiceTypes[$type][] = $id; | |
266 } | |
267 | |
268 /** | |
269 * Registers a definition for the type if possible or throws an exception. | |
270 * | |
271 * @param \ReflectionClass $typeHint | |
272 * @param string $id | |
273 * | |
274 * @return Reference A reference to the registered definition | |
275 * | |
276 * @throws RuntimeException | |
277 */ | |
278 private function createAutowiredDefinition(\ReflectionClass $typeHint, $id) | |
279 { | |
280 if (isset($this->ambiguousServiceTypes[$typeHint->name])) { | |
281 $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; | |
282 $matchingServices = implode(', ', $this->ambiguousServiceTypes[$typeHint->name]); | |
283 | |
284 throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $typeHint->name, $id, $classOrInterface, $matchingServices)); | |
285 } | |
286 | |
287 if (!$typeHint->isInstantiable()) { | |
288 $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; | |
289 throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $id, $classOrInterface)); | |
290 } | |
291 | |
292 $this->autowired[$typeHint->name] = $argumentId = sprintf('autowired.%s', $typeHint->name); | |
293 | |
294 $argumentDefinition = $this->container->register($argumentId, $typeHint->name); | |
295 $argumentDefinition->setPublic(false); | |
296 | |
297 try { | |
298 $this->completeDefinition($argumentId, $argumentDefinition); | |
299 } catch (RuntimeException $e) { | |
300 $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; | |
301 $message = sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $id, $classOrInterface); | |
302 throw new RuntimeException($message, 0, $e); | |
303 } | |
304 | |
305 return new Reference($argumentId); | |
306 } | |
307 | |
308 /** | |
309 * Retrieves the reflection class associated with the given service. | |
310 * | |
311 * @param string $id | |
312 * @param Definition $definition | |
313 * | |
314 * @return \ReflectionClass|false | |
315 */ | |
316 private function getReflectionClass($id, Definition $definition) | |
317 { | |
318 if (isset($this->reflectionClasses[$id])) { | |
319 return $this->reflectionClasses[$id]; | |
320 } | |
321 | |
322 // Cannot use reflection if the class isn't set | |
323 if (!$class = $definition->getClass()) { | |
324 return false; | |
325 } | |
326 | |
327 $class = $this->container->getParameterBag()->resolveValue($class); | |
328 | |
329 if ($deprecated = $definition->isDeprecated()) { | |
330 $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { | |
331 return (E_USER_DEPRECATED === $level || !$prevErrorHandler) ? false : $prevErrorHandler($level, $message, $file, $line); | |
332 }); | |
333 } | |
334 | |
335 $e = null; | |
336 | |
337 try { | |
338 $reflector = new \ReflectionClass($class); | |
339 } catch (\Exception $e) { | |
340 } catch (\Throwable $e) { | |
341 } | |
342 | |
343 if ($deprecated) { | |
344 restore_error_handler(); | |
345 } | |
346 | |
347 if (null !== $e) { | |
348 if (!$e instanceof \ReflectionException) { | |
349 throw $e; | |
350 } | |
351 $reflector = false; | |
352 } | |
353 | |
354 return $this->reflectionClasses[$id] = $reflector; | |
355 } | |
356 | |
357 /** | |
358 * @param \ReflectionClass $reflectionClass | |
359 * | |
360 * @return \ReflectionMethod[] | |
361 */ | |
362 private static function getSetters(\ReflectionClass $reflectionClass) | |
363 { | |
364 foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) { | |
365 if (!$reflectionMethod->isStatic() && 1 === $reflectionMethod->getNumberOfParameters() && 0 === strpos($reflectionMethod->name, 'set')) { | |
366 yield $reflectionMethod; | |
367 } | |
368 } | |
369 } | |
370 | |
371 private static function getResourceMetadataForMethod(\ReflectionMethod $method) | |
372 { | |
373 $methodArgumentsMetadata = array(); | |
374 foreach ($method->getParameters() as $parameter) { | |
375 try { | |
376 $class = $parameter->getClass(); | |
377 } catch (\ReflectionException $e) { | |
378 // type-hint is against a non-existent class | |
379 $class = false; | |
380 } | |
381 | |
382 $isVariadic = method_exists($parameter, 'isVariadic') && $parameter->isVariadic(); | |
383 $methodArgumentsMetadata[] = array( | |
384 'class' => $class, | |
385 'isOptional' => $parameter->isOptional(), | |
386 'defaultValue' => ($parameter->isOptional() && !$isVariadic) ? $parameter->getDefaultValue() : null, | |
387 ); | |
388 } | |
389 | |
390 return $methodArgumentsMetadata; | |
391 } | |
392 } |