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

nette / di / 6350956765

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

push

github

dg
Option 'class' is allowed again

Partially reverts commit 046f89cc3.

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

2250 of 2399 relevant lines covered (93.79%)

0.94 hits per line

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

93.13
/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;
1✔
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) ?? ($annotation = Helpers::getReturnTypeAnnotation($reflection));
1✔
134
                        if ($type && !in_array($type->getSingleName(), ['object', 'mixed'], true)) {
1✔
135
                                if (isset($annotation)) {
1✔
136
                                        trigger_error('Annotation @return should be replaced with native return type at ' . Callback::toString($entity), E_USER_DEPRECATED);
1✔
137
                                }
138

139
                                return Helpers::ensureClassType($type, sprintf('return type of %s()', Callback::toString($entity)), true);
1✔
140
                        }
141

142
                        return null;
1✔
143

144
                } elseif ($entity instanceof Reference) { // alias or factory
1✔
145
                        return $this->resolveReferenceType($entity);
1✔
146

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

157
                        return $entity;
1✔
158
                }
159

160
                return null;
1✔
161
        }
162

163

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

172
                try {
173
                        $def->complete($this);
1✔
174

175
                        $this->addDependency(new \ReflectionClass($def->getType()));
1✔
176

177
                } catch (\Throwable $e) {
1✔
178
                        throw $this->completeException($e, $def);
1✔
179

180
                } finally {
1✔
181
                        $this->currentService = $this->currentServiceType = null;
1✔
182
                }
183
        }
1✔
184

185

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

197
                switch (true) {
198
                        case is_string($entity) && Strings::contains($entity, '?'): // PHP literal
1✔
199
                                break;
1✔
200

201
                        case $entity === 'not':
1✔
202
                                if (count($arguments) !== 1) {
1✔
203
                                        throw new ServiceCreationException(sprintf('Function %s() expects 1 parameter, %s given.', $entity, count($arguments)));
×
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('Function %s() expects 1 parameter, %s given.', $entity, count($arguments)));
1✔
215
                                }
216

217
                                $arguments = [$arguments[0], $entity];
1✔
218
                                $entity = [Helpers::class, 'convertType'];
1✔
219
                                break;
1✔
220

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

238
                                break;
1✔
239

240
                        case $entity instanceof Reference:
1✔
241
                                $entity = [new Reference(ContainerBuilder::ThisContainer), Container::getMethodName($entity->getValue())];
1✔
242
                                break;
1✔
243

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

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

263
                                                $rf = new \ReflectionFunction($entity[1]);
1✔
264
                                                $arguments = self::autowireArguments($rf, $arguments, $getter);
1✔
265
                                                $this->addDependency($rf);
1✔
266
                                                break;
1✔
267

268
                                        case $entity[0] instanceof Statement:
1✔
269
                                                $entity[0] = $this->completeStatement($entity[0], $this->currentServiceAllowed);
1✔
270
                                                // break omitted
271

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

291
                                                                $arguments = self::autowireArguments($rm, $arguments, $getter);
1✔
292
                                                                $this->addDependency($rm);
1✔
293

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

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

308
                        throw $e;
1✔
309
                }
310

311
                return new Statement($entity, $arguments);
1✔
312
        }
313

314

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

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

344

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

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

361
                        $item = new Reference($name);
1✔
362
                }
363

364
                if ($item instanceof Reference) {
1✔
365
                        $item = $this->normalizeReference($item);
1✔
366
                }
367

368
                return $entity;
1✔
369
        }
370

371

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

385
                        return $this->currentService && $service === $this->currentService->getName()
1✔
386
                                ? new Reference(Reference::Self)
1✔
387
                                : $ref;
1✔
388
                }
389

390
                try {
391
                        return $this->getByType($service);
1✔
392
                } catch (NotAllowedDuringResolvingException $e) {
1✔
393
                        return new Reference($service);
1✔
394
                }
395
        }
396

397

398
        public function resolveReference(Reference $ref): Definition
1✔
399
        {
400
                return $ref->isSelf()
1✔
401
                        ? $this->currentService
×
402
                        : $this->builder->getDefinition($ref->getValue());
1✔
403
        }
