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

nette / utils / 21636962765

03 Feb 2026 03:39PM UTC coverage: 93.312% (+0.05%) from 93.264%
21636962765

push

github

dg
added CLAUDE.md

2065 of 2213 relevant lines covered (93.31%)

0.93 hits per line

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

99.42
/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|array<mixed>>
83
         */
84
        public static function mergeTree(array $array1, array $array2): array
1✔
85
        {
86
                $res = $array1 + $array2;
1✔
87
                foreach (array_intersect_key($array1, $array2) as $k => $v) {
1✔
88
                        if (is_array($v) && is_array($array2[$k])) {
1✔
89
                                $res[$k] = self::mergeTree($v, $array2[$k]);
1✔
90
                        }
91
                }
92

93
                return $res;
1✔
94
        }
95

96

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

106

107
        /**
108
         * @param  array<mixed>  $array
109
         * @deprecated  use  getKeyOffset()
110
         */
111
        public static function searchKey(array $array, string|int $key): ?int
112
        {
113
                return self::getKeyOffset($array, $key);
×
114
        }
115

116

117
        /**
118
         * Tests an array for the presence of value.
119
         * @param  array<mixed>  $array
120
         */
121
        public static function contains(array $array, mixed $value): bool
1✔
122
        {
123
                return in_array($value, $array, strict: true);
1✔
124
        }
125

126

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

144

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

162

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

184

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

200

201
        /**
202
         * Inserts the contents of the $inserted array into the $array immediately after the $key.
203
         * If $key is null (or does not exist), it is inserted at the beginning.
204
         * @param  array<mixed>  $array
205
         * @param  array<mixed>  $inserted
206
         */
207
        public static function insertBefore(array &$array, string|int|null $key, array $inserted): void
1✔
208
        {
209
                $offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key);
1✔
210
                $array = array_slice($array, 0, $offset, preserve_keys: true)
1✔
211
                        + $inserted
1✔
212
                        + array_slice($array, $offset, count($array), preserve_keys: true);
1✔
213
        }
1✔
214

215

216
        /**
217
         * Inserts the contents of the $inserted array into the $array before the $key.
218
         * If $key is null (or does not exist), it is inserted at the end.
219
         * @param  array<mixed>  $array
220
         * @param  array<mixed>  $inserted
221
         */
222
        public static function insertAfter(array &$array, string|int|null $key, array $inserted): void
1✔
223
        {
224
                if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) {
1✔
225
                        $offset = count($array) - 1;
1✔
226
                }
227

228
                $array = array_slice($array, 0, $offset + 1, preserve_keys: true)
1✔
229
                        + $inserted
1✔
230
                        + array_slice($array, $offset + 1, count($array), preserve_keys: true);
1✔
231
        }
1✔
232

233

234
        /**
235
         * Renames key in array.
236
         * @param  array<mixed>  $array
237
         */
238
        public static function renameKey(array &$array, string|int $oldKey, string|int $newKey): bool
1✔
239
        {
240
                $offset = self::getKeyOffset($array, $oldKey);
1✔
241
                if ($offset === null) {
1✔
242
                        return false;
1✔
243
                }
244

245
                $val = &$array[$oldKey];
1✔
246
                $keys = array_keys($array);
1✔
247
                $keys[$offset] = $newKey;
1✔
248
                $array = array_combine($keys, $array);
1✔
249
                $array[$newKey] = &$val;
1✔
250
                return true;
1✔
251
        }
252

253

254
        /**
255
         * Returns only those array items, which matches a regular expression $pattern.
256
         * @param  string[]  $array
257
         * @return string[]
258
         */
259
        public static function grep(
1✔
260
                array $array,
261
                #[Language('RegExp')]
262
                string $pattern,
263
                bool|int $invert = false,
264
        ): array
265
        {
266
                $flags = $invert ? PREG_GREP_INVERT : 0;
1✔
267
                return Strings::pcre('preg_grep', [$pattern, $array, $flags]);
1✔
268
        }
269

270

271
        /**
272
         * Transforms multidimensional array to flat array.
273
         * @param  array<mixed>  $array
274
         * @return array<mixed>
275
         */
276
        public static function flatten(array $array, bool $preserveKeys = false): array
1✔
277
        {
278
                $res = [];
1✔
279
                $cb = $preserveKeys
1✔
280
                        ? function ($v, $k) use (&$res): void { $res[$k] = $v; }
1✔
281
                : function ($v) use (&$res): void { $res[] = $v; };
1✔
282
                array_walk_recursive($array, $cb);
1✔
283
                return $res;
1✔
284
        }
