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

nette / utils / 20431395313

22 Dec 2025 12:06PM UTC coverage: 93.164% (+71.8%) from 21.324%
20431395313

push

github

dg
Html::addText() accepts int|null for back compatibility [Closes #332][Closes #333]

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

140 existing lines in 15 files now uncovered.

2058 of 2209 relevant lines covered (93.16%)

0.93 hits per line

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

96.49
/src/Utils/Arrays.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\Utils;
11

12
use JetBrains\PhpStorm\Language;
13
use Nette;
14
use function array_combine, array_intersect_key, array_is_list, array_key_exists, array_key_first, array_key_last, array_keys, array_reverse, array_search, array_slice, array_walk_recursive, count, func_num_args, in_array, is_array, is_int, is_object, key, preg_split;
15
use const PREG_GREP_INVERT, PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_NO_EMPTY;
16

17

18
/**
19
 * Array tools library.
20
 */
21
class Arrays
22
{
23
        use Nette\StaticClass;
24

25
        /**
26
         * Returns item from array. If it does not exist, it throws an exception, unless a default value is set.
27
         * @template T
28
         * @param  array<T>  $array
29
         * @param  array-key|array-key[]  $key
30
         * @param  ?T  $default
31
         * @return ?T
32
         * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
33
         */
34
        public static function get(array $array, string|int|array $key, mixed $default = null): mixed
1✔
35
        {
36
                foreach (is_array($key) ? $key : [$key] as $k) {
1✔
37
                        if (is_array($array) && array_key_exists($k, $array)) {
1✔
38
                                $array = $array[$k];
1✔
39
                        } else {
40
                                if (func_num_args() < 3) {
1✔
41
                                        throw new Nette\InvalidArgumentException("Missing item '$k'.");
1✔
42
                                }
43

44
                                return $default;
1✔
45
                        }
46
                }
47

48
                return $array;
1✔
49
        }
50

51

52
        /**
53
         * Returns reference to array item. If the index does not exist, new one is created with value null.
54
         * @template T
55
         * @param  array<T>  $array
56
         * @param  array-key|array-key[]  $key
57
         * @return ?T
58
         * @throws Nette\InvalidArgumentException if traversed item is not an array
59
         */
60
        public static function &getRef(array &$array, string|int|array $key): mixed
1✔
61
        {
62
                foreach (is_array($key) ? $key : [$key] as $k) {
1✔
63
                        if (is_array($array) || $array === null) {
1✔
64
                                $array = &$array[$k];
1✔
65
                        } else {
66
                                throw new Nette\InvalidArgumentException('Traversed item is not an array.');
1✔
67
                        }
68
                }
69

70
                return $array;
1✔
71
        }
72

73

74
        /**
75
         * Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as
76
         * the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains
77
         * the value from the first array in the case of a key collision.
78
         * @template T1
79
         * @template T2
80
         * @param  array<T1>  $array1
81
         * @param  array<T2>  $array2
82
         * @return array<T1|T2>
83
         */
84
        public static function mergeTree(array $array1, array $array2): array
85
        {
UNCOV
86
                $res = $array1 + $array2;
×
UNCOV
87
                foreach (array_intersect_key($array1, $array2) as $k => $v) {
×
UNCOV
88
                        if (is_array($v) && is_array($array2[$k])) {
×
UNCOV
89
                                $res[$k] = self::mergeTree($v, $array2[$k]);
×
90
                        }
91
                }
92

UNCOV
93
                return $res;
×
94
        }
95

96

97
        /**
98
         * Returns zero-indexed position of given array key. Returns null if key is not found.
99
         */
100
        public static function getKeyOffset(array $array, string|int $key): ?int
1✔
101
        {
102
                return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), strict: true));
1✔
103
        }
104

105

106
        /**
107
         * @deprecated  use  getKeyOffset()
108
         */
109
        public static function searchKey(array $array, $key): ?int
110
        {
UNCOV
111
                return self::getKeyOffset($array, $key);
×
112
        }
113

114

115
        /**
116
         * Tests an array for the presence of value.
117
         */
118
        public static function contains(array $array, mixed $value): bool
1✔
119
        {
120
                return in_array($value, $array, true);
1✔
121
        }
122

123

124
        /**
125
         * Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
126
         * @template K of int|string
127
         * @template V
128
         * @param  array<K, V>  $array
129
         * @param  ?callable(V, K, array<K, V>): bool  $predicate
130
         * @return ?V
131
         */
132
        public static function first(array $array, ?callable $predicate = null, ?callable $else = null): mixed
