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

nette / di / 6350956765

29 Sep 2023 11:27AM UTC coverage: 93.789% (-0.4%) from 94.168%
6350956765

push

github

dg
Option 'class' is allowed again

Partially reverts commit 046f89cc3.

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

2250 of 2399 relevant lines covered (93.79%)

0.94 hits per line

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

93.38
/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[]  services name => type (complete list of available services) */
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 => level => 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, string|\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 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 the service 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
         * Gets the service object by name.
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
         * Gets the service object by name.
161
         * @throws MissingServiceException
162
         */
163
        public function getByName(string $name): object
1✔
164
        {
165
                return $this->getService($name);
1✔
166
        }
167

168

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

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

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

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

191

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

201

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

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

215

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

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

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

242
                return $service;
1✔
243
        }
244

245

246
        /**
247
         * Resolves service by type.
248
         * @template T of object
249
         * @param  class-string<T>  $type
250
         * @return ?T
251
         * @throws MissingServiceException
252
         */
253
        public function getByType(string $type, bool $throw = true): ?object
1✔
254
        {
255
                $type = Helpers::normalizeClass($type);
1✔
256
                if (!empty($this->wiring[$type][0])) {
1✔
257
                        if (count($names = $this->wiring[$type][0]) === 1) {
1✔
258
                                return $this->getService($names[0]);
1✔
259
                        }
260

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

264
                } elseif ($throw) {
1✔
265
                        if (!class_exists($type) && !interface_exists($type)) {
1✔
266
                                throw new MissingServiceException(sprintf("Service of type '%s' not found. Check the class name because it cannot be found.", $type));
1✔
267
                        }
268

269
                        foreach ($this->methods as $method => $foo) {
1✔
270
                                $methodType = (new \ReflectionMethod(static::class, $method))->getReturnType()->getName();
1✔
271
                                if (is_a($methodType, $type, true)) {
1✔
272
                                        throw new MissingServiceException(sprintf(
1✔
273
                                                "Service of type %s is not autowired or is missing in di\u{a0}›\u{a0}export\u{a0}›\u{a0}types.",
1✔
274
                                                $type
275
                                        ));
276
                                }
277
                        }
278

279
                        throw new MissingServiceException(sprintf(
1✔
280
                                'Service of type %s not found. Did you add it to configuration file?',
1✔
281
                                $type
282
                        ));
283
                }
284

285
                return null;
1✔
286
        }
287

288

289
        /**
290
         * Gets the autowired service names of the specified type.
291
         * @return string[]
292
         * @internal
293
         */
294
        public function findAutowired(string $type): array
1✔
295
        {
296
                $type = Helpers::normalizeClass($type);
1✔
297
                return array_merge($this->wiring[$type][0] ?? [], $this->wiring[$type][1] ?? []);
1✔
298
        }
299

300

301
        /**
302
         * Gets the service names of the specified type.
303
         * @return string[]
304
         */
305
        public function findByType(string $type): array
1✔
306
        {
307
                $type = Helpers::normalizeClass($type);
1✔
308
                return empty($this->wiring[$type])
1✔
309
                        ? []
1✔
310
                        : array_merge(...array_values($this->wiring[$type]));
1✔
311
        }
312

313

314
        /**
315
         * Gets the service names of the specified tag.
316
         * @return array of [service name => tag attributes]
317
         */
318
        public function findByTag(string $tag): array
1✔
319
        {
320
                return $this->tags[$tag] ?? [];
1✔
321
        }
322

323

324
        private function preventDeadLock(string $key, \Closure $callback)
1✔
325
        {
326
                if (isset($this->creating[$key])) {
1✔
327
                        throw new Nette\InvalidStateException(sprintf('Circular reference detected for: %s.', implode(', ', array_keys($this->creating))));
1✔
328
                }
329
                try {
330
                        $this->creating[$key] = true;
1✔
331
                        return $callback();
1✔
332
                } finally {
×
333
                        unset($this->creating[$key]);
1✔
334
                }
335
        }
336

337

338
        /********************* autowiring ****************d*g**/
339

340

341
        /**
342
         * Creates new instance using autowiring.
343
         * @throws Nette\InvalidArgumentException
344
         */
345
        public function createInstance(string $class, array $args = []): object
1✔
346
        {
347
                $rc = new \ReflectionClass($class);
1✔
348
                if (!$rc->isInstantiable()) {
1✔
349
                        throw new ServiceCreationException(sprintf('Class %s is not instantiable.', $class));
×
350

351
                } elseif ($constructor = $rc->getConstructor()) {
1✔
352
                        return $rc->newInstanceArgs($this->autowireArguments($constructor, $args));
1✔
353

354
                } elseif ($args) {
×
355
                        throw new ServiceCreationException(sprintf('Unable to pass arguments, class %s has no constructor.', $class));
×
356
                }
357

358
                return new $class;
×
359
        }
360

361

362
        /**
363
         * Calls all methods starting with with "inject" using autowiring.
364
         */
365
        public function callInjects(object $service): void
1✔
366
        {
367
                Extensions\InjectExtension::callInjects($this, $service);
1✔
368
        }
1✔
369

370

371
        /**
372
         * Calls method using autowiring.
373
         * @return mixed
374
         */
375
        public function callMethod(callable $function, array $args = [])
1✔
376
        {
377
                return $function(...$this->autowireArguments(Nette\Utils\Callback::toReflection($function), $args));
1✔
378
        }
379

380

381
        private function autowireArguments(\ReflectionFunctionAbstract $function, array $args = []): array
1✔
382
        {
383
                return Resolver::autowireArguments($function, $args, function (string $type, bool $single) {
1✔
384
                        return $single
1✔
385
                                ? $this->getByType($type)
1✔
386
                                : array_map([$this, 'getService'], $this->findAutowired($type));
1✔
387
                });
1✔
388
        }
389

390

391
        public static function getMethodName(string $name): string
1✔
392
        {
393
                if ($name === '') {
1✔
394
                        throw new Nette\InvalidArgumentException('Service name must be a non-empty string.');
1✔
395
                }
396

397
                return 'createService' . str_replace('.', '__', ucfirst($name));
1✔
398
        }
399

400

401
        public function initialize(): void
402
        {
403
        }
404
}
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