PageRenderTime 54ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/fuel/core/classes/arr.php

https://bitbucket.org/sriedel/iccrm-wip
PHP | 876 lines | 513 code | 94 blank | 269 comment | 52 complexity | e360eb8ec10f8a0e21acbe0710acf44c MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /**
  3. * Part of the Fuel framework.
  4. *
  5. * @package Fuel
  6. * @version 1.0
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2012 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. /**
  14. * The Arr class provides a few nice functions for making
  15. * dealing with arrays easier
  16. *
  17. * @package Fuel
  18. * @subpackage Core
  19. */
  20. class Arr
  21. {
  22. /**
  23. * Gets a dot-notated key from an array, with a default value if it does
  24. * not exist.
  25. *
  26. * @param array $array The search array
  27. * @param mixed $key The dot-notated key or array of keys
  28. * @param string $default The default value
  29. * @return mixed
  30. */
  31. public static function get($array, $key, $default = null)
  32. {
  33. if ( ! is_array($array) and ! $array instanceof \ArrayAccess)
  34. {
  35. throw new \InvalidArgumentException('First parameter must be an array or ArrayAccess object.');
  36. }
  37. if (is_null($key))
  38. {
  39. return $array;
  40. }
  41. if (is_array($key))
  42. {
  43. $return = array();
  44. foreach ($key as $k)
  45. {
  46. $return[$k] = static::get($array, $k, $default);
  47. }
  48. return $return;
  49. }
  50. foreach (explode('.', $key) as $key_part)
  51. {
  52. if (($array instanceof \ArrayAccess and isset($array[$key_part])) === false)
  53. {
  54. if ( ! is_array($array) or ! array_key_exists($key_part, $array))
  55. {
  56. return \Fuel::value($default);
  57. }
  58. }
  59. $array = $array[$key_part];
  60. }
  61. return $array;
  62. }
  63. /**
  64. * Set an array item (dot-notated) to the value.
  65. *
  66. * @param array $array The array to insert it into
  67. * @param mixed $key The dot-notated key to set or array of keys
  68. * @param mixed $value The value
  69. * @return void
  70. */
  71. public static function set(&$array, $key, $value = null)
  72. {
  73. if (is_null($key))
  74. {
  75. $array = $value;
  76. return;
  77. }
  78. if (is_array($key))
  79. {
  80. foreach ($key as $k => $v)
  81. {
  82. static::set($array, $k, $v);
  83. }
  84. }
  85. else
  86. {
  87. $keys = explode('.', $key);
  88. while (count($keys) > 1)
  89. {
  90. $key = array_shift($keys);
  91. if ( ! isset($array[$key]) or ! is_array($array[$key]))
  92. {
  93. $array[$key] = array();
  94. }
  95. $array =& $array[$key];
  96. }
  97. $array[array_shift($keys)] = $value;
  98. }
  99. }
  100. /**
  101. * Pluck an array of values from an array.
  102. *
  103. * @param array $array collection of arrays to pluck from
  104. * @param string $key key of the value to pluck
  105. * @param string $index optional return array index key, true for original index
  106. * @return array array of plucked values
  107. */
  108. public static function pluck($array, $key, $index = null)
  109. {
  110. $return = array();
  111. $get_deep = strpos($key, '.') !== false;
  112. if ( ! $index)
  113. {
  114. foreach ($array as $i => $a)
  115. {
  116. $return[] = (is_object($a) and ! ($a instanceof \ArrayAccess)) ? $a->{$key} :
  117. ($get_deep ? static::get($a, $key) : $a[$key]);
  118. }
  119. }
  120. else
  121. {
  122. foreach ($array as $i => $a)
  123. {
  124. $index !== true and $i = (is_object($a) and ! ($a instanceof \ArrayAccess)) ? $a->{$index} : $a[$index];
  125. $return[$i] = (is_object($a) and ! ($a instanceof \ArrayAccess)) ? $a->{$key} :
  126. ($get_deep ? static::get($a, $key) : $a[$key]);
  127. }
  128. }
  129. return $return;
  130. }
  131. /**
  132. * Array_key_exists with a dot-notated key from an array.
  133. *
  134. * @param array $array The search array
  135. * @param mixed $key The dot-notated key or array of keys
  136. * @return mixed
  137. */
  138. public static function key_exists($array, $key)
  139. {
  140. foreach (explode('.', $key) as $key_part)
  141. {
  142. if ( ! is_array($array) or ! array_key_exists($key_part, $array))
  143. {
  144. return false;
  145. }
  146. $array = $array[$key_part];
  147. }
  148. return true;
  149. }
  150. /**
  151. * Unsets dot-notated key from an array
  152. *
  153. * @param array $array The search array
  154. * @param mixed $key The dot-notated key or array of keys
  155. * @return mixed
  156. */
  157. public static function delete(&$array, $key)
  158. {
  159. if (is_null($key))
  160. {
  161. return false;
  162. }
  163. if (is_array($key))
  164. {
  165. $return = array();
  166. foreach ($key as $k)
  167. {
  168. $return[$k] = static::delete($array, $k);
  169. }
  170. return $return;
  171. }
  172. $key_parts = explode('.', $key);
  173. if ( ! is_array($array) or ! array_key_exists($key_parts[0], $array))
  174. {
  175. return false;
  176. }
  177. $this_key = array_shift($key_parts);
  178. if ( ! empty($key_parts))
  179. {
  180. $key = implode('.', $key_parts);
  181. return static::delete($array[$this_key], $key);
  182. }
  183. else
  184. {
  185. unset($array[$this_key]);
  186. }
  187. return true;
  188. }
  189. /**
  190. * Converts a multi-dimensional associative array into an array of key => values with the provided field names
  191. *
  192. * @param array $assoc the array to convert
  193. * @param string $key_field the field name of the key field
  194. * @param string $val_field the field name of the value field
  195. * @return array
  196. * @throws \InvalidArgumentException
  197. */
  198. public static function assoc_to_keyval($assoc, $key_field, $val_field)
  199. {
  200. if ( ! is_array($assoc) and ! $assoc instanceof \Iterator)
  201. {
  202. throw new \InvalidArgumentException('The first parameter must be an array.');
  203. }
  204. $output = array();
  205. foreach ($assoc as $row)
  206. {
  207. if (isset($row[$key_field]) and isset($row[$val_field]))
  208. {
  209. $output[$row[$key_field]] = $row[$val_field];
  210. }
  211. }
  212. return $output;
  213. }
  214. /**
  215. * Converts the given 1 dimensional non-associative array to an associative
  216. * array.
  217. *
  218. * The array given must have an even number of elements or null will be returned.
  219. *
  220. * Arr::to_assoc(array('foo','bar'));
  221. *
  222. * @param string $arr the array to change
  223. * @return array|null the new array or null
  224. * @throws \BadMethodCallException
  225. */
  226. public static function to_assoc($arr)
  227. {
  228. if (($count = count($arr)) % 2 > 0)
  229. {
  230. throw new \BadMethodCallException('Number of values in to_assoc must be even.');
  231. }
  232. $keys = $vals = array();
  233. for ($i = 0; $i < $count - 1; $i += 2)
  234. {
  235. $keys[] = array_shift($arr);
  236. $vals[] = array_shift($arr);
  237. }
  238. return array_combine($keys, $vals);
  239. }
  240. /**
  241. * Checks if the given array is an assoc array.
  242. *
  243. * @param array $arr the array to check
  244. * @return bool true if its an assoc array, false if not
  245. */
  246. public static function is_assoc($arr)
  247. {
  248. if ( ! is_array($arr))
  249. {
  250. throw new \InvalidArgumentException('The parameter must be an array.');
  251. }
  252. $counter = 0;
  253. foreach ($arr as $key => $unused)
  254. {
  255. if ( ! is_int($key) or $key !== $counter++)
  256. {
  257. return true;
  258. }
  259. }
  260. return false;
  261. }
  262. /**
  263. * Flattens a multi-dimensional associative array down into a 1 dimensional
  264. * associative array.
  265. *
  266. * @param array the array to flatten
  267. * @param string what to glue the keys together with
  268. * @param bool whether to reset and start over on a new array
  269. * @param bool whether to flatten only associative array's, or also indexed ones
  270. * @return array
  271. */
  272. public static function flatten($array, $glue = ':', $reset = true, $indexed = true)
  273. {
  274. static $return = array();
  275. static $curr_key = array();
  276. if ($reset)
  277. {
  278. $return = array();
  279. $curr_key = array();
  280. }
  281. foreach ($array as $key => $val)
  282. {
  283. $curr_key[] = $key;
  284. if (is_array($val) and ($indexed or array_values($val) !== $val))
  285. {
  286. static::flatten_assoc($val, $glue, false);
  287. }
  288. else
  289. {
  290. $return[implode($glue, $curr_key)] = $val;
  291. }
  292. array_pop($curr_key);
  293. }
  294. return $return;
  295. }
  296. /**
  297. * Flattens a multi-dimensional associative array down into a 1 dimensional
  298. * associative array.
  299. *
  300. * @param array the array to flatten
  301. * @param string what to glue the keys together with
  302. * @param bool whether to reset and start over on a new array
  303. * @return array
  304. */
  305. public static function flatten_assoc($array, $glue = ':', $reset = true)
  306. {
  307. return static::flatten($array, $glue, $reset, false);
  308. }
  309. /**
  310. * Reverse a flattened array in its original form.
  311. *
  312. * @param array $array flattened array
  313. * @param string $glue glue used in flattening
  314. * @return array the unflattened array
  315. */
  316. public static function reverse_flatten($array, $glue = ':')
  317. {
  318. $return = array();
  319. foreach ($array as $key => $value)
  320. {
  321. if (stripos($key, $glue) !== false)
  322. {
  323. $keys = explode($glue, $key);
  324. $temp =& $return;
  325. while (count($keys) > 1)
  326. {
  327. $key = array_shift($keys);
  328. $key = is_numeric($key) ? (int) $key : $key;
  329. if ( ! isset($temp[$key]) or ! is_array($temp[$key]))
  330. {
  331. $temp[$key] = array();
  332. }
  333. $temp =& $temp[$key];
  334. }
  335. $key = array_shift($keys);
  336. $key = is_numeric($key) ? (int) $key : $key;
  337. $temp[$key] = $value;
  338. }
  339. else
  340. {
  341. $key = is_numeric($key) ? (int) $key : $key;
  342. $return[$key] = $value;
  343. }
  344. }
  345. return $return;
  346. }
  347. /**
  348. * Filters an array on prefixed associative keys.
  349. *
  350. * @param array the array to filter.
  351. * @param string prefix to filter on.
  352. * @param bool whether to remove the prefix.
  353. * @return array
  354. */
  355. public static function filter_prefixed($array, $prefix, $remove_prefix = true)
  356. {
  357. $return = array();
  358. foreach ($array as $key => $val)
  359. {
  360. if (preg_match('/^'.$prefix.'/', $key))
  361. {
  362. if ($remove_prefix === true)
  363. {
  364. $key = preg_replace('/^'.$prefix.'/','',$key);
  365. }
  366. $return[$key] = $val;
  367. }
  368. }
  369. return $return;
  370. }
  371. /**
  372. * Removes items from an array that match a key prefix.
  373. *
  374. * @param array the array to remove from
  375. * @param string prefix to filter on
  376. * @return array
  377. */
  378. public static function remove_prefixed($array, $prefix)
  379. {
  380. foreach ($array as $key => $val)
  381. {
  382. if (preg_match('/^'.$prefix.'/', $key))
  383. {
  384. unset($array[$key]);
  385. }
  386. }
  387. return $array;
  388. }
  389. /**
  390. * Filters an array on suffixed associative keys.
  391. *
  392. * @param array the array to filter.
  393. * @param string suffix to filter on.
  394. * @param bool whether to remove the suffix.
  395. * @return array
  396. */
  397. public static function filter_suffixed($array, $suffix, $remove_suffix = true)
  398. {
  399. $return = array();
  400. foreach ($array as $key => $val)
  401. {
  402. if (preg_match('/'.$suffix.'$/', $key))
  403. {
  404. if ($remove_suffix === true)
  405. {
  406. $key = preg_replace('/'.$suffix.'$/','',$key);
  407. }
  408. $return[$key] = $val;
  409. }
  410. }
  411. return $return;
  412. }
  413. /**
  414. * Removes items from an array that match a key suffix.
  415. *
  416. * @param array the array to remove from
  417. * @param string suffix to filter on
  418. * @return array
  419. */
  420. public static function remove_suffixed($array, $suffix)
  421. {
  422. foreach ($array as $key => $val)
  423. {
  424. if (preg_match('/'.$suffix.'$/', $key))
  425. {
  426. unset($array[$key]);
  427. }
  428. }
  429. return $array;
  430. }
  431. /**
  432. * Filters an array by an array of keys
  433. *
  434. * @param array the array to filter.
  435. * @param array the keys to filter
  436. * @param bool if true, removes the matched elements.
  437. * @return array
  438. */
  439. public static function filter_keys($array, $keys, $remove = false)
  440. {
  441. $return = array();
  442. foreach ($keys as $key)
  443. {
  444. if (array_key_exists($key, $array))
  445. {
  446. $remove or $return[$key] = $array[$key];
  447. if($remove)
  448. {
  449. unset($array[$key]);
  450. }
  451. }
  452. }
  453. return $remove ? $array : $return;
  454. }
  455. /**
  456. * Insert value(s) into an array, mostly an array_splice alias
  457. * WARNING: original array is edited by reference, only boolean success is returned
  458. *
  459. * @param array the original array (by reference)
  460. * @param array|mixed the value(s) to insert, if you want to insert an array it needs to be in an array itself
  461. * @param int the numeric position at which to insert, negative to count from the end backwards
  462. * @return bool false when array shorter then $pos, otherwise true
  463. */
  464. public static function insert(array &$original, $value, $pos)
  465. {
  466. if (count($original) < abs($pos))
  467. {
  468. \Error::notice('Position larger than number of elements in array in which to insert.');
  469. return false;
  470. }
  471. array_splice($original, $pos, 0, $value);
  472. return true;
  473. }
  474. /**
  475. * Insert value(s) into an array, mostly an array_splice alias
  476. * WARNING: original array is edited by reference, only boolean success is returned
  477. *
  478. * @param array the original array (by reference)
  479. * @param array|mixed the value(s) to insert, if you want to insert an array it needs to be in an array itself
  480. * @param int the numeric position at which to insert, negative to count from the end backwards
  481. * @return bool false when array shorter then $pos, otherwise true
  482. */
  483. public static function insert_assoc(array &$original, array $values, $pos)
  484. {
  485. if (count($original) < abs($pos))
  486. {
  487. return false;
  488. }
  489. $original = array_slice($original, 0, $pos, true) + $values + array_slice($original, $pos, null, true);
  490. return true;
  491. }
  492. /**
  493. * Insert value(s) into an array before a specific key
  494. * WARNING: original array is edited by reference, only boolean success is returned
  495. *
  496. * @param array the original array (by reference)
  497. * @param array|mixed the value(s) to insert, if you want to insert an array it needs to be in an array itself
  498. * @param string|int the key before which to insert
  499. * @param bool wether the input is an associative array
  500. * @return bool false when key isn't found in the array, otherwise true
  501. */
  502. public static function insert_before_key(array &$original, $value, $key, $is_assoc = false)
  503. {
  504. $pos = array_search($key, array_keys($original));
  505. if ($pos === false)
  506. {
  507. \Error::notice('Unknown key before which to insert the new value into the array.');
  508. return false;
  509. }
  510. return $is_assoc ? static::insert_assoc($original, $value, $pos) : static::insert($original, $value, $pos);
  511. }
  512. /**
  513. * Insert value(s) into an array after a specific key
  514. * WARNING: original array is edited by reference, only boolean success is returned
  515. *
  516. * @param array the original array (by reference)
  517. * @param array|mixed the value(s) to insert, if you want to insert an array it needs to be in an array itself
  518. * @param string|int the key after which to insert
  519. * @param bool wether the input is an associative array
  520. * @return bool false when key isn't found in the array, otherwise true
  521. */
  522. public static function insert_after_key(array &$original, $value, $key, $is_assoc = false)
  523. {
  524. $pos = array_search($key, array_keys($original));
  525. if ($pos === false)
  526. {
  527. \Error::notice('Unknown key after which to insert the new value into the array.');
  528. return false;
  529. }
  530. return $is_assoc ? static::insert_assoc($original, $value, $pos + 1) : static::insert($original, $value, $pos + 1);
  531. }
  532. /**
  533. * Insert value(s) into an array after a specific value (first found in array)
  534. *
  535. * @param array the original array (by reference)
  536. * @param array|mixed the value(s) to insert, if you want to insert an array it needs to be in an array itself
  537. * @param string|int the value after which to insert
  538. * @param bool wether the input is an associative array
  539. * @return bool false when value isn't found in the array, otherwise true
  540. */
  541. public static function insert_after_value(array &$original, $value, $search, $is_assoc = false)
  542. {
  543. $key = array_search($search, $original);
  544. if ($key === false)
  545. {
  546. \Error::notice('Unknown value after which to insert the new value into the array.');
  547. return false;
  548. }
  549. return static::insert_after_key($original, $value, $key, $is_assoc);
  550. }
  551. /**
  552. * Insert value(s) into an array before a specific value (first found in array)
  553. *
  554. * @param array the original array (by reference)
  555. * @param array|mixed the value(s) to insert, if you want to insert an array it needs to be in an array itself
  556. * @param string|int the value after which to insert
  557. * @param bool wether the input is an associative array
  558. * @return bool false when value isn't found in the array, otherwise true
  559. */
  560. public static function insert_before_value(array &$original, $value, $search, $is_assoc = false)
  561. {
  562. $key = array_search($search, $original);
  563. if ($key === false)
  564. {
  565. \Error::notice('Unknown value before which to insert the new value into the array.');
  566. return false;
  567. }
  568. return static::insert_before_key($original, $value, $key, $is_assoc);
  569. }
  570. /**
  571. * Sorts a multi-dimensional array by it's values.
  572. *
  573. * @access public
  574. * @param array The array to fetch from
  575. * @param string The key to sort by
  576. * @param string The order (asc or desc)
  577. * @param int The php sort type flag
  578. * @return array
  579. */
  580. public static function sort($array, $key, $order = 'asc', $sort_flags = SORT_REGULAR)
  581. {
  582. if ( ! is_array($array))
  583. {
  584. throw new \InvalidArgumentException('Arr::sort() - $array must be an array.');
  585. }
  586. if (empty($array))
  587. {
  588. return $array;
  589. }
  590. foreach ($array as $k => $v)
  591. {
  592. $b[$k] = static::get($v, $key);
  593. }
  594. switch ($order)
  595. {
  596. case 'asc':
  597. asort($b, $sort_flags);
  598. break;
  599. case 'desc':
  600. arsort($b, $sort_flags);
  601. break;
  602. default:
  603. throw new \InvalidArgumentException('Arr::sort() - $order must be asc or desc.');
  604. break;
  605. }
  606. foreach ($b as $key => $val)
  607. {
  608. $c[] = $array[$key];
  609. }
  610. return $c;
  611. }
  612. /**
  613. * Sorts an array on multitiple values, with deep sorting support.
  614. *
  615. * @param array $array collection of arrays/objects to sort
  616. * @param array $conditions sorting conditions
  617. * @param bool @ignore_case wether to sort case insensitive
  618. */
  619. public static function multisort($array, $conditions, $ignore_case = false)
  620. {
  621. $temp = array();
  622. $keys = array_keys($conditions);
  623. foreach($keys as $key)
  624. {
  625. $temp[$key] = static::pluck($array, $key, true);
  626. is_array($conditions[$key]) or $conditions[$key] = array($conditions[$key]);
  627. }
  628. $args = array();
  629. foreach ($keys as $key)
  630. {
  631. $args[] = $ignore_case ? array_map('strtolower', $temp[$key]) : $temp[$key];
  632. foreach($conditions[$key] as $flag)
  633. {
  634. $args[] = $flag;
  635. }
  636. }
  637. $args[] = &$array;
  638. call_user_func_array('array_multisort', $args);
  639. return $array;
  640. }
  641. /**
  642. * Find the average of an array
  643. *
  644. * @param array the array containing the values
  645. * @return numeric the average value
  646. */
  647. public static function average($array)
  648. {
  649. // No arguments passed, lets not divide by 0
  650. if ( ! ($count = count($array)) > 0)
  651. {
  652. return 0;
  653. }
  654. return (array_sum($array) / $count);
  655. }
  656. /**
  657. * Replaces key names in an array by names in $replace
  658. *
  659. * @param array the array containing the key/value combinations
  660. * @param array|string key to replace or array containing the replacement keys
  661. * @param string the replacement key
  662. * @return array the array with the new keys
  663. */
  664. public static function replace_key($source, $replace, $new_key = null)
  665. {
  666. if(is_string($replace))
  667. {
  668. $replace = array($replace => $new_key);
  669. }
  670. if ( ! is_array($source) or ! is_array($replace))
  671. {
  672. throw new \InvalidArgumentException('Arr::replace_key() - $source must an array. $replace must be an array or string.');
  673. }
  674. $result = array();
  675. foreach ($source as $key => $value)
  676. {
  677. if (array_key_exists($key, $replace))
  678. {
  679. $result[$replace[$key]] = $value;
  680. }
  681. else
  682. {
  683. $result[$key] = $value;
  684. }
  685. }
  686. return $result;
  687. }
  688. /**
  689. * Merge 2 arrays recursively, differs in 2 important ways from array_merge_recursive()
  690. * - When there's 2 different values and not both arrays, the latter value overwrites the earlier
  691. * instead of merging both into an array
  692. * - Numeric keys that don't conflict aren't changed, only when a numeric key already exists is the
  693. * value added using array_push()
  694. *
  695. * @param array multiple variables all of which must be arrays
  696. * @return array
  697. * @throws \InvalidArgumentException
  698. */
  699. public static function merge()
  700. {
  701. $array = func_get_arg(0);
  702. $arrays = array_slice(func_get_args(), 1);
  703. if ( ! is_array($array))
  704. {
  705. throw new \InvalidArgumentException('Arr::merge() - all arguments must be arrays.');
  706. }
  707. foreach ($arrays as $arr)
  708. {
  709. if ( ! is_array($arr))
  710. {
  711. throw new \InvalidArgumentException('Arr::merge() - all arguments must be arrays.');
  712. }
  713. foreach ($arr as $k => $v)
  714. {
  715. // numeric keys are appended
  716. if (is_int($k))
  717. {
  718. array_key_exists($k, $array) ? array_push($array, $v) : $array[$k] = $v;
  719. }
  720. elseif (is_array($v) and array_key_exists($k, $array) and is_array($array[$k]))
  721. {
  722. $array[$k] = static::merge($array[$k], $v);
  723. }
  724. else
  725. {
  726. $array[$k] = $v;
  727. }
  728. }
  729. }
  730. return $array;
  731. }
  732. /**
  733. * Prepends a value with an asociative key to an array.
  734. * Will overwrite if the value exists.
  735. *
  736. * @param array $arr the array to prepend to
  737. * @param string|array $key the key or array of keys and values
  738. * @param mixed $valye the value to prepend
  739. */
  740. public static function prepend(&$arr, $key, $value = null)
  741. {
  742. $arr = (is_array($key) ? $key : array($key => $value)) + $arr;
  743. }
  744. /**
  745. * Recursive in_array
  746. *
  747. * @param mixed $needle what to search for
  748. * @param array $haystack array to search in
  749. * @return bool wether the needle is found in the haystack.
  750. */
  751. public static function in_array_recursive($needle, $haystack, $strict = false)
  752. {
  753. foreach ($haystack as $value)
  754. {
  755. if ( ! $strict and $needle == $value)
  756. {
  757. return true;
  758. }
  759. elseif ($needle === $value)
  760. {
  761. return true;
  762. }
  763. elseif (is_array($value) and static::in_array_recursive($needle, $value, $strict))
  764. {
  765. return true;
  766. }
  767. }
  768. return false;
  769. }
  770. /**
  771. * Checks if the given array is a multidimensional array.
  772. *
  773. * @param array $arr the array to check
  774. * @param array $all_keys if true, check that all elements are arrays
  775. * @return bool true if its a multidimensional array, false if not
  776. */
  777. public static function is_multi($arr, $all_keys = false)
  778. {
  779. $values = array_filter($arr, 'is_array');
  780. return $all_keys ? count($arr) === count($values) : count($values) > 0;
  781. }
  782. }