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

nette / di / 6267597366

21 Sep 2023 10:07PM UTC coverage: 94.291% (+0.003%) from 94.288%
6267597366

push

github

dg
ParametersExtension, Container: redesigned way of handling dynamic parameters via getParameter()

31 of 31 new or added lines in 3 files covered. (100.0%)

2213 of 2347 relevant lines covered (94.29%)

0.94 hits per line

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

93.02
/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
        public function getParameter($key)
64
        {
65
                if (!array_key_exists($key, $this->parameters)) {
1✔
66
                        $this->parameters[$key] = $this->getDynamicParameter($key);
1✔
67
                }
68
                return $this->parameters[$key];
1✔
69
        }
70

71

72
        protected function getDynamicParameter($key)
73
        {
74
                throw new Nette\InvalidStateException(sprintf('Dynamic parameter %s not found.', $key));
×
75
        }
76

77

78
        /**
79
         * Adds the service to the container.
80
         * @param  object  $service  service or its factory
81
         * @return static
82
         */
83
        public function addService(string $name, object $service)
1✔
84
        {
85
                $name = $this->aliases[$name] ?? $name;
1✔
86
                if (isset($this->instances[$name])) {
1✔
87
                        throw new Nette\InvalidStateException(sprintf("Service '%s' already exists.", $name));
1✔
88
                }
89

90
                if ($service instanceof \Closure) {
1✔
91
                        $rt = Nette\Utils\Type::fromReflection(new \ReflectionFunction($service));
1✔
92
                        $type = $rt ? Helpers::ensureClassType($rt, 'return type of closure') : '';
1✔
93
                } else {
94
                        $type = get_class($service);
1✔
95
                }
96

97
                if (!isset($this->methods[self::getMethodName($name)])) {
1✔
98
                        $this->types[$name] = $type;
1✔
99

100
                } elseif (($expectedType = $this->getServiceType($name)) && !is_a($type, $expectedType, true)) {
1✔
101
                        throw new Nette\InvalidArgumentException(sprintf(
1✔
102
                                "Service '%s' must be instance of %s, %s.",
1✔
103
                                $name,
104
                                $expectedType,
105
                                $type ? "$type given" : 'add typehint to closure'
1✔
106
                        ));
107
                }
108

109
                if ($service instanceof \Closure) {
1✔
110
                        $this->methods[self::getMethodName($name)] = $service;
1✔
111
                        $this->types[$name] = $type;
1✔
112
                } else {
113
                        $this->instances[$name] = $service;
1✔
114
                }
115

116
                return $this;
1✔
117
        }
118

119

120
        /**
121
         * Removes the service from the container.
122
         */
123
        public function removeService(string $name): void
124
        {
125
                $name = $this->aliases[$name] ?? $name;
×
126
                unset($this->instances[$name]);
×
127
        }
128

129

130
        /**
131
         * Gets the service object by name.
132
         * @throws MissingServiceException
133
         */
134
        public function getService(string $name): object
1✔
135
        {
136
                if (!isset($this->instances[$name])) {
1✔
137
                        if (isset($this->aliases[$name])) {
1✔
138
                                return $this->getService($this->aliases[$name]);
1✔
139
                        }
140

141
                        $this->instances[$name] = $this->createService($name);
1✔
142
                }
143

144
                return $this->instances[$name];
1✔
145
        }
146

147

148
        /**
149
         * Gets the service object by name.
150
         * @throws MissingServiceException
151
         */
152
        public function getByName(string $name): object
1✔
153
        {
154
                return $this->getService($name);
1✔
155
        }
156

157

158
        /**
159
         * Gets the service type by name.
160
         * @throws MissingServiceException
161
         */
162
        public function getServiceType(string $name): string
1✔
163
        {
164
                $method = self::getMethodName($name);
1✔
165
                if (isset($this->aliases[$name])) {
1✔
166
                        return $this->getServiceType($this->aliases[$name]);
1✔
167

168
                } elseif (isset($this->types[$name])) {
1✔
169
                        return $this->types[$name];
1✔
170

171
                } elseif (isset($this->methods[$method])) {
1✔
172
                        $type = (new \ReflectionMethod($this, $method))->getReturnType();
1✔
173
                        return $type ? $type->getName() : '';
1✔
174

175
                } else {
176
                        throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
1✔
177
                }
178
        }
