Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.65% covered (success)
97.65%
83 / 85
92.00% covered (success)
92.00%
23 / 25
CRAP
0.00% covered (danger)
0.00%
0 / 1
Arr
97.65% covered (success)
97.65%
83 / 85
92.00% covered (success)
92.00%
23 / 25
48
0.00% covered (danger)
0.00%
0 / 1
 isNullOrEmpty
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 tryGet
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 getCount
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 containsKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 containsValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getKeys
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getValues
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 in
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFirstKey
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getLastKey
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 isListImpl
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 isList
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 toUnique
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 replace
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getRandomKeys
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 reverse
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 flip
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 map
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 range
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 repeat
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 sortByValue
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 sortByKey
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 sortNaturalByValue
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 sortCallbackByValue
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 sortCallbackByKey
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace PeServer\Core\Collection;
6
7use Countable;
8use TypeError;
9use PeServer\Core\Collection\OrderBy;
10use PeServer\Core\Cryptography;
11use PeServer\Core\Errors\ErrorHandler;
12use PeServer\Core\Throws\ArgumentException;
13use PeServer\Core\Throws\InvalidOperationException;
14use PeServer\Core\Throws\KeyNotFoundException;
15use PeServer\Core\TypeUtility;
16
17/**
18 * 配列共通処理。
19 *
20 * 遅延処理が必要な場合 `Collections` を参照のこと。
21 *
22 * @see \PeServer\Core\Collection\Collections
23 */
24class Arr
25{
26    #region function
27
28    /**
29     * 配列が `null` か空か。
30     *
31     * @param array<mixed>|null $array 対象配列。
32     * @return boolean `null` か空の場合に真。
33     * @phpstan-assert-if-false non-empty-array $array
34     */
35    public static function isNullOrEmpty(?array $array): bool
36    {
37        if ($array === null) {
38            return true;
39        }
40
41        return empty($array);
42    }
43
44    /**
45     * 配列から値を取得する。
46     *
47     * @template TValue
48     * @param array<int|string,mixed>|null $array 対象配列。
49     * @phpstan-param array<array-key,TValue>|null $array
50     * @param int|string $key キー。
51     * @phpstan-param array-key $key
52     * @param mixed $result 値を格納する変数。
53     * @phpstan-param TValue $result
54     * @return boolean 値が存在したか。
55     */
56    public static function tryGet(?array $array, int|string $key, mixed &$result): bool
57    {
58        if ($array !== null && isset($array[$key])) {
59            $result = $array[$key];
60            return true;
61        }
62
63        return false;
64    }
65
66    /**
67     * 配列の件数を取得。
68     *
69     * @param array<mixed>|Countable|null $array 対象配列。
70     * @return int 件数。
71     * @phpstan-return non-negative-int
72     * @see https://www.php.net/manual/function.count.php
73     */
74    public static function getCount(array|Countable|null $array): int
75    {
76        if ($array === null) {
77            return 0;
78        }
79
80        return count($array);
81    }
82
83    /**
84     * 配列に該当キーは存在するか。
85     *
86     * `array_key_exists` ラッパー。
87     *
88     * @param array<mixed> $haystack 対象配列。
89     * @phpstan-param array<array-key,mixed> $haystack
90     * @param int|string $key キー。
91     * @phpstan-param array-key $key
92     * @return bool
93     * @see https://www.php.net/manual/function.array-key-exists.php
94     */
95    public static function containsKey(array $haystack, int|string $key): bool
96    {
97        return array_key_exists($key, $haystack);
98    }
99
100    /**
101     * 配列に指定要素が存在するか。
102     *
103     * `array_search` ラッパー。
104     *
105     * @template TValue
106     * @param array<mixed> $haystack 対象配列。
107     * @phpstan-param TValue[] $haystack
108     * @param mixed $needle 検索データ。
109     * @phpstan-param TValue $needle
110     * @return bool
111     * @see https://www.php.net/manual/function.array-search.php
112     */
113    public static function containsValue(array $haystack, mixed $needle): bool
114    {
115        return array_search($needle, $haystack) !== false;
116    }
117
118    /**
119     * 対象配列のキー一覧を取得。
120     *
121     * `array_keys` ラッパー。
122     *
123     * @param array<int|string,mixed> $array 対象配列。
124     * @phpstan-param array<array-key,mixed> $array
125     * @return array<int|string>
126     * @phpstan-return list<array-key>
127     * @see https://www.php.net/manual/function.array-keys.php
128     */
129    public static function getKeys(array $array): array
130    {
131        return array_keys($array);
132    }
133
134    /**
135     * 対象配列の値一覧を取得。
136     *
137     * `array_values` ラッパー。
138     *
139     * @template TValue
140     * @param array<mixed> $array 対象配列。
141     * @phpstan-param array<array-key,TValue> $array
142     * @return array<mixed>
143     * @phpstan-return list<TValue>
144     * @see https://www.php.net/manual/function.array-values.php
145     */
146    public static function getValues(array $array): array
147    {
148        return array_values($array);
149    }
150
151    /**
152     * `in_array` ラッパー。
153     *
154     * @template TValue
155     * @param array<int|string,mixed> $haystack 対象配列。
156     * @phpstan-param array<array-key,TValue> $haystack
157     * @param mixed $needle
158     * @phpstan-param TValue $needle
159     * @return boolean
160     * @see https://www.php.net/manual/function.in-array.php
161     */
162    public static function in(array $haystack, mixed $needle): bool
163    {
164        return in_array($needle, $haystack, true);
165    }
166
167    /**
168     * `array_key_first` ラッパー。
169     *
170     * @param array<mixed> $array 対象配列。
171     * @return int|string
172     * @phpstan-return array-key
173     * @see https://www.php.net/manual/function.array-key-first.php
174     * @throws KeyNotFoundException
175     */
176    public static function getFirstKey(array $array): int|string
177    {
178        $result = array_key_first($array);
179        if ($result === null) {
180            throw new KeyNotFoundException();
181        }
182
183        return $result;
184    }
185
186    /**
187     * `array_key_last` ラッパー。
188     *
189     * @param array<mixed> $array 対象配列。
190     * @return int|string
191     * @phpstan-return array-key
192     * @see https://www.php.net/manual/function.array-key-last.php
193     * @throws KeyNotFoundException
194     */
195    public static function getLastKey(array $array): int|string
196    {
197        $result = array_key_last($array);
198        if ($result === null) {
199            throw new KeyNotFoundException();
200        }
201
202        return $result;
203    }
204
205    /**
206     * `isList` 実装。
207     *
208     * @param array<mixed> $array 対象配列。
209     * @return bool
210     * @phpstan-assert-if-true list $array
211     */
212    public static function isListImpl(array $array): bool
213    {
214        // https://www.php.net/manual/function.array-is-list.php#127044
215        $i = 0;
216        foreach ($array as $k => $v) {
217            if ($k !== $i++) {
218                return false;
219            }
220        }
221        return true;
222    }
223
224    /**
225     * `array_is_list` ラッパー。
226     *
227     * @param array<mixed> $array 対象配列。
228     * @return bool
229     * @see https://www.php.net/manual/function.array-is-list.php#127044
230     * @phpstan-assert-if-true list $array
231     */
232    public static function isList(array $array): bool
233    {
234        $function = 'array_is_list'; // ignore intelephense(1010)
235        if (function_exists($function)) {
236            return $function($array);
237        }
238
239        return self::isListImpl($array);
240    }
241
242    /**
243     * `array_unique` ラッパー。
244     *
245     * @template TKey of array-key
246     * @template TValue
247     * @param array<mixed> $array 対象配列。
248     * @phpstan-param array<TKey,TValue> $array
249     * @return array<mixed>
250     * @see https://www.php.net/manual/function.array-unique.php
251     */
252    public static function toUnique(array $array): array
253    {
254        return array_unique($array, SORT_REGULAR);
255    }
256
257    /**
258     * `array_replace(_recursive)` ラッパー。
259     *
260     * @param array<mixed> $base 元になる配列。
261     * @param array<mixed> $overwrite 上書きする配列。
262     * @param bool $recursive 再帰的置き換えを行うか(`_recursive`呼び出し)。
263     * @return array<mixed>
264     * @see https://www.php.net/manual/function.array-replace-recursive.php
265     * @see https://www.php.net/manual/function.array-replace.php
266     */
267    public static function replace(array $base, array $overwrite, bool $recursive = true): array
268    {
269        if ($recursive) {
270            return array_replace_recursive($base, $overwrite);
271        }
272
273        return array_replace($base, $overwrite);
274    }
275
276    /**
277     * キー項目をランダム取得。
278     *
279     * @param array<mixed> $array 対象配列。
280     * @phpstan-param non-empty-array<mixed> $array
281     * @param int $count 取得数。
282     * @phpstan-param positive-int $count
283     * @return array<string|int>
284     * @phpstan-return list<array-key>
285     * @throws ArgumentException
286     */
287    public static function getRandomKeys(array $array, int $count): array
288    {
289        if ($count < 1) { //@phpstan-ignore-line [DOCTYPE]
290            throw new ArgumentException('$count');
291        }
292
293        $length = self::getCount($array);
294        if ($length < $count) {
295            throw new ArgumentException('$length < $count');
296        }
297
298        $result = [];
299        $keys = self::getKeys($array);
300        for ($i = 0; $i < $count; $i++) {
301            $index = Cryptography::generateRandomInteger(0, $length - 1);
302            $result[] = $keys[$index];
303        }
304
305        return $result;
306    }
307
308    /**
309     * `reverse` ラッパー。
310     *
311     * @template TKey of array-key
312     * @template TValue
313     * @param array<mixed> $input 対象配列。
314     * @phpstan-param array<TKey,TValue> $input
315     * @return array<mixed>
316     * @phpstan-return array<TKey,TValue>
317     * @see https://www.php.net/manual/function.array-reverse.php
318     */
319    public static function reverse(array $input): array
320    {
321        return array_reverse($input);
322    }
323
324    /**
325     * `array_flip` ラッパー。
326     *
327     * @template TKey of array-key
328     * @template TValue
329     * @param array<mixed> $input 対象配列。
330     * @phpstan-param array<TKey,TValue> $input
331     * @return array<mixed>
332     * @phpstan-param array<TValue,TKey> $input
333     * @throws ArgumentException
334     * @see https://www.php.net/manual/function.array-flip.php
335     */
336    public static function flip(array $input): array
337    {
338        $result = ErrorHandler::trap(fn () => array_flip($input));
339        if (!$result->success) {
340            throw new ArgumentException();
341        }
342        return $result->value;
343    }
344
345    /**
346     * `array_map` 的なことを行う非ラッパー処理。
347     *
348     * `array_map` がもうなんか順序もコールバック引数も何もかも怖い。
349     *
350     * @template TValue
351     * @template TResult
352     * @param array<mixed> $input 対象配列。
353     * @phpstan-param array<array-key,TValue> $input
354     * @param callable $callback
355     * @phpstan-param callable(TValue,array-key): TResult $callback
356     * @return array<mixed>
357     * @phpstan-return array<array-key,TResult>
358     */
359    public static function map(array $input, callable $callback): array
360    {
361        /** @phpstan-var array<array-key,TResult> */
362        $result = [];
363
364        foreach ($input as $key => $value) {
365            $result[$key] = $callback($value, $key);
366        }
367
368        return $result;
369    }
370
371    /**
372     * 指定した範囲内の整数から配列生成。
373     *
374     * PHP の `range` とは指定方法が違うことに注意。
375     *
376     * @param int $start 開始。
377     * @param int $count 件数。
378     * @phpstan-param non-negative-int $count
379     * @return self
380     * @phpstan-return list<int>
381     */
382    public static function range(int $start, int $count): array
383    {
384        if ($count < 0) { //@phpstan-ignore-line [DOCTYPE]
385            throw new ArgumentException('$count');
386        }
387
388        if ($count === 0) {
389            return [];
390        }
391
392        return \range($start, $start + $count - 1);
393    }
394
395    /**
396     * 繰り返される配列を生成。
397     *
398     * `array_fill` ラッパー。
399     *
400     * @template TRepeatValue
401     * @param mixed $value 値。
402     * @phpstan-param TRepeatValue $value
403     * @param int $count 件数。
404     * @phpstan-param non-negative-int $count
405     * @return self
406     * @phpstan-return list<TRepeatValue>
407     * @see https://www.php.net/manual/function.array-fill.php
408     */
409    public static function repeat(mixed $value, int $count): array
410    {
411        if ($count < 0) { //@phpstan-ignore-line [DOCTYPE]
412            throw new ArgumentException('$count');
413        }
414
415        return array_fill(0, $count, $value);
416    }
417
418    /**
419     * 値による単純ソート。
420     *
421     * @template TValue
422     * @param array $array
423     * @phpstan-param TValue[] $array
424     * @param OrderBy $orderBy
425     * @return array
426     * @phpstan-return TValue[]
427     * @see https://www.php.net/manual/function.sort.php
428     * @see https://www.php.net/manual/function.rsort.php
429     */
430    public static function sortByValue(array $array, OrderBy $orderBy): array
431    {
432        $result = $array;
433        $flags = SORT_REGULAR;
434
435        match ($orderBy) {
436            OrderBy::Ascending => sort($result, $flags),
437            OrderBy::Descending => rsort($result, $flags),
438        };
439
440        return $result;
441    }
442
443    /**
444     * キーによる単純ソート。
445     *
446     * @template TValue
447     * @param array $array
448     * @phpstan-param array<array-key,TValue> $array
449     * @param OrderBy $orderBy
450     * @return array
451     * @phpstan-return array<array-key,TValue>
452     * @see https://www.php.net/manual/function.ksort.php
453     * @see https://www.php.net/manual/function.krsort.php
454     */
455    public static function sortByKey(array $array, OrderBy $orderBy): array
456    {
457        $result = $array;
458        $flags = SORT_REGULAR;
459
460        match ($orderBy) {
461            OrderBy::Ascending => ksort($result, $flags),
462            OrderBy::Descending => krsort($result, $flags),
463        };
464
465        return $result;
466    }
467
468    /**
469     * 値による自然昇順ソート。
470     *
471     * @template TValue
472     * @param array $array
473     * @phpstan-param TValue[] $array
474     * @param bool $ignoreCase 大文字小文字を無視するか。
475     * @return array
476     * @phpstan-return TValue[]
477     * @see https://www.php.net/manual/function.natsort.php
478     * @see https://www.php.net/manual/function.natcasesort.php
479     */
480    public static function sortNaturalByValue(array $array, bool $ignoreCase): array
481    {
482        $result = self::getValues($array);
483
484        if ($ignoreCase) {
485            natcasesort($result);
486        } else {
487            natsort($result);
488        }
489
490        return self::getValues($result);
491    }
492
493    /**
494     * 値によるユーザー定義ソート。
495     *
496     * `asort` とかもこいつでやってくれ。
497     *
498     * @template TValue
499     * @param array $array
500     * @phpstan-param TValue[] $array
501     * @param callable $callback
502     * @phpstan-param callable(TValue,TValue):int $callback
503     * @return array
504     * @phpstan-return TValue[]
505     * @see https://www.php.net/manual/function.usort.php
506     * @see https://www.php.net/manual/function.uasort.php
507     * @see https://www.php.net/manual/function.asort.php
508     * @see https://www.php.net/manual/function.arsort.php
509     */
510    public static function sortCallbackByValue(array $array, callable $callback): array
511    {
512        $result = $array;
513
514        usort($result, $callback);
515
516        return $result;
517    }
518
519    /**
520     * キーによるユーザー定義ソート。
521     *
522     * @template TValue
523     * @param array $array
524     * @phpstan-param array<array-key,TValue> $array
525     * @param callable $callback
526     * @phpstan-param callable(array-key,array-key):int $callback
527     * @return array
528     * @phpstan-return array<array-key,TValue>
529     * @see https://www.php.net/manual/function.usort.php
530     */
531    public static function sortCallbackByKey(array $array, callable $callback): array
532    {
533        $result = $array;
534
535        uksort($result, $callback);
536
537        return $result;
538    }
539
540    // いやこれ無理
541    // public static function multisort(array $array, callable $callback): array
542    // {
543    // }
544
545    #endregion
546}