GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

ArrayUtil   F
last analyzed

Complexity

Total Complexity 104

Size/Duplication

Total Lines 698
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 0

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 104
lcom 0
cbo 0
dl 0
loc 698
ccs 0
cts 307
cp 0
rs 1.902
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A isValidPath() 0 12 2
A getValueByPath() 0 24 5
B setValueByPath() 0 30 6
B removeByPath() 0 32 7
A sortByKeyRecursive() 0 11 4
C arrayExport() 0 49 15
A flatten() 0 15 3
A intersectRecursive() 0 19 6
B renumberKeysToAvoidLeapsIfKeysAreAllNumeric() 0 22 6
B mergeRecursiveWithOverrule() 0 21 11
A inArray() 0 10 4
A removeArrayEntryByValue() 0 12 4
B keepItemsInArray() 0 25 9
A remapArrayKeys() 0 9 4
A arrayDiffAssocRecursive() 0 15 5
A naturalKeySortRecursive() 0 11 3
A filterByValueRecursive() 0 22 4
A sortArraysByKey() 0 18 6

How to fix   Complexity   

Complex Class

Complex classes like ArrayUtil often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ArrayUtil, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Neta\Shopware\SDK\Util;
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
/**
19
 * Class with helper functions for array handling.
20
 */
21
class ArrayUtil
22
{
23
    /**
24
     * Reduce an array by a search value and keep the array structure.
25
     *
26
     * Comparison is type strict:
27
     * - For a given needle of type string, integer, array or boolean,
28
     * value and value type must match to occur in result array
29
     * - For a given object, an object within the array must be a reference to
30
     * the same object to match (not just different instance of same class)
31
     *
32
     * Example:
33
     * - Needle: 'findMe'
34
     * - Given array:
35
     * array(
36
     *   'foo' => 'noMatch',
37
     *   'bar' => 'findMe',
38
     *   'foobar => array(
39
     *     'foo' => 'findMe',
40
     *   ),
41
     * );
42
     * - Result:
43
     * array(
44
     *   'bar' => 'findMe',
45
     *   'foobar' => array(
46
     *     'foo' => findMe',
47
     *   ),
48
     * );
49
     *
50
     * See the unit tests for more examples and expected behaviour
51
     *
52
     * @param mixed $needle   The value to search for
53
     * @param array $haystack The array in which to search
54
     * @return array $haystack array reduced matching $needle values
55
     */
56
    public static function filterByValueRecursive($needle = '', array $haystack = [])
57
    {
58
        $resultArray = [];
59
        // Define a lambda function to be applied to all members of this array dimension
60
        // Call recursive if current value is of type array
61
        // Write to $resultArray (by reference!) if types and value match
62
        $callback = function (&$value, $key) use ($needle, &$resultArray) {
63
            if ($value === $needle) {
64
                ($resultArray[$key] = $value);
65
            } elseif (is_array($value)) {
66
                ($subArrayMatches = static::filterByValueRecursive($needle, $value));
67
                if (! empty($subArrayMatches)) {
68
                    ($resultArray[$key] = $subArrayMatches);
69
                }
70
            }
71
        };
72
        // array_walk() is not affected by the internal pointers, no need to reset
73
        array_walk($haystack, $callback);
74
75
        // Pointers to result array are reset internally
76
        return $resultArray;
77
    }
78
79
    /**
80
     * Checks if a given path exists in array.
81
     *
82
     * Example:
83
     * - array:
84
     * array(
85
     *   'foo' => array(
86
     *     'bar' = 'test',
87
     *   )
88
     * );
89
     * - path: 'foo/bar'
90
     * - return: TRUE
91
     *
92
     * @param array  $array     Given array
93
     * @param string $path      Path to test, 'foo/bar/foobar'
94
     * @param string $delimiter Delimiter for path, default /
95
     * @return bool TRUE if path exists in array
96
     */
97
    public static function isValidPath(array $array, $path, $delimiter = '/')
98
    {
99
        $isValid = true;
100
        try {
101
            // Use late static binding to enable mocking of this call in unit tests
102
            static::getValueByPath($array, $path, $delimiter);
103
        } catch (\RuntimeException $e) {
104
            $isValid = false;
105
        }
106
107
        return $isValid;
108
    }
109
110
    /**
111
     * Returns a value by given path.
112
     *
113
     * Example
114
     * - array:
115
     * array(
116
     *   'foo' => array(
117
     *     'bar' => array(
118
     *       'baz' => 42
119
     *     )
120
     *   )
121
     * );
122
     * - path: foo/bar/baz
123
     * - return: 42
124
     *
125
     * If a path segments contains a delimiter character, the path segment
126
     * must be enclosed by " (double quote), see unit tests for details
127
     *
128
     * @param array  $array     Input array
129
     * @param string $path      Path within the array
130
     * @param string $delimiter Defined path delimiter, default /
131
     * @return mixed
132
     * @throws \RuntimeException
133
     */
134
    public static function getValueByPath(array $array, $path, $delimiter = '/')
135
    {
136
        if (! is_string($path)) {
137
            throw new \RuntimeException('Path must be a string', 1477699595);
138
        }
139
        if ($path === '') {
140
            throw new \RuntimeException('Path must not be empty', 1341397767);
141
        }
142
        // Extract parts of the path
143
        $path = str_getcsv($path, $delimiter);
144
        // Loop through each part and extract its value
145
        $value = $array;
146
        foreach ($path as $segment) {
147
            if (array_key_exists($segment, $value)) {
148
                // Replace current value with child
149
                $value = $value[$segment];
150
            } else {
151
                // Fail if key does not exist
152
                throw new \RuntimeException('Path does not exist in array', 1341397869);
153
            }
154
        }
155
156
        return $value;
157
    }
158
159
    /**
160
     * Modifies or sets a new value in an array by given path.
161
     *
162
     * Example:
163
     * - array:
164
     * array(
165
     *   'foo' => array(
166
     *     'bar' => 42,
167
     *   ),
168
     * );
169
     * - path: foo/bar
170
     * - value: 23
171
     * - return:
172
     * array(
173
     *   'foo' => array(
174
     *     'bar' => 23,
175
     *   ),
176
     * );
177
     *
178
     * @param array  $array     Input array to manipulate
179
     * @param string $path      Path in array to search for
180
     * @param mixed  $value     Value to set at path location in array
181
     * @param string $delimiter Path delimiter
182
     * @return array Modified array
183
     * @throws \RuntimeException
184
     */
185
    public static function setValueByPath(array $array, $path, $value, $delimiter = '/')
186
    {
187
        if (! is_string($path)) {
188
            throw new \RuntimeException('Path must be a string', 1341406402);
189
        }
190
        if ($path === '') {
191
            throw new \RuntimeException('Path must not be empty', 1341406194);
192
        }
193
        // Extract parts of the path
194
        $path = str_getcsv($path, $delimiter);
195
        // Point to the root of the array
196
        $pointer = &$array;
197
        // Find path in given array
198
        foreach ($path as $segment) {
199
            // Fail if the part is empty
200
            if ($segment === '') {
201
                throw new \RuntimeException('Invalid path segment specified', 1341406846);
202
            }
203
            // Create cell if it doesn't exist
204
            if (! array_key_exists($segment, $pointer)) {
205
                $pointer[$segment] = [];
206
            }
207
            // Set pointer to new cell
208
            $pointer = &$pointer[$segment];
209
        }
210
        // Set value of target cell
211
        $pointer = $value;
212
213
        return $array;
214
    }
215
216
    /**
217
     * Remove a sub part from an array specified by path.
218
     *
219
     * @param array  $array     Input array to manipulate
220
     * @param string $path      Path to remove from array
221
     * @param string $delimiter Path delimiter
222
     * @return array Modified array
223
     * @throws \RuntimeException
224
     */
225
    public static function removeByPath(array $array, $path, $delimiter = '/')
226
    {
227
        if (! is_string($path)) {
228
            throw new \RuntimeException('Path must be a string', 1371757719);
229
        }
230
        if ($path === '') {
231
            throw new \RuntimeException('Path must not be empty', 1371757718);
232
        }
233
        // Extract parts of the path
234
        $path = str_getcsv($path, $delimiter);
235
        $pathDepth = count($path);
236
        $currentDepth = 0;
237
        $pointer = &$array;
238
        // Find path in given array
239
        foreach ($path as $segment) {
240
            $currentDepth++;
241
            // Fail if the part is empty
242
            if ($segment === '') {
243
                throw new \RuntimeException('Invalid path segment specified', 1371757720);
244
            }
245
            if (! array_key_exists($segment, $pointer)) {
246
                throw new \RuntimeException('Path segment ' . $segment . ' does not exist in array', 1371758436);
247
            }
248
            if ($currentDepth === $pathDepth) {
249
                unset($pointer[$segment]);
250
            } else {
251
                $pointer = &$pointer[$segment];
252
            }
253
        }
254
255
        return $array;
256
    }
257
258
    /**
259
     * Sorts an array recursively by key.
260
     *
261
     * @param array $array Array to sort recursively by key
262
     * @return array Sorted array
263
     */
264
    public static function sortByKeyRecursive(array $array)
265
    {
266
        ksort($array);
267
        foreach ($array as $key => $value) {
268
            if (is_array($value) && ! empty($value)) {
269
                $array[$key] = self::sortByKeyRecursive($value);
270
            }
271
        }
272
273
        return $array;
274
    }
275
276
    /**
277
     * Sort an array of arrays by a given key using uasort.
278
     *
279
     * @param array  $arrays    Array of arrays to sort
280
     * @param string $key       Key to sort after
281
     * @param bool   $ascending Set to TRUE for ascending order, FALSE for descending order
282
     * @return array Array of sorted arrays
283
     * @throws \RuntimeException
284
     */
285
    public static function sortArraysByKey(array $arrays, $key, $ascending = true)
286
    {
287
        if (empty($arrays)) {
288
            return $arrays;
289
        }
290
        $sortResult = uasort($arrays, function (array $a, array $b) use ($key, $ascending) {
291
            if (! isset($a[$key]) || ! isset($b[$key])) {
292
                throw new \RuntimeException('The specified sorting key "' . $key . '" is not available in the given array.', 1373727309);
293
            }
294
295
            return ($ascending) ? strcasecmp($a[$key], $b[$key]) : strcasecmp($b[$key], $a[$key]);
296
        });
297
        if (! $sortResult) {
298
            throw new \RuntimeException('The function uasort() failed for unknown reasons.', 1373727329);
299
        }
300
301
        return $arrays;
302
    }
303
304
    /**
305
     * Exports an array as string.
306
     * Similar to var_export(), but representation follows the PSR-2 and TYPO3 core CGL.
307
     *
308
     * See unit tests for detailed examples
309
     *
310
     * @param array $array Array to export
311
     * @param int   $level Internal level used for recursion, do *not* set from outside!
312
     * @return string String representation of array
313
     * @throws \RuntimeException
314
     */
315
    public static function arrayExport(array $array = [], $level = 0)
316
    {
317
        $lines = '[' . LF;
318
        $level++;
319
        $writeKeyIndex = false;
320
        $expectedKeyIndex = 0;
321
        foreach ($array as $key => $value) {
322
            if ($key === $expectedKeyIndex) {
323
                $expectedKeyIndex++;
324
            } else {
325
                // Found a non integer or non consecutive key, so we can break here
326
                $writeKeyIndex = true;
327
                break;
328
            }
329
        }
330
        foreach ($array as $key => $value) {
331
            // Indention
332
            $lines .= str_repeat('    ', $level);
333
            if ($writeKeyIndex) {
334
                // Numeric / string keys
335
                $lines .= is_int($key) ? $key . ' => ' : '\'' . $key . '\' => ';
336
            }
337
            if (is_array($value)) {
338
                if (! empty($value)) {
339
                    $lines .= self::arrayExport($value, $level);
340
                } else {
341
                    $lines .= '[],' . LF;
342
                }
343
            } elseif (is_int($value) || is_float($value)) {
344
                $lines .= $value . ',' . LF;
345
            } elseif (is_null($value)) {
346
                $lines .= 'null' . ',' . LF;
347
            } elseif (is_bool($value)) {
348
                $lines .= $value ? 'true' : 'false';
349
                $lines .= ',' . LF;
350
            } elseif (is_string($value)) {
351
                // Quote \ to \\
352
                $stringContent = str_replace('\\', '\\\\', $value);
353
                // Quote ' to \'
354
                $stringContent = str_replace('\'', '\\\'', $stringContent);
355
                $lines .= '\'' . $stringContent . '\'' . ',' . LF;
356
            } else {
357
                throw new \RuntimeException('Objects are not supported', 1342294987);
358
            }
359
        }
360
        $lines .= str_repeat('    ', ($level - 1)) . ']' . ($level - 1 == 0 ? '' : ',' . LF);
361
362
        return $lines;
363
    }
364
365
    /**
366
     * Converts a multidimensional array to a flat representation.
367
     *
368
     * See unit tests for more details
369
     *
370
     * Example:
371
     * - array:
372
     * array(
373
     *   'first.' => array(
374
     *     'second' => 1
375
     *   )
376
     * )
377
     * - result:
378
     * array(
379
     *   'first.second' => 1
380
     * )
381
     *
382
     * Example:
383
     * - array:
384
     * array(
385
     *   'first' => array(
386
     *     'second' => 1
387
     *   )
388
     * )
389
     * - result:
390
     * array(
391
     *   'first.second' => 1
392
     * )
393
     *
394
     * @param array  $array  The (relative) array to be converted
395
     * @param string $prefix The (relative) prefix to be used (e.g. 'section.')
396
     * @return array
397
     */
398
    public static function flatten(array $array, $prefix = '')
399
    {
400
        $flatArray = [];
401
        foreach ($array as $key => $value) {
402
            // Ensure there is no trailling dot:
403
            $key = rtrim($key, '.');
404
            if (! is_array($value)) {
405
                $flatArray[$prefix . $key] = $value;
406
            } else {
407
                $flatArray = array_merge($flatArray, self::flatten($value, $prefix . $key . '.'));
408
            }
409
        }
410
411
        return $flatArray;
412
    }
413
414
    /**
415
     * Determine the intersections between two arrays, recursively comparing keys
416
     * A complete sub array of $source will be preserved, if the key exists in $mask.
417
     *
418
     * See unit tests for more examples and edge cases.
419
     *
420
     * Example:
421
     * - source:
422
     * array(
423
     *   'key1' => 'bar',
424
     *   'key2' => array(
425
     *     'subkey1' => 'sub1',
426
     *     'subkey2' => 'sub2',
427
     *   ),
428
     *   'key3' => 'baz',
429
     * )
430
     * - mask:
431
     * array(
432
     *   'key1' => NULL,
433
     *   'key2' => array(
434
     *     'subkey1' => exists',
435
     *   ),
436
     * )
437
     * - return:
438
     * array(
439
     *   'key1' => 'bar',
440
     *   'key2' => array(
441
     *     'subkey1' => 'sub1',
442
     *   ),
443
     * )
444
     *
445
     * @param array $source Source array
446
     * @param array $mask   Array that has the keys which should be kept in the source array
447
     * @return array Keys which are present in both arrays with values of the source array
448
     */
449
    public static function intersectRecursive(array $source, array $mask = [])
450
    {
451
        $intersection = [];
452
        foreach ($source as $key => $_) {
453
            if (! array_key_exists($key, $mask)) {
454
                continue;
455
            }
456
            if (is_array($source[$key]) && is_array($mask[$key])) {
457
                $value = self::intersectRecursive($source[$key], $mask[$key]);
458
                if (! empty($value)) {
459
                    $intersection[$key] = $value;
460
                }
461
            } else {
462
                $intersection[$key] = $source[$key];
463
            }
464
        }
465
466
        return $intersection;
467
    }
468
469
    /**
470
     * Renumber the keys of an array to avoid leaps if keys are all numeric.
471
     *
472
     * Is called recursively for nested arrays.
473
     *
474
     * Example:
475
     *
476
     * Given
477
     *  array(0 => 'Zero' 1 => 'One', 2 => 'Two', 4 => 'Three')
478
     * as input, it will return
479
     *  array(0 => 'Zero' 1 => 'One', 2 => 'Two', 3 => 'Three')
480
     *
481
     * Will treat keys string representations of number (ie. '1') equal to the
482
     * numeric value (ie. 1).
483
     *
484
     * Example:
485
     * Given
486
     *  array('0' => 'Zero', '1' => 'One' )
487
     * it will return
488
     *  array(0 => 'Zero', 1 => 'One')
489
     *
490
     * @param array $array Input array
491
     * @param int   $level Internal level used for recursion, do *not* set from outside!
492
     * @return array
493
     */
494
    public static function renumberKeysToAvoidLeapsIfKeysAreAllNumeric(array $array = [], $level = 0)
495
    {
496
        $level++;
497
        $allKeysAreNumeric = true;
498
        foreach ($array as $key => $_) {
499
            if (is_numeric($key) === false) {
500
                $allKeysAreNumeric = false;
501
                break;
502
            }
503
        }
504
        $renumberedArray = $array;
505
        if ($allKeysAreNumeric === true) {
506
            $renumberedArray = array_values($array);
507
        }
508
        foreach ($renumberedArray as $key => $value) {
509
            if (is_array($value)) {
510
                $renumberedArray[$key] = self::renumberKeysToAvoidLeapsIfKeysAreAllNumeric($value, $level);
511
            }
512
        }
513
514
        return $renumberedArray;
515
    }
516
517
    /**
518
     * Merges two arrays recursively and "binary safe" (integer keys are
519
     * overridden as well), overruling similar values in the original array
520
     * with the values of the overrule array.
521
     * In case of identical keys, ie. keeping the values of the overrule array.
522
     *
523
     * This method takes the original array by reference for speed optimization with large arrays
524
     *
525
     * The differences to the existing PHP function array_merge_recursive() are:
526
     *  * Keys of the original array can be unset via the overrule array. ($enableUnsetFeature)
527
     *  * Much more control over what is actually merged. ($addKeys, $includeEmptyValues)
528
     *  * Elements or the original array get overwritten if the same key is present in the overrule array.
529
     *
530
     * @param array $original           Original array. It will be *modified* by this method and contains the result afterwards!
531
     * @param array $overrule           Overrule array, overruling the original array
532
     * @param bool  $addKeys            If set to FALSE, keys that are NOT found in $original will not be set. Thus only existing value can/will be overruled from overrule array.
533
     * @param bool  $includeEmptyValues If set, values from $overrule will overrule if they are empty or zero.
534
     * @param bool  $enableUnsetFeature If set, special values "__UNSET" can be used in the overrule array in order to unset array keys in the original array.
535
     * @return void
536
     */
537
    public static function mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys = true, $includeEmptyValues = true, $enableUnsetFeature = true)
