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

tempestphp / tempest-framework / 11294471361

11 Oct 2024 02:47PM UTC coverage: 82.102% (-0.03%) from 82.134%
11294471361

Pull #556

github

web-flow
Merge 92f1b555e into c1233aa56
Pull Request #556: fix(phpstan): fix phpstan issues

5 of 13 new or added lines in 5 files covered. (38.46%)

1 existing line in 1 file now uncovered.

6757 of 8230 relevant lines covered (82.1%)

38.47 hits per line

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

94.55
/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(
373✔
40
        mixed $input = [],
41
    ) {
42
        if (is_array($input)) {
373✔
43
            $this->array = $input;
363✔
44
        } elseif ($input instanceof self) {
69✔
45
            $this->array = $input->array;
7✔
46
        } else {
47
            $this->array = [$input];
69✔
48
        }
49
    }
50

51
    /**
52
     * Get one or a specified number of random values from the array.
53
     *
54
     * @param int $number The number of random values to get.
55
     * @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 )
56
     *
57
     * @return self<TKey, TValue>|mixed The random values or single value if $number is 1.
58
     */
59
    public function random(int $number = 1, bool $preserveKey = false): mixed
5✔
60
    {
61
        $count = count($this->array);
5✔
62

63
        if ($number > $count) {
5✔
64
            throw new InvalidArgumentException("Cannot retrive {$number} items from an array of {$count} items.");
2✔
65
        }
66

67
        if ($number < 1) {
3✔
68
            throw new InvalidArgumentException("Random value only accepts positive integers, {$number} requested.");
1✔
69
        }
70

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

73
        $randomValues = [];
2✔
74
        foreach ($keys as $key) {
2✔
75
            $preserveKey
2✔
76
                ? $randomValues[$key] = $this->array[$key]
1✔
77
                : $randomValues[] = $this->array[$key];
1✔
78
        }
79

80
        if ($preserveKey === false) {
2✔
81
            shuffle($randomValues);
1✔
82
        }
83

84
        return count($randomValues) > 1
2✔
85
            ? new self($randomValues)
2✔
86
            : $randomValues[0];
2✔
87
    }
88

89
    /**
90
     * Retrieve values from a given key in each sub-array of the current array.
91
     * Optionally, you can pass a second parameter to also get the keys following the same pattern.
92
     *
93
     * @param string $value The key to assign the values from, support dot notation.
94
     * @param string|null $key The key to assign the keys from, support dot notation.
95
     *
96
     * @return self<TKey, TValue>
97
     */
98
    public function pluck(string $value, ?string $key = null): self
3✔
99
    {
100
        $results = [];
3✔
101

102
        foreach ($this->array as $item) {
3✔
103
            if (! is_array($item)) {
3✔
104
                continue;
1✔
105
            }
106

107
            $itemValue = arr($item)->get($value);
2✔
108

109
            /**
110
             * Perform basic pluck if no key is given.
111
             * Otherwise, also pluck the key as well.
112
             */
113
            if (is_null($key)) {
2✔
114
                $results[] = $itemValue;
2✔
115
            } else {
116
                $itemKey = arr($item)->get($key);
2✔
117
                $results[$itemKey] = $itemValue;
2✔
118
            }
119
        }
120

121
        return new self($results);
3✔
122
    }
123

124
    /**
125
     * @alias of add.
126
     */
127
    public function push(mixed $value): self
1✔
128
    {
129
        return $this->add($value);
1✔
130
    }
131

132
    /**
133
     * Add an item at the end of the array.
134
     *
135
     *
136
     * @return self<TKey, TValue>
137
     */
138
    public function add(mixed $value): self
2✔
139
    {
140
        $this->array[] = $value;
2✔
141

142
        return $this;
2✔
143
    }
144

145
    /**
146
     * Pad the array to the specified size with a value.
147
     *
148
     *
149
     * @return self<TKey, TValue>
150
     */
151
    public function pad(int $size, mixed $value): self
1✔
152
    {
153
        return new self(array_pad($this->array, $size, $value));
1✔
154
    }
155

156
    /**
157
     * Reverse the keys and values of the array.
158
     *
159
     * @return self<TValue&array-key, TKey>
160
     */
161
    public function flip(): self
1✔
162
    {
163
        return new self(array_flip($this->array));
1✔
164
    }
