Passed
Push — master ( e6d102...7dc300 )
by Eric
12:39
created

Arrays::flatten()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 3
nop 3
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Utility - Collection of various PHP utility functions.
7
 *
8
 * @author    Eric Sizemore <[email protected]>
9
 * @version   2.0.0
10
 * @copyright (C) 2017 - 2024 Eric Sizemore
11
 * @license   The MIT License (MIT)
12
 *
13
 * Copyright (C) 2017 - 2024 Eric Sizemore <https://www.secondversion.com>.
14
 *
15
 * Permission is hereby granted, free of charge, to any person obtaining a copy
16
 * of this software and associated documentation files (the "Software"), to
17
 * deal in the Software without restriction, including without limitation the
18
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
19
 * sell copies of the Software, and to permit persons to whom the Software is
20
 * furnished to do so, subject to the following conditions:
21
 *
22
 * The above copyright notice and this permission notice shall be included in
23
 * all copies or substantial portions of the Software.
24
 *
25
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31
 * THE SOFTWARE.
32
 */
33
34
namespace Esi\Utility;
35
36
// Exceptions
37
use RuntimeException;
38
39
// Classes
40
use ArrayAccess;
41
42
// Functions
43
use function is_array;
44
use function is_null;
45
use function array_key_exists;
46
use function array_merge;
47
use function is_object;
48
use function get_object_vars;
49
use function call_user_func;
50
use function count;
51
use function array_sum;
52
use function array_map;
53
54
/**
55
 * Array utilities.
56
 */
