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

nette / di / 4502239499

pending completion
4502239499

push

github

David Grudl
Accessor, Locator, Factory: generates promoted properties

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

2096 of 2216 relevant lines covered (94.58%)

0.95 hits per line

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

94.64
/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);
1✔
127
                        return $type && !in_array($type->getSingleName(), ['object', 'mixed'], true)
1✔
128
                                ? Helpers::ensureClassType($type, sprintf('return type of %s()', Callback::toString($entity)), allowNullable: true)
1✔
129
                                : null;
1✔
130

131
                } elseif ($entity instanceof Reference) { // alias or factory
1✔
132
                        return $this->resolveReferenceType($entity);
1✔
133

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

144
                        return $entity;
1✔
145
                }
146

147
                return null;
×
148
        }
149

150

151
        public function completeDefinition(Definition $def): void
1✔
152
        {
153
                $this->currentService = in_array($def, $this->builder->getDefinitions(), true)
1✔
154
                        ? $def
1✔
155
                        : null;
1✔
156
                $this->currentServiceType = $def->getType();
1✔
157
                $this->currentServiceAllowed = false;
1✔
158

159
                try {
160
                        $def->complete($this);
1✔
161

162
                        $this->addDependency(new \ReflectionClass($def->getType()));
1✔
163

164
                } catch (\Throwable $e) {
1✔
165
                        throw $this->completeException($e, $def);
1✔
166

167
                } finally {
1✔
168
                        $this->currentService = $this->currentServiceType = null;
1✔
169
                }
170
        }
1✔
171

172

173
        public function completeStatement(Statement $statement, bool $currentServiceAllowed = false): Statement
1✔
174
        {
175
                $this->currentServiceAllowed = $currentServiceAllowed;
1✔
176
                $entity = $this->normalizeEntity($statement);
1✔
177
                $arguments = $this->convertReferences($statement->arguments);
1✔
178
                $getter = fn(string $type, bool $single) => $single
1✔
179
                                ? $this->getByType($type)
1✔
180
                                : array_values(array_filter($this->builder->findAutowired($type), fn($obj) => $obj !== $this->currentService));
1✔
181

182
                switch (true) {
183
                        case is_string($entity) && Strings::contains($entity, '?'): // PHP literal
1✔
184
                                break;
1✔
185

186
                        case $entity === 'not':
1✔
187
                                if (count($arguments) !== 1) {
1✔
188
                                        throw new ServiceCreationException(sprintf('Function %s() expects 1 parameter, %s given.', $entity, count($arguments)));
×
189
                                }
190

191
                                $entity = ['', '!'];
1✔
192
                                break;
1✔
193

194
                        case $entity === 'bool':
1✔
195
                        case $entity === 'int':
1✔
196
                        case $entity === 'float':
1✔
197
                        case $entity === 'string':
1✔
198
                                if (count($arguments) !== 1) {
1✔
199
                                        throw new ServiceCreationException(sprintf('Function %s() expects 1 parameter, %s given.', $entity, count($arguments)));
1✔
200
                                }
201

202
                                $arguments = [$arguments[0], $entity];
1✔
203
                                $entity = [Helpers::class, 'convertType'];
1✔
204
                                break;
1✔
205

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

223
                                break;
1✔
224

225
                        case $entity instanceof Reference:
1✔
226
                                $entity = [new Reference(ContainerBuilder::ThisContainer), Container::getMethodName($entity->getValue())];
1✔
227
                                break;
1✔
228

229
                        case is_array($entity):
1✔
230
                                if (!preg_match('#^\$?(\\\\?' . PhpHelpers::PHP_IDENT . ')+(\[\])?$#D', $entity[1])) {
1✔
231
                                        throw new ServiceCreationException(sprintf(
1✔
232
                                                "Expected function, method or property name, '%s' given.",
1✔
233
                                                $entity[1],
1✔
234
                                        ));
235
                                }
236

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

248
                                                $rf = new \ReflectionFunction($entity[1]);
1✔
249
                                                $arguments = self::autowireArguments($rf, $arguments, $getter);
1✔
250
                                                $this->addDependency($rf);
1✔
251
                                                break;
1✔
252

253
                                        case $entity[0] instanceof Statement:
1✔
254
                                                $entity[0] = $this->completeStatement($entity[0], $this->currentServiceAllowed);
1✔
255
                                                // break omitted
256

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

276
                                                                $arguments = self::autowireArguments($rm, $arguments, $getter);
1✔
277
                                                                $this->addDependency($rm);
1✔
278

279
                                                        } elseif (!Arrays::isList($arguments)) {
1✔
280
                                                                throw new ServiceCreationException(sprintf('Unable to pass specified arguments to %s::%s().', $type, $entity[1]));
×
281
                                                        }
282
                                                }
283
                                }
284
                }
285

286
                try {
287
                        $arguments = $this->completeArguments($arguments);
1✔
288

289
                } catch (ServiceCreationException $e) {
1✔
290
                        if (!str_contains($e->getMessage(), "\nRelated to")) {
1✔
291
                                if (is_string($entity)) {
1✔
292
                                        $desc = $entity . '::__construct()';
1✔
293
                                } else {
294
                                        $desc = Helpers::entityToString($entity);
1✔
295
                                        $desc = preg_replace('~@self::~A', '', $desc);
1✔
296
                                }
297

298
                                if ($currentServiceAllowed) {
1✔
299
                                        $desc .= ' in setup';
1✔
300
                                }
301

302
                                $e->setMessage($e->getMessage() . "\nRelated to $desc.");
1✔
303
                        }
304

305
                        throw $e;
1✔
306
                }
