CollectionTrait.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  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 = true;
  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. static $foundOutType = false;
  347. if (!$foundOutType) {
  348. $isObject = is_object(current($parents));
  349. $foundOutType = true;
  350. }
  351. if (empty($key) || !isset($parents[$key])) {
  352. foreach ($values as $id) {
  353. $parents[$id] = $isObject ? $parents[$id] : new ArrayIterator($parents[$id], 1);
  354. $mapReduce->emit($parents[$id]);
  355. }
  356. return;
  357. }
  358. $children = [];
  359. foreach ($values as $id) {
  360. $children[] =& $parents[$id];
  361. }
  362. $parents[$key]['children'] = $children;
  363. };
  364. return (new Collection(new MapReduce($this->_unwrap(), $mapper, $reducer)))
  365. ->map(function ($value) use (&$isObject) {
  366. return $isObject ? $value : $value->getArrayCopy();
  367. });
  368. }
  369. /**
  370. * {@inheritDoc}
  371. *
  372. * @return \Cake\Collection\Iterator\InsertIterator
  373. */
  374. public function insert($path, $values)
  375. {
  376. return new InsertIterator($this->_unwrap(), $path, $values);
  377. }
  378. /**
  379. * {@inheritDoc}
  380. *
  381. */
  382. public function toArray($preserveKeys = true)
  383. {
  384. $iterator = $this->_unwrap();
  385. if ($iterator instanceof ArrayIterator) {
  386. $items = $iterator->getArrayCopy();
  387. return $preserveKeys ? $items : array_values($items);
  388. }
  389. return iterator_to_array($this, $preserveKeys);
  390. }
  391. /**
  392. * {@inheritDoc}
  393. *
  394. */
  395. public function toList()
  396. {
  397. return $this->toArray(false);
  398. }
  399. /**
  400. * {@inheritDoc}
  401. *
  402. */
  403. public function jsonSerialize()
  404. {
  405. return $this->toArray();
  406. }
  407. /**
  408. * {@inheritDoc}
  409. *
  410. */
  411. public function compile($preserveKeys = true)
  412. {
  413. return new Collection($this->toArray($preserveKeys));
  414. }
  415. /**
  416. * {@inheritDoc}
  417. *
  418. * @return \Cake\Collection\Iterator\BufferedIterator
  419. */
  420. public function buffered()
  421. {
  422. return new BufferedIterator($this);
  423. }
  424. /**
  425. * {@inheritDoc}
  426. *
  427. * @return \Cake\Collection\Iterator\TreeIterator
  428. */
  429. public function listNested($dir = 'desc', $nestingKey = 'children')
  430. {
  431. $dir = strtolower($dir);
  432. $modes = [
  433. 'desc' => TreeIterator::SELF_FIRST,
  434. 'asc' => TreeIterator::CHILD_FIRST,
  435. 'leaves' => TreeIterator::LEAVES_ONLY
  436. ];
  437. return new TreeIterator(
  438. new NestIterator($this, $nestingKey),
  439. isset($modes[$dir]) ? $modes[$dir] : $dir
  440. );
  441. }
  442. /**
  443. * {@inheritDoc}
  444. *
  445. * @return \Cake\Collection\Iterator\StoppableIterator
  446. */
  447. public function stopWhen($condition)
  448. {
  449. if (!is_callable($condition)) {
  450. $condition = $this->_createMatcherFilter($condition);
  451. }
  452. return new StoppableIterator($this, $condition);
  453. }
  454. /**
  455. * {@inheritDoc}
  456. *
  457. */
  458. public function unfold(callable $transformer = null)
  459. {
  460. if ($transformer === null) {
  461. $transformer = function ($item) {
  462. return $item;
  463. };
  464. }
  465. return new Collection(
  466. new RecursiveIteratorIterator(
  467. new UnfoldIterator($this, $transformer),
  468. RecursiveIteratorIterator::LEAVES_ONLY
  469. )
  470. );
  471. }
  472. /**
  473. * Returns the closest nested iterator that can be safely traversed without
  474. * losing any possible transformations.
  475. *
  476. * @return \Iterator
  477. */
  478. protected function _unwrap()
  479. {
  480. $iterator = $this;
  481. while (get_class($iterator) === 'Cake\Collection\Collection') {
  482. $iterator = $iterator->getInnerIterator();
  483. }
  484. return $iterator;
  485. }
  486. }