PluginProbe
Packeta / trunk
Packeta vtrunk
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / deps / nette / di / src / DI / Container.php

Container.php in Packeta trunk, at deps/nette/di/src/DI/Container.php

299 lines 11.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * This file is part of the Nette Framework (https://nette.org)
5 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6 */
7 declare (strict_types=1);
8 namespace Packetery\Nette\DI;
9
10 use Packetery\Nette;
11 /**
12 * The dependency injection container default implementation.
13 */
14 class Container
15 {
16 use \Packetery\Nette\SmartObject;
17 /**
18 * @var mixed[]
19 * @deprecated use Container::getParameter() or getParameters()
20 */
21 public $parameters = [];
22 /** @var string[] services name => type (complete list of available services) */
23 protected $types = [];
24 /** @var string[] alias => service name */
25 protected $aliases = [];
26 /** @var array[] tag name => service name => tag value */
27 protected $tags = [];
28 /** @var array[] type => level => services */
29 protected $wiring = [];
30 /** @var object[] service name => instance */
31 private $instances = [];
32 /** @var array<string, true> circular reference detector */
33 private $creating;
34 /** @var array<string, string|\Closure> */
35 private $methods;
36 public function __construct(array $params = [])
37 {
38 $this->parameters = $params + $this->getStaticParameters();
39 $this->methods = \array_flip(\array_filter(\get_class_methods($this), function ($s) {
40 return \preg_match('#^createService.#', $s);
41 }));
42 }
43 public function getParameters() : array
44 {
45 return $this->parameters;
46 }
47 public function getParameter($key)
48 {
49 if (!\array_key_exists($key, $this->parameters)) {
50 $this->parameters[$key] = $this->preventDeadLock("%{$key}%", function () use($key) {
51 return $this->getDynamicParameter($key);
52 });
53 }
54 return $this->parameters[$key];
55 }
56 protected function getStaticParameters() : array
57 {
58 return [];
59 }
60 protected function getDynamicParameter($key)
61 {
62 throw new \Packetery\Nette\InvalidStateException(\sprintf("Parameter '%s' not found. Check if 'di › export › parameters' is enabled.", $key));
63 }
64 /**
65 * Adds the service to the container.
66 * @param object $service service or its factory
67 * @return static
68 */
69 public function addService(string $name, object $service)
70 {
71 $name = $this->aliases[$name] ?? $name;
72 if (isset($this->instances[$name])) {
73 throw new \Packetery\Nette\InvalidStateException(\sprintf("Service '%s' already exists.", $name));
74 }
75 if ($service instanceof \Closure) {
76 $rt = \Packetery\Nette\Utils\Type::fromReflection(new \ReflectionFunction($service));
77 $type = $rt ? Helpers::ensureClassType($rt, 'return type of closure') : '';
78 } else {
79 $type = \get_class($service);
80 }
81 if (!isset($this->methods[self::getMethodName($name)])) {
82 $this->types[$name] = $type;
83 } elseif (($expectedType = $this->getServiceType($name)) && !\is_a($type, $expectedType, \true)) {
84 throw new \Packetery\Nette\InvalidArgumentException(\sprintf("Service '%s' must be instance of %s, %s.", $name, $expectedType, $type ? "{$type} given" : 'add typehint to closure'));
85 }
86 if ($service instanceof \Closure) {
87 $this->methods[self::getMethodName($name)] = $service;
88 $this->types[$name] = $type;
89 } else {
90 $this->instances[$name] = $service;
91 }
92 return $this;
93 }
94 /**
95 * Removes the service from the container.
96 */
97 public function removeService(string $name) : void
98 {
99 $name = $this->aliases[$name] ?? $name;
100 unset($this->instances[$name]);
101 }
102 /**
103 * Gets the service object by name.
104 * @throws MissingServiceException
105 */
106 public function getService(string $name) : object
107 {
108 if (!isset($this->instances[$name])) {
109 if (isset($this->aliases[$name])) {
110 return $this->getService($this->aliases[$name]);
111 }
112 $this->instances[$name] = $this->createService($name);
113 }
114 return $this->instances[$name];
115 }
116 /**
117 * Gets the service object by name.
118 * @throws MissingServiceException
119 */
120 public function getByName(string $name) : object
121 {
122 return $this->getService($name);
123 }
124 /**
125 * Gets the service type by name.
126 * @throws MissingServiceException
127 */
128 public function getServiceType(string $name) : string
129 {
130 $method = self::getMethodName($name);
131 if (isset($this->aliases[$name])) {
132 return $this->getServiceType($this->aliases[$name]);
133 } elseif (isset($this->types[$name])) {
134 return $this->types[$name];
135 } elseif (isset($this->methods[$method])) {
136 $type = (new \ReflectionMethod($this, $method))->getReturnType();
137 return $type ? $type->getName() : '';
138 } else {
139 throw new MissingServiceException(\sprintf("Service '%s' not found.", $name));
140 }
141 }
142 /**
143 * Does the service exist?
144 */
145 public function hasService(string $name) : bool
146 {
147 $name = $this->aliases[$name] ?? $name;
148 return isset($this->methods[self::getMethodName($name)]) || isset($this->instances[$name]);
149 }
150 /**
151 * Is the service created?
152 */
153 public function isCreated(string $name) : bool
154 {
155 if (!$this->hasService($name)) {
156 throw new MissingServiceException(\sprintf("Service '%s' not found.", $name));
157 }
158 $name = $this->aliases[$name] ?? $name;
159 return isset($this->instances[$name]);
160 }
161 /**
162 * Creates new instance of the service.
163 * @throws MissingServiceException
164 */
165 public function createService(string $name) : object
166 {
167 $name = $this->aliases[$name] ?? $name;
168 $method = self::getMethodName($name);
169 $callback = $this->methods[$method] ?? null;
170 if ($callback === null) {
171 throw new MissingServiceException(\sprintf("Service '%s' not found.", $name));
172 }
173 $service = $this->preventDeadLock($name, function () use($callback, $method) {
174 return $callback instanceof \Closure ? $callback() : $this->{$method}();
175 });
176 if (!\is_object($service)) {
177 throw new \Packetery\Nette\UnexpectedValueException(\sprintf("Unable to create service '{$name}', value returned by %s is not object.", $callback instanceof \Closure ? 'closure' : "method {$method}()"));
178 }
179 return $service;
180 }
181 /**
182 * Resolves service by type.
183 * @template T of object
184 * @param class-string<T> $type
185 * @return ($throw is true ? T : ?T)
186 * @throws MissingServiceException
187 */
188 public function getByType(string $type, bool $throw = \true) : ?object
189 {
190 $type = Helpers::normalizeClass($type);
191 if (!empty($this->wiring[$type][0])) {
192 if (\count($names = $this->wiring[$type][0]) === 1) {
193 return $this->getService($names[0]);
194 }
195 \natsort($names);
196 throw new MissingServiceException(\sprintf("Multiple services of type {$type} found: %s.", \implode(', ', $names)));
197 } elseif ($throw) {
198 if (!\class_exists($type) && !\interface_exists($type)) {
199 throw new MissingServiceException(\sprintf("Service of type '%s' not found. Check the class name because it cannot be found.", $type));
200 }
201 foreach ($this->methods as $method => $foo) {
202 $methodType = (new \ReflectionMethod(static::class, $method))->getReturnType()->getName();
203 if (\is_a($methodType, $type, \true)) {
204 throw new MissingServiceException(\sprintf("Service of type %s is not autowired or is missing in di › export › types.", $type));
205 }
206 }
207 throw new MissingServiceException(\sprintf('Service of type %s not found. Did you add it to configuration file?', $type));
208 }
209 return null;
210 }
211 /**
212 * Gets the autowired service names of the specified type.
213 * @return string[]
214 * @internal
215 */
216 public function findAutowired(string $type) : array
217 {
218 $type = Helpers::normalizeClass($type);
219 return \array_merge($this->wiring[$type][0] ?? [], $this->wiring[$type][1] ?? []);
220 }
221 /**
222 * Gets the service names of the specified type.
223 * @return string[]
224 */
225 public function findByType(string $type) : array
226 {
227 $type = Helpers::normalizeClass($type);
228 return empty($this->wiring[$type]) ? [] : \array_merge(...\array_values($this->wiring[$type]));
229 }
230 /**
231 * Gets the service names of the specified tag.
232 * @return array of [service name => tag attributes]
233 */
234 public function findByTag(string $tag) : array
235 {
236 return $this->tags[$tag] ?? [];
237 }
238 private function preventDeadLock(string $key, \Closure $callback)
239 {
240 if (isset($this->creating[$key])) {
241 throw new \Packetery\Nette\InvalidStateException(\sprintf('Circular reference detected for: %s.', \implode(', ', \array_keys($this->creating))));
242 }
243 try {
244 $this->creating[$key] = \true;
245 return $callback();
246 } finally {
247 unset($this->creating[$key]);
248 }
249 }
250 /********************* autowiring ****************d*g**/
251 /**
252 * Creates new instance using autowiring.
253 * @throws \Packetery\Nette\InvalidArgumentException
254 */
255 public function createInstance(string $class, array $args = []) : object
256 {
257 $rc = new \ReflectionClass($class);
258 if (!$rc->isInstantiable()) {
259 throw new ServiceCreationException(\sprintf('Class %s is not instantiable.', $class));
260 } elseif ($constructor = $rc->getConstructor()) {
261 return $rc->newInstanceArgs($this->autowireArguments($constructor, $args));
262 } elseif ($args) {
263 throw new ServiceCreationException(\sprintf('Unable to pass arguments, class %s has no constructor.', $class));
264 }
265 return new $class();
266 }
267 /**
268 * Calls all methods starting with "inject" using autowiring.
269 */
270 public function callInjects(object $service) : void
271 {
272 Extensions\InjectExtension::callInjects($this, $service);
273 }
274 /**
275 * Calls method using autowiring.
276 * @return mixed
277 */
278 public function callMethod(callable $function, array $args = [])
279 {
280 return $function(...$this->autowireArguments(\Packetery\Nette\Utils\Callback::toReflection($function), $args));
281 }
282 private function autowireArguments(\ReflectionFunctionAbstract $function, array $args = []) : array
283 {
284 return Resolver::autowireArguments($function, $args, function (string $type, bool $single) {
285 return $single ? $this->getByType($type) : \array_map([$this, 'getService'], $this->findAutowired($type));
286 });
287 }
288 public static function getMethodName(string $name) : string
289 {
290 if ($name === '') {
291 throw new \Packetery\Nette\InvalidArgumentException('Service name must be a non-empty string.');
292 }
293 return 'createService' . \str_replace('.', '__', \ucfirst($name));
294 }
295 public function initialize() : void
296 {
297 }
298 }
299