538
    {
539
        foreach ($overrule as $key => $_) {
540
            if ($enableUnsetFeature && $overrule[$key] === '__UNSET') {
541
                unset($original[$key]);
542
                continue;
543
            }
544
            if (isset($original[$key]) && is_array($original[$key])) {
545
                if (is_array($overrule[$key])) {
546
                    self::mergeRecursiveWithOverrule($original[$key], $overrule[$key], $addKeys, $includeEmptyValues, $enableUnsetFeature);
547
                }
548
            } elseif (
549
                ($addKeys || isset($original[$key]))
550
                && ($includeEmptyValues || $overrule[$key])
551
            ) {
552
                $original[$key] = $overrule[$key];
553
            }
554
        }
555
        // This line is kept for backward compatibility reasons.
556
        reset($original);
557
    }
558
559
    /**
560
     * Check if an string item exists in an array.
561
     * Please note that the order of function parameters is reverse compared to the PHP function in_array()!!!
562
     *
563
     * Comparison to PHP in_array():
564
     * -> $array = array(0, 1, 2, 3);
565
     * -> variant_a := \TYPO3\CMS\Core\Utility\ArrayUtility::inArray($array, $needle)
566
     * -> variant_b := in_array($needle, $array)
567
     * -> variant_c := in_array($needle, $array, TRUE)
568
     * +---------+-----------+-----------+-----------+
569
     * | $needle | variant_a | variant_b | variant_c |
570
     * +---------+-----------+-----------+-----------+
571
     * | '1a'    | FALSE     | TRUE      | FALSE     |
572
     * | ''      | FALSE     | TRUE      | FALSE     |
573
     * | '0'     | TRUE      | TRUE      | FALSE     |
574
     * | 0       | TRUE      | TRUE      | TRUE      |
575
     * +---------+-----------+-----------+-----------+
576
     *
577
     * @param array  $in_array One-dimensional array of items
578
     * @param string $item     Item to check for
579
     * @return bool TRUE if $item is in the one-dimensional array $in_array
580
     */
