1<?php
2
3namespace Illuminate\Support;
4
5use ArrayAccess;
6use Illuminate\Support\Traits\Macroable;
7use InvalidArgumentException;
8
9class Arr
10{
11 use Macroable;
12
13 /**
14 * Determine whether the given value is array accessible.
15 *
16 * @param mixed $value
17 * @return bool
18 */
19 public static function accessible($value)
20 {
21 return is_array($value) || $value instanceof ArrayAccess;
22 }
23
24 /**
25 * Add an element to an array using "dot" notation if it doesn't exist.
26 *
27 * @param array $array
28 * @param string $key
29 * @param mixed $value
30 * @return array
31 */
32 public static function add($array, $key, $value)
33 {
34 if (is_null(static::get($array, $key))) {
35 static::set($array, $key, $value);
36 }
37
38 return $array;
39 }
40
41 /**
42 * Collapse an array of arrays into a single array.
43 *
44 * @param iterable $array
45 * @return array
46 */
47 public static function collapse($array)
48 {
49 $results = [];
50
51 foreach ($array as $values) {
52 if ($values instanceof Collection) {
53 $values = $values->all();
54 } elseif (! is_array($values)) {
55 continue;
56 }
57
58 $results[] = $values;
59 }
60
61 return array_merge([], ...$results);
62 }
63
64 /**
65 * Cross join the given arrays, returning all possible permutations.
66 *
67 * @param iterable ...$arrays
68 * @return array
69 */
70 public static function crossJoin(...$arrays)
71 {
72 $results = [[]];
73
74 foreach ($arrays as $index => $array) {
75 $append = [];
76
77 foreach ($results as $product) {
78 foreach ($array as $item) {
79 $product[$index] = $item;
80
81 $append[] = $product;
82 }
83 }
84
85 $results = $append;
86 }
87
88 return $results;
89 }
90
91 /**
92 * Divide an array into two arrays. One with keys and the other with values.
93 *
94 * @param array $array
95 * @return array
96 */
97 public static function divide($array)
98 {
99 return [array_keys($array), array_values($array)];
100 }
101
102 /**
103 * Flatten a multi-dimensional associative array with dots.
104 *
105 * @param iterable $array
106 * @param string $prepend
107 * @return array
108 */
109 public static function dot($array, $prepend = '')
110 {
111 $results = [];
112
113 foreach ($array as $key => $value) {
114 if (is_array($value) && ! empty($value)) {
115 $results = array_merge($results, static::dot($value, $prepend.$key.'.'));
116 } else {
117 $results[$prepend.$key] = $value;
118 }
119 }
120
121 return $results;
122 }
123
124 /**
125 * Get all of the given array except for a specified array of keys.
126 *
127 * @param array $array
128 * @param array|string $keys
129 * @return array
130 */
131 public static function except($array, $keys)
132 {
133 static::forget($array, $keys);
134
135 return $array;
136 }
137
138 /**
139 * Determine if the given key exists in the provided array.
140 *
141 * @param \ArrayAccess|array $array
142 * @param string|int $key
143 * @return bool
144 */
145 public static function exists($array, $key)
146 {
147 if ($array instanceof ArrayAccess) {
148 return $array->offsetExists($key);
149 }
150
151 return array_key_exists($key, $array);
152 }
153
154 /**
155 * Return the first element in an array passing a given truth test.
156 *
157 * @param iterable $array
158 * @param callable|null $callback
159 * @param mixed $default
160 * @return mixed
161 */
162 public static function first($array, callable $callback = null, $default = null)
163 {
164 if (is_null($callback)) {
165 if (empty($array)) {
166 return value($default);
167 }
168
169 foreach ($array as $item) {
170 return $item;
171 }
172 }
173
174 foreach ($array as $key => $value) {
175 if ($callback($value, $key)) {
176 return $value;
177 }
178 }
179
180 return value($default);
181 }
182
183 /**
184 * Return the last element in an array passing a given truth test.
185 *
186 * @param array $array
187 * @param callable|null $callback
188 * @param mixed $default
189 * @return mixed
190 */
191 public static function last($array, callable $callback = null, $default = null)
192 {
193 if (is_null($callback)) {
194 return empty($array) ? value($default) : end($array);
195 }
196
197 return static::first(array_reverse($array, true), $callback, $default);
198 }
199
200 /**
201 * Flatten a multi-dimensional array into a single level.
202 *
203 * @param iterable $array
204 * @param int $depth
205 * @return array
206 */
207 public static function flatten($array, $depth = INF)
208 {
209 $result = [];
210
211 foreach ($array as $item) {
212 $item = $item instanceof Collection ? $item->all() : $item;
213
214 if (! is_array($item)) {
215 $result[] = $item;
216 } else {
217 $values = $depth === 1
218 ? array_values($item)
219 : static::flatten($item, $depth - 1);
220
221 foreach ($values as $value) {
222 $result[] = $value;
223 }
224 }
225 }
226
227 return $result;
228 }
229
230 /**
231 * Remove one or many array items from a given array using "dot" notation.
232 *
233 * @param array $array
234 * @param array|string $keys
235 * @return void
236 */
237 public static function forget(&$array, $keys)
238 {
239 $original = &$array;
240
241 $keys = (array) $keys;
242
243 if (count($keys) === 0) {
244 return;
245 }
246
247 foreach ($keys as $key) {
248 // if the exact key exists in the top-level, remove it
249 if (static::exists($array, $key)) {
250 unset($array[$key]);
251
252 continue;
253 }
254
255 $parts = explode('.', $key);
256
257 // clean up before each pass
258 $array = &$original;
259
260 while (count($parts) > 1) {
261 $part = array_shift($parts);
262
263 if (isset($array[$part]) && is_array($array[$part])) {
264 $array = &$array[$part];
265 } else {
266 continue 2;
267 }
268 }
269
270 unset($array[array_shift($parts)]);
271 }
272 }
273
274 /**
275 * Get an item from an array using "dot" notation.
276 *
277 * @param \ArrayAccess|array $array
278 * @param string|int|null $key
279 * @param mixed $default
280 * @return mixed
281 */
282 public static function get($array, $key, $default = null)
283 {
284 if (! static::accessible($array)) {
285 return value($default);
286 }
287
288 if (is_null($key)) {
289 return $array;
290 }
291
292 if (static::exists($array, $key)) {
293 return $array[$key];
294 }
295
296 if (strpos($key, '.') === false) {
297 return $array[$key] ?? value($default);
298 }
299
300 foreach (explode('.', $key) as $segment) {
301 if (static::accessible($array) && static::exists($array, $segment)) {
302 $array = $array[$segment];
303 } else {
304 return value($default);
305 }
306 }
307
308 return $array;
309 }
310
311 /**
312 * Check if an item or items exist in an array using "dot" notation.
313 *
314 * @param \ArrayAccess|array $array
315 * @param string|array $keys
316 * @return bool
317 */
318 public static function has($array, $keys)
319 {
320 $keys = (array) $keys;
321
322 if (! $array || $keys === []) {
323 return false;
324 }
325
326 foreach ($keys as $key) {
327 $subKeyArray = $array;
328
329 if (static::exists($array, $key)) {
330 continue;
331 }
332
333 foreach (explode('.', $key) as $segment) {
334 if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) {
335 $subKeyArray = $subKeyArray[$segment];
336 } else {
337 return false;
338 }
339 }
340 }
341
342 return true;
343 }
344
345 /**
346 * Determine if any of the keys exist in an array using "dot" notation.
347 *
348 * @param \ArrayAccess|array $array
349 * @param string|array $keys
350 * @return bool
351 */
352 public static function hasAny($array, $keys)
353 {
354 if (is_null($keys)) {
355 return false;
356 }
357
358 $keys = (array) $keys;
359
360 if (! $array) {
361 return false;
362 }
363
364 if ($keys === []) {
365 return false;
366 }
367
368 foreach ($keys as $key) {
369 if (static::has($array, $key)) {
370 return true;
371 }
372 }
373
374 return false;
375 }
376
377 /**
378 * Determines if an array is associative.
379 *
380 * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
381 *
382 * @param array $array
383 * @return bool
384 */
385 public static function isAssoc(array $array)
386 {
387 $keys = array_keys($array);
388
389 return array_keys($keys) !== $keys;
390 }
391
392 /**
393 * Get a subset of the items from the given array.
394 *
395 * @param array $array
396 * @param array|string $keys
397 * @return array
398 */
399 public static function only($array, $keys)
400 {
401 return array_intersect_key($array, array_flip((array) $keys));
402 }
403
404 /**
405 * Pluck an array of values from an array.
406 *
407 * @param iterable $array
408 * @param string|array $value
409 * @param string|array|null $key
410 * @return array
411 */
412 public static function pluck($array, $value, $key = null)
413 {
414 $results = [];
415
416 [$value, $key] = static::explodePluckParameters($value, $key);
417
418 foreach ($array as $item) {
419 $itemValue = data_get($item, $value);
420
421 // If the key is "null", we will just append the value to the array and keep
422 // looping. Otherwise we will key the array using the value of the key we
423 // received from the developer. Then we'll return the final array form.
424 if (is_null($key)) {
425 $results[] = $itemValue;
426 } else {
427 $itemKey = data_get($item, $key);
428
429 if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
430 $itemKey = (string) $itemKey;
431 }
432
433 $results[$itemKey] = $itemValue;
434 }
435 }
436
437 return $results;
438 }
439
440 /**
441 * Explode the "value" and "key" arguments passed to "pluck".
442 *
443 * @param string|array $value
444 * @param string|array|null $key
445 * @return array
446 */
447 protected static function explodePluckParameters($value, $key)
448 {
449 $value = is_string($value) ? explode('.', $value) : $value;
450
451 $key = is_null($key) || is_array($key) ? $key : explode('.', $key);
452
453 return [$value, $key];
454 }
455
456 /**
457 * Push an item onto the beginning of an array.
458 *
459 * @param array $array
460 * @param mixed $value
461 * @param mixed $key
462 * @return array
463 */
464 public static function prepend($array, $value, $key = null)
465 {
466 if (is_null($key)) {
467 array_unshift($array, $value);
468 } else {
469 $array = [$key => $value] + $array;
470 }
471
472 return $array;
473 }
474
475 /**
476 * Get a value from the array, and remove it.
477 *
478 * @param array $array
479 * @param string $key
480 * @param mixed $default
481 * @return mixed
482 */
483 public static function pull(&$array, $key, $default = null)
484 {
485 $value = static::get($array, $key, $default);
486
487 static::forget($array, $key);
488
489 return $value;
490 }
491
492 /**
493 * Get one or a specified number of random values from an array.
494 *
495 * @param array $array
496 * @param int|null $number
497 * @return mixed
498 *
499 * @throws \InvalidArgumentException
500 */
501 public static function random($array, $number = null)
502 {
503 $requested = is_null($number) ? 1 : $number;
504
505 $count = count($array);
506
507 if ($requested > $count) {
508 throw new InvalidArgumentException(
509 "You requested {$requested} items, but there are only {$count} items available."
510 );
511 }
512
513 if (is_null($number)) {
514 return $array[array_rand($array)];
515 }
516
517 if ((int) $number === 0) {
518 return [];
519 }
520
521 $keys = array_rand($array, $number);
522
523 $results = [];
524
525 foreach ((array) $keys as $key) {
526 $results[] = $array[$key];
527 }
528
529 return $results;
530 }
531
532 /**
533 * Set an array item to a given value using "dot" notation.
534 *
535 * If no key is given to the method, the entire array will be replaced.
536 *
537 * @param array $array
538 * @param string|null $key
539 * @param mixed $value
540 * @return array
541 */
542 public static function set(&$array, $key, $value)
543 {
544 if (is_null($key)) {
545 return $array = $value;
546 }
547
548 $keys = explode('.', $key);
549
550 foreach ($keys as $i => $key) {
551 if (count($keys) === 1) {
552 break;
553 }
554
555 unset($keys[$i]);
556
557 // If the key doesn't exist at this depth, we will just create an empty array
558 // to hold the next value, allowing us to create the arrays to hold final
559 // values at the correct depth. Then we'll keep digging into the array.
560 if (! isset($array[$key]) || ! is_array($array[$key])) {
561 $array[$key] = [];
562 }
563
564 $array = &$array[$key];
565 }
566
567 $array[array_shift($keys)] = $value;
568
569 return $array;
570 }
571
572 /**
573 * Shuffle the given array and return the result.
574 *
575 * @param array $array
576 * @param int|null $seed
577 * @return array
578 */
579 public static function shuffle($array, $seed = null)
580 {
581 if (is_null($seed)) {
582 shuffle($array);
583 } else {
584 mt_srand($seed);
585 shuffle($array);
586 mt_srand();
587 }
588
589 return $array;
590 }
591
592 /**
593 * Sort the array using the given callback or "dot" notation.
594 *
595 * @param array $array
596 * @param callable|string|null $callback
597 * @return array
598 */
599 public static function sort($array, $callback = null)
600 {
601 return Collection::make($array)->sortBy($callback)->all();
602 }
603
604 /**
605 * Recursively sort an array by keys and values.
606 *
607 * @param array $array
608 * @return array
609 */
610 public static function sortRecursive($array)
611 {
612 foreach ($array as &$value) {
613 if (is_array($value)) {
614 $value = static::sortRecursive($value);
615 }
616 }
617
618 if (static::isAssoc($array)) {
619 ksort($array);
620 } else {
621 sort($array);
622 }
623
624 return $array;
625 }
626
627 /**
628 * Convert the array into a query string.
629 *
630 * @param array $array
631 * @return string
632 */
633 public static function query($array)
634 {
635 return http_build_query($array, null, '&', PHP_QUERY_RFC3986);
636 }
637
638 /**
639 * Filter the array using the given callback.
640 *
641 * @param array $array
642 * @param callable $callback
643 * @return array
644 */
645 public static function where($array, callable $callback)
646 {
647 return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
648 }
649
650 /**
651 * If the given value is not an array and not null, wrap it in one.
652 *
653 * @param mixed $value
654 * @return array
655 */
656 public static function wrap($value)
657 {
658 if (is_null($value)) {
659 return [];
660 }
661
662 return is_array($value) ? $value : [$value];
663 }
664}
665