404

405

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

421
                $name = $this->builder->getByType($type, true);
1✔
422
                if (
423
                        !$this->currentServiceAllowed
1✔
424
                        && $this->currentService === $this->builder->getDefinition($name)
1✔
425
                ) {
426
                        throw new MissingServiceException;
1✔
427
                }
428

429
                return new Reference($name);
1✔
430
        }
431

432

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

444

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

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

463
                $message .= $type
1✔
464
                        ? str_replace("$type::", preg_replace('~.*\\\\~', '', $type) . '::', $e->getMessage())
1✔
465
                        : $e->getMessage();
1✔
466

467
                return $e instanceof ServiceCreationException
1✔
468
                        ? $e->setMessage($message)
1✔
469
                        : new ServiceCreationException($message, 0, $e);
1✔
470
        }
471

472

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

489
                        if ($entity[0] instanceof Reference) {
1✔
490
                                $entity[0] = $referenceToText($entity[0]);
1✔
491
                        } elseif (!is_string($entity[0])) {
1✔
492
                                return $entity[1];
1✔
493
                        }
494

495
                        return implode('::', $entity);
1✔
496
                }
497

498
                return (string) $entity;
×
499
        }
500

501

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

521

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

538
                foreach ($method->getParameters() as $num => $param) {
1✔
539
                        $paramName = $param->name;
1✔
540

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

548
                                } elseif (array_key_exists($paramName, $arguments)) {
1✔
549
                                        if (!is_array($arguments[$paramName])) {
1✔
550
                                                throw new ServiceCreationException(sprintf(
1✔
551
                                                        'Parameter %s must be array, %s given.',
1✔
552
                                                        Reflection::toString($param),
1✔
553
                                                        gettype($arguments[$paramName])
1✔
554
                                                ));
555
                                        }
556

557
                                        $res = array_merge($res, $arguments[$paramName]);
1✔
558
                                        unset($arguments[$paramName]);
1✔
559

560
                                } else {
561
                                        $res = array_merge($res, $arguments);
1✔
562
                                        $arguments = [];
1✔
563
                                }
564

565
                                $optCount = 0;
1✔
566
                                break;
1✔
567

568
                        } elseif (array_key_exists($key = $paramName, $arguments) || array_key_exists($key = $num, $arguments)) {
1✔
569
                                $res[$useName ? $paramName : $num] = $arguments[$key];
1✔
570
                                unset($arguments[$key], $arguments[$num]); // unset $num to enable overwriting in configuration
1✔
571

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

575
                        } elseif (PHP_VERSION_ID >= 80000) {
1✔
576
                                if ($param->isOptional()) {
×
577
                                        $useName = true;
×
578
                                } else {
579
                                        $res[$num] = null;
×
580
                                        trigger_error(sprintf(
×
581
                                                'The parameter %s should have a declared value in the configuration.',
×
582
                                                Reflection::toString($param)
×
583
                                        ), E_USER_DEPRECATED);
×
584
                                }
585

586
                        } else {
587
                                $res[$num] = $param->isDefaultValueAvailable()
1✔
588
                                        ? Reflection::getParameterDefaultValue($param)
1✔
589
                                        : null;
1✔
590

591
                                if (!$param->isOptional()) {
1✔
592
                                        trigger_error(sprintf(
1✔
593
                                                'The parameter %s should have a declared value in the configuration.',
1✔
594
                                                Reflection::toString($param)
1✔
595
                                        ), E_USER_DEPRECATED);
1✔
596
                                }
597
                        }
598

599
                        if (PHP_VERSION_ID < 80000) {
1✔
600
                                $optCount = $param->isOptional() && $res[$num] === ($param->isDefaultValueAvailable() ? Reflection::getParameterDefaultValue($param) : null)
1✔
601
                                        ? $optCount + 1
1✔
602
                                        : 0;
1✔
603
                        }
604
                }
605

606
                // extra parameters
607
                while (!$useName && !$optCount && array_key_exists(++$num, $arguments)) {
1✔
608
                        $res[$num] = $arguments[$num];
1✔
609
                        unset($arguments[$num]);
1✔
610
                }
611