1✔
133
        {
134
                $key = self::firstKey($array, $predicate);
1✔
135
                return $key === null
1✔
136
                        ? ($else ? $else() : null)
1✔
137
                        : $array[$key];
1✔
138
        }
139

140

141
        /**
142
         * Returns the last item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
143
         * @template K of int|string
144
         * @template V
145
         * @param  array<K, V>  $array
146
         * @param  ?callable(V, K, array<K, V>): bool  $predicate
147
         * @return ?V
148
         */
149
        public static function last(array $array, ?callable $predicate = null, ?callable $else = null): mixed
1✔
150
        {
151
                $key = self::lastKey($array, $predicate);
1✔
152
                return $key === null
1✔
153
                        ? ($else ? $else() : null)
1✔
154
                        : $array[$key];
1✔
155
        }
156

157

158
        /**
159
         * Returns the key of first item (matching the specified predicate if given) or null if there is no such item.
160
         * @template K of int|string
161
         * @template V
162
         * @param  array<K, V>  $array
163
         * @param  ?callable(V, K, array<K, V>): bool  $predicate
164
         * @return ?K
165
         */
166
        public static function firstKey(array $array, ?callable $predicate = null): int|string|null
1✔
167
        {
168
                if (!$predicate) {
1✔
169
                        return array_key_first($array);
1✔
170
                }
171
                foreach ($array as $k => $v) {
1✔
172
                        if ($predicate($v, $k, $array)) {
1✔
173
                                return $k;
1✔
174
                        }
175
                }
176
                return null;
1✔
177
        }
178

179

180
        /**
181
         * Returns the key of last item (matching the specified predicate if given) or null if there is no such item.
182
         * @template K of int|string
183
         * @template V
184
         * @param  array<K, V>  $array
185
         * @param  ?callable(V, K, array<K, V>): bool  $predicate
186
         * @return ?K
187
         */
188
        public static function lastKey(array $array, ?callable $predicate = null): int|string|null
1✔
189
        {
190
                return $predicate
1✔
191
                        ? self::firstKey(array_reverse($array, preserve_keys: true), $predicate)
1✔
192
                        : array_key_last($array);
1✔
193
        }
194

195

196
        /**
197
         * Inserts the contents of the $inserted array into the $array immediately after the $key.
198
         * If $key is null (or does not exist), it is inserted at the beginning.
199
         */
200
        public static function insertBefore(array &$array, string|int|null $key, array $inserted): void
1✔
201
        {
202
                $offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key);
1✔
203
                $array = array_slice($array, 0, $offset, preserve_keys: true)
1✔
204
                        + $inserted
1✔
205
                        + array_slice($array, $offset, count($array), preserve_keys: true);
1✔
206
        }
1✔
207

208

209
        /**
210
         * Inserts the contents of the $inserted array into the $array before the $key.
211
         * If $key is null (or does not exist), it is inserted at the end.
212
         */
213
        public static function insertAfter(array &$array, string|int|null $key, array $inserted): void
1✔
214
        {
215
                if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) {
1✔
216
                        $offset = count($array) - 1;
1✔
217
                }
218

219
                $array = array_slice($array, 0, $offset + 1, preserve_keys: true)
1✔
220
                        + $inserted
1✔
221
                        + array_slice($array, $offset + 1, count($array), preserve_keys: true);
1✔
222
        }
1✔
223

224

225
        /**
226
         * Renames key in array.
227
         */
228
        public static function renameKey(array &$array, string|int $oldKey, string|int $newKey): bool
1✔
229
        {
230
                $offset = self::getKeyOffset($array, $oldKey);
1✔
231
                if ($offset === null) {
1✔
232
                        return false;
1✔
233
                }
234

235
                $val = &$array[$oldKey];
1✔
236
                $keys = array_keys($array);
1✔
237
                $keys[$offset] = $newKey;
1✔
238
                $array = array_combine($keys, $array);
1✔
239
                $array[$newKey] = &$val;
1✔
240
                return true;
1✔
241
        }
242

243

244
        /**
245
         * Returns only those array items, which matches a regular expression $pattern.
246
         * @param  string[]  $array
247
         * @return string[]
248
         */
249
        public static function grep(
1✔
250
                array $array,
251
                #[Language('RegExp')]
252
                string $pattern,
253
                bool|int $invert = false,
254
        ): array
255
        {
256
                $flags = $invert ? PREG_GREP_INVERT : 0;
1✔
257
                return Strings::pcre('preg_grep', [$pattern, $array, $flags]);
1✔
258
        }
259

260

261
        /**
262
         * Transforms multidimensional array to flat array.
263
         */
264
        public static function flatten(array $array, bool $preserveKeys = false): array
