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

nette / di / 4292646203

pending completion
4292646203

push

github

David Grudl
Sensitive parameters are put in markers /*sensitive{*/ and /*}*/

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

2112 of 2237 relevant lines covered (94.41%)

0.94 hits per line

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

94.66
/src/DI/Resolver.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
use Nette\DI\Definitions\Definition;
14
use Nette\DI\Definitions\Reference;
15
use Nette\DI\Definitions\Statement;
16
use Nette\PhpGenerator\Helpers as PhpHelpers;
17
use Nette\Utils\Arrays;
18
use Nette\Utils\Callback;
19
use Nette\Utils\Reflection;
20
use Nette\Utils\Strings;
21
use Nette\Utils\Validators;
22
use ReflectionClass;
23

24

25
/**
26
 * Services resolver
27
 * @internal
28
 */
29
class Resolver
30
{
31
        use Nette\SmartObject;
32

33
        private ContainerBuilder $builder;
34
        private ?Definition $currentService = null;
35
        private ?string $currentServiceType = null;
36
        private bool $currentServiceAllowed = false;
37

38
        /** circular reference detector */
39
        private \SplObjectStorage $recursive;
40

41

42
        public function __construct(ContainerBuilder $builder)
1✔
43
        {
44
                $this->builder = $builder;
1✔
45
                $this->recursive = new \SplObjectStorage;
1✔
46
        }
1✔
47

48

49
        public function getContainerBuilder(): ContainerBuilder
50
        {
51
                return $this->builder;
1✔
52
        }
53

54

55
        public function resolveDefinition(Definition $def): void
1✔
56
        {
57
                if ($this->recursive->contains($def)) {
1✔
58
                        $names = array_map(fn($item) => $item->getName(), iterator_to_array($this->recursive));
1✔
59
                        throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', $names)));
1✔
60
                }
61

62
                try {
63
                        $this->recursive->attach($def);
1✔
64

65
                        $def->resolveType($this);
1✔
66

67
                        if (!$def->getType()) {
1✔
68
                                throw new ServiceCreationException('Type of service is unknown.');
1✔
69
                        }
70
                } catch (\Throwable $e) {
1✔
71
                        throw $this->completeException($e, $def);
1✔
72

73
                } finally {
1✔
74
                        $this->recursive->detach($def);
1✔
75
                }
76
        }
1✔
77

78

79
        public function resolveReferenceType(Reference $ref): ?string
1✔
80
        {
81
                if ($ref->isSelf()) {
1✔
82
                        return $this->currentServiceType;
1✔
83
                } elseif ($ref->isType()) {
1✔
84
                        return ltrim($ref->getValue(), '\\');
1✔
85
                }
86

87
                $def = $this->resolveReference($ref);
1✔
88
                if (!$def->getType()) {
1✔
89
                        $this->resolveDefinition($def);
1✔
90
                }
91

92
                return $def->getType();
1✔
93
        }
94

95

96
        public function resolveEntityType(Statement $statement): ?string
1✔
97
        {
98
                $entity = $this->normalizeEntity($statement);
1✔
99

100
                if (is_array($entity)) {
1✔
101
                        if ($entity[0] instanceof Reference || $entity[0] instanceof Statement) {
1✔
102
                                $entity[0] = $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0]));
1✔
103
                                if (!$entity[0]) {
1✔
104
                                        return null;
1✔
105
                                }
106
                        }
107

108
                        try {
109
                                $reflection = Callback::toReflection($entity[0] === '' ? $entity[1] : $entity);
1✔
110
                                assert($reflection instanceof \ReflectionMethod || $reflection instanceof \ReflectionFunction);
111
                                $refClass = $reflection instanceof \ReflectionMethod
1✔
112
                                        ? $reflection->getDeclaringClass()
1✔
113
                                        : null;
1✔
114
                        } catch (\ReflectionException $e) {
1✔
115
                                $refClass = $reflection = null;
1✔
116
                        }