307

308
                return new Statement($entity, $arguments);
1✔
309
        }
310

311

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

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

341

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

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

358
                        $item = new Reference($name);
1✔
359
                }
360

361
                if ($item instanceof Reference) {
1✔
362
                        $item = $this->normalizeReference($item);
1✔
363
                }
364

365
                return $entity;
1✔
366
        }
367

368

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

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

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

394

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

402

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

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

426
                return new Reference($name);
1✔
427
        }
428

429

430
        /**
431
         * Adds item to the list of dependencies.
432
         */
433
        public function addDependency(\ReflectionClass|\ReflectionFunctionAbstract|string $dep): static
1✔
434
        {
435
                $this->builder->addDependency($dep);
1✔
436
                return $this;
1✔
437
        }
438

439

440
        private function completeException(\Throwable $e, Definition $def): ServiceCreationException
1✔
441
        {
442
                $message = $e->getMessage();
1✔
443
                if ($e instanceof ServiceCreationException && str_starts_with($message, '[Service ')) {
1✔
444
                        return $e;
1✔
445
                }
446

447
                if ($tmp = $def->getType()) {
1✔
448
                        $message = str_replace(" $tmp::", ' ' . preg_replace('~.*\\\\~', '', $tmp) . '::', $message);
1✔
449
                }
450

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

453
                return $e instanceof ServiceCreationException
1✔
454
                        ? $e->setMessage($message)
1✔
455
                        : new ServiceCreationException($message, 0, $e);
1✔
456
        }
457

458

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

478

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

494
                foreach ($method->getParameters() as $num => $param) {
1✔
495
                        $paramName = $param->name;
1✔
496

497
                        if ($param->isVariadic()) {
1✔
498
                                if ($useName && Arrays::some($arguments, fn($val, $key) => is_int($key))) {
1✔
499
                                        throw new ServiceCreationException(sprintf(
1✔
500
                                                'Cannot use positional argument after named or omitted argument in %s.',
1✔
501
                                                Reflection::toString($param),
1✔
502
                                        ));
503
                                }
504

505
                                $res = array_merge($res, $arguments);
1✔
506
                                $arguments = [];
1✔
507
                        } elseif (array_key_exists($key = $paramName, $arguments) || array_key_exists($key = $num, $arguments)) {
1✔
508
                                $val = $arguments[$key];
1✔
509
                                $res[$useName ? $paramName : $num] = is_scalar($val) && self::isSensitive($param)
1✔
510
                                        ? ContainerBuilder::literal('/*sensitive{*/?/*}*/', [$val])
1✔
511
                                        : $val;
1✔
512
                                unset($arguments[$key], $arguments[$num]); // unset $num to enable overwriting in configuration
1✔
513

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

517
                        } elseif ($param->isOptional()) {
1✔
518
                                $useName = true;
1✔
519

520
                        } else {
521
                                $res[$num] = null;
1✔
522

523
                                trigger_error(sprintf(
1✔
524
                                        'The parameter %s should have a declared value in the configuration.',
1✔
525
                                        Reflection::toString($param),
1✔
526
                                ), E_USER_DEPRECATED);
1✔
527
                        }
528
                }
529

530
                // extra parameters
531
                while (!$useName && array_key_exists(++$num, $arguments)) {
1✔
532
                        $res[$num] = $arguments[$num];
1✔
533
                        unset($arguments[$num]);
1✔
534
                }
535

536
                if ($arguments) {
1✔
537
                        throw new ServiceCreationException(sprintf(
×
538
                                'Unable to pass specified arguments to %s.',
×
539
                                Reflection::toString($method),
×
540
                        ));
541
                }
542

543
                return $res;
1✔
544
        }
545

546

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

557
                if ($type?->isClass()) {
1✔
558
                        $class = $type->getSingleName();
1✔
559
                        try {
560
                                $res = $getter($class, true);
1✔
561
                        } catch (MissingServiceException $e) {
1✔
562
                                $res = null;
1✔
563
                        } catch (ServiceCreationException $e) {
1✔
564
                                throw new ServiceCreationException(sprintf("%s\nRequired by %s.", $e->getMessage(), $desc), 0, $e);
1✔
565
                        }
566

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

583
                } elseif ($itemType = self::isArrayOf($parameter, $type)) {
1✔
584
                        return $getter($itemType, false);
1✔
585

586
                } elseif ($parameter->isOptional()) {
1✔
587
                        return null;
1✔
588

589
                } else {
590
                        throw new ServiceCreationException(sprintf(
1✔
591
                                'Parameter %s has %s, so its value must be specified.',
1✔
592
                                $desc,
593
                                $type && !$type->isSimple() ? 'complex type and no default value' : 'no class type or default value',
1✔
594
                        ));
595
                }
596
        }
597

598

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

615

616
        private static function isSensitive(\ReflectionParameter $param): bool
1✔
617
        {
618
                return (bool) $param->getAttributes(\SensitiveParameter::class);
1✔
619
        }
620
}
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