1✔
265
        {
266
                $res = [];
1✔
267
                $cb = $preserveKeys
1✔
268
                        ? function ($v, $k) use (&$res): void { $res[$k] = $v; }
1✔
269
                : function ($v) use (&$res): void { $res[] = $v; };
1✔
270
                array_walk_recursive($array, $cb);
1✔
271
                return $res;
1✔
272
        }
273

274

275
        /**
276
         * Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list.
277
         * @return ($value is list ? true : false)
278
         */
279
        public static function isList(mixed $value): bool
1✔
280
        {
281
                return is_array($value) && array_is_list($value);
1✔
282
        }
283

284

285
        /**
286
         * Reformats table to associative tree. Path looks like 'field|field[]field->field=field'.
287
         * @param  string|string[]  $path
288
         */
289
        public static function associate(array $array, $path): array|\stdClass
1✔
290
        {
291
                $parts = is_array($path)
1✔
292
                        ? $path
1✔
293
                        : preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
1✔
294

295
                if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') {
1✔
296
                        throw new Nette\InvalidArgumentException("Invalid path '$path'.");
1✔
297
                }
298

299
                $res = $parts[0] === '->' ? new \stdClass : [];
1✔
300

301
                foreach ($array as $rowOrig) {
1✔
302
                        $row = (array) $rowOrig;
1✔
303
                        $x = &$res;
1✔
304

305
                        for ($i = 0; $i < count($parts); $i++) {
1✔
306
                                $part = $parts[$i];
1✔
307
                                if ($part === '[]') {
1✔
308
                                        $x = &$x[];
1✔
309

310
                                } elseif ($part === '=') {
1✔
311
                                        if (isset($parts[++$i])) {
1✔
312
                                                $x = $row[$parts[$i]];
1✔
313
                                                $row = null;
1✔
314
                                        }
315
                                } elseif ($part === '->') {
1✔
316
                                        if (isset($parts[++$i])) {
1✔
317
                                                if ($x === null) {
1✔
318
                                                        $x = new \stdClass;
1✔
319
                                                }
320

321
                                                $x = &$x->{$row[$parts[$i]]};
1✔
322
                                        } else {
323
                                                $row = is_object($rowOrig) ? $rowOrig : (object) $row;
1✔
324
                                        }
325
                                } elseif ($part !== '|') {
1✔
326
                                        $x = &$x[(string) $row[$part]];
1✔
327
                                }
328
                        }
329

330
                        if ($x === null) {
1✔
331
                                $x = $row;
1✔
332
                        }
333
                }
334

335
                return $res;
1✔
336
        }
337

338

339
        /**
340
         * Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling.
341
         */
342
        public static function normalize(array $array, mixed $filling = null): array
1✔
343
        {
344
                $res = [];
1✔
345
                foreach ($array as $k => $v) {
1✔
346
                        $res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v;
1✔
347
                }
348

349
                return $res;
1✔
350
        }
351

352

353
        /**
354
         * Returns and removes the value of an item from an array. If it does not exist, it throws an exception,
355
         * or returns $default, if provided.
356
         * @template T
357
         * @param  array<T>  $array
358
         * @param  ?T  $default
359
         * @return ?T
360
         * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
361
         */
362
        public static function pick(array &$array, string|int $key, mixed $default = null): mixed
1✔
363
        {
364
                if (array_key_exists($key, $array)) {
1✔
365
                        $value = $array[$key];
1✔
366
                        unset($array[$key]);
1✔
367
                        return $value;
1✔
368

369
                } elseif (func_num_args() < 3) {
1✔
370
                        throw new Nette\InvalidArgumentException("Missing item '$key'.");
1✔
371

372
                } else {
373
                        return $default;
1✔
374
                }
375
        }
376

377

378
        /**
379
         * Tests whether at least one element in the array passes the test implemented by the provided function.
380
         * @template K of int|string
381
         * @template V
382
         * @param  array<K, V>  $array
383
         * @param  callable(V, K, array<K, V>): bool  $predicate
384
         */
385
        public static function some(iterable $array, callable $predicate): bool
1✔
386
        {
387
                foreach ($array as $k => $v) {
1✔
388
                        if ($predicate($v, $k, $array)) {
1✔
389
                                return true;
1✔
390
                        }
391
                }
392

393
                return false;
1✔
394
        }
395

396

397
        /**
398
         * Tests whether all elements in the array pass the test implemented by the provided function.
399
         * @template K of int|string
400
         * @template V
401
         * @param  array<K, V>  $array
402
         * @param  callable(V, K, array<K, V>): bool  $predicate
403
         */
