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

nette / di / 4285543690

pending completion
4285543690

push

github

David Grudl
Resolver: refactoring

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

2153 of 2313 relevant lines covered (93.08%)

0.93 hits per line

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

92.55
/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
        /** @var ContainerBuilder */
34
        private $builder;
35

36
        /** @var Definition|null */
37
        private $currentService;
38

39
        /** @var string|null */
40
        private $currentServiceType;
41

42
        /** @var bool */
43
        private $currentServiceAllowed = false;
44

45
        /** @var \SplObjectStorage  circular reference detector */
46
        private $recursive;
47

48

49
        public function __construct(ContainerBuilder $builder)
1✔
50
        {
51
                $this->builder = $builder;
1✔
52
                $this->recursive = new \SplObjectStorage;
1✔
53
        }
1✔
54

55

56
        public function getContainerBuilder(): ContainerBuilder
57
        {
58
                return $this->builder;
1✔
59
        }
60

61

62
        public function resolveDefinition(Definition $def): void
1✔
63
        {
64
                if ($this->recursive->contains($def)) {
1✔
65
                        $names = array_map(function ($item) { return $item->getName(); }, iterator_to_array($this->recursive));
1✔
66
                        throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', $names)));
1✔
67
                }
68

69
                try {
70
                        $this->recursive->attach($def);
1✔
71

72
                        $def->resolveType($this);
1✔
73

74
                        if (!$def->getType()) {
1✔
75
                                throw new ServiceCreationException('Type of service is unknown.');
1✔
76
                        }
77
                } catch (\Throwable $e) {
1✔
78
                        throw $this->completeException($e, $def);
1✔
79

80
                } finally {
1✔
81
                        $this->recursive->detach($def);
1✔
82
                }
83
        }
1✔
84

85

86
        public function resolveReferenceType(Reference $ref): ?string
1✔
87
        {
88
                if ($ref->isSelf()) {
1✔
89
                        return $this->currentServiceType;
1✔
90
                } elseif ($ref->isType()) {
1✔
91
                        return ltrim($ref->getValue(), '\\');
1✔
92
                }
93

94
                $def = $this->resolveReference($ref);
1✔
95
                if (!$def->getType()) {
1✔
96
                        $this->resolveDefinition($def);
1✔
97
                }
98

99
                return $def->getType();
1✔
100
        }
101

102

103
        public function resolveEntityType(Statement $statement): ?string
1✔
104
        {
105
                $entity = $this->normalizeEntity($statement);
1✔
106

107
                if (is_array($entity)) {
1✔
108
                        if ($entity[0] instanceof Reference || $entity[0] instanceof Statement) {
1✔
109
                                $entity[0] = $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0]));
1✔
110
                                if (!$entity[0]) {
1✔
111
                                        return null;
×
112
                                }
113
                        }
114

115
                        try {
116
                                $reflection = Callback::toReflection($entity[0] === '' ? $entity[1] : $entity);
1✔
117
                                assert($reflection instanceof \ReflectionMethod || $reflection instanceof \ReflectionFunction);
118
                                $refClass = $reflection instanceof \ReflectionMethod
1✔
119
                                        ? $reflection->getDeclaringClass()
1✔
120
                                        : null;
1✔
121
                        } catch (\ReflectionException $e) {
1✔
122
                                $refClass = $reflection = null;
1✔
123
                        }
124

125
                        if (isset($e) || ($refClass && (!$reflection->isPublic()
1✔
126
                                || ($refClass->isTrait() && !$reflection->isStatic())
1✔
127
                        ))) {
128
                                throw new ServiceCreationException(sprintf('Method %s() is not callable.', Callback::toString($entity)), 0, $e ?? null);
1✔
129
                        }
130

131
                        $this->addDependency($reflection);
1✔
132

133
                        $type = Nette\Utils\Type::fromReflection($reflection) ?? Helpers::getReturnTypeAnnotation($reflection);
1✔
134
                        if ($type && !in_array((string) $type, ['object', 'mixed'], true)) {
1✔
135
                                return Helpers::ensureClassType($type, sprintf('return type of %s()', Callback::toString($entity)));
1✔
136
                        }
137

138
                        return null;
1✔
139

140
                } elseif ($entity instanceof Reference) { // alias or factory
1✔
141
                        return $this->resolveReferenceType($entity);
1✔
142

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

153
                        return $entity;
1✔
154
                }
