Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
AutowirePass.php
001 <?php
002
003 /*
004 * This file is part of the Symfony package.
005 *
006 * (c) Fabien Potencier <fabien@symfony.com>
007 *
008 * For the full copyright and license information, please view the LICENSE
009 * file that was distributed with this source code.
010 */
011
012 namespace Symfony\Component\DependencyInjection\Compiler;
013
014 use Symfony\Component\Config\Resource\ClassExistenceResource;
015 use Symfony\Component\DependencyInjection\Config\AutowireServiceResource;
016 use Symfony\Component\DependencyInjection\ContainerBuilder;
017 use Symfony\Component\DependencyInjection\Definition;
018 use Symfony\Component\DependencyInjection\Exception\AutowiringFailedException;
019 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
020 use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
021 use Symfony\Component\DependencyInjection\TypedReference;
022
023 /**
024 * Inspects existing service definitions and wires the autowired ones using the type hints of their classes.
025 *
026 * @author Kévin Dunglas <dunglas@gmail.com>
027 * @author Nicolas Grekas <p@tchwork.com>
028 */
029 class AutowirePass extends AbstractRecursivePass
030 {
031 private $definedTypes = [];
032 private $types;
033 private $ambiguousServiceTypes;
034 private $autowired = [];
035 private $lastFailure;
036 private $throwOnAutowiringException;
037 private $autowiringExceptions = [];
038 private $strictMode;
039
040 /**
041 * @param bool $throwOnAutowireException Errors can be retrieved via Definition::getErrors()
042 */
043 public function __construct($throwOnAutowireException = true)
044 {
045 $this->throwOnAutowiringException = $throwOnAutowireException;
046 }
047
048 /**
049 * @deprecated since version 3.4, to be removed in 4.0.
050 *
051 * @return AutowiringFailedException[]
052 */
053 public function getAutowiringExceptions()
054 {
055 @trigger_error('Calling AutowirePass::getAutowiringExceptions() is deprecated since Symfony 3.4 and will be removed in 4.0. Use Definition::getErrors() instead.', \E_USER_DEPRECATED);
056
057 return $this->autowiringExceptions;
058 }
059
060 /**
061 * {@inheritdoc}
062 */
063 public function process(ContainerBuilder $container)
064 {
065 // clear out any possibly stored exceptions from before
066 $this->autowiringExceptions = [];
067 $this->strictMode = $container->hasParameter('container.autowiring.strict_mode') && $container->getParameter('container.autowiring.strict_mode');
068
069 try {
070 parent::process($container);
071 } finally {
072 $this->definedTypes = [];
073 $this->types = null;
074 $this->ambiguousServiceTypes = null;
075 $this->autowired = [];
076 }
077 }
078
079 /**
080 * Creates a resource to help know if this service has changed.
081 *
082 * @return AutowireServiceResource
083 *
084 * @deprecated since version 3.3, to be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.
085 */
086 public static function createResourceForClass(\ReflectionClass $reflectionClass)
087 {
088 @trigger_error('The '.__METHOD__.'() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use ContainerBuilder::getReflectionClass() instead.', \E_USER_DEPRECATED);
089
090 $metadata = [];
091
092 foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
093 if (!$reflectionMethod->isStatic()) {
094 $metadata[$reflectionMethod->name] = self::getResourceMetadataForMethod($reflectionMethod);
095 }
096 }
097
098 return new AutowireServiceResource($reflectionClass->name, $reflectionClass->getFileName(), $metadata);
099 }
100
101 /**
102 * {@inheritdoc}
103 */
104 protected function processValue($value, $isRoot = false)
105 {
106 try {
107 return $this->doProcessValue($value, $isRoot);
108 } catch (AutowiringFailedException $e) {
109 if ($this->throwOnAutowiringException) {
110 throw $e;
111 }
112
113 $this->autowiringExceptions[] = $e;
114 $this->container->getDefinition($this->currentId)->addError($e->getMessage());
115
116 return parent::processValue($value, $isRoot);
117 }
118 }
119
120 private function doProcessValue($value, $isRoot = false)
121 {
122 if ($value instanceof TypedReference) {
123 if ($ref = $this->getAutowiredReference($value, $value->getRequiringClass() ? sprintf('for "%s" in "%s"', $value->getType(), $value->getRequiringClass()) : '')) {
124 return $ref;
125 }
126 $this->container->log($this, $this->createTypeNotFoundMessage($value, 'it'));
127 }
128 $value = parent::processValue($value, $isRoot);
129
130 if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
131 return $value;
132 }
133 if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
134 $this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" cannot be loaded.', $this->currentId, $value->getClass()));
135
136 return $value;
137 }
138
139 $methodCalls = $value->getMethodCalls();
140
141 try {
142 $constructor = $this->getConstructor($value, false);
143 } catch (RuntimeException $e) {
144 throw new AutowiringFailedException($this->currentId, $e->getMessage(), 0, $e);
145 }
146
147 if ($constructor) {
148 array_unshift($methodCalls, [$constructor, $value->getArguments()]);
149 }
150
151 $methodCalls = $this->autowireCalls($reflectionClass, $methodCalls);
152
153 if ($constructor) {
154 list(, $arguments) = array_shift($methodCalls);
155
156 if ($arguments !== $value->getArguments()) {
157 $value->setArguments($arguments);
158 }
159 }
160
161 if ($methodCalls !== $value->getMethodCalls()) {
162 $value->setMethodCalls($methodCalls);
163 }
164
165 return $value;
166 }
167
168 /**
169 * @return array
170 */
171 private function autowireCalls(\ReflectionClass $reflectionClass, array $methodCalls)
172 {
173 foreach ($methodCalls as $i => $call) {
174 list($method, $arguments) = $call;
175
176 if ($method instanceof \ReflectionFunctionAbstract) {
177 $reflectionMethod = $method;
178 } else {
179 $definition = new Definition($reflectionClass->name);
180 try {
181 $reflectionMethod = $this->getReflectionMethod($definition, $method);
182 } catch (RuntimeException $e) {
183 if ($definition->getFactory()) {
184 continue;
185 }
186 throw $e;
187 }
188 }
189
190 $arguments = $this->autowireMethod($reflectionMethod, $arguments);
191
192 if ($arguments !== $call[1]) {
193 $methodCalls[$i][1] = $arguments;
194 }
195 }
196
197 return $methodCalls;
198 }
199
200 /**
201 * Autowires the constructor or a method.
202 *
203 * @return array The autowired arguments
204 *
205 * @throws AutowiringFailedException
206 */
207 private function autowireMethod(\ReflectionFunctionAbstract $reflectionMethod, array $arguments)
208 {
209 $class = $reflectionMethod instanceof \ReflectionMethod ? $reflectionMethod->class : $this->currentId;
210 $method = $reflectionMethod->name;
211 $parameters = $reflectionMethod->getParameters();
212 if (method_exists('ReflectionMethod', 'isVariadic') && $reflectionMethod->isVariadic()) {
213 array_pop($parameters);
214 }
215
216 foreach ($parameters as $index => $parameter) {
217 if (\array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
218 continue;
219 }
220
221 $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true);
222
223 if (!$type) {
224 if (isset($arguments[$index])) {
225 continue;
226 }
227
228 // no default value? Then fail
229 if (!$parameter->isDefaultValueAvailable()) {
230 // For core classes, isDefaultValueAvailable() can
231 // be false when isOptional() returns true. If the
232 // argument *is* optional, allow it to be missing
233 if ($parameter->isOptional()) {
234 continue;
235 }
236 $type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, false);
237 $type = $type ? sprintf('is type-hinted "%s"', $type) : 'has no type-hint';
238
239 throw new AutowiringFailedException($this->currentId, sprintf('Cannot autowire service "%s": argument "$%s" of method "%s()" %s, you should configure its value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method, $type));
240 }
241
242 // specifically pass the default value
243 $arguments[$index] = $parameter->getDefaultValue();
244
245 continue;
246 }
247
248 if (!$value = $this->getAutowiredReference($ref = new TypedReference($type, $type, !$parameter->isOptional() ? $class : ''), 'for '.sprintf('argument "$%s" of method "%s()"', $parameter->name, $class.'::'.$method))) {
249 $failureMessage = $this->createTypeNotFoundMessage($ref, sprintf('argument "$%s" of method "%s()"', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
250
251 if ($parameter->isDefaultValueAvailable()) {
252 $value = $parameter->getDefaultValue();
253 } elseif (!$parameter->allowsNull()) {
254 throw new AutowiringFailedException($this->currentId, $failureMessage);
255 }
256 $this->container->log($this, $failureMessage);
257 }
258
259 $arguments[$index] = $value;
260 }
261
262 if ($parameters && !isset($arguments[++$index])) {
263 while (0 <= --$index) {
264 $parameter = $parameters[$index];
265 if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) {
266 break;
267 }
268 unset($arguments[$index]);
269 }
270 }
271
272 // it's possible index 1 was set, then index 0, then 2, etc
273 // make sure that we re-order so they're injected as expected
274 ksort($arguments);
275
276 return $arguments;
277 }
278
279 /**
280 * @return TypedReference|null A reference to the service matching the given type, if any
281 */
282 private function getAutowiredReference(TypedReference $reference, $deprecationMessage)
283 {
284 $this->lastFailure = null;
285 $type = $reference->getType();
286
287 if ($type !== $this->container->normalizeId($reference) || ($this->container->has($type) && !$this->container->findDefinition($type)->isAbstract())) {
288 return $reference;
289 }
290
291 if (null === $this->types) {
292 $this->populateAvailableTypes($this->strictMode);
293 }
294
295 if (isset($this->definedTypes[$type])) {
296 return new TypedReference($this->types[$type], $type);
297 }
298
299 if (!$this->strictMode && isset($this->types[$type])) {
300 $message = 'Autowiring services based on the types they implement is deprecated since Symfony 3.3 and won\'t be supported in version 4.0.';
301 if ($aliasSuggestion = $this->getAliasesSuggestionForType($type = $reference->getType(), $deprecationMessage)) {
302 $message .= ' '.$aliasSuggestion;
303 } else {
304 $message .= sprintf(' You should %s the "%s" service to "%s" instead.', isset($this->types[$this->types[$type]]) ? 'alias' : 'rename (or alias)', $this->types[$type], $type);
305 }
306
307 @trigger_error($message, \E_USER_DEPRECATED);
308
309 return new TypedReference($this->types[$type], $type);
310 }
311
312 if (!$reference->canBeAutoregistered() || isset($this->types[$type]) || isset($this->ambiguousServiceTypes[$type])) {
313 return null;
314 }
315
316 if (isset($this->autowired[$type])) {
317 return $this->autowired[$type] ? new TypedReference($this->autowired[$type], $type) : null;
318 }
319
320 if (!$this->strictMode) {
321 return $this->createAutowiredDefinition($type);
322 }
323
324 return null;
325 }
326
327 /**
328 * Populates the list of available types.
329 */
330 private function populateAvailableTypes($onlyAutowiringTypes = false)
331 {
332 $this->types = [];
333 if (!$onlyAutowiringTypes) {
334 $this->ambiguousServiceTypes = [];
335 }
336
337 foreach ($this->container->getDefinitions() as $id => $definition) {
338 $this->populateAvailableType($id, $definition, $onlyAutowiringTypes);
339 }
340 }
341
342 /**
343 * Populates the list of available types for a given definition.
344 *
345 * @param string $id
346 */
347 private function populateAvailableType($id, Definition $definition, $onlyAutowiringTypes)
348 {
349 // Never use abstract services
350 if ($definition->isAbstract()) {
351 return;
352 }
353
354 foreach ($definition->getAutowiringTypes(false) as $type) {
355 $this->definedTypes[$type] = true;
356 $this->types[$type] = $id;
357 unset($this->ambiguousServiceTypes[$type]);
358 }
359
360 if ($onlyAutowiringTypes) {
361 return;
362 }
363
364 if (preg_match('/^\d+_[^~]++~[._a-zA-Z\d]{7}$/', $id) || $definition->isDeprecated() || !$reflectionClass = $this->container->getReflectionClass($definition->getClass(), false)) {
365 return;
366 }
367
368 foreach ($reflectionClass->getInterfaces() as $reflectionInterface) {
369 $this->set($reflectionInterface->name, $id);
370 }
371
372 do {
373 $this->set($reflectionClass->name, $id);
374 } while ($reflectionClass = $reflectionClass->getParentClass());
375 }
376
377 /**
378 * Associates a type and a service id if applicable.
379 *
380 * @param string $type
381 * @param string $id
382 */
383 private function set($type, $id)
384 {
385 if (isset($this->definedTypes[$type])) {
386 return;
387 }
388
389 // is this already a type/class that is known to match multiple services?
390 if (isset($this->ambiguousServiceTypes[$type])) {
391 $this->ambiguousServiceTypes[$type][] = $id;
392
393 return;
394 }
395
396 // check to make sure the type doesn't match multiple services
397 if (!isset($this->types[$type]) || $this->types[$type] === $id) {
398 $this->types[$type] = $id;
399
400 return;
401 }
402
403 // keep an array of all services matching this type
404 if (!isset($this->ambiguousServiceTypes[$type])) {
405 $this->ambiguousServiceTypes[$type] = [$this->types[$type]];
406 unset($this->types[$type]);
407 }
408 $this->ambiguousServiceTypes[$type][] = $id;
409 }
410
411 /**
412 * Registers a definition for the type if possible or throws an exception.
413 *
414 * @param string $type
415 *
416 * @return TypedReference|null A reference to the registered definition
417 */
418 private function createAutowiredDefinition($type)
419 {
420 if (!($typeHint = $this->container->getReflectionClass($type, false)) || !$typeHint->isInstantiable()) {
421 return null;
422 }
423
424 $currentId = $this->currentId;
425 $this->currentId = $type;
426 $this->autowired[$type] = $argumentId = sprintf('autowired.%s', $type);
427 $argumentDefinition = new Definition($type);
428 $argumentDefinition->setPublic(false);
429 $argumentDefinition->setAutowired(true);
430
431 try {
432 $originalThrowSetting = $this->throwOnAutowiringException;
433 $this->throwOnAutowiringException = true;
434 $this->processValue($argumentDefinition, true);
435 $this->container->setDefinition($argumentId, $argumentDefinition);
436 } catch (AutowiringFailedException $e) {
437 $this->autowired[$type] = false;
438 $this->lastFailure = $e->getMessage();
439 $this->container->log($this, $this->lastFailure);
440
441 return null;
442 } finally {
443 $this->throwOnAutowiringException = $originalThrowSetting;
444 $this->currentId = $currentId;
445 }
446
447 @trigger_error(sprintf('Relying on service auto-registration for type "%s" is deprecated since Symfony 3.4 and won\'t be supported in 4.0. Create a service named "%s" instead.', $type, $type), \E_USER_DEPRECATED);
448
449 $this->container->log($this, sprintf('Type "%s" has been auto-registered for service "%s".', $type, $this->currentId));
450
451 return new TypedReference($argumentId, $type);
452 }
453
454 private function createTypeNotFoundMessage(TypedReference $reference, $label)
455 {
456 $trackResources = $this->container->isTrackingResources();
457 $this->container->setResourceTracking(false);
458 try {
459 if ($r = $this->container->getReflectionClass($type = $reference->getType(), false)) {
460 $alternatives = $this->createTypeAlternatives($reference);
461 }
462 } finally {
463 $this->container->setResourceTracking($trackResources);
464 }
465
466 if (!$r) {
467 // either $type does not exist or a parent class does not exist
468 try {
469 $resource = new ClassExistenceResource($type, false);
470 // isFresh() will explode ONLY if a parent class/trait does not exist
471 $resource->isFresh(0);
472 $parentMsg = false;
473 } catch (\ReflectionException $e) {
474 $parentMsg = $e->getMessage();
475 }
476
477 $message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found');
478 } else {
479 $message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists';
480 $message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives);
481
482 if ($r->isInterface() && !$alternatives) {
483 $message .= ' Did you create a class that implements this interface?';
484 }
485 }
486
487 $message = sprintf('Cannot autowire service "%s": %s %s', $this->currentId, $label, $message);
488
489 if (null !== $this->lastFailure) {
490 $message = $this->lastFailure."\n".$message;
491 $this->lastFailure = null;
492 }
493
494 return $message;
495 }
496
497 private function createTypeAlternatives(TypedReference $reference)
498 {
499 // try suggesting available aliases first
500 if ($message = $this->getAliasesSuggestionForType($type = $reference->getType())) {
501 return ' '.$message;
502 }
503 if (null === $this->ambiguousServiceTypes) {
504 $this->populateAvailableTypes();
505 }
506
507 if (isset($this->ambiguousServiceTypes[$type])) {
508 $message = sprintf('one of these existing services: "%s"', implode('", "', $this->ambiguousServiceTypes[$type]));
509 } elseif (isset($this->types[$type])) {
510 $message = sprintf('the existing "%s" service', $this->types[$type]);
511 } elseif ($reference->getRequiringClass() && !$reference->canBeAutoregistered() && !$this->strictMode) {
512 return ' It cannot be auto-registered because it is from a different root namespace.';
513 } else {
514 return '';
515 }
516
517 return sprintf(' You should maybe alias this %s to %s.', class_exists($type, false) ? 'class' : 'interface', $message);
518 }
519
520 /**
521 * @deprecated since version 3.3, to be removed in 4.0.
522 */
523 private static function getResourceMetadataForMethod(\ReflectionMethod $method)
524 {
525 $methodArgumentsMetadata = [];
526 foreach ($method->getParameters() as $parameter) {
527 try {
528 if (method_exists($parameter, 'getType')) {
529 $type = $parameter->getType();
530 if ($type && !$type->isBuiltin()) {
531 $class = new \ReflectionClass($type instanceof \ReflectionNamedType ? $type->getName() : (string) $type);
532 } else {
533 $class = null;
534 }
535 } else {
536 $class = $parameter->getClass();
537 }
538 } catch (\ReflectionException $e) {
539 // type-hint is against a non-existent class
540 $class = false;
541 }
542
543 $isVariadic = method_exists($parameter, 'isVariadic') && $parameter->isVariadic();
544 $methodArgumentsMetadata[] = [
545 'class' => $class,
546 'isOptional' => $parameter->isOptional(),
547 'defaultValue' => ($parameter->isOptional() && !$isVariadic) ? $parameter->getDefaultValue() : null,
548 ];
549 }
550
551 return $methodArgumentsMetadata;
552 }
553
554 private function getAliasesSuggestionForType($type, $extraContext = null)
555 {
556 $aliases = [];
557 foreach (class_parents($type) + class_implements($type) as $parent) {
558 if ($this->container->has($parent) && !$this->container->findDefinition($parent)->isAbstract()) {
559 $aliases[] = $parent;
560 }
561 }
562
563 $extraContext = $extraContext ? ' '.$extraContext : '';
564 if (1 < $len = \count($aliases)) {
565 $message = sprintf('Try changing the type-hint%s to one of its parents: ', $extraContext);
566 for ($i = 0, --$len; $i < $len; ++$i) {
567 $message .= sprintf('%s "%s", ', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]);
568 }
569 $message .= sprintf('or %s "%s".', class_exists($aliases[$i], false) ? 'class' : 'interface', $aliases[$i]);
570
571 return $message;
572 }
573
574 if ($aliases) {
575 return sprintf('Try changing the type-hint%s to "%s" instead.', $extraContext, $aliases[0]);
576 }
577
578 return null;
579 }
580 }
581