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

nette / utils / 15764605036

19 Jun 2025 06:55PM UTC coverage: 92.467% (-0.05%) from 92.516%
15764605036

push

github

dg
Strings::trim() trims Line Separator and Ideographic Space (#326)

These characters occur in our data.

There are more characters listed at https://en.wikipedia.org/wiki/Whitespace_character#Unicode, maybe all of them should be included?

2050 of 2217 relevant lines covered (92.47%)

0.92 hits per line

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

96.55
/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, range;
15
use const PHP_VERSION_ID, 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
        {
86
                $res = $array1 + $array2;
×
87
                foreach (array_intersect_key($array1, $array2) as $k => $v) {
×
88
                        if (is_array($v) && is_array($array2[$k])) {
×
89
                                $res[$k] = self::mergeTree($v, $array2[$k]);
×
90
                        }
91
                }
92

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
        {
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) && (
1✔
282
                        PHP_VERSION_ID < 80100
1✔
283
                        ? !$value || array_keys($value) === range(0, count($value) - 1)
1✔
284
                        : array_is_list($value)
1✔
285
                );
286
        }
287

288

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

299
                if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') {
1✔
300
                        throw new Nette\InvalidArgumentException("Invalid path '$path'.");
1✔
301
                }
302

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

305
                foreach ($array as $rowOrig) {
1✔
306
                        $row = (array) $rowOrig;
1✔
307
                        $x = &$res;
1✔
308

309
                        for ($i = 0; $i < count($parts); $i++) {
1✔
310
                                $part = $parts[$i];
1✔
311
                                if ($part === '[]') {
1✔
312
                                        $x = &$x[];
1✔
313

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

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

334
                        if ($x === null) {
1✔
335
                                $x = $row;
1✔
336
                        }
337
                }
338

339
                return $res;
1✔
340
        }
341

342

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

353
                return $res;
1✔
354
        }
355

356

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

373
                } elseif (func_num_args() < 3) {
1✔
374
                        throw new Nette\InvalidArgumentException("Missing item '$key'.");
1✔
375

376
                } else {
377
                        return $default;
1✔
378
                }
379
        }
380

381

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

397
                return false;
1✔
398
        }
399

400

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

416
                return true;
1✔
417
        }
418

419

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

439

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

456
                return $res;
1✔
457
        }
458

459

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

481
                return $res;
1✔
482
        }
483

484

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

496
                return $res;
1✔
497
        }
498

499

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

511
                return $res;
1✔
512
        }
513

514

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

527
                return $object;
1✔
528
        }
529

530

531
        /**
532
         * Converts value to array key.
533
         */
534
        public static function toKey(mixed $value): int|string
1✔
535
        {
536
                return key([$value => null]);
1✔
537
        }
538

539

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

553
                return $res;
1✔
554
        }
555
}
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