165

166
    /**
167
     * Keep only the unique items in the array.
168
     *
169
     * @param string|null $key The key to use as the uniqueness criteria in nested arrays.
170
     * @param bool $should_be_strict Whether the comparison should be strict, only used when giving a key parameter.
171
     *
172
     * @return self<TKey, TValue>
173
     */
174
    public function unique(?string $key = null, bool $should_be_strict = false): self
9✔
175
    {
176
        if (is_null($key) && $should_be_strict === false) {
9✔
177
            return new self(array_unique($this->array, flags: SORT_REGULAR));
4✔
178
        }
179

180
        $uniqueItems = [];
5✔
181
        $uniqueFilteredValues = [];
5✔
182
        foreach ($this->array as $item) {
5✔
183
            // Ensure we don't check raw values with key filter
184
            if (! is_null($key) && ! is_array($item)) {
5✔
185
                continue;
1✔
186
            }
187

188
            $filterValue = is_array($item)
5✔
189
                ? arr($item)->get($key)
4✔
190
                : $item;
1✔
191

192
            if (is_null($filterValue)) {
5✔
193
                continue;
×
194
            }
195

196
            if (in_array($filterValue, $uniqueFilteredValues, strict: $should_be_strict)) {
5✔
197
                continue;
4✔
198
            }
199

200
            $uniqueItems[] = $item;
5✔
201
            $uniqueFilteredValues[] = $filterValue;
5✔
202
        }
203

204
        return new self($uniqueItems);
5✔
205
    }
206

207
    /**
208
     * Keep only the items that are not present in any of the given arrays.
209
     *
210
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays
211
     *
212
     * @return self<TKey, TValue>
213
     */
214
    public function diff(array|self ...$arrays): self
1✔
215
    {
216
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
1✔
217

218
        return new self(array_diff($this->array, ...$arrays));
1✔
219
    }
220

221
    /**
222
     * Keep only the items whose keys are not present in any of the given arrays.
223
     *
224
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays
225
     *
226
     * @return self<TKey, TValue>
227
     */
228
    public function diffKeys(array|self ...$arrays): self
1✔
229
    {
230
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
1✔
231

232
        return new self(array_diff_key($this->array, ...$arrays));
1✔
233
    }
234

235
    /**
236
     * Keep only the items that are present in all of the given arrays.
237
     *
238
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays
239
     *
240
     * @return self<TKey, TValue>
241
     */
242
    public function intersect(array|self ...$arrays): self
1✔
243
    {
244
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
1✔
245

246
        return new self(array_intersect($this->array, ...$arrays));
1✔
247
    }
248

249
    /**
250
     * Keep only the items whose keys are present in all of the given arrays.
251
     *
252
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays
253
     *
254
     * @return self<TKey, TValue>
255
     */
256
    public function intersectKeys(array|self ...$arrays): self
1✔
257
    {
258
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
1✔
259

260
        return new self(array_intersect_key($this->array, ...$arrays));
1✔
261
    }
262

263
    /**
264
     * Merge the array with the given arrays.
265
     *
266
     * @param array<TKey, TValue>|self<TKey, TValue> ...$arrays The arrays to merge.
267
     *
268
     * @return self<TKey, TValue>
269
     */
270
    public function merge(array|self ...$arrays): self
2✔
271
    {
272
        $arrays = array_map(fn (array|self $array) => $array instanceof self ? $array->toArray() : $array, $arrays);
2✔
273

274
        return new self(array_merge($this->array, ...$arrays));
2✔
275
    }
276

277
    /**
278
     * Create a new array with this current array values as keys and the given values as values.
279
     *
280
     * @template TCombineValue
281
     *
282
     * @param array<array-key, TCombineValue>|self<array-key, TCombineValue> $values
283
     *
284
     * @return self<array-key, TCombineValue>
285
     */
286
    public function combine(array|self $values): self
4✔
287
    {
288
        $values = $values instanceof self
4✔
289
            ? $values->toArray()
1✔
290
            : $values;
3✔
291

292
        return new self(array_combine($this->array, $values));
4✔
293
    }
294

295
    public static function explode(string|Stringable $string, string $separator = ' '): self