117

118
                        if (isset($e) || ($refClass && (!$reflection->isPublic()
1✔
119
                                || ($refClass->isTrait() && !$reflection->isStatic())
1✔
120
                        ))) {
121
                                throw new ServiceCreationException(sprintf('Method %s() is not callable.', Callback::toString($entity)), 0, $e ?? null);
1✔
122
                        }
123

124
                        $this->addDependency($reflection);
1✔
125

126
                        $type = Nette\Utils\Type::fromReflection($reflection) ?? ($annotation = Helpers::getReturnTypeAnnotation($reflection));
1✔
127
                        if ($type && !in_array($type->getSingleName(), ['object', 'mixed'], true)) {
1✔
128
                                if (isset($annotation)) {
1✔
129
                                        trigger_error('Annotation @return should be replaced with native return type at ' . Callback::toString($entity), E_USER_DEPRECATED);
1✔
130
                                }
131

132
                                return Helpers::ensureClassType($type, sprintf('return type of %s()', Callback::toString($entity)));
1✔
133
                        }
134

135
                        return null;
1✔
136

137
                } elseif ($entity instanceof Reference) { // alias or factory
1✔
138
                        return $this->resolveReferenceType($entity);
1✔
139

140
                } elseif (is_string($entity)) { // class
1✔
141
                        if (!class_exists($entity)) {
1✔
142
                                throw new ServiceCreationException(sprintf(
1✔
143
                                        interface_exists($entity)
1✔
144
                                                ? "Interface %s can not be used as 'create' or 'factory', did you mean 'implement'?"
×
145
                                                : "Class '%s' not found.",
1✔
146
                                        $entity,
147
                                ));
148
                        }
149

150
                        return $entity;
1✔
151
                }
152

153
                return null;
1✔
154
        }
155

156

157
        public function completeDefinition(Definition $def): void
1✔
158
        {
159
                $this->currentService = in_array($def, $this->builder->getDefinitions(), true)
1✔
160
                        ? $def
1✔
161
                        : null;
1✔
162
                $this->currentServiceType = $def->getType();
1✔
163
                $this->currentServiceAllowed = false;
1✔
164

165
                try {
166
                        $def->complete($this);
1✔
167

168
                        $this->addDependency(new \ReflectionClass($def->getType()));
1✔
169

170
                } catch (\Throwable $e) {
1✔
171
                        throw $this->completeException($e, $def);
1✔
172

173
                } finally {
1✔
174
                        $this->currentService = $this->currentServiceType = null;
1✔
175
                }
176
        }
1✔
177

178

179
        public function completeStatement(Statement $statement, bool $currentServiceAllowed = false): Statement
