Arr.php 18 KB

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