Passed
Push — master ( 0d207d...0ce24c )
by Eric
05:53
created

Arrays::mapDeep()   B

Complexity

Conditions 8
Paths 13

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 17
nc 13
nop 2
dl 0
loc 33
ccs 6
cts 6
cp 1
crap 8
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of Esi\Utility.
7
 *
8
 * (c) 2017 - 2024 Eric Sizemore <[email protected]>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE.md file that was distributed with this source code.
12
 */
13
14
namespace Esi\Utility;
15
16
use ArrayAccess;
17
use RuntimeException;
18
use SplObjectStorage;
19
20
use function array_map;
21
use function array_merge;
22
use function array_sum;
23
24
/**
25
 * Array utilities.
26
 *
27
 * @see Tests\ArraysTest
28
 *
29
 * @template TObject of SplObjectStorage
30
 * @template TData
31
 *
32
 * @uses SplObjectStorage<TObject, TData>
33
 */
34
abstract class Arrays
35
{
36
    /**
37
     * flatten().
38
     *
39
     * Flattens a multidimensional array.
40
     *
41
     * Keys are preserved based on $separator.
42
     *
43
     * @param array<TKey, TValue> $array     Array to flatten.
44
     * @param string              $separator The new keys are a list of original keys separated by $separator.
45
     * @param string              $prepend   A string to prepend to resulting array keys.
46 1
     *
47
     * @since 1.2.0
48 1
     *
49
     * @return array<TKey, TValue> The flattened array.
50 1
     *
51 1
     * @template TKey
52 1
     * @template TValue
53
     */
54 1
    public static function flatten(array $array, string $separator = '.', string $prepend = ''): array
55
    {
56
        $result = [];
57
58 1
        foreach ($array as $key => $value) {
59
            if (\is_array($value) && $value !== []) {
60
                $result = array_merge($result, Arrays::flatten($value, $separator, $prepend . $key . $separator));
61
            } else {
62
                $result[$prepend . $key] = $value;
63
            }
64
        }
65
66
        /**
67
         * @var array<TKey, TValue> $result
68
         */
69
        return $result;
70
    }
71
72 7
    /**
73
     * get().
74 7
     *
75 7
     * Retrieve a value from an array.
76
     *
77
     * @param array<TKey, TValue>|ArrayAccess<TKey, TValue> $array   Array to retrieve value from.
78 5
     * @param TKey                                          $key     Key to retrieve
0 ignored issues
show
Bug introduced by
The type Esi\Utility\TKey was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
79
     * @param TDefault                                      $default A default value to return if $key does not exist
0 ignored issues
show
Bug introduced by
The type Esi\Utility\TDefault was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
80
     *
81
     * @throws RuntimeException If $array is not accessible
82
     *
83
     * @template TKey of (int|string)
84
     * @template TValue
85
     * @template TDefault
86
     *
87
     * @uses ArrayAccess<TKey, TValue>
88
     */
89
    public static function get(array|ArrayAccess $array, int|string $key, mixed $default = null): mixed
90
    {
91
        if (Arrays::keyExists($array, $key)) {
92
            return $array[$key];
93
        }
94 2
95
        return $default;
96 2
    }
97
98 2
    /**
99 2
     * Returns an associative array, grouped by $key, where the keys are the distinct values of $key,
100
     * and the values are arrays of items that share the same $key.
101
     *
102
     * *Important to note:* if a $key is provided that does not exist, the result will be an empty array.
103
     *
104
     * @since 2.0.0
105 2
     *
106 1
     * @param array<TKey, array<TKey, mixed>> $array Input array.
107
     * @param string                          $key   Key to use for grouping.
108
     *
109 1
     * @return array<mixed, non-empty-list<array<TKey, mixed>>>|array{}
1 ignored issue
show
Documentation Bug introduced by
The doc comment array<mixed, non-empty-l...<TKey, mixed>>>|array{} at position 4 could not be parsed: Unknown type name 'non-empty-list' at position 4 in array<mixed, non-empty-list<array<TKey, mixed>>>|array{}.
Loading history...
110
     *
111 1
     * @template TKey
112 1
     */
113
    public static function groupBy(array $array, string $key): array
114
    {
115 2
        $result = [];
116
117
        foreach ($array as $item) {
118
            if (!self::isAssociative($item)) {
119
                //@codeCoverageIgnoreStart
120
                continue;
121
                //@codeCoverageIgnoreEnd
122
            }
123
124
            if (!isset($item[$key])) {
125
                continue;
126
            }
127
128
            $groupKey = $item[$key];
129
130
            $result[$groupKey] ??= [];
131
            $result[$groupKey][] = $item;
132
        }
133
134
        return $result;
135
    }
136
137
    /**
138
     * interlace().
139
     *
140
     * Interlaces one or more arrays' values (not preserving keys).
141
     *
142
     * Example:
143
     *
144
     *      var_dump(Utility\Arrays::interlace(
145
     *          [1, 2, 3],
146 1
     *          ['a', 'b', 'c']
147
     *      ));
148 1
     *
149
     * Result:
150 1
     *      Array (
151 1
     *          [0] => 1
152
     *          [1] => a
153
     *          [2] => 2
154 1
     *          [3] => b
155 1
     *          [4] => 3
156
     *          [5] => c
157
     *      )
158 1
     *
159 1
     * @since 1.2.0
160
     *
161 1
     * @param array<TKey, TValue> ...$args
162 1
     *
163 1
     * @return array<TKey, TValue>|false|list<TValue>
0 ignored issues
show
Bug introduced by
The type Esi\Utility\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
164 1
     *
165
     * @template TKey
166
     * @template TValue
167
     */
168
    public static function interlace(array ...$args): array|false
169 1
    {
170
        $numArgs = \count($args);
171
172
        if ($numArgs === 0) {
173
            return false;
174
        }
175
176
        if ($numArgs === 1) {
177
            return $args[0];
178
        }
179 3
180
        $newArray      = [];
181 3
        $totalElements = array_sum(array_map('count', $args));
182
183
        for ($i = 0; $i < $totalElements; ++$i) {
184
            foreach ($args as $arg) {
185
                if (isset($arg[$i])) {
186
                    $newArray[] = $arg[$i];
187
                }
188
            }
189
        }
190 8
191
        return $newArray;
192 8
    }
193 1
194
    /**
195
     * isAssociative().
196 8
     *
197
     * Determines if the given array is an associative array.
198
     *
199
     * @param array<mixed> $array Array to check
200
     */
201
    public static function isAssociative(array $array): bool
202
    {
203
        return !array_is_list($array);
204
    }
205
206
    /**
207
     * Checks if a key exists in an array.
208
     *
209
     * @param array<TKey, TValue>|ArrayAccess<TKey, TValue> $array Array to check
210 2
     * @param TKey                                          $key   Key to check
211
     *
212 2
     * @return bool
213 2
     *
214 2
     * @template TKey of (int|string)
215
     * @template TValue
216 2
     *
217 1
     * @uses ArrayAccess<TKey, TValue>
218 1
     */
219
    public static function keyExists(array|ArrayAccess $array, int|string $key): bool
220
    {
221 2
        if ($array instanceof ArrayAccess) {
222
            return $array->offsetExists($key);
223
        }
224 2
225
        return \array_key_exists($key, $array);
226
    }
227
228
    /**
229
     * mapDeep().
230
     *
231
     * Recursively applies a callback to all non-iterable elements of an array or an object.
232
     *
233
     * @since 1.2.0 - updated with inspiration from the WordPress map_deep() function.
234
     * @see https://developer.wordpress.org/reference/functions/map_deep/
235
     *
236
     * @param array<mixed>|mixed|object $array    The array to apply $callback to.
237
     * @param callable                  $callback The callback function to apply.
238
     */
239
    public static function mapDeep(mixed $array, callable $callback): mixed
240 6
    {
241
        /**
242 6
         * @var ?SplObjectStorage<TObject, TData> $visited
243 1
         */
244
        static $visited;
245 6
246
        $visited ??= new SplObjectStorage();
247
248
        if (\is_object($array)) {
249
            if ($visited->contains($array)) {
250
                return $array; // Avoid circular references.
251
            }
252
            $visited->attach($array);
253
        }
254
255 26
        if (\is_array($array)) {
256
            foreach ($array as $key => $value) {
257 26
                $array[$key] = Arrays::mapDeep($value, $callback);
258
            }
259
        } elseif (\is_object($array)) {
260
            foreach (get_object_vars($array) as $key => $value) {
261
                $array->{$key} = Arrays::mapDeep($value, $callback);
262
            }
263
        } else {
264
            $array = $callback($array);
265
        }
266
267
        if (\is_object($array)) {
268
            $visited->detach($array);
269
        }
270
271
        return $array;
272
    }
273
274
    /**
275
     * set().
276
     *
277
     * Add a value to an array.
278
     *
279
     * @param array<TKey, TValue>|ArrayAccess<TKey, TValue> $array Array to add value to.
280
     * @param TKey                                          $key   Key to add
281
     * @param TValue                                        $value Value to add
0 ignored issues
show
Bug introduced by
The type Esi\Utility\TValue was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
282
     *
283
     * @param-out TValue|non-empty-array<TKey, TValue>|ArrayAccess<TKey, TValue> $array
284
     *
285
     * @throws RuntimeException If $array is not accessible
286
     *
287
     * @template TKey of (int|string)
288
     * @template TValue
289
     *
290
     * @uses ArrayAccess<TKey, TValue>
291
     */
292
    public static function set(array|ArrayAccess &$array, null|int|string $key, mixed $value): void
293
    {
294
        if ($key === null) {
295
            $array = $value;
296
        } else {
297
            if ($array instanceof ArrayAccess) {
0 ignored issues
show
introduced by
$array is never a sub-type of ArrayAccess.
Loading history...
298
                $array->offsetSet($key, $value);
299
            } else {
300
                $array[$key] = $value;
301
            }
302
        }
303
    }
304
305
    /**
306
     * Checks if a value exists in an array.
307
     *
308
     * @param array<TKey, TValue> $array Array to check
309
     * @param TKey                $value Value to check
310
     *
311
     * @template TKey of (int|string)
312
     * @template TValue
313
     */
314
    public static function valueExists(array $array, int|string $value): bool
315
    {
316
        return \in_array($value, $array, true);
317
    }
318
}
319