1✔
180
        {
181
                $this->currentServiceAllowed = $currentServiceAllowed;
1✔
182
                $entity = $this->normalizeEntity($statement);
1✔
183
                $arguments = $this->convertReferences($statement->arguments);
1✔
184
                $getter = fn(string $type, bool $single) => $single
1✔
185
                                ? $this->getByType($type)
1✔
186
                                : array_values(array_filter($this->builder->findAutowired($type), fn($obj) => $obj !== $this->currentService));
1✔
187

188
                switch (true) {
189
                        case is_string($entity) && Strings::contains($entity, '?'): // PHP literal
1✔
190
                                break;
1✔
191

192
                        case $entity === 'not':
1✔
193
                                if (count($arguments) !== 1) {
1✔
194
                                        throw new ServiceCreationException(sprintf('Function %s() expects 1 parameter, %s given.', $entity, count($arguments)));
×
195
                                }
196

197
                                $entity = ['', '!'];
1✔
198
                                break;
1✔
199

200
                        case $entity === 'bool':
1✔
201
                        case $entity === 'int':
1✔
202
                        case $entity === 'float':
1✔
203
                        case $entity === 'string':
1✔
204
                                if (count($arguments) !== 1) {
1✔
205
                                        throw new ServiceCreationException(sprintf('Function %s() expects 1 parameter, %s given.', $entity, count($arguments)));
1✔
206
                                }
207

208
                                $arguments = [$arguments[0], $entity];
1✔
209
                                $entity = [Helpers::class, 'convertType'];
1✔
210
                                break;
1✔
211

212
                        case is_string($entity): // create class
1✔
213
                                if (!class_exists($entity)) {
1✔
214
                                        throw new ServiceCreationException(sprintf("Class '%s' not found.", $entity));
1✔
215
                                } elseif ((new ReflectionClass($entity))->isAbstract()) {
1✔
216
                                        throw new ServiceCreationException(sprintf('Class %s is abstract.', $entity));
1✔
217
                                } elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== null && !$rm->isPublic()) {
1✔
218
                                        throw new ServiceCreationException(sprintf('Class %s has %s constructor.', $entity, $rm->isProtected() ? 'protected' : 'private'));
1✔
219
                                } elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) {
1✔
220
                                        $arguments = self::autowireArguments($constructor, $arguments, $getter);
1✔
221
                                        $this->addDependency($constructor);
1✔
222
                                } elseif ($arguments) {
1✔
223
                                        throw new ServiceCreationException(sprintf(
×
224
                                                'Unable to pass arguments, class %s has no constructor.',
×
225
                                                $entity,
226
                                        ));
227
                                }
228

229
                                break;
1✔
230

231
                        case $entity instanceof Reference:
1✔
232
                                $entity = [new Reference(ContainerBuilder::ThisContainer), Container::getMethodName($entity->getValue())];
1✔
233
                                break;
1✔
234

235
                        case is_array($entity):
1✔
236
                                if (!preg_match('#^\$?(\\\\?' . PhpHelpers::PHP_IDENT . ')+(\[\])?$#D', $entity[1])) {
1✔
237
                                        throw new ServiceCreationException(sprintf(
1✔
238
                                                "Expected function, method or property name, '%s' given.",
1✔
239
                                                $entity[1],
1✔
240
                                        ));
241
                                }
242

243
                                switch (true) {
244
                                        case $entity[0] === '': // function call
1✔
245
                                                if (!Arrays::isList($arguments)) {
1✔
246
                                                        throw new ServiceCreationException(sprintf(
×
247
                                                                'Unable to pass specified arguments to %s.',
×
248
                                                                $entity[0],
×
249
                                                        ));
250
                                                } elseif (!function_exists($entity[1])) {
1✔
251
                                                        throw new ServiceCreationException(sprintf("Function %s doesn't exist.", $entity[1]));
×
252
                                                }
253

254
                                                $rf = new \ReflectionFunction($entity[1]);
1✔
255
                                                $arguments = self::autowireArguments($rf, $arguments, $getter);
1✔
256
                                                $this->addDependency($rf);
1✔
257
                                                break;
1✔
258

259
                                        case $entity[0] instanceof Statement:
1✔
260
                                                $entity[0] = $this->completeStatement($entity[0], $this->currentServiceAllowed);
1✔
261
                                                // break omitted
262

263
                                        case is_string($entity[0]): // static method call
1✔
264
                                        case $entity[0] instanceof Reference:
1✔
265
                                                if ($entity[1][0] === '$') { // property getter, setter or appender
1✔
266
                                                        Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Callback::toString($entity) . "'");
1✔
267
                                                        if (!$arguments && str_ends_with($entity[1], '[]')) {
1✔
268
                                                                throw new ServiceCreationException(sprintf('Missing argument for %s.', $entity[1]));
1✔
269
                                                        }
270
                                                } elseif (
271
                                                        $type = $entity[0] instanceof Reference
1✔
272
                                                                ? $this->resolveReferenceType($entity[0])
1✔
273
                                                                : $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0]))
