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

nette / di / 4405606637

pending completion
4405606637

push

github

David Grudl
more self explanatory message for factory and service mismatch (closes #199) (#284)

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

2118 of 2238 relevant lines covered (94.64%)

0.95 hits per line

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

94.67
/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;
×
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

295
                } catch (ServiceCreationException $e) {
1✔
296
                        if (!str_contains($e->getMessage(), "\nRelated to")) {
1✔
297
                                if (is_string($entity)) {
1✔
298
                                        $desc = $entity . '::__construct()';
1✔
299
                                } else {
300
                                        $desc = Helpers::entityToString($entity);
1✔
301
                                        $desc = preg_replace('~@self::~A', '', $desc);
1✔
302
                                }
303

304
                                if ($currentServiceAllowed) {
1✔
305
                                        $desc .= ' in setup';
1✔
306
                                }
307

308
                                $e->setMessage($e->getMessage() . "\nRelated to $desc.");
1✔
309
                        }
310

311
                        throw $e;
1✔
312
                }
313

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

317

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

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

347

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

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

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

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

371
                return $entity;
1✔
372
        }
373

374

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

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

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

400

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

408

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

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

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

435

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

445

446
        private function completeException(\Throwable $e, Definition $def): ServiceCreationException
1✔
447
        {
448
                $message = $e->getMessage();
1✔
449
                if ($e instanceof ServiceCreationException && str_starts_with($message, '[Service ')) {
1✔
450
                        return $e;
1✔
451
                }
452

453
                if ($tmp = $def->getType()) {
1✔
454
                        $message = str_replace(" $tmp::", ' ' . preg_replace('~.*\\\\~', '', $tmp) . '::', $message);
1✔
455
                }
456

457
                $message = '[' . $def->getDescriptor() . "]\n" . $message;
1✔
458

459
                return $e instanceof ServiceCreationException
1✔
460
                        ? $e->setMessage($message)
1✔
461
                        : new ServiceCreationException($message, 0, $e);
1✔
462
        }
463

464

465
        private function convertReferences(array $arguments): array
1✔
466
        {
467
                array_walk_recursive($arguments, function (&$val): void {
1✔
468
                        if (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
1✔
469
                                $pair = explode('::', substr($val, 1), 2);
1✔
470
                                if (!isset($pair[1])) { // @service
1✔
471
                                        $val = new Reference($pair[0]);
1✔
472
                                } elseif (preg_match('#^[A-Z][a-zA-Z0-9_]*$#D', $pair[1], $m)) { // @service::CONSTANT
1✔
473
                                        $val = ContainerBuilder::literal($this->resolveReferenceType(new Reference($pair[0])) . '::' . $pair[1]);
1✔
474
                                } else { // @service::property
475
                                        $val = new Statement([new Reference($pair[0]), '$' . $pair[1]]);
1✔
476
                                }
477
                        } elseif (is_string($val) && str_starts_with($val, '@@')) { // escaped text @@
1✔
478
                                $val = substr($val, 1);
1✔
479
                        }
480
                });
1✔
481
                return $arguments;
1✔
482
        }
483

484

485
        /**
486
         * Add missing arguments using autowiring.
487
         * @param  (callable(string $type, bool $single): (object|object[]|null))  $getter
488
         * @throws ServiceCreationException
489
         */
490
        public static function autowireArguments(
1✔
491
                \ReflectionFunctionAbstract $method,
492
                array $arguments,
493
                callable $getter,
494
        ): array