581
    public static function inArray(array $in_array, $item)
582
    {
583
        foreach ($in_array as $val) {
584
            if (! is_array($val) && (string) $val === (string) $item) {
585
                return true;
586
            }
587
        }
588
589
        return false;
590
    }
591
592
    /**
593
     * Removes the value $cmpValue from the $array if found there. Returns the modified array.
594
     *
595
     * @param array  $array    Array containing the values
596
     * @param string $cmpValue Value to search for and if found remove array entry where found.
597
     * @return array Output array with entries removed if search string is found
598
     */
599
    public static function removeArrayEntryByValue(array $array, $cmpValue)
600
    {
601
        foreach ($array as $k => $v) {
602
            if (is_array($v)) {
603
                $array[$k] = self::removeArrayEntryByValue($v, $cmpValue);
604
            } elseif ((string) $v === (string) $cmpValue) {
605
                unset($array[$k]);
606
            }
607
        }
608
609
        return $array;
610
    }
611
612
    /**
613
     * Filters an array to reduce its elements to match the condition.
614
     * The values in $keepItems can be optionally evaluated by a custom callback function.
615
     *
616
     * Example (arguments used to call this function):
617
     * $array = array(
618
     * array('aa' => array('first', 'second'),
619
     * array('bb' => array('third', 'fourth'),
620
     * array('cc' => array('fifth', 'sixth'),
621
     * );
622
     * $keepItems = array('third');
623
     * $getValueFunc = function($value) { return $value[0]; }
624
     *
625
     * Returns:
626
     * array(
627
     * array('bb' => array('third', 'fourth'),
628
     * )
629
     *
630
     * @param array  $array        The initial array to be filtered/reduced
631
     * @param mixed  $keepItems    The items which are allowed/kept in the array - accepts array or csv string
632
     * @param string $getValueFunc (optional) Callback function used to get the value to keep
633
     * @return array The filtered/reduced array with the kept items
634
     */