155

156
                return null;
1✔
157
        }
158

159

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

168
                try {
169
                        $def->complete($this);
1✔
170

171
                        $this->addDependency(new \ReflectionClass($def->getType()));
1✔
172

173
                } catch (\Throwable $e) {
1✔
174
                        throw $this->completeException($e, $def);
1✔
175

176
                } finally {
1✔
177
                        $this->currentService = $this->currentServiceType = null;
1✔
178
                }
179
        }
1✔
180

181

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

193
                switch (true) {
194
                        case is_string($entity) && Strings::contains($entity, '?'): // PHP literal
1✔
195
                                break;
1✔
196

197
                        case $entity === 'not':
1✔
198
                                if (count($arguments) > 1) {
1✔
199
                                        throw new ServiceCreationException(sprintf(
×
200
                                                'Function %s() expects at most 1 parameter, %s given.',
×
201
                                                $entity,
202
                                                count($arguments)
×
203
                                        ));
204
                                }
205

206
                                $entity = ['', '!'];
1✔
207
                                break;
1✔
208

209
                        case $entity === 'bool':
1✔
210
                        case $entity === 'int':
1✔
211
                        case $entity === 'float':
1✔
212
                        case $entity === 'string':
1✔
213
                                if (count($arguments) > 1) {
1✔
214
                                        throw new ServiceCreationException(sprintf(
1✔
215
                                                'Function %s() expects at most 1 parameter, %s given.',
1✔
216
                                                $entity,
217
                                                count($arguments)
1✔
218
                                        ));
219
                                }
220

221
                                $arguments = [$arguments[0], $entity];
1✔
222
                                $entity = [Helpers::class, 'convertType'];
1✔
223
                                break;
1✔
224

225
                        case is_string($entity): // create class
1✔
226
                                if (!class_exists($entity)) {
1✔
227
                                        throw new ServiceCreationException(sprintf("Class '%s' not found.", $entity));
1✔
228
                                } elseif ((new ReflectionClass($entity))->isAbstract()) {
1✔
229
                                        throw new ServiceCreationException(sprintf('Class %s is abstract.', $entity));
1✔
230
                                } elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== null && !$rm->isPublic()) {
1✔
231
                                        throw new ServiceCreationException(sprintf('Class %s has %s constructor.', $entity, $rm->isProtected() ? 'protected' : 'private'));
1✔
232
                                } elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) {
1✔
233
                                        $arguments = self::autowireArguments($constructor, $arguments, $getter);
1✔
234
                                        $this->addDependency($constructor);
1✔
235
                                } elseif ($arguments) {
1✔
236
                                        throw new ServiceCreationException(sprintf(
×
237
                                                'Unable to pass arguments, class %s has no constructor.',
×
238
                                                $entity
239
                                        ));
240
                                }
241

242
                                break;
1✔
243

244
                        case $entity instanceof Reference:
1✔
245
                                $entity = [new Reference(ContainerBuilder::THIS_CONTAINER), Container::getMethodName($entity->getValue())];
1✔
246
                                break;
1✔
247

248
                        case is_array($entity):
1✔
249
                                if (!preg_match('#^\$?(\\\\?' . PhpHelpers::PHP_IDENT . ')+(\[\])?$#D', $entity[1])) {
1✔
250
                                        throw new ServiceCreationException(sprintf(
1✔
251
                                                "Expected function, method or property name, '%s' given.",
1✔
252
                                                $entity[1]
1✔
253
                                        ));
254
                                }
255

