CollectionTrait.php 15 KB

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