• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / di / 8868806744

28 Apr 2024 04:08PM UTC coverage: 93.23%. Remained the same
8868806744

push

github

dg
Container::getByType() fixed cooperation with dynamic factory [Closes #314]

3 of 3 new or added lines in 1 file covered. (100.0%)

4 existing lines in 1 file now uncovered.

2162 of 2319 relevant lines covered (93.23%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.44
/src/DI/Container.php
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

8
declare(strict_types=1);
9

10
namespace Nette\DI;
11

12
use Nette;
13

14

15
/**
16
 * The dependency injection container default implementation.
17
 */
18
class Container
19
{
20
        use Nette\SmartObject;
21

22
        /** @var mixed[]  user parameters */
23
        public $parameters = [];
24

25
        /** @var string[]  service name => type */
26
        protected $types = [];
27

28
        /** @var string[]  alias => service name */
29
        protected $aliases = [];
30

31
        /** @var array[]  tag name => service name => tag value */
32
        protected $tags = [];
33

34
        /** @var array[]  type => (high, low, no) => services */
35
        protected $wiring = [];
36

37
        /** @var object[]  service name => instance */
38
        private $instances = [];
39

40
        /** @var array<string, true>  circular reference detector */
41
        private $creating;
42

43
        /** @var array<string, int|\Closure> */
44
        private $methods;
45

46

47
        public function __construct(array $params = [])
1✔
48
        {
49
                $this->parameters = $params;
1✔
50
                $this->methods = array_flip(array_filter(
1✔
51
                        get_class_methods($this),
1✔
52
                        function ($s) { return preg_match('#^createService.#', $s); }
1✔
53
                ));
54
        }
1✔
55

56

57
        public function getParameters(): array
58
        {
59
                return $this->parameters;
×
60
        }
61

62

63
        /**
64
         * Adds the service or its factory to the container.
65
         * @param  object  $service  service or its factory
66
         * @return static
67
         */
68
        public function addService(string $name, $service)
1✔
69
        {
70
                $name = $this->aliases[$name] ?? $name;
1✔
71
                if (isset($this->instances[$name])) {
1✔
72
                        throw new Nette\InvalidStateException(sprintf("Service '%s' already exists.", $name));
1✔
73

74
                } elseif (!is_object($service)) {
1✔
75
                        throw new Nette\InvalidArgumentException(sprintf("Service '%s' must be a object, %s given.", $name, gettype($service)));
1✔
76
                }
77

78
                if ($service instanceof \Closure) {
1✔
79
                        $rt = Nette\Utils\Type::fromReflection(new \ReflectionFunction($service));
1✔
80
                        $type = $rt ? Helpers::ensureClassType($rt, 'return type of closure') : '';
1✔
81
                } else {
82
                        $type = get_class($service);
1✔
83
                }
84

85
                if (!isset($this->methods[self::getMethodName($name)])) {
1✔
86
                        $this->types[$name] = $type;
1✔
87

88
                } elseif (($expectedType = $this->getServiceType($name)) && !is_a($type, $expectedType, true)) {
1✔
89
                        throw new Nette\InvalidArgumentException(sprintf(
1✔
90
                                "Service '%s' must be instance of %s, %s.",
1✔
91
                                $name,
92
                                $expectedType,
93
                                $type ? "$type given" : 'add typehint to closure'
1✔
94
                        ));
95
                }
96

97
                if ($service instanceof \Closure) {
1✔
98
                        $this->methods[self::getMethodName($name)] = $service;
1✔
99
                        $this->types[$name] = $type;
1✔
100
                } else {
101
                        $this->instances[$name] = $service;
1✔
102
                }
103

104
                return $this;
1✔
105
        }
106

107

108
        /**
109
         * Removes a service instance from the container.
110
         */
111
        public function removeService(string $name): void
112
        {
113
                $name = $this->aliases[$name] ?? $name;
×
114
                unset($this->instances[$name]);
×
115
        }
116

117

118
        /**
119
         * Returns the service instance. If it has not been created yet, it creates it.
120
         * @return object
121
         * @throws MissingServiceException
122
         */
123
        public function getService(string $name)
1✔
124
        {
125
                if (!isset($this->instances[$name])) {
1✔
126
                        if (isset($this->aliases[$name])) {
1✔
127
                                return $this->getService($this->aliases[$name]);
1✔
128
                        }
129

130
                        $this->instances[$name] = $this->createService($name);
1✔
131
                }
132

133
                return $this->instances[$name];
1✔
134
        }
135

136

137
        /**
138
         * Returns the service instance. If it has not been created yet, it creates it.
139
         * Alias for getService().
140
         * @return object
141
         * @throws MissingServiceException
142
         */
143
        public function getByName(string $name)
1✔
144
        {
145
                return $this->getService($name);
1✔
146
        }
147

148

149
        /**
150
         * Returns type of the service.
151
         * @throws MissingServiceException
152
         */
153
        public function getServiceType(string $name): string
1✔
154
        {
155
                $method = self::getMethodName($name);
1✔
156
                if (isset($this->aliases[$name])) {
1✔
157
                        return $this->getServiceType($this->aliases[$name]);
1✔
158

159
                } elseif (isset($this->types[$name])) {
1✔
160
                        return $this->types[$name];
1✔
161

162
                } elseif (isset($this->methods[$method])) {
1✔
163
                        $type = (new \ReflectionMethod($this, $method))->getReturnType();
1✔
164
                        return $type ? $type->getName() : '';
1✔
165

166
                } else {
167
                        throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
1✔
168
                }
169
        }
170

171

172
        /**
173
         * Does the service exist?
174
         */
175
        public function hasService(string $name): bool
1✔
176
        {
177
                $name = $this->aliases[$name] ?? $name;
1✔
178
                return isset($this->methods[self::getMethodName($name)]) || isset($this->instances[$name]);
1✔
179
        }
180

181

182
        /**
183
         * Has a service instance been created?
184
         */
185
        public function isCreated(string $name): bool
1✔
186
        {
187
                if (!$this->hasService($name)) {
1✔
188
                        throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
×
189
                }
190

191
                $name = $this->aliases[$name] ?? $name;
1✔
192
                return isset($this->instances[$name]);
1✔
193
        }
194

195

196
        /**
197
         * Creates new instance of the service.
198
         * @return object
199
         * @throws MissingServiceException
200
         */
201
        public function createService(string $name, array $args = [])
1✔
202
        {
203
                $name = $this->aliases[$name] ?? $name;
1✔
204
                $method = self::getMethodName($name);
1✔
205
                $cb = $this->methods[$method] ?? null;
1✔
206
                if (isset($this->creating[$name])) {
1✔
207
                        throw new Nette\InvalidStateException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($this->creating))));
1✔
208

209
                } elseif ($cb === null) {
1✔
210
                        throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
1✔
211
                }
212

213
                try {
214
                        $this->creating[$name] = true;
1✔
215
                        $service = $cb instanceof \Closure
1✔
216
                                ? $cb(...$args)
1✔
217
                                : $this->$method(...$args);
1✔
218

219
                } finally {
1✔
220
                        unset($this->creating[$name]);
1✔
221
                }
222

223
                if (!is_object($service)) {
1✔
224
                        throw new Nette\UnexpectedValueException(sprintf(
1✔
225
                                "Unable to create service '$name', value returned by %s is not object.",
1✔
226
                                $cb instanceof \Closure ? 'closure' : "method $method()"
1✔
227
                        ));
228
                }
229

230
                return $service;
1✔
231
        }