256
                                switch (true) {
257
                                        case $entity[0] === '': // function call
1✔
258
                                                if (!Arrays::isList($arguments)) {
1✔
259
                                                        throw new ServiceCreationException(sprintf(
×
260
                                                                'Unable to pass specified arguments to %s.',
×
261
                                                                $entity[0]
×
262
                                                        ));
263
                                                } elseif (!function_exists($entity[1])) {
1✔
264
                                                        throw new ServiceCreationException(sprintf("Function %s doesn't exist.", $entity[1]));
×
265
                                                }
266

267
                                                $rf = new \ReflectionFunction($entity[1]);
1✔
268
                                                $arguments = self::autowireArguments($rf, $arguments, $getter);
1✔
269
                                                $this->addDependency($rf);
1✔
270
                                                break;
1✔
271

272
                                        case $entity[0] instanceof Statement:
1✔
273
                                                $entity[0] = $this->completeStatement($entity[0], $this->currentServiceAllowed);
1✔
274
                                                // break omitted
275

276
                                        case is_string($entity[0]): // static method call
1✔
277
                                        case $entity[0] instanceof Reference:
1✔
278
                                                if ($entity[1][0] === '$') { // property getter, setter or appender
1✔
279
                                                        Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Callback::toString($entity) . "'");
1✔
280
                                                        if (!$arguments && substr($entity[1], -2) === '[]') {
1✔
281
                                                                throw new ServiceCreationException(sprintf('Missing argument for %s.', $entity[1]));
1✔
282
                                                        }
283
                                                } elseif (
284
                                                        $type = $entity[0] instanceof Reference
1✔
285
                                                                ? $this->resolveReferenceType($entity[0])
1✔
286
                                                                : $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0]))
1✔
287
                                                ) {
288
                                                        $rc = new ReflectionClass($type);
1✔
289
                                                        if ($rc->hasMethod($entity[1])) {
1✔
290
                                                                $rm = $rc->getMethod($entity[1]);
1✔
291
                                                                if (!$rm->isPublic()) {
1✔
292
                                                                        throw new ServiceCreationException(sprintf('%s::%s() is not callable.', $type, $entity[1]));
×
293
                                                                }
294

295
                                                                $arguments = self::autowireArguments($rm, $arguments, $getter);
1✔
296
                                                                $this->addDependency($rm);
1✔
297

298
                                                        } elseif (!Arrays::isList($arguments)) {
1✔
299
                                                                throw new ServiceCreationException(sprintf('Unable to pass specified arguments to %s::%s().', $type, $entity[1]));
×
300
                                                        }
301
                                                }
302
                                }
303
                }
304

305
                try {
306
                        $arguments = $this->completeArguments($arguments);
1✔
307
                } catch (ServiceCreationException $e) {
1✔
308
                        if (!strpos($e->getMessage(), ' (used in')) {
1✔
309
                                $e->setMessage($e->getMessage() . " (used in {$this->entityToString($entity)})");
1✔
310
                        }
311

312
                        throw $e;
1✔
313
                }
314

315
                return new Statement($entity, $arguments);
1✔
316
        }
317

318

319
        public function completeArguments(array $arguments): array
1✔
320
        {
321
                array_walk_recursive($arguments, function (&$val): void {
1✔
322
                        if ($val instanceof Statement) {
1✔
323
                                $entity = $val->getEntity();
1✔
324
                                if ($entity === 'typed' || $entity === 'tagged') {
1✔
325
                                        $services = [];
1✔
326
                                        $current = $this->currentService
1✔
327
                                                ? $this->currentService->getName()
1✔
328
                                                : null;
1✔
329
                                        foreach ($val->arguments as $argument) {
1✔
330
                                                foreach ($entity === 'tagged' ? $this->builder->findByTag($argument) : $this->builder->findAutowired($argument) as $name => $foo) {
1✔
331
                                                        if ($name !== $current) {
1✔
332
                                                                $services[] = new Reference($name);
1✔
333
                                                        }
334
                                                }
335
                                        }
336

337
                                        $val = $this->completeArguments($services);
1✔
338
                                } else {
339
                                        $val = $this->completeStatement($val, $this->currentServiceAllowed);
1✔
340
                                }
341
                        } elseif ($val instanceof Definition || $val instanceof Reference) {
1✔
342
                                $val = $this->normalizeEntity(new Statement($val));
1✔
343
                        }
344
                });
1✔
345
                return $arguments;
1✔
346
        }
347

348

349
        /** @return string|array|Reference  literal, Class, Reference, [Class, member], [, globalFunc], [Reference, member], [Statement, member] */
350
        private function normalizeEntity(Statement $statement)
1✔
351
        {
352
                $entity = $statement->getEntity();
1✔
353
                if (is_array($entity)) {
1✔
354
                        $item = &$entity[0];
1✔
355
                } else {
356
                        $item = &$entity;
1✔
357
                }
358

359
                if ($item instanceof Definition) {
1✔
360
                        $name = current(array_keys($this->builder->getDefinitions(), $item, true));
1✔
361
                        if ($name === false) {
1✔
362
                                throw new ServiceCreationException(sprintf("Service '%s' not found in definitions.", $item->getName()));
×
363
                        }
364

365
                        $item = new Reference($name);
1✔
366
                }
367

368
                if ($item instanceof Reference) {
1✔
369
                        $item = $this->normalizeReference($item);
1✔
370
                }
371

372
                return $entity;
1✔
373
        }
