ServiceLocator.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\DependencyInjection;
  11. use Psr\Container\ContainerExceptionInterface;
  12. use Psr\Container\NotFoundExceptionInterface;
  13. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  14. use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
  15. use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
  16. use Symfony\Contracts\Service\ServiceLocatorTrait;
  17. use Symfony\Contracts\Service\ServiceProviderInterface;
  18. use Symfony\Contracts\Service\ServiceSubscriberInterface;
  19. /**
  20. * @author Robin Chalas <robin.chalas@gmail.com>
  21. * @author Nicolas Grekas <p@tchwork.com>
  22. */
  23. class ServiceLocator implements ServiceProviderInterface
  24. {
  25. use ServiceLocatorTrait {
  26. get as private doGet;
  27. }
  28. private $externalId;
  29. private $container;
  30. /**
  31. * {@inheritdoc}
  32. *
  33. * @return mixed
  34. */
  35. public function get($id)
  36. {
  37. if (!$this->externalId) {
  38. return $this->doGet($id);
  39. }
  40. try {
  41. return $this->doGet($id);
  42. } catch (RuntimeException $e) {
  43. $what = sprintf('service "%s" required by "%s"', $id, $this->externalId);
  44. $message = preg_replace('/service "\.service_locator\.[^"]++"/', $what, $e->getMessage());
  45. if ($e->getMessage() === $message) {
  46. $message = sprintf('Cannot resolve %s: %s', $what, $message);
  47. }
  48. $r = new \ReflectionProperty($e, 'message');
  49. $r->setAccessible(true);
  50. $r->setValue($e, $message);
  51. throw $e;
  52. }
  53. }
  54. public function __invoke(string $id)
  55. {
  56. return isset($this->factories[$id]) ? $this->get($id) : null;
  57. }
  58. /**
  59. * @internal
  60. *
  61. * @return static
  62. */
  63. public function withContext(string $externalId, Container $container): self
  64. {
  65. $locator = clone $this;
  66. $locator->externalId = $externalId;
  67. $locator->container = $container;
  68. return $locator;
  69. }
  70. private function createNotFoundException(string $id): NotFoundExceptionInterface
  71. {
  72. if ($this->loading) {
  73. $msg = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $this->formatAlternatives());
  74. return new ServiceNotFoundException($id, end($this->loading) ?: null, null, [], $msg);
  75. }
  76. $class = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 4);
  77. $class = isset($class[3]['object']) ? \get_class($class[3]['object']) : null;
  78. $externalId = $this->externalId ?: $class;
  79. $msg = [];
  80. $msg[] = sprintf('Service "%s" not found:', $id);
  81. if (!$this->container) {
  82. $class = null;
  83. } elseif ($this->container->has($id) || isset($this->container->getRemovedIds()[$id])) {
  84. $msg[] = 'even though it exists in the app\'s container,';
  85. } else {
  86. try {
  87. $this->container->get($id);
  88. $class = null;
  89. } catch (ServiceNotFoundException $e) {
  90. if ($e->getAlternatives()) {
  91. $msg[] = sprintf('did you mean %s? Anyway,', $this->formatAlternatives($e->getAlternatives(), 'or'));
  92. } else {
  93. $class = null;
  94. }
  95. }
  96. }
  97. if ($externalId) {
  98. $msg[] = sprintf('the container inside "%s" is a smaller service locator that %s', $externalId, $this->formatAlternatives());
  99. } else {
  100. $msg[] = sprintf('the current service locator %s', $this->formatAlternatives());
  101. }
  102. if (!$class) {
  103. // no-op
  104. } elseif (is_subclass_of($class, ServiceSubscriberInterface::class)) {
  105. $msg[] = sprintf('Unless you need extra laziness, try using dependency injection instead. Otherwise, you need to declare it using "%s::getSubscribedServices()".', preg_replace('/([^\\\\]++\\\\)++/', '', $class));
  106. } else {
  107. $msg[] = 'Try using dependency injection instead.';
  108. }
  109. return new ServiceNotFoundException($id, end($this->loading) ?: null, null, [], implode(' ', $msg));
  110. }
  111. private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface
  112. {
  113. return new ServiceCircularReferenceException($id, $path);
  114. }
  115. private function formatAlternatives(array $alternatives = null, string $separator = 'and'): string
  116. {
  117. $format = '"%s"%s';
  118. if (null === $alternatives) {
  119. if (!$alternatives = array_keys($this->factories)) {
  120. return 'is empty...';
  121. }
  122. $format = sprintf('only knows about the %s service%s.', $format, 1 < \count($alternatives) ? 's' : '');
  123. }
  124. $last = array_pop($alternatives);
  125. return sprintf($format, $alternatives ? implode('", "', $alternatives) : $last, $alternatives ? sprintf(' %s "%s"', $separator, $last) : '');
  126. }
  127. }