1<?php 2 3namespace Illuminate\Support; 4 5use ArrayAccess; 6use InvalidArgumentException; 7use Illuminate\Support\Traits\Macroable; 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 array $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 array ...$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 array $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 array $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 (call_user_func($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 array $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 $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 * Determines if an array is associative. 347 * 348 * An array is "associative" if it doesn't have sequential numerical keys beginning with zero. 349 * 350 * @param array $array 351 * @return bool 352 */ 353 public static function isAssoc(array $array) 354 { 355 $keys = array_keys($array); 356 357 return array_keys($keys) !== $keys; 358 } 359 360 /** 361 * Get a subset of the items from the given array. 362 * 363 * @param array $array 364 * @param array|string $keys 365 * @return array 366 */ 367 public static function only($array, $keys) 368 { 369 return array_intersect_key($array, array_flip((array) $keys)); 370 } 371 372 /** 373 * Pluck an array of values from an array. 374 * 375 * @param array $array 376 * @param string|array $value 377 * @param string|array|null $key 378 * @return array 379 */ 380 public static function pluck($array, $value, $key = null) 381 { 382 $results = []; 383 384 [$value, $key] = static::explodePluckParameters($value, $key); 385 386 foreach ($array as $item) { 387 $itemValue = data_get($item, $value); 388 389 // If the key is "null", we will just append the value to the array and keep 390 // looping. Otherwise we will key the array using the value of the key we 391 // received from the developer. Then we'll return the final array form. 392 if (is_null($key)) { 393 $results[] = $itemValue; 394 } else { 395 $itemKey = data_get($item, $key); 396 397 if (is_object($itemKey) && method_exists($itemKey, '__toString')) { 398 $itemKey = (string) $itemKey; 399 } 400 401 $results[$itemKey] = $itemValue; 402 } 403 } 404 405 return $results; 406 } 407 408 /** 409 * Explode the "value" and "key" arguments passed to "pluck". 410 * 411 * @param string|array $value 412 * @param string|array|null $key 413 * @return array 414 */ 415 protected static function explodePluckParameters($value, $key) 416 { 417 $value = is_string($value) ? explode('.', $value) : $value; 418 419 $key = is_null($key) || is_array($key) ? $key : explode('.', $key); 420 421 return [$value, $key]; 422 } 423 424 /** 425 * Push an item onto the beginning of an array. 426 * 427 * @param array $array 428 * @param mixed $value 429 * @param mixed $key 430 * @return array 431 */ 432 public static function prepend($array, $value, $key = null) 433 { 434 if (is_null($key)) { 435 array_unshift($array, $value); 436 } else { 437 $array = [$key => $value] + $array; 438 } 439 440 return $array; 441 } 442 443 /** 444 * Get a value from the array, and remove it. 445 * 446 * @param array $array 447 * @param string $key 448 * @param mixed $default 449 * @return mixed 450 */ 451 public static function pull(&$array, $key, $default = null) 452 { 453 $value = static::get($array, $key, $default); 454 455 static::forget($array, $key); 456 457 return $value; 458 } 459 460 /** 461 * Get one or a specified number of random values from an array. 462 * 463 * @param array $array 464 * @param int|null $number 465 * @return mixed 466 * 467 * @throws \InvalidArgumentException 468 */ 469 public static function random($array, $number = null) 470 { 471 $requested = is_null($number) ? 1 : $number; 472 473 $count = count($array); 474 475 if ($requested > $count) { 476 throw new InvalidArgumentException( 477 "You requested {$requested} items, but there are only {$count} items available." 478 ); 479 } 480 481 if (is_null($number)) { 482 return $array[array_rand($array)]; 483 } 484 485 if ((int) $number === 0) { 486 return []; 487 } 488 489 $keys = array_rand($array, $number); 490 491 $results = []; 492 493 foreach ((array) $keys as $key) { 494 $results[] = $array[$key]; 495 } 496 497 return $results; 498 } 499 500 /** 501 * Set an array item to a given value using "dot" notation. 502 * 503 * If no key is given to the method, the entire array will be replaced. 504 * 505 * @param array $array 506 * @param string $key 507 * @param mixed $value 508 * @return array 509 */ 510 public static function set(&$array, $key, $value) 511 { 512 if (is_null($key)) { 513 return $array = $value; 514 } 515 516 $keys = explode('.', $key); 517 518 while (count($keys) > 1) { 519 $key = array_shift($keys); 520 521 // If the key doesn't exist at this depth, we will just create an empty array 522 // to hold the next value, allowing us to create the arrays to hold final 523 // values at the correct depth. Then we'll keep digging into the array. 524 if (! isset($array[$key]) || ! is_array($array[$key])) { 525 $array[$key] = []; 526 } 527 528 $array = &$array[$key]; 529 } 530 531 $array[array_shift($keys)] = $value; 532 533 return $array; 534 } 535 536 /** 537 * Shuffle the given array and return the result. 538 * 539 * @param array $array 540 * @param int|null $seed 541 * @return array 542 */ 543 public static function shuffle($array, $seed = null) 544 { 545 if (is_null($seed)) { 546 shuffle($array); 547 } else { 548 mt_srand($seed); 549 shuffle($array); 550 mt_srand(); 551 } 552 553 return $array; 554 } 555 556 /** 557 * Sort the array using the given callback or "dot" notation. 558 * 559 * @param array $array 560 * @param callable|string|null $callback 561 * @return array 562 */ 563 public static function sort($array, $callback = null) 564 { 565 return Collection::make($array)->sortBy($callback)->all(); 566 } 567 568 /** 569 * Recursively sort an array by keys and values. 570 * 571 * @param array $array 572 * @return array 573 */ 574 public static function sortRecursive($array) 575 { 576 foreach ($array as &$value) { 577 if (is_array($value)) { 578 $value = static::sortRecursive($value); 579 } 580 } 581 582 if (static::isAssoc($array)) { 583 ksort($array); 584 } else { 585 sort($array); 586 } 587 588 return $array; 589 } 590 591 /** 592 * Convert the array into a query string. 593 * 594 * @param array $array 595 * @return string 596 */ 597 public static function query($array) 598 { 599 return http_build_query($array, null, '&', PHP_QUERY_RFC3986); 600 } 601 602 /** 603 * Filter the array using the given callback. 604 * 605 * @param array $array 606 * @param callable $callback 607 * @return array 608 */ 609 public static function where($array, callable $callback) 610 { 611 return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); 612 } 613 614 /** 615 * If the given value is not an array and not null, wrap it in one. 616 * 617 * @param mixed $value 618 * @return array 619 */ 620 public static function wrap($value) 621 { 622 if (is_null($value)) { 623 return []; 624 } 625 626 return is_array($value) ? $value : [$value]; 627 } 628} 629