Arr.php 16 KB

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