635
    public static function keepItemsInArray(array $array, $keepItems, $getValueFunc = null)
636
    {
637
        if ($array) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $array of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
638
            // Convert strings to arrays:
639
            if (is_string($keepItems)) {
640
                $keepItems = GeneralUtility::trimExplode(',', $keepItems);
641
            }
642
            // Check if valueFunc can be executed:
643
            if (! is_callable($getValueFunc)) {
644
                $getValueFunc = null;
645
            }
646
            // Do the filtering:
647
            if (is_array($keepItems) && ! empty($keepItems)) {
648
                foreach ($array as $key => $value) {
649
                    // Get the value to compare by using the callback function:
650
                    $keepValue = isset($getValueFunc) ? call_user_func($getValueFunc, $value) : $value;
651
                    if (! in_array($keepValue, $keepItems)) {
652
                        unset($array[$key]);
653
                    }
654
                }
655
            }
656
        }
657
658
        return $array;
659
    }
660
661
    /**
662
     * Rename Array keys with a given mapping table.
663
     *
664
     * @param array $array        Array by reference which should be remapped
665
     * @param array $mappingTable Array with remap information, array/$oldKey => $newKey)
666
     */
667
    public static function remapArrayKeys(array &$array, array $mappingTable)
668
    {
669
        foreach ($mappingTable as $old => $new) {
670
            if ($new && isset($array[$old])) {
671
                $array[$new] = $array[$old];
672
                unset($array[$old]);
673
            }
674
        }
675
    }
