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

tempestphp / tempest-framework / 11828633715

13 Nov 2024 10:13PM UTC coverage: 82.602% (+0.1%) from 82.479%
11828633715

push

github

web-flow
refactor: replace `ramsey/uuid` with `symfony/uid` (#724)

2 of 2 new or added lines in 2 files covered. (100.0%)

32 existing lines in 4 files now uncovered.

7435 of 9001 relevant lines covered (82.6%)

49.19 hits per line

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

94.59
/src/Tempest/Support/src/ArrayHelper.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Support;
6

7
use ArrayAccess;
8
use Closure;
9
use Countable;
10
use Generator;
11
use InvalidArgumentException;
12
use Iterator;
13
use Random\Randomizer;
14
use Serializable;
15
use Stringable;
16
use function Tempest\map;
17

18
/**
19
 * @template TKey of array-key
20
 * @template TValue
21
 *
22
 * @implements ArrayAccess<TKey, TValue>
23
 * @implements Iterator<TKey, TValue>
24
 */
25
final class ArrayHelper implements Iterator, ArrayAccess, Serializable, Countable
26
{
27
    use IsIterable;
28

29
    /** @var array<TKey, TValue> */
30
    private array $array;
31

32
    /**
33
     * @param array<TKey, TValue>|self<TKey, TValue>|TValue $input
34
     */
35
    public function __construct(
486✔
36
        mixed $input = [],
37
    ) {
38
        if (is_array($input)) {
486✔
39
            $this->array = $input;
462✔
40
        } elseif ($input instanceof self) {
185✔
41
            $this->array = $input->array;
2✔
42
        } elseif ($input === null) {
184✔
43
            $this->array = [];
1✔
44
        } else {
45
            $this->array = [$input];
184✔
46
        }
47
    }
48

49
    /**
50
     * Finds a value in the array and return the corresponding key if successful.
51
     *
52
     * @param (Closure(TValue, TKey): bool)|mixed $value The value to search for, a Closure will find the first item that returns true.
53
     * @param bool $strict Whether to use strict comparison.
54
     *
55
     * @return array-key|null The key for `$value` if found, `null` otherwise.
56
     */
57
    public function findKey(mixed $value, bool $strict = false): int|string|null
7✔
58
    {
59
        if (! $value instanceof Closure) {
7✔
60
            $search = array_search($value, $this->array, $strict);
5✔
61

62
            return $search === false ? null : $search; // Keep empty values but convert false to null
5✔
63
        }
64

65
        foreach ($this->array as $key => $item) {
3✔
66
            if ($value($item, $key) === true) {
2✔
67
                return $key;
2✔
68
            }
69
        }
70

71
        return null;
2✔
72
    }
73

74
    /**
75
     * Chunks the array into chunks of the given size.
76
     *
77
     * @param int $size The size of each chunk.
78
     * @param bool $preserveKeys Whether to preserve the keys of the original array.
79
     *
80
     * @return self<array-key, self>
81
     */
82
    public function chunk(int $size, bool $preserveKeys = true): self
2✔
83
    {
84
        if ($size <= 0) {
2✔
UNCOV
85
            return new self();
×
86
        }
87

88
        $chunks = [];
2✔
89
        foreach (array_chunk($this->array, $size, $preserveKeys) as $chunk) {
2✔
90
            $chunks[] = new self($chunk);
2✔
91
        }
92

93
        return new self($chunks);
2✔
94
    }
95

96
    /**
97
     * Reduces the array to a single value using a callback.
98
     *
99
     * @template TReduceInitial
100
     * @template TReduceReturnType
101
     *
102
     * @param callable(TReduceInitial|TReduceReturnType, TValue, TKey): TReduceReturnType $callback
103
     * @param TReduceInitial $initial
104
     *
105
     * @return TReduceReturnType
106
     */
107
    public function reduce(callable $callback, mixed $initial = null): mixed
3✔
108
    {
109
        $result = $initial;
3✔
110

111
        foreach ($this->array as $key => $value) {
3✔
112
            $result = $callback($result, $value, $key);
2✔
113
        }
114

115
        return $result;
3✔
116
    }
117

118
    /**
119
     * Gets a value from the array and remove it.
120
     *
121
     * @param array-key $key
122
     */
123
    public function pull(string|int $key, mixed $default = null): mixed
1✔
124
    {
125
        $value = $this->get($key, $default);
1✔
126

127
        $this->remove($key);
1✔
128

129
        return $value;
1✔
130
    }
131

132
    /**
133
     * Shuffles the array.
134
     *
135
     * @return self<TKey, TValue>
136
     */
137
    public function shuffle(): self
2✔
138
    {
139
        return new self((new Randomizer())->shuffleArray($this->array));
2✔
140
    }
141

142
    /**
143
     * @alias of `remove`.
144
     */
145
    public function forget(string|int|array $keys): self
1✔
146
    {
147
        return $this->remove($keys);
1✔
148
    }
149

150
    /**
151
     * Removes the specified items from the array.
152
     *
153
     * @param array-key|array<array-key> $keys The keys of the items to remove.
154
     *
155
     * @return self<TKey, TValue>
156
     */
157
    public function remove(string|int|array $keys): self
5✔
158
    {
159
        $keys = is_array($keys) ? $keys : [$keys];
5✔
160

161
        foreach ($keys as $key) {
5✔
162
            $this->offsetUnset($key);
5✔
163
        }
164

165
        return $this;
5✔
166
    }
167

168
    /**
169
     * Asserts whether the array is a list.
170
     * An array is a list if its keys consist of consecutive numbers.
171
     */
172
    public function isList(): bool
13✔
173
    {
174
        return array_is_list($this->array);
13✔
175
    }
176

177
    /**
178
     * Asserts whether the array is a associative.
179
     * An array is associative if its keys do not consist of consecutive numbers.
180
     */
181
    public function isAssoc(): bool
12✔
182
    {
183
        return ! $this->isList();
12✔
184
    }
185

186
    /**
187
     * Gets one or a specified number of random values from the array.
188
     *
189
     * @param int $number The number of random values to get.
190
     * @param bool $preserveKey Whether to include the keys of the original array.
191
     *
192
     * @return self<TKey, TValue>|mixed The random values, or a single value if `$number` is 1.
193
     */
194
    public function random(int $number = 1, bool $preserveKey = false): mixed
5✔
195
    {
196
        $count = count($this->array);
5✔
197

198
        if ($number > $count) {
5✔
199
            throw new InvalidArgumentException("Cannot retrive {$number} items from an array of {$count} items.");
2✔
200
        }
201

202
        if ($number < 1) {
3✔
203
            throw new InvalidArgumentException("Random value only accepts positive integers, {$number} requested.");
1✔
204
        }
205

206
        $keys = (new Randomizer())->pickArrayKeys($this->array, $number);
2✔
207

208
        $randomValues = [];
2✔
209
        foreach ($keys as $key) {
2✔
210
            $preserveKey
2✔
211
                ? $randomValues[$key] = $this->array[$key]
1✔
212
                : $randomValues[] = $this->array[$key];
1✔
213
        }
214

215
        if ($preserveKey === false) {
2✔
216
            shuffle($randomValues);
1✔
217
        }
218

219
        return count($randomValues) > 1
2✔
220
            ? new self($randomValues)
2✔
221
            : $randomValues[0];
2✔
222
    }
223

224
    /**
225
     * Retrieves values from a given key in each sub-array of the current array.
226
     * Optionally, you can pass a second parameter to also get the keys following the same pattern.
227
     *
228
     * @param string $value The key to assign the values from, support dot notation.
229
     * @param string|null $key The key to assign the keys from, support dot notation.
230
     *
231
     * @return self<TKey, TValue>
232
     */
233
    public function pluck(string $value, ?string $key = null): self
3✔
234
    {
235
        $results = [];
3✔
236

237
        foreach ($this->array as $item) {
3✔
238
            if (! is_array($item)) {
3✔
239
                continue;
1✔
240
            }
241

242
            $itemValue = arr($item)->get($value);
2✔
243

244
            /**
245
             * Perform basic pluck if no key is given.
246
             * Otherwise, also pluck the key as well.
247
             */
248
            if (is_null($key)) {
2✔
249
                $results[] = $itemValue;
2✔
250
            } else {
251
                $itemKey = arr($item)->get($key);
2✔
252
                $results[$itemKey] = $itemValue;
2✔
253
            }
254
        }
255

256
        return new self($results);
3✔
257
    }
258

259
    /**
260
     * @alias of `add`.
261
     */
262
    public function push(mixed $value): self
1✔
263
    {
264
        return $this->add($value);
1✔
265
    }
266

267
    /**
268
     * Appends the specified value to the array.
269
     *
270
     * @return self<TKey, TValue>
271
     */
272
    public function add(mixed $value): self
2✔
273
    {
274
        $this->array[] = $value;
2✔
275

276
        return $this;
2✔
277
    }
278

279
    /**
280
     * Pads the array to the specified size with a value.
281
     *
282
     * @return self<TKey, TValue>
283
     */
284
    public function pad(int $size, mixed $value): self
1✔
285
    {
286
        return new self(array_pad($this->array, $size, $value));
1✔
287
    }
288

289
    /**
290
     * Reverses the keys and values of the array.
291
     *
292
     * @return self<TValue&array-key, TKey>
293
     */
294
    public function flip(): self
1✔
295
    {
296
        return new self(array_flip($this->array));
1✔
297
    }
298

299
    /**
300
     * Returns a new instance with only unique items from the original array.
301
     *
302
     * @param string|null $key The key to use as the uniqueness criteria in nested arrays.
303
     * @param bool $shouldBeStrict Whether the comparison should be strict, only used when giving a key parameter.
304
     *
305
     * @return self<TKey, TValue>
306
     */
307
    public function unique(?string $key = null, bool $shouldBeStrict = false): self
9✔
308
    {
309
        if (is_null($key) && $shouldBeStrict === false) {
9✔
310
            return new self(array_unique($this->array, flags: SORT_REGULAR));
4✔
311
        }
312

313
        $uniqueItems = [];
5✔
314
        $uniqueFilteredValues = [];
5✔
315
        foreach ($this->array as $item) {
5✔
316
            // Ensure we don't check raw values with key filter
317
            if (! is_null($key) && ! is_array($item)) {
5✔
318
                continue;
1✔
319
            }
320

321
            $filterValue = is_array($item)
5✔
322
                ? arr($item)->get($key)
4✔
323
                : $item;
1✔
324

325
            if (is_null($filterValue)) {
5✔
UNCOV
326
                continue;
×
327
            }
328

329
            if (in_array($filterValue, $uniqueFilteredValues, strict: $shouldBeStrict)) {
5✔
330
                continue;
4✔
331
            }
332

333
            $uniqueItems[] = $item;
5✔
334
            $uniqueFilteredValues[] = $filterValue;
5✔
335
        }
336

337
        return new self($uniqueItems);
5✔
338
    }
339

340
    /**
341
     * Returns a new instance of the array with only the items that are not present in any of the given arrays.
342
     *
343
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays
344
     *
345
     * @return self<TKey, TValue>
346
     */
347
    public function diff(array|self ...$arrays): self
1✔
348
    {
349
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
1✔
350

351
        return new self(array_diff($this->array, ...$arrays));
1✔
352
    }
353

354
    /**
355
     * Returns a new instance of the array with only the items whose keys are not present in any of the given arrays.
356
     *
357
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays
358
     *
359
     * @return self<TKey, TValue>
360
     */
361
    public function diffKeys(array|self ...$arrays): self
1✔
362
    {
363
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
1✔
364

365
        return new self(array_diff_key($this->array, ...$arrays));
1✔
366
    }
367

368
    /**
369
     * Returns a new instance of the array with only the items that are present in all of the given arrays.
370
     *
371
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays
372
     *
373
     * @return self<TKey, TValue>
374
     */
375
    public function intersect(array|self ...$arrays): self
1✔
376
    {
377
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
1✔
378

379
        return new self(array_intersect($this->array, ...$arrays));
1✔
380
    }
381

382
    /**
383
     * Returns a new instance of the array with only the items whose keys are present in all of the given arrays.
384
     *
385
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays
386
     *
387
     * @return self<TKey, TValue>
388
     */
389
    public function intersectKeys(array|self ...$arrays): self
1✔
390
    {
391
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
1✔
392

393
        return new self(array_intersect_key($this->array, ...$arrays));
1✔
394
    }
395

396
    /**
397
     * Merges the array with the given arrays.
398
     *
399
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays The arrays to merge.
400
     *
401
     * @return self<TKey, TValue>
402
     */
403
    public function merge(array|self ...$arrays): self
2✔
404
    {
405
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
2✔
406

407
        return new self(array_merge($this->array, ...$arrays));
2✔
408
    }
409

410
    /**
411
     * Creates a new array with this current array values as keys and the given values as values.
412
     *
413
     * @template TCombineValue
414
     *
415
     * @param array<array-key, TCombineValue>|self<array-key, TCombineValue> $values
416
     *
417
     * @return self<array-key, TCombineValue>
418
     */
419
    public function combine(array|self $values): self
4✔
420
    {
421
        $values = $values instanceof self
4✔
422
            ? $values->toArray()
1✔
423
            : $values;
3✔
424

425
        return new self(array_combine($this->array, $values));
4✔
426
    }
427

428
    /**
429
     * Creates an array from the specified `$string`, split by the given `$separator`.
430
     */
431
    public static function explode(string|Stringable $string, string $separator = ' '): self
4✔
432
    {
433
        if ($separator === '') {
4✔
434
            return new self([(string) $string]);
1✔
435
        }
436

437
        if ((string) $string === '') {
4✔
UNCOV
438
            return new self();
×
439
        }
440

441
        return new self(explode($separator, (string) $string));
4✔
442
    }
443

444
    /**
445
     * Asserts whether this instance is equal to the given array.
446
     */
447
    public function equals(array|self $other): bool
16✔
448
    {
449
        $other = is_array($other) ? $other : $other->array;
16✔
450

451
        return $this->array === $other;
16✔
452
    }
453

454
    /**
455
     * Returns the first item in the instance that matches the given `$filter`.
456
     * If `$filter` is `null`, returns the first item.
457
     *
458
     * @param null|Closure(TValue $value, TKey $key): bool $filter
459
     *
460
     * @return TValue
461
     */
462
    public function first(?Closure $filter = null): mixed
14✔
463
    {
464
        if ($this->array === []) {
14✔
465
            return null;
2✔
466
        }
467

468
        if ($filter === null) {
14✔
469
            return $this->array[array_key_first($this->array)];
2✔
470
        }
471

472
        foreach ($this as $key => $value) {
12✔
473
            if ($filter($value, $key)) {
12✔
474
                return $value;
12✔
475
            }
476
        }
477

478
        return null;
7✔
479
    }
480

481
    /**
482
     * Returns the last item in the instance that matches the given `$filter`.
483
     * If `$filter` is `null`, returns the last item.
484
     *
485
     * @param null|Closure(TValue $value, TKey $key): bool $filter
486
     *
487
     * @return TValue
488
     */
489
    public function last(?Closure $filter = null): mixed
18✔
490
    {
491
        if ($this->array === []) {
18✔
492
            return null;
2✔
493
        }
494

495
        if ($filter === null) {
18✔
496
            return $this->array[array_key_last($this->array)];
18✔
497
        }
498

UNCOV
499
        foreach ($this->reverse() as $key => $value) {
×
UNCOV
500
            if ($filter($value, $key)) {
×
UNCOV
501
                return $value;
×
502
            }
503
        }
504

UNCOV
505
        return null;
×
506
    }
507

508
    /**
509
     * Returns an instance of the array without the last value.
510
     *
511
     * @param mixed $value The popped value will be stored in this variable
512
     */
513
    public function pop(mixed &$value = null): self
17✔
514
    {
515
        $value = $this->last();
17✔
516

517
        return new self(array_slice($this->array, 0, -1));
17✔
518
    }
519

520
    /**
521
     * Returns an instance of the array without the first value.
522
     *
523
     * @param mixed $value The unshifted value will be stored in this variable
524
     */
525
    public function unshift(mixed &$value = null): self
1✔
526
    {
527
        $value = $this->first();
1✔
528

529
        return new self(array_slice($this->array, 1));
1✔
530
    }
531

532
    /**
533
     * Returns a new instance of the array in reverse order.
534
     */
535
    public function reverse(): self
1✔
536
    {
537
        return new self(array_reverse($this->array));
1✔
538
    }
539

540
    /**
541
     * Asserts whether the array is empty.
542
     */
543
    public function isEmpty(): bool
17✔
544
    {
545
        return empty($this->array);
17✔
546
    }
547

548
    /**
549
     * Asserts whether the array is not empty.
550
     */
551
    public function isNotEmpty(): bool
14✔
552
    {
553
        return ! $this->isEmpty();
14✔
554
    }
555

556
    /**
557
     * Returns an instance of `StringHelper` with the values of the instance joined with the given `$glue`.
558
     */
559
    public function implode(string $glue): StringHelper
80✔
560
    {
561
        return str(implode($glue, $this->array));
80✔
562
    }
563

564
    /**
565
     * Returns a new instance with the keys of this array as values.
566
     *
567
     * @return self<array-key, TKey>
568
     */
569
    public function keys(): self
1✔
570
    {
571
        return new self(array_keys($this->array));
1✔
572
    }
573

574
    /**
575
     * Returns a new instance of this array without its keys.
576
     *
577
     * @return self<int, TValue>
578
     */
579
    public function values(): self
355✔
580
    {
581
        return new self(array_values($this->array));
355✔
582
    }
583

584
    /**
585
     * Returns a new instance of this array with only the items that pass the given `$filter`.
586
     * If `$filter` is `null`, the new instance will contain only values that are not `false` or `null`.
587
     *
588
     * @param null|Closure(mixed $value, mixed $key): bool $filter
589
     */
590
    public function filter(?Closure $filter = null): self
1✔
591
    {
592
        $array = [];
1✔
593
        $filter ??= static fn (mixed $value, mixed $_) => ! in_array($value, [false, null], strict: true);
1✔
594

595
        foreach ($this->array as $key => $value) {
1✔
596
            if ($filter($value, $key)) {
1✔
597
                $array[$key] = $value;
1✔
598
            }
599
        }
600

601
        return new self($array);
1✔
602
    }
603

604
    /**
605
     * Applies the given callback to all items of the instance.
606
     *
607
     * @param Closure(mixed $value, mixed $key): void $each
608
     */
609
    public function each(Closure $each): self
70✔
610
    {
611
        foreach ($this as $key => $value) {
70✔
612
            $each($value, $key);
68✔
613
        }
614

615
        return $this;
70✔
616
    }
617

618
    /**
619
     * Returns a new instance of the array, with each item transformed by the given callback.
620
     *
621
     * @template TMapValue
622
     *
623
     * @param  Closure(TValue, TKey): TMapValue $map
624
     *
625
     * @return static<TKey, TMapValue>
626
     */
627
    public function map(Closure $map): self
353✔
628
    {
629
        $array = [];
353✔
630

631
        foreach ($this->array as $key => $value) {
353✔
632
            $array[$key] = $map($value, $key);
353✔
633
        }
634

635
        return new self($array);
353✔
636
    }
637

638
    /**
639
     * Returns a new instance of the array, with each item transformed by the given callback.
640
     * The callback must return a generator, associating a key and a value.
641
     *
642
     * ### Example
643
     * ```php
644
     * arr(['a', 'b'])->mapWithKeys(fn (mixed $value, mixed $key) => yield $key => $value);
645
     * ```
646
     *
647
     * @param Closure(mixed $value, mixed $key): Generator $map
648
     */
649
    public function mapWithKeys(Closure $map): self
3✔
650
    {
651
        $array = [];
3✔
652

653
        foreach ($this->array as $key => $value) {
3✔
654
            $generator = $map($value, $key);
3✔
655

656
            if (! $generator instanceof Generator) {
3✔
657
                throw new InvalidMapWithKeysUsage();
1✔
658
            }
659

660
            $array[$generator->key()] = $generator->current();
2✔
661
        }
662

663
        return new self($array);
2✔
664
    }
665

666
    /**
667
     * Gets the value identified by the specified `$key`, or `$default` if no such value exists.
668
     *
669
     * @return mixed|ArrayHelper
670
     */
671
    public function get(int|string $key, mixed $default = null): mixed
353✔
672
    {
673
        $value = $this->array;
353✔
674

675
        $keys = is_int($key)
353✔
UNCOV
676
            ? [$key]
×
677
            : explode('.', $key);
353✔
678

679
        foreach ($keys as $key) {
353✔
680
            if (! isset($value[$key])) {
353✔
681
                return $default;
2✔
682
            }
683

684
            $value = $value[$key];
353✔
685
        }
686

687
        if (is_array($value)) {
353✔
688
            return new self($value);
346✔
689
        }
690

691
        return $value;
8✔
692
    }
693

694
    /**
695
     * Asserts whether a value identified by the specified `$key` exists.
696
     */
697
    public function has(int|string $key): bool
1✔
698
    {
699
        $array = $this->array;
1✔
700

701
        $keys = is_int($key)
1✔
UNCOV
702
            ? [$key]
×
703
            : explode('.', $key);
1✔
704

705
        foreach ($keys as $key) {
1✔
706
            if (! isset($array[$key])) {
1✔
707
                return false;
1✔
708
            }
709

710
            $array = &$array[$key];
1✔
711
        }
712

713
        return true;
1✔
714
    }
715

716
    /**
717
     * Asserts whether the instance contains an item that can be identified by `$search`.
718
     */
719
    public function contains(mixed $search): bool
1✔
720
    {
721
        return $this->first(fn (mixed $value) => $value === $search) !== null;
1✔
722
    }
723

724
    /**
725
     * Associates the given `$value` to the given `$key` on the instance.
726
     */
727
    public function set(string $key, mixed $value): self
2✔
728
    {
729
        $array = $this->array;
2✔
730

731
        $current = &$array;
2✔
732

733
        $keys = explode('.', $key);
2✔
734

735
        foreach ($keys as $i => $key) {
2✔
736
            // If this is the last key in dot notation, we don't
737
            // need to go through the next steps.
738
            if (count($keys) === 1) {
2✔
739
                break;
2✔
740
            }
741

742
            // Remove the current key from our keys array
743
            // so that later we can use the first value
744
            // from that array as our key.
745
            unset($keys[$i]);
2✔
746

747
            // If we know this key is not an array, make it one.
748
            if (! isset($current[$key]) || ! is_array($current[$key])) {
2✔
749
                $current[$key] = [];
2✔
750
            }
751

752
            // Set the context to this key.
753
            $current = &$current[$key];
2✔
754
        }
755

756
        // Pull the first key out of the array
757
        // and use it to set the value.
758
        $current[array_shift($keys)] = $value;
2✔
759

760
        return new self($array);
2✔
761
    }
762

763
    /**
764
     * @alias of `set`
765
     */
766
    public function put(string $key, mixed $value): self
1✔
767
    {
768
        return $this->set($key, $value);
1✔
769
    }
770

771
    /**
772
     * Converts the dot-notated keys of the instance to a set of nested arrays.
773
     */
774
    public function unwrap(): self
92✔
775
    {
776
        $unwrapValue = function (string|int $key, mixed $value) {
92✔
777
            if (is_int($key)) {
88✔
UNCOV
778
                return [$key => $value];
×
779
            }
780

781
            $keys = explode('.', $key);
88✔
782

783
            for ($i = array_key_last($keys); $i >= 0; $i--) {
88✔
784
                $currentKey = $keys[$i];
88✔
785

786
                $value = [$currentKey => $value];
88✔
787
            }
788

789
            return $value;
88✔
790
        };
92✔
791

792
        $array = [];
92✔
793

794
        foreach ($this->array as $key => $value) {
92✔
795
            $array = array_merge_recursive($array, $unwrapValue($key, $value));
88✔
796
        }
797

798
        return new self($array);
92✔
799
    }
800

801
    /**
802
     * Joins all values using the specified `$glue`. The last item of the string is separated by `$finalGlue`.
803
     */
804
    public function join(string $glue = ', ', ?string $finalGlue = ' and '): StringHelper
16✔
805
    {
806
        if ($finalGlue === '' || is_null($finalGlue)) {
16✔
807
            return $this->implode($glue);
2✔
808
        }
809

810
        if ($this->isEmpty()) {
14✔
UNCOV
811
            return str('');
×
812
        }
813

814
        $parts = $this->pop($last);
14✔
815

816
        if ($parts->isNotEmpty()) {
14✔
817
            return $parts->implode($glue)->append($finalGlue, $last);
9✔
818
        }
819

820
        return str($last);
6✔
821
    }
822

823
    /**
824
     * Flattens the instance to a single-level array, or until the specified `$depth` is reached.
825
     *
826
     * ### Example
827
     * ```php
828
     * arr(['foo', ['bar', 'baz']])->flatten(); // ['foo', 'bar', 'baz']
829
     * ```
830
     */
831
    public function flatten(int|float $depth = INF): self
2✔
832
    {
833
        $result = [];
2✔
834

835
        foreach ($this->array as $item) {
2✔
836
            if (! is_array($item)) {
2✔
837
                $result[] = $item;
2✔
838

839
                continue;
2✔
840
            }
841

842
            $values = $depth === 1
2✔
843
                ? array_values($item)
2✔
844
                : arr($item)->flatten($depth - 1);
2✔
845

846
            foreach ($values as $value) {
2✔
847
                $result[] = $value;
2✔
848
            }
849
        }
850

851
        return new self($result);
2✔
852
    }
853

854
    /**
855
     * Returns a new instance of the array, with each item transformed by the given callback, then flattens it by the specified depth.
856
     *
857
     * @template TMapValue
858
     *
859
     * @param  Closure(TValue, TKey): TMapValue[] $map
860
     *
861
     * @return static<TKey, TMapValue>
862
     */
863
    public function flatMap(Closure $map, int|float $depth = 1): self
1✔
864
    {
865
        return $this->map($map)->flatten($depth);
1✔
866
    }
867

868
    /**
869
     * Dumps the instance.
870
     */
UNCOV
871
    public function dump(mixed ...$dumps): self
×
872
    {
UNCOV
873
        lw($this->array, ...$dumps);
×
874

UNCOV
875
        return $this;
×
876
    }
877

878
    /**
879
     * Dumps the instance and stops the execution of the script.
880
     */
UNCOV
881
    public function dd(mixed ...$dd): void
×
882
    {
UNCOV
883
        ld($this->array, ...$dd);
×
884
    }
885

886
    /**
887
     * Returns the underlying array of the instance.
888
     *
889
     * @return array<TKey, TValue>
890
     */
891
    public function toArray(): array
392✔
892
    {
893
        return $this->array;
392✔
894
    }
895

896
    /**
897
     * Maps the items of the instance to the given object.
898
     *
899
     * @see Tempest\map()
900
     *
901
     * @template T
902
     * @param class-string<T> $to
903
     * @return self<T>
904
     */
905
    public function mapTo(string $to): self
1✔
906
    {
907
        return new self(map($this->array)->collection()->to($to));
1✔
908
    }
909

910
    /**
911
     * Returns a new instance of this array sorted by its values.
912
     *
913
     * @param bool $desc Sorts in descending order if `true`; defaults to `false` (ascending).
914
     * @param bool|null $preserveKeys Preserves array keys if `true`; reindexes numerically if `false`.
915
     *                                Defaults to `null`, which auto-detects preservation based on array type  (associative or list).
916
     * @param int $flags Sorting flags to define comparison behavior, defaulting to `SORT_REGULAR`.
917
     * @return self<array-key, TValue> Key type depends on whether array keys are preserved or not.
918
     */
919
    public function sort(bool $desc = false, ?bool $preserveKeys = null, int $flags = SORT_REGULAR): self
10✔
920
    {
921
        $array = $this->array;
10✔
922

923
        if ($preserveKeys === null) {
10✔
924
            $preserveKeys = $this->isAssoc();
10✔
925
        }
926

927
        if ($preserveKeys) {
10✔
928
            $desc ? arsort($array, $flags) : asort($array, $flags);
1✔
929
        } else {
930
            $desc ? rsort($array, $flags) : sort($array, $flags);
10✔
931
        }
932

933
        return new self($array);
10✔
934
    }
935

936
    /**
937
     * Returns a new instance of this array sorted by its values using a callback function.
938
     *
939
     * @param callable $callback The function to use for comparing values. It should accept two parameters
940
     *                           and return an integer less than, equal to, or greater than zero if the
941
     *                           first argument is considered to be respectively less than, equal to, or
942
     *                           greater than the second.
943
     * @param bool|null $preserveKeys Preserves array keys if `true`; reindexes numerically if `false`.
944
     *                                Defaults to `null`, which auto-detects preservation based on array type  (associative or list).
945
     * @return self<array-key, TValue> Key type depends on whether array keys are preserved or not.
946
     */
947
    public function sortByCallback(callable $callback, ?bool $preserveKeys = null): self
1✔
948
    {
949
        $array = $this->array;
1✔
950

951
        if ($preserveKeys === null) {
1✔
952
            $preserveKeys = $this->isAssoc();
1✔
953
        }
954

955
        $preserveKeys ? uasort($array, $callback) : usort($array, $callback);
1✔
956

957
        return new self($array);
1✔
958
    }
959

960
    /**
961
     * Returns a new instance of this array sorted by its keys.
962
     *
963
     * @param bool $desc Sorts in descending order if `true`; defaults to `false` (ascending).
964
     * @param int $flags Sorting flags to define comparison behavior, defaulting to `SORT_REGULAR`.
965
     * @return self<TKey, TValue>
966
     */
967
    public function sortKeys(bool $desc = false, int $flags = SORT_REGULAR): self
1✔
968
    {
969
        $array = $this->array;
1✔
970

971
        $desc ? krsort($array, $flags) : ksort($array, $flags);
1✔
972

973
        return new self($array);
1✔
974
    }
975

976
    /**
977
     * Returns a new instance of this array sorted by its keys using a callback function.
978
     *
979
     * @param callable $callback The function to use for comparing keys. It should accept two parameters
980
     *                           and return an integer less than, equal to, or greater than zero if the
981
     *                           first argument is considered to be respectively less than, equal to, or
982
     *                           greater than the second.
983
     * @return self<TKey, TValue>
984
     */
985
    public function sortKeysByCallback(callable $callback): self
1✔
986
    {
987
        $array = $this->array;
1✔
988

989
        uksort($array, $callback);
1✔
990

991
        return new self($array);
1✔
992
    }
993
}
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