232

233

234
        /**
235
         * Returns an instance of the autowired service of the given type. If it has not been created yet, it creates it.
236
         * @return object|null  service
237
         * @throws MissingServiceException
238
         */
239
        public function getByType(string $type, bool $throw = true)
1✔
240
        {
241
                $type = Helpers::normalizeClass($type);
1✔
242
                if (!empty($this->wiring[$type][0])) {
1✔
243
                        if (count($names = $this->wiring[$type][0]) === 1) {
1✔
244
                                return $this->getService($names[0]);
1✔
245
                        }
246

247
                        natsort($names);
1✔
248
                        throw new MissingServiceException(sprintf("Multiple services of type $type found: %s.", implode(', ', $names)));
1✔
249

250
                } elseif ($throw) {
1✔
251
                        if (!class_exists($type) && !interface_exists($type)) {
1✔
252
                                throw new MissingServiceException(sprintf("Service of type '%s' not found. Check the class name because it cannot be found.", $type));
1✔
253
                        } elseif ($this->findByType($type)) {
1✔
254
                                throw new MissingServiceException(sprintf("Service of type %s is not autowired or is missing in di\u{a0}›\u{a0}export\u{a0}›\u{a0}types.", $type));
1✔
255
                        } else {
256
                                throw new MissingServiceException(sprintf('Service of type %s not found. Did you add it to configuration file?', $type));
1✔
257
                        }
258
                }
259

260
                return null;
1✔
261
        }