285

286

287
        /**
288
         * Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list.
289
         * @return ($value is list ? true : false)
290
         */
291
        public static function isList(mixed $value): bool
1✔
292
        {
293
                return is_array($value) && array_is_list($value);
1✔
294
        }
295

296

297
        /**
298
         * Reformats table to associative tree. Path looks like 'field|field[]field->field=field'.
299
         * @param  array<mixed>  $array
300
         * @param  string|string[]  $path
301
         * @return array<mixed>|\stdClass
302
         */
303
        public static function associate(array $array, $path): array|\stdClass
1✔
304
        {
305
                $parts = is_array($path)
1✔
306
                        ? $path
1✔
307
                        : preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
1✔
308

309
                if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') {
1✔
310
                        throw new Nette\InvalidArgumentException("Invalid path '" . (is_array($path) ? implode('', $path) : $path) . "'.");
1✔
311
                }
312

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

315
                foreach ($array as $rowOrig) {
1✔
316
                        $row = (array) $rowOrig;
1✔
317
                        $x = &$res;
1✔
318

319
                        for ($i = 0; $i < count($parts); $i++) {
1✔
320
                                $part = $parts[$i];
1✔
321
                                if ($part === '[]') {
1✔
322
                                        $x = &$x[];
1✔
323

324
                                } elseif ($part === '=') {
1✔
325
                                        if (isset($parts[++$i])) {
1✔
326
                                                $x = $row[$parts[$i]];
1✔
327
                                                $row = null;
1✔
328
                                        }
329
                                } elseif ($part === '->') {
1✔
330
                                        if (isset($parts[++$i])) {
1✔
331
                                                if ($x === null) {
1✔
332
                                                        $x = new \stdClass;
1✔
333
                                                }
334

335
                                                $x = &$x->{$row[$parts[$i]]};
1✔
336
                                        } else {
337
                                                $row = is_object($rowOrig) ? $rowOrig : (object) $row;
1✔
338
                                        }
339
                                } elseif ($part !== '|') {
1✔
340
                                        $x = &$x[(string) $row[$part]];
1✔
341
                                }
342
                        }
343

344
                        if ($x === null) {
1✔
345
                                $x = $row;
1✔
346
                        }
347
                }
348

349
                return $res;
1✔
350
        }
351

352

353
        /**
354
         * Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling.
355
         * @param  array<mixed>  $array
356
         * @return array<mixed>
357
         */
358
        public static function normalize(array $array, mixed $filling = null): array
1✔
359
        {
360
                $res = [];
1✔
361
                foreach ($array as $k => $v) {
1✔
362
                        $res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v;
1✔
363
                }
364

365
                return $res;
1✔
366
        }
367

368

369
        /**
370
         * Returns and removes the value of an item from an array. If it does not exist, it throws an exception,
371
         * or returns $default, if provided.
372
         * @template T
373
         * @param  array<T>  $array
374
         * @param  ?T  $default
375
         * @return ?T
376
         * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
377
         */
378
        public static function pick(array &$array, string|int $key, mixed $default = null): mixed
1✔
379
        {
380
                if (array_key_exists($key, $array)) {
1✔
381
                        $value = $array[$key];
1✔
382
                        unset($array[$key]);
1✔
383
                        return $value;
1✔
384

385
                } elseif (func_num_args() < 3) {
1✔
386
                        throw new Nette\InvalidArgumentException("Missing item '$key'.");
1✔
387

388
                } else {
389
                        return $default;
1✔
390
                }
391
        }
392

393

394
        /**
395
         * Tests whether at least one element in the array passes the test implemented by the provided function.
396
         * @template K of int|string
397
         * @template V
398
         * @param  array<K, V>  $array
399
         * @param  callable(V, K, array<K, V>): bool  $predicate
400
         */
401
        public static function some(iterable $array, callable $predicate): bool
1✔
402
        {
403
                foreach ($array as $k => $v) {
1✔
404
                        if ($predicate($v, $k, $array)) {
1✔
405
                                return true;
1✔
406
                        }
407
                }
408

409
                return false;
1✔
410
        }
411

412

413
        /**
414
         * Tests whether all elements in the array pass the test implemented by the provided function.
415
         * @template K of int|string
416
         * @template V
417
         * @param  array<K, V>  $array
418
         * @param  callable(V, K, array<K, V>): bool  $predicate
419
         */
420
        public static function every(iterable $array, callable $predicate): bool
1✔
421
        {
422
                foreach ($array as $k => $v) {
1✔
423
                        if (!$predicate($v, $k, $array)) {
1✔
424
                                return false;
1✔
425
                        }
426
                }
427

428
                return true;
1✔
429
        }