612
                if ($arguments) {
1✔
613
                        throw new ServiceCreationException(sprintf(
1✔
614
                                'Unable to pass specified arguments to %s.',
1✔
615
                                Reflection::toString($method)
1✔
616
                        ));
617
                } elseif ($optCount) {
1✔
618
                        $res = array_slice($res, 0, -$optCount);
1✔
619
                }
620

621
                return $res;
1✔
622
        }
623

624

625
        /**
626
         * Resolves missing argument using autowiring.
627
         * @param  (callable(string $type, bool $single): (object|object[]|null))  $getter
628
         * @throws ServiceCreationException
629
         * @return mixed
630
         */
631
        private static function autowireArgument(\ReflectionParameter $parameter, callable $getter)
1✔
632
        {
633
                $desc = Reflection::toString($parameter);
1✔
634
                $type = Nette\Utils\Type::fromReflection($parameter);
1✔
635

636
                if ($type && $type->isClass()) {
1✔
637
                        $class = $type->getSingleName();
1✔
638
                        try {
639
                                $res = $getter($class, true);
1✔
640
                        } catch (MissingServiceException $e) {
1✔
641
                                $res = null;
1✔
642
                        } catch (ServiceCreationException $e) {
1✔
643
                                throw new ServiceCreationException("{$e->getMessage()} (required by $desc)", 0, $e);
1✔
644
                        }
645

646
                        if ($res !== null || $parameter->allowsNull()) {
1✔
647
                                return $res;
1✔
648
                        } elseif (class_exists($class) || interface_exists($class)) {
1✔
649
                                throw new ServiceCreationException(sprintf(
1✔
650
                                        'Service of type %s required by %s not found. Did you add it to configuration file?',
1✔
651
                                        $class,
652
                                        $desc
653
                                ));
654
                        } else {
655
                                throw new ServiceCreationException(sprintf(
1✔
656
                                        "Class '%s' required by %s not found. Check the parameter type and 'use' statements.",
1✔
657
                                        $class,
658
                                        $desc
659
                                ));
660
                        }
661

662
                } elseif ($itemType = self::isArrayOf($parameter, $type)) {
1✔
663
                        return $getter($itemType, false);
1✔
664

665
                } elseif (
666
                        ($type && $parameter->allowsNull())
1✔
667
                        || $parameter->isOptional()
1✔
668
                        || $parameter->isDefaultValueAvailable()
1✔
669
                ) {
670
                        // !optional + defaultAvailable, !optional + !defaultAvailable since 8.1.0 = func($a = null, $b)
671
                        // optional + !defaultAvailable, optional + defaultAvailable since 8.0.0 = i.e. Exception::__construct, mysqli::mysqli, ...
672
                        // optional + !defaultAvailable = variadics
673
                        // in other cases the optional and defaultAvailable are identical
674
                        return null;
1✔
675

676
                } else {
677
                        throw new ServiceCreationException(sprintf(
1✔
678
                                'Parameter %s has %s, so its value must be specified.',
1✔
679
                                $desc,
680
                                $type && !$type->isSingle() ? 'complex type and no default value' : 'no class type or default value'
1✔
681
                        ));
682
                }
683
        }
684

685

686
        private static function isArrayOf(\ReflectionParameter $parameter, ?Nette\Utils\Type $type): ?string
1✔
687
        {
688
                $method = $parameter->getDeclaringFunction();
1✔
689
                return $method instanceof \ReflectionMethod
1✔
690
                        && $type
1✔
691
                        && $type->getSingleName() === 'array'
1✔
692
                        && preg_match(
1✔
693
                                '#@param[ \t]+(?|([\w\\\\]+)\[\]|list<([\w\\\\]+)>|array<int,\s*([\w\\\\]+)>)[ \t]+\$' . $parameter->name . '#',
1✔
694
                                (string) $method->getDocComment(),
1✔
695
                                $m
696
                        )
697
                        && ($itemType = Reflection::expandClassName($m[1], $method->getDeclaringClass()))
1✔
698
                        && (class_exists($itemType) || interface_exists($itemType))
1✔
699
                                ? $itemType
1✔
700
                                : null;
1✔
701
        }
702
}
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