These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php namespace nyx\utils; |
||
2 | |||
3 | // External dependencies |
||
4 | use nyx\diagnostics; |
||
5 | |||
6 | /** |
||
7 | * Arr |
||
8 | * |
||
9 | * The Arr class provides a few helper methods to make dealing with arrays easier. |
||
10 | * |
||
11 | * All methods which work with string delimited keys accept a string delimiter. If none is given (ie. null is passed), |
||
12 | * the default delimiter (dot) set statically in this class will be used. |
||
13 | * |
||
14 | * Some code in this class can be simplified and some duplication could be avoided but it is laid out so that the |
||
15 | * most common use cases are checked for first with performance being the priority. |
||
16 | * |
||
17 | * Some methods have aliases. To avoid the overhead please use the base methods, not the aliases. The methods which |
||
18 | * have aliases are documented as such and each alias directly points to the base method. |
||
19 | * |
||
20 | * Note: This class is based on Laravel, FuelPHP, Lo-dash and a few others, but certain methods which appear in |
||
21 | * those are not included since they would add overhead we consider 'not worth it' and don't want to encourage |
||
22 | * the use thereof: |
||
23 | * |
||
24 | * - Arr::each() -> use array_map() instead. |
||
25 | * - Arr::filter(), Arr::reject() -> use array_filter() instead. |
||
26 | * - Arr::range() -> use range() instead. |
||
27 | * - Arr::repeat() -> use array_fill() instead. |
||
28 | * - Arr::search() -> use array_search() instead. |
||
29 | * - Arr::shuffle() -> use shuffle() instead. |
||
30 | * - Arr::size() -> use count() instead. |
||
31 | * |
||
32 | * @package Nyx\Utils |
||
33 | * @version 0.1.0 |
||
34 | * @author Michal Chojnacki <[email protected]> |
||
35 | * @copyright 2012-2016 Nyx Dev Team |
||
36 | * @link http://docs.muyo.io/nyx/utils/index.html |
||
37 | * @todo Arr::sort() and Arr:sortBy() (add sortBy() to core\traits\Collection as well). |
||
38 | * @todo Add ArrayObject support? How? Just strip the array type hints so as to not add overhead with checks? |
||
39 | */ |
||
40 | class Arr |
||
41 | { |
||
42 | /** |
||
43 | * The traits of the Arr class. |
||
44 | */ |
||
45 | use traits\StaticallyExtendable; |
||
46 | |||
47 | /** |
||
48 | * @var string The default delimiter to use to separate array dimensions. |
||
49 | */ |
||
50 | public static $delimiter = '.'; |
||
51 | |||
52 | /** |
||
53 | * Adds an element to the given array but only if it does not yet exist. |
||
54 | * |
||
55 | * Note: Null as value of an item is considered a non-existing item for the purposes |
||
56 | * of this method. |
||
57 | * |
||
58 | * @param array $array The array to which the element should be added. |
||
59 | * @param string $key The key at which the value should be added. |
||
60 | * @param mixed $value The value of the element. |
||
61 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
62 | */ |
||
63 | public static function add(array& $array, string $key, $value, string $delimiter = null) |
||
64 | { |
||
65 | if (null === static::get($array, $key, null, $delimiter)) { |
||
66 | static::set($array, $key, $value, $delimiter); |
||
67 | } |
||
68 | } |
||
69 | |||
70 | /** |
||
71 | * Checks whether all elements in the given array pass the given truth test. |
||
72 | * |
||
73 | * Aliases: |
||
74 | * - @see Arr::every() |
||
75 | * |
||
76 | * @param array $array The array to traverse. |
||
77 | * @param callable $callback The truth test the elements should pass. |
||
78 | * @return bool True when the elements passed the truth test, false otherwise. |
||
79 | */ |
||
80 | public static function all(array $array, callable $callback) : bool |
||
81 | { |
||
82 | // Map the array and then search for a 'false' boolean. If none is found, |
||
83 | // we assume all elements passed the test. |
||
84 | return !in_array(false, array_map($callback, $array), true); |
||
85 | } |
||
86 | |||
87 | /** |
||
88 | * Checks whether any of the elements in the given array passes the given truth test. |
||
89 | * |
||
90 | * Aliases: |
||
91 | * - @see Arr::some() |
||
92 | * |
||
93 | * @param array $array The array to traverse. |
||
94 | * @param callable $callback The truth test the elements should pass. |
||
95 | * @return bool True when at least on the the elements passed the truth test, false |
||
96 | * otherwise. |
||
97 | */ |
||
98 | public static function any(array $array, callable $callback) : bool |
||
99 | { |
||
100 | // Map the array and then search for a 'true' boolean. If at least one is found, |
||
101 | // we assume at least one element passed the test. |
||
102 | return in_array(true, array_map($callback, $array), true); |
||
103 | } |
||
104 | |||
105 | /** |
||
106 | * Returns the average value of the given array. |
||
107 | * |
||
108 | * @param array $array The array to traverse. |
||
109 | * @param int $decimals The number of decimals to return. |
||
110 | * @return float The average value. |
||
111 | */ |
||
112 | public static function average(array $array, int $decimals = 0) : float |
||
113 | { |
||
114 | return round((array_sum($array) / count($array)), $decimals); |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Removes all elements containing falsy values from the given array. The keys are preserved. |
||
119 | * |
||
120 | * See {@link http://php.net/manual/en/language.types.boolean.php} for information on which values evaluate |
||
121 | * to false. |
||
122 | * |
||
123 | * @param array $array The array to traverse. |
||
124 | * @return array The resulting array. |
||
125 | */ |
||
126 | public static function clean(array $array) : array |
||
127 | { |
||
128 | return array_filter($array, function($value) { |
||
129 | return (bool) $value; |
||
130 | }); |
||
131 | } |
||
132 | |||
133 | /** |
||
134 | * Collapses an array of arrays into a single array. Not recursive, ie. only the first dimension of arrays |
||
135 | * will be collapsed down. |
||
136 | * |
||
137 | * Standard array_merge() rules apply - @link http://php.net/manual/en/function.array-merge.php - |
||
138 | * non-array values with numeric keys will be appended to the resulting array in the order they are given, while |
||
139 | * non-array values with non-numeric keys will have their keys preserved but the values may be overwritten by |
||
140 | * the nested arrays being collapsed down if those contain values with the same non-numeric keys. Latter arrays |
||
141 | * overwrite previous arrays' keys on collisions. |
||
142 | * |
||
143 | * @param array $array The array to collapse. |
||
144 | * @return array The resulting array. |
||
145 | */ |
||
146 | public static function collapse(array $array) : array |
||
147 | { |
||
148 | $results = []; |
||
149 | |||
150 | foreach ($array as $key => $item) { |
||
151 | |||
152 | // Nested arrays will be merged in (non-recursively). |
||
153 | if (is_array($item)) { |
||
154 | $results = array_merge($results, $item); continue; |
||
155 | } |
||
156 | |||
157 | // Values with numeric keys will be appended in any case. |
||
158 | if (is_int($key)) { |
||
159 | $results[] = $item; continue; |
||
160 | } |
||
161 | |||
162 | // Non-numeric keys. If we've got the given key in $results already, it means it was merged |
||
163 | // in from one of the nested arrays and those are meant to overwrite the initial values on collisions - |
||
164 | // thus we're not going to do anything in that case. |
||
165 | if (!array_key_exists($key, $results)) { |
||
166 | $results[$key] = $item; |
||
167 | } |
||
168 | } |
||
169 | |||
170 | return $results; |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * Checks whether the given value is contained within the given array. Equivalent of a recursive in_array. |
||
175 | * When you are sure you are dealing with a 1-dimensional array, use in_array instead to avoid the overhead. |
||
176 | * |
||
177 | * @param array $haystack The array to search in. |
||
178 | * @param mixed $needle The value to search for. |
||
179 | * @param bool $strict Whether strict equality matches should be performed on the values. |
||
180 | * @return bool True when the value was found in the array, false otherwise. |
||
181 | */ |
||
182 | public static function contains(array $haystack, $needle, bool $strict = true) |
||
183 | { |
||
184 | foreach ($haystack as $value) { |
||
185 | if ((!$strict && $needle == $value) || $needle === $value) { |
||
186 | return true; |
||
187 | } |
||
188 | |||
189 | if (is_array($value) && static::contains($needle, $value, $strict)) { |
||
190 | return true; |
||
191 | } |
||
192 | } |
||
193 | |||
194 | return false; |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Flattens a multi-dimensional array using the given delimiter. |
||
199 | * |
||
200 | * @param array $array The initial array. |
||
201 | * @param string $prepend A string that should be prepended to the keys. |
||
202 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
203 | * @return array The resulting array. |
||
204 | */ |
||
205 | public static function delimit(array $array, string $prepend = '', string $delimiter = null) |
||
206 | { |
||
207 | // Results holder. |
||
208 | $results = []; |
||
209 | |||
210 | // Which string delimiter should we use? |
||
211 | if (null === $delimiter) { |
||
212 | $delimiter = static::$delimiter; |
||
213 | } |
||
214 | |||
215 | foreach ($array as $key => $value) { |
||
216 | if (is_array($value) && !empty($value)) { |
||
217 | $results = array_merge($results, static::delimit($value, $prepend.$key.$delimiter)); |
||
218 | } else { |
||
219 | $results[$prepend.$key] = $value; |
||
220 | } |
||
221 | } |
||
222 | |||
223 | return $results; |
||
224 | } |
||
225 | |||
226 | /** |
||
227 | * Alias for @see Arr::find() |
||
228 | */ |
||
229 | public static function detect(array $array, callable $callback, $default = null) |
||
230 | { |
||
231 | return static::find($array, $callback, $default); |
||
232 | } |
||
233 | |||
234 | /** |
||
235 | * Divides an array into two arrays - the first containing the keys, the second containing the values. |
||
236 | * |
||
237 | * @param array $array The initial array. |
||
238 | * @return array The resulting array. |
||
239 | */ |
||
240 | public static function divide(array $array) |
||
241 | { |
||
242 | return [array_keys($array), array_values($array)]; |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * Alias for @see Arr::all() |
||
247 | */ |
||
248 | public static function every(array $array, callable $callback) |
||
249 | { |
||
250 | return static::all($array, $callback); |
||
251 | } |
||
252 | |||
253 | /** |
||
254 | * Returns a subset of the given array, containing all keys except for the ones specified. |
||
255 | * |
||
256 | * @param array $array The initial array. |
||
257 | * @param array $keys An array of keys to exclude from the initial array. |
||
258 | * @return array |
||
259 | */ |
||
260 | public static function except(array $array, array $keys) |
||
261 | { |
||
262 | return array_diff_key($array, array_flip($keys)); |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * Fetches a flattened array of an element nested in the initial array. |
||
267 | * |
||
268 | * @param array $array The initial array. |
||
269 | * @param string $key The string delimited key. |
||
270 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
271 | * @return array The resulting array. |
||
272 | */ |
||
273 | public static function fetch(array $array, string $key, string $delimiter = null) : array |
||
274 | { |
||
275 | // Results holder. |
||
276 | $results = []; |
||
277 | |||
278 | // Which string delimiter should we use? |
||
279 | if (null === $delimiter) { |
||
280 | $delimiter = static::$delimiter; |
||
281 | } |
||
282 | |||
283 | foreach (explode($delimiter, $key) as $segment) { |
||
284 | $results = []; |
||
285 | |||
286 | foreach ($array as $value) { |
||
287 | $value = (array) $value; |
||
288 | |||
289 | $results[] = $value[$segment]; |
||
290 | } |
||
291 | |||
292 | $array = array_values($results); |
||
293 | } |
||
294 | |||
295 | return array_values($results); |
||
296 | } |
||
297 | |||
298 | /** |
||
299 | * Returns the first value which passes the given truth test. |
||
300 | * |
||
301 | * Aliases: |
||
302 | * - @see Arr::detect() |
||
303 | * |
||
304 | * @param array $array The array to traverse. |
||
305 | * @param callable $callback The truth test the value should pass. |
||
306 | * @param mixed $default The default value to be returned if none of the elements passes the test. |
||
307 | * @return mixed |
||
308 | */ |
||
309 | public static function find(array $array, callable $callback, $default = null) |
||
310 | { |
||
311 | foreach ($array as $key => $value) { |
||
312 | if (call_user_func($callback, $key, $value)) { |
||
313 | return $value; |
||
314 | } |
||
315 | } |
||
316 | |||
317 | return $default; |
||
318 | } |
||
319 | |||
320 | /** |
||
321 | * Returns the first element of the array, the first $elements of the array when $elements is a positive integer, |
||
322 | * or the first element which passes the given truth test when the $elements is a callable. |
||
323 | * |
||
324 | * Aliases: @see \nyx\utils\Arr::head(), \nyx\utils\Arr::take() |
||
325 | * Opposite: @see \nyx\utils\Arr::last() |
||
326 | * |
||
327 | * @param array $array The array to traverse. |
||
328 | * @param callable|int $elements The truth test the value should pass or an integer denoting how many |
||
329 | * of the initial elements of the array should be returned. |
||
330 | * When not given, the method will return the first element of the array. |
||
331 | * @param mixed $default The default value to be returned if none of the elements passes |
||
332 | * the test or the array is empty. |
||
333 | * @throws \UnderflowException When more values are requested than there are items in the array. |
||
334 | * @throws \InvalidArgumentException When $elements is neither a valid integer nor a callable. |
||
335 | * @return mixed |
||
336 | */ |
||
337 | public static function first(array $array, $elements = null, $default = null) |
||
338 | { |
||
339 | if (empty($array)) { |
||
340 | return $default; |
||
341 | } |
||
342 | |||
343 | // Most common use case - simply return the first value of the array. |
||
344 | if (!isset($elements) || $elements === 1) { |
||
345 | return reset($array); |
||
346 | } |
||
347 | |||
348 | // With a integer given, return a slice containing the first $callback elements. |
||
349 | if (is_int($elements)) { |
||
350 | |||
351 | if ($elements < 1) { |
||
352 | throw new \InvalidArgumentException("At least 1 element must be requested, while [$elements] were requested."); |
||
353 | } |
||
354 | |||
355 | if ($elements > ($count = count($array))) { |
||
356 | throw new \UnderflowException("Requested [$elements] items, but the structure only contains [$count] items."); |
||
357 | } |
||
358 | |||
359 | return array_slice($array, 0, $elements); |
||
360 | } |
||
361 | |||
362 | // With a callable given, return the first value which passes the given truth test. |
||
363 | if (is_callable($elements)) { |
||
364 | return static::find($array, $elements, $default); |
||
365 | } |
||
366 | |||
367 | throw new \InvalidArgumentException('Expected $callback to be a positive integer or a callable, got ['.diagnostics\Debug::getTypeName($elements).'] instead.'); |
||
368 | } |
||
369 | |||
370 | /** |
||
371 | * Flattens a multi-dimensional array. |
||
372 | * |
||
373 | * @param array $array The initial array. |
||
374 | * @return array The flattened array. |
||
375 | */ |
||
376 | public static function flatten(array $array) |
||
377 | { |
||
378 | $results = []; |
||
379 | |||
380 | array_walk_recursive($array, function($x) use (&$results) { |
||
381 | $results[] = $x; |
||
382 | }); |
||
383 | |||
384 | return $results; |
||
385 | } |
||
386 | |||
387 | /** |
||
388 | * Returns a string delimited key from an array, with a default value if the given key does not exist. If null |
||
389 | * is given instead of a key, the whole initial array will be returned. |
||
390 | * |
||
391 | * Note: Nested objects will be accessed as if they were arrays, eg. if "some.nested.object" is an object, |
||
392 | * then looking for "some.nested.object.property" will be handled just as a normal array would. |
||
393 | * |
||
394 | * @param array $array The array to search in. |
||
395 | * @param string|array $key The string delimited key or a chain (array) of nested keys pointing |
||
396 | * to the desired key. |
||
397 | * @param mixed $default The default value. |
||
398 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
399 | * @return mixed |
||
400 | */ |
||
401 | public static function get(array $array, $key = null, $default = null, string $delimiter = null) |
||
402 | { |
||
403 | // Make loops easier for the end-user - return the initial array if the key is null instead of forcing |
||
404 | // a valid value. |
||
405 | if (!isset($key)) { |
||
406 | return $array; |
||
407 | } |
||
408 | |||
409 | // Which string delimiter should we use? |
||
410 | if (!isset($delimiter)) { |
||
411 | $delimiter = static::$delimiter; |
||
412 | } |
||
413 | |||
414 | // If the key is string delimited, we need to explode it into an array of segments. |
||
415 | $segments = is_array($key) ? $key : explode($delimiter, $key); |
||
416 | |||
417 | // One dimension at a time. |
||
418 | while ($segment = array_shift($segments)) { |
||
419 | |||
420 | // If the current segment is a wildcard, make sure the it points to an array |
||
421 | // and pluck the remaining segments from it. |
||
422 | if ($segment === '*') { |
||
423 | return is_array($array) ? static::pluck($array, $segments, $delimiter) : $default; |
||
424 | } |
||
425 | |||
426 | // Note: isset() is the cheapest condition to check for while being rather probable at the same time, |
||
427 | // thus the seemingly unintuitive condition ordering. |
||
428 | if (isset($array->{$segment})) { |
||
429 | $array = $array->{$segment}; |
||
430 | } elseif (is_array($array) && array_key_exists($segment, $array)) { |
||
431 | $array = $array[$segment]; |
||
432 | } else { |
||
433 | return $default; |
||
434 | } |
||
435 | } |
||
436 | |||
437 | return $array; |
||
438 | } |
||
439 | |||
440 | /** |
||
441 | * Checks whether the given string delimited key exists in the array. |
||
442 | * |
||
443 | * Note: Nested objects will not be accessed as if they were arrays, ie. if "some.nested.object" is an object, |
||
444 | * not an array, then looking for "some.nested.object.key" will always return false. |
||
445 | * |
||
446 | * @param array $array The array to search in. |
||
447 | * @param string $key The string delimited key. |
||
448 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
449 | * @return bool True when the given key exists, false otherwise. |
||
450 | */ |
||
451 | public static function has(array $array, string $key, string $delimiter = null) |
||
452 | { |
||
453 | // Which string delimiter should we use? |
||
454 | if (null === $delimiter) { |
||
455 | $delimiter = static::$delimiter; |
||
456 | } |
||
457 | |||
458 | foreach (explode($delimiter, $key) as $segment) { |
||
459 | if (!is_array($array) || !array_key_exists($segment, $array)) { |
||
460 | return false; |
||
461 | } |
||
462 | |||
463 | $array = $array[$segment]; |
||
464 | } |
||
465 | |||
466 | return true; |
||
467 | } |
||
468 | |||
469 | /** |
||
470 | * @see \nyx\utils\Arr::first() |
||
471 | */ |
||
472 | public static function head(array $array, $callback = null, $default = null) |
||
473 | { |
||
474 | return static::first($array, $callback, $default); |
||
475 | } |
||
476 | |||
477 | /** |
||
478 | * Returns all but the last value(s) of the given array. |
||
479 | * |
||
480 | * If a callable is passed, elements at the end of the array are excluded from the result as long as the |
||
481 | * callback returns a truthy value. If a number is passed, the last n values are excluded from the result. |
||
482 | * |
||
483 | * @param array $array The array to traverse. |
||
484 | * @param callable|int $callback The truth test the value should pass or an integer denoting how many |
||
485 | * of the final elements of the array should be excluded. The count is |
||
486 | * 1-indexed, ie. if you want to exclude the last 2 elements, pass 2. |
||
487 | * @param mixed $default The default value to be returned if none of the elements passes the test. |
||
488 | * Only useful when $callback is a callable. |
||
489 | * @return mixed |
||
490 | */ |
||
491 | public static function initial(array $array, $callback, $default = null) |
||
492 | { |
||
493 | // When given a callable, keep counting as long as the callable returns a truthy value. |
||
494 | View Code Duplication | if (is_callable($callback)) { |
|
495 | $i = 0; |
||
496 | |||
497 | foreach (array_reverse($array) as $key => $value) { |
||
498 | if (!call_user_func($callback, $key, $value)) { |
||
499 | break; |
||
500 | } |
||
501 | |||
502 | $i++; |
||
503 | } |
||
504 | |||
505 | // If we didn't get at least a single truthy value, return the default. |
||
506 | if ($i === 0) { |
||
507 | return $default; |
||
508 | } |
||
509 | |||
510 | // Otherwise we're just gonna overwrite the $callback and proceed as if it were an integer in the |
||
511 | // first place. |
||
512 | $callback = $i; |
||
513 | } |
||
514 | |||
515 | // At this point we need a positive integer, 1 at minimum. |
||
516 | return array_slice($array, 0, -(int) (!$callback ? 1 : abs($callback))); |
||
517 | } |
||
518 | |||
519 | /** |
||
520 | * Checks whether the given array is an associative array. |
||
521 | * |
||
522 | * @param array $array The array to check. |
||
523 | * @return bool True when the array is associative, false otherwise. |
||
524 | */ |
||
525 | public static function isAssociative(array $array) : bool |
||
526 | { |
||
527 | return array_keys($array) !== range(0, count($array) - 1); |
||
528 | } |
||
529 | |||
530 | /** |
||
531 | * Checks whether the given array is a multidimensional array. |
||
532 | * |
||
533 | * @param array $array The array to check. |
||
534 | * @return bool True when the array has multiple dimensions, false otherwise. |
||
535 | */ |
||
536 | public static function isMultidimensional(array $array) : bool |
||
537 | { |
||
538 | return count($array) !== count($array, COUNT_RECURSIVE); |
||
539 | } |
||
540 | |||
541 | /** |
||
542 | * Returns the last element of the array, the final $callback elements of the array when $callback is a number, |
||
543 | * or the last element which passes the given truth test when the $callback is a callable. |
||
544 | * |
||
545 | * @param array $array The array to traverse. |
||
546 | * @param callable|int|bool $callback The truth test the value should pass or an integer / numeric string |
||
547 | * denoting how many of the final elements of the array should be returned. |
||
548 | * When *any* other value is given, the method will return the last |
||
549 | * element of the array. |
||
550 | * @param mixed $default The default value to be returned if none of the elements passes |
||
551 | * the test or the array is empty. |
||
552 | * @return mixed |
||
553 | */ |
||
554 | public static function last(array $array, $callback = false, $default = null) |
||
555 | { |
||
556 | // Avoid some overhead at this point already if possible. |
||
557 | if (empty($array)) { |
||
558 | return $default; |
||
559 | } |
||
560 | |||
561 | // Most common use case - simply return the last value of the array. |
||
562 | if (!$callback) { |
||
563 | return end($array); |
||
564 | } |
||
565 | |||
566 | // With a callable given, return the last value which passes the given truth test. |
||
567 | if (is_callable($callback)) { |
||
568 | foreach (array_reverse($array) as $key => $value) { |
||
569 | if (call_user_func($callback, $key, $value)) { |
||
570 | return $value; |
||
571 | } |
||
572 | } |
||
573 | |||
574 | return $default; |
||
575 | } |
||
576 | |||
577 | // Return only the last element when abs(callback) equals 1, otherwise return the final $callback elements. |
||
578 | return (-1 === $callback = -1 * abs((int) $callback)) ? end($array) : array_slice($array, $callback); |
||
579 | } |
||
580 | |||
581 | /** |
||
582 | * Returns the biggest value from the given array. |
||
583 | * |
||
584 | * @param array $array The array to traverse. |
||
585 | * @return mixed The resulting value. |
||
586 | */ |
||
587 | public static function max(array $array) |
||
588 | { |
||
589 | // Avoid some overhead at this point already if possible. |
||
590 | if (empty($array)) { |
||
591 | return null; |
||
592 | } |
||
593 | |||
594 | // Sort in a descending order. |
||
595 | arsort($array); |
||
596 | |||
597 | // Return the first element of the sorted array. |
||
598 | return reset($array); |
||
599 | } |
||
600 | |||
601 | /** |
||
602 | * Merges 2 or more arrays recursively. Differs in two important aspects from array_merge_recursive(), to |
||
603 | * more closely resemble the standard behaviour of the non-recursive array_merge(): |
||
604 | * - In case of 2 different values, when they are not arrays, the latter one overwrites the earlier instead |
||
605 | * of merging them into an array; |
||
606 | * - Non-conflicting numeric keys are left unchanged. In case of a conflict, the new value will be appended |
||
607 | * to the resulting array (not preserving its key). |
||
608 | * |
||
609 | * @param array $array The initial array. |
||
610 | * @param array ...$with One or more (ie. separate arguments) arrays to merge in. |
||
611 | * @return array The resulting merged array. |
||
612 | */ |
||
613 | public static function merge(array $array, array ...$with) : array |
||
614 | { |
||
615 | foreach ($with as $arr) { |
||
616 | foreach ($arr as $key => $value) { |
||
617 | // Append numeric keys. |
||
618 | if (is_int($key)) { |
||
619 | array_key_exists($key, $array) ? $array[] = $value : $array[$key] = $value; |
||
620 | } |
||
621 | // Merge multi-dimensional arrays recursively. |
||
622 | elseif (is_array($value) && array_key_exists($key, $array) && is_array($array[$key])) { |
||
623 | $array[$key] = static::merge($array[$key], $value); |
||
624 | } else { |
||
625 | $array[$key] = $value; |
||
626 | } |
||
627 | } |
||
628 | } |
||
629 | |||
630 | return $array; |
||
631 | } |
||
632 | |||
633 | /** |
||
634 | * Returns the smallest value from the given array. |
||
635 | * |
||
636 | * @param array $array The array to traverse. |
||
637 | * @return mixed The resulting value. |
||
638 | */ |
||
639 | public static function min(array $array) |
||
640 | { |
||
641 | // Avoid some overhead at this point already if possible. |
||
642 | if (empty($array)) { |
||
643 | return null; |
||
644 | } |
||
645 | |||
646 | // Sort in an ascending order. |
||
647 | asort($array); |
||
648 | |||
649 | // Return the first element of the sorted array. |
||
650 | return reset($array); |
||
651 | } |
||
652 | |||
653 | /** |
||
654 | * Returns a subset of the given array, containing only the specified keys. |
||
655 | * |
||
656 | * @param array $array The initial array. |
||
657 | * @param array $keys An array of keys (the keys are expected to be values of this array). |
||
658 | * @return array |
||
659 | */ |
||
660 | public static function only(array $array, array $keys) : array |
||
661 | { |
||
662 | return array_intersect_key($array, array_values($keys)); |
||
663 | } |
||
664 | |||
665 | /** |
||
666 | * Returns a random value from the given array, or $elements random values when $elements is a positive integer, or |
||
667 | * the first random element that passes the given truth test when $elements is a callable. |
||
668 | * |
||
669 | * @param array $array The array to search in. |
||
670 | * @param callable|int $elements The number of random values to return or a callable to return the first |
||
671 | * randomly shuffled element that passes the given truth test. |
||
672 | * @param mixed $default The default value to be returned if none of the elements passes |
||
673 | * the test or the array is empty. |
||
674 | * @return mixed |
||
675 | */ |
||
676 | public static function pick(array $array, $elements = null, $default = null) |
||
677 | { |
||
678 | // There are *slightly* better performing alternatives, but simply shuffling and delegating |
||
679 | // the actual picking to static::first() allows us to avoid a fair amount of duplicated code. |
||
680 | shuffle($array); |
||
681 | |||
682 | return static::first($array, $elements, $default); |
||
683 | } |
||
684 | |||
685 | /** |
||
686 | * Given an array containing other arrays or objects, this method will look for the value with the given |
||
687 | * key/property of $value within them and return a new array containing all values of said key from the |
||
688 | * initial array. Essentially like fetching a column from a classic database table. |
||
689 | * |
||
690 | * When the optional $key parameter is given, the resulting array will be indexed by the values corresponding |
||
691 | * to the given $key. |
||
692 | * |
||
693 | * @see array_column() A faster and simpler alternative, if you do not need to pluck data with support |
||
694 | * for delimited keys or wildcards. |
||
695 | * |
||
696 | * @param array $array The array to search in. |
||
697 | * @param string|array $value The key of the value to look for. |
||
698 | * @param string|array $key The key of the value to index the resulting array by. |
||
699 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
700 | * @return array |
||
701 | */ |
||
702 | public static function pluck(array $array, $value, $key = null, string $delimiter = null) : array |
||
703 | { |
||
704 | $results = []; |
||
705 | |||
706 | // Which string delimiter should we use? |
||
707 | if (!isset($delimiter)) { |
||
708 | $delimiter = static::$delimiter; |
||
709 | } |
||
710 | |||
711 | foreach ($array as $item) { |
||
712 | |||
713 | $itemValue = static::get($item, $value, $delimiter); |
||
714 | |||
715 | // If the key given is null, the resulting array will contain numerically indexed keys. |
||
716 | if (!isset($key)) { |
||
717 | $results[] = $itemValue; |
||
718 | } |
||
719 | // Otherwise we are going use the value of the given key and use it in the resulting array as key |
||
720 | // for the value determined earlier. |
||
721 | else { |
||
722 | $results[static::get($item, $key, $delimiter)] = $itemValue; |
||
723 | } |
||
724 | } |
||
725 | |||
726 | return $results; |
||
727 | } |
||
728 | |||
729 | /** |
||
730 | * Returns the value for a string delimited key from an array and then removes it. |
||
731 | * |
||
732 | * @param array $array The array to search in. |
||
733 | * @param string $key The string delimited key. |
||
734 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
735 | * @return mixed |
||
736 | */ |
||
737 | public static function pull(&$array, string $key, string $delimiter = null) |
||
738 | { |
||
739 | $value = static::get($array, $key, null, $delimiter); |
||
740 | |||
741 | static::remove($array, $key, $delimiter); |
||
742 | |||
743 | return $value; |
||
744 | } |
||
745 | |||
746 | /** |
||
747 | * Removes a string delimited key from the given array. |
||
748 | * |
||
749 | * @param array& $array The array to search in. |
||
0 ignored issues
–
show
|
|||
750 | * @param string $key The string delimited key. |
||
751 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
752 | */ |
||
753 | public static function remove(array& $array, string $key, string $delimiter = null) |
||
754 | { |
||
755 | // Which string delimiter should we use? |
||
756 | if (!isset($delimiter)) { |
||
757 | $delimiter = static::$delimiter; |
||
758 | } |
||
759 | |||
760 | // Explode the key according to that delimiter. |
||
761 | $keys = explode($delimiter, $key); |
||
762 | |||
763 | while ($key = array_shift($keys)) { |
||
764 | if (!isset($array[$key]) || !is_array($array[$key])) { |
||
765 | return; |
||
766 | } |
||
767 | |||
768 | $array =& $array[$key]; |
||
769 | } |
||
770 | |||
771 | unset($array[array_shift($keys)]); |
||
772 | } |
||
773 | |||
774 | /** |
||
775 | * Returns all but the first value of the given array, all but the first elements for which the $callback |
||
776 | * returns true if $callback is a callable, or all but the first $callback elements if $callback is a number. |
||
777 | * |
||
778 | * Aliases: |
||
779 | * - @see Arr::tail() |
||
780 | * |
||
781 | * @param array $array The array to traverse. |
||
782 | * @param callable|int|bool $callback The truth test the value should pass or an integer denoting how many |
||
783 | * of the initial elements of the array should be excluded. The count |
||
784 | * is 1-indexed, ie. if you want to exclude the first 2 elements, pass 2. |
||
785 | * When a falsy value is given, the method will return all but the first |
||
786 | * element of the array. |
||
787 | * @param mixed $default The default value to be returned if none of the elements passes the |
||
788 | * test or the array contains no more than one item. |
||
789 | * @return mixed |
||
790 | */ |
||
791 | public static function rest(array $array, $callback = false, $default = null) |
||
792 | { |
||
793 | // Avoid some overhead at this point already if possible. We need at least 2 elements in the array for |
||
794 | // this method to make any usage sense. |
||
795 | if (2 > count($array)) { |
||
796 | return $default; |
||
797 | } |
||
798 | |||
799 | // For a falsy callback, return all but the first element of the array. |
||
800 | if (!$callback) { |
||
801 | return array_slice($array, 1); |
||
802 | } |
||
803 | |||
804 | // With a callable given, keep counting as long as the callable returns a truthy value. |
||
805 | View Code Duplication | if (is_callable($callback)) { |
|
806 | $i = 0; |
||
807 | |||
808 | foreach ($array as $key => $value) { |
||
809 | if (!call_user_func($callback, $key, $value)) { |
||
810 | break; |
||
811 | } |
||
812 | |||
813 | $i++; |
||
814 | } |
||
815 | |||
816 | // If we didn't get at least a single truthy value, return the default. |
||
817 | if ($i === 0) { |
||
818 | return $default; |
||
819 | } |
||
820 | |||
821 | // Otherwise we're just gonna overwrite the $callback and proceed as if it were an integer in the |
||
822 | // first place. |
||
823 | $callback = $i; |
||
824 | } |
||
825 | |||
826 | // Return the final $callback elements. |
||
827 | return array_slice($array, abs((int) $callback)); |
||
828 | } |
||
829 | |||
830 | /** |
||
831 | * Sets the given value for a string delimited key within the given array. If null is given instead of a key, |
||
832 | * the whole initial array will be overwritten with the given value. |
||
833 | * |
||
834 | * @param array $array The array to set the value in. |
||
835 | * @param string $key The string delimited key. |
||
836 | * @param mixed $value The value to set. |
||
837 | * @param string $delimiter The delimiter to use when exploding the key into parts. |
||
838 | * @return mixed |
||
839 | */ |
||
840 | public static function set(array& $array, $key, $value, string $delimiter = null) |
||
841 | { |
||
842 | // Make loops easier for the end-user - overwrite the whole array if the key is null. |
||
843 | if (null === $key) { |
||
844 | return $array = $value; |
||
845 | } |
||
846 | |||
847 | // Which string delimiter should we use? |
||
848 | if (null === $delimiter) { |
||
849 | $delimiter = static::$delimiter; |
||
850 | } |
||
851 | |||
852 | // Explode the key according to that delimiter. |
||
853 | $keys = explode($delimiter, $key); |
||
854 | |||
855 | while (count($keys) > 1) { |
||
856 | $key = array_shift($keys); |
||
857 | |||
858 | if (!isset($array[$key]) || !is_array($array[$key])) { |
||
859 | $array[$key] = []; |
||
860 | } |
||
861 | |||
862 | $array =& $array[$key]; |
||
863 | } |
||
864 | |||
865 | return $array[array_shift($keys)] = $value; |
||
866 | } |
||
867 | |||
868 | /** |
||
869 | * Alias for {@see static::any()} |
||
870 | */ |
||
871 | public static function some(array $array, callable $callback) : bool |
||
872 | { |
||
873 | return static::any($array, $callback); |
||
874 | } |
||
875 | |||
876 | /** |
||
877 | * Alias for {@see static::rest()} |
||
878 | */ |
||
879 | public static function tail(array $array, $callback = false, $default = null) |
||
880 | { |
||
881 | return static::rest($array, $callback, $default); |
||
882 | } |
||
883 | |||
884 | /** |
||
885 | * @see \nyx\utils\Arr::first() |
||
886 | */ |
||
887 | public static function take(array $array, $callback = null, $default = null) |
||
888 | { |
||
889 | return static::first($array, $callback, $default); |
||
890 | } |
||
891 | |||
892 | /** |
||
893 | * Returns an array based on the initial array with all occurrences of the passed values removed. Uses strict |
||
894 | * equality comparisons. |
||
895 | * |
||
896 | * @param array $array The array to traverse. |
||
897 | * @param mixed ...$values The values which should get removed. |
||
898 | * @return array |
||
899 | */ |
||
900 | public static function without(array $array, ...$values) : array |
||
901 | { |
||
902 | return array_filter($array, function($value) use ($values) { |
||
903 | return !in_array($value, $values, true); |
||
904 | }); |
||
905 | } |
||
906 | } |
||
907 |
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.