1✔
274
                                                ) {
275
                                                        $rc = new ReflectionClass($type);
1✔
276
                                                        if ($rc->hasMethod($entity[1])) {
1✔
277
                                                                $rm = $rc->getMethod($entity[1]);
1✔
278
                                                                if (!$rm->isPublic()) {
1✔
279
                                                                        throw new ServiceCreationException(sprintf('%s::%s() is not callable.', $type, $entity[1]));
×
280
                                                                }
281

282
                                                                $arguments = self::autowireArguments($rm, $arguments, $getter);
1✔
283
                                                                $this->addDependency($rm);
1✔
284

285
                                                        } elseif (!Arrays::isList($arguments)) {
1✔
286
                                                                throw new ServiceCreationException(sprintf('Unable to pass specified arguments to %s::%s().', $type, $entity[1]));
×
287
                                                        }
288
                                                }
289
                                }
290
                }
291

292
                try {
293
                        $arguments = $this->completeArguments($arguments);
1✔
294
                } catch (ServiceCreationException $e) {
1✔
295
                        if (!str_contains($e->getMessage(), ' (used in')) {
1✔
296
                                $e->setMessage($e->getMessage() . " (used in {$this->entityToString($entity)})");
1✔
297
                        }
298

299
                        throw $e;
1✔
300
                }
301

302
                return new Statement($entity, $arguments);
1✔
303
        }
304

305

306
        public function completeArguments(array $arguments): array
1✔
307
        {
308
                array_walk_recursive($arguments, function (&$val): void {
1✔
309
                        if ($val instanceof Statement) {
1✔
310
                                $entity = $val->getEntity();
1✔
311
                                if ($entity === 'typed' || $entity === 'tagged') {
1✔
312
                                        $services = [];
1✔
313
                                        $current = $this->currentService
1✔
314
                                                ? $this->currentService->getName()
1✔
315
                                                : null;
×
316
                                        foreach ($val->arguments as $argument) {
1✔
317
                                                foreach ($entity === 'tagged' ? $this->builder->findByTag($argument) : $this->builder->findAutowired($argument) as $name => $foo) {
1✔
318
                                                        if ($name !== $current) {
1✔
319
                                                                $services[] = new Reference($name);
1✔
320
                                                        }
321
                                                }
322
                                        }
323

324
                                        $val = $this->completeArguments($services);
1✔
325
                                } else {
326
                                        $val = $this->completeStatement($val, $this->currentServiceAllowed);
1✔
327
                                }
328
                        } elseif ($val instanceof Definition || $val instanceof Reference) {
1✔
329
                                $val = $this->normalizeEntity(new Statement($val));
1✔
330
                        }
331
                });
1✔
332
                return $arguments;
1✔
333
        }
334

335

336
        /** Returns literal, Class, Reference, [Class, member], [, globalFunc], [Reference, member], [Statement, member] */
337
        private function normalizeEntity(Statement $statement): string|array|Reference|null
1✔
338
        {
339
                $entity = $statement->getEntity();
1✔
340
                if (is_array($entity)) {
1✔
341
                        $item = &$entity[0];
1✔
342
                } else {
343
                        $item = &$entity;
1✔
344
                }
345

346
                if ($item instanceof Definition) {
1✔
347
                        $name = current(array_keys($this->builder->getDefinitions(), $item, true));
1✔
348
                        if ($name === false) {
1✔
349
                                throw new ServiceCreationException(sprintf("Service '%s' not found in definitions.", $item->getName()));
×
350
                        }
351

352
                        $item = new Reference($name);
1✔
353
                }
354

355
                if ($item instanceof Reference) {
1✔
356
                        $item = $this->normalizeReference($item);
1✔
357
                }
358

359
                return $entity;
1✔
360
        }
361

362

363
        /**
364
         * Normalizes reference to 'self' or named reference (or leaves it typed if it is not possible during resolving) and checks existence of service.
365
         */
366
        public function normalizeReference(Reference $ref): Reference
