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

nette / di / 6738965929

02 Nov 2023 10:31PM UTC coverage: 93.846% (+0.002%) from 93.844%
6738965929

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%)

2257 of 2405 relevant lines covered (93.85%)

0.94 hits per line

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

98.52
/src/DI/Helpers.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\Reference;
14
use Nette\DI\Definitions\Statement;
15
use Nette\Utils\Reflection;
16
use Nette\Utils\Type;
17

18

19
/**
20
 * The DI helpers.
21
 * @internal
22
 */
23
final class Helpers
24
{
25
        use Nette\StaticClass;
26

27
        /**
28
         * Expands %placeholders%.
29
         * @param  mixed  $var
30
         * @param  bool|array  $recursive
31
         * @return mixed
32
         * @throws Nette\InvalidArgumentException
33
         */
34
        public static function expand($var, array $params, $recursive = false)
1✔
35
        {
36
                if (is_array($var)) {
1✔
37
                        $res = [];
1✔
38
                        foreach ($var as $key => $val) {
1✔
39
                                $res[self::expand($key, $params, $recursive)] = self::expand($val, $params, $recursive);
1✔
40
                        }
41
                        return $res;
1✔
42

43
                } elseif ($var instanceof Statement) {
1✔
44
                        return new Statement(
1✔
45
                                self::expand($var->getEntity(), $params, $recursive),
1✔
46
                                self::expand($var->arguments, $params, $recursive)
1✔
47
                        );
48

49
                } elseif ($var === '%parameters%' && !array_key_exists('parameters', $params)) {
1✔
50
                        return $recursive
1✔
51
                                ? self::expand($params, $params, $recursive)
1✔
52
                                : $params;
1✔
53

54
                } elseif (is_string($var)) {
1✔
55
                        $recursive = is_array($recursive) ? $recursive : ($recursive ? [] : null);
1✔
56
                        return self::expandString($var, $params, $recursive);
1✔
57

58
                } else {
59
                        return $var;
1✔
60
                }
61
        }
62

63

64
        /**
65
         * Expands %placeholders% in string
66
         * @throws Nette\InvalidArgumentException
67
         */
68
        private static function expandString(string $var, array $params, ?array $recursive, bool $onlyString = false)
1✔
69
        {
70
                $parts = preg_split('#%([\w.-]*)%#i', $var, -1, PREG_SPLIT_DELIM_CAPTURE);
1✔
71
                $res = [];
1✔
72
                $dynamic = false;
1✔
73
                foreach ($parts as $n => $part) {
1✔
74
                        if ($n % 2 === 0) {
1✔
75
                                $res[] = $part;
1✔
76

77
                        } elseif ($part === '') {
1✔
78
                                $res[] = '%';
1✔
79

80
                        } else {
81
                                $val = $params;
1✔
82
                                $path = [];
1✔
83
                                $keys = explode('.', $part);
1✔
84
                                while (($key = $path[] = array_shift($keys)) !== null) {
1✔
85
                                        if (is_array($val) && array_key_exists($key, $val)) {
1✔
86
                                                $val = $val[$key];
1✔
87
                                                $fullExpand = !$onlyString && !$keys; // last
1✔
88
                                                if (is_array($recursive) && ($fullExpand || is_string($val))) {
1✔
89
                                                        $pathStr = implode('.', $path);
1✔
90
                                                        if (isset($recursive[$pathStr])) {
1✔
91
                                                                throw new Nette\InvalidArgumentException('Circular reference detected for parameters: %' . implode('%, %', array_keys($recursive)) . '%');
1✔
92
                                                        }
93
                                                        $val = $fullExpand
1✔
94
                                                                ? self::expand($val, $params, $recursive + [$pathStr => 1])
1✔
95
                                                                : self::expandString($val, $params, $recursive + [$pathStr => 1], true);
1✔
96
                                                }
97
                                        } elseif ($val instanceof DynamicParameter) {
1✔
98
                                                $val = new DynamicParameter($val . '[' . var_export($key, true) . ']');
1✔
99
                                        } else {
100
                                                throw new Nette\InvalidArgumentException(sprintf("Missing parameter '%s'.", $part));
1✔
101
                                        }
102
                                }
103

104
                                if (strlen($part) + 2 === strlen($var)) {
1✔
105
                                        return $val;
1✔
106
                                } elseif ($val instanceof DynamicParameter || $val instanceof Statement) {
1✔
107
                                        $dynamic = true;
1✔
108
                                } elseif (!is_scalar($val)) {
1✔
109
                                        throw new Nette\InvalidArgumentException(sprintf("Unable to concatenate non-scalar parameter '%s' into '%s'.", $part, $var));
1✔
110
                                }
111

112
                                $res[] = $val;
1✔
113
                        }
114
                }
115

116
                return $dynamic
1✔
117
                        ? new Statement('::implode', ['', $res])
1✔
118
                        : implode('', $res);
1✔
119
        }
120

121

122
        /**
123
         * Escapes '%' and '@'
124
         * @param  mixed  $value
125
         * @return mixed
126
         */
127
        public static function escape($value)
128
        {
129
                if (is_array($value)) {
1✔
130
                        $res = [];
1✔
131
                        foreach ($value as $key => $val) {
1✔
132
                                $key = is_string($key) ? str_replace('%', '%%', $key) : $key;
1✔
133
                                $res[$key] = self::escape($val);
1✔
134
                        }
135

136
                        return $res;
1✔
137
                } elseif (is_string($value)) {
1✔
138
                        return preg_replace('#^@|%#', '$0$0', $value);
1✔
139
                }
140

141
                return $value;
1✔
142
        }
143

144

145
        /**
146
         * Process constants recursively.
147
         */
148
        public static function filterArguments(array $args): array
1✔
149
        {
150
                foreach ($args as $k => $v) {
1✔
151
                        if (
152
                                PHP_VERSION_ID >= 80100
1✔
153
                                && is_string($v)
1✔
154
                                && preg_match('#^([\w\\\\]+)::\w+$#D', $v, $m)
1✔
155
                                && enum_exists($m[1])
1✔
156
                        ) {
157
                                $args[$k] = new Nette\PhpGenerator\PhpLiteral($v);
×
158
                        } elseif (is_string($v) && preg_match('#^[\w\\\\]*::[A-Z][a-zA-Z0-9_]*$#D', $v)) {
1✔
159
                                $args[$k] = new Nette\PhpGenerator\PhpLiteral(ltrim($v, ':'));
1✔
160
                        } elseif (is_string($v) && preg_match('#^@[\w\\\\]+$#D', $v)) {
1✔
161
                                $args[$k] = new Reference(substr($v, 1));
1✔
162
                        } elseif (is_array($v)) {
1✔
163
                                $args[$k] = self::filterArguments($v);
1✔
164
                        } elseif ($v instanceof Statement) {
1✔
165
                                [$tmp] = self::filterArguments([$v->getEntity()]);
1✔
166
                                $args[$k] = new Statement($tmp, self::filterArguments($v->arguments));
1✔
167
                        }
168
                }
169

170
                return $args;
1✔
171
        }
172

173

174
        /**
175
         * Replaces @extension with real extension name in service definition.
176
         * @param  mixed  $config
177
         * @return mixed
178
         */
179
        public static function prefixServiceName($config, string $namespace)
1✔
180
        {
181
                if (is_string($config)) {
1✔
182
                        if (strncmp($config, '@extension.', 10) === 0) {
1✔
183
                                $config = '@' . $namespace . '.' . substr($config, 11);
1✔
184
                        }
185
                } elseif ($config instanceof Reference) {
1✔
186
                        if (strncmp($config->getValue(), 'extension.', 9) === 0) {
1✔
187
                                $config = new Reference($namespace . '.' . substr($config->getValue(), 10));
1✔
188
                        }
189
                } elseif ($config instanceof Statement) {
1✔
190
                        return new Statement(
1✔
191
                                self::prefixServiceName($config->getEntity(), $namespace),
1✔
192
                                self::prefixServiceName($config->arguments, $namespace)
1✔
193
                        );
194
                } elseif (is_array($config)) {
1✔
195
                        foreach ($config as &$val) {
1✔
196
                                $val = self::prefixServiceName($val, $namespace);
1✔
197
                        }
198
                }
199

200
                return $config;
1✔
201
        }
202

203

204
        /**
205
         * Returns an annotation value.
206
         * @param  \ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionClass  $ref
207
         */
208
        public static function parseAnnotation(\Reflector $ref, string $name): ?string
1✔
209
        {
210
                if (!Reflection::areCommentsAvailable()) {
1✔
211
                        throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.');
×
212
                }
213

214
                $re = '#[\s*]@' . preg_quote($name, '#') . '(?=\s|$)(?:[ \t]+([^@\s]\S*))?#';
1✔
215
                if ($ref->getDocComment() && preg_match($re, trim($ref->getDocComment(), '/*'), $m)) {
1✔
216
                        return $m[1] ?? '';
1✔
217
                }
218

219
                return null;
1✔
220
        }
221

222

223
        public static function getReturnTypeAnnotation(\ReflectionFunctionAbstract $func): ?Type
1✔
224
        {
225
                $type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return'));
1✔
226
                if (!$type || $type === 'object' || $type === 'mixed') {
1✔
227
                        return null;
1✔
228
                } elseif ($func instanceof \ReflectionMethod) {
1✔
229
                        $type = $type === '$this' ? 'static' : $type;
1✔
230
                        $type = Reflection::expandClassName($type, $func->getDeclaringClass());
1✔
231
                }
232

233
                return Type::fromString($type);
1✔
234
        }
235

236

237
        public static function ensureClassType(?Type $type, string $hint, bool $allowNullable = false): string
1✔
238
        {
239
                if (!$type) {
1✔
240
                        throw new ServiceCreationException(sprintf('%s is not declared.', ucfirst($hint)));
1✔
241
                } elseif (!$type->isClass() || (!$allowNullable && $type->allows('null'))) {
1✔
242
                        throw new ServiceCreationException(sprintf("%s is expected to not be %sbuilt-in/complex, '%s' given.", ucfirst($hint), $allowNullable ? '' : 'nullable/', $type));
1✔
243
                }
244

245
                $class = $type->getSingleName();
1✔
246
                if (!class_exists($class) && !interface_exists($class)) {
1✔
247
                        throw new ServiceCreationException(sprintf("Class '%s' not found.\nCheck the %s.", $class, $hint));
1✔
248
                }
249

250
                return $class;
1✔
251
        }
252

253

254
        public static function normalizeClass(string $type): string
1✔
255
        {
256
                return class_exists($type) || interface_exists($type)
1✔
257
                        ? (new \ReflectionClass($type))->name
1✔
258
                        : $type;
1✔
259
        }
260

261

262
        /**
263
         * Non data-loss type conversion.
264
         * @param  mixed  $value
265
         * @return mixed
266
         * @throws Nette\InvalidStateException
267
         */
268
        public static function convertType($value, string $type)
1✔
269
        {
270
                if (is_scalar($value)) {
1✔
271
                        $norm = ($value === false ? '0' : (string) $value);
1✔
272
                        if ($type === 'float') {
1✔
273
                                $norm = preg_replace('#\.0*$#D', '', $norm);
1✔
274
                        }
275

276
                        $orig = $norm;
1✔
277
                        settype($norm, $type);
1✔
278
                        if ($orig === ($norm === false ? '0' : (string) $norm)) {
1✔
279
                                return $norm;
1✔
280
                        }
281
                }
282

283
                throw new Nette\InvalidStateException(sprintf(
1✔
284
                        'Cannot convert %s to %s.',
1✔
285
                        is_scalar($value) ? "'$value'" : gettype($value),
1✔
286
                        $type
287
                ));
288
        }
289
}
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