2✔
296
    {
297
        if ($separator === '') {
2✔
298
            return new self([(string) $string]);
1✔
299
        }
300

301
        return new self(explode($separator, (string) $string));
2✔
302
    }
303

304
    public function equals(array|self $other): bool
13✔
305
    {
306
        $other = is_array($other) ? $other : $other->array;
13✔
307

308
        return $this->array === $other;
13✔
309
    }
310

311
    /** @param Closure(mixed $value, mixed $key): bool $filter */
312
    public function first(?Closure $filter = null): mixed
8✔
313
    {
314
        if ($filter === null) {
8✔
315
            return $this->array[array_key_first($this->array)];
2✔
316
        }
317

318
        foreach ($this as $key => $value) {
6✔
319
            if ($filter($value, $key)) {
6✔
320
                return $value;
6✔
321
            }
322
        }
323

324
        return null;
6✔
325
    }
326

327
    /** @param Closure(mixed $value, mixed $key): bool $filter */
328
    public function last(?Closure $filter = null): mixed
10✔
329
    {
330
        if ($filter === null) {
10✔
331
            return $this->array[array_key_last($this->array)];
10✔
332
        }
333

334
        foreach ($this->reverse() as $key => $value) {
×
335
            if ($filter($value, $key)) {
×
336
                return $value;
×
337
            }
338
        }
339

340
        return null;
×
341
    }
342

343
    /** @param mixed $value The popped value will be stored in this variable */
344
    public function pop(mixed &$value): self
9✔
345
    {
346
        $value = $this->last();
9✔
347

348
        return new self(array_slice($this->array, 0, -1));
9✔
349
    }
350

351
    /** @param mixed $value The unshifted value will be stored in this variable */
352
    public function unshift(mixed &$value): self
1✔
353
    {
354
        $value = $this->first();
1✔
355

356
        return new self(array_slice($this->array, 1));
1✔
357
    }
358

359
    public function reverse(): self
1✔
360
    {
361
        return new self(array_reverse($this->array));
1✔
362
    }
363

364
    public function isEmpty(): bool
9✔
365
    {
366
        return empty($this->array);
9✔
367
    }
368

369
    public function isNotEmpty(): bool
8✔
370
    {
371
        return ! $this->isEmpty();
8✔
372
    }
373

374
    public function implode(string $glue): StringHelper
51✔
375
    {
376
        return str(implode($glue, $this->array));
51✔
377
    }
378

379
    /**
380
     * Create a new array with the keys of this array as values.
381
     *
382
     * @return self<array-key, TKey>
383
     */
384
    public function keys(): self
1✔
385
    {
386
        return new self(array_keys($this->array));
1✔
387
    }
388

389
    public function values(): self
289✔
390
    {
391
        return new self(array_values($this->array));
289✔
392
    }
393

394
    /** @param null|Closure(mixed $value, mixed $key): bool $filter */
395
    public function filter(?Closure $filter = null): self
1✔
396
    {
397
        $array = [];
1✔
398
        $filter ??= static fn (mixed $value, mixed $_) => ! in_array($value, [false, null], strict: true);
1✔
399

400
        foreach ($this->array as $key => $value) {
1✔
401
            if ($filter($value, $key)) {
1✔
402
                $array[$key] = $value;
1✔
403
            }
404
        }
405

406
        return new self($array);
1✔
407
    }
408

409
    /** @param Closure(mixed $value, mixed $key): void $each */
410
    public function each(Closure $each): self
53✔
411
    {
412
        foreach ($this as $key => $value) {
53✔
413
            $each($value, $key);
51✔
414
        }
415

416
        return $this;
53✔
417
    }
418

419
    /** @param Closure(mixed $value, mixed $key): mixed $map */
420
    public function map(Closure $map): self
282✔
421
    {
422
        $array = [];
282✔
423

424
        foreach ($this->array as $key => $value) {
282✔
425
            $array[$key] = $map($value, $key);
282✔
426
        }
427

428
        return new self($array);
282✔
429
    }
430

431
    /** @param Closure(mixed $value, mixed $key): Generator $map */
432
    public function mapWithKeys(Closure $map): self
3✔
433
    {
434
        $array = [];
3✔
435

436
        foreach ($this->array as $key => $value) {
3✔
437
            $generator = $map($value, $key);
3✔
438

439
            if (! $generator instanceof Generator) {
3✔
440
                throw new InvalidMapWithKeysUsage();
1✔
441
            }
442

443
            $array[$generator->key()] = $generator->current();
2✔
444
        }
445

446
        return new self($array);
2✔
447
    }