495
        {
496
                $useName = false;
1✔
497
                $num = -1;
1✔
498
                $res = [];
1✔
499

500
                foreach ($method->getParameters() as $num => $param) {
1✔
501
                        $paramName = $param->name;
1✔
502

503
                        if ($param->isVariadic()) {
1✔
504
                                if ($useName && Arrays::some($arguments, fn($val, $key) => is_int($key))) {
1✔
505
                                        throw new ServiceCreationException(sprintf(
1✔
506
                                                'Cannot use positional argument after named or omitted argument in %s.',
1✔
507
                                                Reflection::toString($param),
1✔
508
                                        ));
509
                                }
510

511
                                $res = array_merge($res, $arguments);
1✔
512
                                $arguments = [];
1✔
513
                        } elseif (array_key_exists($key = $paramName, $arguments) || array_key_exists($key = $num, $arguments)) {
1✔
514
                                $val = $arguments[$key];
1✔
515
                                $res[$useName ? $paramName : $num] = is_scalar($val) && self::isSensitive($param)
1✔
516
                                        ? ContainerBuilder::literal('/*sensitive{*/?/*}*/', [$val])
1✔
517
                                        : $val;
1✔
518
                                unset($arguments[$key], $arguments[$num]); // unset $num to enable overwriting in configuration
1✔
519

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

523
                        } elseif ($param->isOptional()) {
1✔
524
                                $useName = true;
1✔
525

526
                        } else {
527
                                $res[$num] = null;
1✔
528

529
                                trigger_error(sprintf(
1✔
530
                                        'The parameter %s should have a declared value in the configuration.',
1✔
531
                                        Reflection::toString($param),
1✔
532
                                ), E_USER_DEPRECATED);
1✔
533
                        }
534
                }
535

536
                // extra parameters
537
                while (!$useName && array_key_exists(++$num, $arguments)) {
1✔
538
                        $res[$num] = $arguments[$num];
1✔
539
                        unset($arguments[$num]);
1✔
540
                }
541

542
                if ($arguments) {
1✔
543
                        throw new ServiceCreationException(sprintf(
×
544
                                'Unable to pass specified arguments to %s.',
×
545
                                Reflection::toString($method),
×
546
                        ));
547
                }
548

549
                return $res;
1✔
550
        }
551

552

553
        /**
554
         * Resolves missing argument using autowiring.
555
         * @param  (callable(string $type, bool $single): (object|object[]|null))  $getter
556
         * @throws ServiceCreationException
557
         */
558
        private static function autowireArgument(\ReflectionParameter $parameter, callable $getter): mixed
1✔
559
        {
560
                $desc = Reflection::toString($parameter);
1✔
561
                $type = Nette\Utils\Type::fromReflection($parameter);
1✔
562

563
                if ($type?->isClass()) {
1✔
564
                        $class = $type->getSingleName();
1✔
565
                        try {
566
                                $res = $getter($class, true);
1✔
567
                        } catch (MissingServiceException $e) {
1✔
568
                                $res = null;
1✔
569
                        } catch (ServiceCreationException $e) {
1✔
570
                                throw new ServiceCreationException(sprintf("%s\nRequired by %s.", $e->getMessage(), $desc), 0, $e);
1✔
571
                        }
572

573
                        if ($res !== null || $parameter->allowsNull()) {
1✔
574
                                return $res;
1✔
575
                        } elseif (class_exists($class) || interface_exists($class)) {
1✔
576
                                throw new ServiceCreationException(sprintf(
1✔
577
                                        "Service of type %s required by %s not found.\nDid you add it to configuration file?",
1✔
578
                                        $class,
579
                                        $desc,
580
                                ));
581
                        } else {
582
                                throw new ServiceCreationException(sprintf(
1✔
583
                                        "Class '%s' required by %s not found.\nCheck the parameter type and 'use' statements.",
1✔
584
                                        $class,
585
                                        $desc,
586
                                ));
587
                        }
588

589
                } elseif ($itemType = self::isArrayOf($parameter, $type)) {
1✔
590
                        return $getter($itemType, false);
1✔
591

592
                } elseif ($parameter->isOptional()) {
1✔
593
                        return null;
1✔
594

595
                } else {
596
                        throw new ServiceCreationException(sprintf(
1✔
597
                                'Parameter %s has %s, so its value must be specified.',
1✔
598
                                $desc,
599
                                $type && !$type->isSimple() ? 'complex type and no default value' : 'no class type or default value',
1✔
600
                        ));
601
                }
602
        }
603

604

605
        private static function isArrayOf(\ReflectionParameter $parameter, ?Nette\Utils\Type $type): ?string
1✔
606
        {
607
                $method = $parameter->getDeclaringFunction();
1✔
608
                return $method instanceof \ReflectionMethod
1✔
609
                        && $type?->getSingleName() === 'array'
1✔
610
                        && preg_match(
1✔
611
                                '#@param[ \t]+(?|([\w\\\\]+)\[\]|array<int,\s*([\w\\\\]+)>)[ \t]+\$' . $parameter->name . '#',
1✔
612
                                (string) $method->getDocComment(),
1✔
613
                                $m,
614
                        )
615
                        && ($itemType = Reflection::expandClassName($m[1], $method->getDeclaringClass()))
1✔
616
                        && (class_exists($itemType) || interface_exists($itemType))
1✔
617
                                ? $itemType
1✔
618
                                : null;
1✔
619
        }
620

621

622
        private static function isSensitive(\ReflectionParameter $param): bool
1✔
623
        {
624
                return (bool) $param->getAttributes(\SensitiveParameter::class);
1✔
625
        }
626
}
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