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

nette / di / 8868805348

28 Apr 2024 04:08PM UTC coverage: 93.802%. Remained the same
8868805348

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%)

5 existing lines in 1 file now uncovered.

2270 of 2420 relevant lines covered (93.8%)

0.94 hits per line

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

93.18
/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
        /**
23
         * @var mixed[]
24
         * @deprecated use Container::getParameter() or getParameters()
25
         */
26
        public $parameters = [];
27

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

31
        /** @var string[]  alias => service name */
32
        protected $aliases = [];
33

34
        /** @var array[]  tag name => service name => tag value */
35
        protected $tags = [];
36

37
        /** @var array[]  type => (high, low, no) => services */
38
        protected $wiring = [];
39

40
        /** @var object[]  service name => instance */
41
        private $instances = [];
42

43
        /** @var array<string, true>  circular reference detector */
44
        private $creating;
45

46
        /** @var array<string, int|\Closure> */
47
        private $methods;
48

49

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

59

60
        public function getParameters(): array
61
        {
62
                return $this->parameters;
1✔
63
        }
64

65

66
        public function getParameter($key)
67
        {
68
                if (!array_key_exists($key, $this->parameters)) {
1✔
69
                        $this->parameters[$key] = $this->preventDeadLock("%$key%", function () use ($key) {
1✔
70
                                return $this->getDynamicParameter($key);
1✔
71
                        });
1✔
72
                }
73
                return $this->parameters[$key];
1✔
74
        }
75

76

77
        protected function getStaticParameters(): array
78
        {
79
                return [];
1✔
80
        }
81

82

83
        protected function getDynamicParameter($key)
84
        {
85
                throw new Nette\InvalidStateException(sprintf("Parameter '%s' not found. Check if 'di › export › parameters' is enabled.", $key));
×
86
        }
87

88

89
        /**
90
         * Adds the service or its factory to the container.
91
         * @param  object  $service  service or its factory
92
         * @return static
93
         */
94
        public function addService(string $name, object $service)
1✔
95
        {
96
                $name = $this->aliases[$name] ?? $name;
1✔
97
                if (isset($this->instances[$name])) {
1✔
98
                        throw new Nette\InvalidStateException(sprintf("Service '%s' already exists.", $name));
1✔
99
                }
100

101
                if ($service instanceof \Closure) {
1✔
102
                        $rt = Nette\Utils\Type::fromReflection(new \ReflectionFunction($service));
1✔
103
                        $type = $rt ? Helpers::ensureClassType($rt, 'return type of closure') : '';
1✔
104
                } else {
105
                        $type = get_class($service);
1✔
106
                }
107

108
                if (!isset($this->methods[self::getMethodName($name)])) {
1✔
109
                        $this->types[$name] = $type;
1✔
110

111
                } elseif (($expectedType = $this->getServiceType($name)) && !is_a($type, $expectedType, true)) {
1✔
112
                        throw new Nette\InvalidArgumentException(sprintf(
1✔
113
                                "Service '%s' must be instance of %s, %s.",
1✔
114
                                $name,
115
                                $expectedType,
116
                                $type ? "$type given" : 'add typehint to closure'
1✔
117
                        ));
118
                }
119

120
                if ($service instanceof \Closure) {
1✔
121
                        $this->methods[self::getMethodName($name)] = $service;
1✔
122
                        $this->types[$name] = $type;
1✔
123
                } else {
124
                        $this->instances[$name] = $service;
1✔
125
                }
126

127
                return $this;
1✔
128
        }
129

130

131
        /**
132
         * Removes a service instance from the container.
133
         */
134
        public function removeService(string $name): void
135
        {
136
                $name = $this->aliases[$name] ?? $name;
×
137
                unset($this->instances[$name]);
×
138
        }
139

140

141
        /**
142
         * Returns the service instance. If it has not been created yet, it creates it.
143
         * @throws MissingServiceException
144
         */
145
        public function getService(string $name): object
1✔
146
        {
147
                if (!isset($this->instances[$name])) {
1✔
148
                        if (isset($this->aliases[$name])) {
1✔
149
                                return $this->getService($this->aliases[$name]);
1✔
150
                        }
151

152
                        $this->instances[$name] = $this->createService($name);
1✔
153
                }
154

155
                return $this->instances[$name];
1✔
156
        }