374

375

376
        /**
377
         * Normalizes reference to 'self' or named reference (or leaves it typed if it is not possible during resolving) and checks existence of service.
378
         */
379
        public function normalizeReference(Reference $ref): Reference
1✔
380
        {
381
                $service = $ref->getValue();
1✔
382
                if ($ref->isSelf()) {
1✔
383
                        return $ref;
1✔
384
                } elseif ($ref->isName()) {
1✔
385
                        if (!$this->builder->hasDefinition($service)) {
1✔
386
                                throw new ServiceCreationException(sprintf("Reference to missing service '%s'.", $service));
1✔
387
                        }
388

389
                        return $this->currentService && $service === $this->currentService->getName()
1✔
390
                                ? new Reference(Reference::SELF)
1✔
391
                                : $ref;
1✔
392
                }
393

394
                try {
395
                        return $this->getByType($service);
1✔
396
                } catch (NotAllowedDuringResolvingException $e) {
1✔
397
                        return new Reference($service);
1✔
398
                }
399
        }
400

401

402
        public function resolveReference(Reference $ref): Definition
1✔
403
        {
404
                return $ref->isSelf()
1✔
405
                        ? $this->currentService
×
406
                        : $this->builder->getDefinition($ref->getValue());
1✔
407
        }
408

409

410
        /**
411
         * Returns named reference to service resolved by type (or 'self' reference for local-autowiring).
412
         * @throws ServiceCreationException when multiple found
413
         * @throws MissingServiceException when not found
414
         */
415
        public function getByType(string $type): Reference
1✔
416
        {
417
                if (
418
                        $this->currentService
1✔
419
                        && $this->currentServiceAllowed
1✔
420
                        && is_a($this->currentServiceType, $type, true)
1✔
421
                ) {
422
                        return new Reference(Reference::SELF);
1✔
423
                }
424

425
                $name = $this->builder->getByType($type, true);
1✔
426
                if (
427
                        !$this->currentServiceAllowed
1✔
428
                        && $this->currentService === $this->builder->getDefinition($name)
1✔
429
                ) {
430
                        throw new MissingServiceException;
1✔
431
                }
432

433
                return new Reference($name);
1✔
434
        }
435

436

437
        /**
438
         * Adds item to the list of dependencies.
439
         * @param  \ReflectionClass|\ReflectionFunctionAbstract|string  $dep
440
         * @return static
441
         */
442
        public function addDependency($dep)
443
        {
444
                $this->builder->addDependency($dep);
1✔
445
                return $this;
1✔
446
        }
447

448

449
        private function completeException(\Throwable $e, Definition $def): ServiceCreationException
1✔
450
        {
451
                if ($e instanceof ServiceCreationException && Strings::startsWith($e->getMessage(), "Service '")) {
1✔
452
                        return $e;
1✔
453
                }
454

455
                $name = $def->getName();
1✔
456
                $type = $def->getType();
1✔
457
                if ($name && !ctype_digit($name)) {
1✔
458
                        $message = "Service '$name'" . ($type ? " (type of $type)" : '') . ': ';
1✔
459
                } elseif ($type) {
1✔
460
                        $message = "Service of type $type: ";
1✔
461
                } elseif ($def instanceof Definitions\ServiceDefinition && $def->getEntity()) {
1✔
462
                        $message = 'Service (' . $this->entityToString($def->getEntity()) . '): ';
1✔
463
                } else {
464
                        $message = '';
1✔
465
                }
466

467
                $message .= $type
1✔
468
                        ? str_replace("$type::", preg_replace('~.*\\\\~', '', $type) . '::', $e->getMessage())
1✔
469
                        : $e->getMessage();
1✔
470

471
                return $e instanceof ServiceCreationException
1✔
472
                        ? $e->setMessage($message)
1✔
473
                        : new ServiceCreationException($message, 0, $e);
1✔
474
        }
475

476

477
        private function entityToString($entity): string
