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