157

158

159
        /**
160
         * Returns the service instance. If it has not been created yet, it creates it.
161
         * Alias for getService().
162
         * @throws MissingServiceException
163
         */
164
        public function getByName(string $name): object
1✔
165
        {
166
                return $this->getService($name);
1✔
167
        }
168

169

170
        /**
171
         * Returns type of the service.
172
         * @throws MissingServiceException
173
         */
174
        public function getServiceType(string $name): string
1✔
175
        {
176
                $method = self::getMethodName($name);
1✔
177
                if (isset($this->aliases[$name])) {
1✔
178
                        return $this->getServiceType($this->aliases[$name]);
1✔
179

180
                } elseif (isset($this->types[$name])) {
1✔
181
                        return $this->types[$name];
1✔
182

183
                } elseif (isset($this->methods[$method])) {
1✔
184
                        $type = (new \ReflectionMethod($this, $method))->getReturnType();
1✔
185
                        return $type ? $type->getName() : '';
1✔
186

187
                } else {
188
                        throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
1✔
189
                }
190
        }
191

192

193
        /**
194
         * Does the service exist?
195
         */
196
        public function hasService(string $name): bool
1✔
197
        {
198
                $name = $this->aliases[$name] ?? $name;
1✔
199
                return isset($this->methods[self::getMethodName($name)]) || isset($this->instances[$name]);
1✔
200
        }
201

202

203
        /**
204
         * Has a service instance been created?
205
         */
206
        public function isCreated(string $name): bool
1✔
207
        {
208
                if (!$this->hasService($name)) {
1✔
209
                        throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
×
210
                }
211

212
                $name = $this->aliases[$name] ?? $name;
1✔
213
                return isset($this->instances[$name]);
1✔
214
        }
215

216

217
        /**
218
         * Creates new instance of the service.
219
         * @throws MissingServiceException
220
         */
221
        public function createService(string $name): object
1✔
222
        {
223
                $name = $this->aliases[$name] ?? $name;
1✔
224
                $method = self::getMethodName($name);
1✔
225
                $callback = $this->methods[$method] ?? null;
1✔
226
                if ($callback === null) {
1✔
227
                        throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
1✔
228
                }
229

230
                $service = $this->preventDeadLock($name, function () use ($callback, $method) {
1✔
231
                        return $callback instanceof \Closure
1✔
232
                                ? $callback()
1✔
233
                                : $this->$method();
1✔
234
                });
1✔
235

236
                if (!is_object($service)) {
1✔
237
                        throw new Nette\UnexpectedValueException(sprintf(
1✔
238
                                "Unable to create service '$name', value returned by %s is not object.",
1✔
239
                                $callback instanceof \Closure ? 'closure' : "method $method()"
1✔
240
                        ));
241
                }
242

243
                return $service;
1✔
244
        }
245

246

247
        /**
248
         * Returns an instance of the autowired service of the given type. If it has not been created yet, it creates it.
249
         * @template T of object
250
         * @param  class-string<T>  $type
251
         * @return ($throw is true ? T : ?T)
252
         * @throws MissingServiceException
253
         */
254
        public function getByType(string $type, bool $throw = true): ?object
1✔
255
        {
256
                $type = Helpers::normalizeClass($type);
1✔
257
                if (!empty($this->wiring[$type][0])) {
1✔
258
                        if (count($names = $this->wiring[$type][0]) === 1) {
1✔
259
                                return $this->getService($names[0]);
1✔
260
                        }
261

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

265
                } elseif ($throw) {
1✔
266
                        if (!class_exists($type) && !interface_exists($type)) {
1✔
267
                                throw new MissingServiceException(sprintf("Service of type '%s' not found. Check the class name because it cannot be found.", $type));
1✔
268
                        } elseif ($this->findByType($type)) {
1✔
269
                                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✔
270
                        } else {
271
                                throw new MissingServiceException(sprintf('Service of type %s not found. Did you add it to configuration file?', $type));
1✔
272
                        }
273
                }
274

275
                return null;
1✔
276
        }
277

278

