You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

576 lines
20KB

  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  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 <qiang.xue@gmail.com>
  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 (is_array($object)) {
  59. if ($recursive) {
  60. foreach ($object as $key => $value) {
  61. if (is_array($value) || is_object($value)) {
  62. $object[$key] = static::toArray($value, $properties, true);
  63. }
  64. }
  65. }
  66. return $object;
  67. } elseif (is_object($object)) {
  68. if (!empty($properties)) {
  69. $className = get_class($object);
  70. if (!empty($properties[$className])) {
  71. $result = [];
  72. foreach ($properties[$className] as $key => $name) {
  73. if (is_int($key)) {
  74. $result[$name] = $object->$name;
  75. } else {
  76. $result[$key] = static::getValue($object, $name);
  77. }
  78. }
  79. return $recursive ? static::toArray($result, $properties) : $result;
  80. }
  81. }
  82. if ($object instanceof Arrayable) {
  83. $result = $object->toArray();
  84. } else {
  85. $result = [];
  86. foreach ($object as $key => $value) {
  87. $result[$key] = $value;
  88. }
  89. }
  90. return $recursive ? static::toArray($result) : $result;
  91. } else {
  92. return [$object];
  93. }
  94. }
  95. /**
  96. * Merges two or more arrays into one recursively.
  97. * If each array has an element with the same string key value, the latter
  98. * will overwrite the former (different from array_merge_recursive).
  99. * Recursive merging will be conducted if both arrays have an element of array
  100. * type and are having the same key.
  101. * For integer-keyed elements, the elements from the latter array will
  102. * be appended to the former array.
  103. * @param array $a array to be merged to
  104. * @param array $b array to be merged from. You can specify additional
  105. * arrays via third argument, fourth argument etc.
  106. * @return array the merged array (the original arrays are not changed.)
  107. */
  108. public static function merge($a, $b)
  109. {
  110. $args = func_get_args();
  111. $res = array_shift($args);
  112. while (!empty($args)) {
  113. $next = array_shift($args);
  114. foreach ($next as $k => $v) {
  115. if (is_integer($k)) {
  116. if (isset($res[$k])) {
  117. $res[] = $v;
  118. } else {
  119. $res[$k] = $v;
  120. }
  121. } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) {
  122. $res[$k] = self::merge($res[$k], $v);
  123. } else {
  124. $res[$k] = $v;
  125. }
  126. }
  127. }
  128. return $res;
  129. }
  130. /**
  131. * Retrieves the value of an array element or object property with the given key or property name.
  132. * If the key does not exist in the array or object, the default value will be returned instead.
  133. *
  134. * The key may be specified in a dot format to retrieve the value of a sub-array or the property
  135. * of an embedded object. In particular, if the key is `x.y.z`, then the returned value would
  136. * be `$array['x']['y']['z']` or `$array->x->y->z` (if `$array` is an object). If `$array['x']`
  137. * or `$array->x` is neither an array nor an object, the default value will be returned.
  138. * Note that if the array already has an element `x.y.z`, then its value will be returned
  139. * instead of going through the sub-arrays.
  140. *
  141. * Below are some usage examples,
  142. *
  143. * ~~~
  144. * // working with array
  145. * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username');
  146. * // working with object
  147. * $username = \yii\helpers\ArrayHelper::getValue($user, 'username');
  148. * // working with anonymous function
  149. * $fullName = \yii\helpers\ArrayHelper::getValue($user, function ($user, $defaultValue) {
  150. * return $user->firstName . ' ' . $user->lastName;
  151. * });
  152. * // using dot format to retrieve the property of embedded object
  153. * $street = \yii\helpers\ArrayHelper::getValue($users, 'address.street');
  154. * ~~~
  155. *
  156. * @param array|object $array array or object to extract value from
  157. * @param string|\Closure $key key name of the array element, or property name of the object,
  158. * or an anonymous function returning the value. The anonymous function signature should be:
  159. * `function($array, $defaultValue)`.
  160. * @param mixed $default the default value to be returned if the specified array key does not exist. Not used when
  161. * getting value from an object.
  162. * @return mixed the value of the element if found, default value otherwise
  163. * @throws InvalidParamException if $array is neither an array nor an object.
  164. */
  165. public static function getValue($array, $key, $default = null)
  166. {
  167. if ($key instanceof \Closure) {
  168. return $key($array, $default);
  169. }
  170. if (is_array($array) && array_key_exists($key, $array)) {
  171. return $array[$key];
  172. }
  173. if (($pos = strrpos($key, '.')) !== false) {
  174. $array = static::getValue($array, substr($key, 0, $pos), $default);
  175. $key = substr($key, $pos + 1);
  176. }
  177. if (is_object($array)) {
  178. return $array->$key;
  179. } elseif (is_array($array)) {
  180. return array_key_exists($key, $array) ? $array[$key] : $default;
  181. } else {
  182. return $default;
  183. }
  184. }
  185. /**
  186. * Removes an item from an array and returns the value. If the key does not exist in the array, the default value
  187. * will be returned instead.
  188. *
  189. * Usage examples,
  190. *
  191. * ~~~
  192. * // $array = ['type' => 'A', 'options' => [1, 2]];
  193. * // working with array
  194. * $type = \yii\helpers\ArrayHelper::remove($array, 'type');
  195. * // $array content
  196. * // $array = ['options' => [1, 2]];
  197. * ~~~
  198. *
  199. * @param array $array the array to extract value from
  200. * @param string $key key name of the array element
  201. * @param mixed $default the default value to be returned if the specified key does not exist
  202. * @return mixed|null the value of the element if found, default value otherwise
  203. */
  204. public static function remove(&$array, $key, $default = null)
  205. {
  206. if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) {
  207. $value = $array[$key];
  208. unset($array[$key]);
  209. return $value;
  210. }
  211. return $default;
  212. }
  213. /**
  214. * Indexes an array according to a specified key.
  215. * The input array should be multidimensional or an array of objects.
  216. *
  217. * The key can be a key name of the sub-array, a property name of object, or an anonymous
  218. * function which returns the key value given an array element.
  219. *
  220. * If a key value is null, the corresponding array element will be discarded and not put in the result.
  221. *
  222. * For example,
  223. *
  224. * ~~~
  225. * $array = [
  226. * ['id' => '123', 'data' => 'abc'],
  227. * ['id' => '345', 'data' => 'def'],
  228. * ];
  229. * $result = ArrayHelper::index($array, 'id');
  230. * // the result is:
  231. * // [
  232. * // '123' => ['id' => '123', 'data' => 'abc'],
  233. * // '345' => ['id' => '345', 'data' => 'def'],
  234. * // ]
  235. *
  236. * // using anonymous function
  237. * $result = ArrayHelper::index($array, function ($element) {
  238. * return $element['id'];
  239. * });
  240. * ~~~
  241. *
  242. * @param array $array the array that needs to be indexed
  243. * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array
  244. * @return array the indexed array
  245. */
  246. public static function index($array, $key)
  247. {
  248. $result = [];
  249. foreach ($array as $element) {
  250. $value = static::getValue($element, $key);
  251. $result[$value] = $element;
  252. }
  253. return $result;
  254. }
  255. /**
  256. * Returns the values of a specified column in an array.
  257. * The input array should be multidimensional or an array of objects.
  258. *
  259. * For example,
  260. *
  261. * ~~~
  262. * $array = [
  263. * ['id' => '123', 'data' => 'abc'],
  264. * ['id' => '345', 'data' => 'def'],
  265. * ];
  266. * $result = ArrayHelper::getColumn($array, 'id');
  267. * // the result is: ['123', '345']
  268. *
  269. * // using anonymous function
  270. * $result = ArrayHelper::getColumn($array, function ($element) {
  271. * return $element['id'];
  272. * });
  273. * ~~~
  274. *
  275. * @param array $array
  276. * @param string|\Closure $name
  277. * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array
  278. * will be re-indexed with integers.
  279. * @return array the list of column values
  280. */
  281. public static function getColumn($array, $name, $keepKeys = true)
  282. {
  283. $result = [];
  284. if ($keepKeys) {
  285. foreach ($array as $k => $element) {
  286. $result[$k] = static::getValue($element, $name);
  287. }
  288. } else {
  289. foreach ($array as $element) {
  290. $result[] = static::getValue($element, $name);
  291. }
  292. }
  293. return $result;
  294. }
  295. /**
  296. * Builds a map (key-value pairs) from a multidimensional array or an array of objects.
  297. * The `$from` and `$to` parameters specify the key names or property names to set up the map.
  298. * Optionally, one can further group the map according to a grouping field `$group`.
  299. *
  300. * For example,
  301. *
  302. * ~~~
  303. * $array = [
  304. * ['id' => '123', 'name' => 'aaa', 'class' => 'x'],
  305. * ['id' => '124', 'name' => 'bbb', 'class' => 'x'],
  306. * ['id' => '345', 'name' => 'ccc', 'class' => 'y'],
  307. * ];
  308. *
  309. * $result = ArrayHelper::map($array, 'id', 'name');
  310. * // the result is:
  311. * // [
  312. * // '123' => 'aaa',
  313. * // '124' => 'bbb',
  314. * // '345' => 'ccc',
  315. * // ]
  316. *
  317. * $result = ArrayHelper::map($array, 'id', 'name', 'class');
  318. * // the result is:
  319. * // [
  320. * // 'x' => [
  321. * // '123' => 'aaa',
  322. * // '124' => 'bbb',
  323. * // ],
  324. * // 'y' => [
  325. * // '345' => 'ccc',
  326. * // ],
  327. * // ]
  328. * ~~~
  329. *
  330. * @param array $array
  331. * @param string|\Closure $from
  332. * @param string|\Closure $to
  333. * @param string|\Closure $group
  334. * @return array
  335. */
  336. public static function map($array, $from, $to, $group = null)
  337. {
  338. $result = [];
  339. foreach ($array as $element) {
  340. $key = static::getValue($element, $from);
  341. $value = static::getValue($element, $to);
  342. if ($group !== null) {
  343. $result[static::getValue($element, $group)][$key] = $value;
  344. } else {
  345. $result[$key] = $value;
  346. }
  347. }
  348. return $result;
  349. }
  350. /**
  351. * Checks if the given array contains the specified key.
  352. * This method enhances the `array_key_exists()` function by supporting case-insensitive
  353. * key comparison.
  354. * @param string $key the key to check
  355. * @param array $array the array with keys to check
  356. * @param boolean $caseSensitive whether the key comparison should be case-sensitive
  357. * @return boolean whether the array contains the specified key
  358. */
  359. public static function keyExists($key, $array, $caseSensitive = true)
  360. {
  361. if ($caseSensitive) {
  362. return array_key_exists($key, $array);
  363. } else {
  364. foreach (array_keys($array) as $k) {
  365. if (strcasecmp($key, $k) === 0) {
  366. return true;
  367. }
  368. }
  369. return false;
  370. }
  371. }
  372. /**
  373. * Sorts an array of objects or arrays (with the same structure) by one or several keys.
  374. * @param array $array the array to be sorted. The array will be modified after calling this method.
  375. * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array
  376. * elements, a property name of the objects, or an anonymous function returning the values for comparison
  377. * purpose. The anonymous function signature should be: `function($item)`.
  378. * To sort by multiple keys, provide an array of keys here.
  379. * @param integer|array $direction the sorting direction. It can be either `SORT_ASC` or `SORT_DESC`.
  380. * When sorting by multiple keys with different sorting directions, use an array of sorting directions.
  381. * @param integer|array $sortFlag the PHP sort flag. Valid values include
  382. * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING`, `SORT_LOCALE_STRING`, `SORT_NATURAL` and `SORT_FLAG_CASE`.
  383. * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php)
  384. * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags.
  385. * @throws InvalidParamException if the $direction or $sortFlag parameters do not have
  386. * correct number of elements as that of $key.
  387. */
  388. public static function multisort(&$array, $key, $direction = SORT_ASC, $sortFlag = SORT_REGULAR)
  389. {
  390. $keys = is_array($key) ? $key : [$key];
  391. if (empty($keys) || empty($array)) {
  392. return;
  393. }
  394. $n = count($keys);
  395. if (is_scalar($direction)) {
  396. $direction = array_fill(0, $n, $direction);
  397. } elseif (count($direction) !== $n) {
  398. throw new InvalidParamException('The length of $direction parameter must be the same as that of $keys.');
  399. }
  400. if (is_scalar($sortFlag)) {
  401. $sortFlag = array_fill(0, $n, $sortFlag);
  402. } elseif (count($sortFlag) !== $n) {
  403. throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.');
  404. }
  405. $args = [];
  406. foreach ($keys as $i => $key) {
  407. $flag = $sortFlag[$i];
  408. $args[] = static::getColumn($array, $key);
  409. $args[] = $direction[$i];
  410. $args[] = $flag;
  411. }
  412. $args[] = &$array;
  413. call_user_func_array('array_multisort', $args);
  414. }
  415. /**
  416. * Encodes special characters in an array of strings into HTML entities.
  417. * Only array values will be encoded by default.
  418. * If a value is an array, this method will also encode it recursively.
  419. * Only string values will be encoded.
  420. * @param array $data data to be encoded
  421. * @param boolean $valuesOnly whether to encode array values only. If false,
  422. * both the array keys and array values will be encoded.
  423. * @param string $charset the charset that the data is using. If not set,
  424. * [[\yii\base\Application::charset]] will be used.
  425. * @return array the encoded data
  426. * @see http://www.php.net/manual/en/function.htmlspecialchars.php
  427. */
  428. public static function htmlEncode($data, $valuesOnly = true, $charset = null)
  429. {
  430. if ($charset === null) {
  431. $charset = Yii::$app->charset;
  432. }
  433. $d = [];
  434. foreach ($data as $key => $value) {
  435. if (!$valuesOnly && is_string($key)) {
  436. $key = htmlspecialchars($key, ENT_QUOTES, $charset);
  437. }
  438. if (is_string($value)) {
  439. $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset);
  440. } elseif (is_array($value)) {
  441. $d[$key] = static::htmlEncode($value, $valuesOnly, $charset);
  442. } else {
  443. $d[$key] = $value;
  444. }
  445. }
  446. return $d;
  447. }
  448. /**
  449. * Decodes HTML entities into the corresponding characters in an array of strings.
  450. * Only array values will be decoded by default.
  451. * If a value is an array, this method will also decode it recursively.
  452. * Only string values will be decoded.
  453. * @param array $data data to be decoded
  454. * @param boolean $valuesOnly whether to decode array values only. If false,
  455. * both the array keys and array values will be decoded.
  456. * @return array the decoded data
  457. * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php
  458. */
  459. public static function htmlDecode($data, $valuesOnly = true)
  460. {
  461. $d = [];
  462. foreach ($data as $key => $value) {
  463. if (!$valuesOnly && is_string($key)) {
  464. $key = htmlspecialchars_decode($key, ENT_QUOTES);
  465. }
  466. if (is_string($value)) {
  467. $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES);
  468. } elseif (is_array($value)) {
  469. $d[$key] = static::htmlDecode($value);
  470. } else {
  471. $d[$key] = $value;
  472. }
  473. }
  474. return $d;
  475. }
  476. /**
  477. * Returns a value indicating whether the given array is an associative array.
  478. *
  479. * An array is associative if all its keys are strings. If `$allStrings` is false,
  480. * then an array will be treated as associative if at least one of its keys is a string.
  481. *
  482. * Note that an empty array will NOT be considered associative.
  483. *
  484. * @param array $array the array being checked
  485. * @param boolean $allStrings whether the array keys must be all strings in order for
  486. * the array to be treated as associative.
  487. * @return boolean whether the array is associative
  488. */
  489. public static function isAssociative($array, $allStrings = true)
  490. {
  491. if (!is_array($array) || empty($array)) {
  492. return false;
  493. }
  494. if ($allStrings) {
  495. foreach ($array as $key => $value) {
  496. if (!is_string($key)) {
  497. return false;
  498. }
  499. }
  500. return true;
  501. } else {
  502. foreach ($array as $key => $value) {
  503. if (is_string($key)) {
  504. return true;
  505. }
  506. }
  507. return false;
  508. }
  509. }
  510. /**
  511. * Returns a value indicating whether the given array is an indexed array.
  512. *
  513. * An array is indexed if all its keys are integers. If `$consecutive` is true,
  514. * then the array keys must be a consecutive sequence starting from 0.
  515. *
  516. * Note that an empty array will be considered indexed.
  517. *
  518. * @param array $array the array being checked
  519. * @param boolean $consecutive whether the array keys must be a consecutive sequence
  520. * in order for the array to be treated as indexed.
  521. * @return boolean whether the array is associative
  522. */
  523. public static function isIndexed($array, $consecutive = false)
  524. {
  525. if (!is_array($array)) {
  526. return false;
  527. }
  528. if (empty($array)) {
  529. return true;
  530. }
  531. if ($consecutive) {
  532. return array_keys($array) === range(0, count($array) - 1);
  533. } else {
  534. foreach ($array as $key => $value) {
  535. if (!is_integer($key)) {
  536. return false;
  537. }
  538. }
  539. return true;
  540. }
  541. }
  542. }