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.

Arrays   F
last analyzed

Complexity

Total Complexity 85

Size/Duplication

Total Lines 624
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 85
lcom 1
cbo 0
dl 0
loc 624
rs 1.976
c 0
b 0
f 0

25 Methods

Rating   Name   Duplication   Size   Complexity  
A getFirstSet() 0 10 3
A partition() 0 19 5
A unsetAll() 0 6 2
A nullifyEmptyStrings() 0 8 4
A getNested() 0 14 4
A changeKeyCase() 0 20 5
A flatten() 0 17 5
A getAllWhereKeyExists() 0 11 3
A anyKeysExist() 0 10 3
A get() 0 4 2
A getIfSet() 0 4 2
A copyIfKeysExist() 0 7 1
A copyIfSet() 0 7 1
A tryGet() 0 10 4
A project() 0 16 4
A where() 0 17 6
A embedInto() 0 23 5
A fillIfKeysExist() 0 11 3
B extract() 0 38 6
A rename() 0 14 4
A underscoreKeys() 0 11 2
A camelCaseKeys() 0 13 2
A ensureValidKey() 0 10 3
A ensureIsArray() 0 10 2
A copyValueIf() 0 12 4

How to fix   Complexity   

Complex Class

Complex classes like Arrays 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 Arrays, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Defines the \TraderInteractive\Util\Arrays class.
4
 */
5
6
namespace TraderInteractive\Util;
7
8
use InvalidArgumentException;
9
use UnexpectedValueException;
10
11
/**
12
 * Class of static array utility functions.
13
 */