262

263

264
        /**
265
         * Returns the names of autowired services of the given type.
266
         * @return string[]
267
         * @internal
268
         */
269
        public function findAutowired(string $type): array
1✔
270
        {
271
                $type = Helpers::normalizeClass($type);
1✔
272
                return array_merge($this->wiring[$type][0] ?? [], $this->wiring[$type][1] ?? []);
1✔
273
        }
274

275

276
        /**
277
         * Returns the names of all services of the given type.
278
         * @return string[]
279
         */
280
        public function findByType(string $type): array
1✔
281
        {
282
                $type = Helpers::normalizeClass($type);
1✔
283
                return empty($this->wiring[$type])
1✔
284
                        ? []
1✔
285
                        : array_merge(...array_values($this->wiring[$type]));
1✔
286
        }
287

288

289
        /**
290
         * Returns the names of services with the given tag.
291
         * @return array of [service name => tag attributes]
292
         */
293
        public function findByTag(string $tag): array
1✔
294
        {
295
                return $this->tags[$tag] ?? [];
1✔
296
        }
297

298

299
        /********************* autowiring ****************d*g**/
300

301

302
        /**
303
         * Creates an instance of the class and passes dependencies to the constructor using autowiring.
304
         * @return object
305
         * @throws Nette\InvalidArgumentException
306
         */
307
        public function createInstance(string $class, array $args = [])
1✔
308
        {
309
                $rc = new \ReflectionClass($class);
1✔
310
                if (!$rc->isInstantiable()) {
1✔
UNCOV
311
                        throw new ServiceCreationException(sprintf('Class %s is not instantiable.', $class));
×
312

313
                } elseif ($constructor = $rc->getConstructor()) {
1✔
314
                        return $rc->newInstanceArgs($this->autowireArguments($constructor, $args));
1✔
315

UNCOV
316
                } elseif ($args) {
×
UNCOV
317
                        throw new ServiceCreationException(sprintf('Unable to pass arguments, class %s has no constructor.', $class));
×
318
                }
319

UNCOV
320
                return new $class;
×
321
        }
322

323

324
        /**
325
         * Calls all methods starting with 'inject' and passes dependencies to them via autowiring.
326
         * @param  object  $service
327
         */
328
        public function callInjects($service): void
329
        {
330
                Extensions\InjectExtension::callInjects($this, $service);
1✔
331
        }
1✔
332

333

334
        /**
335
         * Calls the method and passes dependencies to it via autowiring.
336
         * @return mixed
337
         */
338
        public function callMethod(callable $function, array $args = [])
1✔
339
        {
340
                return $function(...$this->autowireArguments(Nette\Utils\Callback::toReflection($function), $args));
1✔
341
        }
342

343

344
        private function autowireArguments(\ReflectionFunctionAbstract $function, array $args = []): array
1✔
345
        {
346
                return Resolver::autowireArguments($function, $args, function (string $type, bool $single) {
1✔
347
                        return $single
1✔
348
                                ? $this->getByType($type)
1✔
349
                                : array_map([$this, 'getService'], $this->findAutowired($type));
1✔
350
                });
1✔
351
        }
352

353

354
        /**
355
         * Returns the method name for creating a service.
356
         */
357
        public static function getMethodName(string $name): string
1✔
358
        {
359
                if ($name === '') {
1✔
360
                        throw new Nette\InvalidArgumentException('Service name must be a non-empty string.');
1✔
361
                }
362

363
                return 'createService' . str_replace('.', '__', ucfirst($name));
1✔
364
        }
365

366

367
        public function initialize(): void
368
        {
369
        }
370
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc