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