blob: 57350cca5c12de9d890c97ad92d1073f21934e0c [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3namespace Tightenco\Collect\Support;
4
5use ArrayAccess;
6use Tightenco\Collect\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 /**
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100125 * Convert a flatten "dot" notation array into an expanded array.
126 *
127 * @param iterable $array
128 * @return array
129 */
130 public static function undot($array)
131 {
132 $results = [];
133
134 foreach ($array as $key => $value) {
135 static::set($results, $key, $value);
136 }
137
138 return $results;
139 }
140
141 /**
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200142 * Get all of the given array except for a specified array of keys.
143 *
144 * @param array $array
145 * @param array|string $keys
146 * @return array
147 */
148 public static function except($array, $keys)
149 {
150 static::forget($array, $keys);
151
152 return $array;
153 }
154
155 /**
156 * Determine if the given key exists in the provided array.
157 *
158 * @param \ArrayAccess|array $array
159 * @param string|int $key
160 * @return bool
161 */
162 public static function exists($array, $key)
163 {
164 if ($array instanceof Enumerable) {
165 return $array->has($key);
166 }
167
168 if ($array instanceof ArrayAccess) {
169 return $array->offsetExists($key);
170 }
171
172 return array_key_exists($key, $array);
173 }
174
175 /**
176 * Return the first element in an array passing a given truth test.
177 *
178 * @param iterable $array
179 * @param callable|null $callback
180 * @param mixed $default
181 * @return mixed
182 */
183 public static function first($array, callable $callback = null, $default = null)
184 {
185 if (is_null($callback)) {
186 if (empty($array)) {
187 return value($default);
188 }
189
190 foreach ($array as $item) {
191 return $item;
192 }
193 }
194
195 foreach ($array as $key => $value) {
196 if ($callback($value, $key)) {
197 return $value;
198 }
199 }
200
201 return value($default);
202 }
203
204 /**
205 * Return the last element in an array passing a given truth test.
206 *
207 * @param array $array
208 * @param callable|null $callback
209 * @param mixed $default
210 * @return mixed
211 */
212 public static function last($array, callable $callback = null, $default = null)
213 {
214 if (is_null($callback)) {
215 return empty($array) ? value($default) : end($array);
216 }
217
218 return static::first(array_reverse($array, true), $callback, $default);
219 }
220
221 /**
222 * Flatten a multi-dimensional array into a single level.
223 *
224 * @param iterable $array
225 * @param int $depth
226 * @return array
227 */
228 public static function flatten($array, $depth = INF)
229 {
230 $result = [];
231
232 foreach ($array as $item) {
233 $item = $item instanceof Collection ? $item->all() : $item;
234
235 if (! is_array($item)) {
236 $result[] = $item;
237 } else {
238 $values = $depth === 1
239 ? array_values($item)
240 : static::flatten($item, $depth - 1);
241
242 foreach ($values as $value) {
243 $result[] = $value;
244 }
245 }
246 }
247
248 return $result;
249 }
250
251 /**
252 * Remove one or many array items from a given array using "dot" notation.
253 *
254 * @param array $array
255 * @param array|string $keys
256 * @return void
257 */
258 public static function forget(&$array, $keys)
259 {
260 $original = &$array;
261
262 $keys = (array) $keys;
263
264 if (count($keys) === 0) {
265 return;
266 }
267
268 foreach ($keys as $key) {
269 // if the exact key exists in the top-level, remove it
270 if (static::exists($array, $key)) {
271 unset($array[$key]);
272
273 continue;
274 }
275
276 $parts = explode('.', $key);
277
278 // clean up before each pass
279 $array = &$original;
280
281 while (count($parts) > 1) {
282 $part = array_shift($parts);
283
284 if (isset($array[$part]) && is_array($array[$part])) {
285 $array = &$array[$part];
286 } else {
287 continue 2;
288 }
289 }
290
291 unset($array[array_shift($parts)]);
292 }
293 }
294
295 /**
296 * Get an item from an array using "dot" notation.
297 *
298 * @param \ArrayAccess|array $array
299 * @param string|int|null $key
300 * @param mixed $default
301 * @return mixed
302 */
303 public static function get($array, $key, $default = null)
304 {
305 if (! static::accessible($array)) {
306 return value($default);
307 }
308
309 if (is_null($key)) {
310 return $array;
311 }
312
313 if (static::exists($array, $key)) {
314 return $array[$key];
315 }
316
317 if (strpos($key, '.') === false) {
318 return $array[$key] ?? value($default);
319 }
320
321 foreach (explode('.', $key) as $segment) {
322 if (static::accessible($array) && static::exists($array, $segment)) {
323 $array = $array[$segment];
324 } else {
325 return value($default);
326 }
327 }
328
329 return $array;
330 }
331
332 /**
333 * Check if an item or items exist in an array using "dot" notation.
334 *
335 * @param \ArrayAccess|array $array
336 * @param string|array $keys
337 * @return bool
338 */
339 public static function has($array, $keys)
340 {
341 $keys = (array) $keys;
342
343 if (! $array || $keys === []) {
344 return false;
345 }
346
347 foreach ($keys as $key) {
348 $subKeyArray = $array;
349
350 if (static::exists($array, $key)) {
351 continue;
352 }
353
354 foreach (explode('.', $key) as $segment) {
355 if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) {
356 $subKeyArray = $subKeyArray[$segment];
357 } else {
358 return false;
359 }
360 }
361 }
362
363 return true;
364 }
365
366 /**
367 * Determine if any of the keys exist in an array using "dot" notation.
368 *
369 * @param \ArrayAccess|array $array
370 * @param string|array $keys
371 * @return bool
372 */
373 public static function hasAny($array, $keys)
374 {
375 if (is_null($keys)) {
376 return false;
377 }
378
379 $keys = (array) $keys;
380
381 if (! $array) {
382 return false;
383 }
384
385 if ($keys === []) {
386 return false;
387 }
388
389 foreach ($keys as $key) {
390 if (static::has($array, $key)) {
391 return true;
392 }
393 }
394
395 return false;
396 }
397
398 /**
399 * Determines if an array is associative.
400 *
401 * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
402 *
403 * @param array $array
404 * @return bool
405 */
406 public static function isAssoc(array $array)
407 {
408 $keys = array_keys($array);
409
410 return array_keys($keys) !== $keys;
411 }
412
413 /**
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100414 * Determines if an array is a list.
415 *
416 * An array is a "list" if all array keys are sequential integers starting from 0 with no gaps in between.
417 *
418 * @param array $array
419 * @return bool
420 */
421 public static function isList($array)
422 {
423 return ! self::isAssoc($array);
424 }
425
426 /**
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200427 * Get a subset of the items from the given array.
428 *
429 * @param array $array
430 * @param array|string $keys
431 * @return array
432 */
433 public static function only($array, $keys)
434 {
435 return array_intersect_key($array, array_flip((array) $keys));
436 }
437
438 /**
439 * Pluck an array of values from an array.
440 *
441 * @param iterable $array
442 * @param string|array|int|null $value
443 * @param string|array|null $key
444 * @return array
445 */
446 public static function pluck($array, $value, $key = null)
447 {
448 $results = [];
449
450 [$value, $key] = static::explodePluckParameters($value, $key);
451
452 foreach ($array as $item) {
453 $itemValue = data_get($item, $value);
454
455 // If the key is "null", we will just append the value to the array and keep
456 // looping. Otherwise we will key the array using the value of the key we
457 // received from the developer. Then we'll return the final array form.
458 if (is_null($key)) {
459 $results[] = $itemValue;
460 } else {
461 $itemKey = data_get($item, $key);
462
463 if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
464 $itemKey = (string) $itemKey;
465 }
466
467 $results[$itemKey] = $itemValue;
468 }
469 }
470
471 return $results;
472 }
473
474 /**
475 * Explode the "value" and "key" arguments passed to "pluck".
476 *
477 * @param string|array $value
478 * @param string|array|null $key
479 * @return array
480 */
481 protected static function explodePluckParameters($value, $key)
482 {
483 $value = is_string($value) ? explode('.', $value) : $value;
484
485 $key = is_null($key) || is_array($key) ? $key : explode('.', $key);
486
487 return [$value, $key];
488 }
489
490 /**
491 * Push an item onto the beginning of an array.
492 *
493 * @param array $array
494 * @param mixed $value
495 * @param mixed $key
496 * @return array
497 */
498 public static function prepend($array, $value, $key = null)
499 {
500 if (func_num_args() == 2) {
501 array_unshift($array, $value);
502 } else {
503 $array = [$key => $value] + $array;
504 }
505
506 return $array;
507 }
508
509 /**
510 * Get a value from the array, and remove it.
511 *
512 * @param array $array
513 * @param string $key
514 * @param mixed $default
515 * @return mixed
516 */
517 public static function pull(&$array, $key, $default = null)
518 {
519 $value = static::get($array, $key, $default);
520
521 static::forget($array, $key);
522
523 return $value;
524 }
525
526 /**
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100527 * Convert the array into a query string.
528 *
529 * @param array $array
530 * @return string
531 */
532 public static function query($array)
533 {
534 return http_build_query($array, '', '&', PHP_QUERY_RFC3986);
535 }
536
537 /**
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200538 * Get one or a specified number of random values from an array.
539 *
540 * @param array $array
541 * @param int|null $number
542 * @param bool|false $preserveKeys
543 * @return mixed
544 *
545 * @throws \InvalidArgumentException
546 */
547 public static function random($array, $number = null, $preserveKeys = false)
548 {
549 $requested = is_null($number) ? 1 : $number;
550
551 $count = count($array);
552
553 if ($requested > $count) {
554 throw new InvalidArgumentException(
555 "You requested {$requested} items, but there are only {$count} items available."
556 );
557 }
558
559 if (is_null($number)) {
560 return $array[array_rand($array)];
561 }
562
563 if ((int) $number === 0) {
564 return [];
565 }
566
567 $keys = array_rand($array, $number);
568
569 $results = [];
570
571 if ($preserveKeys) {
572 foreach ((array) $keys as $key) {
573 $results[$key] = $array[$key];
574 }
575 } else {
576 foreach ((array) $keys as $key) {
577 $results[] = $array[$key];
578 }
579 }
580
581 return $results;
582 }
583
584 /**
585 * Set an array item to a given value using "dot" notation.
586 *
587 * If no key is given to the method, the entire array will be replaced.
588 *
589 * @param array $array
590 * @param string|null $key
591 * @param mixed $value
592 * @return array
593 */
594 public static function set(&$array, $key, $value)
595 {
596 if (is_null($key)) {
597 return $array = $value;
598 }
599
600 $keys = explode('.', $key);
601
602 foreach ($keys as $i => $key) {
603 if (count($keys) === 1) {
604 break;
605 }
606
607 unset($keys[$i]);
608
609 // If the key doesn't exist at this depth, we will just create an empty array
610 // to hold the next value, allowing us to create the arrays to hold final
611 // values at the correct depth. Then we'll keep digging into the array.
612 if (! isset($array[$key]) || ! is_array($array[$key])) {
613 $array[$key] = [];
614 }
615
616 $array = &$array[$key];
617 }
618
619 $array[array_shift($keys)] = $value;
620
621 return $array;
622 }
623
624 /**
625 * Shuffle the given array and return the result.
626 *
627 * @param array $array
628 * @param int|null $seed
629 * @return array
630 */
631 public static function shuffle($array, $seed = null)
632 {
633 if (is_null($seed)) {
634 shuffle($array);
635 } else {
636 mt_srand($seed);
637 shuffle($array);
638 mt_srand();
639 }
640
641 return $array;
642 }
643
644 /**
645 * Sort the array using the given callback or "dot" notation.
646 *
647 * @param array $array
648 * @param callable|array|string|null $callback
649 * @return array
650 */
651 public static function sort($array, $callback = null)
652 {
653 return Collection::make($array)->sortBy($callback)->all();
654 }
655
656 /**
657 * Recursively sort an array by keys and values.
658 *
659 * @param array $array
660 * @param int $options
661 * @param bool $descending
662 * @return array
663 */
664 public static function sortRecursive($array, $options = SORT_REGULAR, $descending = false)
665 {
666 foreach ($array as &$value) {
667 if (is_array($value)) {
668 $value = static::sortRecursive($value, $options, $descending);
669 }
670 }
671
672 if (static::isAssoc($array)) {
673 $descending
674 ? krsort($array, $options)
675 : ksort($array, $options);
676 } else {
677 $descending
678 ? rsort($array, $options)
679 : sort($array, $options);
680 }
681
682 return $array;
683 }
684
685 /**
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100686 * Conditionally compile classes from an array into a CSS class list.
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200687 *
688 * @param array $array
689 * @return string
690 */
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100691 public static function toCssClasses($array)
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200692 {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100693 $classList = static::wrap($array);
694
695 $classes = [];
696
697 foreach ($classList as $class => $constraint) {
698 if (is_numeric($class)) {
699 $classes[] = $constraint;
700 } elseif ($constraint) {
701 $classes[] = $class;
702 }
703 }
704
705 return implode(' ', $classes);
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200706 }
707
708 /**
709 * Filter the array using the given callback.
710 *
711 * @param array $array
712 * @param callable $callback
713 * @return array
714 */
715 public static function where($array, callable $callback)
716 {
717 return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
718 }
719
720 /**
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100721 * Filter items where the value is not null.
722 *
723 * @param array $array
724 * @return array
725 */
726 public static function whereNotNull($array)
727 {
728 return static::where($array, function ($value) {
729 return ! is_null($value);
730 });
731 }
732
733 /**
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200734 * If the given value is not an array and not null, wrap it in one.
735 *
736 * @param mixed $value
737 * @return array
738 */
739 public static function wrap($value)
740 {
741 if (is_null($value)) {
742 return [];
743 }
744
745 return is_array($value) ? $value : [$value];
746 }
747}