1✔
367
        {
368
                $service = $ref->getValue();
1✔
369
                if ($ref->isSelf()) {
1✔
370
                        return $ref;
1✔
371
                } elseif ($ref->isName()) {
1✔
372
                        if (!$this->builder->hasDefinition($service)) {
1✔
373
                                throw new ServiceCreationException(sprintf("Reference to missing service '%s'.", $service));
1✔
374
                        }
375

376
                        return $this->currentService && $service === $this->currentService->getName()
1✔
377
                                ? new Reference(Reference::Self)
1✔
378
                                : $ref;
1✔
379
                }
380

381
                try {
382
                        return $this->getByType($service);
1✔
383
                } catch (NotAllowedDuringResolvingException $e) {
1✔
384
                        return new Reference($service);
1✔
385
                }
386
        }
387

388

389
        public function resolveReference(Reference $ref): Definition
1✔
390
        {
391
                return $ref->isSelf()
1✔
392
                        ? $this->currentService
×
393
                        : $this->builder->getDefinition($ref->getValue());
1✔
394
        }
395

396

397
        /**
398
         * Returns named reference to service resolved by type (or 'self' reference for local-autowiring).
399
         * @throws ServiceCreationException when multiple found
400
         * @throws MissingServiceException when not found
401
         */
402
        public function getByType(string $type): Reference
1✔
403
        {
404
                if (
405
                        $this->currentService
1✔
406
                        && $this->currentServiceAllowed
1✔
407
                        && is_a($this->currentServiceType, $type, true)
1✔
408
                ) {
409
                        return new Reference(Reference::Self);
1✔
410
                }
411

412
                $name = $this->builder->getByType($type, true);
1✔
413
                if (
414
                        !$this->currentServiceAllowed
1✔
415
                        && $this->currentService === $this->builder->getDefinition($name)
1✔
416
                ) {
417
                        throw new MissingServiceException;
1✔
418
                }
419

420
                return new Reference($name);
1✔
421
        }
422

423

424
        /**
425
         * Adds item to the list of dependencies.
426
         */
427
        public function addDependency(\ReflectionClass|\ReflectionFunctionAbstract|string $dep): static
1✔
428
        {
429
                $this->builder->addDependency($dep);
1✔
430
                return $this;
1✔
431
        }
432

433

434
        private function completeException(\Throwable $e, Definition $def): ServiceCreationException
1✔
435
        {
436
                if ($e instanceof ServiceCreationException && str_starts_with($e->getMessage(), "Service '")) {
1✔
437
                        return $e;
1✔
438
                }
439

440
                $name = $def->getName();
1✔
441
                $type = $def->getType();
1✔
442
                if ($name && !ctype_digit($name)) {
1✔
443
                        $message = "Service '$name'" . ($type ? " (type of $type)" : '') . ': ';
1✔
444
                } elseif ($type) {
1✔
445
                        $message = "Service of type $type: ";
1✔
446
                } elseif ($def instanceof Definitions\ServiceDefinition && $def->getEntity()) {
1✔
447
                        $message = 'Service (' . $this->entityToString($def->getEntity()) . '): ';
1✔
448
                } else {
449
                        $message = '';
1✔
450
                }
451

452
                $message .= $type
1✔
453
                        ? str_replace("$type::", preg_replace('~.*\\\\~', '', $type) . '::', $e->getMessage())
1✔
454
                        : $e->getMessage();
1✔
455

456
                return $e instanceof ServiceCreationException
1✔
457
                        ? $e->setMessage($message)
1✔
458
                        : new ServiceCreationException($message, 0, $e);
1✔
459
        }
460

461

462
        private function entityToString($entity): string