57
final class Arrays
58
{
59
    /**
60
     * isAssociative()
61
     *
62
     * Determines if the given array is an associative array.
63
     *
64
     * @param  array<mixed>  $array  Array to check
65
     * @return bool
66
     */
67
    public static function isAssociative(array $array): bool
68
    {
69
        return !array_is_list($array);
70
    }
71
72
    /**
73
     * get()
74
     *
75
     * Retrieve a value from an array.
76
     *
77
     * @param  array<mixed>|ArrayAccess<mixed, mixed>  $array    Array to retrieve value from.
78
     * @param  string|int    $key      Key to retrieve
79
     * @param  mixed         $default  A default value to return if $key does not exist
80
     * @return mixed
81
     *
82
     * @throws RuntimeException  If $array is not accessible
83
     */
84
    public static function get(array | ArrayAccess $array, string | int $key, mixed $default = null): mixed
85
    {
86
        if (Arrays::exists($array, $key)) {
87
            return $array[$key];
88
        }
89
        return $default;
90
    }
91
92
    /**
93
     * set()
94
     *
95
     * Add a value to an array.
96
     *
97
     * @param  array<mixed>|ArrayAccess<mixed, mixed>     &$array  Array to add value to.
98
     * @param  string|int|null  $key     Key to add
99
     * @param  mixed            $value   Value to add
100
     * @return mixed
101
     *
102
     * @throws RuntimeException  If $array is not accessible
103
     */
104
    public static function set(array | ArrayAccess &$array, string | int | null $key, mixed $value): mixed
105
    {
106
        if (is_null($key)) {
107
            return $array = $value;
108
        }
109
        return $array[$key] = $value;
110
    }
111
112
    /**
113
     * Checks if a key exists in an array.
114
     *
115
     * @param  array<mixed>|ArrayAccess<mixed, mixed>  $array  Array to check
116
     * @param  string|int                $key    Key to check
117
     * @return bool
118
     */
119
    public static function exists(array | ArrayAccess $array, string | int $key): bool
120
    {
121
        if ($array instanceof ArrayAccess) {
1 ignored issue
show
introduced by
$array is never a sub-type of ArrayAccess.
Loading history...
122
            return $array->offsetExists($key);
123
        }
124
        return ((array_key_exists($key, $array)) || (in_array($key, $array, true)));
125
    }
126
127
    /**
128
     * flatten()
129
     *
130
     * Flattens a multidimensional array.
131
     *
132
     * Keys are preserved based on $separator.
133
     *
134
     * @param   array<mixed>   $array      Array to flatten.
135
     * @param   string         $separator  The new keys are a list of original keys separated by $separator.
136
     *
137
     * @since 1.2.0
138
     * @param   string         $prepend    A string to prepend to resulting array keys.
139
     *
140
     * @return  array<mixed>               The flattened array.
141
     */
142
    public static function flatten(array $array, string $separator = '.', string $prepend = ''): array
143
    {
144
        $result = [];
145
146
        foreach ($array as $key => $value) {
147
            if (is_array($value) && $value !== []) {
148
                $result = array_merge($result, Arrays::flatten($value, $separator, $prepend . $key . $separator));
149
            } else {
150
                $result[$prepend . $key] = $value;
151
            }
152
        }
153
        return $result;
154
    }
155
156
    /**
157
     * mapDeep()
158
     *
159
     * Recursively applies a callback to all non-iterable elements of an array or an object.
160
     *
161
     * @since 1.2.0 - updated with inspiration from the WordPress map_deep() function.
162
     *      @see https://developer.wordpress.org/reference/functions/map_deep/
163
     *
164
     * @param   mixed     $array     The array to apply $callback to.
165
     * @param   callable  $callback  The callback function to apply.
166
     * @return  mixed
167
     */
168
    public static function mapDeep(mixed $array, callable $callback): mixed
169
    {
170
        if (is_array($array)) {
171
            foreach ($array as $key => $value) {
172
                $array[$key] = Arrays::mapDeep($value, $callback);
173
            }
174
        } elseif (is_object($array)) {
175
            foreach (get_object_vars($array) as $key => $value) {
176
                // @phpstan-ignore-next-line
177
                $array->$key = Arrays::mapDeep($value, $callback);
178
            }
179
        } else {
180
            $array = call_user_func($callback, $array);
181
        }
182
        return $array;
183
    }
184
185
    /**
186
     * interlace()
187
     *
188
     * Interlaces one or more arrays' values (not preserving keys).
189
     *
190
     * Example:
191
     *
192
     *      var_dump(Utility\Arrays::interlace(
193
     *          [1, 2, 3],
194
     *          ['a', 'b', 'c']
195
     *      ));
196
     *
197
     * Result:
198
     *      Array (
199
     *          [0] => 1
200
     *          [1] => a
201
     *          [2] => 2
202
     *          [3] => b
203
     *          [4] => 3
204
     *          [5] => c
205
     *      )
206
     *
207
     * @since 1.2.0
208
     *
209
     * @param  array<mixed>        ...$args
210
     * @return array<mixed>|false
211
     */
212
    public static function interlace(array ...$args): array | false
213
    {
214
        $numArgs = count($args);
215
216
        if ($numArgs === 0) {
217
            return false;
218
        }
219
220
        if ($numArgs === 1) {
221
            return $args[0];
222
        }
223
224
        $newArray = [];
225
        $totalElements = array_sum(array_map('count', $args));
226
227
        for ($i = 0; $i < $totalElements; $i++) {
228
            foreach ($args as $arg) {
229
                if (isset($arg[$i])) {
230
                    $newArray[] = $arg[$i];
231
                }
232
            }
233
        }
234
        return $newArray;
235
    }
236
237
    /**
238
     *
239
     * Returns an associative array, grouped by $key, where the keys are the distinct values of $key,
240
     * and the values are arrays of items that share the same $key.
241
     *
242
     * *Important to note:* if a $key is provided that does not exist, the result will be an empty array.
243
     *
244
     * @since 2.0.0
245
     *
246
     * @param array<mixed, array<mixed>>  $array
247
     * @param string                      $key
248
     * @return array<mixed, array<mixed>>
249
     */
250
    public static function groupBy(array $array, string $key): array
251
    {
252
        $result = [];
253
254
        foreach ($array as $item) {
255
            if (!self::isAssociative($item)) {
256
                //@codeCoverageIgnoreStart
257
                continue;
258
                //@codeCoverageIgnoreEnd
259
            }
260
261
            if (!isset($item[$key])) {
262
                continue;
263
            }
264
265
            $groupKey = $item[$key];
266
267
            if (!isset($result[$groupKey])) {
268
                $result[$groupKey] = [];
269
            }
270
271
            $result[$groupKey][] = $item;
272
        }
273
        return $result;
274
    }
275
}
276