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

tempestphp / tempest-framework / 11310141859

12 Oct 2024 06:15PM UTC coverage: 82.1% (-0.03%) from 82.134%
11310141859

push

github

web-flow
chore: local defined variables cleanup (#580)

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

62 existing lines in 6 files now uncovered.

6770 of 8246 relevant lines covered (82.1%)

38.42 hits per line

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

94.66
/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
    /**
30
     * The underlying array.
31
     *
32
     * @var array<TKey, TValue>
33
     */
34
    private array $array;
35

36
    /**
37
     * @param array<TKey, TValue>|self<TKey, TValue>|TValue $input
38
     */
39
    public function __construct(
381✔
40
        mixed $input = [],
41
    ) {
42
        if (is_array($input)) {
381✔
43
            $this->array = $input;
367✔
44
        } elseif ($input instanceof self) {
74✔
45
            $this->array = $input->array;
7✔
46
        } else {
47
            $this->array = [$input];
74✔
48
        }
49
    }
50

51
    /**
52
     * Determines if the array is a list.
53
     *
54
     * An array is a list if its keys consist of consecutive numbers.
55
     */
56
    public function isList(): bool
2✔
57
    {
58
        return array_is_list($this->array);
2✔
59
    }
60

61
    /**
62
     * Determines if the array is an associative.
63
     *
64
     * An array is associative if its keys doesn't consist of consecutive numbers.
65
     */
66
    public function isAssoc(): bool
1✔
67
    {
68
        return ! $this->isList();
1✔
69
    }
70

71
    /**
72
     * Get one or a specified number of random values from the array.
73
     *
74
     * @param int $number The number of random values to get.
75
     * @param bool $preserveKey Whether to preserve the keys of the original array. ( won't work if $number is 1 as it will return a single value )
76
     *
77
     * @return self<TKey, TValue>|mixed The random values or single value if $number is 1.
78
     */
79
    public function random(int $number = 1, bool $preserveKey = false): mixed
5✔
80
    {
81
        $count = count($this->array);
5✔
82

83
        if ($number > $count) {
5✔
84
            throw new InvalidArgumentException("Cannot retrive {$number} items from an array of {$count} items.");
2✔
85
        }
86

87
        if ($number < 1) {
3✔
88
            throw new InvalidArgumentException("Random value only accepts positive integers, {$number} requested.");
1✔
89
        }
90

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

93
        $randomValues = [];
2✔
94
        foreach ($keys as $key) {
2✔
95
            $preserveKey
2✔
96
                ? $randomValues[$key] = $this->array[$key]
1✔
97
                : $randomValues[] = $this->array[$key];
1✔
98
        }
99

100
        if ($preserveKey === false) {
2✔
101
            shuffle($randomValues);
1✔
102
        }
103

104
        return count($randomValues) > 1
2✔
105
            ? new self($randomValues)
2✔
106
            : $randomValues[0];
2✔
107
    }
108

109
    /**
110
     * Retrieve values from a given key in each sub-array of the current array.
111
     * Optionally, you can pass a second parameter to also get the keys following the same pattern.
112
     *
113
     * @param string $value The key to assign the values from, support dot notation.
114
     * @param string|null $key The key to assign the keys from, support dot notation.
115
     *
116
     * @return self<TKey, TValue>
117
     */
118
    public function pluck(string $value, ?string $key = null): self
3✔
119
    {
120
        $results = [];
3✔
121

122
        foreach ($this->array as $item) {
3✔
123
            if (! is_array($item)) {
3✔
124
                continue;
1✔
125
            }
126

127
            $itemValue = arr($item)->get($value);
2✔
128

129
            /**
130
             * Perform basic pluck if no key is given.
131
             * Otherwise, also pluck the key as well.
132
             */
133
            if (is_null($key)) {
2✔
134
                $results[] = $itemValue;
2✔
135
            } else {
136
                $itemKey = arr($item)->get($key);
2✔
137
                $results[$itemKey] = $itemValue;
2✔
138
            }
139
        }
140

141
        return new self($results);
3✔
142
    }
143

144
    /**
145
     * @alias of add.
146
     */
147
    public function push(mixed $value): self
1✔
148
    {
149
        return $this->add($value);
1✔
150
    }
151

152
    /**
153
     * Add an item at the end of the array.
154
     *
155
     *
156
     * @return self<TKey, TValue>
157
     */
158
    public function add(mixed $value): self
2✔
159
    {
160
        $this->array[] = $value;
2✔
161

162
        return $this;
2✔
163
    }
164

165
    /**
166
     * Pad the array to the specified size with a value.
167
     *
168
     *
169
     * @return self<TKey, TValue>
170
     */
171
    public function pad(int $size, mixed $value): self
1✔
172
    {
173
        return new self(array_pad($this->array, $size, $value));
1✔
174
    }
175

176
    /**
177
     * Reverse the keys and values of the array.
178
     *
179
     * @return self<TValue&array-key, TKey>
180
     */
181
    public function flip(): self
1✔
182
    {
183
        return new self(array_flip($this->array));
1✔
184
    }
185

186
    /**
187
     * Keep only the unique items in the array.
188
     *
189
     * @param string|null $key The key to use as the uniqueness criteria in nested arrays.
190
     * @param bool $should_be_strict Whether the comparison should be strict, only used when giving a key parameter.
191
     *
192
     * @return self<TKey, TValue>
193
     */
194
    public function unique(?string $key = null, bool $should_be_strict = false): self
9✔
195
    {
196
        if (is_null($key) && $should_be_strict === false) {
9✔
197
            return new self(array_unique($this->array, flags: SORT_REGULAR));
4✔
198
        }
199

200
        $uniqueItems = [];
5✔
201
        $uniqueFilteredValues = [];
5✔
202
        foreach ($this->array as $item) {
5✔
203
            // Ensure we don't check raw values with key filter
204
            if (! is_null($key) && ! is_array($item)) {
5✔
205
                continue;
1✔
206
            }
207

208
            $filterValue = is_array($item)
5✔
209
                ? arr($item)->get($key)
4✔
210
                : $item;
1✔
211

212
            if (is_null($filterValue)) {
5✔
UNCOV
213
                continue;
×
214
            }
215

216
            if (in_array($filterValue, $uniqueFilteredValues, strict: $should_be_strict)) {
5✔
217
                continue;
4✔
218
            }
219

220
            $uniqueItems[] = $item;
5✔
221
            $uniqueFilteredValues[] = $filterValue;
5✔
222
        }
223

224
        return new self($uniqueItems);
5✔
225
    }
226

227
    /**
228
     * Keep only the items that are not present in any of the given arrays.
229
     *
230
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays
231
     *
232
     * @return self<TKey, TValue>
233
     */
234
    public function diff(array|self ...$arrays): self
1✔
235
    {
236
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
1✔
237

238
        return new self(array_diff($this->array, ...$arrays));
1✔
239
    }
240

241
    /**
242
     * Keep only the items whose keys are not present in any of the given arrays.
243
     *
244
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays
245
     *
246
     * @return self<TKey, TValue>
247
     */
248
    public function diffKeys(array|self ...$arrays): self
1✔
249
    {
250
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
1✔
251

252
        return new self(array_diff_key($this->array, ...$arrays));
1✔
253
    }
254

255
    /**
256
     * Keep only the items that are present in all of the given arrays.
257
     *
258
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays
259
     *
260
     * @return self<TKey, TValue>
261
     */
262
    public function intersect(array|self ...$arrays): self
1✔
263
    {
264
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
1✔
265

266
        return new self(array_intersect($this->array, ...$arrays));
1✔
267
    }
268

269
    /**
270
     * Keep only the items whose keys are present in all of the given arrays.
271
     *
272
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays
273
     *
274
     * @return self<TKey, TValue>
275
     */
276
    public function intersectKeys(array|self ...$arrays): self
1✔
277
    {
278
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
1✔
279

280
        return new self(array_intersect_key($this->array, ...$arrays));
1✔
281
    }
282

283
    /**
284
     * Merge the array with the given arrays.
285
     *
286
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays The arrays to merge.
287
     *
288
     * @return self<TKey, TValue>
289
     */
290
    public function merge(array|self ...$arrays): self
2✔
291
    {
292
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
2✔
293

294
        return new self(array_merge($this->array, ...$arrays));
2✔
295
    }
296

297
    /**
298
     * Create a new array with this current array values as keys and the given values as values.
299
     *
300
     * @template TCombineValue
301
     *
302
     * @param array<array-key, TCombineValue>|self<array-key, TCombineValue> $values
303
     *
304
     * @return self<array-key, TCombineValue>
305
     */
306
    public function combine(array|self $values): self
4✔
307
    {
308
        $values = $values instanceof self
4✔
309
            ? $values->toArray()
1✔
310
            : $values;
3✔
311

312
        return new self(array_combine($this->array, $values));
4✔
313
    }
314

315
    public static function explode(string|Stringable $string, string $separator = ' '): self
2✔
316
    {
317
        if ($separator === '') {
2✔
318
            return new self([(string) $string]);
1✔
319
        }
320

321
        return new self(explode($separator, (string) $string));
2✔
322
    }
323

324
    public function equals(array|self $other): bool
13✔
325
    {
326
        $other = is_array($other) ? $other : $other->array;
13✔
327

328
        return $this->array === $other;
13✔
329
    }
330

331
    /** @param Closure(mixed $value, mixed $key): bool $filter */
332
    public function first(?Closure $filter = null): mixed
8✔
333
    {
334
        if ($filter === null) {
8✔
335
            return $this->array[array_key_first($this->array)];
2✔
336
        }
337

338
        foreach ($this as $key => $value) {
6✔
339
            if ($filter($value, $key)) {
6✔
340
                return $value;
6✔
341
            }
342
        }
343

344
        return null;
6✔
345
    }
346

347
    /** @param Closure(mixed $value, mixed $key): bool $filter */
348
    public function last(?Closure $filter = null): mixed
10✔
349
    {
350
        if ($filter === null) {
10✔
351
            return $this->array[array_key_last($this->array)];
10✔
352
        }
353

UNCOV
354
        foreach ($this->reverse() as $key => $value) {
×
UNCOV
355
            if ($filter($value, $key)) {
×
UNCOV
356
                return $value;
×
357
            }
358
        }
359

UNCOV
360
        return null;
×
361
    }
362

363
    /** @param mixed $value The popped value will be stored in this variable */
364
    public function pop(mixed &$value): self
9✔
365
    {
366
        $value = $this->last();
9✔
367

368
        return new self(array_slice($this->array, 0, -1));
9✔
369
    }
370

371
    /** @param mixed $value The unshifted value will be stored in this variable */
372
    public function unshift(mixed &$value): self
1✔
373
    {
374
        $value = $this->first();
1✔
375

376
        return new self(array_slice($this->array, 1));
1✔
377
    }
378

379
    public function reverse(): self
1✔
380
    {
381
        return new self(array_reverse($this->array));
1✔
382
    }
383

384
    public function isEmpty(): bool
9✔
385
    {
386
        return empty($this->array);
9✔
387
    }
388

389
    public function isNotEmpty(): bool
8✔
390
    {
391
        return ! $this->isEmpty();
8✔
392
    }
393

394
    public function implode(string $glue): StringHelper
51✔
395
    {
396
        return str(implode($glue, $this->array));
51✔
397
    }
398

399
    /**
400
     * Create a new array with the keys of this array as values.
401
     *
402
     * @return self<array-key, TKey>
403
     */
404
    public function keys(): self
1✔
405
    {
406
        return new self(array_keys($this->array));
1✔
407
    }
408

409
    public function values(): self
289✔
410
    {
411
        return new self(array_values($this->array));
289✔
412
    }
413

414
    /** @param null|Closure(mixed $value, mixed $key): bool $filter */
415
    public function filter(?Closure $filter = null): self
1✔
416
    {
417
        $array = [];
1✔
418
        $filter ??= static fn (mixed $value, mixed $_) => ! in_array($value, [false, null], strict: true);
1✔
419

420
        foreach ($this->array as $key => $value) {
1✔
421
            if ($filter($value, $key)) {
1✔
422
                $array[$key] = $value;
1✔
423
            }
424
        }
425

426
        return new self($array);
1✔
427
    }
428

429
    /** @param Closure(mixed $value, mixed $key): void $each */
430
    public function each(Closure $each): self
53✔
431
    {
432
        foreach ($this as $key => $value) {
53✔
433
            $each($value, $key);
51✔
434
        }
435

436
        return $this;
53✔
437
    }
438

439
    /** @param Closure(mixed $value, mixed $key): mixed $map */
440
    public function map(Closure $map): self
283✔
441
    {
442
        $array = [];
283✔
443

444
        foreach ($this->array as $key => $value) {
283✔
445
            $array[$key] = $map($value, $key);
283✔
446
        }
447

448
        return new self($array);
283✔
449
    }
450

451
    /** @param Closure(mixed $value, mixed $key): Generator $map */
452
    public function mapWithKeys(Closure $map): self
3✔
453
    {
454
        $array = [];
3✔
455

456
        foreach ($this->array as $key => $value) {
3✔
457
            $generator = $map($value, $key);
3✔
458

459
            if (! $generator instanceof Generator) {
3✔
460
                throw new InvalidMapWithKeysUsage();
1✔
461
            }
462

463
            $array[$generator->key()] = $generator->current();
2✔
464
        }
465

466
        return new self($array);
2✔
467
    }
468

469
    /** @return mixed|ArrayHelper */
470
    public function get(string $key, mixed $default = null): mixed
292✔
471
    {
472
        $value = $this->array;
292✔
473

474
        $keys = explode('.', $key);
292✔
475

476
        foreach ($keys as $key) {
292✔
477
            if (! isset($value[$key])) {
292✔
478
                return $default;
12✔
479
            }
480

481
            $value = $value[$key];
290✔
482
        }
483

484
        if (is_array($value)) {
290✔
485
            return new self($value);
280✔
486
        }
487

488
        return $value;
14✔
489
    }
490

491
    public function has(string $key): bool
1✔
492
    {
493
        $array = $this->array;
1✔
494

495
        $keys = explode('.', $key);
1✔
496

497
        foreach ($keys as $key) {
1✔
498
            if (! isset($array[$key])) {
1✔
499
                return false;
1✔
500
            }
501

502
            $array = &$array[$key];
1✔
503
        }
504

505
        return true;
1✔
506
    }
507

508
    public function contains(mixed $search): bool
1✔
509
    {
510
        return $this->first(fn ($value) => $value === $search) !== null;
1✔
511
    }
512

513
    public function set(string $key, mixed $value): self
2✔
514
    {
515
        $array = $this->array;
2✔
516

517
        $current = &$array;
2✔
518

519
        $keys = explode('.', $key);
2✔
520

521
        foreach ($keys as $i => $key) {
2✔
522
            // If this is the last key in dot notation, we don't
523
            // need to go through the next steps.
524
            if (count($keys) === 1) {
2✔
525
                break;
2✔
526
            }
527

528
            // Remove the current key from our keys array
529
            // so that later we can use the first value
530
            // from that array as our key.
531
            unset($keys[$i]);
2✔
532

533
            // If we know this key is not an array, make it one.
534
            if (! isset($current[$key]) || ! is_array($current[$key])) {
2✔
535
                $current[$key] = [];
2✔
536
            }
537

538
            // Set the context to this key.
539
            $current = &$current[$key];
2✔
540
        }
541

542
        // Pull the first key out of the array
543
        // and use it to set the value.
544
        $current[array_shift($keys)] = $value;
2✔
545

546
        return new self($array);
2✔
547
    }
548

549
    /**
550
     * @alias self::set()
551
     */
552
    public function put(string $key, mixed $value): self
1✔
553
    {
554
        return $this->set($key, $value);
1✔
555
    }
556

557
    public function unwrap(): self
86✔
558
    {
559
        $unwrapValue = function (string|int $key, mixed $value) {
86✔
560
            if (is_int($key)) {
82✔
UNCOV
561
                return [$key => $value];
×
562
            }
563

564
            $keys = explode('.', $key);
82✔
565

566
            for ($i = array_key_last($keys); $i >= 0; $i--) {
82✔
567
                $currentKey = $keys[$i];
82✔
568

569
                $value = [$currentKey => $value];
82✔
570
            }
571

572
            return $value;
82✔
573
        };
86✔
574

575
        $array = [];
86✔
576

577
        foreach ($this->array as $key => $value) {
86✔
578
            $array = array_merge_recursive($array, $unwrapValue($key, $value));
82✔
579
        }
580

581
        return new self($array);
86✔
582
    }
583

UNCOV
584
    public function dump(mixed ...$dumps): self
×
585
    {
UNCOV
586
        lw($this->array, ...$dumps);
×
587

UNCOV
588
        return $this;
×
589
    }
590

UNCOV
591
    public function dd(mixed ...$dd): void
×
592
    {
UNCOV
593
        ld($this->array, ...$dd);
×
594
    }
595

596
    /**
597
     * @return array<TKey, TValue>
598
     */
599
    public function toArray(): array
313✔
600
    {
601
        return $this->array;
313✔
602
    }
603

604
    /**
605
     * @template T
606
     * @param class-string<T> $to
607
     * @return self<T>
608
     */
609
    public function mapTo(string $to): self
1✔
610
    {
611
        return new self(map($this->array)->collection()->to($to));
1✔
612
    }
613
}
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