463
        {
464
                $referenceToText = fn(Reference $ref): string => $ref->isSelf() && $this->currentService
1✔
465
                                ? '@' . $this->currentService->getName()
1✔
466
                                : '@' . $ref->getValue();
1✔
467
                if (is_string($entity)) {
1✔
468
                        return $entity . '::__construct()';
1✔
469
                } elseif ($entity instanceof Reference) {
1✔
470
                        $entity = $referenceToText($entity);
×
471
                } elseif (is_array($entity)) {
1✔
472
                        if (!str_contains($entity[1], '$')) {
1✔
473
                                $entity[1] .= '()';
1✔
474
                        }
475

476
                        if ($entity[0] instanceof Reference) {
1✔
477
                                $entity[0] = $referenceToText($entity[0]);
1✔
478
                        } elseif (!is_string($entity[0])) {
1✔
479
                                return $entity[1];
1✔
480
                        }
481

482
                        return implode('::', $entity);
1✔
483
                }
484

485
                return (string) $entity;
×
486
        }
487

488

489
        private function convertReferences(array $arguments): array
1✔
490
        {
491
                array_walk_recursive($arguments, function (&$val): void {
1✔
492
                        if (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
1✔
493
                                $pair = explode('::', substr($val, 1), 2);
1✔
494
                                if (!isset($pair[1])) { // @service
1✔
495
                                        $val = new Reference($pair[0]);
1✔
496
                                } elseif (preg_match('#^[A-Z][a-zA-Z0-9_]*$#D', $pair[1], $m)) { // @service::CONSTANT
1✔
497
                                        $val = ContainerBuilder::literal($this->resolveReferenceType(new Reference($pair[0])) . '::' . $pair[1]);
1✔
498
                                } else { // @service::property
499
                                        $val = new Statement([new Reference($pair[0]), '$' . $pair[1]]);
1✔
500
                                }
501
                        } elseif (is_string($val) && str_starts_with($val, '@@')) { // escaped text @@
1✔
502
                                $val = substr($val, 1);
1✔
503
                        }
504
                });
1✔
505
                return $arguments;
1✔
506
        }
507

508

509
        /**
510
         * Add missing arguments using autowiring.
511
         * @param  (callable(string $type, bool $single): (object|object[]|null))  $getter
512
         * @throws ServiceCreationException
513
         */
514
        public static function autowireArguments(
1✔
515
                \ReflectionFunctionAbstract $method,
516
                array $arguments,
517
                callable $getter,
518
        ): array
519
        {
520
                $useName = false;
1✔
521
                $num = -1;
1✔
522
                $res = [];
1✔
523

524
                foreach ($method->getParameters() as $num => $param) {
1✔
525
                        $paramName = $param->name;
1✔
526

527
                        if ($param->isVariadic()) {
1✔
528
                                if ($useName && Arrays::some($arguments, fn($val, $key) => is_int($key))) {
1✔
529
                                        throw new ServiceCreationException(sprintf(
1✔
530
                                                'Cannot use positional argument after named or omitted argument in %s.',
1✔
531
                                                Reflection::toString($param),
1✔
532
                                        ));
533
                                }
534

535
                                $res = array_merge($res, $arguments);
1✔
536
                                $arguments = [];
1✔
537
                        } elseif (array_key_exists($key = $paramName, $arguments) || array_key_exists($key = $num, $arguments)) {
1✔
538
                                $val = $arguments[$key];
1✔
539
                                $res[$useName ? $paramName : $num] = is_scalar($val) && self::isSensitive($param)
1✔
540
                                        ? ContainerBuilder::literal('/*sensitive{*/?/*}*/', [$val])
1✔
541
                                        : $val;
1✔
542
                                unset($arguments[$key], $arguments[$num]); // unset $num to enable overwriting in configuration
1✔
543

544
                        } elseif (($aw = self::autowireArgument($param, $getter)) !== null) {
1✔
545
                                $res[$useName ? $paramName : $num] = $aw;
1✔
546

547
                        } elseif ($param->isOptional()) {
1✔
548
                                $useName = true;
1✔
549

550
                        } else {
551
                                $res[$num] = null;
1✔
552

553
                                trigger_error(sprintf(
1✔
554
                                        'The parameter %s should have a declared value in the configuration.',
1✔
555
                                        Reflection::toString($param),
1✔
556
                                ), E_USER_DEPRECATED);
1✔
557
                        }
558
                }
559

560
                // extra parameters
561
                while (!$useName && array_key_exists(++$num, $arguments)) {
1✔
562
                        $res[$num] = $arguments[$num];
1✔
563
                        unset($arguments[$num]);
1✔
564
                }
565

566
                if ($arguments) {
1✔
567
                        throw new ServiceCreationException(sprintf(
×
568
                                'Unable to pass specified arguments to %s.',
×
569
                                Reflection::toString($method),
×
570
                        ));
571
                }
572

573
                return $res;
1✔
574
        }