279
        /**
280
         * Returns the names of autowired services of the given type.
281
         * @return string[]
282
         * @internal
283
         */
284
        public function findAutowired(string $type): array
1✔
285
        {
286
                $type = Helpers::normalizeClass($type);
1✔
287
                return array_merge($this->wiring[$type][0] ?? [], $this->wiring[$type][1] ?? []);
1✔
288
        }
289

290

291
        /**
292
         * Returns the names of all services of the given type.
293
         * @return string[]
294
         */
295
        public function findByType(string $type): array
1✔
296
        {
297
                $type = Helpers::normalizeClass($type);
1✔
298
                return empty($this->wiring[$type])
1✔
299
                        ? []
1✔
300
                        : array_merge(...array_values($this->wiring[$type]));
1✔
301
        }
302

303

304
        /**
305
         * Returns the names of services with the given tag.
306
         * @return array of [service name => tag attributes]
307
         */
308
        public function findByTag(string $tag): array
1✔
309
        {
310
                return $this->tags[$tag] ?? [];
1✔
311
        }
312

313

314
        /**
315
         * Prevents circular references during service creation by checking if the service is already being created.
316
         */
317
        private function preventDeadLock(string $key, \Closure $callback)
1✔
318
        {
319
                if (isset($this->creating[$key])) {
1✔
320
                        throw new Nette\InvalidStateException(sprintf('Circular reference detected for: %s.', implode(', ', array_keys($this->creating))));
1✔
321
                }
322
                try {
323
                        $this->creating[$key] = true;
1✔
324
                        return $callback();
1✔
UNCOV
325
                } finally {
×
326
                        unset($this->creating[$key]);
1✔
327
                }
328
        }
329

330

331
        /********************* autowiring ****************d*g**/
332

333

334
        /**
335
         * Creates an instance of the class and passes dependencies to the constructor using autowiring.
336
         * @throws Nette\InvalidArgumentException
337
         */
338
        public function createInstance(string $class, array $args = []): object
1✔
339
        {
340
                $rc = new \ReflectionClass($class);
1✔
341
                if (!$rc->isInstantiable()) {
1✔
UNCOV
342
                        throw new ServiceCreationException(sprintf('Class %s is not instantiable.', $class));
×
343

344
                } elseif ($constructor = $rc->getConstructor()) {
1✔
345
                        return $rc->newInstanceArgs($this->autowireArguments($constructor, $args));
1✔
346

UNCOV
347
                } elseif ($args) {
×
UNCOV
348
                        throw new ServiceCreationException(sprintf('Unable to pass arguments, class %s has no constructor.', $class));
×
349
                }
350

UNCOV
351
                return new $class;
×
352
        }
353

354

355
        /**
356
         * Calls all methods starting with 'inject' and passes dependencies to them via autowiring.
357
         */
358
        public function callInjects(object $service): void
1✔
359
        {
360
                Extensions\InjectExtension::callInjects($this, $service);
1✔
361
        }
1✔
362

363

364
        /**
365
         * Calls the method and passes dependencies to it via autowiring.
366
         * @return mixed
367
         */
368
        public function callMethod(callable $function, array $args = [])
1✔
369
        {
370
                return $function(...$this->autowireArguments(Nette\Utils\Callback::toReflection($function), $args));
1✔
371
        }
372

373

374
        private function autowireArguments(\ReflectionFunctionAbstract $function, array $args = []): array
1✔
375
        {
376
                return Resolver::autowireArguments($function, $args, function (string $type, bool $single) {
1✔
377
                        return $single
1✔
378
                                ? $this->getByType($type)
1✔
379
                                : array_map([$this, 'getService'], $this->findAutowired($type));
1✔
380
                });
1✔
381
        }
382

383

384
        /**
385
         * Returns the method name for creating a service.
386
         */
387
        public static function getMethodName(string $name): string
1✔
388
        {
389
                if ($name === '') {
1✔
390
                        throw new Nette\InvalidArgumentException('Service name must be a non-empty string.');
1✔
391
                }
392

393
                return 'createService' . str_replace('.', '__', ucfirst($name));
1✔
394
        }
395

396

397
        public function initialize(): void
398
        {
399
        }
400
}
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