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

nette / di / 6027138074

30 Aug 2023 03:26PM UTC coverage: 94.171% (-0.02%) from 94.186%
6027138074

push

github

dg
ParametersExtension: parameters containing expressions have static value '* unavailable value *'

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

2181 of 2316 relevant lines covered (94.17%)

0.94 hits per line

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

93.6
/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 array  user parameters */
23
        public $parameters = [];
24

25
        /** @var string[]  services name => type (complete list of available services) */
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 => level => services */
35
        protected $wiring = [];
36

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

40
        /** @var array circular reference detector */
41
        private $creating;
42

43
        /** @var array */
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 to the container.
65
         * @param  object  $service  service or its factory
66
         * @return static
67
         */
68
        public function addService(string $name, object $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

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

82
                if (!isset($this->methods[self::getMethodName($name)])) {
1✔
83
                        $this->types[$name] = $type;
1✔
84

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

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

101
                return $this;
1✔
102
        }
103

104

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

114

115
        /**
116
         * Gets the service object by name.
117
         * @throws MissingServiceException
118
         */
119
        public function getService(string $name): object
1✔
120
        {
121
                if (!isset($this->instances[$name])) {
1✔
122
                        if (isset($this->aliases[$name])) {
1✔
123
                                return $this->getService($this->aliases[$name]);
1✔
124
                        }
125

126
                        $this->instances[$name] = $this->createService($name);
1✔
127
                }
128

129
                return $this->instances[$name];
1✔
130
        }
131

132

133
        /**
134
         * Gets the service object by name.
135
         * @throws MissingServiceException
136
         */
137
        public function getByName(string $name): object
1✔
138
        {
139
                return $this->getService($name);
1✔
140
        }
141

142

143
        /**
144
         * Gets the service type by name.
145
         * @throws MissingServiceException
146
         */
147
        public function getServiceType(string $name): string
1✔
148
        {
149
                $method = self::getMethodName($name);
1✔
150
                if (isset($this->aliases[$name])) {
1✔
151
                        return $this->getServiceType($this->aliases[$name]);
1✔
152

153
                } elseif (isset($this->types[$name])) {
1✔
154
                        return $this->types[$name];
1✔
155

156
                } elseif (isset($this->methods[$method])) {
1✔
157
                        $type = (new \ReflectionMethod($this, $method))->getReturnType();
1✔
158
                        return $type ? $type->getName() : '';
1✔
159

160
                } else {
161
                        throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
1✔
162
                }
163
        }
164

165

166
        /**
167
         * Does the service exist?
168
         */
169
        public function hasService(string $name): bool
1✔
170
        {
171
                $name = $this->aliases[$name] ?? $name;
1✔
172
                return isset($this->methods[self::getMethodName($name)]) || isset($this->instances[$name]);
1✔
173
        }
174

175

176
        /**
177
         * Is the service created?
178
         */
179
        public function isCreated(string $name): bool
1✔
180
        {
181
                if (!$this->hasService($name)) {
1✔
182
                        throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
×
183
                }
184

185
                $name = $this->aliases[$name] ?? $name;
1✔
186
                return isset($this->instances[$name]);
1✔
187
        }
188

189

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

202
                } elseif ($cb === null) {
1✔
203
                        throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
1✔
204
                }
205

206
                try {
207
                        $this->creating[$name] = true;
1✔
208
                        $service = $cb instanceof \Closure
1✔
209
                                ? $cb(...$args)
1✔
210
                                : $this->$method(...$args);
1✔
211

212
                } finally {
1✔
213
                        unset($this->creating[$name]);
1✔
214
                }
215

216
                if (!is_object($service)) {
1✔
217
                        throw new Nette\UnexpectedValueException(sprintf(
1✔
218
                                "Unable to create service '$name', value returned by %s is not object.",
1✔
219
                                $cb instanceof \Closure ? 'closure' : "method $method()"
1✔
220
                        ));
221
                }
222

223
                return $service;
1✔
224
        }
225

226

227
        /**
228
         * Resolves service by type.
229
         * @template T of object
230
         * @param  class-string<T>  $type
231
         * @return ?T
232
         * @throws MissingServiceException
233
         */
234
        public function getByType(string $type, bool $throw = true): ?object