478
        {
479
                $referenceToText = function (Reference $ref): string {
1✔
480
                        return $ref->isSelf() && $this->currentService
1✔
481
                                ? '@' . $this->currentService->getName()
1✔
482
                                : '@' . $ref->getValue();
1✔
483
                };
1✔
484
                if (is_string($entity)) {
1✔
485
                        return $entity . '::__construct()';
1✔
486
                } elseif ($entity instanceof Reference) {
1✔
487
                        $entity = $referenceToText($entity);
×
488
                } elseif (is_array($entity)) {
1✔
489
                        if (strpos($entity[1], '$') === false) {
1✔
490
                                $entity[1] .= '()';
1✔
491
                        }
492

493
                        if ($entity[0] instanceof Reference) {
1✔
494
                                $entity[0] = $referenceToText($entity[0]);
1✔
495
                        } elseif (!is_string($entity[0])) {
1✔
496
                                return $entity[1];
1✔
497
                        }
498

499
                        return implode('::', $entity);
1✔
500
                }
501

502
                return (string) $entity;
×
503
        }
504

505

506
        private function convertReferences(array $arguments): array
1✔
507
        {
508
                array_walk_recursive($arguments, function (&$val): void {
1✔
509
                        if (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
1✔
510
                                $pair = explode('::', substr($val, 1), 2);
1✔
511
                                if (!isset($pair[1])) { // @service
1✔
512
                                        $val = new Reference($pair[0]);
1✔
513
                                } elseif (preg_match('#^[A-Z][A-Z0-9_]*$#D', $pair[1], $m)) { // @service::CONSTANT
1✔
514
                                        $val = ContainerBuilder::literal($this->resolveReferenceType(new Reference($pair[0])) . '::' . $pair[1]);
1✔
515
                                } else { // @service::property
516
                                        $val = new Statement([new Reference($pair[0]), '$' . $pair[1]]);
1✔
517
                                }
518
                        } elseif (is_string($val) && substr($val, 0, 2) === '@@') { // escaped text @@
1✔
519
                                $val = substr($val, 1);
1✔
520
                        }
521
                });
1✔
522
                return $arguments;
1✔
523
        }
524

525

526
        /**
527
         * Add missing arguments using autowiring.
528
         * @param  (callable(string $type, bool $single): (object|object[]|null))  $getter
529
         * @throws ServiceCreationException
530
         */
531
        public static function autowireArguments(
1✔
532
                \ReflectionFunctionAbstract $method,
533
                array $arguments,
534
                callable $getter
535
        ): array
536
        {
537
                $optCount = 0;
1✔
538
                $useName = false;
1✔
539
                $num = -1;
1✔
540
                $res = [];
1✔
541

542
                foreach ($method->getParameters() as $num => $param) {
1✔
543
                        $paramName = $param->name;
1✔
544

545
                        if ($param->isVariadic()) {
1✔
546
                                if ($useName && Arrays::some($arguments, function ($val, $key) { return is_int($key); })) {
1✔
547
                                        throw new ServiceCreationException(sprintf(
×
548
                                                'Cannot use positional argument after named or omitted argument in %s.',
×
549
                                                Reflection::toString($param)
×
550
                                        ));
551
                                }
552

553
                                $res = array_merge($res, $arguments);
1✔
554
                                $arguments = [];
1✔
555
                                $optCount = 0;
1✔
556
                                break;
1✔
557

558
                        } elseif (array_key_exists($key = $paramName, $arguments) || array_key_exists($key = $num, $arguments)) {
1✔
559
                                $res[$useName ? $paramName : $num] = $arguments[$key];
1✔
560
                                unset($arguments[$key], $arguments[$num]); // unset $num to enable overwriting in configuration
1✔
561

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

565
                        } elseif (PHP_VERSION_ID >= 80000) {
1✔
566
                                if ($param->isOptional()) {
×
567
                                        $useName = true;
×
568
                                } else {
569
                                        $res[$num] = null;
×
570
                                }
571

572
                        } else {
573
                                $res[$num] = $param->isDefaultValueAvailable()
1✔
574
                                        ? Reflection::getParameterDefaultValue($param)
1✔
575
                                        : null;
1✔
576
                        }
577

578
                        if (PHP_VERSION_ID < 80000) {
1✔
579
                                $optCount = $param->isOptional() && $res[$num] === ($param->isDefaultValueAvailable() ? Reflection::getParameterDefaultValue($param) : null)
1✔
580
                                        ? $optCount + 1
1✔
581
                                        : 0;
1✔
582
                        }
583
                }
584

585
                // extra parameters
586
                while (!$useName && !$optCount && array_key_exists(++$num, $arguments)) {
1✔
587
                        $res[$num] = $arguments[$num];
1✔
588
                        unset($arguments[$num]);
1✔
589
                }
590

591
                if ($arguments) {
1✔
592
                        throw new ServiceCreationException(sprintf(
×
593
                                'Unable to pass specified arguments to %s.',
×
594
                                Reflection::toString($method)
×
595
                        ));
596
                } elseif ($optCount) {
1✔
597
                        $res = array_slice($res, 0, -$optCount);
1✔
598
                }
599

600
                return $res;
1✔
601
        }