575

576

577
        /**
578
         * Resolves missing argument using autowiring.
579
         * @param  (callable(string $type, bool $single): (object|object[]|null))  $getter
580
         * @throws ServiceCreationException
581
         */
582
        private static function autowireArgument(\ReflectionParameter $parameter, callable $getter): mixed
1✔
583
        {
584
                $desc = Reflection::toString($parameter);
1✔
585
                $type = Nette\Utils\Type::fromReflection($parameter);
1✔
586

587
                if ($type?->isClass()) {
1✔
588
                        $class = $type->getSingleName();
1✔
589
                        try {
590
                                $res = $getter($class, true);
1✔
591
                        } catch (MissingServiceException $e) {
1✔
592
                                $res = null;
1✔
593
                        } catch (ServiceCreationException $e) {
1✔
594
                                throw new ServiceCreationException("{$e->getMessage()} (required by $desc)", 0, $e);
1✔
595
                        }
596

597
                        if ($res !== null || $parameter->allowsNull()) {
1✔
598
                                return $res;
1✔
599
                        } elseif (class_exists($class) || interface_exists($class)) {
1✔
600
                                throw new ServiceCreationException(sprintf(
1✔
601
                                        'Service of type %s required by %s not found. Did you add it to configuration file?',
1✔
602
                                        $class,
603
                                        $desc,
604
                                ));
605
                        } else {
606
                                throw new ServiceCreationException(sprintf(
1✔
607
                                        "Class '%s' required by %s not found. Check the parameter type and 'use' statements.",
1✔
608
                                        $class,
609
                                        $desc,
610
                                ));
611
                        }
612

613
                } elseif ($itemType = self::isArrayOf($parameter, $type)) {
1✔
614
                        return $getter($itemType, false);
1✔
615

616
                } elseif ($parameter->isOptional()) {
1✔
617
                        return null;
1✔
618

619
                } else {
620
                        throw new ServiceCreationException(sprintf(
1✔
621
                                'Parameter %s has %s, so its value must be specified.',
1✔
622
                                $desc,
623
                                $type && !$type->isSimple() ? 'complex type and no default value' : 'no class type or default value',
1✔
624
                        ));
625
                }
626
        }
627

628

629
        private static function isArrayOf(\ReflectionParameter $parameter, ?Nette\Utils\Type $type): ?string
1✔
630
        {
631
                $method = $parameter->getDeclaringFunction();
1✔
632
                return $method instanceof \ReflectionMethod
1✔
633
                        && $type?->getSingleName() === 'array'
1✔
634
                        && preg_match(
1✔
635
                                '#@param[ \t]+(?|([\w\\\\]+)\[\]|array<int,\s*([\w\\\\]+)>)[ \t]+\$' . $parameter->name . '#',
1✔
636
                                (string) $method->getDocComment(),
1✔
637
                                $m,
638
                        )
639
                        && ($itemType = Reflection::expandClassName($m[1], $method->getDeclaringClass()))
1✔
640
                        && (class_exists($itemType) || interface_exists($itemType))
1✔
641
                                ? $itemType
1✔
642
                                : null;
1✔
643
        }
644

645

646
        private static function isSensitive(\ReflectionParameter $param): bool
1✔
647
        {
648
                return (bool) $param->getAttributes(\SensitiveParameter::class);
1✔
649
        }
650
}
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