CollectionTest.php 79 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 3.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Collection;
  17. use ArrayIterator;
  18. use ArrayObject;
  19. use Cake\Collection\Collection;
  20. use Cake\Collection\Iterator\BufferedIterator;
  21. use Cake\ORM\Entity;
  22. use Cake\TestSuite\TestCase;
  23. use DateInterval;
  24. use DatePeriod;
  25. use DateTime;
  26. use Generator;
  27. use InvalidArgumentException;
  28. use LogicException;
  29. use NoRewindIterator;
  30. use stdClass;
  31. use TestApp\Collection\CountableIterator;
  32. use TestApp\Collection\TestCollection;
  33. /**
  34. * Collection Test
  35. *
  36. * @coversDefaultClass \Cake\Collection\Collection
  37. */
  38. class CollectionTest extends TestCase
  39. {
  40. /**
  41. * Tests that it is possible to convert an array into a collection
  42. */
  43. public function testArrayIsWrapped(): void
  44. {
  45. $items = [1, 2, 3];
  46. $collection = new Collection($items);
  47. $this->assertEquals($items, iterator_to_array($collection));
  48. }
  49. /**
  50. * Provider for average tests
  51. *
  52. * @return array
  53. */
  54. public function avgProvider(): array
  55. {
  56. $items = [1, 2, 3];
  57. return [
  58. 'array' => [$items],
  59. 'iterator' => [$this->yieldItems($items)],
  60. ];
  61. }
  62. /**
  63. * Tests the avg method
  64. *
  65. * @dataProvider avgProvider
  66. */
  67. public function testAvg(iterable $items): void
  68. {
  69. $collection = new Collection($items);
  70. $this->assertSame(2, $collection->avg());
  71. $items = [['foo' => 1], ['foo' => 2], ['foo' => 3]];
  72. $collection = new Collection($items);
  73. $this->assertSame(2, $collection->avg('foo'));
  74. }
  75. /**
  76. * Tests the avg method when on an empty collection
  77. */
  78. public function testAvgWithEmptyCollection(): void
  79. {
  80. $collection = new Collection([]);
  81. $this->assertNull($collection->avg());
  82. $collection = new Collection([null, null]);
  83. $this->assertSame(0, $collection->avg());
  84. }
  85. /**
  86. * Provider for average tests with use of a matcher
  87. *
  88. * @return array
  89. */
  90. public function avgWithMatcherProvider(): array
  91. {
  92. $items = [['foo' => 1], ['foo' => 2], ['foo' => 3]];
  93. return [
  94. 'array' => [$items],
  95. 'iterator' => [$this->yieldItems($items)],
  96. ];
  97. }
  98. /**
  99. * ests the avg method
  100. *
  101. * @dataProvider avgWithMatcherProvider
  102. */
  103. public function testAvgWithMatcher(iterable $items): void
  104. {
  105. $collection = new Collection($items);
  106. $this->assertSame(2, $collection->avg('foo'));
  107. }
  108. /**
  109. * Provider for some median tests
  110. *
  111. * @return array
  112. */
  113. public function medianProvider(): array
  114. {
  115. $items = [5, 2, 4];
  116. return [
  117. 'array' => [$items],
  118. 'iterator' => [$this->yieldItems($items)],
  119. ];
  120. }
  121. /**
  122. * Tests the median method
  123. *
  124. * @dataProvider medianProvider
  125. */
  126. public function testMedian(iterable $items): void
  127. {
  128. $collection = new Collection($items);
  129. $this->assertSame(4, $collection->median());
  130. }
  131. /**
  132. * Tests the median method when on an empty collection
  133. */
  134. public function testMedianWithEmptyCollection(): void
  135. {
  136. $collection = new Collection([]);
  137. $this->assertNull($collection->median());
  138. $collection = new Collection([null, null]);
  139. $this->assertSame(0, $collection->median());
  140. }
  141. /**
  142. * Tests the median method
  143. *
  144. * @dataProvider simpleProvider
  145. */
  146. public function testMedianEven(iterable $items): void
  147. {
  148. $collection = new Collection($items);
  149. $this->assertSame(2.5, $collection->median());
  150. }
  151. /**
  152. * Provider for median tests with use of a matcher
  153. *
  154. * @return array
  155. */
  156. public function medianWithMatcherProvider(): array
  157. {
  158. $items = [
  159. ['invoice' => ['total' => 400]],
  160. ['invoice' => ['total' => 500]],
  161. ['invoice' => ['total' => 200]],
  162. ['invoice' => ['total' => 100]],
  163. ['invoice' => ['total' => 333]],
  164. ];
  165. return [
  166. 'array' => [$items],
  167. 'iterator' => [$this->yieldItems($items)],
  168. ];
  169. }
  170. /**
  171. * Tests the median method
  172. *
  173. * @dataProvider medianWithMatcherProvider
  174. */
  175. public function testMedianWithMatcher(iterable $items): void
  176. {
  177. $this->assertSame(333, (new Collection($items))->median('invoice.total'));
  178. }
  179. /**
  180. * Tests that it is possible to convert an iterator into a collection
  181. */
  182. public function testIteratorIsWrapped(): void
  183. {
  184. $items = new ArrayObject([1, 2, 3]);
  185. $collection = new Collection($items);
  186. $this->assertEquals(iterator_to_array($items), iterator_to_array($collection));
  187. }
  188. /**
  189. * Test running a method over all elements in the collection
  190. */
  191. public function testEach(): void
  192. {
  193. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  194. $collection = new Collection($items);
  195. $results = [];
  196. $collection->each(function ($value, $key) use (&$results): void {
  197. $results[] = [$key => $value];
  198. });
  199. $this->assertSame([['a' => 1], ['b' => 2], ['c' => 3]], $results);
  200. }
  201. public function filterProvider(): array
  202. {
  203. $items = [1, 2, 0, 3, false, 4, null, 5, ''];
  204. return [
  205. 'array' => [$items],
  206. 'iterator' => [$this->yieldItems($items)],
  207. ];
  208. }
  209. /**
  210. * Test filter() with no callback.
  211. *
  212. * @dataProvider filterProvider
  213. */
  214. public function testFilterNoCallback(iterable $items): void
  215. {
  216. $collection = new Collection($items);
  217. $result = $collection->filter()->toArray();
  218. $expected = [1, 2, 3, 4, 5];
  219. $this->assertSame($expected, array_values($result));
  220. }
  221. /**
  222. * Tests that it is possible to chain filter() as it returns a collection object
  223. */
  224. public function testFilterChaining(): void
  225. {
  226. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  227. $collection = new Collection($items);
  228. $filtered = $collection->filter(function ($value, $key, $iterator) {
  229. return $value > 2;
  230. });
  231. $this->assertInstanceOf(Collection::class, $filtered);
  232. $results = [];
  233. $filtered->each(function ($value, $key) use (&$results): void {
  234. $results[] = [$key => $value];
  235. });
  236. $this->assertSame([['c' => 3]], $results);
  237. }
  238. /**
  239. * Tests reject
  240. */
  241. public function testReject(): void
  242. {
  243. $collection = new Collection([]);
  244. $result = $collection->reject(function ($v) {
  245. return false;
  246. });
  247. $this->assertSame([], iterator_to_array($result));
  248. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  249. $collection = new Collection($items);
  250. $result = $collection->reject(function ($v, $k, $items) use ($collection) {
  251. $this->assertSame($collection->getInnerIterator(), $items);
  252. return $v > 2;
  253. });
  254. $this->assertEquals(['a' => 1, 'b' => 2], iterator_to_array($result));
  255. $this->assertInstanceOf('Cake\Collection\Collection', $result);
  256. }
  257. /**
  258. * Tests every when the callback returns true for all elements
  259. */
  260. public function testEveryReturnTrue(): void
  261. {
  262. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  263. $collection = new Collection($items);
  264. $results = [];
  265. $this->assertTrue($collection->every(function ($value, $key) use (&$results) {
  266. $results[] = [$key => $value];
  267. return true;
  268. }));
  269. $this->assertSame([['a' => 1], ['b' => 2], ['c' => 3]], $results);
  270. }
  271. /**
  272. * Tests every when the callback returns false for one of the elements
  273. */
  274. public function testEveryReturnFalse(): void
  275. {
  276. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  277. $collection = new Collection($items);
  278. $results = [];
  279. $this->assertFalse($collection->every(function ($value, $key) use (&$results) {
  280. $results[] = [$key => $value];
  281. return $key !== 'b';
  282. }));
  283. $this->assertSame([['a' => 1], ['b' => 2]], $results);
  284. }
  285. /**
  286. * Tests some() when one of the calls return true
  287. */
  288. public function testSomeReturnTrue(): void
  289. {
  290. $collection = new Collection([]);
  291. $result = $collection->some(function ($v) {
  292. return true;
  293. });
  294. $this->assertFalse($result);
  295. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  296. $collection = new Collection($items);
  297. $results = [];
  298. $this->assertTrue($collection->some(function ($value, $key) use (&$results) {
  299. $results[] = [$key => $value];
  300. return $key === 'b';
  301. }));
  302. $this->assertSame([['a' => 1], ['b' => 2]], $results);
  303. }
  304. /**
  305. * Tests some() when none of the calls return true
  306. */
  307. public function testSomeReturnFalse(): void
  308. {
  309. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  310. $collection = new Collection($items);
  311. $results = [];
  312. $this->assertFalse($collection->some(function ($value, $key) use (&$results) {
  313. $results[] = [$key => $value];
  314. return false;
  315. }));
  316. $this->assertSame([['a' => 1], ['b' => 2], ['c' => 3]], $results);
  317. }
  318. /**
  319. * Tests contains
  320. */
  321. public function testContains(): void
  322. {
  323. $collection = new Collection([]);
  324. $this->assertFalse($collection->contains('a'));
  325. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  326. $collection = new Collection($items);
  327. $this->assertTrue($collection->contains(2));
  328. $this->assertTrue($collection->contains(1));
  329. $this->assertFalse($collection->contains(10));
  330. $this->assertFalse($collection->contains('2'));
  331. }
  332. /**
  333. * Provider for some simple tests
  334. *
  335. * @return array
  336. */
  337. public function simpleProvider(): array
  338. {
  339. $items = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4];
  340. return [
  341. 'array' => [$items],
  342. 'iterator' => [$this->yieldItems($items)],
  343. ];
  344. }
  345. /**
  346. * Tests map
  347. *
  348. * @dataProvider simpleProvider
  349. */
  350. public function testMap(iterable $items): void
  351. {
  352. $collection = new Collection($items);
  353. $map = $collection->map(function ($v, $k, $it) use ($collection) {
  354. $this->assertSame($collection->getInnerIterator(), $it);
  355. return $v * $v;
  356. });
  357. $this->assertInstanceOf('Cake\Collection\Iterator\ReplaceIterator', $map);
  358. $this->assertEquals(['a' => 1, 'b' => 4, 'c' => 9, 'd' => 16], iterator_to_array($map));
  359. }
  360. /**
  361. * Tests reduce with initial value
  362. *
  363. * @dataProvider simpleProvider
  364. */
  365. public function testReduceWithInitialValue(iterable $items): void
  366. {
  367. $collection = new Collection($items);
  368. $this->assertSame(20, $collection->reduce(function ($reduction, $value, $key) {
  369. return $value + $reduction;
  370. }, 10));
  371. }
  372. /**
  373. * Tests reduce without initial value
  374. *
  375. * @dataProvider simpleProvider
  376. */
  377. public function testReduceWithoutInitialValue(iterable $items): void
  378. {
  379. $collection = new Collection($items);
  380. $this->assertSame(10, $collection->reduce(function ($reduction, $value, $key) {
  381. return $value + $reduction;
  382. }));
  383. }
  384. /**
  385. * Provider for some extract tests
  386. *
  387. * @return array
  388. */
  389. public function extractProvider(): array
  390. {
  391. $items = [['a' => ['b' => ['c' => 1]]], 2];
  392. return [
  393. 'array' => [$items],
  394. 'iterator' => [$this->yieldItems($items)],
  395. ];
  396. }
  397. /**
  398. * Tests extract
  399. *
  400. * @dataProvider extractProvider
  401. */
  402. public function testExtract(iterable $items): void
  403. {
  404. $collection = new Collection($items);
  405. $map = $collection->extract('a.b.c');
  406. $this->assertInstanceOf('Cake\Collection\Iterator\ExtractIterator', $map);
  407. $this->assertEquals([1, null], iterator_to_array($map));
  408. }
  409. /**
  410. * Provider for some sort tests
  411. *
  412. * @return array
  413. */
  414. public function sortProvider(): array
  415. {
  416. $items = [
  417. ['a' => ['b' => ['c' => 4]]],
  418. ['a' => ['b' => ['c' => 10]]],
  419. ['a' => ['b' => ['c' => 6]]],
  420. ];
  421. return [
  422. 'array' => [$items],
  423. 'iterator' => [$this->yieldItems($items)],
  424. ];
  425. }
  426. /**
  427. * Tests sort
  428. *
  429. * @dataProvider sortProvider
  430. */
  431. public function testSortString(iterable $items): void
  432. {
  433. $collection = new Collection($items);
  434. $map = $collection->sortBy('a.b.c');
  435. $this->assertInstanceOf('Cake\Collection\Collection', $map);
  436. $expected = [
  437. ['a' => ['b' => ['c' => 10]]],
  438. ['a' => ['b' => ['c' => 6]]],
  439. ['a' => ['b' => ['c' => 4]]],
  440. ];
  441. $this->assertEquals($expected, $map->toList());
  442. }
  443. /**
  444. * Tests max
  445. *
  446. * @dataProvider sortProvider
  447. */
  448. public function testMax(iterable $items): void
  449. {
  450. $collection = new Collection($items);
  451. $this->assertEquals(['a' => ['b' => ['c' => 10]]], $collection->max('a.b.c'));
  452. }
  453. /**
  454. * Tests max
  455. *
  456. * @dataProvider sortProvider
  457. */
  458. public function testMaxCallback(iterable $items): void
  459. {
  460. $collection = new Collection($items);
  461. $callback = function ($e) {
  462. return $e['a']['b']['c'] * -1;
  463. };
  464. $this->assertEquals(['a' => ['b' => ['c' => 4]]], $collection->max($callback));
  465. }
  466. /**
  467. * Tests max
  468. *
  469. * @dataProvider sortProvider
  470. */
  471. public function testMaxCallable(iterable $items): void
  472. {
  473. $collection = new Collection($items);
  474. $this->assertEquals(['a' => ['b' => ['c' => 4]]], $collection->max(function ($e) {
  475. return $e['a']['b']['c'] * -1;
  476. }));
  477. }
  478. /**
  479. * Test max with a collection of Entities
  480. */
  481. public function testMaxWithEntities(): void
  482. {
  483. $collection = new Collection([
  484. new Entity(['id' => 1, 'count' => 18]),
  485. new Entity(['id' => 2, 'count' => 9]),
  486. new Entity(['id' => 3, 'count' => 42]),
  487. new Entity(['id' => 4, 'count' => 4]),
  488. new Entity(['id' => 5, 'count' => 22]),
  489. ]);
  490. $expected = new Entity(['id' => 3, 'count' => 42]);
  491. $this->assertEquals($expected, $collection->max('count'));
  492. }
  493. /**
  494. * Tests min
  495. *
  496. * @dataProvider sortProvider
  497. */
  498. public function testMin(iterable $items): void
  499. {
  500. $collection = new Collection($items);
  501. $this->assertEquals(['a' => ['b' => ['c' => 4]]], $collection->min('a.b.c'));
  502. }
  503. /**
  504. * Test min with a collection of Entities
  505. */
  506. public function testMinWithEntities(): void
  507. {
  508. $collection = new Collection([
  509. new Entity(['id' => 1, 'count' => 18]),
  510. new Entity(['id' => 2, 'count' => 9]),
  511. new Entity(['id' => 3, 'count' => 42]),
  512. new Entity(['id' => 4, 'count' => 4]),
  513. new Entity(['id' => 5, 'count' => 22]),
  514. ]);
  515. $expected = new Entity(['id' => 4, 'count' => 4]);
  516. $this->assertEquals($expected, $collection->min('count'));
  517. }
  518. /**
  519. * Provider for some groupBy tests
  520. *
  521. * @return array
  522. */
  523. public function groupByProvider(): array
  524. {
  525. $items = [
  526. ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  527. ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  528. ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  529. ];
  530. return [
  531. 'array' => [$items],
  532. 'iterator' => [$this->yieldItems($items)],
  533. ];
  534. }
  535. /**
  536. * Tests groupBy
  537. *
  538. * @dataProvider groupByProvider
  539. */
  540. public function testGroupBy(iterable $items): void
  541. {
  542. $collection = new Collection($items);
  543. $grouped = $collection->groupBy('parent_id');
  544. $expected = [
  545. 10 => [
  546. ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  547. ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  548. ],
  549. 11 => [
  550. ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  551. ],
  552. ];
  553. $this->assertEquals($expected, iterator_to_array($grouped));
  554. $this->assertInstanceOf('Cake\Collection\Collection', $grouped);
  555. }
  556. /**
  557. * Tests groupBy
  558. *
  559. * @dataProvider groupByProvider
  560. */
  561. public function testGroupByCallback(iterable $items): void
  562. {
  563. $collection = new Collection($items);
  564. $expected = [
  565. 10 => [
  566. ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  567. ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  568. ],
  569. 11 => [
  570. ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  571. ],
  572. ];
  573. $grouped = $collection->groupBy(function ($element) {
  574. return $element['parent_id'];
  575. });
  576. $this->assertEquals($expected, iterator_to_array($grouped));
  577. }
  578. /**
  579. * Tests grouping by a deep key
  580. */
  581. public function testGroupByDeepKey(): void
  582. {
  583. $items = [
  584. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  585. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  586. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  587. ];
  588. $collection = new Collection($items);
  589. $grouped = $collection->groupBy('thing.parent_id');
  590. $expected = [
  591. 10 => [
  592. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  593. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  594. ],
  595. 11 => [
  596. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  597. ],
  598. ];
  599. $this->assertEquals($expected, iterator_to_array($grouped));
  600. }
  601. /**
  602. * Tests passing an invalid path to groupBy.
  603. */
  604. public function testGroupByInvalidPath(): void
  605. {
  606. $items = [
  607. ['id' => 1, 'name' => 'foo'],
  608. ['id' => 2, 'name' => 'bar'],
  609. ['id' => 3, 'name' => 'baz'],
  610. ];
  611. $collection = new Collection($items);
  612. $this->expectException(InvalidArgumentException::class);
  613. $this->expectExceptionMessage('Cannot group by path that does not exist or contains a null value.');
  614. $collection->groupBy('missing');
  615. }
  616. /**
  617. * Provider for some indexBy tests
  618. *
  619. * @return array
  620. */
  621. public function indexByProvider(): array
  622. {
  623. $items = [
  624. ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  625. ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  626. ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  627. ];
  628. return [
  629. 'array' => [$items],
  630. 'iterator' => [$this->yieldItems($items)],
  631. ];
  632. }
  633. /**
  634. * Tests indexBy
  635. *
  636. * @dataProvider indexByProvider
  637. */
  638. public function testIndexBy(iterable $items): void
  639. {
  640. $collection = new Collection($items);
  641. $grouped = $collection->indexBy('id');
  642. $expected = [
  643. 1 => ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  644. 3 => ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  645. 2 => ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  646. ];
  647. $this->assertEquals($expected, iterator_to_array($grouped));
  648. $this->assertInstanceOf('Cake\Collection\Collection', $grouped);
  649. }
  650. /**
  651. * Tests indexBy
  652. *
  653. * @dataProvider indexByProvider
  654. */
  655. public function testIndexByCallback(iterable $items): void
  656. {
  657. $collection = new Collection($items);
  658. $grouped = $collection->indexBy(function ($element) {
  659. return $element['id'];
  660. });
  661. $expected = [
  662. 1 => ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  663. 3 => ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  664. 2 => ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  665. ];
  666. $this->assertEquals($expected, iterator_to_array($grouped));
  667. }
  668. /**
  669. * Tests indexBy with a deep property
  670. */
  671. public function testIndexByDeep(): void
  672. {
  673. $items = [
  674. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  675. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  676. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  677. ];
  678. $collection = new Collection($items);
  679. $grouped = $collection->indexBy('thing.parent_id');
  680. $expected = [
  681. 10 => ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  682. 11 => ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  683. ];
  684. $this->assertEquals($expected, iterator_to_array($grouped));
  685. }
  686. /**
  687. * Tests passing an invalid path to indexBy.
  688. */
  689. public function testIndexByInvalidPath(): void
  690. {
  691. $items = [
  692. ['id' => 1, 'name' => 'foo'],
  693. ['id' => 2, 'name' => 'bar'],
  694. ['id' => 3, 'name' => 'baz'],
  695. ];
  696. $collection = new Collection($items);
  697. $this->expectException(InvalidArgumentException::class);
  698. $this->expectExceptionMessage('Cannot index by path that does not exist or contains a null value');
  699. $collection->indexBy('missing');
  700. }
  701. /**
  702. * Tests passing an invalid path to indexBy.
  703. */
  704. public function testIndexByInvalidPathCallback(): void
  705. {
  706. $items = [
  707. ['id' => 1, 'name' => 'foo'],
  708. ['id' => 2, 'name' => 'bar'],
  709. ['id' => 3, 'name' => 'baz'],
  710. ];
  711. $collection = new Collection($items);
  712. $this->expectException(InvalidArgumentException::class);
  713. $this->expectExceptionMessage('Cannot index by path that does not exist or contains a null value');
  714. $collection->indexBy(function ($e) {
  715. return null;
  716. });
  717. }
  718. /**
  719. * Tests countBy
  720. *
  721. * @dataProvider groupByProvider
  722. */
  723. public function testCountBy(iterable $items): void
  724. {
  725. $collection = new Collection($items);
  726. $grouped = $collection->countBy('parent_id');
  727. $expected = [
  728. 10 => 2,
  729. 11 => 1,
  730. ];
  731. $result = iterator_to_array($grouped);
  732. $this->assertInstanceOf('Cake\Collection\Collection', $grouped);
  733. $this->assertEquals($expected, $result);
  734. }
  735. /**
  736. * Tests countBy
  737. *
  738. * @dataProvider groupByProvider
  739. */
  740. public function testCountByCallback(iterable $items): void
  741. {
  742. $expected = [
  743. 10 => 2,
  744. 11 => 1,
  745. ];
  746. $collection = new Collection($items);
  747. $grouped = $collection->countBy(function ($element) {
  748. return $element['parent_id'];
  749. });
  750. $this->assertEquals($expected, iterator_to_array($grouped));
  751. }
  752. /**
  753. * Tests shuffle
  754. *
  755. * @dataProvider simpleProvider
  756. */
  757. public function testShuffle(iterable $data): void
  758. {
  759. $collection = (new Collection($data))->shuffle();
  760. $result = $collection->toArray();
  761. $this->assertCount(4, $result);
  762. $this->assertContains(1, $result);
  763. $this->assertContains(2, $result);
  764. $this->assertContains(3, $result);
  765. $this->assertContains(4, $result);
  766. }
  767. /**
  768. * Tests shuffle with duplicate keys.
  769. */
  770. public function testShuffleDuplicateKeys(): void
  771. {
  772. $collection = (new Collection(['a' => 1]))->append(['a' => 2])->shuffle();
  773. $result = $collection->toArray();
  774. $this->assertCount(2, $result);
  775. $this->assertEquals([0, 1], array_keys($result));
  776. $this->assertContainsEquals(1, $result);
  777. $this->assertContainsEquals(2, $result);
  778. }
  779. /**
  780. * Tests sample
  781. *
  782. * @dataProvider simpleProvider
  783. */
  784. public function testSample(iterable $data): void
  785. {
  786. $result = (new Collection($data))->sample(2)->toArray();
  787. $this->assertCount(2, $result);
  788. foreach ($result as $number) {
  789. $this->assertContains($number, [1, 2, 3, 4]);
  790. }
  791. }
  792. /**
  793. * Tests the sample() method with a traversable non-iterator
  794. */
  795. public function testSampleWithTraversableNonIterator(): void
  796. {
  797. $collection = new Collection($this->datePeriod('2017-01-01', '2017-01-07'));
  798. $result = $collection->sample(3)->toList();
  799. $list = [
  800. '2017-01-01',
  801. '2017-01-02',
  802. '2017-01-03',
  803. '2017-01-04',
  804. '2017-01-05',
  805. '2017-01-06',
  806. ];
  807. $this->assertCount(3, $result);
  808. foreach ($result as $date) {
  809. $this->assertContains($date->format('Y-m-d'), $list);
  810. }
  811. }
  812. /**
  813. * Test toArray method
  814. */
  815. public function testToArray(): void
  816. {
  817. $data = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4];
  818. $collection = new Collection($data);
  819. $this->assertEquals($data, $collection->toArray());
  820. }
  821. /**
  822. * Test toList method
  823. *
  824. * @dataProvider simpleProvider
  825. */
  826. public function testToList(iterable $data): void
  827. {
  828. $collection = new Collection($data);
  829. $this->assertEquals([1, 2, 3, 4], $collection->toList());
  830. }
  831. /**
  832. * Test JSON encoding
  833. */
  834. public function testToJson(): void
  835. {
  836. $data = [1, 2, 3, 4];
  837. $collection = new Collection($data);
  838. $this->assertEquals(json_encode($data), json_encode($collection));
  839. }
  840. /**
  841. * Tests that Count returns the number of elements
  842. *
  843. * @dataProvider simpleProvider
  844. */
  845. public function testCollectionCount(iterable $list): void
  846. {
  847. $list = (new Collection($list))->buffered();
  848. $collection = new Collection($list);
  849. $this->assertSame(8, $collection->append($list)->count());
  850. }
  851. /**
  852. * Tests that countKeys returns the number of unique keys
  853. *
  854. * @dataProvider simpleProvider
  855. */
  856. public function testCollectionCountKeys(iterable $list): void
  857. {
  858. $list = (new Collection($list))->buffered();
  859. $collection = new Collection($list);
  860. $this->assertSame(4, $collection->append($list)->countKeys());
  861. }
  862. /**
  863. * Tests take method
  864. */
  865. public function testTake(): void
  866. {
  867. $data = [1, 2, 3, 4];
  868. $collection = new Collection($data);
  869. $taken = $collection->take(2);
  870. $this->assertEquals([1, 2], $taken->toArray());
  871. $taken = $collection->take(3);
  872. $this->assertEquals([1, 2, 3], $taken->toArray());
  873. $taken = $collection->take(500);
  874. $this->assertEquals([1, 2, 3, 4], $taken->toArray());
  875. $taken = $collection->take(1);
  876. $this->assertEquals([1], $taken->toArray());
  877. $taken = $collection->take();
  878. $this->assertEquals([1], $taken->toArray());
  879. $taken = $collection->take(2, 2);
  880. $this->assertEquals([2 => 3, 3 => 4], $taken->toArray());
  881. }
  882. /**
  883. * Tests the take() method with a traversable non-iterator
  884. */
  885. public function testTakeWithTraversableNonIterator(): void
  886. {
  887. $collection = new Collection($this->datePeriod('2017-01-01', '2017-01-07'));
  888. $result = $collection->take(3, 1)->toList();
  889. $expected = [
  890. new DateTime('2017-01-02'),
  891. new DateTime('2017-01-03'),
  892. new DateTime('2017-01-04'),
  893. ];
  894. $this->assertEquals($expected, $result);
  895. }
  896. /**
  897. * Tests match
  898. */
  899. public function testMatch(): void
  900. {
  901. $items = [
  902. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  903. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  904. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  905. ];
  906. $collection = new Collection($items);
  907. $matched = $collection->match(['thing.parent_id' => 10, 'name' => 'baz']);
  908. $this->assertEquals([2 => $items[2]], $matched->toArray());
  909. $matched = $collection->match(['thing.parent_id' => 10]);
  910. $this->assertEquals(
  911. [0 => $items[0], 2 => $items[2]],
  912. $matched->toArray()
  913. );
  914. $matched = $collection->match(['thing.parent_id' => 500]);
  915. $this->assertEquals([], $matched->toArray());
  916. $matched = $collection->match(['parent_id' => 10, 'name' => 'baz']);
  917. $this->assertEquals([], $matched->toArray());
  918. }
  919. /**
  920. * Tests firstMatch
  921. */
  922. public function testFirstMatch(): void
  923. {
  924. $items = [
  925. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  926. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  927. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  928. ];
  929. $collection = new Collection($items);
  930. $matched = $collection->firstMatch(['thing.parent_id' => 10]);
  931. $this->assertEquals(
  932. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  933. $matched
  934. );
  935. $matched = $collection->firstMatch(['thing.parent_id' => 10, 'name' => 'baz']);
  936. $this->assertEquals(
  937. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  938. $matched
  939. );
  940. }
  941. /**
  942. * Tests the append method
  943. */
  944. public function testAppend(): void
  945. {
  946. $collection = new Collection([1, 2, 3]);
  947. $combined = $collection->append([4, 5, 6]);
  948. $this->assertEquals([1, 2, 3, 4, 5, 6], $combined->toArray(false));
  949. $collection = new Collection(['a' => 1, 'b' => 2]);
  950. $combined = $collection->append(['c' => 3, 'a' => 4]);
  951. $this->assertEquals(['a' => 4, 'b' => 2, 'c' => 3], $combined->toArray());
  952. }
  953. /**
  954. * Tests the appendItem method
  955. */
  956. public function testAppendItem(): void
  957. {
  958. $collection = new Collection([1, 2, 3]);
  959. $combined = $collection->appendItem(4);
  960. $this->assertEquals([1, 2, 3, 4], $combined->toArray(false));
  961. $collection = new Collection(['a' => 1, 'b' => 2]);
  962. $combined = $collection->appendItem(3, 'c');
  963. $combined = $combined->appendItem(4, 'a');
  964. $this->assertEquals(['a' => 4, 'b' => 2, 'c' => 3], $combined->toArray());
  965. }
  966. /**
  967. * Tests the prepend method
  968. */
  969. public function testPrepend(): void
  970. {
  971. $collection = new Collection([1, 2, 3]);
  972. $combined = $collection->prepend(['a']);
  973. $this->assertEquals(['a', 1, 2, 3], $combined->toList());
  974. $collection = new Collection(['c' => 3, 'd' => 4]);
  975. $combined = $collection->prepend(['a' => 1, 'b' => 2]);
  976. $this->assertEquals(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4], $combined->toArray());
  977. }
  978. /**
  979. * Tests prependItem method
  980. */
  981. public function testPrependItem(): void
  982. {
  983. $collection = new Collection([1, 2, 3]);
  984. $combined = $collection->prependItem('a');
  985. $this->assertEquals(['a', 1, 2, 3], $combined->toList());
  986. $collection = new Collection(['c' => 3, 'd' => 4]);
  987. $combined = $collection->prependItem(2, 'b');
  988. $combined = $combined->prependItem(1, 'a');
  989. $this->assertEquals(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4], $combined->toArray());
  990. }
  991. /**
  992. * Tests prependItem method
  993. */
  994. public function testPrependItemPreserveKeys(): void
  995. {
  996. $collection = new Collection([1, 2, 3]);
  997. $combined = $collection->prependItem('a');
  998. $this->assertEquals(['a', 1, 2, 3], $combined->toList());
  999. $collection = new Collection(['c' => 3, 'd' => 4]);
  1000. $combined = $collection->prependItem(2, 'b');
  1001. $combined = $combined->prependItem(1, 'a');
  1002. $this->assertEquals(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4], $combined->toArray());
  1003. }
  1004. /**
  1005. * Tests the append method with iterator
  1006. */
  1007. public function testAppendIterator(): void
  1008. {
  1009. $collection = new Collection([1, 2, 3]);
  1010. $iterator = new ArrayIterator([4, 5, 6]);
  1011. $combined = $collection->append($iterator);
  1012. $this->assertEquals([1, 2, 3, 4, 5, 6], $combined->toList());
  1013. }
  1014. public function testAppendNotCollectionInstance(): void
  1015. {
  1016. $collection = new TestCollection([1, 2, 3]);
  1017. $combined = $collection->append([4, 5, 6]);
  1018. $this->assertEquals([1, 2, 3, 4, 5, 6], $combined->toList());
  1019. }
  1020. /**
  1021. * Tests that by calling compile internal iteration operations are not done
  1022. * more than once
  1023. */
  1024. public function testCompile(): void
  1025. {
  1026. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  1027. $collection = new Collection($items);
  1028. $results = [];
  1029. $compiled = $collection
  1030. ->map(function ($value, $key) use (&$results) {
  1031. $results[] = [$key => $value];
  1032. return $value + 3;
  1033. })
  1034. ->compile();
  1035. $this->assertSame(['a' => 4, 'b' => 5, 'c' => 6], $compiled->toArray());
  1036. $this->assertSame(['a' => 4, 'b' => 5, 'c' => 6], $compiled->toArray());
  1037. $this->assertSame([['a' => 1], ['b' => 2], ['c' => 3]], $results);
  1038. }
  1039. /**
  1040. * Tests converting a non rewindable iterator into a rewindable one using
  1041. * the buffered method.
  1042. */
  1043. public function testBuffered(): void
  1044. {
  1045. $items = new NoRewindIterator(new ArrayIterator(['a' => 4, 'b' => 5, 'c' => 6]));
  1046. $buffered = (new Collection($items))->buffered();
  1047. $this->assertEquals(['a' => 4, 'b' => 5, 'c' => 6], $buffered->toArray());
  1048. $this->assertEquals(['a' => 4, 'b' => 5, 'c' => 6], $buffered->toArray());
  1049. }
  1050. public function testBufferedIterator(): void
  1051. {
  1052. $data = [
  1053. ['myField' => '1'],
  1054. ['myField' => '2'],
  1055. ['myField' => '3'],
  1056. ];
  1057. $buffered = (new Collection($data))->buffered();
  1058. // Check going forwards
  1059. $this->assertNotEmpty($buffered->firstMatch(['myField' => '1']));
  1060. $this->assertNotEmpty($buffered->firstMatch(['myField' => '2']));
  1061. $this->assertNotEmpty($buffered->firstMatch(['myField' => '3']));
  1062. // And backwards.
  1063. $this->assertNotEmpty($buffered->firstMatch(['myField' => '3']));
  1064. $this->assertNotEmpty($buffered->firstMatch(['myField' => '2']));
  1065. $this->assertNotEmpty($buffered->firstMatch(['myField' => '1']));
  1066. }
  1067. /**
  1068. * Tests the combine method
  1069. */
  1070. public function testCombine(): void
  1071. {
  1072. $items = [
  1073. ['id' => 1, 'name' => 'foo', 'parent' => 'a'],
  1074. ['id' => 2, 'name' => 'bar', 'parent' => 'b'],
  1075. ['id' => 3, 'name' => 'baz', 'parent' => 'a'],
  1076. ];
  1077. $collection = (new Collection($items))->combine('id', 'name');
  1078. $expected = [1 => 'foo', 2 => 'bar', 3 => 'baz'];
  1079. $this->assertEquals($expected, $collection->toArray());
  1080. $expected = ['foo' => 1, 'bar' => 2, 'baz' => 3];
  1081. $collection = (new Collection($items))->combine('name', 'id');
  1082. $this->assertEquals($expected, $collection->toArray());
  1083. $collection = (new Collection($items))->combine('id', 'name', 'parent');
  1084. $expected = ['a' => [1 => 'foo', 3 => 'baz'], 'b' => [2 => 'bar']];
  1085. $this->assertEquals($expected, $collection->toArray());
  1086. $expected = [
  1087. '0-1' => ['foo-0-1' => '0-1-foo'],
  1088. '1-2' => ['bar-1-2' => '1-2-bar'],
  1089. '2-3' => ['baz-2-3' => '2-3-baz'],
  1090. ];
  1091. $collection = (new Collection($items))->combine(
  1092. function ($value, $key) {
  1093. return $value['name'] . '-' . $key;
  1094. },
  1095. function ($value, $key) {
  1096. return $key . '-' . $value['name'];
  1097. },
  1098. function ($value, $key) {
  1099. return $key . '-' . $value['id'];
  1100. }
  1101. );
  1102. $this->assertEquals($expected, $collection->toArray());
  1103. $collection = (new Collection($items))->combine('id', 'crazy');
  1104. $this->assertEquals([1 => null, 2 => null, 3 => null], $collection->toArray());
  1105. }
  1106. /**
  1107. * Tests the nest method with only one level
  1108. */
  1109. public function testNest(): void
  1110. {
  1111. $items = [
  1112. ['id' => 1, 'parent_id' => null],
  1113. ['id' => 2, 'parent_id' => 1],
  1114. ['id' => 3, 'parent_id' => 1],
  1115. ['id' => 4, 'parent_id' => 1],
  1116. ['id' => 5, 'parent_id' => 6],
  1117. ['id' => 6, 'parent_id' => null],
  1118. ['id' => 7, 'parent_id' => 1],
  1119. ['id' => 8, 'parent_id' => 6],
  1120. ['id' => 9, 'parent_id' => 6],
  1121. ['id' => 10, 'parent_id' => 6],
  1122. ];
  1123. $collection = (new Collection($items))->nest('id', 'parent_id');
  1124. $expected = [
  1125. [
  1126. 'id' => 1,
  1127. 'parent_id' => null,
  1128. 'children' => [
  1129. ['id' => 2, 'parent_id' => 1, 'children' => []],
  1130. ['id' => 3, 'parent_id' => 1, 'children' => []],
  1131. ['id' => 4, 'parent_id' => 1, 'children' => []],
  1132. ['id' => 7, 'parent_id' => 1, 'children' => []],
  1133. ],
  1134. ],
  1135. [
  1136. 'id' => 6,
  1137. 'parent_id' => null,
  1138. 'children' => [
  1139. ['id' => 5, 'parent_id' => 6, 'children' => []],
  1140. ['id' => 8, 'parent_id' => 6, 'children' => []],
  1141. ['id' => 9, 'parent_id' => 6, 'children' => []],
  1142. ['id' => 10, 'parent_id' => 6, 'children' => []],
  1143. ],
  1144. ],
  1145. ];
  1146. $this->assertEquals($expected, $collection->toArray());
  1147. }
  1148. /**
  1149. * Tests the nest method with alternate nesting key
  1150. */
  1151. public function testNestAlternateNestingKey(): void
  1152. {
  1153. $items = [
  1154. ['id' => 1, 'parent_id' => null],
  1155. ['id' => 2, 'parent_id' => 1],
  1156. ['id' => 3, 'parent_id' => 1],
  1157. ['id' => 4, 'parent_id' => 1],
  1158. ['id' => 5, 'parent_id' => 6],
  1159. ['id' => 6, 'parent_id' => null],
  1160. ['id' => 7, 'parent_id' => 1],
  1161. ['id' => 8, 'parent_id' => 6],
  1162. ['id' => 9, 'parent_id' => 6],
  1163. ['id' => 10, 'parent_id' => 6],
  1164. ];
  1165. $collection = (new Collection($items))->nest('id', 'parent_id', 'nodes');
  1166. $expected = [
  1167. [
  1168. 'id' => 1,
  1169. 'parent_id' => null,
  1170. 'nodes' => [
  1171. ['id' => 2, 'parent_id' => 1, 'nodes' => []],
  1172. ['id' => 3, 'parent_id' => 1, 'nodes' => []],
  1173. ['id' => 4, 'parent_id' => 1, 'nodes' => []],
  1174. ['id' => 7, 'parent_id' => 1, 'nodes' => []],
  1175. ],
  1176. ],
  1177. [
  1178. 'id' => 6,
  1179. 'parent_id' => null,
  1180. 'nodes' => [
  1181. ['id' => 5, 'parent_id' => 6, 'nodes' => []],
  1182. ['id' => 8, 'parent_id' => 6, 'nodes' => []],
  1183. ['id' => 9, 'parent_id' => 6, 'nodes' => []],
  1184. ['id' => 10, 'parent_id' => 6, 'nodes' => []],
  1185. ],
  1186. ],
  1187. ];
  1188. $this->assertEquals($expected, $collection->toArray());
  1189. }
  1190. /**
  1191. * Tests the nest method with more than one level
  1192. */
  1193. public function testNestMultiLevel(): void
  1194. {
  1195. $items = [
  1196. ['id' => 1, 'parent_id' => null],
  1197. ['id' => 2, 'parent_id' => 1],
  1198. ['id' => 3, 'parent_id' => 2],
  1199. ['id' => 4, 'parent_id' => 2],
  1200. ['id' => 5, 'parent_id' => 3],
  1201. ['id' => 6, 'parent_id' => null],
  1202. ['id' => 7, 'parent_id' => 3],
  1203. ['id' => 8, 'parent_id' => 4],
  1204. ['id' => 9, 'parent_id' => 6],
  1205. ['id' => 10, 'parent_id' => 6],
  1206. ];
  1207. $collection = (new Collection($items))->nest('id', 'parent_id', 'nodes');
  1208. $expected = [
  1209. [
  1210. 'id' => 1,
  1211. 'parent_id' => null,
  1212. 'nodes' => [
  1213. [
  1214. 'id' => 2,
  1215. 'parent_id' => 1,
  1216. 'nodes' => [
  1217. [
  1218. 'id' => 3,
  1219. 'parent_id' => 2,
  1220. 'nodes' => [
  1221. ['id' => 5, 'parent_id' => 3, 'nodes' => []],
  1222. ['id' => 7, 'parent_id' => 3, 'nodes' => []],
  1223. ],
  1224. ],
  1225. [
  1226. 'id' => 4,
  1227. 'parent_id' => 2,
  1228. 'nodes' => [
  1229. ['id' => 8, 'parent_id' => 4, 'nodes' => []],
  1230. ],
  1231. ],
  1232. ],
  1233. ],
  1234. ],
  1235. ],
  1236. [
  1237. 'id' => 6,
  1238. 'parent_id' => null,
  1239. 'nodes' => [
  1240. ['id' => 9, 'parent_id' => 6, 'nodes' => []],
  1241. ['id' => 10, 'parent_id' => 6, 'nodes' => []],
  1242. ],
  1243. ],
  1244. ];
  1245. $this->assertEquals($expected, $collection->toArray());
  1246. }
  1247. /**
  1248. * Tests the nest method with more than one level
  1249. */
  1250. public function testNestMultiLevelAlternateNestingKey(): void
  1251. {
  1252. $items = [
  1253. ['id' => 1, 'parent_id' => null],
  1254. ['id' => 2, 'parent_id' => 1],
  1255. ['id' => 3, 'parent_id' => 2],
  1256. ['id' => 4, 'parent_id' => 2],
  1257. ['id' => 5, 'parent_id' => 3],
  1258. ['id' => 6, 'parent_id' => null],
  1259. ['id' => 7, 'parent_id' => 3],
  1260. ['id' => 8, 'parent_id' => 4],
  1261. ['id' => 9, 'parent_id' => 6],
  1262. ['id' => 10, 'parent_id' => 6],
  1263. ];
  1264. $collection = (new Collection($items))->nest('id', 'parent_id');
  1265. $expected = [
  1266. [
  1267. 'id' => 1,
  1268. 'parent_id' => null,
  1269. 'children' => [
  1270. [
  1271. 'id' => 2,
  1272. 'parent_id' => 1,
  1273. 'children' => [
  1274. [
  1275. 'id' => 3,
  1276. 'parent_id' => 2,
  1277. 'children' => [
  1278. ['id' => 5, 'parent_id' => 3, 'children' => []],
  1279. ['id' => 7, 'parent_id' => 3, 'children' => []],
  1280. ],
  1281. ],
  1282. [
  1283. 'id' => 4,
  1284. 'parent_id' => 2,
  1285. 'children' => [
  1286. ['id' => 8, 'parent_id' => 4, 'children' => []],
  1287. ],
  1288. ],
  1289. ],
  1290. ],
  1291. ],
  1292. ],
  1293. [
  1294. 'id' => 6,
  1295. 'parent_id' => null,
  1296. 'children' => [
  1297. ['id' => 9, 'parent_id' => 6, 'children' => []],
  1298. ['id' => 10, 'parent_id' => 6, 'children' => []],
  1299. ],
  1300. ],
  1301. ];
  1302. $this->assertEquals($expected, $collection->toArray());
  1303. }
  1304. /**
  1305. * Tests the nest method with more than one level
  1306. */
  1307. public function testNestObjects(): void
  1308. {
  1309. $items = [
  1310. new ArrayObject(['id' => 1, 'parent_id' => null]),
  1311. new ArrayObject(['id' => 2, 'parent_id' => 1]),
  1312. new ArrayObject(['id' => 3, 'parent_id' => 2]),
  1313. new ArrayObject(['id' => 4, 'parent_id' => 2]),
  1314. new ArrayObject(['id' => 5, 'parent_id' => 3]),
  1315. new ArrayObject(['id' => 6, 'parent_id' => null]),
  1316. new ArrayObject(['id' => 7, 'parent_id' => 3]),
  1317. new ArrayObject(['id' => 8, 'parent_id' => 4]),
  1318. new ArrayObject(['id' => 9, 'parent_id' => 6]),
  1319. new ArrayObject(['id' => 10, 'parent_id' => 6]),
  1320. ];
  1321. $collection = (new Collection($items))->nest('id', 'parent_id');
  1322. $expected = [
  1323. new ArrayObject([
  1324. 'id' => 1,
  1325. 'parent_id' => null,
  1326. 'children' => [
  1327. new ArrayObject([
  1328. 'id' => 2,
  1329. 'parent_id' => 1,
  1330. 'children' => [
  1331. new ArrayObject([
  1332. 'id' => 3,
  1333. 'parent_id' => 2,
  1334. 'children' => [
  1335. new ArrayObject(['id' => 5, 'parent_id' => 3, 'children' => []]),
  1336. new ArrayObject(['id' => 7, 'parent_id' => 3, 'children' => []]),
  1337. ],
  1338. ]),
  1339. new ArrayObject([
  1340. 'id' => 4,
  1341. 'parent_id' => 2,
  1342. 'children' => [
  1343. new ArrayObject(['id' => 8, 'parent_id' => 4, 'children' => []]),
  1344. ],
  1345. ]),
  1346. ],
  1347. ]),
  1348. ],
  1349. ]),
  1350. new ArrayObject([
  1351. 'id' => 6,
  1352. 'parent_id' => null,
  1353. 'children' => [
  1354. new ArrayObject(['id' => 9, 'parent_id' => 6, 'children' => []]),
  1355. new ArrayObject(['id' => 10, 'parent_id' => 6, 'children' => []]),
  1356. ],
  1357. ]),
  1358. ];
  1359. $this->assertEquals($expected, $collection->toArray());
  1360. }
  1361. /**
  1362. * Tests the nest method with more than one level
  1363. */
  1364. public function testNestObjectsAlternateNestingKey(): void
  1365. {
  1366. $items = [
  1367. new ArrayObject(['id' => 1, 'parent_id' => null]),
  1368. new ArrayObject(['id' => 2, 'parent_id' => 1]),
  1369. new ArrayObject(['id' => 3, 'parent_id' => 2]),
  1370. new ArrayObject(['id' => 4, 'parent_id' => 2]),
  1371. new ArrayObject(['id' => 5, 'parent_id' => 3]),
  1372. new ArrayObject(['id' => 6, 'parent_id' => null]),
  1373. new ArrayObject(['id' => 7, 'parent_id' => 3]),
  1374. new ArrayObject(['id' => 8, 'parent_id' => 4]),
  1375. new ArrayObject(['id' => 9, 'parent_id' => 6]),
  1376. new ArrayObject(['id' => 10, 'parent_id' => 6]),
  1377. ];
  1378. $collection = (new Collection($items))->nest('id', 'parent_id', 'nodes');
  1379. $expected = [
  1380. new ArrayObject([
  1381. 'id' => 1,
  1382. 'parent_id' => null,
  1383. 'nodes' => [
  1384. new ArrayObject([
  1385. 'id' => 2,
  1386. 'parent_id' => 1,
  1387. 'nodes' => [
  1388. new ArrayObject([
  1389. 'id' => 3,
  1390. 'parent_id' => 2,
  1391. 'nodes' => [
  1392. new ArrayObject(['id' => 5, 'parent_id' => 3, 'nodes' => []]),
  1393. new ArrayObject(['id' => 7, 'parent_id' => 3, 'nodes' => []]),
  1394. ],
  1395. ]),
  1396. new ArrayObject([
  1397. 'id' => 4,
  1398. 'parent_id' => 2,
  1399. 'nodes' => [
  1400. new ArrayObject(['id' => 8, 'parent_id' => 4, 'nodes' => []]),
  1401. ],
  1402. ]),
  1403. ],
  1404. ]),
  1405. ],
  1406. ]),
  1407. new ArrayObject([
  1408. 'id' => 6,
  1409. 'parent_id' => null,
  1410. 'nodes' => [
  1411. new ArrayObject(['id' => 9, 'parent_id' => 6, 'nodes' => []]),
  1412. new ArrayObject(['id' => 10, 'parent_id' => 6, 'nodes' => []]),
  1413. ],
  1414. ]),
  1415. ];
  1416. $this->assertEquals($expected, $collection->toArray());
  1417. }
  1418. /**
  1419. * Tests insert
  1420. */
  1421. public function testInsert(): void
  1422. {
  1423. $items = [['a' => 1], ['b' => 2]];
  1424. $collection = new Collection($items);
  1425. $iterator = $collection->insert('c', [3, 4]);
  1426. $this->assertInstanceOf('Cake\Collection\Iterator\InsertIterator', $iterator);
  1427. $this->assertEquals(
  1428. [['a' => 1, 'c' => 3], ['b' => 2, 'c' => 4]],
  1429. iterator_to_array($iterator)
  1430. );
  1431. }
  1432. /**
  1433. * Provider for testing each of the directions for listNested
  1434. *
  1435. * @return array
  1436. */
  1437. public function nestedListProvider(): array
  1438. {
  1439. return [
  1440. ['desc', [1, 2, 3, 5, 7, 4, 8, 6, 9, 10]],
  1441. ['asc', [5, 7, 3, 8, 4, 2, 1, 9, 10, 6]],
  1442. ['leaves', [5, 7, 8, 9, 10]],
  1443. ];
  1444. }
  1445. /**
  1446. * Tests the listNested method with the default 'children' nesting key
  1447. *
  1448. * @dataProvider nestedListProvider
  1449. */
  1450. public function testListNested(string $dir, array $expected): void
  1451. {
  1452. $items = [
  1453. ['id' => 1, 'parent_id' => null],
  1454. ['id' => 2, 'parent_id' => 1],
  1455. ['id' => 3, 'parent_id' => 2],
  1456. ['id' => 4, 'parent_id' => 2],
  1457. ['id' => 5, 'parent_id' => 3],
  1458. ['id' => 6, 'parent_id' => null],
  1459. ['id' => 7, 'parent_id' => 3],
  1460. ['id' => 8, 'parent_id' => 4],
  1461. ['id' => 9, 'parent_id' => 6],
  1462. ['id' => 10, 'parent_id' => 6],
  1463. ];
  1464. $collection = (new Collection($items))->nest('id', 'parent_id')->listNested($dir);
  1465. $this->assertEquals($expected, $collection->extract('id')->toArray(false));
  1466. }
  1467. /**
  1468. * Tests the listNested spacer output.
  1469. */
  1470. public function testListNestedSpacer(): void
  1471. {
  1472. $items = [
  1473. ['id' => 1, 'parent_id' => null, 'name' => 'Birds'],
  1474. ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'],
  1475. ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'],
  1476. ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'],
  1477. ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish'],
  1478. ['id' => 6, 'parent_id' => null, 'name' => 'Fish'],
  1479. ];
  1480. $collection = (new Collection($items))->nest('id', 'parent_id')->listNested();
  1481. $expected = [
  1482. 'Birds',
  1483. '---Land Birds',
  1484. '---Eagle',
  1485. '---Seagull',
  1486. 'Fish',
  1487. '---Clown Fish',
  1488. ];
  1489. $this->assertSame($expected, $collection->printer('name', 'id', '---')->toList());
  1490. }
  1491. /**
  1492. * Tests using listNested with a different nesting key
  1493. */
  1494. public function testListNestedCustomKey(): void
  1495. {
  1496. $items = [
  1497. ['id' => 1, 'stuff' => [['id' => 2, 'stuff' => [['id' => 3]]]]],
  1498. ['id' => 4, 'stuff' => [['id' => 5]]],
  1499. ];
  1500. $collection = (new Collection($items))->listNested('desc', 'stuff');
  1501. $this->assertEquals(range(1, 5), $collection->extract('id')->toArray(false));
  1502. }
  1503. /**
  1504. * Tests flattening the collection using a custom callable function
  1505. */
  1506. public function testListNestedWithCallable(): void
  1507. {
  1508. $items = [
  1509. ['id' => 1, 'stuff' => [['id' => 2, 'stuff' => [['id' => 3]]]]],
  1510. ['id' => 4, 'stuff' => [['id' => 5]]],
  1511. ];
  1512. $collection = (new Collection($items))->listNested('desc', function ($item) {
  1513. return $item['stuff'] ?? [];
  1514. });
  1515. $this->assertEquals(range(1, 5), $collection->extract('id')->toArray(false));
  1516. }
  1517. /**
  1518. * Provider for sumOf tests
  1519. *
  1520. * @return array
  1521. */
  1522. public function sumOfProvider(): array
  1523. {
  1524. $items = [
  1525. ['invoice' => ['total' => 100]],
  1526. ['invoice' => ['total' => 200]],
  1527. ];
  1528. $floatItems = [
  1529. ['invoice' => ['total' => 100.0]],
  1530. ['invoice' => ['total' => 200.0]],
  1531. ];
  1532. return [
  1533. 'array' => [$items, 300],
  1534. 'iterator' => [$this->yieldItems($items), 300],
  1535. 'floatArray' => [$floatItems, 300.0],
  1536. 'floatIterator' => [$this->yieldItems($floatItems), 300.0],
  1537. ];
  1538. }
  1539. /**
  1540. * Tests the sumOf method
  1541. *
  1542. * @dataProvider sumOfProvider
  1543. * @param float|int $expected
  1544. */
  1545. public function testSumOf(iterable $items, $expected): void
  1546. {
  1547. $this->assertEquals($expected, (new Collection($items))->sumOf('invoice.total'));
  1548. }
  1549. /**
  1550. * Tests the sumOf method
  1551. *
  1552. * @dataProvider sumOfProvider
  1553. * @param float|int $expected
  1554. */
  1555. public function testSumOfCallable(iterable $items, $expected): void
  1556. {
  1557. $sum = (new Collection($items))->sumOf(function ($v) {
  1558. return $v['invoice']['total'];
  1559. });
  1560. $this->assertEquals($expected, $sum);
  1561. }
  1562. /**
  1563. * Tests the stopWhen method with a callable
  1564. *
  1565. * @dataProvider simpleProvider
  1566. */
  1567. public function testStopWhenCallable(iterable $items): void
  1568. {
  1569. $collection = (new Collection($items))->stopWhen(function ($v) {
  1570. return $v > 3;
  1571. });
  1572. $this->assertEquals(['a' => 1, 'b' => 2, 'c' => 3], $collection->toArray());
  1573. }
  1574. /**
  1575. * Tests the stopWhen method with a matching array
  1576. */
  1577. public function testStopWhenWithArray(): void
  1578. {
  1579. $items = [
  1580. ['foo' => 'bar'],
  1581. ['foo' => 'baz'],
  1582. ['foo' => 'foo'],
  1583. ];
  1584. $collection = (new Collection($items))->stopWhen(['foo' => 'baz']);
  1585. $this->assertEquals([['foo' => 'bar']], $collection->toArray());
  1586. }
  1587. /**
  1588. * Tests the unfold method
  1589. */
  1590. public function testUnfold(): void
  1591. {
  1592. $items = [
  1593. [1, 2, 3, 4],
  1594. [5, 6],
  1595. [7, 8],
  1596. ];
  1597. $collection = (new Collection($items))->unfold();
  1598. $this->assertEquals(range(1, 8), $collection->toArray(false));
  1599. $items = [
  1600. [1, 2],
  1601. new Collection([3, 4]),
  1602. ];
  1603. $collection = (new Collection($items))->unfold();
  1604. $this->assertEquals(range(1, 4), $collection->toArray(false));
  1605. }
  1606. /**
  1607. * Tests the unfold method with empty levels
  1608. */
  1609. public function testUnfoldEmptyLevels(): void
  1610. {
  1611. $items = [[], [1, 2], []];
  1612. $collection = (new Collection($items))->unfold();
  1613. $this->assertEquals(range(1, 2), $collection->toArray(false));
  1614. $items = [];
  1615. $collection = (new Collection($items))->unfold();
  1616. $this->assertEmpty($collection->toArray(false));
  1617. }
  1618. /**
  1619. * Tests the unfold when passing a callable
  1620. */
  1621. public function testUnfoldWithCallable(): void
  1622. {
  1623. $items = [1, 2, 3];
  1624. $collection = (new Collection($items))->unfold(function ($item) {
  1625. return range($item, $item * 2);
  1626. });
  1627. $expected = [1, 2, 2, 3, 4, 3, 4, 5, 6];
  1628. $this->assertEquals($expected, $collection->toArray(false));
  1629. }
  1630. /**
  1631. * Tests the through() method
  1632. */
  1633. public function testThrough(): void
  1634. {
  1635. $items = [1, 2, 3];
  1636. $collection = (new Collection($items))->through(function ($collection) {
  1637. return $collection->append($collection->toList());
  1638. });
  1639. $this->assertEquals([1, 2, 3, 1, 2, 3], $collection->toList());
  1640. }
  1641. /**
  1642. * Tests the through method when it returns an array
  1643. */
  1644. public function testThroughReturnArray(): void
  1645. {
  1646. $items = [1, 2, 3];
  1647. $collection = (new Collection($items))->through(function ($collection) {
  1648. $list = $collection->toList();
  1649. return array_merge($list, $list);
  1650. });
  1651. $this->assertEquals([1, 2, 3, 1, 2, 3], $collection->toList());
  1652. }
  1653. /**
  1654. * Tests that the sortBy method does not die when something that is not a
  1655. * collection is passed
  1656. */
  1657. public function testComplexSortBy(): void
  1658. {
  1659. $results = collection([3, 7])
  1660. ->unfold(function ($value) {
  1661. return [
  1662. ['sorting' => $value * 2],
  1663. ['sorting' => $value * 2],
  1664. ];
  1665. })
  1666. ->sortBy('sorting')
  1667. ->extract('sorting')
  1668. ->toList();
  1669. $this->assertEquals([14, 14, 6, 6], $results);
  1670. }
  1671. /**
  1672. * Tests __debugInfo() or debug() usage
  1673. */
  1674. public function testDebug(): void
  1675. {
  1676. $items = [1, 2, 3];
  1677. $collection = new Collection($items);
  1678. $result = $collection->__debugInfo();
  1679. $expected = [
  1680. 'count' => 3,
  1681. ];
  1682. $this->assertSame($expected, $result);
  1683. // Calling it again will rewind
  1684. $result = $collection->__debugInfo();
  1685. $expected = [
  1686. 'count' => 3,
  1687. ];
  1688. $this->assertSame($expected, $result);
  1689. // Make sure it also works with non rewindable iterators
  1690. $iterator = new NoRewindIterator(new ArrayIterator($items));
  1691. $collection = new Collection($iterator);
  1692. $result = $collection->__debugInfo();
  1693. $expected = [
  1694. 'count' => 3,
  1695. ];
  1696. $this->assertSame($expected, $result);
  1697. // Calling it again will in this case not rewind
  1698. $result = $collection->__debugInfo();
  1699. $expected = [
  1700. 'count' => 0,
  1701. ];
  1702. $this->assertSame($expected, $result);
  1703. }
  1704. /**
  1705. * Tests the isEmpty() method
  1706. */
  1707. public function testIsEmpty(): void
  1708. {
  1709. $collection = new Collection([1, 2, 3]);
  1710. $this->assertFalse($collection->isEmpty());
  1711. $collection = $collection->map(function () {
  1712. return null;
  1713. });
  1714. $this->assertFalse($collection->isEmpty());
  1715. $collection = $collection->filter();
  1716. $this->assertTrue($collection->isEmpty());
  1717. }
  1718. /**
  1719. * Tests the isEmpty() method does not consume data
  1720. * from buffered iterators.
  1721. */
  1722. public function testIsEmptyDoesNotConsume(): void
  1723. {
  1724. $array = new ArrayIterator([1, 2, 3]);
  1725. $inner = new BufferedIterator($array);
  1726. $collection = new Collection($inner);
  1727. $this->assertFalse($collection->isEmpty());
  1728. $this->assertCount(3, $collection->toArray());
  1729. }
  1730. /**
  1731. * Tests the zip() method
  1732. */
  1733. public function testZip(): void
  1734. {
  1735. $collection = new Collection([1, 2]);
  1736. $zipped = $collection->zip([3, 4]);
  1737. $this->assertEquals([[1, 3], [2, 4]], $zipped->toList());
  1738. $collection = new Collection([1, 2]);
  1739. $zipped = $collection->zip([3]);
  1740. $this->assertEquals([[1, 3]], $zipped->toList());
  1741. $collection = new Collection([1, 2]);
  1742. $zipped = $collection->zip([3, 4], [5, 6], [7, 8], [9, 10, 11]);
  1743. $this->assertEquals([
  1744. [1, 3, 5, 7, 9],
  1745. [2, 4, 6, 8, 10],
  1746. ], $zipped->toList());
  1747. }
  1748. /**
  1749. * Tests the zipWith() method
  1750. */
  1751. public function testZipWith(): void
  1752. {
  1753. $collection = new Collection([1, 2]);
  1754. $zipped = $collection->zipWith([3, 4], function ($a, $b) {
  1755. return $a * $b;
  1756. });
  1757. $this->assertEquals([3, 8], $zipped->toList());
  1758. $zipped = $collection->zipWith([3, 4], [5, 6, 7], function (...$args) {
  1759. return array_sum($args);
  1760. });
  1761. $this->assertEquals([9, 12], $zipped->toList());
  1762. }
  1763. /**
  1764. * Tests the skip() method
  1765. */
  1766. public function testSkip(): void
  1767. {
  1768. $collection = new Collection([1, 2, 3, 4, 5]);
  1769. $this->assertEquals([3, 4, 5], $collection->skip(2)->toList());
  1770. $this->assertEquals([1, 2, 3, 4, 5], $collection->skip(0)->toList());
  1771. $this->assertEquals([4, 5], $collection->skip(3)->toList());
  1772. $this->assertEquals([5], $collection->skip(4)->toList());
  1773. }
  1774. /**
  1775. * Test skip() with an overflow
  1776. */
  1777. public function testSkipOverflow(): void
  1778. {
  1779. $collection = new Collection([1, 2, 3]);
  1780. $this->assertEquals([], $collection->skip(3)->toArray());
  1781. $this->assertEquals([], $collection->skip(4)->toArray());
  1782. }
  1783. /**
  1784. * Tests the skip() method with a traversable non-iterator
  1785. */
  1786. public function testSkipWithTraversableNonIterator(): void
  1787. {
  1788. $collection = new Collection($this->datePeriod('2017-01-01', '2017-01-07'));
  1789. $result = $collection->skip(3)->toList();
  1790. $expected = [
  1791. new DateTime('2017-01-04'),
  1792. new DateTime('2017-01-05'),
  1793. new DateTime('2017-01-06'),
  1794. ];
  1795. $this->assertEquals($expected, $result);
  1796. }
  1797. /**
  1798. * Tests the first() method with a traversable non-iterator
  1799. */
  1800. public function testFirstWithTraversableNonIterator(): void
  1801. {
  1802. $collection = new Collection($this->datePeriod('2017-01-01', '2017-01-07'));
  1803. $date = $collection->first();
  1804. $this->assertInstanceOf('DateTime', $date);
  1805. $this->assertSame('2017-01-01', $date->format('Y-m-d'));
  1806. }
  1807. /**
  1808. * Tests the last() method
  1809. */
  1810. public function testLast(): void
  1811. {
  1812. $collection = new Collection([1, 2, 3]);
  1813. $this->assertSame(3, $collection->last());
  1814. $collection = $collection->map(function ($e) {
  1815. return $e * 2;
  1816. });
  1817. $this->assertSame(6, $collection->last());
  1818. }
  1819. /**
  1820. * Tests the last() method when on an empty collection
  1821. */
  1822. public function testLastWithEmptyCollection(): void
  1823. {
  1824. $collection = new Collection([]);
  1825. $this->assertNull($collection->last());
  1826. }
  1827. /**
  1828. * Tests the last() method with a countable object
  1829. */
  1830. public function testLastWithCountable(): void
  1831. {
  1832. $collection = new Collection(new ArrayObject([1, 2, 3]));
  1833. $this->assertSame(3, $collection->last());
  1834. }
  1835. /**
  1836. * Tests the last() method with an empty countable object
  1837. */
  1838. public function testLastWithEmptyCountable(): void
  1839. {
  1840. $collection = new Collection(new ArrayObject([]));
  1841. $this->assertNull($collection->last());
  1842. }
  1843. /**
  1844. * Tests the last() method with a non-rewindable iterator
  1845. */
  1846. public function testLastWithNonRewindableIterator(): void
  1847. {
  1848. $iterator = new NoRewindIterator(new ArrayIterator([1, 2, 3]));
  1849. $collection = new Collection($iterator);
  1850. $this->assertSame(3, $collection->last());
  1851. }
  1852. /**
  1853. * Tests the last() method with a traversable non-iterator
  1854. */
  1855. public function testLastWithTraversableNonIterator(): void
  1856. {
  1857. $collection = new Collection($this->datePeriod('2017-01-01', '2017-01-07'));
  1858. $date = $collection->last();
  1859. $this->assertInstanceOf('DateTime', $date);
  1860. $this->assertSame('2017-01-06', $date->format('Y-m-d'));
  1861. }
  1862. /**
  1863. * Tests the takeLast() method
  1864. *
  1865. * @dataProvider simpleProvider
  1866. * @param iterable $data The data to test with.
  1867. * @covers ::takeLast
  1868. */
  1869. public function testLastN($data): void
  1870. {
  1871. $collection = new Collection($data);
  1872. $result = $collection->takeLast(3)->toArray();
  1873. $expected = ['b' => 2, 'c' => 3, 'd' => 4];
  1874. $this->assertEquals($expected, $result);
  1875. }
  1876. /**
  1877. * Tests the takeLast() method with overflow
  1878. *
  1879. * @dataProvider simpleProvider
  1880. * @param iterable $data The data to test with.
  1881. * @covers ::takeLast
  1882. */
  1883. public function testLastNtWithOverflow($data): void
  1884. {
  1885. $collection = new Collection($data);
  1886. $result = $collection->takeLast(10)->toArray();
  1887. $expected = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4];
  1888. $this->assertEquals($expected, $result);
  1889. }
  1890. /**
  1891. * Tests the takeLast() with an odd numbers collection
  1892. *
  1893. * @dataProvider simpleProvider
  1894. * @param iterable $data The data to test with.
  1895. * @covers ::takeLast
  1896. */
  1897. public function testLastNtWithOddData($data): void
  1898. {
  1899. $collection = new Collection($data);
  1900. $result = $collection->take(3)->takeLast(2)->toArray();
  1901. $expected = ['b' => 2, 'c' => 3];
  1902. $this->assertEquals($expected, $result);
  1903. }
  1904. /**
  1905. * Tests the takeLast() with countable collection
  1906. *
  1907. * @covers ::takeLast
  1908. */
  1909. public function testLastNtWithCountable(): void
  1910. {
  1911. $rangeZeroToFive = range(0, 5);
  1912. $collection = new Collection(new CountableIterator($rangeZeroToFive));
  1913. $result = $collection->takeLast(2)->toList();
  1914. $this->assertEquals([4, 5], $result);
  1915. $collection = new Collection(new CountableIterator($rangeZeroToFive));
  1916. $result = $collection->takeLast(1)->toList();
  1917. $this->assertEquals([5], $result);
  1918. }
  1919. /**
  1920. * Tests the takeLast() with countable collection
  1921. *
  1922. * @dataProvider simpleProvider
  1923. * @param iterable $data The data to test with.
  1924. * @covers ::takeLast
  1925. */
  1926. public function testLastNtWithNegative($data): void
  1927. {
  1928. $collection = new Collection($data);
  1929. $this->expectException(InvalidArgumentException::class);
  1930. $this->expectExceptionMessage('The takeLast method requires a number greater than 0.');
  1931. $collection->takeLast(-1)->toArray();
  1932. }
  1933. /**
  1934. * Tests sumOf with no parameters
  1935. */
  1936. public function testSumOfWithIdentity(): void
  1937. {
  1938. $collection = new Collection([1, 2, 3]);
  1939. $this->assertSame(6, $collection->sumOf());
  1940. $collection = new Collection(['a' => 1, 'b' => 4, 'c' => 6]);
  1941. $this->assertSame(11, $collection->sumOf());
  1942. }
  1943. /**
  1944. * Tests using extract with the {*} notation
  1945. */
  1946. public function testUnfoldedExtract(): void
  1947. {
  1948. $items = [
  1949. ['comments' => [['id' => 1], ['id' => 2]]],
  1950. ['comments' => [['id' => 3], ['id' => 4]]],
  1951. ['comments' => [['id' => 7], ['nope' => 8]]],
  1952. ];
  1953. $extracted = (new Collection($items))->extract('comments.{*}.id');
  1954. $this->assertEquals([1, 2, 3, 4, 7, null], $extracted->toArray());
  1955. $items = [
  1956. [
  1957. 'comments' => [
  1958. [
  1959. 'voters' => [['id' => 1], ['id' => 2]],
  1960. ],
  1961. ],
  1962. ],
  1963. [
  1964. 'comments' => [
  1965. [
  1966. 'voters' => [['id' => 3], ['id' => 4]],
  1967. ],
  1968. ],
  1969. ],
  1970. [
  1971. 'comments' => [
  1972. [
  1973. 'voters' => [['id' => 5], ['nope' => 'fail'], ['id' => 6]],
  1974. ],
  1975. ],
  1976. ],
  1977. [
  1978. 'comments' => [
  1979. [
  1980. 'not_voters' => [['id' => 5]],
  1981. ],
  1982. ],
  1983. ],
  1984. ['not_comments' => []],
  1985. ];
  1986. $extracted = (new Collection($items))->extract('comments.{*}.voters.{*}.id');
  1987. $expected = [1, 2, 3, 4, 5, null, 6];
  1988. $this->assertEquals($expected, $extracted->toArray());
  1989. $this->assertEquals($expected, $extracted->toList());
  1990. }
  1991. /**
  1992. * Tests serializing a simple collection
  1993. */
  1994. public function testSerializeSimpleCollection(): void
  1995. {
  1996. $collection = new Collection([1, 2, 3]);
  1997. $serialized = serialize($collection);
  1998. $unserialized = unserialize($serialized);
  1999. $this->assertEquals($collection->toList(), $unserialized->toList());
  2000. $this->assertEquals($collection->toArray(), $unserialized->toArray());
  2001. }
  2002. /**
  2003. * Tests serialization when using append
  2004. */
  2005. public function testSerializeWithAppendIterators(): void
  2006. {
  2007. $collection = new Collection([1, 2, 3]);
  2008. $collection = $collection->append(['a' => 4, 'b' => 5, 'c' => 6]);
  2009. $serialized = serialize($collection);
  2010. $unserialized = unserialize($serialized);
  2011. $this->assertEquals($collection->toList(), $unserialized->toList());
  2012. $this->assertEquals($collection->toArray(), $unserialized->toArray());
  2013. }
  2014. /**
  2015. * Tests serialization when using nested iterators
  2016. */
  2017. public function testSerializeWithNestedIterators(): void
  2018. {
  2019. $collection = new Collection([1, 2, 3]);
  2020. $collection = $collection->map(function ($e) {
  2021. return $e * 3;
  2022. });
  2023. $collection = $collection->groupBy(function ($e) {
  2024. return $e % 2;
  2025. });
  2026. $serialized = serialize($collection);
  2027. $unserialized = unserialize($serialized);
  2028. $this->assertEquals($collection->toList(), $unserialized->toList());
  2029. $this->assertEquals($collection->toArray(), $unserialized->toArray());
  2030. }
  2031. /**
  2032. * Tests serializing a zip() call
  2033. */
  2034. public function testSerializeWithZipIterator(): void
  2035. {
  2036. $collection = new Collection([4, 5]);
  2037. $collection = $collection->zip([1, 2]);
  2038. $serialized = serialize($collection);
  2039. $unserialized = unserialize($serialized);
  2040. $this->assertEquals($collection->toList(), $unserialized->toList());
  2041. }
  2042. /**
  2043. * Provider for some chunk tests
  2044. *
  2045. * @return array
  2046. */
  2047. public function chunkProvider(): array
  2048. {
  2049. $items = range(1, 10);
  2050. return [
  2051. 'array' => [$items],
  2052. 'iterator' => [$this->yieldItems($items)],
  2053. ];
  2054. }
  2055. /**
  2056. * Tests the chunk method with exact chunks
  2057. *
  2058. * @dataProvider chunkProvider
  2059. */
  2060. public function testChunk(iterable $items): void
  2061. {
  2062. $collection = new Collection($items);
  2063. $chunked = $collection->chunk(2)->toList();
  2064. $expected = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]];
  2065. $this->assertEquals($expected, $chunked);
  2066. }
  2067. /**
  2068. * Tests the chunk method with overflowing chunk size
  2069. */
  2070. public function testChunkOverflow(): void
  2071. {
  2072. $collection = new Collection(range(1, 11));
  2073. $chunked = $collection->chunk(2)->toList();
  2074. $expected = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]];
  2075. $this->assertEquals($expected, $chunked);
  2076. }
  2077. /**
  2078. * Tests the chunk method with non-scalar items
  2079. */
  2080. public function testChunkNested(): void
  2081. {
  2082. $collection = new Collection([1, 2, 3, [4, 5], 6, [7, [8, 9], 10], 11]);
  2083. $chunked = $collection->chunk(2)->toList();
  2084. $expected = [[1, 2], [3, [4, 5]], [6, [7, [8, 9], 10]], [11]];
  2085. $this->assertEquals($expected, $chunked);
  2086. }
  2087. /**
  2088. * Tests the chunkWithKeys method with exact chunks
  2089. */
  2090. public function testChunkWithKeys(): void
  2091. {
  2092. $collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6]);
  2093. $chunked = $collection->chunkWithKeys(2)->toList();
  2094. $expected = [['a' => 1, 'b' => 2], ['c' => 3, 'd' => 4], ['e' => 5, 'f' => 6]];
  2095. $this->assertEquals($expected, $chunked);
  2096. }
  2097. /**
  2098. * Tests the chunkWithKeys method with overflowing chunk size
  2099. */
  2100. public function testChunkWithKeysOverflow(): void
  2101. {
  2102. $collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7]);
  2103. $chunked = $collection->chunkWithKeys(2)->toList();
  2104. $expected = [['a' => 1, 'b' => 2], ['c' => 3, 'd' => 4], ['e' => 5, 'f' => 6], ['g' => 7]];
  2105. $this->assertEquals($expected, $chunked);
  2106. }
  2107. /**
  2108. * Tests the chunkWithKeys method with non-scalar items
  2109. */
  2110. public function testChunkWithKeysNested(): void
  2111. {
  2112. $collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3, 'd' => [4, 5], 'e' => 6, 'f' => [7, [8, 9], 10], 'g' => 11]);
  2113. $chunked = $collection->chunkWithKeys(2)->toList();
  2114. $expected = [['a' => 1, 'b' => 2], ['c' => 3, 'd' => [4, 5]], ['e' => 6, 'f' => [7, [8, 9], 10]], ['g' => 11]];
  2115. $this->assertEquals($expected, $chunked);
  2116. }
  2117. /**
  2118. * Tests the chunkWithKeys method without preserving keys
  2119. */
  2120. public function testChunkWithKeysNoPreserveKeys(): void
  2121. {
  2122. $collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7]);
  2123. $chunked = $collection->chunkWithKeys(2, false)->toList();
  2124. $expected = [[0 => 1, 1 => 2], [0 => 3, 1 => 4], [0 => 5, 1 => 6], [0 => 7]];
  2125. $this->assertEquals($expected, $chunked);
  2126. }
  2127. /**
  2128. * Tests cartesianProduct
  2129. */
  2130. public function testCartesianProduct(): void
  2131. {
  2132. $collection = new Collection([]);
  2133. $result = $collection->cartesianProduct();
  2134. $expected = [];
  2135. $this->assertEquals($expected, $result->toList());
  2136. $collection = new Collection([['A', 'B', 'C'], [1, 2, 3]]);
  2137. $result = $collection->cartesianProduct();
  2138. $expected = [
  2139. ['A', 1],
  2140. ['A', 2],
  2141. ['A', 3],
  2142. ['B', 1],
  2143. ['B', 2],
  2144. ['B', 3],
  2145. ['C', 1],
  2146. ['C', 2],
  2147. ['C', 3],
  2148. ];
  2149. $this->assertEquals($expected, $result->toList());
  2150. $collection = new Collection([[1, 2, 3], ['A', 'B', 'C'], ['a', 'b', 'c']]);
  2151. $result = $collection->cartesianProduct(function ($value) {
  2152. return [strval($value[0]) . $value[1] . $value[2]];
  2153. }, function ($value) {
  2154. return $value[0] >= 2;
  2155. });
  2156. $expected = [
  2157. ['2Aa'],
  2158. ['2Ab'],
  2159. ['2Ac'],
  2160. ['2Ba'],
  2161. ['2Bb'],
  2162. ['2Bc'],
  2163. ['2Ca'],
  2164. ['2Cb'],
  2165. ['2Cc'],
  2166. ['3Aa'],
  2167. ['3Ab'],
  2168. ['3Ac'],
  2169. ['3Ba'],
  2170. ['3Bb'],
  2171. ['3Bc'],
  2172. ['3Ca'],
  2173. ['3Cb'],
  2174. ['3Cc'],
  2175. ];
  2176. $this->assertEquals($expected, $result->toList());
  2177. $collection = new Collection([['1', '2', '3', '4'], ['A', 'B', 'C'], ['name', 'surname', 'telephone']]);
  2178. $result = $collection->cartesianProduct(function ($value) {
  2179. return [$value[0] => [$value[1] => $value[2]]];
  2180. }, function ($value) {
  2181. return $value[2] !== 'surname';
  2182. });
  2183. $expected = [
  2184. [1 => ['A' => 'name']],
  2185. [1 => ['A' => 'telephone']],
  2186. [1 => ['B' => 'name']],
  2187. [1 => ['B' => 'telephone']],
  2188. [1 => ['C' => 'name']],
  2189. [1 => ['C' => 'telephone']],
  2190. [2 => ['A' => 'name']],
  2191. [2 => ['A' => 'telephone']],
  2192. [2 => ['B' => 'name']],
  2193. [2 => ['B' => 'telephone']],
  2194. [2 => ['C' => 'name']],
  2195. [2 => ['C' => 'telephone']],
  2196. [3 => ['A' => 'name']],
  2197. [3 => ['A' => 'telephone']],
  2198. [3 => ['B' => 'name']],
  2199. [3 => ['B' => 'telephone']],
  2200. [3 => ['C' => 'name']],
  2201. [3 => ['C' => 'telephone']],
  2202. [4 => ['A' => 'name']],
  2203. [4 => ['A' => 'telephone']],
  2204. [4 => ['B' => 'name']],
  2205. [4 => ['B' => 'telephone']],
  2206. [4 => ['C' => 'name']],
  2207. [4 => ['C' => 'telephone']],
  2208. ];
  2209. $this->assertEquals($expected, $result->toList());
  2210. $collection = new Collection([
  2211. [
  2212. 'name1' => 'alex',
  2213. 'name2' => 'kostas',
  2214. 0 => 'leon',
  2215. ],
  2216. [
  2217. 'val1' => 'alex@example.com',
  2218. 24 => 'kostas@example.com',
  2219. 'val2' => 'leon@example.com',
  2220. ],
  2221. ]);
  2222. $result = $collection->cartesianProduct();
  2223. $expected = [
  2224. ['alex', 'alex@example.com'],
  2225. ['alex', 'kostas@example.com'],
  2226. ['alex', 'leon@example.com'],
  2227. ['kostas', 'alex@example.com'],
  2228. ['kostas', 'kostas@example.com'],
  2229. ['kostas', 'leon@example.com'],
  2230. ['leon', 'alex@example.com'],
  2231. ['leon', 'kostas@example.com'],
  2232. ['leon', 'leon@example.com'],
  2233. ];
  2234. $this->assertEquals($expected, $result->toList());
  2235. }
  2236. /**
  2237. * Tests that an exception is thrown if the cartesian product is called with multidimensional arrays
  2238. */
  2239. public function testCartesianProductMultidimensionalArray(): void
  2240. {
  2241. $this->expectException(LogicException::class);
  2242. $collection = new Collection([
  2243. [
  2244. 'names' => [
  2245. 'alex', 'kostas', 'leon',
  2246. ],
  2247. ],
  2248. [
  2249. 'locations' => [
  2250. 'crete', 'london', 'paris',
  2251. ],
  2252. ],
  2253. ]);
  2254. $result = $collection->cartesianProduct();
  2255. }
  2256. public function testTranspose(): void
  2257. {
  2258. $collection = new Collection([
  2259. ['Products', '2012', '2013', '2014'],
  2260. ['Product A', '200', '100', '50'],
  2261. ['Product B', '300', '200', '100'],
  2262. ['Product C', '400', '300', '200'],
  2263. ['Product D', '500', '400', '300'],
  2264. ]);
  2265. $transposed = $collection->transpose();
  2266. $expected = [
  2267. ['Products', 'Product A', 'Product B', 'Product C', 'Product D'],
  2268. ['2012', '200', '300', '400', '500'],
  2269. ['2013', '100', '200', '300', '400'],
  2270. ['2014', '50', '100', '200', '300'],
  2271. ];
  2272. $this->assertEquals($expected, $transposed->toList());
  2273. }
  2274. /**
  2275. * Tests that provided arrays do not have even length
  2276. */
  2277. public function testTransposeUnEvenLengthShouldThrowException(): void
  2278. {
  2279. $this->expectException(LogicException::class);
  2280. $collection = new Collection([
  2281. ['Products', '2012', '2013', '2014'],
  2282. ['Product A', '200', '100', '50'],
  2283. ['Product B', '300'],
  2284. ['Product C', '400', '300'],
  2285. ]);
  2286. $collection->transpose();
  2287. }
  2288. /**
  2289. * Yields all the elements as passed
  2290. *
  2291. * @param iterable $items the elements to be yielded
  2292. * @return \Generator<array>
  2293. */
  2294. protected function yieldItems(iterable $items): Generator
  2295. {
  2296. foreach ($items as $k => $v) {
  2297. yield $k => $v;
  2298. }
  2299. }
  2300. /**
  2301. * Create a DatePeriod object.
  2302. *
  2303. * @param string $start Start date
  2304. * @param string $end End date
  2305. */
  2306. protected function datePeriod($start, $end): DatePeriod
  2307. {
  2308. return new DatePeriod(new DateTime($start), new DateInterval('P1D'), new DateTime($end));
  2309. }
  2310. /**
  2311. * Tests that elements in a lazy collection are not fetched immediately.
  2312. */
  2313. public function testLazy(): void
  2314. {
  2315. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  2316. $collection = (new Collection($items))->lazy();
  2317. $callable = $this->getMockBuilder(stdClass::class)
  2318. ->addMethods(['__invoke'])
  2319. ->getMock();
  2320. $callable->expects($this->never())->method('__invoke');
  2321. $collection->filter($callable)->filter($callable);
  2322. }
  2323. }