676
677
    /**
678
     * Filters keys off from first array that also exist in second array. Comparison is done by keys.
679
     * This method is a recursive version of php array_diff_assoc().
680
     *
681
     * @param array $array1 Source array
682
     * @param array $array2 Reduce source array by this array
683
     * @return array Source array reduced by keys also present in second array
684
     */
685
    public static function arrayDiffAssocRecursive(array $array1, array $array2)
686
    {
687
        $differenceArray = [];
688
        foreach ($array1 as $key => $value) {
689
            if (! array_key_exists($key, $array2)) {
690
                $differenceArray[$key] = $value;
691
            } elseif (is_array($value)) {
692
                if (is_array($array2[$key])) {
693
                    $differenceArray[$key] = self::arrayDiffAssocRecursive($value, $array2[$key]);
694
                }
695
            }
696
        }
697
698
        return $differenceArray;
699
    }
700
701
    /**
702
     * Sorts an array by key recursive - uses natural sort order (aAbB-zZ).
703
     *
704
     * @param array $array array to be sorted recursively, passed by reference
705
     * @return bool always TRUE
706
     */
707
    public static function naturalKeySortRecursive(array &$array)
708
    {
709
        uksort($array, 'strnatcasecmp');
710
        foreach ($array as $key => &$value) {
711
            if (is_array($value)) {
712
                self::naturalKeySortRecursive($value);
713
            }
714
        }
715
716
        return true;
717
    }
718
}
719