CollectionTrait.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854
  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 CakePHP(tm) v 3.0.0
  13. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  14. */
  15. namespace Cake\Collection;
  16. use AppendIterator;
  17. use ArrayObject;
  18. use Cake\Collection\Collection;
  19. use Cake\Collection\Iterator\ExtractIterator;
  20. use Cake\Collection\Iterator\FilterIterator;
  21. use Cake\Collection\Iterator\InsertIterator;
  22. use Cake\Collection\Iterator\MapReduce;
  23. use Cake\Collection\Iterator\ReplaceIterator;
  24. use Cake\Collection\Iterator\SortIterator;
  25. use LimitIterator;
  26. /**
  27. * Offers a handful of method to manipulate iterators
  28. */
  29. trait CollectionTrait {
  30. use ExtractTrait;
  31. /**
  32. * Executes the passed callable for each of the elements in this collection
  33. * and passes both the value and key for them on each step.
  34. * Returns the same collection for chaining.
  35. *
  36. * ###Example:
  37. *
  38. * {{{
  39. * $collection = (new Collection($items))->each(function($value, $key) {
  40. * echo "Element $key: $value";
  41. * });
  42. * }}}
  43. *
  44. * @param callable $c callable function that will receive each of the elements
  45. * in this collection
  46. * @return \Cake\Collection\Collection
  47. */
  48. public function each(callable $c) {
  49. foreach ($this as $k => $v) {
  50. $c($v, $k);
  51. }
  52. return $this;
  53. }
  54. /**
  55. * Looks through each value in the collection, and returns another collection with
  56. * all the values that pass a truth test. Only the values for which the callback
  57. * returns true will be present in the resulting collection.
  58. *
  59. * Each time the callback is executed it will receive the value of the element
  60. * in the current iteration, the key of the element and this collection as
  61. * arguments, in that order.
  62. *
  63. * ##Example:
  64. *
  65. * Filtering odd numbers in an array, at the end only the value 2 will
  66. * be present in the resulting collection:
  67. *
  68. * {{{
  69. * $collection = (new Collection([1, 2, 3]))->filter(function($value, $key) {
  70. * return $value % 2 === 0;
  71. * });
  72. * }}}
  73. *
  74. * @param callable $c the method that will receive each of the elements and
  75. * returns true whether or not they should be in the resulting collection.
  76. * @return \Cake\Collection\Iterator\FilterIterator
  77. */
  78. public function filter(callable $c) {
  79. return new FilterIterator($this, $c);
  80. }
  81. /**
  82. * Looks through each value in the collection, and returns another collection with
  83. * all the values that do not pass a truth test. This is the opposite of `filter`.
  84. *
  85. * Each time the callback is executed it will receive the value of the element
  86. * in the current iteration, the key of the element and this collection as
  87. * arguments, in that order.
  88. *
  89. * ##Example:
  90. *
  91. * Filtering even numbers in an array, at the end only values 1 and 3 will
  92. * be present in the resulting collection:
  93. *
  94. * {{{
  95. * $collection = (new Collection([1, 2, 3]))->reject(function($value, $key) {
  96. * return $value % 2 === 0;
  97. * });
  98. * }}}
  99. *
  100. * @param callable $c the method that will receive each of the elements and
  101. * returns true whether or not they should be out of the resulting collection.
  102. * @return \Cake\Collection\Iterator\FilterIterator
  103. */
  104. public function reject(callable $c) {
  105. return new FilterIterator($this, function ($key, $value, $items) use ($c) {
  106. return !$c($key, $value, $items);
  107. });
  108. }
  109. /**
  110. * Returns true if all values in this collection pass the truth test provided
  111. * in the callback.
  112. *
  113. * Each time the callback is executed it will receive the value of the element
  114. * in the current iteration and the key of the element as arguments, in that
  115. * order.
  116. *
  117. * ###Example:
  118. *
  119. * {{{
  120. * $overTwentyOne = (new Collection([24, 45, 60, 15]))->every(function($value, $key) {
  121. * return $value > 21;
  122. * });
  123. * }}}
  124. *
  125. * @param callable $c a callback function
  126. * @return boolean true if for all elements in this collection the provided
  127. * callback returns true, false otherwise
  128. */
  129. public function every(callable $c) {
  130. foreach ($this as $key => $value) {
  131. if (!$c($value, $key)) {
  132. return false;
  133. }
  134. }
  135. return true;
  136. }
  137. /**
  138. * Returns true if any of the values in this collection pass the truth test
  139. * provided in the callback.
  140. *
  141. * Each time the callback is executed it will receive the value of the element
  142. * in the current iteration and the key of the element as arguments, in that
  143. * order.
  144. *
  145. * ###Example:
  146. *
  147. * {{{
  148. * $hasYoungPeople = (new Collection([24, 45, 15]))->every(function($value, $key) {
  149. * return $value < 21;
  150. * });
  151. * }}}
  152. *
  153. * @param callable $c a callback function
  154. * @return boolean true if for all elements in this collection the provided
  155. * callback returns true, false otherwise
  156. */
  157. public function some(callable $c) {
  158. foreach ($this as $key => $value) {
  159. if ($c($value, $key) === true) {
  160. return true;
  161. }
  162. }
  163. return false;
  164. }
  165. /**
  166. * Returns true if $value is present in this collection. Comparisons are made
  167. * both by value and type.
  168. *
  169. * @param mixed $value the value to check for
  170. * @return boolean true if $value is present in this collection
  171. */
  172. public function contains($value) {
  173. foreach ($this as $v) {
  174. if ($value === $v) {
  175. return true;
  176. }
  177. }
  178. return false;
  179. }
  180. /**
  181. * Returns another collection after modifying each of the values in this one using
  182. * the provided callable.
  183. *
  184. * Each time the callback is executed it will receive the value of the element
  185. * in the current iteration, the key of the element and this collection as
  186. * arguments, in that order.
  187. *
  188. * ##Example:
  189. *
  190. * Getting a collection of booleans where true indicates if a person is female:
  191. *
  192. * {{{
  193. * $collection = (new Collection($people))->map(function($person, $key) {
  194. * return $person->gender === 'female';
  195. * });
  196. * }}}
  197. *
  198. * @param callable $c the method that will receive each of the elements and
  199. * returns the new value for the key that is being iterated
  200. * @return \Cake\Collection\Iterator\ReplaceIterator
  201. */
  202. public function map(callable $c) {
  203. return new ReplaceIterator($this, $c);
  204. }
  205. /**
  206. * Folds the values in this collection to a single value, as the result of
  207. * applying the callback function to all elements. $zero is the initial state
  208. * of the reduction, and each successive step of it should be returned
  209. * by the callback function.
  210. *
  211. * @param callable $c The callback function to be called
  212. * @param mixed $zero The state of reduction
  213. * @return void
  214. */
  215. public function reduce(callable $c, $zero) {
  216. $result = $zero;
  217. foreach ($this as $k => $value) {
  218. $result = $c($result, $value, $k);
  219. }
  220. return $result;
  221. }
  222. /**
  223. * Returns a new collection containing the column or property value found in each
  224. * of the elements, as requested in the $matcher param.
  225. *
  226. * The matcher can be a string with a property name to extract or a dot separated
  227. * path of properties that should be followed to get the last one in the path.
  228. *
  229. * If a column or property could not be found for a particular element in the
  230. * collection, that position is filled with null.
  231. *
  232. * ### Example:
  233. *
  234. * Extract the user name for all comments in the array:
  235. *
  236. * {{{
  237. * $items = [
  238. * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']],
  239. * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']]
  240. * ];
  241. * $extracted = (new Collection($items))->extract('comment.user.name');
  242. *
  243. * //Result will look like this when converted to array
  244. * ['Mark', 'Renan']
  245. * }}}
  246. *
  247. * @param string $matcher a dot separated string symbolizing the path to follow
  248. * inside the hierarchy of each value so that the column can be extracted.
  249. * @return \Cake\Collection\Iterator\ExtractIterator
  250. */
  251. public function extract($matcher) {
  252. return new ExtractIterator($this, $matcher);
  253. }
  254. /**
  255. * Returns the top element in this collection after being sorted by a property.
  256. * Check the sortBy method for information on the callback and $type parameters
  257. *
  258. * ###Examples:
  259. *
  260. * {{{
  261. * //For a collection of employees
  262. * $max = $collection->max('age');
  263. * $max = $collection->max('user.salary');
  264. * $max = $collection->max(function($e) {
  265. * return $e->get('user')->get('salary');
  266. * });
  267. *
  268. * //Display employee name
  269. * echo $max->name;
  270. * }}}
  271. *
  272. * @param callable|string $callback the callback or column name to use for sorting
  273. * @param integer $type the type of comparison to perform, either SORT_STRING
  274. * SORT_NUMERIC or SORT_NATURAL
  275. * @see \Cake\Collection\Collection::sortBy()
  276. * @return mixed The value of the top element in the collection
  277. */
  278. public function max($callback, $type = SORT_NUMERIC) {
  279. $sorted = new SortIterator($this, $callback, SORT_DESC, $type);
  280. return $sorted->top();
  281. }
  282. /**
  283. * Returns the bottom element in this collection after being sorted by a property.
  284. * Check the sortBy method for information on the callback and $type parameters
  285. *
  286. * ###Examples:
  287. *
  288. * {{{
  289. * //For a collection of employees
  290. * $min = $collection->min('age');
  291. * $min = $collection->min('user.salary');
  292. * $min = $collection->min(function($e) {
  293. * return $e->get('user')->get('salary');
  294. * });
  295. *
  296. * //Display employee name
  297. * echo $min->name;
  298. * }}}
  299. *
  300. *
  301. * @param callable|string $callback the callback or column name to use for sorting
  302. * @param integer $type the type of comparison to perform, either SORT_STRING
  303. * SORT_NUMERIC or SORT_NATURAL
  304. * @see \Cake\Collection\Collection::sortBy()
  305. * @return mixed The value of the bottom element in the collection
  306. */
  307. public function min($callback, $type = SORT_NUMERIC) {
  308. $sorted = new SortIterator($this, $callback, SORT_ASC, $type);
  309. return $sorted->top();
  310. }
  311. /**
  312. * Returns a sorted iterator out of the elements in this colletion,
  313. * ranked in ascending order by the results of running each value through a
  314. * callback. $callback can also be a string representing the column or property
  315. * name.
  316. *
  317. * The callback will receive as its first argument each of the elements in $items,
  318. * the value returned by the callback will be used as the value for sorting such
  319. * element. Please note that the callback function could be called more than once
  320. * per element.
  321. *
  322. * ###Example:
  323. *
  324. * {{{
  325. * $items = $collection->sortBy(function($user) {
  326. * return $user->age;
  327. * });
  328. *
  329. * //alternatively
  330. * $items = $collection->sortBy('age');
  331. *
  332. * //or use a property path
  333. * $items = $collection->sortBy('department.name');
  334. *
  335. * // output all user name order by their age in descending order
  336. * foreach ($items as $user) {
  337. * echo $user->name;
  338. * }
  339. * }}}
  340. *
  341. * @param callable|string $callback the callback or column name to use for sorting
  342. * @param integer $dir either SORT_DESC or SORT_ASC
  343. * @param integer $type the type of comparison to perform, either SORT_STRING
  344. * SORT_NUMERIC or SORT_NATURAL
  345. * @return \Cake\Collection\Collection
  346. */
  347. public function sortBy($callback, $dir = SORT_DESC, $type = SORT_NUMERIC) {
  348. return new Collection(new SortIterator($this, $callback, $dir, $type));
  349. }
  350. /**
  351. * Splits a collection into sets, grouped by the result of running each value
  352. * through the callback. If $callback is is a string instead of a callable,
  353. * groups by the property named by $callback on each of the values.
  354. *
  355. * When $callback is a string it should be a property name to extract or
  356. * a dot separated path of properties that should be followed to get the last
  357. * one in the path.
  358. *
  359. * ###Example:
  360. *
  361. * {{{
  362. * $items = [
  363. * ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  364. * ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  365. * ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  366. * ];
  367. *
  368. * $group = (new Collection($items))->groupBy('parent_id');
  369. *
  370. * //Or
  371. * $group = (new Collection($items))->groupBy(function($e) {
  372. * return $e['parent_id'];
  373. * });
  374. *
  375. * //Result will look like this when converted to array
  376. * [
  377. * 10 => [
  378. * ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  379. * ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  380. * ],
  381. * 11 => [
  382. * ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  383. * ]
  384. * ];
  385. * }}}
  386. *
  387. * @param callable|string $callback the callback or column name to use for grouping
  388. * or a function returning the grouping key out of the provided element
  389. * @return \Cake\Collection\Collection
  390. */
  391. public function groupBy($callback) {
  392. $callback = $this->_propertyExtractor($callback);
  393. $group = [];
  394. foreach ($this as $value) {
  395. $group[$callback($value)][] = $value;
  396. }
  397. return new Collection($group);
  398. }
  399. /**
  400. * Given a list and a callback function that returns a key for each element
  401. * in the list (or a property name), returns an object with an index of each item.
  402. * Just like groupBy, but for when you know your keys are unique.
  403. *
  404. * When $callback is a string it should be a property name to extract or
  405. * a dot separated path of properties that should be followed to get the last
  406. * one in the path.
  407. *
  408. * ###Example:
  409. *
  410. * {{{
  411. * $items = [
  412. * ['id' => 1, 'name' => 'foo'],
  413. * ['id' => 2, 'name' => 'bar'],
  414. * ['id' => 3, 'name' => 'baz'],
  415. * ];
  416. *
  417. * $indexed = (new Collection($items))->indexBy('id');
  418. *
  419. * //Or
  420. * $indexed = (new Collection($items))->indexBy(function($e) {
  421. * return $e['id'];
  422. * });
  423. *
  424. * //Result will look like this when converted to array
  425. * [
  426. * 1 => ['id' => 1, 'name' => 'foo'],
  427. * 3 => ['id' => 3, 'name' => 'baz'],
  428. * 2 => ['id' => 2, 'name' => 'bar'],
  429. * ];
  430. * }}}
  431. *
  432. * @param callable|string $callback the callback or column name to use for indexing
  433. * or a function returning the indexing key out of the provided element
  434. * @return \Cake\Collection\Collection
  435. */
  436. public function indexBy($callback) {
  437. $callback = $this->_propertyExtractor($callback);
  438. $group = [];
  439. foreach ($this as $value) {
  440. $group[$callback($value)] = $value;
  441. }
  442. return new Collection($group);
  443. }
  444. /**
  445. * Sorts a list into groups and returns a count for the number of elements
  446. * in each group. Similar to groupBy, but instead of returning a list of values,
  447. * returns a count for the number of values in that group.
  448. *
  449. * When $callback is a string it should be a property name to extract or
  450. * a dot separated path of properties that should be followed to get the last
  451. * one in the path.
  452. *
  453. * ###Example:
  454. *
  455. * {{{
  456. * $items = [
  457. * ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  458. * ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  459. * ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  460. * ];
  461. *
  462. * $group = (new Collection($items))->countBy('parent_id');
  463. *
  464. * //Or
  465. * $group = (new Collection($items))->countBy(function($e) {
  466. * return $e['parent_id'];
  467. * });
  468. *
  469. * //Result will look like this when converted to array
  470. * [
  471. * 10 => 2,
  472. * 11 => 1
  473. * ];
  474. * }}}
  475. *
  476. * @param callable|string $callback the callback or column name to use for indexing
  477. * or a function returning the indexing key out of the provided element
  478. * @return \Cake\Collection\Collection
  479. */
  480. public function countBy($callback) {
  481. $callback = $this->_propertyExtractor($callback);
  482. $mapper = function($value, $key, $mr) use ($callback) {
  483. $mr->emitIntermediate($value, $callback($value));
  484. };
  485. $reducer = function ($values, $key, $mr) {
  486. $mr->emit(count($values), $key);
  487. };
  488. return new Collection(new MapReduce($this, $mapper, $reducer));
  489. }
  490. /**
  491. * Returns a new collection with the elements placed in a random order,
  492. * this function does not preserve the original keys in the collection.
  493. *
  494. * @return \Cake\Collection\Collection
  495. */
  496. public function shuffle() {
  497. $elements = iterator_to_array($this);
  498. shuffle($elements);
  499. return new Collection($elements);
  500. }
  501. /**
  502. * Returns a new collection with maximum $size random elements
  503. * from this collection
  504. *
  505. * @param integer $size the maximum number of elements to randomly
  506. * take from this collection
  507. * @return \Cake\Collection\Collection
  508. */
  509. public function sample($size = 10) {
  510. return new Collection(new LimitIterator($this->shuffle(), 0, $size));
  511. }
  512. /**
  513. * Returns a new collection with maximum $size elements in the internal
  514. * order this collection was created. If a second parameter is passed, it
  515. * will determine from what position to start taking elements.
  516. *
  517. * @param integer $size the maximum number of elements to take from
  518. * this collection
  519. * @param integer $from A positional offset from where to take the elements
  520. * @return \Cake\Collection\Collection
  521. */
  522. public function take($size = 1, $from = 0) {
  523. return new Collection(new LimitIterator($this, $from, $size));
  524. }
  525. /**
  526. * Looks through each value in the list, returning a Collection of all the
  527. * values that contain all of the key-value pairs listed in $conditions.
  528. *
  529. * ###Example:
  530. *
  531. * {{{
  532. * $items = [
  533. * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']],
  534. * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']]
  535. * ];
  536. *
  537. * $extracted = (new Collection($items))->match(['user.name' => 'Renan']);
  538. *
  539. * //Result will look like this when converted to array
  540. * [
  541. * ['comment' => ['body' => 'very cool', 'user' => ['name' => 'Renan']]
  542. * ]
  543. * }}}
  544. *
  545. * @param array $conditions a key-value list of conditions where
  546. * the key is a property path as accepted by `Collection::extract,
  547. * and the value the condition against with each element will be matched
  548. * @return \Cake\Collection\Collection
  549. */
  550. public function match(array $conditions) {
  551. $matchers = [];
  552. foreach ($conditions as $property => $value) {
  553. $extractor = $this->_propertyExtractor($property);
  554. $matchers[] = function($v) use ($extractor, $value) {
  555. return $extractor($v) == $value;
  556. };
  557. }
  558. $filter = function($value) use ($matchers) {
  559. $valid = true;
  560. foreach ($matchers as $match) {
  561. $valid = $valid && $match($value);
  562. }
  563. return $valid;
  564. };
  565. return $this->filter($filter);
  566. }
  567. /**
  568. * Returns the first result matching all of the key-value pairs listed in
  569. * conditions.
  570. *
  571. * @param array $conditions a key-value list of conditions where the key is
  572. * a property path as accepted by `Collection::extract`, and the value the
  573. * condition against with each element will be matched
  574. * @see \Cake\Collection\Collection::match()
  575. * @return mixed
  576. */
  577. public function firstMatch(array $conditions) {
  578. return $this->match($conditions)->first();
  579. }
  580. /**
  581. * Returns the first result in this collection
  582. *
  583. * @return mixed The first value in the collection will be returned.
  584. */
  585. public function first() {
  586. foreach ($this->take(1) as $result) {
  587. return $result;
  588. }
  589. }
  590. /**
  591. * Returns a new collection as the result of concatenating the list of elements
  592. * in this collection with the passed list of elements
  593. *
  594. * @param array|\Traversable $items
  595. * @return \Cake\Collection\Collection
  596. */
  597. public function append($items) {
  598. $list = new AppendIterator;
  599. $list->append($this);
  600. $list->append(new Collection($items));
  601. return new Collection($list);
  602. }
  603. /**
  604. * Returns a new collection where the values extracted based on a value path
  605. * and then indexed by a key path. Optionally this method can produce parent
  606. * groups based on a group property path.
  607. *
  608. * ### Examples:
  609. *
  610. * {{{
  611. * $items = [
  612. * ['id' => 1, 'name' => 'foo', 'parent' => 'a'],
  613. * ['id' => 2, 'name' => 'bar', 'parent' => 'b'],
  614. * ['id' => 3, 'name' => 'baz', 'parent' => 'a'],
  615. * ];
  616. *
  617. * $combined = (new Collection($items))->combine('id', 'name');
  618. *
  619. * //Result will look like this when converted to array
  620. * [
  621. * 1 => 'foo',
  622. * 2 => 'bar',
  623. * 3 => 'baz,
  624. * ];
  625. *
  626. * $combined = (new Collection($items))->combine('id', 'name', 'parent');
  627. *
  628. * //Result will look like this when converted to array
  629. * [
  630. * 'a' => [1 => 'foo', 3 => 'baz'],
  631. * 'b' => [2 => 'bar']
  632. * ];
  633. * }}}
  634. *
  635. * @param callable|string $keyPath the column name path to use for indexing
  636. * or a function returning the indexing key out of the provided element
  637. * @param callable|string $valuePath the column name path to use as the array value
  638. * or a function returning the value out of the provided element
  639. * @param callable|string $valuePath the column name path to use as the parent
  640. * grouping key or a function returning the key out of the provided element
  641. * @return \Cake\Collection\Collection
  642. */
  643. public function combine($keyPath, $valuePath, $groupPath = null) {
  644. $options = [
  645. 'keyPath' => $this->_propertyExtractor($keyPath),
  646. 'valuePath' => $this->_propertyExtractor($valuePath),
  647. 'groupPath' => $groupPath ? $this->_propertyExtractor($groupPath) : null
  648. ];
  649. $mapper = function($value, $key, $mapReduce) use ($options) {
  650. $rowKey = $options['keyPath'];
  651. $rowVal = $options['valuePath'];
  652. if (!($options['groupPath'])) {
  653. $mapReduce->emit($rowVal($value, $key), $rowKey($value, $key));
  654. return;
  655. }
  656. $key = $options['groupPath']($value, $key);
  657. $mapReduce->emitIntermediate(
  658. [$rowKey($value, $key) => $rowVal($value, $key)],
  659. $key
  660. );
  661. };
  662. $reducer = function($values, $key, $mapReduce) {
  663. $result = [];
  664. foreach ($values as $value) {
  665. $result += $value;
  666. }
  667. $mapReduce->emit($result, $key);
  668. };
  669. return new Collection(new MapReduce($this, $mapper, $reducer));
  670. }
  671. /**
  672. * Returns a new collection where the values are nested in a tree-like structure
  673. * based on an id property path and a parent id property path.
  674. *
  675. * @param callable|string $idPath the column name path to use for determining
  676. * whether an element is parent of another
  677. * @param callable|string $parentPath the column name path to use for determining
  678. * whether an element is child of another
  679. * @return \Cake\Collection\Collection
  680. */
  681. public function nest($idPath, $parentPath) {
  682. $parents = [];
  683. $idPath = $this->_propertyExtractor($idPath);
  684. $parentPath = $this->_propertyExtractor($parentPath);
  685. $isObject = !is_array((new Collection($this))->first());
  686. $mapper = function($row, $key, $mapReduce) use (&$parents, $idPath, $parentPath) {
  687. $row['children'] = [];
  688. $id = $idPath($row, $key);
  689. $parentId = $parentPath($row, $key);
  690. $parents[$id] =& $row;
  691. $mapReduce->emitIntermediate($id, $parentId);
  692. };
  693. $reducer = function($values, $key, $mapReduce) use (&$parents, $isObject) {
  694. if (empty($key) || !isset($parents[$key])) {
  695. foreach ($values as $id) {
  696. $parents[$id] = $isObject ? $parents[$id] : new ArrayObject($parents[$id]);
  697. $mapReduce->emit($parents[$id]);
  698. }
  699. return;
  700. }
  701. foreach ($values as $id) {
  702. $parents[$key]['children'][] =& $parents[$id];
  703. }
  704. };
  705. $collection = new MapReduce($this, $mapper, $reducer);
  706. if (!$isObject) {
  707. $collection = (new Collection($collection))->map(function($value) {
  708. return (array)$value;
  709. });
  710. }
  711. return new Collection($collection);
  712. }
  713. /**
  714. * Returns a new collection containing each of the elements found in `$values` as
  715. * a property inside the corresponding elements in this collection. The property
  716. * where the values will be inserted is described by the `$path` parameter.
  717. *
  718. * The $path can be a string with a property name or a dot separated path of
  719. * properties that should be followed to get the last one in the path.
  720. *
  721. * If a column or property could not be found for a particular element in the
  722. * collection as part of the path, the element will be kept unchanged.
  723. *
  724. * ### Example:
  725. *
  726. * Insert ages into a collection containing users:
  727. *
  728. * {{{
  729. * $items = [
  730. * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark']],
  731. * ['comment' => ['body' => 'awesome', 'user' => ['name' => 'Renan']]
  732. * ];
  733. * $ages = [25, 28];
  734. * $inserted = (new Collection($items))->insert('comment.user.age', $ages);
  735. *
  736. * //Result will look like this when converted to array
  737. * [
  738. * ['comment' => ['body' => 'cool', 'user' => ['name' => 'Mark', 'age' => 25]],
  739. * ['comment' => ['body' => 'awesome', 'user' => ['name' => 'Renan', 'age' => 28]]
  740. * ];
  741. * }}}
  742. *
  743. * @param string $path a dot separated string symbolizing the path to follow
  744. * inside the hierarchy of each value so that the value can be inserted
  745. * @param array|\Traversable The values to be inserted at the specified path,
  746. * values are matched with the elements in this collection by its positional index.
  747. * @return \Cake\Collection\Iterator\InsertIterator
  748. */
  749. public function insert($path, $values) {
  750. return new InsertIterator($this, $path, $values);
  751. }
  752. /**
  753. * Returns an array representation of the results
  754. *
  755. * @param boolean $preserveKeys whether to use the keys returned by this
  756. * collection as the array keys. Keep in mind that it is valid for iterators
  757. * to return the same key for different elements, setting this value to false
  758. * can help getting all items if keys are not important in the result.
  759. * @return array
  760. */
  761. public function toArray($preserveKeys = true) {
  762. return iterator_to_array($this, $preserveKeys);
  763. }
  764. /**
  765. * Convert a result set into JSON.
  766. *
  767. * Part of JsonSerializable interface.
  768. *
  769. * @return array The data to convert to JSON
  770. */
  771. public function jsonSerialize() {
  772. return $this->toArray();
  773. }
  774. /**
  775. * Iterates once all elements in this collection and executes all stacked
  776. * operations of them, finally it returns a new collection with the result.
  777. * This is useful for converting non-rewindable internal iterators into
  778. * a collection that can be rewound and used multiple times.
  779. *
  780. * A common use case is to re-use the same variable for calculating different
  781. * data. In those cases it may be helpful and more performant to first compile
  782. * a collection and then apply more operations to it.
  783. *
  784. * ### Example:
  785. *
  786. * {{{
  787. * $collection->map($mapper)->sortBy('age')->extract('name');
  788. * $compiled = $collection->compile();
  789. * $isJohnHere = $compiled->some($johnMatcher);
  790. * $allButJohn = $compiled->filter($johnMatcher);
  791. * }}}
  792. *
  793. * In the above example, had the collection not been compiled before, the
  794. * iterations for `map`, `sortBy` and `extract` would've been executed twice:
  795. * once for getting `$isJohnHere` and once for `$allButJohn`
  796. *
  797. * You can think of this method as a way to create save points for complex
  798. * calculations in a collection.
  799. *
  800. * @param boolean $preserveKeys whether to use the keys returned by this
  801. * collection as the array keys. Keep in mind that it is valid for iterators
  802. * to return the same key for different elements, setting this value to false
  803. * can help getting all items if keys are not important in the result.
  804. * @return \Cake\Collection\Collection
  805. */
  806. public function compile($preserveKeys = true) {
  807. return new Collection($this->toArray($preserveKeys));
  808. }
  809. }