430

431

432
        /**
433
         * Returns a new array containing all key-value pairs matching the given $predicate.
434
         * @template K of int|string
435
         * @template V
436
         * @param  array<K, V>  $array
437
         * @param  callable(V, K, array<K, V>): bool  $predicate
438
         * @return array<K, V>
439
         */
440
        public static function filter(array $array, callable $predicate): array
1✔
441
        {
442
                $res = [];
1✔
443
                foreach ($array as $k => $v) {
1✔
444
                        if ($predicate($v, $k, $array)) {
1✔
445
                                $res[$k] = $v;
1✔
446
                        }
447
                }
448
                return $res;
1✔
449
        }
450

451

452
        /**
453
         * Returns an array containing the original keys and results of applying the given transform function to each element.
454
         * @template K of int|string
455
         * @template V
456
         * @template R
457
         * @param  array<K, V>  $array
458
         * @param  callable(V, K, array<K, V>): R  $transformer
459
         * @return array<K, R>
460
         */
461
        public static function map(iterable $array, callable $transformer): array
1✔
462
        {
463
                $res = [];
1✔
464
                foreach ($array as $k => $v) {
1✔
465
                        $res[$k] = $transformer($v, $k, $array);
1✔
466
                }
467

468
                return $res;
1✔
469
        }
470

471

472
        /**
473
         * Returns an array containing new keys and values generated by applying the given transform function to each element.
474
         * If the function returns null, the element is skipped.
475
         * @template K of int|string
476
         * @template V
477
         * @template ResK of int|string
478
         * @template ResV
479
         * @param  array<K, V>  $array
480
         * @param  callable(V, K, array<K, V>): ?array{ResK, ResV}  $transformer
481
         * @return array<ResK, ResV>
482
         */
483
        public static function mapWithKeys(array $array, callable $transformer): array
1✔
484
        {
485
                $res = [];
1✔
486
                foreach ($array as $k => $v) {
1✔
487
                        $pair = $transformer($v, $k, $array);
1✔
488
                        if ($pair) {
1✔
489
                                $res[$pair[0]] = $pair[1];
1✔
490
                        }
491
                }
492

493
                return $res;
1✔
494
        }
495

496

497
        /**
498
         * Invokes all callbacks and returns array of results.
499
         * @param  iterable<callable(): mixed>  $callbacks
500
         * @param  mixed  ...$args
501
         * @return array<mixed>
502
         */
503
        public static function invoke(iterable $callbacks, mixed ...$args): array
1✔
504
        {
505
                $res = [];
1✔
506
                foreach ($callbacks as $k => $cb) {
1✔
507
                        $res[$k] = $cb(...$args);
1✔
508
                }
509

510
                return $res;
1✔
511
        }
512

513

514
        /**
515
         * Invokes method on every object in an array and returns array of results.
516
         * @param  object[]  $objects
517
         * @param  mixed  ...$args
518
         * @return array<mixed>
519
         */
520
        public static function invokeMethod(iterable $objects, string $method, mixed ...$args): array
1✔
521
        {
522
                $res = [];
1✔
523
                foreach ($objects as $k => $obj) {
1✔
524
                        $res[$k] = $obj->$method(...$args);
1✔
525
                }
526

527
                return $res;
1✔
528
        }
529

530

531
        /**
532
         * Copies the elements of the $array array to the $object object and then returns it.
533
         * @template T of object
534
         * @param  iterable<mixed>  $array
535
         * @param  T  $object
536
         * @return T
537
         */
538
        public static function toObject(iterable $array, object $object): object
1✔
539
        {
540
                foreach ($array as $k => $v) {
1✔
541
                        $object->$k = $v;
1✔
542
                }
543

544
                return $object;
1✔
545
        }
546

547

548
        /**
549
         * Converts value to array key.
550
         */
551
        public static function toKey(mixed $value): int|string
1✔
552
        {
553
                return key(@[$value => null]);
1✔
554
        }
555

556

557
        /**
558
         * Returns copy of the $array where every item is converted to string
559
         * and prefixed by $prefix and suffixed by $suffix.
560
         * @param  string[]  $array
561
         * @return string[]
562
         */
563
        public static function wrap(array $array, string $prefix = '', string $suffix = ''): array
1✔
564
        {
565
                $res = [];
1✔
566
                foreach ($array as $k => $v) {
1✔
567
                        $res[$k] = $prefix . $v . $suffix;
1✔
568
                }
569

570
                return $res;
1✔
571
        }
572
}
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