602

603

604
        /**
605
         * Resolves missing argument using autowiring.
606
         * @param  (callable(string $type, bool $single): (object|object[]|null))  $getter
607
         * @throws ServiceCreationException
608
         * @return mixed
609
         */
610
        private static function autowireArgument(\ReflectionParameter $parameter, callable $getter)
1✔
611
        {
612
                $desc = Reflection::toString($parameter);
1✔
613
                $type = Nette\Utils\Type::fromReflection($parameter);
1✔
614

615
                if ($type && $type->isClass()) {
1✔
616
                        $class = $type->getSingleName();
1✔
617
                        try {
618
                                $res = $getter($class, true);
1✔
619
                        } catch (MissingServiceException $e) {
1✔
620
                                $res = null;
1✔
621
                        } catch (ServiceCreationException $e) {
1✔
622
                                throw new ServiceCreationException("{$e->getMessage()} (required by $desc)", 0, $e);
1✔
623
                        }
624

625
                        if ($res !== null || $parameter->allowsNull()) {
1✔
626
                                return $res;
1✔
627
                        } elseif (class_exists($class) || interface_exists($class)) {
1✔
628
                                throw new ServiceCreationException(sprintf(
1✔
629
                                        'Service of type %s required by %s not found. Did you add it to configuration file?',
1✔
630
                                        $class,
631
                                        $desc
632
                                ));
633
                        } else {
634
                                throw new ServiceCreationException(sprintf(
1✔
635
                                        "Class '%s' required by %s not found. Check the parameter type and 'use' statements.",
1✔
636
                                        $class,
637
                                        $desc
638
                                ));
639
                        }
640

641
                } elseif ($itemType = self::isArrayOf($parameter, $type)) {
1✔
642
                        return $getter($itemType, false);
1✔
643

644
                } elseif (
645
                        ($type && $parameter->allowsNull())
1✔
646
                        || $parameter->isOptional()
1✔
647
                        || $parameter->isDefaultValueAvailable()
1✔
648
                ) {
649
                        // !optional + defaultAvailable, !optional + !defaultAvailable since 8.1.0 = func($a = null, $b)
650
                        // optional + !defaultAvailable, optional + defaultAvailable since 8.0.0 = i.e. Exception::__construct, mysqli::mysqli, ...
651
                        // optional + !defaultAvailable = variadics
652
                        // in other cases the optional and defaultAvailable are identical
653
                        return null;
1✔
654

655
                } else {
656
                        throw new ServiceCreationException(sprintf(
1✔
657
                                'Parameter %s has %s, so its value must be specified.',
1✔
658
                                $desc,
659
                                $type && !$type->isSingle() ? 'complex type and no default value' : 'no class type or default value'
1✔
660
                        ));
661
                }
662
        }
663

664

665
        private static function isArrayOf(\ReflectionParameter $parameter, ?Nette\Utils\Type $type): ?string
1✔
666
        {
667
                $method = $parameter->getDeclaringFunction();
1✔
668
                return $method instanceof \ReflectionMethod
1✔
669
                        && $type
1✔
670
                        && $type->getSingleName() === 'array'
1✔
671
                        && preg_match(
1✔
672
                                '#@param[ \t]+(?|([\w\\\\]+)\[\]|array<int,\s*([\w\\\\]+)>)[ \t]+\$' . $parameter->name . '#',
1✔
673
                                (string) $method->getDocComment(),
1✔
674
                                $m
675
                        )
676
                        && ($itemType = Reflection::expandClassName($m[1], $method->getDeclaringClass()))
1✔
677
                        && (class_exists($itemType) || interface_exists($itemType))
1✔
678
                                ? $itemType
1✔
679
                                : null;
1✔
680
        }
681
}
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