Arr.php 16 KB

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