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

ICanBoogie / Common / 11626790543

01 Nov 2024 08:47AM UTC coverage: 45.993% (-0.3%) from 46.341%
11626790543

push

github

olvlvl
Support PHP 8.4

132 of 287 relevant lines covered (45.99%)

2.29 hits per line

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

72.48
/lib/array.php
1
<?php
2

3
namespace ICanBoogie;
4

5
use LogicException;
6

7
use function array_filter;
8
use function array_key_exists;
9
use function array_keys;
10
use function array_merge;
11
use function array_search;
12
use function array_shift;
13
use function array_splice;
14
use function array_unshift;
15
use function array_walk;
16
use function asort;
17
use function count;
18
use function is_array;
19
use function is_numeric;
20
use function is_scalar;
21
use function max;
22
use function min;
23
use function reset;
24
use function substr;
25
use function trigger_error;
26

27
use const E_USER_DEPRECATED;
28

29
/**
30
 * Sorts an array using a stable sorting algorithm while preserving its keys.
31
 *
32
 * A stable sorting algorithm maintains the relative order of values with equal keys.
33
 *
34
 * The array is always sorted in ascending order, but one can use the array_reverse() function to
35
 * reverse the array. Also, keys are preserved, even numeric ones, use the array_values() function
36
 * to create an array with an ascending index.
37
 *
38
 * @param array<int|string, mixed> $array
39
 *
40
 * @deprecated Sorting has been stable since PHP 8.0 https://wiki.php.net/rfc/stable_sorting
41
 */
42
function stable_sort(array &$array, ?callable $picker = null): void
43
{
44
    @trigger_error('icanboogie/common: stable_sort() is deprecated, use asort() or uasort()', E_USER_DEPRECATED);
×
45

46
    if ($picker) {
×
47
        uasort($array, $picker);
×
48
    } else {
49
        asort($array);
×
50
    }
51
}
52

53
/**
54
 * Sort an array according to the weight of its items.
55
 *
56
 * The weight of the items is defined as an integer; a position relative to another member of the
57
 * array `before:<key>` or `after:<key>`; the special words `top` and `bottom`.
58
 *
59
 * @param array<int|string, mixed> $array
60
 * @param callable $weight_picker The callback function used to pick the weight of the item. The
61
 * function is called with the following arguments: `$value`, `$key`.
62
 *
63
 * @return array<int|string, mixed> A sorted copy of the array.
64
 */
65
function sort_by_weight(array $array, callable $weight_picker): array
66
{
67
    if (!$array) {
3✔
68
        return $array;
×
69
    }
70

71
    $order = [];
3✔
72

73
    foreach ($array as $k => $v) {
3✔
74
        $order[$k] = $weight_picker($v, $k);
3✔
75
    }
76

77
    $n = count($order);
3✔
78

79
    $numeric_order = array_filter($order, fn($weight) => is_scalar($weight) && is_numeric($weight));
3✔
80

81
    if ($numeric_order) {
3✔
82
        $top = min($numeric_order) - $n;
3✔
83
        $bottom = max($numeric_order) + $n;
3✔
84
    } else {
85
        $top = -$n;
×
86
        $bottom = $n;
×
87
    }
88

89
    foreach ($order as &$weight) {
3✔
90
        if ($weight === 'top') {
3✔
91
            $weight = --$top;
2✔
92
        } else {
93
            if ($weight === 'bottom') {
3✔
94
                $weight = ++$bottom;
2✔
95
            }
96
        }
97
    }
98

99
    foreach ($order as $k => &$weight) {
3✔
100
        if (str_starts_with($weight, 'before:')) {
3✔
101
            $target = substr($weight, 7);
1✔
102

103
            if (isset($order[$target])) {
1✔
104
                $order = array_insert($order, $target, $order[$target], $k);
1✔
105
            } else {
106
                $weight = 0;
×
107
            }
108
        } else {
109
            if (str_starts_with($weight, 'after:')) {
3✔
110
                $target = substr($weight, 6);
2✔
111

112
                if (isset($order[$target])) {
2✔
113
                    $order = array_insert($order, $target, $order[$target], $k, true);
1✔
114
                } else {
115
                    $weight = 0;
1✔
116
                }
117
            }
118
        }
119
    }
120

121
    asort($order);
3✔
122

123
    array_walk($order, function (&$v, $k) use ($array) {
3✔
124
        $v = $array[$k];
3✔
125
    });
3✔
126

127
    return $order;
3✔
128
}
129