179

180

181
        /**
182
         * Does the service exist?
183
         */
184
        public function hasService(string $name): bool
1✔
185
        {
186
                $name = $this->aliases[$name] ?? $name;
1✔
187
                return isset($this->methods[self::getMethodName($name)]) || isset($this->instances[$name]);
1✔
188
        }
189

190

191
        /**
192
         * Is the service created?
193
         */
194
        public function isCreated(string $name): bool
1✔
195
        {
196
                if (!$this->hasService($name)) {
1✔
197
                        throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
×
198
                }
199

200
                $name = $this->aliases[$name] ?? $name;
1✔
201
                return isset($this->instances[$name]);
1✔
202
        }
203

204

205
        /**
206
         * Creates new instance of the service.
207
         * @throws MissingServiceException
208
         */
209
        public function createService(string $name, array $args = []): object
1✔
210
        {
211
                $name = $this->aliases[$name] ?? $name;
1✔
212
                $method = self::getMethodName($name);
1✔
213
                $cb = $this->methods[$method] ?? null;
1✔
214
                if (isset($this->creating[$name])) {
1✔
215
                        throw new Nette\InvalidStateException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($this->creating))));
1✔
216

217
                } elseif ($cb === null) {
1✔
218
                        throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
1✔
219
                }
220

221
                try {
222
                        $this->creating[$name] = true;
1✔
223
                        $service = $cb instanceof \Closure
1✔
224
                                ? $cb(...$args)
1✔
225
                                : $this->$method(...$args);
1✔
226

227
                } finally {
1✔
228
                        unset($this->creating[$name]);
1✔
229
                }
230

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

238
                return $service;
1✔
239
        }
240

241

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

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

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

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

275
                        throw new MissingServiceException(sprintf(
1✔
276
                                'Service of type %s not found. Did you add it to configuration file?',
1✔
277
                                $type
278
                        ));
279
                }
280

281
                return null;
1✔
282
        }
283

284

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

296

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

309

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

319

320
        /********************* autowiring ****************d*g**/
321

322

323
        /**
324
         * Creates new instance using autowiring.
325
         * @throws Nette\InvalidArgumentException
326
         */
327
        public function createInstance(string $class, array $args = []): object
1✔
328
        {
329
                $rc = new \ReflectionClass($class);
1✔
330
                if (!$rc->isInstantiable()) {
1✔
331
                        throw new ServiceCreationException(sprintf('Class %s is not instantiable.', $class));
×
332

333
                } elseif ($constructor = $rc->getConstructor()) {
1✔
334
                        return $rc->newInstanceArgs($this->autowireArguments($constructor, $args));
1✔
335

336
                } elseif ($args) {
×
337
                        throw new ServiceCreationException(sprintf('Unable to pass arguments, class %s has no constructor.', $class));
×
338
                }
339

340
                return new $class;
×
341
        }
342

343

344
        /**
345
         * Calls all methods starting with with "inject" using autowiring.
346
         */
347
        public function callInjects(object $service): void
1✔
348
        {
349
                Extensions\InjectExtension::callInjects($this, $service);
1✔
350
        }
1✔
351

352

353
        /**
354
         * Calls method using autowiring.
355
         * @return mixed
356
         */
357
        public function callMethod(callable $function, array $args = [])
1✔
358
        {
359
                return $function(...$this->autowireArguments(Nette\Utils\Callback::toReflection($function), $args));
1✔
360
        }
361

362

363
        private function autowireArguments(\ReflectionFunctionAbstract $function, array $args = []): array
1✔
364
        {
365
                return Resolver::autowireArguments($function, $args, function (string $type, bool $single) {
1✔
366
                        return $single
1✔
367
                                ? $this->getByType($type)
1✔
368
                                : array_map([$this, 'getService'], $this->findAutowired($type));
1✔
369
                });
1✔
370
        }
371

372

373
        public static function getMethodName(string $name): string
1✔
374
        {
375
                if ($name === '') {
1✔
376
                        throw new Nette\InvalidArgumentException('Service name must be a non-empty string.');
1✔
377
                }
378

379
                return 'createService' . str_replace('.', '__', ucfirst($name));
1✔
380
        }
381

382

383
        public function initialize(): void
384
        {
385
        }
386
}
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