CollectionTrait.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  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 Cake\Collection\Iterator\ZipIterator;
  31. use Countable;
  32. use Iterator;
  33. use LimitIterator;
  34. use RecursiveIteratorIterator;
  35. /**
  36. * Offers a handful of method to manipulate iterators
  37. */
  38. trait CollectionTrait
  39. {
  40. use ExtractTrait;
  41. /**
  42. * {@inheritDoc}
  43. *
  44. */
  45. public function each(callable $c)
  46. {
  47. foreach ($this->unwrap() as $k => $v) {
  48. $c($v, $k);
  49. }
  50. return $this;
  51. }
  52. /**
  53. * {@inheritDoc}
  54. *
  55. * @return \Cake\Collection\Iterator\FilterIterator
  56. */
  57. public function filter(callable $c = null)
  58. {
  59. if ($c === null) {
  60. $c = function ($v) {
  61. return (bool)$v;
  62. };
  63. }
  64. return new FilterIterator($this->unwrap(), $c);
  65. }
  66. /**
  67. * {@inheritDoc}
  68. *
  69. * @return \Cake\Collection\Iterator\FilterIterator
  70. */
  71. public function reject(callable $c)
  72. {
  73. return new FilterIterator($this->unwrap(), function ($key, $value, $items) use ($c) {
  74. return !$c($key, $value, $items);
  75. });
  76. }
  77. /**
  78. * {@inheritDoc}
  79. *
  80. */
  81. public function every(callable $c)
  82. {
  83. foreach ($this->unwrap() as $key => $value) {
  84. if (!$c($value, $key)) {
  85. return false;
  86. }
  87. }
  88. return true;
  89. }
  90. /**
  91. * {@inheritDoc}
  92. *
  93. */
  94. public function some(callable $c)
  95. {
  96. foreach ($this->unwrap() as $key => $value) {
  97. if ($c($value, $key) === true) {
  98. return true;
  99. }
  100. }
  101. return false;
  102. }
  103. /**
  104. * {@inheritDoc}
  105. *
  106. */
  107. public function contains($value)
  108. {
  109. foreach ($this->unwrap() as $v) {
  110. if ($value === $v) {
  111. return true;
  112. }
  113. }
  114. return false;
  115. }
  116. /**
  117. * {@inheritDoc}
  118. *
  119. * @return \Cake\Collection\Iterator\ReplaceIterator
  120. */
  121. public function map(callable $c)
  122. {
  123. return new ReplaceIterator($this->unwrap(), $c);
  124. }
  125. /**
  126. * {@inheritDoc}
  127. *
  128. */
  129. public function reduce(callable $c, $zero = null)
  130. {
  131. $isFirst = false;
  132. if (func_num_args() < 2) {
  133. $isFirst = true;
  134. }
  135. $result = $zero;
  136. foreach ($this->unwrap() as $k => $value) {
  137. if ($isFirst) {
  138. $result = $value;
  139. $isFirst = false;
  140. continue;
  141. }
  142. $result = $c($result, $value, $k);
  143. }
  144. return $result;
  145. }
  146. /**
  147. * {@inheritDoc}
  148. *
  149. */
  150. public function extract($matcher)
  151. {
  152. $extractor = new ExtractIterator($this->unwrap(), $matcher);
  153. if (is_string($matcher) && strpos($matcher, '{*}') !== false) {
  154. $extractor = $extractor
  155. ->filter(function ($data) {
  156. return $data !== null && ($data instanceof \Traversable || is_array($data));
  157. })
  158. ->unfold();
  159. }
  160. return $extractor;
  161. }
  162. /**
  163. * {@inheritDoc}
  164. *
  165. */
  166. public function max($callback, $type = SORT_NUMERIC)
  167. {
  168. return (new SortIterator($this->unwrap(), $callback, SORT_DESC, $type))->first();
  169. }
  170. /**
  171. * {@inheritDoc}
  172. *
  173. */
  174. public function min($callback, $type = SORT_NUMERIC)
  175. {
  176. return (new SortIterator($this->unwrap(), $callback, SORT_ASC, $type))->first();
  177. }
  178. /**
  179. * {@inheritDoc}
  180. *
  181. */
  182. public function sortBy($callback, $dir = SORT_DESC, $type = SORT_NUMERIC)
  183. {
  184. return new SortIterator($this->unwrap(), $callback, $dir, $type);
  185. }
  186. /**
  187. * {@inheritDoc}
  188. *
  189. */
  190. public function groupBy($callback)
  191. {
  192. $callback = $this->_propertyExtractor($callback);
  193. $group = [];
  194. foreach ($this as $value) {
  195. $group[$callback($value)][] = $value;
  196. }
  197. return new Collection($group);
  198. }
  199. /**
  200. * {@inheritDoc}
  201. *
  202. */
  203. public function indexBy($callback)
  204. {
  205. $callback = $this->_propertyExtractor($callback);
  206. $group = [];
  207. foreach ($this as $value) {
  208. $group[$callback($value)] = $value;
  209. }
  210. return new Collection($group);
  211. }
  212. /**
  213. * {@inheritDoc}
  214. *
  215. */
  216. public function countBy($callback)
  217. {
  218. $callback = $this->_propertyExtractor($callback);
  219. $mapper = function ($value, $key, $mr) use ($callback) {
  220. $mr->emitIntermediate($value, $callback($value));
  221. };
  222. $reducer = function ($values, $key, $mr) {
  223. $mr->emit(count($values), $key);
  224. };
  225. return new Collection(new MapReduce($this->unwrap(), $mapper, $reducer));
  226. }
  227. /**
  228. * {@inheritDoc}
  229. *
  230. */
  231. public function sumOf($matcher = null)
  232. {
  233. if ($matcher === null) {
  234. return array_sum($this->toList());
  235. }
  236. $callback = $this->_propertyExtractor($matcher);
  237. $sum = 0;
  238. foreach ($this as $k => $v) {
  239. $sum += $callback($v, $k);
  240. }
  241. return $sum;
  242. }
  243. /**
  244. * {@inheritDoc}
  245. *
  246. */
  247. public function shuffle()
  248. {
  249. $elements = $this->toArray();
  250. shuffle($elements);
  251. return new Collection($elements);
  252. }
  253. /**
  254. * {@inheritDoc}
  255. *
  256. */
  257. public function sample($size = 10)
  258. {
  259. return new Collection(new LimitIterator($this->shuffle(), 0, $size));
  260. }
  261. /**
  262. * {@inheritDoc}
  263. *
  264. */
  265. public function take($size = 1, $from = 0)
  266. {
  267. return new Collection(new LimitIterator($this->unwrap(), $from, $size));
  268. }
  269. /**
  270. * {@inheritDoc}
  271. *
  272. */
  273. public function skip($howMany)
  274. {
  275. return new Collection(new LimitIterator($this->unwrap(), $howMany));
  276. }
  277. /**
  278. * {@inheritDoc}
  279. *
  280. */
  281. public function match(array $conditions)
  282. {
  283. return $this->filter($this->_createMatcherFilter($conditions));
  284. }
  285. /**
  286. * {@inheritDoc}
  287. *
  288. */
  289. public function firstMatch(array $conditions)
  290. {
  291. return $this->match($conditions)->first();
  292. }
  293. /**
  294. * {@inheritDoc}
  295. *
  296. */
  297. public function first()
  298. {
  299. foreach ($this->take(1) as $result) {
  300. return $result;
  301. }
  302. }
  303. /**
  304. * {@inheritDoc}
  305. *
  306. */
  307. public function last()
  308. {
  309. $iterator = $this->unwrap();
  310. $count = $iterator instanceof Countable ?
  311. count($iterator) :
  312. iterator_count($iterator);
  313. if ($count === 0) {
  314. return null;
  315. }
  316. foreach ($this->take(1, $count - 1) as $last) {
  317. return $last;
  318. }
  319. }
  320. /**
  321. * {@inheritDoc}
  322. *
  323. */
  324. public function append($items)
  325. {
  326. $list = new AppendIterator;
  327. $list->append($this->unwrap());
  328. $list->append((new Collection($items))->unwrap());
  329. return new Collection($list);
  330. }
  331. /**
  332. * {@inheritDoc}
  333. *
  334. */
  335. public function combine($keyPath, $valuePath, $groupPath = null)
  336. {
  337. $options = [
  338. 'keyPath' => $this->_propertyExtractor($keyPath),
  339. 'valuePath' => $this->_propertyExtractor($valuePath),
  340. 'groupPath' => $groupPath ? $this->_propertyExtractor($groupPath) : null
  341. ];
  342. $mapper = function ($value, $key, $mapReduce) use ($options) {
  343. $rowKey = $options['keyPath'];
  344. $rowVal = $options['valuePath'];
  345. if (!($options['groupPath'])) {
  346. $mapReduce->emit($rowVal($value, $key), $rowKey($value, $key));
  347. return;
  348. }
  349. $key = $options['groupPath']($value, $key);
  350. $mapReduce->emitIntermediate(
  351. [$rowKey($value, $key) => $rowVal($value, $key)],
  352. $key
  353. );
  354. };
  355. $reducer = function ($values, $key, $mapReduce) {
  356. $result = [];
  357. foreach ($values as $value) {
  358. $result += $value;
  359. }
  360. $mapReduce->emit($result, $key);
  361. };
  362. return new Collection(new MapReduce($this->unwrap(), $mapper, $reducer));
  363. }
  364. /**
  365. * {@inheritDoc}
  366. *
  367. */
  368. public function nest($idPath, $parentPath)
  369. {
  370. $parents = [];
  371. $idPath = $this->_propertyExtractor($idPath);
  372. $parentPath = $this->_propertyExtractor($parentPath);
  373. $isObject = true;
  374. $mapper = function ($row, $key, $mapReduce) use (&$parents, $idPath, $parentPath) {
  375. $row['children'] = [];
  376. $id = $idPath($row, $key);
  377. $parentId = $parentPath($row, $key);
  378. $parents[$id] =& $row;
  379. $mapReduce->emitIntermediate($id, $parentId);
  380. };
  381. $reducer = function ($values, $key, $mapReduce) use (&$parents, &$isObject) {
  382. static $foundOutType = false;
  383. if (!$foundOutType) {
  384. $isObject = is_object(current($parents));
  385. $foundOutType = true;
  386. }
  387. if (empty($key) || !isset($parents[$key])) {
  388. foreach ($values as $id) {
  389. $parents[$id] = $isObject ? $parents[$id] : new ArrayIterator($parents[$id], 1);
  390. $mapReduce->emit($parents[$id]);
  391. }
  392. return;
  393. }
  394. $children = [];
  395. foreach ($values as $id) {
  396. $children[] =& $parents[$id];
  397. }
  398. $parents[$key]['children'] = $children;
  399. };
  400. return (new Collection(new MapReduce($this->unwrap(), $mapper, $reducer)))
  401. ->map(function ($value) use (&$isObject) {
  402. return $isObject ? $value : $value->getArrayCopy();
  403. });
  404. }
  405. /**
  406. * {@inheritDoc}
  407. *
  408. * @return \Cake\Collection\Iterator\InsertIterator
  409. */
  410. public function insert($path, $values)
  411. {
  412. return new InsertIterator($this->unwrap(), $path, $values);
  413. }
  414. /**
  415. * {@inheritDoc}
  416. *
  417. */
  418. public function toArray($preserveKeys = true)
  419. {
  420. $iterator = $this->unwrap();
  421. if ($iterator instanceof ArrayIterator) {
  422. $items = $iterator->getArrayCopy();
  423. return $preserveKeys ? $items : array_values($items);
  424. }
  425. return iterator_to_array($this, $preserveKeys);
  426. }
  427. /**
  428. * {@inheritDoc}
  429. *
  430. */
  431. public function toList()
  432. {
  433. return $this->toArray(false);
  434. }
  435. /**
  436. * {@inheritDoc}
  437. *
  438. */
  439. public function jsonSerialize()
  440. {
  441. return $this->toArray();
  442. }
  443. /**
  444. * {@inheritDoc}
  445. *
  446. */
  447. public function compile($preserveKeys = true)
  448. {
  449. return new Collection($this->toArray($preserveKeys));
  450. }
  451. /**
  452. * {@inheritDoc}
  453. *
  454. * @return \Cake\Collection\Iterator\BufferedIterator
  455. */
  456. public function buffered()
  457. {
  458. return new BufferedIterator($this);
  459. }
  460. /**
  461. * {@inheritDoc}
  462. *
  463. * @return \Cake\Collection\Iterator\TreeIterator
  464. */
  465. public function listNested($dir = 'desc', $nestingKey = 'children')
  466. {
  467. $dir = strtolower($dir);
  468. $modes = [
  469. 'desc' => TreeIterator::SELF_FIRST,
  470. 'asc' => TreeIterator::CHILD_FIRST,
  471. 'leaves' => TreeIterator::LEAVES_ONLY
  472. ];
  473. return new TreeIterator(
  474. new NestIterator($this, $nestingKey),
  475. isset($modes[$dir]) ? $modes[$dir] : $dir
  476. );
  477. }
  478. /**
  479. * {@inheritDoc}
  480. *
  481. * @return \Cake\Collection\Iterator\StoppableIterator
  482. */
  483. public function stopWhen($condition)
  484. {
  485. if (!is_callable($condition)) {
  486. $condition = $this->_createMatcherFilter($condition);
  487. }
  488. return new StoppableIterator($this, $condition);
  489. }
  490. /**
  491. * {@inheritDoc}
  492. *
  493. */
  494. public function unfold(callable $transformer = null)
  495. {
  496. if ($transformer === null) {
  497. $transformer = function ($item) {
  498. return $item;
  499. };
  500. }
  501. return new Collection(
  502. new RecursiveIteratorIterator(
  503. new UnfoldIterator($this, $transformer),
  504. RecursiveIteratorIterator::LEAVES_ONLY
  505. )
  506. );
  507. }
  508. /**
  509. * {@inheritDoc}
  510. *
  511. */
  512. public function through(callable $handler)
  513. {
  514. $result = $handler($this);
  515. return $result instanceof CollectionInterface ? $result: new Collection($result);
  516. }
  517. /**
  518. * {@inheritDoc}
  519. *
  520. */
  521. public function zip($items)
  522. {
  523. return new ZipIterator(array_merge([$this], func_get_args()));
  524. }
  525. /**
  526. * {@inheritDoc}
  527. *
  528. */
  529. public function zipWith($items, $callable)
  530. {
  531. if (func_num_args() > 2) {
  532. $items = func_get_args();
  533. $callable = array_pop($items);
  534. } else {
  535. $items = [$items];
  536. }
  537. return new ZipIterator(array_merge([$this], $items), $callable);
  538. }
  539. /**
  540. * {@inheritDoc}
  541. *
  542. */
  543. public function isEmpty()
  544. {
  545. return iterator_count($this->take(1)) === 0;
  546. }
  547. /**
  548. * {@inheritDoc}
  549. *
  550. */
  551. public function unwrap()
  552. {
  553. $iterator = $this;
  554. while (get_class($iterator) === 'Cake\Collection\Collection') {
  555. $iterator = $iterator->getInnerIterator();
  556. }
  557. return $iterator;
  558. }
  559. /**
  560. * Backwards compatible wrapper for unwrap()
  561. *
  562. * @return \Iterator
  563. * @deprecated
  564. */
  565. public function _unwrap()
  566. {
  567. return $this->unwrap();
  568. }
  569. }