130
/**
131
 * Inserts a value in an array before, or after, a given key.
132
 *
133
 * Numeric keys are not preserved.
134
 *
135
 * @param array<int|string, mixed> $array
136
 * @param mixed $relative
137
 * @param mixed $value
138
 * @param string|int|null $key
139
 * @param bool $after
140
 *
141
 * @return array<int|string, mixed>
142
 */
143
function array_insert(
144
    array $array,
145
    mixed $relative,
146
    mixed $value,
147
    string|int|null $key = null,
148
    bool $after = false
149
): array {
150
    if ($key) {
1✔
151
        unset($array[$key]);
1✔
152
    }
153

154
    $keys = array_keys($array);
1✔
155
    $pos = array_search($relative, $keys, true);
1✔
156

157
    if ($pos === false) {
1✔
158
        throw new LogicException("Relative not found.");
×
159
    }
160

161
    if ($after) {
1✔
162
        $pos++;
1✔
163
    }
164

165
    $spliced = array_splice($array, $pos);
1✔
166

167
    if ($key !== null) {
1✔
168
        $array = array_merge($array, [ $key => $value ]);
1✔
169
    } else {
170
        array_unshift($spliced, $value);
×
171
    }
172

173
    return array_merge($array, $spliced);
1✔
174
}
175

176
/**
177
 * Flattens an array.
178
 *
179
 * @param array<int|string, mixed> $array
180
 * @param string|array{ string, string } $separator
181
 *
182
 * @return array<int|string, mixed>
183
 */
184
function array_flatten(array $array, string|array $separator = '.', int $depth = 0): array
185
{
186
    $rc = [];
2✔
187

188
    if (is_array($separator)) {
2✔
189
        foreach ($array as $key => $value) {
1✔
190
            if (!is_array($value)) {
1✔
191
                $rc[$key . ($depth ? $separator[1] : '')] = $value;
1✔
192

193
                continue;
1✔
194
            }
195

196
            $values = array_flatten($value, $separator, $depth + 1);
1✔
197

198
            foreach ($values as $sk => $sv) {
1✔
199
                $rc[$key . ($depth ? $separator[1] : '') . $separator[0] . $sk] = $sv;
1✔
200
            }
201
        }
202
    } else {
203
        foreach ($array as $key => $value) {
1✔
204
            if (!is_array($value)) {
1✔
205
                $rc[$key] = $value;
1✔
206

207
                continue;
1✔
208
            }
209

210
            $values = array_flatten($value, $separator);
1✔
211

212
            foreach ($values as $sk => $sv) {
1✔
213
                $rc[$key . $separator . $sk] = $sv;
1✔
214
            }
215
        }
216
    }
217

218
    return $rc;
2✔
219
}
220

221
/**
222
 * Merge arrays recursively with a different algorithm than PHP.
223
 *
224
 * @param array<int|string, mixed> ...$arrays
225
 *
226
 * @return array<int|string, mixed>
227
 */
228
function array_merge_recursive(array ...$arrays): array
229
{
230
    if (count($arrays) < 2) {
×
231
        return reset($arrays) ?: [];
×
232
    }
233

234
    $merge = array_shift($arrays);
×
235

236
    foreach ($arrays as $array) {
×
237
        foreach ($array as $key => $val) {
×
238
            #
239
            # if the value is an array and the key already exists
240
            # we have to make a recursion
241
            #
242

243
            if (is_array($val) && array_key_exists($key, $merge)) {
×
244
                $val = array_merge_recursive((array) $merge[$key], $val);
×
245
            }
246

247
            #
248
            # if the key is numeric, the value is pushed. Otherwise, it replaces
249
            # the value of the _merge_ array.
250
            #
251

252
            if (is_numeric($key)) {
×
253
                $merge[] = $val;
×
254
            } else {
255
                $merge[$key] = $val;
×
256
            }
257
        }
258
    }
259

260
    return $merge;
×
261
}
262

