CollectionTrait.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Collection;
  16. use AppendIterator;
  17. use ArrayIterator;
  18. use Cake\Collection\Collection;
  19. use Cake\Collection\Iterator\BufferedIterator;
  20. use Cake\Collection\Iterator\ExtractIterator;
  21. use Cake\Collection\Iterator\FilterIterator;
  22. use Cake\Collection\Iterator\InsertIterator;
  23. use Cake\Collection\Iterator\MapReduce;
  24. use Cake\Collection\Iterator\NestIterator;
  25. use Cake\Collection\Iterator\ReplaceIterator;
  26. use Cake\Collection\Iterator\SortIterator;
  27. use Cake\Collection\Iterator\StoppableIterator;
  28. use Cake\Collection\Iterator\TreeIterator;
  29. use Cake\Collection\Iterator\UnfoldIterator;
  30. use Iterator;
  31. use LimitIterator;
  32. use RecursiveIteratorIterator;
  33. /**
  34. * Offers a handful of method to manipulate iterators
  35. */
  36. trait CollectionTrait
  37. {
  38. use ExtractTrait;
  39. /**
  40. * {@inheritDoc}
  41. *
  42. */
  43. public function each(callable $c)
  44. {
  45. foreach ($this->_unwrap() as $k => $v) {
  46. $c($v, $k);
  47. }
  48. return $this;
  49. }
  50. /**
  51. * {@inheritDoc}
  52. *
  53. * @return \Cake\Collection\Iterator\FilterIterator
  54. */
  55. public function filter(callable $c = null)
  56. {
  57. if ($c === null) {
  58. $c = function ($v) {
  59. return (bool)$v;
  60. };
  61. }
  62. return new FilterIterator($this->_unwrap(), $c);
  63. }
  64. /**
  65. * {@inheritDoc}
  66. *
  67. * @return \Cake\Collection\Iterator\FilterIterator
  68. */
  69. public function reject(callable $c)
  70. {
  71. return new FilterIterator($this->_unwrap(), function ($key, $value, $items) use ($c) {
  72. return !$c($key, $value, $items);
  73. });
  74. }
  75. /**
  76. * {@inheritDoc}
  77. *
  78. */
  79. public function every(callable $c)
  80. {
  81. foreach ($this->_unwrap() as $key => $value) {
  82. if (!$c($value, $key)) {
  83. return false;
  84. }
  85. }
  86. return true;
  87. }
  88. /**
  89. * {@inheritDoc}
  90. *
  91. */
  92. public function some(callable $c)
  93. {
  94. foreach ($this->_unwrap() as $key => $value) {
  95. if ($c($value, $key) === true) {
  96. return true;
  97. }
  98. }
  99. return false;
  100. }
  101. /**
  102. * {@inheritDoc}
  103. *
  104. */
  105. public function contains($value)
  106. {
  107. foreach ($this->_unwrap() as $v) {
  108. if ($value === $v) {
  109. return true;
  110. }
  111. }
  112. return false;
  113. }
  114. /**
  115. * {@inheritDoc}
  116. *
  117. * @return \Cake\Collection\Iterator\ReplaceIterator
  118. */
  119. public function map(callable $c)
  120. {
  121. return new ReplaceIterator($this->_unwrap(), $c);
  122. }
  123. /**
  124. * {@inheritDoc}
  125. *
  126. */
  127. public function reduce(callable $c, $zero = null)
  128. {
  129. $isFirst = false;
  130. if (func_num_args() < 2) {
  131. $isFirst = true;
  132. }
  133. $result = $zero;
  134. foreach ($this->_unwrap() as $k => $value) {
  135. if ($isFirst) {
  136. $result = $value;
  137. $isFirst = false;
  138. continue;
  139. }
  140. $result = $c($result, $value, $k);
  141. }
  142. return $result;
  143. }
  144. /**
  145. * {@inheritDoc}
  146. *
  147. * @return \Cake\Collection\Iterator\ExtractIterator
  148. */
  149. public function extract($matcher)
  150. {
  151. return new ExtractIterator($this->_unwrap(), $matcher);
  152. }
  153. /**
  154. * {@inheritDoc}
  155. *
  156. */
  157. public function max($callback, $type = SORT_NUMERIC)
  158. {
  159. return (new SortIterator($this->_unwrap(), $callback, SORT_DESC, $type))->first();
  160. }
  161. /**
  162. * {@inheritDoc}
  163. *
  164. */
  165. public function min($callback, $type = SORT_NUMERIC)
  166. {
  167. return (new SortIterator($this->_unwrap(), $callback, SORT_ASC, $type))->first();
  168. }
  169. /**
  170. * {@inheritDoc}
  171. *
  172. */
  173. public function sortBy($callback, $dir = SORT_DESC, $type = SORT_NUMERIC)
  174. {
  175. return new SortIterator($this->_unwrap(), $callback, $dir, $type);
  176. }
  177. /**
  178. * {@inheritDoc}
  179. *
  180. */
  181. public function groupBy($callback)
  182. {
  183. $callback = $this->_propertyExtractor($callback);
  184. $group = [];
  185. foreach ($this as $value) {
  186. $group[$callback($value)][] = $value;
  187. }
  188. return new Collection($group);
  189. }
  190. /**
  191. * {@inheritDoc}
  192. *
  193. */
  194. public function indexBy($callback)
  195. {
  196. $callback = $this->_propertyExtractor($callback);
  197. $group = [];
  198. foreach ($this as $value) {
  199. $group[$callback($value)] = $value;
  200. }
  201. return new Collection($group);
  202. }
  203. /**
  204. * {@inheritDoc}
  205. *
  206. */
  207. public function countBy($callback)
  208. {
  209. $callback = $this->_propertyExtractor($callback);
  210. $mapper = function ($value, $key, $mr) use ($callback) {
  211. $mr->emitIntermediate($value, $callback($value));
  212. };
  213. $reducer = function ($values, $key, $mr) {
  214. $mr->emit(count($values), $key);
  215. };
  216. return new Collection(new MapReduce($this->_unwrap(), $mapper, $reducer));
  217. }
  218. /**
  219. * {@inheritDoc}
  220. *
  221. */
  222. public function sumOf($matcher)
  223. {
  224. $callback = $this->_propertyExtractor($matcher);
  225. $sum = 0;
  226. foreach ($this as $k => $v) {
  227. $sum += $callback($v, $k);
  228. }
  229. return $sum;
  230. }
  231. /**
  232. * {@inheritDoc}
  233. *
  234. */
  235. public function shuffle()
  236. {
  237. $elements = $this->toArray();
  238. shuffle($elements);
  239. return new Collection($elements);
  240. }
  241. /**
  242. * {@inheritDoc}
  243. *
  244. */
  245. public function sample($size = 10)
  246. {
  247. return new Collection(new LimitIterator($this->shuffle(), 0, $size));
  248. }
  249. /**
  250. * {@inheritDoc}
  251. *
  252. */
  253. public function take($size = 1, $from = 0)
  254. {
  255. return new Collection(new LimitIterator($this->_unwrap(), $from, $size));
  256. }
  257. /**
  258. * {@inheritDoc}
  259. *
  260. */
  261. public function match(array $conditions)
  262. {
  263. return $this->filter($this->_createMatcherFilter($conditions));
  264. }
  265. /**
  266. * {@inheritDoc}
  267. *
  268. */
  269. public function firstMatch(array $conditions)
  270. {
  271. return $this->match($conditions)->first();
  272. }
  273. /**
  274. * {@inheritDoc}
  275. *
  276. */
  277. public function first()
  278. {
  279. foreach ($this->take(1) as $result) {
  280. return $result;
  281. }
  282. }
  283. /**
  284. * {@inheritDoc}
  285. *
  286. */
  287. public function append($items)
  288. {
  289. $items = $items instanceof Iterator ? $items : new Collection($items);
  290. $list = new AppendIterator;
  291. $list->append($this);
  292. $list->append($items->_unwrap());
  293. return new Collection($list);
  294. }
  295. /**
  296. * {@inheritDoc}
  297. *
  298. */
  299. public function combine($keyPath, $valuePath, $groupPath = null)
  300. {
  301. $options = [
  302. 'keyPath' => $this->_propertyExtractor($keyPath),
  303. 'valuePath' => $this->_propertyExtractor($valuePath),
  304. 'groupPath' => $groupPath ? $this->_propertyExtractor($groupPath) : null
  305. ];
  306. $mapper = function ($value, $key, $mapReduce) use ($options) {
  307. $rowKey = $options['keyPath'];
  308. $rowVal = $options['valuePath'];
  309. if (!($options['groupPath'])) {
  310. $mapReduce->emit($rowVal($value, $key), $rowKey($value, $key));
  311. return;
  312. }
  313. $key = $options['groupPath']($value, $key);
  314. $mapReduce->emitIntermediate(
  315. [$rowKey($value, $key) => $rowVal($value, $key)],
  316. $key
  317. );
  318. };
  319. $reducer = function ($values, $key, $mapReduce) {
  320. $result = [];
  321. foreach ($values as $value) {
  322. $result += $value;
  323. }
  324. $mapReduce->emit($result, $key);
  325. };
  326. return new Collection(new MapReduce($this->_unwrap(), $mapper, $reducer));
  327. }
  328. /**
  329. * {@inheritDoc}
  330. *
  331. */
  332. public function nest($idPath, $parentPath)
  333. {
  334. $parents = [];
  335. $idPath = $this->_propertyExtractor($idPath);
  336. $parentPath = $this->_propertyExtractor($parentPath);
  337. $isObject = !is_array((new Collection($this))->first());
  338. $mapper = function ($row, $key, $mapReduce) use (&$parents, $idPath, $parentPath) {
  339. $row['children'] = [];
  340. $id = $idPath($row, $key);
  341. $parentId = $parentPath($row, $key);
  342. $parents[$id] =& $row;
  343. $mapReduce->emitIntermediate($id, $parentId);
  344. };
  345. $reducer = function ($values, $key, $mapReduce) use (&$parents, $isObject) {
  346. if (empty($key) || !isset($parents[$key])) {
  347. foreach ($values as $id) {
  348. $parents[$id] = $isObject ? $parents[$id] : new ArrayIterator($parents[$id], 1);
  349. $mapReduce->emit($parents[$id]);
  350. }
  351. return;
  352. }
  353. foreach ($values as $id) {
  354. $parents[$key]['children'][] =& $parents[$id];
  355. }
  356. };
  357. $collection = new MapReduce($this, $mapper, $reducer);
  358. if (!$isObject) {
  359. $collection = (new Collection($collection))->map(function ($value) {
  360. return $value->getArrayCopy();
  361. });
  362. }
  363. return new Collection($collection);
  364. }
  365. /**
  366. * {@inheritDoc}
  367. *
  368. * @return \Cake\Collection\Iterator\InsertIterator
  369. */
  370. public function insert($path, $values)
  371. {
  372. return new InsertIterator($this->_unwrap(), $path, $values);
  373. }
  374. /**
  375. * {@inheritDoc}
  376. *
  377. */
  378. public function toArray($preserveKeys = true)
  379. {
  380. $iterator = $this->_unwrap();
  381. if ($iterator instanceof ArrayIterator) {
  382. $items = $iterator->getArrayCopy();
  383. return $preserveKeys ? $items : array_values($items);
  384. }
  385. return iterator_to_array($this, $preserveKeys);
  386. }
  387. /**
  388. * {@inheritDoc}
  389. *
  390. */
  391. public function toList()
  392. {
  393. return $this->toArray(false);
  394. }
  395. /**
  396. * {@inheritDoc}
  397. *
  398. */
  399. public function jsonSerialize()
  400. {
  401. return $this->toArray();
  402. }
  403. /**
  404. * {@inheritDoc}
  405. *
  406. */
  407. public function compile($preserveKeys = true)
  408. {
  409. return new Collection($this->toArray($preserveKeys));
  410. }
  411. /**
  412. * {@inheritDoc}
  413. *
  414. * @return \Cake\Collection\Iterator\BufferedIterator
  415. */
  416. public function buffered()
  417. {
  418. return new BufferedIterator($this);
  419. }
  420. /**
  421. * {@inheritDoc}
  422. *
  423. * @return \Cake\Collection\Iterator\TreeIterator
  424. */
  425. public function listNested($dir = 'desc', $nestingKey = 'children')
  426. {
  427. $dir = strtolower($dir);
  428. $modes = [
  429. 'desc' => TreeIterator::SELF_FIRST,
  430. 'asc' => TreeIterator::CHILD_FIRST,
  431. 'leaves' => TreeIterator::LEAVES_ONLY
  432. ];
  433. return new TreeIterator(
  434. new NestIterator($this, $nestingKey),
  435. isset($modes[$dir]) ? $modes[$dir] : $dir
  436. );
  437. }
  438. /**
  439. * {@inheritDoc}
  440. *
  441. * @return \Cake\Collection\Iterator\StoppableIterator
  442. */
  443. public function stopWhen($condition)
  444. {
  445. if (!is_callable($condition)) {
  446. $condition = $this->_createMatcherFilter($condition);
  447. }
  448. return new StoppableIterator($this, $condition);
  449. }
  450. /**
  451. * {@inheritDoc}
  452. *
  453. */
  454. public function unfold(callable $transformer = null)
  455. {
  456. if ($transformer === null) {
  457. $transformer = function ($item) {
  458. return $item;
  459. };
  460. }
  461. return new Collection(
  462. new RecursiveIteratorIterator(
  463. new UnfoldIterator($this, $transformer),
  464. RecursiveIteratorIterator::LEAVES_ONLY
  465. )
  466. );
  467. }
  468. /**
  469. * Returns the closest nested iterator that can be safely traversed without
  470. * losing any possible transformations.
  471. *
  472. * @return \Iterator
  473. */
  474. protected function _unwrap()
  475. {
  476. $iterator = $this;
  477. while (get_class($iterator) === 'Cake\Collection\Collection') {
  478. $iterator = $iterator->getInnerIterator();
  479. }
  480. return $iterator;
  481. }
  482. }