404
        public static function every(iterable $array, callable $predicate): bool
1✔
405
        {
406
                foreach ($array as $k => $v) {
1✔
407
                        if (!$predicate($v, $k, $array)) {
1✔
408
                                return false;
1✔
409
                        }
410
                }
411

412
                return true;
1✔
413
        }
414

415

416
        /**
417
         * Returns a new array containing all key-value pairs matching the given $predicate.
418
         * @template K of int|string
419
         * @template V
420
         * @param  array<K, V>  $array
421
         * @param  callable(V, K, array<K, V>): bool  $predicate
422
         * @return array<K, V>
423
         */
424
        public static function filter(array $array, callable $predicate): array
1✔
425
        {
426
                $res = [];
1✔
427
                foreach ($array as $k => $v) {
1✔
428
                        if ($predicate($v, $k, $array)) {
1✔
429
                                $res[$k] = $v;
1✔
430
                        }
431
                }
432
                return $res;
1✔
433
        }
434

435

436
        /**
437
         * Returns an array containing the original keys and results of applying the given transform function to each element.
438
         * @template K of int|string
439
         * @template V
440
         * @template R
441
         * @param  array<K, V>  $array
442
         * @param  callable(V, K, array<K, V>): R  $transformer
443
         * @return array<K, R>
444
         */
445
        public static function map(iterable $array, callable $transformer): array
1✔
446
        {
447
                $res = [];
1✔
448
                foreach ($array as $k => $v) {
1✔
449
                        $res[$k] = $transformer($v, $k, $array);
1✔
450
                }
451

452
                return $res;
1✔
453
        }
454

455

456
        /**
457
         * Returns an array containing new keys and values generated by applying the given transform function to each element.
458
         * If the function returns null, the element is skipped.
459
         * @template K of int|string
460
         * @template V
461
         * @template ResK of int|string
462
         * @template ResV
463
         * @param  array<K, V>  $array
464
         * @param  callable(V, K, array<K, V>): ?array{ResK, ResV}  $transformer
465
         * @return array<ResK, ResV>
466
         */
467
        public static function mapWithKeys(array $array, callable $transformer): array
1✔
468
        {
469
                $res = [];
1✔
470
                foreach ($array as $k => $v) {
1✔
471
                        $pair = $transformer($v, $k, $array);
1✔
472
                        if ($pair) {
1✔
473
                                $res[$pair[0]] = $pair[1];
1✔
474
                        }
475
                }
476

477
                return $res;
1✔
478
        }
479

480

481
        /**
482
         * Invokes all callbacks and returns array of results.
483
         * @param  callable[]  $callbacks
484
         */
485
        public static function invoke(iterable $callbacks, ...$args): array
1✔
486
        {
487
                $res = [];
1✔
488
                foreach ($callbacks as $k => $cb) {
1✔
489
                        $res[$k] = $cb(...$args);
1✔
490
                }
491

492
                return $res;
1✔
493
        }
494

495

496
        /**
497
         * Invokes method on every object in an array and returns array of results.
498
         * @param  object[]  $objects
499
         */
500
        public static function invokeMethod(iterable $objects, string $method, ...$args): array
1✔
501
        {
502
                $res = [];
1✔
503
                foreach ($objects as $k => $obj) {
1✔
504
                        $res[$k] = $obj->$method(...$args);
1✔
505
                }
506

507
                return $res;
1✔
508
        }
509

510

511
        /**
512
         * Copies the elements of the $array array to the $object object and then returns it.
513
         * @template T of object
514
         * @param  T  $object
515
         * @return T
516
         */
517
        public static function toObject(iterable $array, object $object): object
1✔
518
        {
519
                foreach ($array as $k => $v) {
1✔
520
                        $object->$k = $v;
1✔
521
                }
522

523
                return $object;
1✔
524
        }
525

526

527
        /**
528
         * Converts value to array key.
529
         */
530
        public static function toKey(mixed $value): int|string
1✔
531
        {
532
                return key(@[$value => null]);
1✔
533
        }
534

535

536
        /**
537
         * Returns copy of the $array where every item is converted to string
538
         * and prefixed by $prefix and suffixed by $suffix.
539
         * @param  string[]  $array
540
         * @return string[]
541
         */
542
        public static function wrap(array $array, string $prefix = '', string $suffix = ''): array
1✔
543
        {
544
                $res = [];
1✔
545
                foreach ($array as $k => $v) {
1✔
546
                        $res[$k] = $prefix . $v . $suffix;
1✔
547
                }
548

549
                return $res;
1✔
550
        }
551
}
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

© 2025 Coveralls, Inc