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

nette / utils / 22290136219

23 Feb 2026 01:47AM UTC coverage: 93.125% (-0.003%) from 93.128%
22290136219

push

github

dg
added CLAUDE.md

2086 of 2240 relevant lines covered (93.13%)

0.93 hits per line

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

99.43
/src/Utils/Arrays.php
1
<?php declare(strict_types=1);
1✔
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
namespace Nette\Utils;
9

10
use JetBrains\PhpStorm\Language;
11
use Nette;
12
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;
13
use const PREG_GREP_INVERT, PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_NO_EMPTY;
14

15

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

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

42
                                return $default;
1✔
43
                        }
44
                }
45

46
                return $array;
1✔
47
        }
48

49

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

68
                return $array;
1✔
69
        }
70

71

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

91
                return $res;
1✔
92
        }
93

94

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

104

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

114

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

124

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

143

144
        /**
145
         * Returns the last item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
146
         * @template K of int|string
147
         * @template V
148
         * @template E
149
         * @param  array<K, V>  $array
150
         * @param  ?callable(V, K, array<K, V>): bool  $predicate
151
         * @param  ?callable(): E  $else
152
         * @return ($else is null ? ?V : V|E)
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|list<string>  $path
301
         * @return array<mixed>|\stdClass
302
         */
303
        public static function associate(array $array, string|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
                                        break; // '=' is always the final operation
1✔
330

331
                                } elseif ($part === '->') {
1✔
332
                                        if (isset($parts[++$i])) {
1✔
333
                                                if ($x === null) {
1✔
334
                                                        $x = new \stdClass;
1✔
335
                                                }
336

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

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

351
                return $res;
1✔
352
        }
353

354

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

367
                return $res;
1✔
368
        }
369

370

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

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

390
                } else {
391
                        return $default;
1✔
392
                }
393
        }
394

395

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

411
                return false;
1✔
412
        }
413

414

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

430
                return true;
1✔
431
        }
432

433

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

453

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

470
                return $res;
1✔
471
        }
472

473

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

495
                return $res;
1✔
496
        }
497

498

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

511
                return $res;
1✔
512
        }
513

514

515
        /**
516
         * Invokes method on every object in an array and returns array of results.
517
         * @param  object[]  $objects
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