Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

847 lines
31KB

  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|string $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. * ```php
  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. * ```php
  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([], [], $recursive);
  84. } else {
  85. $result = [];
  86. foreach ($object as $key => $value) {
  87. $result[$key] = $value;
  88. }
  89. }
  90. return $recursive ? static::toArray($result, $properties) : $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_int($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. So it is better to be done specifying an array of key names
  140. * like `['x', 'y', 'z']`.
  141. *
  142. * Below are some usage examples,
  143. *
  144. * ```php
  145. * // working with array
  146. * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username');
  147. * // working with object
  148. * $username = \yii\helpers\ArrayHelper::getValue($user, 'username');
  149. * // working with anonymous function
  150. * $fullName = \yii\helpers\ArrayHelper::getValue($user, function ($user, $defaultValue) {
  151. * return $user->firstName . ' ' . $user->lastName;
  152. * });
  153. * // using dot format to retrieve the property of embedded object
  154. * $street = \yii\helpers\ArrayHelper::getValue($users, 'address.street');
  155. * // using an array of keys to retrieve the value
  156. * $value = \yii\helpers\ArrayHelper::getValue($versions, ['1.0', 'date']);
  157. * ```
  158. *
  159. * @param array|object $array array or object to extract value from
  160. * @param string|\Closure|array $key key name of the array element, an array of keys or property name of the object,
  161. * or an anonymous function returning the value. The anonymous function signature should be:
  162. * `function($array, $defaultValue)`.
  163. * The possibility to pass an array of keys is available since version 2.0.4.
  164. * @param mixed $default the default value to be returned if the specified array key does not exist. Not used when
  165. * getting value from an object.
  166. * @return mixed the value of the element if found, default value otherwise
  167. * @throws InvalidParamException if $array is neither an array nor an object.
  168. */
  169. public static function getValue($array, $key, $default = null)
  170. {
  171. if ($key instanceof \Closure) {
  172. return $key($array, $default);
  173. }
  174. if (is_array($key)) {
  175. $lastKey = array_pop($key);
  176. foreach ($key as $keyPart) {
  177. $array = static::getValue($array, $keyPart);
  178. }
  179. $key = $lastKey;
  180. }
  181. if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array)) ) {
  182. return $array[$key];
  183. }
  184. if (($pos = strrpos($key, '.')) !== false) {
  185. $array = static::getValue($array, substr($key, 0, $pos), $default);
  186. $key = substr($key, $pos + 1);
  187. }
  188. if (is_object($array)) {
  189. // this is expected to fail if the property does not exist, or __get() is not implemented
  190. // it is not reliably possible to check whether a property is accessable beforehand
  191. return $array->$key;
  192. } elseif (is_array($array)) {
  193. return (isset($array[$key]) || array_key_exists($key, $array)) ? $array[$key] : $default;
  194. } else {
  195. return $default;
  196. }
  197. }
  198. /**
  199. * Removes an item from an array and returns the value. If the key does not exist in the array, the default value
  200. * will be returned instead.
  201. *
  202. * Usage examples,
  203. *
  204. * ```php
  205. * // $array = ['type' => 'A', 'options' => [1, 2]];
  206. * // working with array
  207. * $type = \yii\helpers\ArrayHelper::remove($array, 'type');
  208. * // $array content
  209. * // $array = ['options' => [1, 2]];
  210. * ```
  211. *
  212. * @param array $array the array to extract value from
  213. * @param string $key key name of the array element
  214. * @param mixed $default the default value to be returned if the specified key does not exist
  215. * @return mixed|null the value of the element if found, default value otherwise
  216. */
  217. public static function remove(&$array, $key, $default = null)
  218. {
  219. if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) {
  220. $value = $array[$key];
  221. unset($array[$key]);
  222. return $value;
  223. }
  224. return $default;
  225. }
  226. /**
  227. * Indexes and/or groups the array according to a specified key.
  228. * The input should be either multidimensional array or an array of objects.
  229. *
  230. * The $key can be either a key name of the sub-array, a property name of object, or an anonymous
  231. * function that must return the value that will be used as a key.
  232. *
  233. * $groups is an array of keys, that will be used to group the input array into one or more sub-arrays based
  234. * on keys specified.
  235. *
  236. * If the `$key` is specified as `null` or a value of an element corresponding to the key is `null` in addition
  237. * to `$groups` not specified then the element is discarded.
  238. *
  239. * For example:
  240. *
  241. * ```php
  242. * $array = [
  243. * ['id' => '123', 'data' => 'abc', 'device' => 'laptop'],
  244. * ['id' => '345', 'data' => 'def', 'device' => 'tablet'],
  245. * ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'],
  246. * ];
  247. * $result = ArrayHelper::index($array, 'id');
  248. * ```
  249. *
  250. * The result will be an associative array, where the key is the value of `id` attribute
  251. *
  252. * ```php
  253. * [
  254. * '123' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'],
  255. * '345' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone']
  256. * // The second element of an original array is overwritten by the last element because of the same id
  257. * ]
  258. * ```
  259. *
  260. * An anonymous function can be used in the grouping array as well.
  261. *
  262. * ```php
  263. * $result = ArrayHelper::index($array, function ($element) {
  264. * return $element['id'];
  265. * });
  266. * ```
  267. *
  268. * Passing `id` as a third argument will group `$array` by `id`:
  269. *
  270. * ```php
  271. * $result = ArrayHelper::index($array, null, 'id');
  272. * ```
  273. *
  274. * The result will be a multidimensional array grouped by `id` on the first level, by `device` on the second level
  275. * and indexed by `data` on the third level:
  276. *
  277. * ```php
  278. * [
  279. * '123' => [
  280. * ['id' => '123', 'data' => 'abc', 'device' => 'laptop']
  281. * ],
  282. * '345' => [ // all elements with this index are present in the result array
  283. * ['id' => '345', 'data' => 'def', 'device' => 'tablet'],
  284. * ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'],
  285. * ]
  286. * ]
  287. * ```
  288. *
  289. * The anonymous function can be used in the array of grouping keys as well:
  290. *
  291. * ```php
  292. * $result = ArrayHelper::index($array, 'data', [function ($element) {
  293. * return $element['id'];
  294. * }, 'device']);
  295. * ```
  296. *
  297. * The result will be a multidimensional array grouped by `id` on the first level, by the `device` on the second one
  298. * and indexed by the `data` on the third level:
  299. *
  300. * ```php
  301. * [
  302. * '123' => [
  303. * 'laptop' => [
  304. * 'abc' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop']
  305. * ]
  306. * ],
  307. * '345' => [
  308. * 'tablet' => [
  309. * 'def' => ['id' => '345', 'data' => 'def', 'device' => 'tablet']
  310. * ],
  311. * 'smartphone' => [
  312. * 'hgi' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone']
  313. * ]
  314. * ]
  315. * ]
  316. * ```
  317. *
  318. * @param array $array the array that needs to be indexed or grouped
  319. * @param string|\Closure|null $key the column name or anonymous function which result will be used to index the array
  320. * @param string|string[]|\Closure[]|null $groups the array of keys, that will be used to group the input array
  321. * by one or more keys. If the $key attribute or its value for the particular element is null and $groups is not
  322. * defined, the array element will be discarded. Otherwise, if $groups is specified, array element will be added
  323. * to the result array without any key. This parameter is available since version 2.0.8.
  324. * @return array the indexed and/or grouped array
  325. */
  326. public static function index($array, $key, $groups = [])
  327. {
  328. $result = [];
  329. $groups = (array)$groups;
  330. foreach ($array as $element) {
  331. $lastArray = &$result;
  332. foreach ($groups as $group) {
  333. $value = static::getValue($element, $group);
  334. if (!array_key_exists($value, $lastArray)) {
  335. $lastArray[$value] = [];
  336. }
  337. $lastArray = &$lastArray[$value];
  338. }
  339. if ($key === null) {
  340. if (!empty($groups)) {
  341. $lastArray[] = $element;
  342. }
  343. } else {
  344. $value = static::getValue($element, $key);
  345. if ($value !== null) {
  346. if (is_float($value)) {
  347. $value = (string) $value;
  348. }
  349. $lastArray[$value] = $element;
  350. }
  351. }
  352. unset($lastArray);
  353. }
  354. return $result;
  355. }
  356. /**
  357. * Returns the values of a specified column in an array.
  358. * The input array should be multidimensional or an array of objects.
  359. *
  360. * For example,
  361. *
  362. * ```php
  363. * $array = [
  364. * ['id' => '123', 'data' => 'abc'],
  365. * ['id' => '345', 'data' => 'def'],
  366. * ];
  367. * $result = ArrayHelper::getColumn($array, 'id');
  368. * // the result is: ['123', '345']
  369. *
  370. * // using anonymous function
  371. * $result = ArrayHelper::getColumn($array, function ($element) {
  372. * return $element['id'];
  373. * });
  374. * ```
  375. *
  376. * @param array $array
  377. * @param string|\Closure $name
  378. * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array
  379. * will be re-indexed with integers.
  380. * @return array the list of column values
  381. */
  382. public static function getColumn($array, $name, $keepKeys = true)
  383. {
  384. $result = [];
  385. if ($keepKeys) {
  386. foreach ($array as $k => $element) {
  387. $result[$k] = static::getValue($element, $name);
  388. }
  389. } else {
  390. foreach ($array as $element) {
  391. $result[] = static::getValue($element, $name);
  392. }
  393. }
  394. return $result;
  395. }
  396. /**
  397. * Builds a map (key-value pairs) from a multidimensional array or an array of objects.
  398. * The `$from` and `$to` parameters specify the key names or property names to set up the map.
  399. * Optionally, one can further group the map according to a grouping field `$group`.
  400. *
  401. * For example,
  402. *
  403. * ```php
  404. * $array = [
  405. * ['id' => '123', 'name' => 'aaa', 'class' => 'x'],
  406. * ['id' => '124', 'name' => 'bbb', 'class' => 'x'],
  407. * ['id' => '345', 'name' => 'ccc', 'class' => 'y'],
  408. * ];
  409. *
  410. * $result = ArrayHelper::map($array, 'id', 'name');
  411. * // the result is:
  412. * // [
  413. * // '123' => 'aaa',
  414. * // '124' => 'bbb',
  415. * // '345' => 'ccc',
  416. * // ]
  417. *
  418. * $result = ArrayHelper::map($array, 'id', 'name', 'class');
  419. * // the result is:
  420. * // [
  421. * // 'x' => [
  422. * // '123' => 'aaa',
  423. * // '124' => 'bbb',
  424. * // ],
  425. * // 'y' => [
  426. * // '345' => 'ccc',
  427. * // ],
  428. * // ]
  429. * ```
  430. *
  431. * @param array $array
  432. * @param string|\Closure $from
  433. * @param string|\Closure $to
  434. * @param string|\Closure $group
  435. * @return array
  436. */
  437. public static function map($array, $from, $to, $group = null)
  438. {
  439. $result = [];
  440. foreach ($array as $element) {
  441. $key = static::getValue($element, $from);
  442. $value = static::getValue($element, $to);
  443. if ($group !== null) {
  444. $result[static::getValue($element, $group)][$key] = $value;
  445. } else {
  446. $result[$key] = $value;
  447. }
  448. }
  449. return $result;
  450. }
  451. /**
  452. * Checks if the given array contains the specified key.
  453. * This method enhances the `array_key_exists()` function by supporting case-insensitive
  454. * key comparison.
  455. * @param string $key the key to check
  456. * @param array $array the array with keys to check
  457. * @param boolean $caseSensitive whether the key comparison should be case-sensitive
  458. * @return boolean whether the array contains the specified key
  459. */
  460. public static function keyExists($key, $array, $caseSensitive = true)
  461. {
  462. if ($caseSensitive) {
  463. return array_key_exists($key, $array);
  464. } else {
  465. foreach (array_keys($array) as $k) {
  466. if (strcasecmp($key, $k) === 0) {
  467. return true;
  468. }
  469. }
  470. return false;
  471. }
  472. }
  473. /**
  474. * Sorts an array of objects or arrays (with the same structure) by one or several keys.
  475. * @param array $array the array to be sorted. The array will be modified after calling this method.
  476. * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array
  477. * elements, a property name of the objects, or an anonymous function returning the values for comparison
  478. * purpose. The anonymous function signature should be: `function($item)`.
  479. * To sort by multiple keys, provide an array of keys here.
  480. * @param integer|array $direction the sorting direction. It can be either `SORT_ASC` or `SORT_DESC`.
  481. * When sorting by multiple keys with different sorting directions, use an array of sorting directions.
  482. * @param integer|array $sortFlag the PHP sort flag. Valid values include
  483. * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING`, `SORT_LOCALE_STRING`, `SORT_NATURAL` and `SORT_FLAG_CASE`.
  484. * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php)
  485. * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags.
  486. * @throws InvalidParamException if the $direction or $sortFlag parameters do not have
  487. * correct number of elements as that of $key.
  488. */
  489. public static function multisort(&$array, $key, $direction = SORT_ASC, $sortFlag = SORT_REGULAR)
  490. {
  491. $keys = is_array($key) ? $key : [$key];
  492. if (empty($keys) || empty($array)) {
  493. return;
  494. }
  495. $n = count($keys);
  496. if (is_scalar($direction)) {
  497. $direction = array_fill(0, $n, $direction);
  498. } elseif (count($direction) !== $n) {
  499. throw new InvalidParamException('The length of $direction parameter must be the same as that of $keys.');
  500. }
  501. if (is_scalar($sortFlag)) {
  502. $sortFlag = array_fill(0, $n, $sortFlag);
  503. } elseif (count($sortFlag) !== $n) {
  504. throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.');
  505. }
  506. $args = [];
  507. foreach ($keys as $i => $key) {
  508. $flag = $sortFlag[$i];
  509. $args[] = static::getColumn($array, $key);
  510. $args[] = $direction[$i];
  511. $args[] = $flag;
  512. }
  513. // This fix is used for cases when main sorting specified by columns has equal values
  514. // Without it it will lead to Fatal Error: Nesting level too deep - recursive dependency?
  515. $args[] = range(1, count($array));
  516. $args[] = SORT_ASC;
  517. $args[] = SORT_NUMERIC;
  518. $args[] = &$array;
  519. call_user_func_array('array_multisort', $args);
  520. }
  521. /**
  522. * Encodes special characters in an array of strings into HTML entities.
  523. * Only array values will be encoded by default.
  524. * If a value is an array, this method will also encode it recursively.
  525. * Only string values will be encoded.
  526. * @param array $data data to be encoded
  527. * @param boolean $valuesOnly whether to encode array values only. If false,
  528. * both the array keys and array values will be encoded.
  529. * @param string $charset the charset that the data is using. If not set,
  530. * [[\yii\base\Application::charset]] will be used.
  531. * @return array the encoded data
  532. * @see http://www.php.net/manual/en/function.htmlspecialchars.php
  533. */
  534. public static function htmlEncode($data, $valuesOnly = true, $charset = null)
  535. {
  536. if ($charset === null) {
  537. $charset = Yii::$app ? Yii::$app->charset : 'UTF-8';
  538. }
  539. $d = [];
  540. foreach ($data as $key => $value) {
  541. if (!$valuesOnly && is_string($key)) {
  542. $key = htmlspecialchars($key, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
  543. }
  544. if (is_string($value)) {
  545. $d[$key] = htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
  546. } elseif (is_array($value)) {
  547. $d[$key] = static::htmlEncode($value, $valuesOnly, $charset);
  548. } else {
  549. $d[$key] = $value;
  550. }
  551. }
  552. return $d;
  553. }
  554. /**
  555. * Decodes HTML entities into the corresponding characters in an array of strings.
  556. * Only array values will be decoded by default.
  557. * If a value is an array, this method will also decode it recursively.
  558. * Only string values will be decoded.
  559. * @param array $data data to be decoded
  560. * @param boolean $valuesOnly whether to decode array values only. If false,
  561. * both the array keys and array values will be decoded.
  562. * @return array the decoded data
  563. * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php
  564. */
  565. public static function htmlDecode($data, $valuesOnly = true)
  566. {
  567. $d = [];
  568. foreach ($data as $key => $value) {
  569. if (!$valuesOnly && is_string($key)) {
  570. $key = htmlspecialchars_decode($key, ENT_QUOTES);
  571. }
  572. if (is_string($value)) {
  573. $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES);
  574. } elseif (is_array($value)) {
  575. $d[$key] = static::htmlDecode($value);
  576. } else {
  577. $d[$key] = $value;
  578. }
  579. }
  580. return $d;
  581. }
  582. /**
  583. * Returns a value indicating whether the given array is an associative array.
  584. *
  585. * An array is associative if all its keys are strings. If `$allStrings` is false,
  586. * then an array will be treated as associative if at least one of its keys is a string.
  587. *
  588. * Note that an empty array will NOT be considered associative.
  589. *
  590. * @param array $array the array being checked
  591. * @param boolean $allStrings whether the array keys must be all strings in order for
  592. * the array to be treated as associative.
  593. * @return boolean whether the array is associative
  594. */
  595. public static function isAssociative($array, $allStrings = true)
  596. {
  597. if (!is_array($array) || empty($array)) {
  598. return false;
  599. }
  600. if ($allStrings) {
  601. foreach ($array as $key => $value) {
  602. if (!is_string($key)) {
  603. return false;
  604. }
  605. }
  606. return true;
  607. } else {
  608. foreach ($array as $key => $value) {
  609. if (is_string($key)) {
  610. return true;
  611. }
  612. }
  613. return false;
  614. }
  615. }
  616. /**
  617. * Returns a value indicating whether the given array is an indexed array.
  618. *
  619. * An array is indexed if all its keys are integers. If `$consecutive` is true,
  620. * then the array keys must be a consecutive sequence starting from 0.
  621. *
  622. * Note that an empty array will be considered indexed.
  623. *
  624. * @param array $array the array being checked
  625. * @param boolean $consecutive whether the array keys must be a consecutive sequence
  626. * in order for the array to be treated as indexed.
  627. * @return boolean whether the array is associative
  628. */
  629. public static function isIndexed($array, $consecutive = false)
  630. {
  631. if (!is_array($array)) {
  632. return false;
  633. }
  634. if (empty($array)) {
  635. return true;
  636. }
  637. if ($consecutive) {
  638. return array_keys($array) === range(0, count($array) - 1);
  639. } else {
  640. foreach ($array as $key => $value) {
  641. if (!is_int($key)) {
  642. return false;
  643. }
  644. }
  645. return true;
  646. }
  647. }
  648. /**
  649. * Check whether an array or [[\Traversable]] contains an element.
  650. *
  651. * This method does the same as the PHP function [in_array()](http://php.net/manual/en/function.in-array.php)
  652. * but additionally works for objects that implement the [[\Traversable]] interface.
  653. * @param mixed $needle The value to look for.
  654. * @param array|\Traversable $haystack The set of values to search.
  655. * @param boolean $strict Whether to enable strict (`===`) comparison.
  656. * @return boolean `true` if `$needle` was found in `$haystack`, `false` otherwise.
  657. * @throws InvalidParamException if `$haystack` is neither traversable nor an array.
  658. * @see http://php.net/manual/en/function.in-array.php
  659. * @since 2.0.7
  660. */
  661. public static function isIn($needle, $haystack, $strict = false)
  662. {
  663. if ($haystack instanceof \Traversable) {
  664. foreach ($haystack as $value) {
  665. if ($needle == $value && (!$strict || $needle === $value)) {
  666. return true;
  667. }
  668. }
  669. } elseif (is_array($haystack)) {
  670. return in_array($needle, $haystack, $strict);
  671. } else {
  672. throw new InvalidParamException('Argument $haystack must be an array or implement Traversable');
  673. }
  674. return false;
  675. }
  676. /**
  677. * Checks whether a variable is an array or [[\Traversable]].
  678. *
  679. * This method does the same as the PHP function [is_array()](http://php.net/manual/en/function.is-array.php)
  680. * but additionally works on objects that implement the [[\Traversable]] interface.
  681. * @param mixed $var The variable being evaluated.
  682. * @return boolean whether $var is array-like
  683. * @see http://php.net/manual/en/function.is_array.php
  684. * @since 2.0.8
  685. */
  686. public static function isTraversable($var)
  687. {
  688. return is_array($var) || $var instanceof \Traversable;
  689. }
  690. /**
  691. * Checks whether an array or [[\Traversable]] is a subset of another array or [[\Traversable]].
  692. *
  693. * This method will return `true`, if all elements of `$needles` are contained in
  694. * `$haystack`. If at least one element is missing, `false` will be returned.
  695. * @param array|\Traversable $needles The values that must **all** be in `$haystack`.
  696. * @param array|\Traversable $haystack The set of value to search.
  697. * @param boolean $strict Whether to enable strict (`===`) comparison.
  698. * @throws InvalidParamException if `$haystack` or `$needles` is neither traversable nor an array.
  699. * @return boolean `true` if `$needles` is a subset of `$haystack`, `false` otherwise.
  700. * @since 2.0.7
  701. */
  702. public static function isSubset($needles, $haystack, $strict = false)
  703. {
  704. if (is_array($needles) || $needles instanceof \Traversable) {
  705. foreach ($needles as $needle) {
  706. if (!static::isIn($needle, $haystack, $strict)) {
  707. return false;
  708. }
  709. }
  710. return true;
  711. } else {
  712. throw new InvalidParamException('Argument $needles must be an array or implement Traversable');
  713. }
  714. }
  715. /**
  716. * Filters array according to rules specified.
  717. *
  718. * For example:
  719. * ```php
  720. * $array = [
  721. * 'A' => [1, 2],
  722. * 'B' => [
  723. * 'C' => 1,
  724. * 'D' => 2,
  725. * ],
  726. * 'E' => 1,
  727. * ];
  728. *
  729. * $result = \yii\helpers\ArrayHelper::filter($array, ['A']);
  730. * // $result will be:
  731. * // [
  732. * // 'A' => [1, 2],
  733. * // ]
  734. *
  735. * $result = \yii\helpers\ArrayHelper::filter($array, ['A', 'B.C']);
  736. * // $result will be:
  737. * // [
  738. * // 'A' => [1, 2],
  739. * // 'B' => ['C' => 1],
  740. * // ]
  741. * ```
  742. *
  743. * $result = \yii\helpers\ArrayHelper::filter($array, ['B', '!B.C']);
  744. * // $result will be:
  745. * // [
  746. * // 'B' => ['D' => 2],
  747. * // ]
  748. * ```
  749. *
  750. * @param array $array Source array
  751. * @param array $filters Rules that define array keys which should be left or removed from results.
  752. * Each rule is:
  753. * - `var` - `$array['var']` will be left in result.
  754. * - `var.key` = only `$array['var']['key'] will be left in result.
  755. * - `!var.key` = `$array['var']['key'] will be removed from result.
  756. * @return array Filtered array
  757. * @since 2.0.9
  758. */
  759. public static function filter($array, $filters)
  760. {
  761. $result = [];
  762. $forbiddenVars = [];
  763. foreach ($filters as $var) {
  764. $keys = explode('.', $var);
  765. $globalKey = $keys[0];
  766. $localKey = isset($keys[1]) ? $keys[1] : null;
  767. if ($globalKey[0] === '!') {
  768. $forbiddenVars[] = [
  769. substr($globalKey, 1),
  770. $localKey,
  771. ];
  772. continue;
  773. }
  774. if (empty($array[$globalKey])) {
  775. continue;
  776. }
  777. if ($localKey === null) {
  778. $result[$globalKey] = $array[$globalKey];
  779. continue;
  780. }
  781. if (!isset($array[$globalKey][$localKey])) {
  782. continue;
  783. }
  784. if (!array_key_exists($globalKey, $result)) {
  785. $result[$globalKey] = [];
  786. }
  787. $result[$globalKey][$localKey] = $array[$globalKey][$localKey];
  788. }
  789. foreach ($forbiddenVars as $var) {
  790. list($globalKey, $localKey) = $var;
  791. if (array_key_exists($globalKey, $result)) {
  792. unset($result[$globalKey][$localKey]);
  793. }
  794. }
  795. return $result;
  796. }
  797. }