263
/**
264
 * @param array<int|string, mixed> ...$arrays
265
 *
266
 * @return array<int|string, mixed>
267
 */
268
function exact_array_merge_recursive(array ...$arrays): array
269
{
270
    if (count($arrays) < 2) {
×
271
        return reset($arrays) ?: [];
×
272
    }
273

274
    $merge = array_shift($arrays);
×
275

276
    foreach ($arrays as $array) {
×
277
        foreach ($array as $key => $val) {
×
278
            #
279
            # if the value is an array and the key already exists
280
            # we have to make a recursion
281
            #
282

283
            if (is_array($val) && array_key_exists($key, $merge)) {
×
284
                $val = exact_array_merge_recursive((array) $merge[$key], $val);
×
285
            }
286

287
            $merge[$key] = $val;
×
288
        }
289
    }
290

291
    return $merge;
×
292
}
293

294
/**
295
 * Creates a dictionary from an iterable according to the specified key selector function
296
 * and optional element selector function.
297
 *
298
 * @template TSource
299
 * @template TElement
300
 *
301
 * @param iterable<TSource> $it
302
 * @param callable(TSource):(int|string) $key_selector
303
 * @param ?callable(TSource):TElement $element_selector
304
 *
305
 * @return ($element_selector is null ? array<int|string, TSource> : array<int|string, TElement>)
306
 */
307
function iterable_to_dictionary(iterable $it, callable $key_selector, ?callable $element_selector = null): array
308
{
309
    $ar = [];
4✔
310
    $element_selector ??= fn ($source) => $source;
4✔
311

312
    foreach ($it as $source) {
4✔
313
        /** @var TElement $element */
314
        $key = $key_selector($source);
4✔
315
        $element = $element_selector($source);
4✔
316
        $ar[$key] = $element;
4✔
317
    }
318

319
    return $ar;
4✔
320
}
321

322
/**
323
 * Groups the elements of a sequence according to the specified key selector function
324
 * and optionally projects the elements for each group by using a specified function.
325
 *
326
 * @template TSource
327
 * @template TElement
328
 *
329
 * @param iterable<TSource> $it
330
 * @param callable(TSource):(int|string) $key_selector
331
 * @param ?callable(TSource):TElement $element_selector
332
 *
333
 * @return ($element_selector is null ? array<int|string, array<TSource>> : array<int|string, array<TElement>>)
334
 */
335
function iterable_to_groups(iterable $it, callable $key_selector, ?callable $element_selector = null): array
336
{
337
    $ar = [];
4✔
338
    $element_selector ??= fn ($source) => $source;
4✔
339

340
    foreach ($it as $source) {
4✔
341
        /** @var TElement $element */
342
        $key = $key_selector($source);
4✔
343
        $element = $element_selector($source);
4✔
344
        $ar[$key][] = $element;
4✔
345
    }
346

347
    return $ar;
4✔
348
}
349

350
/**
351
 * Tests whether every value in the iterable match the predicate.
352
 *
353
 * @template T
354
 *
355
 * @param iterable<int|string, T> $it
356
 * @param callable(T):bool $predicate
357
 */
358
function iterable_every(iterable $it, callable $predicate): bool
359
{
360
    foreach ($it as $value) {
1✔
361
        if (!$predicate($value)) {
1✔
362
            return false;
1✔
363
        }
364
    }
365

366
    return true;
1✔
367
}
368

369
/**
370
 * Tests whether at least one element in the array matches the predicate.
371
 *
372
 * @template T
373
 *
374
 * @param iterable<int|string, T> $it
375
 * @param callable(T):bool $predicate
376
 */
377
function iterable_some(iterable $it, callable $predicate): bool
378
{
379
    foreach ($it as $value) {
1✔
380
        if ($predicate($value)) {
1✔
381
            return true;
1✔
382
        }
383
    }
384

385
    return false;
1✔
386
}
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