Completed
Pull Request — master (#5)
by Chad
05:29
created

Arrays::fillIfKeysExist()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 11
rs 9.4285
cc 3
eloc 6
nc 3
nop 2
1
<?php
2
/**
3
 * Defines the \DominionEnterprises\Util\Arrays class.
4
 */
5
6
namespace DominionEnterprises\Util;
7
8
/**
9
 * Class of static array utility functions.
10
 */
11
final class Arrays
12
{
13
    /**
14
     * Simply returns an array value if the key exist or null if it does not.
15
     *
16
     * @param array $array the array to be searched
17
     * @param string|integer $key the key to search for
18
     * @param mixed $default the value to return if the $key is not found in $array
19
     *
20
     * @return mixed array value or given default value
21
     */
22
    public static function get(array $array, $key, $default = null)
23
    {
24
        return array_key_exists($key, $array) ? $array[$key] : $default;
25
    }
26
27
    /**
28
     * Simply returns an array value if the key isset,4 $default if it is not
29
     *
30
     * @param array $array the array to be searched
31
     * @param string|integer $key the key to search for
32
     * @param mixed $default the value to return if the $key is not found in $array or if the value of $key element is
33
     *                       null
34
     *
35
     * @return mixed array value or given default value
36
     */
37
    public static function getIfSet(array $array, $key, $default = null)
38
    {
39
        return isset($array[$key]) ? $array[$key] : $default;
40
    }
41
42
    /**
43
     * Sets destination array values to be the source values if the source key exist in the source array.
44
     *
45
     * @param array $source
46
     * @param array &$dest
47
     * @param array $keyMap mapping of dest keys to source keys. If $keyMap is associative, the keys will be the
48
     *                      destination keys. If numeric the values will be the destination keys
49
     *
50
     * @return void
51
     */
52 View Code Duplication
    public static function copyIfKeysExist(array $source, array &$dest, array $keyMap)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
53
    {
54
        foreach ($keyMap as $destKey => $sourceKey) {
55
            if (is_int($destKey)) {
56
                $destKey = $sourceKey;
57
            }
58
59
            if (array_key_exists($sourceKey, $source)) {
60
                $dest[$destKey] = $source[$sourceKey];
61
            }
62
        }
63
    }
64
65
    /**
66
     * Sets destination array values to be the source values if the source key is set in the source array.
67
     *
68
     * @param array $source
69
     * @param array &$dest
70
     * @param array $keyMap mapping of dest keys to source keys. If $keyMap is associative, the keys will be the
71
     *                      destination keys. If numeric the values will be the destination keys
72
     *
73
     * @return void
74
     */
75 View Code Duplication
    public static function copyIfSet(array $source, array &$dest, array $keyMap)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
76
    {
77
        foreach ($keyMap as $destKey => $sourceKey) {
78
            if (is_int($destKey)) {
79
                $destKey = $sourceKey;
80
            }
81
82
            if (isset($source[$sourceKey])) {
83
                $dest[$destKey] = $source[$sourceKey];
84
            }
85
        }
86
    }
87
88
    /**
89
     * Returns true and fills $value if $key exists in $array, otherwise fills $value with null and returns false
90
     *
91
     * @param array $array The array to pull from
92
     * @param string|integer $key The key to get
93
     * @param mixed &$value The value to set
94
     *
95
     * @return bool true if $key was found and filled in $value, false if $key was not found and $value was set to null
96
     */
97
    public static function tryGet(array $array, $key, &$value)
98
    {
99
        if ((is_string($key) || is_int($key)) && array_key_exists($key, $array)) {
100
            $value = $array[$key];
101
            return true;
102
        }
103
104
        $value = null;
105
        return false;
106
    }
107
108
    /**
109
     * Projects values of a key into an array.
110
     *
111
     * if $input = [
112
     *     ['key 1' => 'item 1 value 1', 'key 2' => 'item 1 value 2'],
113
     *     ['key 1' => 'item 2 value 1', 'key 2' => 'item 2 value 2'],
114
     *     ['key 1' => 'item 3 value 1'],
115
     * ]
116
     * and $key = 'key 2'
117
     * and $strictKeyCheck = false
118
     *
119
     * then return ['item 1 value 2', 'item 2 value 2']
120
     *
121
     * but if $strictKeyCheck = true then an InvalidArgumentException occurs since 'key 2' wasnt in item 3
122
     *
123
     * @param array $input the array to project from
124
     * @param string|integer $key the key which values we are to project
125
     * @param boolean $strictKeyCheck ensure key is in each $input array or not
126
     *
127
     * @return array the projection
128
     *
129
     * @throws \InvalidArgumentException if $strictKeyCheck was not a bool
130
     * @throws \InvalidArgumentException if a value in $input was not an array
131
     * @throws \InvalidArgumentException if a key was not in one of the $input arrays
132
     */
133
    public static function project(array $input, $key, $strictKeyCheck = true)
134
    {
135
        if ($strictKeyCheck !== false && $strictKeyCheck !== true) {
136
            throw new \InvalidArgumentException('$strictKeyCheck was not a bool');
137
        }
138
139
        $projection = [];
140
141
        foreach ($input as $itemKey => $item) {
142
            if (!is_array($item)) {
143
                throw new \InvalidArgumentException('a value in $input was not an array');
144
            }
145
146
            if (array_key_exists($key, $item)) {
147
                $projection[$itemKey] = $item[$key];
148
            } elseif ($strictKeyCheck) {
149
                throw new \InvalidArgumentException('key was not in one of the $input arrays');
150
            }
151
        }
152
153
        return $projection;
154
    }
155
156
    /**
157
     * Returns a sub set of the given $array based on the given $conditions
158
     *
159
     * @param array[] $array an array of arrays to be checked
160
     * @param array $conditions array of key/value pairs to filter by
161
     *
162
     * @return array the subset
163
     *
164
     * @throws \InvalidArgumentException if a value in $array was not an array
165
     */
166
    public static function where(array $array, array $conditions)
167
    {
168
        $result = [];
169
        foreach ($array as $item) {
170
            if (!is_array($item)) {
171
                throw new \InvalidArgumentException('a value in $array was not an array');
172
            }
173
174
            foreach ($conditions as $key => $value) {
175
                if (!array_key_exists($key, $item) || $item[$key] !== $value) {
176
                    continue 2; // continue to the next item in $array
177
                }
178
            }
179
180
            $result[] = $item;
181
        }
182
183
        return $result;
184
    }
185
186
    /**
187
     * Takes each item and embeds it into the destination array, returning the result.
188
     *
189
     * Each item's key is used as the key in the destination array so that keys are preserved.  Each resulting item in
190
     * the destination will be embedded into a field named by $fieldName.  Any items that don't have an entry in
191
     * destination already will be added, not skipped.
192
     *
193
     * For example, embedInto(['Joe', 'Sue'], 'lastName', [['firstName' => 'Billy'], ['firstName' => 'Bobby']]) will
194
     * return [['firstName' => 'Billy', 'lastName' => 'Joe'], ['firstName' => 'Bobby', 'lastName' => 'Sue']]
195
     *
196
     * @param array $items The items to embed into the result.
197
     * @param string $fieldName The name of the field to embed the items into.  This field must not exist in the
198
     *                          destination items already.
199
     * @param array $destination An optional array of arrays to embed the items into.  If this is not provided then
200
     *                           empty records are assumed and the new record will be created only containing
201
     *                           $fieldName.
202
     * @param bool $overwrite whether to overwrite $fieldName in $destination array
203
     *
204
     * @return array $destination, with all items in $items added using their keys, but underneath a nested $fieldName
205
     *               key.
206
     *
207
     * @throws \InvalidArgumentException if $fieldName was not a string
208
     * @throws \InvalidArgumentException if a value in $destination was not an array
209
     * @throws \Exception if $fieldName key already exists in a $destination array
210
     */
211
    public static function embedInto(array $items, $fieldName, array $destination = [], $overwrite = false)
212
    {
213
        if (!is_string($fieldName)) {
214
            throw new \InvalidArgumentException('$fieldName was not a string');
215
        }
216
217
        if ($overwrite !== false && $overwrite !== true) {
218
            throw new \InvalidArgumentException('$overwrite was not a bool');
219
        }
220
221
        foreach ($items as $key => $item) {
222
            if (array_key_exists($key, $destination)) {
223
                if (!is_array($destination[$key])) {
224
                    throw new \InvalidArgumentException('a value in $destination was not an array');
225
                }
226
227
                if (!$overwrite && array_key_exists($fieldName, $destination[$key])) {
228
                    throw new \Exception('$fieldName key already exists in a $destination array');
229
                }
230
231
                $destination[$key][$fieldName] = $item;
232
            } else {
233
                $destination[$key] = [$fieldName => $item];
234
            }
235
        }
236
237
        return $destination;
238
    }
239
240
    /**
241
     * Fills the given $template array with values from the $source array
242
     *
243
     * @param array $template the array to be filled
244
     * @param array $source the array to fetch values from
245
     *
246
     * @return array Returns a filled version of $template
247
     */
248
    public static function fillIfKeysExist(array $template, array $source)
249
    {
250
        $result = $template;
251
        foreach ($template as $key => $value) {
252
            if (array_key_exists($key, $source)) {
253
                $result[$key] = $source[$key];
254
            }
255
        }
256
257
        return $result;
258
    }
259
260
    /**
261
     * Extracts an associative array from the given multi-dimensional array.
262
     *
263
     * @param array $input The multi-dimensional array.
264
     * @param string|int $keyIndex The index to be used as the key of the resulting single dimensional result array.
265
     * @param string|int $valueIndex The index to be used as the value of the resulting single dimensional result array.
266
     *                               If a sub array does not contain this element null will be used as the value.
267
     * @param string $duplicateBehavior Instruct how to handle duplicate resulting values, 'takeFirst', 'takeLast',
268
     *                                  'throw'
269
     *
270
     * @return array an associative array
271
     *
272
     * @throws \InvalidArgumentException Thrown if $input is not an multi-dimensional array
273
     * @throws \InvalidArgumentException Thrown if $keyIndex is not an int or string
274
     * @throws \InvalidArgumentException Thrown if $valueIndex is not an int or string
275
     * @throws \InvalidArgumentException Thrown if $duplicateBehavior is not 'takeFirst', 'takeLast', 'throw'
276
     * @throws \UnexpectedValueException Thrown if a $keyIndex value is not a string or integer
277
     * @throws \Exception Thrown if $duplicatedBehavior is 'throw' and duplicate entries are found.
278
     */
279
    public static function extract(array $input, $keyIndex, $valueIndex, $duplicateBehavior = 'takeLast')
280
    {
281
        if (!in_array($duplicateBehavior, ['takeFirst', 'takeLast', 'throw'])) {
282
            throw new \InvalidArgumentException("\$duplicateBehavior was not 'takeFirst', 'takeLast', or 'throw'");
283
        }
284
285
        if (!is_string($keyIndex) && !is_int($keyIndex)) {
286
            throw new \InvalidArgumentException('$keyIndex was not a string or integer');
287
        }
288
289
        if (!is_string($valueIndex) && !is_int($valueIndex)) {
290
            throw new \InvalidArgumentException('$valueIndex was not a string or integer');
291
        }
292
293
        $result = [];
294
        foreach ($input as $index => $array) {
295
            if (!is_array($array)) {
296
                throw new \InvalidArgumentException('$arrays was not a multi-dimensional array');
297
            }
298
299
            $key = self::get($array, $keyIndex);
300
            if (!is_string($key) && !is_int($key)) {
301
                throw new \UnexpectedValueException(
302
                    "Value for \$arrays[{$index}][{$keyIndex}] was not a string or integer"
303
                );
304
            }
305
306
            $value = self::get($array, $valueIndex);
307
            if (!array_key_exists($key, $result)) {
308
                $result[$key] = $value;
309
                continue;
310
            }
311
312
            if ($duplicateBehavior === 'throw') {
313
                throw new \Exception("Duplicate entry for '{$key}' found.");
314
            }
315
316
            if ($duplicateBehavior === 'takeLast') {
317
                $result[$key] = $value;
318
            }
319
        }
320
321
        return $result;
322
    }
323
324
    /**
325
     * Returns the first set {@see isset()} value specified by the given array of keys.
326
     *
327
     * @param array $array The array containing the possible values.
328
     * @param array $keys Array of keys to search for. The first set value will be returned.
329
     * @param mixed $default The default value to return if no set value was found in the array.
330
     *
331
     * @return mixed Returns the found set value or the given default value.
332
     */
333
    public static function getFirstSet(array $array, array $keys, $default = null)
334
    {
335
        foreach ($keys as $key) {
336
            if (isset($array[$key])) {
337
                return $array[$key];
338
            }
339
        }
340
341
        return $default;
342
    }
343
344
    /**
345
     * Partitions the given $input array into an array of $partitionCount sub arrays.
346
     *
347
     * This is a slight modification of the function suggested on
348
     * http://php.net/manual/en/function.array-chunk.php#75022. This method does not pad with empty partitions and
349
     * ensures positive partition count.
350
     *
351
     * @param array $input The array to partition.
352
     * @param int $partitionCount The maximum number of partitions to create.
353
     * @param bool $preserveKeys Flag to preserve numeric array indexes. Associative indexes are preserved by default.
354
     *
355
     * @return array A multi-dimensional array containing $partitionCount sub arrays.
356
     *
357
     * @throws \InvalidArgumentException Thrown if $partitionCount is not a positive integer.
358
     * @throws \InvalidArgumentException Thrown if $preserveKeys is not a boolean value.
359
     */
360
    public static function partition(array $input, $partitionCount, $preserveKeys = false)
361
    {
362
        if (!is_int($partitionCount) || $partitionCount < 1) {
363
            throw new \InvalidArgumentException('$partitionCount must be a positive integer');
364
        }
365
366
        if ($preserveKeys !== false && $preserveKeys !== true) {
367
            throw new \InvalidArgumentException('$preserveKeys must be a boolean value');
368
        }
369
370
        $inputLength = count($input);
371
        $partitionLength = floor($inputLength / $partitionCount);
372
        $partitionRemainder = $inputLength % $partitionCount;
373
        $partitions = [];
374
        $sliceOffset = 0;
375
        for ($partitionIndex = 0; $partitionIndex < $partitionCount && $sliceOffset < $inputLength; $partitionIndex++) {
376
            $sliceLength = ($partitionIndex < $partitionRemainder) ? $partitionLength + 1 : $partitionLength;
377
            $partitions[$partitionIndex] = array_slice($input, $sliceOffset, $sliceLength, $preserveKeys);
378
            $sliceOffset += $sliceLength;
379
        }
380
381
        return $partitions;
382
    }
383
384
    /**
385
     * Unsets all elements in the given $array specified by $keys
386
     *
387
     * @param array &$array The array containing the elements to unset.
388
     * @param array $keys Array of keys to unset.
389
     *
390
     * @return void
391
     */
392
    public static function unsetAll(array &$array, array $keys)
393
    {
394
        foreach ($keys as $key) {
395
            unset($array[$key]);
396
        }
397
    }
398
399
    /**
400
     * Convert all empty strings or strings that contain only whitespace to null in the given array
401
     *
402
     * @param array &$array The array containing empty strings
403
     *
404
     * @return void
405
     */
406
    public static function nullifyEmptyStrings(array &$array)
407
    {
408
        foreach ($array as &$value) {
409
            if (is_string($value) && trim($value) === '') {
410
                $value = null;
411
            }
412
        }
413
    }
414
415
    /**
416
     * Traverses the given $array using the key path specified by $delimitedKey and returns the final value.
417
     *
418
     * Example:
419
     * <br />
420
     * <pre>
421
     * use DominionEnterprises\Util\Arrays;
422
     * $array = [
423
     *     'db' => [
424
     *         'host' => 'localhost',
425
     *         'login' => [
426
     *             'username' => 'scott',
427
     *             'password' => 'tiger',
428
     *         ],
429
     *     ],
430
     * ];
431
     * echo Arrays::getNested($array, 'db.login.username');
432
     * </pre>
433
     * <br />
434
     * Output:
435
     * <pre>
436
     * scott
437
     * </pre>
438
     *
439
     * @param array  $array        The array to traverse.
440
     * @param string $delimitedKey A string of keys to traverse into the array.
441
     * @param string $delimiter    A string specifiying how the keys are delimited. The default is '.'.
442
     *
443
     * @return mixed The value a the inner most key or null if a key does not exist.
444
     */
445
    final public static function getNested(array $array, $delimitedKey, $delimiter = '.')
446
    {
447
        $pointer = $array;
448
        foreach (explode($delimiter, $delimitedKey) as $key) {
449
            if (is_array($pointer) && array_key_exists($key, $pointer)) {
450
                $pointer = $pointer[$key];
451
                continue;
452
            }
453
454
            return null;
455
        }
456
457
        return $pointer;
458
    }
459
}
460