BaseArrayHelper.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2008 Yii Software LLC
  4. * @link http://www.yiiframework.com/
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\helpers;
  8. use Yii;
  9. use yii\base\Arrayable;
  10. use yii\base\InvalidParamException;
  11. /**
  12. * BaseArrayHelper provides concrete implementation for [[ArrayHelper]].
  13. *
  14. * Do not use BaseArrayHelper. Use [[ArrayHelper]] instead.
  15. *
  16. * @author Qiang Xue <[email protected]>
  17. * @since 2.0
  18. */
  19. class BaseArrayHelper
  20. {
  21. /**
  22. * Converts an object or an array of objects into an array.
  23. * @param object|array $object the object to be converted into an array
  24. * @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays.
  25. * The properties specified for each class is an array of the following format:
  26. *
  27. * ~~~
  28. * [
  29. * 'app\models\Post' => [
  30. * 'id',
  31. * 'title',
  32. * // the key name in array result => property name
  33. * 'createTime' => 'created_at',
  34. * // the key name in array result => anonymous function
  35. * 'length' => function ($post) {
  36. * return strlen($post->content);
  37. * },
  38. * ],
  39. * ]
  40. * ~~~
  41. *
  42. * The result of `ArrayHelper::toArray($post, $properties)` could be like the following:
  43. *
  44. * ~~~
  45. * [
  46. * 'id' => 123,
  47. * 'title' => 'test',
  48. * 'createTime' => '2013-01-01 12:00AM',
  49. * 'length' => 301,
  50. * ]
  51. * ~~~
  52. *
  53. * @param boolean $recursive whether to recursively converts properties which are objects into arrays.
  54. * @return array the array representation of the object
  55. */
  56. public static function toArray($object, $properties = [], $recursive = true)
  57. {
  58. if (!empty($properties) && is_object($object)) {
  59. $className = get_class($object);
  60. if (!empty($properties[$className])) {
  61. $result = [];
  62. foreach ($properties[$className] as $key => $name) {
  63. if (is_int($key)) {
  64. $result[$name] = $object->$name;
  65. } else {
  66. $result[$key] = static::getValue($object, $name);
  67. }
  68. }
  69. return $result;
  70. }
  71. }
  72. if ($object instanceof Arrayable) {
  73. $object = $object->toArray();
  74. if (!$recursive) {
  75. return $object;
  76. }
  77. }
  78. $result = [];
  79. foreach ($object as $key => $value) {
  80. if ($recursive && (is_array($value) || is_object($value))) {
  81. $result[$key] = static::toArray($value, true);
  82. } else {
  83. $result[$key] = $value;
  84. }
  85. }
  86. return $result;
  87. }
  88. /**
  89. * Merges two or more arrays into one recursively.
  90. * If each array has an element with the same string key value, the latter
  91. * will overwrite the former (different from array_merge_recursive).
  92. * Recursive merging will be conducted if both arrays have an element of array
  93. * type and are having the same key.
  94. * For integer-keyed elements, the elements from the latter array will
  95. * be appended to the former array.
  96. * @param array $a array to be merged to
  97. * @param array $b array to be merged from. You can specify additional
  98. * arrays via third argument, fourth argument etc.
  99. * @return array the merged array (the original arrays are not changed.)
  100. */
  101. public static function merge($a, $b)
  102. {
  103. $args = func_get_args();
  104. $res = array_shift($args);
  105. while (!empty($args)) {
  106. $next = array_shift($args);
  107. foreach ($next as $k => $v) {
  108. if (is_integer($k)) {
  109. isset($res[$k]) ? $res[] = $v : $res[$k] = $v;
  110. } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) {
  111. $res[$k] = self::merge($res[$k], $v);
  112. } else {
  113. $res[$k] = $v;
  114. }
  115. }
  116. }
  117. return $res;
  118. }
  119. /**
  120. * Retrieves the value of an array element or object property with the given key or property name.
  121. * If the key does not exist in the array or object, the default value will be returned instead.
  122. *
  123. * The key may be specified in a dot format to retrieve the value of a sub-array or the property
  124. * of an embedded object. In particular, if the key is `x.y.z`, then the returned value would
  125. * be `$array['x']['y']['z']` or `$array->x->y->z` (if `$array` is an object). If `$array['x']`
  126. * or `$array->x` is neither an array nor an object, the default value will be returned.
  127. * Note that if the array already has an element `x.y.z`, then its value will be returned
  128. * instead of going through the sub-arrays.
  129. *
  130. * Below are some usage examples,
  131. *
  132. * ~~~
  133. * // working with array
  134. * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username');
  135. * // working with object
  136. * $username = \yii\helpers\ArrayHelper::getValue($user, 'username');
  137. * // working with anonymous function
  138. * $fullName = \yii\helpers\ArrayHelper::getValue($user, function($user, $defaultValue) {
  139. * return $user->firstName . ' ' . $user->lastName;
  140. * });
  141. * // using dot format to retrieve the property of embedded object
  142. * $street = \yii\helpers\ArrayHelper::getValue($users, 'address.street');
  143. * ~~~
  144. *
  145. * @param array|object $array array or object to extract value from
  146. * @param string|\Closure $key key name of the array element, or property name of the object,
  147. * or an anonymous function returning the value. The anonymous function signature should be:
  148. * `function($array, $defaultValue)`.
  149. * @param mixed $default the default value to be returned if the specified key does not exist
  150. * @return mixed the value of the element if found, default value otherwise
  151. * @throws InvalidParamException if $array is neither an array nor an object.
  152. */
  153. public static function getValue($array, $key, $default = null)
  154. {
  155. if ($key instanceof \Closure) {
  156. return $key($array, $default);
  157. }
  158. if (is_array($array) && array_key_exists($key, $array)) {
  159. return $array[$key];
  160. }
  161. if (($pos = strrpos($key, '.')) !== false) {
  162. $array = static::getValue($array, substr($key, 0, $pos), $default);
  163. $key = substr($key, $pos + 1);
  164. }
  165. if (is_object($array)) {
  166. return $array->$key;
  167. } elseif (is_array($array)) {
  168. return array_key_exists($key, $array) ? $array[$key] : $default;
  169. } else {
  170. return $default;
  171. }
  172. }
  173. /**
  174. * Removes an item from an array and returns the value. If the key does not exist in the array, the default value
  175. * will be returned instead.
  176. *
  177. * Usage examples,
  178. *
  179. * ~~~
  180. * // $array = ['type' => 'A', 'options' => [1, 2]];
  181. * // working with array
  182. * $type = \yii\helpers\ArrayHelper::remove($array, 'type');
  183. * // $array content
  184. * // $array = ['options' => [1, 2]];
  185. * ~~~
  186. *
  187. * @param array $array the array to extract value from
  188. * @param string $key key name of the array element
  189. * @param mixed $default the default value to be returned if the specified key does not exist
  190. * @return mixed|null the value of the element if found, default value otherwise
  191. */
  192. public static function remove(&$array, $key, $default = null)
  193. {
  194. if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) {
  195. $value = $array[$key];
  196. unset($array[$key]);
  197. return $value;
  198. }
  199. return $default;
  200. }
  201. /**
  202. * Indexes an array according to a specified key.
  203. * The input array should be multidimensional or an array of objects.
  204. *
  205. * The key can be a key name of the sub-array, a property name of object, or an anonymous
  206. * function which returns the key value given an array element.
  207. *
  208. * If a key value is null, the corresponding array element will be discarded and not put in the result.
  209. *
  210. * For example,
  211. *
  212. * ~~~
  213. * $array = [
  214. * ['id' => '123', 'data' => 'abc'],
  215. * ['id' => '345', 'data' => 'def'],
  216. * ];
  217. * $result = ArrayHelper::index($array, 'id');
  218. * // the result is:
  219. * // [
  220. * // '123' => ['id' => '123', 'data' => 'abc'],
  221. * // '345' => ['id' => '345', 'data' => 'def'],
  222. * // ]
  223. *
  224. * // using anonymous function
  225. * $result = ArrayHelper::index($array, function ($element) {
  226. * return $element['id'];
  227. * });
  228. * ~~~
  229. *
  230. * @param array $array the array that needs to be indexed
  231. * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array
  232. * @return array the indexed array
  233. */
  234. public static function index($array, $key)
  235. {
  236. $result = [];
  237. foreach ($array as $element) {
  238. $value = static::getValue($element, $key);
  239. $result[$value] = $element;
  240. }
  241. return $result;
  242. }
  243. /**
  244. * Returns the values of a specified column in an array.
  245. * The input array should be multidimensional or an array of objects.
  246. *
  247. * For example,
  248. *
  249. * ~~~
  250. * $array = [
  251. * ['id' => '123', 'data' => 'abc'],
  252. * ['id' => '345', 'data' => 'def'],
  253. * ];
  254. * $result = ArrayHelper::getColumn($array, 'id');
  255. * // the result is: ['123', '345']
  256. *
  257. * // using anonymous function
  258. * $result = ArrayHelper::getColumn($array, function ($element) {
  259. * return $element['id'];
  260. * });
  261. * ~~~
  262. *
  263. * @param array $array
  264. * @param string|\Closure $name
  265. * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array
  266. * will be re-indexed with integers.
  267. * @return array the list of column values
  268. */
  269. public static function getColumn($array, $name, $keepKeys = true)
  270. {
  271. $result = [];
  272. if ($keepKeys) {
  273. foreach ($array as $k => $element) {
  274. $result[$k] = static::getValue($element, $name);
  275. }
  276. } else {
  277. foreach ($array as $element) {
  278. $result[] = static::getValue($element, $name);
  279. }
  280. }
  281. return $result;
  282. }
  283. /**
  284. * Builds a map (key-value pairs) from a multidimensional array or an array of objects.
  285. * The `$from` and `$to` parameters specify the key names or property names to set up the map.
  286. * Optionally, one can further group the map according to a grouping field `$group`.
  287. *
  288. * For example,
  289. *
  290. * ~~~
  291. * $array = [
  292. * ['id' => '123', 'name' => 'aaa', 'class' => 'x'],
  293. * ['id' => '124', 'name' => 'bbb', 'class' => 'x'],
  294. * ['id' => '345', 'name' => 'ccc', 'class' => 'y'],
  295. * );
  296. *
  297. * $result = ArrayHelper::map($array, 'id', 'name');
  298. * // the result is:
  299. * // [
  300. * // '123' => 'aaa',
  301. * // '124' => 'bbb',
  302. * // '345' => 'ccc',
  303. * // ]
  304. *
  305. * $result = ArrayHelper::map($array, 'id', 'name', 'class');
  306. * // the result is:
  307. * // [
  308. * // 'x' => [
  309. * // '123' => 'aaa',
  310. * // '124' => 'bbb',
  311. * // ],
  312. * // 'y' => [
  313. * // '345' => 'ccc',
  314. * // ],
  315. * // ]
  316. * ~~~
  317. *
  318. * @param array $array
  319. * @param string|\Closure $from
  320. * @param string|\Closure $to
  321. * @param string|\Closure $group
  322. * @return array
  323. */
  324. public static function map($array, $from, $to, $group = null)
  325. {
  326. $result = [];
  327. foreach ($array as $element) {
  328. $key = static::getValue($element, $from);
  329. $value = static::getValue($element, $to);
  330. if ($group !== null) {
  331. $result[static::getValue($element, $group)][$key] = $value;
  332. } else {
  333. $result[$key] = $value;
  334. }
  335. }
  336. return $result;
  337. }
  338. /**
  339. * Checks if the given array contains the specified key.
  340. * This method enhances the `array_key_exists()` function by supporting case-insensitive
  341. * key comparison.
  342. * @param string $key the key to check
  343. * @param array $array the array with keys to check
  344. * @param boolean $caseSensitive whether the key comparison should be case-sensitive
  345. * @return boolean whether the array contains the specified key
  346. */
  347. public static function keyExists($key, $array, $caseSensitive = true)
  348. {
  349. if ($caseSensitive) {
  350. return array_key_exists($key, $array);
  351. } else {
  352. foreach (array_keys($array) as $k) {
  353. if (strcasecmp($key, $k) === 0) {
  354. return true;
  355. }
  356. }
  357. return false;
  358. }
  359. }
  360. /**
  361. * Sorts an array of objects or arrays (with the same structure) by one or several keys.
  362. * @param array $array the array to be sorted. The array will be modified after calling this method.
  363. * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array
  364. * elements, a property name of the objects, or an anonymous function returning the values for comparison
  365. * purpose. The anonymous function signature should be: `function($item)`.
  366. * To sort by multiple keys, provide an array of keys here.
  367. * @param integer|array $direction the sorting direction. It can be either `SORT_ASC` or `SORT_DESC`.
  368. * When sorting by multiple keys with different sorting directions, use an array of sorting directions.
  369. * @param integer|array $sortFlag the PHP sort flag. Valid values include
  370. * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING`, `SORT_LOCALE_STRING`, `SORT_NATURAL` and `SORT_FLAG_CASE`.
  371. * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php)
  372. * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags.
  373. * @throws InvalidParamException if the $descending or $sortFlag parameters do not have
  374. * correct number of elements as that of $key.
  375. */
  376. public static function multisort(&$array, $key, $direction = SORT_ASC, $sortFlag = SORT_REGULAR)
  377. {
  378. $keys = is_array($key) ? $key : [$key];
  379. if (empty($keys) || empty($array)) {
  380. return;
  381. }
  382. $n = count($keys);
  383. if (is_scalar($direction)) {
  384. $direction = array_fill(0, $n, $direction);
  385. } elseif (count($direction) !== $n) {
  386. throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.');
  387. }
  388. if (is_scalar($sortFlag)) {
  389. $sortFlag = array_fill(0, $n, $sortFlag);
  390. } elseif (count($sortFlag) !== $n) {
  391. throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.');
  392. }
  393. $args = [];
  394. foreach ($keys as $i => $key) {
  395. $flag = $sortFlag[$i];
  396. $args[] = static::getColumn($array, $key);
  397. $args[] = $direction[$i];
  398. $args[] = $flag;
  399. }
  400. $args[] = &$array;
  401. call_user_func_array('array_multisort', $args);
  402. }
  403. /**
  404. * Encodes special characters in an array of strings into HTML entities.
  405. * Both the array keys and values will be encoded.
  406. * If a value is an array, this method will also encode it recursively.
  407. * @param array $data data to be encoded
  408. * @param boolean $valuesOnly whether to encode array values only. If false,
  409. * both the array keys and array values will be encoded.
  410. * @param string $charset the charset that the data is using. If not set,
  411. * [[\yii\base\Application::charset]] will be used.
  412. * @return array the encoded data
  413. * @see http://www.php.net/manual/en/function.htmlspecialchars.php
  414. */
  415. public static function htmlEncode($data, $valuesOnly = true, $charset = null)
  416. {
  417. if ($charset === null) {
  418. $charset = Yii::$app->charset;
  419. }
  420. $d = [];
  421. foreach ($data as $key => $value) {
  422. if (!$valuesOnly && is_string($key)) {
  423. $key = htmlspecialchars($key, ENT_QUOTES, $charset);
  424. }
  425. if (is_string($value)) {
  426. $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset);
  427. } elseif (is_array($value)) {
  428. $d[$key] = static::htmlEncode($value, $charset);
  429. }
  430. }
  431. return $d;
  432. }
  433. /**
  434. * Decodes HTML entities into the corresponding characters in an array of strings.
  435. * Both the array keys and values will be decoded.
  436. * If a value is an array, this method will also decode it recursively.
  437. * @param array $data data to be decoded
  438. * @param boolean $valuesOnly whether to decode array values only. If false,
  439. * both the array keys and array values will be decoded.
  440. * @return array the decoded data
  441. * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php
  442. */
  443. public static function htmlDecode($data, $valuesOnly = true)
  444. {
  445. $d = [];
  446. foreach ($data as $key => $value) {
  447. if (!$valuesOnly && is_string($key)) {
  448. $key = htmlspecialchars_decode($key, ENT_QUOTES);
  449. }
  450. if (is_string($value)) {
  451. $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES);
  452. } elseif (is_array($value)) {
  453. $d[$key] = static::htmlDecode($value);
  454. }
  455. }
  456. return $d;
  457. }
  458. }