14
final class Arrays
15
{
16
    /**
17
     * Const for lower cased array keys.
18
     *
19
     * @const integer
20
     */
21
    const CASE_LOWER = 1;
22
23
    /**
24
     * Const for upper cased array keys.
25
     *
26
     * @const integer
27
     */
28
    const CASE_UPPER = 2;
29
30
    /**
31
     * Const for camel caps cased array keys.
32
     *
33
     * @const integer
34
     */
35
    const CASE_CAMEL_CAPS = 4;
36
37
    /**
38
     * Const for underscored cased array keys.
39
     *
40
     * @const integer
41
     */
42
    const CASE_UNDERSCORE = 8;
43
44
    /**
45
     * Simply returns an array value if the key exist or null if it does not.
46
     *
47
     * @param array $array the array to be searched
48
     * @param string|integer $key the key to search for
49
     * @param mixed $default the value to return if the $key is not found in $array
50
     *
51
     * @return mixed array value or given default value
52
     */
53
    public static function get(array $array, $key, $default = null)
54
    {
55
        return array_key_exists($key, $array) ? $array[$key] : $default;
56
    }
57
58
    /**
59
     * Simply returns an array value if the key isset,4 $default if it is not
60
     *
61
     * @param array $array the array to be searched
62
     * @param string|integer $key the key to search for
63
     * @param mixed $default the value to return if the $key is not found in $array or if the value of $key element is
64
     *                       null
65
     *
66
     * @return mixed array value or given default value
67
     */
68
    public static function getIfSet(array $array, $key, $default = null)
69
    {
70
        return isset($array[$key]) ? $array[$key] : $default;
71
    }
72
73
    /**
74
     * Sets destination array values to be the source values if the source key exist in the source array.
75
     *
76
     * @param array $source
77
     * @param array &$dest
78
     * @param array $keyMap mapping of dest keys to source keys. If $keyMap is associative, the keys will be the
79
     *                      destination keys. If numeric the values will be the destination keys
80
     *
81
     * @return void
82
     */
83
    public static function copyIfKeysExist(array $source, array &$dest, array $keyMap)
84
    {
85
        $callable = function (array $source, $key) {
86
            return array_key_exists($key, $source);
87
        };
88
        self::copyValueIf($source, $dest, $keyMap, $callable);
89
    }
90
91
    /**
92
     * Sets destination array values to be the source values if the source key is set in the source array.
93
     *
94
     * @param array $source
95
     * @param array &$dest
96
     * @param array $keyMap mapping of dest keys to source keys. If $keyMap is associative, the keys will be the
97
     *                      destination keys. If numeric the values will be the destination keys
98
     *
99
     * @return void
100
     */
101
    public static function copyIfSet(array $source, array &$dest, array $keyMap)
102
    {
103
        $callable = function (array $source, $key) {
104
            return isset($source[$key]);
105
        };
106
        self::copyValueIf($source, $dest, $keyMap, $callable);
107
    }
108
109
    /**
110
     * Returns true and fills $value if $key exists in $array, otherwise fills $value with null and returns false
111
     *
112
     * @param array $array The array to pull from
113
     * @param string|integer $key The key to get
114
     * @param mixed &$value The value to set
115
     *
116
     * @return bool true if $key was found and filled in $value, false if $key was not found and $value was set to null
117
     */
118
    public static function tryGet(array $array, $key, &$value) : bool
119
    {
120
        if ((is_string($key) || is_int($key)) && array_key_exists($key, $array)) {
121
            $value = $array[$key];
122
            return true;
123
        }
124
125
        $value = null;
126
        return false;
127
    }
128
129
    /**
130
     * Projects values of a key into an array.
131
     *
132
     * if $input = [
133
     *     ['key 1' => 'item 1 value 1', 'key 2' => 'item 1 value 2'],
134
     *     ['key 1' => 'item 2 value 1', 'key 2' => 'item 2 value 2'],
135
     *     ['key 1' => 'item 3 value 1'],
136
     * ]
137
     * and $key = 'key 2'
138
     * and $strictKeyCheck = false
139
     *
140
     * then return ['item 1 value 2', 'item 2 value 2']
141
     *
142
     * but if $strictKeyCheck = true then an InvalidArgumentException occurs since 'key 2' wasnt in item 3
143
     *
144
     * @param array $input the array to project from
145
     * @param string|integer $key the key which values we are to project
146
     * @param boolean $strictKeyCheck ensure key is in each $input array or not
147
     *
148
     * @return array the projection
149
     *
150
     * @throws \InvalidArgumentException if a value in $input was not an array
151
     * @throws \InvalidArgumentException if a key was not in one of the $input arrays
152
     */
153
    public static function project(array $input, $key, bool $strictKeyCheck = true) : array
154
    {
155
        $projection = [];
156
157
        foreach ($input as $itemKey => $item) {
158
            self::ensureIsArray($item, 'a value in $input was not an array');
159
160
            if (array_key_exists($key, $item)) {
161
                $projection[$itemKey] = $item[$key];
162
            } elseif ($strictKeyCheck) {
163
                throw new \InvalidArgumentException('key was not in one of the $input arrays');
164
            }
165
        }
166
167
        return $projection;
168
    }
169
170
    /**
171
     * Returns a sub set of the given $array based on the given $conditions
172
     *
173
     * @param array[] $array an array of arrays to be checked
174
     * @param array $conditions array of key/value pairs to filter by
175
     * @param bool  $preserveKeys Flag to preserve the original keys of the array
176
     *
177
     * @return array the subset
178
     *
179
     * @throws \InvalidArgumentException if a value in $array was not an array
180
     */
181
    public static function where(array $array, array $conditions, bool $preserveKeys = false) : array
182
    {
183
        $result = [];
184
        foreach ($array as $index => $item) {
185
            self::ensureIsArray($item, 'a value in $array was not an array');
186
187
            foreach ($conditions as $key => $value) {
188
                if (!array_key_exists($key, $item) || $item[$key] !== $value) {
189
                    continue 2; // continue to the next item in $array
190
                }
191
            }
192
193
            $result[$index] = $item;
194
        }
195
196
        return $preserveKeys ? $result : array_values($result);
197
    }
198
199
    /**
200
     * Takes each item and embeds it into the destination array, returning the result.
201
     *
202
     * Each item's key is used as the key in the destination array so that keys are preserved.  Each resulting item in
203
     * the destination will be embedded into a field named by $fieldName.  Any items that don't have an entry in
204
     * destination already will be added, not skipped.
205
     *
206
     * For example, embedInto(['Joe', 'Sue'], 'lastName', [['firstName' => 'Billy'], ['firstName' => 'Bobby']]) will
207
     * return [['firstName' => 'Billy', 'lastName' => 'Joe'], ['firstName' => 'Bobby', 'lastName' => 'Sue']]
208
     *
209
     * @param array $items The items to embed into the result.
210
     * @param string $fieldName The name of the field to embed the items into.  This field must not exist in the
211
     *                          destination items already.
212
     * @param array $destination An optional array of arrays to embed the items into.  If this is not provided then
213
     *                           empty records are assumed and the new record will be created only containing
214
     *                           $fieldName.
215
     * @param bool $overwrite whether to overwrite $fieldName in $destination array
216
     *
217
     * @return array $destination, with all items in $items added using their keys, but underneath a nested $fieldName
218
     *               key.
219
     *
220
     * @throws \InvalidArgumentException if $fieldName was not a string
221
     * @throws \InvalidArgumentException if a value in $destination was not an array
222
     * @throws \Exception if $fieldName key already exists in a $destination array
223
     */
224
    public static function embedInto(
225
        array $items,
226
        string $fieldName,
227
        array $destination = [],
228
        bool $overwrite = false
229
    ) : array {
230
        foreach ($items as $key => $item) {
231
            if (!array_key_exists($key, $destination)) {
232
                $destination[$key] = [$fieldName => $item];
233
                continue;
234
            }
235
236
            self::ensureIsArray($destination[$key], 'a value in $destination was not an array');
237
238
            if (!$overwrite && array_key_exists($fieldName, $destination[$key])) {
239
                throw new \Exception('$fieldName key already exists in a $destination array');
240
            }
241
242
            $destination[$key][$fieldName] = $item;
243
        }
244
245
        return $destination;
246
    }
247
248
    /**
249
     * Fills the given $template array with values from the $source array
250
     *
251
     * @param array $template the array to be filled
252
     * @param array $source the array to fetch values from
253
     *
254
     * @return array Returns a filled version of $template
255
     */
256
    public static function fillIfKeysExist(array $template, array $source)
257
    {
258
        $result = $template;
259
        foreach ($template as $key => $value) {
260
            if (array_key_exists($key, $source)) {
261
                $result[$key] = $source[$key];
262
            }
263
        }
264
265
        return $result;
266
    }
267
268
    /**
269
     * Extracts an associative array from the given multi-dimensional array.
270
     *
271
     * @param array $input The multi-dimensional array.
272
     * @param string|int $keyIndex The index to be used as the key of the resulting single dimensional result array.
273
     * @param string|int $valueIndex The index to be used as the value of the resulting single dimensional result array.
274
     *                               If a sub array does not contain this element null will be used as the value.
275
     * @param string $duplicateBehavior Instruct how to handle duplicate resulting values, 'takeFirst', 'takeLast',
276
     *                                  'throw'
277
     *
278
     * @return array an associative array
279
     *
280
     * @throws \InvalidArgumentException Thrown if $input is not an multi-dimensional array
281
     * @throws \InvalidArgumentException Thrown if $keyIndex is not an int or string
282
     * @throws \InvalidArgumentException Thrown if $valueIndex is not an int or string
283
     * @throws \InvalidArgumentException Thrown if $duplicateBehavior is not 'takeFirst', 'takeLast', 'throw'
284
     * @throws \UnexpectedValueException Thrown if a $keyIndex value is not a string or integer
285
     * @throws \Exception Thrown if $duplicatedBehavior is 'throw' and duplicate entries are found.
286
     */
287
    public static function extract(
288
        array $input,
289
        $keyIndex,
290
        $valueIndex,
291
        string $duplicateBehavior = 'takeLast'
292
    ) : array {
293
        if (!in_array($duplicateBehavior, ['takeFirst', 'takeLast', 'throw'])) {
294
            throw new \InvalidArgumentException("\$duplicateBehavior was not 'takeFirst', 'takeLast', or 'throw'");
295
        }
296
297
        self::ensureValidKey($keyIndex, '$keyIndex was not a string or integer');
298
        self::ensureValidKey($valueIndex, '$valueIndex was not a string or integer');
299
300
        $result = [];
301
        foreach ($input as $index => $array) {
302
            self::ensureIsArray($array, '$arrays was not a multi-dimensional array');
303
304
            $key = self::get($array, $keyIndex);
305
            $message = "Value for \$arrays[{$index}][{$keyIndex}] was not a string or integer";
306
            self::ensureValidKey($key, $message, UnexpectedValueException::class);
307
308
            $value = self::get($array, $valueIndex);
309
            if (!array_key_exists($key, $result)) {
310
                $result[$key] = $value;
311
                continue;
312
            }
313
314
            if ($duplicateBehavior === 'throw') {
315
                throw new \Exception("Duplicate entry for '{$key}' found.");
316
            }
317
318
            if ($duplicateBehavior === 'takeLast') {
319
                $result[$key] = $value;
320
            }
321
        }
322
323
        return $result;
324
    }
325
326
    /**
327
     * Returns the first set {@see isset()} value specified by the given array of keys.
328
     *
329
     * @param array $array The array containing the possible values.
330
     * @param array $keys Array of keys to search for. The first set value will be returned.
331
     * @param mixed $default The default value to return if no set value was found in the array.
332
     *
333
     * @return mixed Returns the found set value or the given default value.
334
     */
335
    public static function getFirstSet(array $array, array $keys, $default = null)
336
    {
337
        foreach ($keys as $key) {
338
            if (isset($array[$key])) {
339
                return $array[$key];
340
            }
341
        }
342
343
        return $default;
344
    }
345
346
    /**
347
     * Partitions the given $input array into an array of $partitionCount sub arrays.
348
     *
349
     * This is a slight modification of the function suggested on
350
     * http://php.net/manual/en/function.array-chunk.php#75022. This method does not pad with empty partitions and
351
     * ensures positive partition count.
352
     *
353
     * @param array $input The array to partition.
354
     * @param int $partitionCount The maximum number of partitions to create.
355
     * @param bool $preserveKeys Flag to preserve numeric array indexes. Associative indexes are preserved by default.
356
     *
357
     * @return array A multi-dimensional array containing $partitionCount sub arrays.
358
     *
359
     * @throws \InvalidArgumentException Thrown if $partitionCount is not a positive integer.
360
     * @throws \InvalidArgumentException Thrown if $preserveKeys is not a boolean value.
361
     */
362
    public static function partition(array $input, int $partitionCount, bool $preserveKeys = false) : array
363
    {
364
        if ($partitionCount < 1) {
365
            throw new \InvalidArgumentException('$partitionCount must be a positive integer');
366
        }
367
368
        $inputLength = count($input);
369
        $partitionLength = floor($inputLength / $partitionCount);
370
        $partitionRemainder = $inputLength % $partitionCount;
371
        $partitions = [];
372
        $sliceOffset = 0;
373
        for ($partitionIndex = 0; $partitionIndex < $partitionCount && $sliceOffset < $inputLength; $partitionIndex++) {
374
            $sliceLength = ($partitionIndex < $partitionRemainder) ? $partitionLength + 1 : $partitionLength;
375
            $partitions[$partitionIndex] = array_slice($input, $sliceOffset, $sliceLength, $preserveKeys);
376
            $sliceOffset += $sliceLength;
377
        }
378
379
        return $partitions;
380
    }
381
382
    /**
383
     * Unsets all elements in the given $array specified by $keys
384
     *
385
     * @param array &$array The array containing the elements to unset.
386
     * @param array $keys Array of keys to unset.
387
     *
388
     * @return void
389
     */
390
    public static function unsetAll(array &$array, array $keys)
391
    {
392
        foreach ($keys as $key) {
393
            unset($array[$key]);
394
        }
395
    }
396
397
    /**
398
     * Convert all empty strings or strings that contain only whitespace to null in the given array
399
     *
400
     * @param array &$array The array containing empty strings
401
     *
402
     * @return void
403
     */
404
    public static function nullifyEmptyStrings(array &$array)
405
    {
406
        foreach ($array as &$value) {
407
            if (is_string($value) && trim($value) === '') {
408
                $value = null;
409
            }
410
        }
411
    }
412
413
    /**
414
     * Traverses the given $array using the key path specified by $delimitedKey and returns the final value.
415
     *
416
     * Example:
417
     * <br />
418
     * <pre>
419
     * use TraderInteractive\Util\Arrays;
420
     * $array = [
421
     *     'db' => [
422
     *         'host' => 'localhost',
423
     *         'login' => [
424
     *             'username' => 'scott',
425
     *             'password' => 'tiger',
426
     *         ],
427
     *     ],
428
     * ];
429
     * echo Arrays::getNested($array, 'db.login.username');
430
     * </pre>
431
     * <br />
432
     * Output:
433
     * <pre>
434
     * scott
435
     * </pre>
436
     *
437
     * @param array  $array        The array to traverse.
438
     * @param string $delimitedKey A string of keys to traverse into the array.
439
     * @param string $delimiter    A string specifiying how the keys are delimited. The default is '.'.
440
     *
441
     * @return mixed The value at the inner most key or null if a key does not exist.
442
     */
443
    final public static function getNested(array $array, string $delimitedKey, string $delimiter = '.')
444
    {
445
        $pointer = $array;
446
        foreach (explode($delimiter, $delimitedKey) as $key) {
447
            if (is_array($pointer) && array_key_exists($key, $pointer)) {
448
                $pointer = $pointer[$key];
449
                continue;
450
            }
451
452
            return null;
453
        }
454
455
        return $pointer;
456
    }
457
458
    /**
459
     * Changes the case of all keys in an array. Numbered indices are left as is.
460
     *
461
     * @param array   $input The array to work on.
462
     * @param integer $case  The case to which the keys should be set.
463
     *
464
     * @return array Returns an array with its keys case changed.
465
     */
466
    public static function changeKeyCase(array $input, int $case = self::CASE_LOWER) : array
467
    {
468
        if ($case & self::CASE_UNDERSCORE) {
469
            $input = self::underscoreKeys($input);
470
        }
471
472
        if ($case & self::CASE_CAMEL_CAPS) {
473
            $input = self::camelCaseKeys($input);
474
        }
475
476
        if ($case & self::CASE_UPPER) {
477
            $input = array_change_key_case($input, \CASE_UPPER);
478
        }
479
480
        if ($case & self::CASE_LOWER) {
481
            $input = array_change_key_case($input, \CASE_LOWER);
482
        }
483
484
        return $input;
485
    }
486
487
    /**
488
     * Converts a multi-dimensional array into a single associative array whose keys are the concatinated keys
489
     *
490
     * @param array  $input     The array to flatten
491
     * @param string $delimiter The separator for the concatinated keys.
492
     *
493
     * @return array The flattened array
494
     */
495
    final public static function flatten(array $input, string $delimiter = '.') : array
496
    {
497
        $args = func_get_args();
498
        $prefix = count($args) === 3 ? array_pop($args) : '';
499
        $result = [];
500
        foreach ($input as $key => $value) {
501
            $newKey = $prefix . (empty($prefix) ? '' : $delimiter) . $key;
502
            if (is_array($value)) {
503
                $result = array_merge($result, self::flatten($value, $delimiter, $newKey));
504
                continue;
505
            }
506
507
            $result[$newKey] = $value;
508
        }
509
510
        return $result;
511
    }
512
513
    /**
514
     * Returns all elements in the given $input array which contain the specifed $targetKey index.
515
     *
516
     * @param array          $input     The multi-dimensional array to check.
517
     * @param string|integer $targetKey The key to search for.
518
     *
519
     * @return array All elements of $input which contained $targetKey element with original keys preserved.
520
     */
521
    final public static function getAllWhereKeyExists(array $input, $targetKey) : array
522
    {
523
        $result = [];
524
        foreach ($input as $key => $value) {
525
            if (array_key_exists($targetKey, $value)) {
526
                $result[$key] = $value;
527
            }
528
        }
529
530
        return $result;
531
    }
532
533
    /**
534
     * Returns TRUE if any of the given $keys exist in the $input array.
535
     *
536
     * @param array $array An array with keys to check.
537
     * @param array $keys  The keys to check
538
     *
539
     * @return bool
540
     */
541
    final public static function anyKeysExist(array $array, array $keys) : bool
542
    {
543
        foreach ($keys as $key) {
544
            if (array_key_exists($key, $array)) {
545
                return true;
546
            }
547
        }
548
549
        return false;
550
    }
551
552
    /**
553
     * Method to rename a key within array to a new key name.
554
     *
555
     * @param array  $input     The array containing the element to rename.
556
     * @param string $oldKey    The old key value.
557
     * @param string $newKey    The new key value.
558
     * @param bool   $overwrite Flag to allow overwriting if the $newKey exists in the array
559
     *
560
     * @return void
561
     */
562
    final public static function rename(array &$input, string $oldKey, string $newKey, bool $overwrite = false)
563
    {
564
        if (!array_key_exists($oldKey, $input)) {
565
            throw new InvalidArgumentException("{$oldKey} does not exist in the given array");
566
        }
567
568
        if (array_key_exists($newKey, $input) && !$overwrite) {
569
            throw new InvalidArgumentException("{$newKey} found the given array");
570
        }
571
572
        $value = $input[$oldKey];
573
        unset($input[$oldKey]);
574
        $input[$newKey] = $value;
575
    }
576
577
    private static function underscoreKeys(array $input) : array
578
    {
579
        $copy = [];
580
        foreach ($input as $key => $value) {
581
            $copy[preg_replace("/([a-z])([A-Z0-9])/", '$1_$2', $key)] = $value;
582
        }
583
584
        $input = $copy;
585
        unset($copy); //garbage collection
586
        return $input;
587
    }
588
589
    private static function camelCaseKeys(array $input) : array
590
    {
591
        $copy = [];
592
        foreach ($input as $key => $value) {
593
            $key = implode(' ', array_filter(preg_split('/[^a-z0-9]/i', $key)));
594
            $key = lcfirst(str_replace(' ', '', ucwords(strtolower($key))));
595
            $copy[$key] = $value;
596
        }
597
598
        $input = $copy;
599
        unset($copy); //garbage collection
600
        return $input;
601
    }
602
603
    private static function ensureValidKey(
604
        $key,
605
        string $message,
606
        string $exceptionClass = '\\InvalidArgumentException'
607
    ) {
608
        if (!is_string($key) && !is_int($key)) {
609
            $reflectionClass = new \ReflectionClass($exceptionClass);
610
            throw $reflectionClass->newInstanceArgs([$message]);
611
        }
612
    }
613
614
    private static function ensureIsArray(
615
        $value,
616
        string $message,
617
        string $exceptionClass = '\\InvalidArgumentException'
618
    ) {
619
        if (!is_array($value)) {
620
            $reflectionClass = new \ReflectionClass($exceptionClass);
621
            throw $reflectionClass->newInstanceArgs([$message]);
622
        }
623
    }
624
625
    private static function copyValueIf(array $source, array &$dest, array $keyMap, callable $condition)
626
    {
627
        foreach ($keyMap as $destKey => $sourceKey) {
628
            if (is_int($destKey)) {
629
                $destKey = $sourceKey;
630
            }
631
632
            if ($condition($source, $sourceKey)) {
633
                $dest[$destKey] = $source[$sourceKey];
634
            }
635
        }
636
    }
637
}
638