448

449
    /** @return mixed|ArrayHelper */
450
    public function get(string $key, mixed $default = null): mixed
292✔
451
    {
452
        $value = $this->array;
292✔
453

454
        $keys = explode('.', $key);
292✔
455

456
        foreach ($keys as $key) {
292✔
457
            if (! isset($value[$key])) {
292✔
458
                return $default;
12✔
459
            }
460

461
            $value = $value[$key];
290✔
462
        }
463

464
        if (is_array($value)) {
290✔
465
            return new self($value);
280✔
466
        }
467

468
        return $value;
14✔
469
    }
470

471
    public function has(string $key): bool
1✔
472
    {
473
        $array = $this->array;
1✔
474

475
        $keys = explode('.', $key);
1✔
476

477
        foreach ($keys as $key) {
1✔
478
            if (! isset($array[$key])) {
1✔
479
                return false;
1✔
480
            }
481

482
            $array = &$array[$key];
1✔
483
        }
484

485
        return true;
1✔
486
    }
487

488
    public function contains(mixed $search): bool
1✔
489
    {
490
        return $this->first(fn ($value) => $value === $search) !== null;
1✔
491
    }
492

493
    public function set(string $key, mixed $value): self
2✔
494
    {
495
        $array = $this->array;
2✔
496

497
        $current = &$array;
2✔
498

499
        $keys = explode('.', $key);
2✔
500

501
        foreach ($keys as $i => $key) {
2✔
502
            // If this is the last key in dot notation, we don't
503
            // need to go through the next steps.
504
            if (count($keys) === 1) {
2✔
505
                break;
2✔
506
            }
507

508
            // Remove the current key from our keys array
509
            // so that later we can use the first value
510
            // from that array as our key.
511
            unset($keys[$i]);
2✔
512

513
            // If we know this key is not an array, make it one.
514
            if (! isset($current[$key]) || ! is_array($current[$key])) {
2✔
515
                $current[$key] = [];
2✔
516
            }
517

518
            // Set the context to this key.
519
            $current = &$current[$key];
2✔
520
        }
521

522
        // Pull the first key out of the array
523
        // and use it to set the value.
524
        $current[array_shift($keys)] = $value;
2✔
525

526
        return new self($array);
2✔
527
    }
528

529
    /**
530
     * @alias self::set()
531
     */
532
    public function put(string $key, mixed $value): self
1✔
533
    {
534
        return $this->set($key, $value);
1✔
535
    }
536

537
    public function unwrap(): self
86✔
538
    {
539
        $unwrapValue = function (string|int $key, mixed $value) {
86✔
540
            if (is_int($key)) {
82✔
541
                return [$key => $value];
×
542
            }
543

544
            $keys = explode('.', $key);
82✔
545

546
            for ($i = array_key_last($keys); $i >= 0; $i--) {
82✔
547
                $currentKey = $keys[$i];
82✔
548

549
                $value = [$currentKey => $value];
82✔
550
            }
551

552
            return $value;
82✔
553
        };
86✔
554

555
        $array = [];
86✔
556

557
        foreach ($this->array as $key => $value) {
86✔
558
            $array = array_merge_recursive($array, $unwrapValue($key, $value));
82✔
559
        }
560

561
        return new self($array);
86✔
562
    }
563

564
    public function dump(mixed ...$dumps): self
×
565
    {
NEW
566
        lw($this->array, ...$dumps);
×
567

568
        return $this;
×
569
    }
570

571
    public function dd(mixed ...$dd): void
×
572
    {
NEW
573
        ld($this->array, ...$dd);
×
574
    }
575

576
    /**
577
     * @return array<TKey, TValue>
578
     */
579
    public function toArray(): array
313✔
580
    {
581
        return $this->array;
313✔
582
    }
583

584
    /**
585
     * @template T
586
     * @param class-string<T> $to
587
     * @return self<T>
588
     */
589
    public function mapTo(string $to): self
1✔
590
    {
591
        return new self(map($this->array)->collection()->to($to));
1✔
592
    }
593
}
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

© 2025 Coveralls, Inc