1✔
235
        {
236
                $type = Helpers::normalizeClass($type);
1✔
237
                if (!empty($this->wiring[$type][0])) {
1✔
238
                        if (count($names = $this->wiring[$type][0]) === 1) {
1✔
239
                                return $this->getService($names[0]);
1✔
240
                        }
241

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

245
                } elseif ($throw) {
1✔
246
                        if (!class_exists($type) && !interface_exists($type)) {
1✔
247
                                throw new MissingServiceException(sprintf("Service of type '%s' not found. Check the class name because it cannot be found.", $type));
1✔
248
                        }
249

250
                        foreach ($this->methods as $method => $foo) {
1✔
251
                                $methodType = (new \ReflectionMethod(static::class, $method))->getReturnType()->getName();
1✔
252
                                if (is_a($methodType, $type, true)) {
1✔
253
                                        throw new MissingServiceException(sprintf(
1✔
254
                                                "Service of type %s is not autowired or is missing in di\u{a0}›\u{a0}export\u{a0}›\u{a0}types.",
1✔
255
                                                $type
256
                                        ));
257
                                }
258
                        }
259

260
                        throw new MissingServiceException(sprintf(
1✔
261
                                'Service of type %s not found. Did you add it to configuration file?',
1✔
262
                                $type
263
                        ));
264
                }
265

266
                return null;
1✔
267
        }
268

269

270
        /**
271
         * Gets the autowired service names of the specified type.
272
         * @return string[]
273
         * @internal
274
         */
275
        public function findAutowired(string $type): array
1✔
276
        {
277
                $type = Helpers::normalizeClass($type);
1✔
278
                return array_merge($this->wiring[$type][0] ?? [], $this->wiring[$type][1] ?? []);
1✔
279
        }
280

281

282
        /**
283
         * Gets the service names of the specified type.
284
         * @return string[]
285
         */
286
        public function findByType(string $type): array
1✔
287
        {
288
                $type = Helpers::normalizeClass($type);
1✔
289
                return empty($this->wiring[$type])
1✔
290
                        ? []
1✔
291
                        : array_merge(...array_values($this->wiring[$type]));
1✔
292
        }
293

294

295
        /**
296
         * Gets the service names of the specified tag.
297
         * @return array of [service name => tag attributes]
298
         */
299
        public function findByTag(string $tag): array
1✔
300
        {
301
                return $this->tags[$tag] ?? [];
1✔
302
        }
303

304

305
        /********************* autowiring ****************d*g**/
306

307

308
        /**
309
         * Creates new instance using autowiring.
310
         * @throws Nette\InvalidArgumentException
311
         */
312
        public function createInstance(string $class, array $args = []): object
1✔
313
        {
314
                $rc = new \ReflectionClass($class);
1✔
315
                if (!$rc->isInstantiable()) {
1✔
316
                        throw new ServiceCreationException(sprintf('Class %s is not instantiable.', $class));
×
317

318
                } elseif ($constructor = $rc->getConstructor()) {
1✔
319
                        return $rc->newInstanceArgs($this->autowireArguments($constructor, $args));
1✔
320

321
                } elseif ($args) {
×
322
                        throw new ServiceCreationException(sprintf('Unable to pass arguments, class %s has no constructor.', $class));
×
323
                }
324

325
                return new $class;
×
326
        }
327

328

329
        /**
330
         * Calls all methods starting with with "inject" using autowiring.
331
         */
332
        public function callInjects(object $service): void
1✔
333
        {
334
                Extensions\InjectExtension::callInjects($this, $service);
1✔
335
        }
1✔
336

337

338
        /**
339
         * Calls method using autowiring.
340
         * @return mixed
341
         */
342
        public function callMethod(callable $function, array $args = [])
1✔
343
        {
344
                return $function(...$this->autowireArguments(Nette\Utils\Callback::toReflection($function), $args));
1✔
345
        }
346

347

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

357

358
        public static function getMethodName(string $name): string
1✔
359
        {
360
                if ($name === '') {
1✔
361
                        throw new Nette\InvalidArgumentException('Service name must be a non-empty string.');
1✔
362
                }
363

364
                return 'createService' . str_replace('.', '__', ucfirst($name));
1✔
365
        }
366
}
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