Arr.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. <?php
  2. /**
  3. * Array helper.
  4. *
  5. * @package KO7
  6. * @category Helpers
  7. *
  8. * @copyright (c) 2007-2016 Kohana Team
  9. * @copyright (c) since 2016 Koseven Team
  10. * @license https://koseven.dev/LICENSE
  11. */
  12. class KO7_Arr {
  13. /**
  14. * @var string default delimiter for path()
  15. */
  16. public static $delimiter = '.';
  17. /**
  18. * Tests if an array is associative or not.
  19. *
  20. * // Returns TRUE
  21. * Arr::is_assoc(array('username' => 'john.doe'));
  22. *
  23. * // Returns FALSE
  24. * Arr::is_assoc('foo', 'bar');
  25. *
  26. * @param array $array array to check
  27. * @return boolean
  28. */
  29. public static function is_assoc(array $array)
  30. {
  31. // Keys of the array
  32. $keys = array_keys($array);
  33. // If the array keys of the keys match the keys, then the array must
  34. // not be associative (e.g. the keys array looked like {0:0, 1:1...}).
  35. return array_keys($keys) !== $keys;
  36. }
  37. /**
  38. * Test if a value is an array with an additional check for array-like objects.
  39. *
  40. * // Returns TRUE
  41. * Arr::is_array(array());
  42. * Arr::is_array(new ArrayObject);
  43. *
  44. * // Returns FALSE
  45. * Arr::is_array(FALSE);
  46. * Arr::is_array('not an array!');
  47. * Arr::is_array(Database::instance());
  48. *
  49. * @param mixed $value value to check
  50. * @return boolean
  51. */
  52. public static function is_array($value)
  53. {
  54. if (is_array($value))
  55. {
  56. // Definitely an array
  57. return TRUE;
  58. }
  59. else
  60. {
  61. // Possibly a Traversable object, functionally the same as an array
  62. return (is_object($value) AND $value instanceof Traversable);
  63. }
  64. }
  65. /**
  66. * Gets a value from an array using a dot separated path.
  67. *
  68. * // Get the value of $array['foo']['bar']
  69. * $value = Arr::path($array, 'foo.bar');
  70. *
  71. * Using a wildcard "*" will search intermediate arrays and return an array.
  72. *
  73. * // Get the values of "color" in theme
  74. * $colors = Arr::path($array, 'theme.*.color');
  75. *
  76. * // Using an array of keys
  77. * $colors = Arr::path($array, array('theme', '*', 'color'));
  78. *
  79. * @param array $array array to search
  80. * @param mixed $path key path string (delimiter separated) or array of keys
  81. * @param mixed $default default value if the path is not set
  82. * @param string $delimiter key path delimiter
  83. * @return mixed
  84. */
  85. public static function path($array, $path, $default = NULL, $delimiter = NULL)
  86. {
  87. if ( ! Arr::is_array($array))
  88. {
  89. // This is not an array!
  90. return $default;
  91. }
  92. if (is_array($path))
  93. {
  94. // The path has already been separated into keys
  95. $keys = $path;
  96. }
  97. else
  98. {
  99. if (isset($array[$path]))
  100. {
  101. // No need to do extra processing
  102. return $array[$path];
  103. }
  104. if ($delimiter === NULL)
  105. {
  106. // Use the default delimiter
  107. $delimiter = Arr::$delimiter;
  108. }
  109. // Remove starting delimiters and spaces
  110. $path = ltrim($path, "{$delimiter} ");
  111. // Remove ending delimiters, spaces, and wildcards
  112. $path = rtrim($path, "{$delimiter} *");
  113. // Split the keys by delimiter
  114. $keys = explode($delimiter, $path);
  115. }
  116. do
  117. {
  118. $key = array_shift($keys);
  119. if (ctype_digit($key))
  120. {
  121. // Make the key an integer
  122. $key = (int) $key;
  123. }
  124. if (isset($array[$key]))
  125. {
  126. if ($keys)
  127. {
  128. if (Arr::is_array($array[$key]))
  129. {
  130. // Dig down into the next part of the path
  131. $array = $array[$key];
  132. }
  133. else
  134. {
  135. // Unable to dig deeper
  136. break;
  137. }
  138. }
  139. else
  140. {
  141. // Found the path requested
  142. return $array[$key];
  143. }
  144. }
  145. elseif ($key === '*')
  146. {
  147. // Handle wildcards
  148. $values = [];
  149. foreach ($array as $arr)
  150. {
  151. if ($value = Arr::path($arr, implode('.', $keys)))
  152. {
  153. $values[] = $value;
  154. }
  155. }
  156. if ($values)
  157. {
  158. // Found the values requested
  159. return $values;
  160. }
  161. else
  162. {
  163. // Unable to dig deeper
  164. break;
  165. }
  166. }
  167. else
  168. {
  169. // Unable to dig deeper
  170. break;
  171. }
  172. }
  173. while ($keys);
  174. // Unable to find the value requested
  175. return $default;
  176. }
  177. /**
  178. * Set a value on an array by path.
  179. *
  180. * @see Arr::path()
  181. * @param array $array Array to update
  182. * @param string $path Path
  183. * @param mixed $value Value to set
  184. * @param string $delimiter Path delimiter
  185. */
  186. public static function set_path( & $array, $path, $value, $delimiter = NULL)
  187. {
  188. if ( ! $delimiter)
  189. {
  190. // Use the default delimiter
  191. $delimiter = Arr::$delimiter;
  192. }
  193. // The path has already been separated into keys
  194. $keys = $path;
  195. if ( ! is_array($path))
  196. {
  197. // Split the keys by delimiter
  198. $keys = explode($delimiter, $path);
  199. }
  200. // Set current $array to inner-most array path
  201. while (count($keys) > 1)
  202. {
  203. $key = array_shift($keys);
  204. if (ctype_digit($key))
  205. {
  206. // Make the key an integer
  207. $key = (int) $key;
  208. }
  209. if ( ! isset($array[$key]))
  210. {
  211. $array[$key] = [];
  212. }
  213. $array = & $array[$key];
  214. }
  215. // Set key on inner-most array
  216. $array[array_shift($keys)] = $value;
  217. }
  218. /**
  219. * Fill an array with a range of numbers.
  220. *
  221. * // Fill an array with values 5, 10, 15, 20
  222. * $values = Arr::range(5, 20);
  223. *
  224. * @param integer $step stepping
  225. * @param integer $max ending number
  226. * @return array
  227. */
  228. public static function range($step = 10, $max = 100)
  229. {
  230. if ($step < 1)
  231. return [];
  232. $array = [];
  233. for ($i = $step; $i <= $max; $i += $step)
  234. {
  235. $array[$i] = $i;
  236. }
  237. return $array;
  238. }
  239. /**
  240. * Retrieve a single key from an array. If the key does not exist in the
  241. * array, the default value will be returned instead.
  242. *
  243. * // Get the value "username" from $_POST, if it exists
  244. * $username = Arr::get($_POST, 'username');
  245. *
  246. * // Get the value "sorting" from $_GET, if it exists
  247. * $sorting = Arr::get($_GET, 'sorting');
  248. *
  249. * @param array $array array to extract from
  250. * @param string $key key name
  251. * @param mixed $default default value
  252. * @return mixed
  253. */
  254. public static function get($array, $key, $default = NULL)
  255. {
  256. if ($array instanceof ArrayObject) {
  257. // This is a workaround for inconsistent implementation of isset between PHP and HHVM
  258. // See https://github.com/facebook/hhvm/issues/3437
  259. return $array->offsetExists($key) ? $array->offsetGet($key) : $default;
  260. } else {
  261. return isset($array[$key]) ? $array[$key] : $default;
  262. }
  263. }
  264. /**
  265. * Retrieves multiple paths from an array. If the path does not exist in the
  266. * array, the default value will be added instead.
  267. *
  268. * // Get the values "username", "password" from $_POST
  269. * $auth = Arr::extract($_POST, array('username', 'password'));
  270. *
  271. * // Get the value "level1.level2a" from $data
  272. * $data = array('level1' => array('level2a' => 'value 1', 'level2b' => 'value 2'));
  273. * Arr::extract($data, array('level1.level2a', 'password'));
  274. *
  275. * @param array $array array to extract paths from
  276. * @param array $paths list of path
  277. * @param mixed $default default value
  278. * @return array
  279. */
  280. public static function extract($array, array $paths, $default = NULL)
  281. {
  282. $found = [];
  283. foreach ($paths as $path)
  284. {
  285. Arr::set_path($found, $path, Arr::path($array, $path, $default));
  286. }
  287. return $found;
  288. }
  289. /**
  290. * Retrieves muliple single-key values from a list of arrays.
  291. *
  292. * // Get all of the "id" values from a result
  293. * $ids = Arr::pluck($result, 'id');
  294. *
  295. * [!!] A list of arrays is an array that contains arrays, eg: array(array $a, array $b, array $c, ...)
  296. *
  297. * @param array $array list of arrays to check
  298. * @param string $key key to pluck
  299. * @return array
  300. */
  301. public static function pluck($array, $key)
  302. {
  303. $values = [];
  304. foreach ($array as $row)
  305. {
  306. if (isset($row[$key]))
  307. {
  308. // Found a value in this row
  309. $values[] = $row[$key];
  310. }
  311. }
  312. return $values;
  313. }
  314. /**
  315. * Adds a value to the beginning of an associative array.
  316. *
  317. * // Add an empty value to the start of a select list
  318. * Arr::unshift($array, 'none', 'Select a value');
  319. *
  320. * @param array $array array to modify
  321. * @param string $key array key name
  322. * @param mixed $val array value
  323. * @return array
  324. */
  325. public static function unshift( array & $array, $key, $val)
  326. {
  327. $array = array_reverse($array, TRUE);
  328. $array[$key] = $val;
  329. $array = array_reverse($array, TRUE);
  330. return $array;
  331. }
  332. /**
  333. * Recursive version of [array_map](http://php.net/array_map), applies one or more
  334. * callbacks to all elements in an array, including sub-arrays.
  335. *
  336. * // Apply "strip_tags" to every element in the array
  337. * $array = Arr::map('strip_tags', $array);
  338. *
  339. * // Apply $this->filter to every element in the array
  340. * $array = Arr::map(array(array($this,'filter')), $array);
  341. *
  342. * // Apply strip_tags and $this->filter to every element
  343. * $array = Arr::map(array('strip_tags',array($this,'filter')), $array);
  344. *
  345. * [!!] Because you can pass an array of callbacks, if you wish to use an array-form callback
  346. * you must nest it in an additional array as above. Calling Arr::map(array($this,'filter'), $array)
  347. * will cause an error.
  348. * [!!] Unlike `array_map`, this method requires a callback and will only map
  349. * a single array.
  350. *
  351. * @param mixed $callbacks array of callbacks to apply to every element in the array
  352. * @param array $array array to map
  353. * @param array $keys array of keys to apply to
  354. * @return array
  355. */
  356. public static function map($callbacks, $array, $keys = NULL)
  357. {
  358. foreach ($array as $key => $val)
  359. {
  360. if (is_array($val))
  361. {
  362. $array[$key] = Arr::map($callbacks, $array[$key], $keys);
  363. }
  364. elseif ( ! is_array($keys) OR in_array($key, $keys))
  365. {
  366. if (is_array($callbacks))
  367. {
  368. foreach ($callbacks as $cb)
  369. {
  370. $array[$key] = call_user_func($cb, $array[$key]);
  371. }
  372. }
  373. else
  374. {
  375. $array[$key] = call_user_func($callbacks, $array[$key]);
  376. }
  377. }
  378. }
  379. return $array;
  380. }
  381. /**
  382. * Recursively merge two or more arrays. Values in an associative array
  383. * overwrite previous values with the same key. Values in an indexed array
  384. * are appended, but only when they do not already exist in the result.
  385. *
  386. * Note that this does not work the same as [array_merge_recursive](http://php.net/array_merge_recursive)!
  387. *
  388. * $john = array('name' => 'john', 'children' => array('fred', 'paul', 'sally', 'jane'));
  389. * $mary = array('name' => 'mary', 'children' => array('jane'));
  390. *
  391. * // John and Mary are married, merge them together
  392. * $john = Arr::merge($john, $mary);
  393. *
  394. * // The output of $john will now be:
  395. * array('name' => 'mary', 'children' => array('fred', 'paul', 'sally', 'jane'))
  396. *
  397. * @param array $array1 initial array
  398. * @param array $array2,... array to merge
  399. * @return array
  400. */
  401. public static function merge($array1, $array2)
  402. {
  403. if (Arr::is_assoc($array2))
  404. {
  405. foreach ($array2 as $key => $value)
  406. {
  407. if (is_array($value)
  408. AND isset($array1[$key])
  409. AND is_array($array1[$key])
  410. )
  411. {
  412. $array1[$key] = Arr::merge($array1[$key], $value);
  413. }
  414. else
  415. {
  416. $array1[$key] = $value;
  417. }
  418. }
  419. }
  420. else
  421. {
  422. foreach ($array2 as $value)
  423. {
  424. if ( ! in_array($value, $array1, TRUE))
  425. {
  426. $array1[] = $value;
  427. }
  428. }
  429. }
  430. if (func_num_args() > 2)
  431. {
  432. foreach (array_slice(func_get_args(), 2) as $array2)
  433. {
  434. if (Arr::is_assoc($array2))
  435. {
  436. foreach ($array2 as $key => $value)
  437. {
  438. if (is_array($value)
  439. AND isset($array1[$key])
  440. AND is_array($array1[$key])
  441. )
  442. {
  443. $array1[$key] = Arr::merge($array1[$key], $value);
  444. }
  445. else
  446. {
  447. $array1[$key] = $value;
  448. }
  449. }
  450. }
  451. else
  452. {
  453. foreach ($array2 as $value)
  454. {
  455. if ( ! in_array($value, $array1, TRUE))
  456. {
  457. $array1[] = $value;
  458. }
  459. }
  460. }
  461. }
  462. }
  463. return $array1;
  464. }
  465. /**
  466. * Overwrites an array with values from input arrays.
  467. * Keys that do not exist in the first array will not be added!
  468. *
  469. * $a1 = array('name' => 'john', 'mood' => 'happy', 'food' => 'bacon');
  470. * $a2 = array('name' => 'jack', 'food' => 'tacos', 'drink' => 'beer');
  471. *
  472. * // Overwrite the values of $a1 with $a2
  473. * $array = Arr::overwrite($a1, $a2);
  474. *
  475. * // The output of $array will now be:
  476. * array('name' => 'jack', 'mood' => 'happy', 'food' => 'tacos')
  477. *
  478. * @param array $array1 master array
  479. * @param array $array2 input arrays that will overwrite existing values
  480. * @return array
  481. */
  482. public static function overwrite($array1, $array2)
  483. {
  484. foreach (array_intersect_key($array2, $array1) as $key => $value)
  485. {
  486. $array1[$key] = $value;
  487. }
  488. if (func_num_args() > 2)
  489. {
  490. foreach (array_slice(func_get_args(), 2) as $array2)
  491. {
  492. foreach (array_intersect_key($array2, $array1) as $key => $value)
  493. {
  494. $array1[$key] = $value;
  495. }
  496. }
  497. }
  498. return $array1;
  499. }
  500. /**
  501. * Creates a callable function and parameter list from a string representation.
  502. * Note that this function does not validate the callback string.
  503. *
  504. * // Get the callback function and parameters
  505. * list($func, $params) = Arr::callback('Foo::bar(apple,orange)');
  506. *
  507. * // Get the result of the callback
  508. * $result = call_user_func_array($func, $params);
  509. *
  510. * @param string $str callback string
  511. * @return array function, params
  512. */
  513. public static function callback($str)
  514. {
  515. // Overloaded as parts are found
  516. $command = $params = NULL;
  517. // command[param,param]
  518. if (preg_match('/^([^\(]*+)\((.*)\)$/', $str, $match))
  519. {
  520. // command
  521. $command = $match[1];
  522. if ($match[2] !== '')
  523. {
  524. // param,param
  525. $params = preg_split('/(?<!\\\\),/', $match[2]);
  526. $params = str_replace('\,', ',', $params);
  527. }
  528. }
  529. else
  530. {
  531. // command
  532. $command = $str;
  533. }
  534. if (strpos($command, '::') !== FALSE)
  535. {
  536. // Create a static method callable command
  537. $command = explode('::', $command, 2);
  538. }
  539. return [$command, $params];
  540. }
  541. /**
  542. * Convert a multi-dimensional array into a single-dimensional array.
  543. *
  544. * $array = array('set' => array('one' => 'something'), 'two' => 'other');
  545. *
  546. * // Flatten the array
  547. * $array = Arr::flatten($array);
  548. *
  549. * // The array will now be
  550. * array('one' => 'something', 'two' => 'other');
  551. *
  552. * [!!] The keys of array values will be discarded.
  553. *
  554. * @param array $array array to flatten
  555. * @return array
  556. * @since 3.0.6
  557. */
  558. public static function flatten($array)
  559. {
  560. $is_assoc = Arr::is_assoc($array);
  561. $flat = [];
  562. foreach ($array as $key => $value)
  563. {
  564. if (is_array($value))
  565. {
  566. $flat = array_merge($flat, Arr::flatten($value));
  567. }
  568. else
  569. {
  570. if ($is_assoc)
  571. {
  572. $flat[$key] = $value;
  573. }
  574. else
  575. {
  576. $flat[] = $value;
  577. }
  578. }
  579. }
  580. return $flat;
  581. }
  582. }