CollectionTest.php 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\Collection;
  16. use ArrayIterator;
  17. use ArrayObject;
  18. use Cake\Collection\Collection;
  19. use Cake\Collection\CollectionInterface;
  20. use Cake\Collection\CollectionTrait;
  21. use Cake\TestSuite\TestCase;
  22. use NoRewindIterator;
  23. class TestCollection extends \IteratorIterator implements CollectionInterface
  24. {
  25. use CollectionTrait;
  26. public function __construct($items)
  27. {
  28. if (is_array($items)) {
  29. $items = new \ArrayIterator($items);
  30. }
  31. if (!($items instanceof \Traversable)) {
  32. $msg = 'Only an array or \Traversable is allowed for Collection';
  33. throw new \InvalidArgumentException($msg);
  34. }
  35. parent::__construct($items);
  36. }
  37. }
  38. /**
  39. * CollectionTest
  40. */
  41. class CollectionTest extends TestCase
  42. {
  43. /**
  44. * Tests that it is possible to convert an array into a collection
  45. *
  46. * @return void
  47. */
  48. public function testArrayIsWrapped()
  49. {
  50. $items = [1, 2, 3];
  51. $collection = new Collection($items);
  52. $this->assertEquals($items, iterator_to_array($collection));
  53. }
  54. /**
  55. * Tests that it is possible to convert an iterator into a collection
  56. *
  57. * @return void
  58. */
  59. public function testIteratorIsWrapped()
  60. {
  61. $items = new \ArrayObject([1, 2, 3]);
  62. $collection = new Collection($items);
  63. $this->assertEquals(iterator_to_array($items), iterator_to_array($collection));
  64. }
  65. /**
  66. * Test running a method over all elements in the collection
  67. *
  68. * @return void
  69. */
  70. public function testEeach()
  71. {
  72. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  73. $collection = new Collection($items);
  74. $callable = $this->getMockBuilder(\StdClass::class)
  75. ->setMethods(['__invoke'])
  76. ->getMock();
  77. $callable->expects($this->at(0))
  78. ->method('__invoke')
  79. ->with(1, 'a');
  80. $callable->expects($this->at(1))
  81. ->method('__invoke')
  82. ->with(2, 'b');
  83. $callable->expects($this->at(2))
  84. ->method('__invoke')
  85. ->with(3, 'c');
  86. $collection->each($callable);
  87. }
  88. /**
  89. * Test filter() with no callback.
  90. *
  91. * @return void
  92. */
  93. public function testFilterNoCallback()
  94. {
  95. $items = [1, 2, 0, 3, false, 4, null, 5, ''];
  96. $collection = new Collection($items);
  97. $result = $collection->filter()->toArray();
  98. $expected = [1, 2, 3, 4, 5];
  99. $this->assertEquals($expected, array_values($result));
  100. }
  101. /**
  102. * Tests that it is possible to chain filter() as it returns a collection object
  103. *
  104. * @return void
  105. */
  106. public function testFilterChaining()
  107. {
  108. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  109. $collection = new Collection($items);
  110. $callable = $this->getMockBuilder(\StdClass::class)
  111. ->setMethods(['__invoke'])
  112. ->getMock();
  113. $callable->expects($this->once())
  114. ->method('__invoke')
  115. ->with(3, 'c');
  116. $filtered = $collection->filter(function ($value, $key, $iterator) {
  117. return $value > 2;
  118. });
  119. $this->assertInstanceOf('Cake\Collection\Collection', $filtered);
  120. $filtered->each($callable);
  121. }
  122. /**
  123. * Tests reject
  124. *
  125. * @return void
  126. */
  127. public function testReject()
  128. {
  129. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  130. $collection = new Collection($items);
  131. $result = $collection->reject(function ($v, $k, $items) use ($collection) {
  132. $this->assertSame($collection->getInnerIterator(), $items);
  133. return $v > 2;
  134. });
  135. $this->assertEquals(['a' => 1, 'b' => 2], iterator_to_array($result));
  136. $this->assertInstanceOf('Cake\Collection\Collection', $result);
  137. }
  138. /**
  139. * Tests every when the callback returns true for all elements
  140. *
  141. * @return void
  142. */
  143. public function testEveryReturnTrue()
  144. {
  145. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  146. $collection = new Collection($items);
  147. $callable = $this->getMockBuilder(\StdClass::class)
  148. ->setMethods(['__invoke'])
  149. ->getMock();
  150. $callable->expects($this->at(0))
  151. ->method('__invoke')
  152. ->with(1, 'a')
  153. ->will($this->returnValue(true));
  154. $callable->expects($this->at(1))
  155. ->method('__invoke')
  156. ->with(2, 'b')
  157. ->will($this->returnValue(true));
  158. $callable->expects($this->at(2))
  159. ->method('__invoke')
  160. ->with(3, 'c')
  161. ->will($this->returnValue(true));
  162. $this->assertTrue($collection->every($callable));
  163. }
  164. /**
  165. * Tests every when the callback returns false for one of the elements
  166. *
  167. * @return void
  168. */
  169. public function testEveryReturnFalse()
  170. {
  171. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  172. $collection = new Collection($items);
  173. $callable = $this->getMockBuilder(\StdClass::class)
  174. ->setMethods(['__invoke'])
  175. ->getMock();
  176. $callable->expects($this->at(0))
  177. ->method('__invoke')
  178. ->with(1, 'a')
  179. ->will($this->returnValue(true));
  180. $callable->expects($this->at(1))
  181. ->method('__invoke')
  182. ->with(2, 'b')
  183. ->will($this->returnValue(false));
  184. $callable->expects($this->exactly(2))->method('__invoke');
  185. $this->assertFalse($collection->every($callable));
  186. $items = [];
  187. $collection = new Collection($items);
  188. $callable = $this->getMockBuilder(\StdClass::class)
  189. ->setMethods(['__invoke'])
  190. ->getMock();
  191. $callable->expects($this->never())
  192. ->method('__invoke');
  193. $this->assertFalse($collection->every($callable));
  194. }
  195. /**
  196. * Tests some() when one of the calls return true
  197. *
  198. * @return void
  199. */
  200. public function testSomeReturnTrue()
  201. {
  202. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  203. $collection = new Collection($items);
  204. $callable = $this->getMockBuilder(\StdClass::class)
  205. ->setMethods(['__invoke'])
  206. ->getMock();
  207. $callable->expects($this->at(0))
  208. ->method('__invoke')
  209. ->with(1, 'a')
  210. ->will($this->returnValue(false));
  211. $callable->expects($this->at(1))
  212. ->method('__invoke')
  213. ->with(2, 'b')
  214. ->will($this->returnValue(true));
  215. $callable->expects($this->exactly(2))->method('__invoke');
  216. $this->assertTrue($collection->some($callable));
  217. }
  218. /**
  219. * Tests some() when none of the calls return true
  220. *
  221. * @return void
  222. */
  223. public function testSomeReturnFalse()
  224. {
  225. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  226. $collection = new Collection($items);
  227. $callable = $this->getMockBuilder(\StdClass::class)
  228. ->setMethods(['__invoke'])
  229. ->getMock();
  230. $callable->expects($this->at(0))
  231. ->method('__invoke')
  232. ->with(1, 'a')
  233. ->will($this->returnValue(false));
  234. $callable->expects($this->at(1))
  235. ->method('__invoke')
  236. ->with(2, 'b')
  237. ->will($this->returnValue(false));
  238. $callable->expects($this->at(2))
  239. ->method('__invoke')
  240. ->with(3, 'c')
  241. ->will($this->returnValue(false));
  242. $this->assertFalse($collection->some($callable));
  243. }
  244. /**
  245. * Tests contains
  246. *
  247. * @return void
  248. */
  249. public function testContains()
  250. {
  251. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  252. $collection = new Collection($items);
  253. $this->assertTrue($collection->contains(2));
  254. $this->assertTrue($collection->contains(1));
  255. $this->assertFalse($collection->contains(10));
  256. $this->assertFalse($collection->contains('2'));
  257. }
  258. /**
  259. * Tests map
  260. *
  261. * @return void
  262. */
  263. public function testMap()
  264. {
  265. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  266. $collection = new Collection($items);
  267. $map = $collection->map(function ($v, $k, $it) use ($collection) {
  268. $this->assertSame($collection->getInnerIterator(), $it);
  269. return $v * $v;
  270. });
  271. $this->assertInstanceOf('Cake\Collection\Iterator\ReplaceIterator', $map);
  272. $this->assertEquals(['a' => 1, 'b' => 4, 'c' => 9], iterator_to_array($map));
  273. }
  274. /**
  275. * Tests reduce with initial value
  276. *
  277. * @return void
  278. */
  279. public function testReduceWithInitialValue()
  280. {
  281. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  282. $collection = new Collection($items);
  283. $callable = $this->getMockBuilder(\StdClass::class)
  284. ->setMethods(['__invoke'])
  285. ->getMock();
  286. $callable->expects($this->at(0))
  287. ->method('__invoke')
  288. ->with(10, 1, 'a')
  289. ->will($this->returnValue(11));
  290. $callable->expects($this->at(1))
  291. ->method('__invoke')
  292. ->with(11, 2, 'b')
  293. ->will($this->returnValue(13));
  294. $callable->expects($this->at(2))
  295. ->method('__invoke')
  296. ->with(13, 3, 'c')
  297. ->will($this->returnValue(16));
  298. $this->assertEquals(16, $collection->reduce($callable, 10));
  299. }
  300. /**
  301. * Tests reduce without initial value
  302. *
  303. * @return void
  304. */
  305. public function testReduceWithoutInitialValue()
  306. {
  307. $items = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4];
  308. $collection = new Collection($items);
  309. $callable = $this->getMockBuilder(\StdClass::class)
  310. ->setMethods(['__invoke'])
  311. ->getMock();
  312. $callable->expects($this->at(0))
  313. ->method('__invoke')
  314. ->with(1, 2, 'b')
  315. ->will($this->returnValue(3));
  316. $callable->expects($this->at(1))
  317. ->method('__invoke')
  318. ->with(3, 3, 'c')
  319. ->will($this->returnValue(6));
  320. $callable->expects($this->at(2))
  321. ->method('__invoke')
  322. ->with(6, 4, 'd')
  323. ->will($this->returnValue(10));
  324. $this->assertEquals(10, $collection->reduce($callable));
  325. }
  326. /**
  327. * Tests extract
  328. *
  329. * @return void
  330. */
  331. public function testExtract()
  332. {
  333. $items = [['a' => ['b' => ['c' => 1]]], 2];
  334. $collection = new Collection($items);
  335. $map = $collection->extract('a.b.c');
  336. $this->assertInstanceOf('Cake\Collection\Iterator\ExtractIterator', $map);
  337. $this->assertEquals([1, null], iterator_to_array($map));
  338. }
  339. /**
  340. * Tests sort
  341. *
  342. * @return void
  343. */
  344. public function testSortString()
  345. {
  346. $items = [
  347. ['a' => ['b' => ['c' => 4]]],
  348. ['a' => ['b' => ['c' => 10]]],
  349. ['a' => ['b' => ['c' => 6]]]
  350. ];
  351. $collection = new Collection($items);
  352. $map = $collection->sortBy('a.b.c');
  353. $this->assertInstanceOf('Cake\Collection\Collection', $map);
  354. $expected = [
  355. ['a' => ['b' => ['c' => 10]]],
  356. ['a' => ['b' => ['c' => 6]]],
  357. ['a' => ['b' => ['c' => 4]]],
  358. ];
  359. $this->assertEquals($expected, $map->toList());
  360. }
  361. /**
  362. * Tests max
  363. *
  364. * @return void
  365. */
  366. public function testMax()
  367. {
  368. $items = [
  369. ['a' => ['b' => ['c' => 4]]],
  370. ['a' => ['b' => ['c' => 10]]],
  371. ['a' => ['b' => ['c' => 6]]]
  372. ];
  373. $collection = new Collection($items);
  374. $this->assertEquals(['a' => ['b' => ['c' => 10]]], $collection->max('a.b.c'));
  375. $callback = function ($e) {
  376. return $e['a']['b']['c'] * - 1;
  377. };
  378. $this->assertEquals(['a' => ['b' => ['c' => 4]]], $collection->max($callback));
  379. }
  380. /**
  381. * Tests min
  382. *
  383. * @return void
  384. */
  385. public function testMin()
  386. {
  387. $items = [
  388. ['a' => ['b' => ['c' => 4]]],
  389. ['a' => ['b' => ['c' => 10]]],
  390. ['a' => ['b' => ['c' => 6]]]
  391. ];
  392. $collection = new Collection($items);
  393. $this->assertEquals(['a' => ['b' => ['c' => 4]]], $collection->min('a.b.c'));
  394. }
  395. /**
  396. * Tests groupBy
  397. *
  398. * @return void
  399. */
  400. public function testGroupBy()
  401. {
  402. $items = [
  403. ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  404. ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  405. ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  406. ];
  407. $collection = new Collection($items);
  408. $grouped = $collection->groupBy('parent_id');
  409. $expected = [
  410. 10 => [
  411. ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  412. ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  413. ],
  414. 11 => [
  415. ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  416. ]
  417. ];
  418. $this->assertEquals($expected, iterator_to_array($grouped));
  419. $this->assertInstanceOf('Cake\Collection\Collection', $grouped);
  420. $grouped = $collection->groupBy(function ($element) {
  421. return $element['parent_id'];
  422. });
  423. $this->assertEquals($expected, iterator_to_array($grouped));
  424. }
  425. /**
  426. * Tests grouping by a deep key
  427. *
  428. * @return void
  429. */
  430. public function testGroupByDeepKey()
  431. {
  432. $items = [
  433. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  434. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  435. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  436. ];
  437. $collection = new Collection($items);
  438. $grouped = $collection->groupBy('thing.parent_id');
  439. $expected = [
  440. 10 => [
  441. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  442. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  443. ],
  444. 11 => [
  445. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  446. ]
  447. ];
  448. $this->assertEquals($expected, iterator_to_array($grouped));
  449. }
  450. /**
  451. * Tests indexBy
  452. *
  453. * @return void
  454. */
  455. public function testIndexBy()
  456. {
  457. $items = [
  458. ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  459. ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  460. ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  461. ];
  462. $collection = new Collection($items);
  463. $grouped = $collection->indexBy('id');
  464. $expected = [
  465. 1 => ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  466. 3 => ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  467. 2 => ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  468. ];
  469. $this->assertEquals($expected, iterator_to_array($grouped));
  470. $this->assertInstanceOf('Cake\Collection\Collection', $grouped);
  471. $grouped = $collection->indexBy(function ($element) {
  472. return $element['id'];
  473. });
  474. $this->assertEquals($expected, iterator_to_array($grouped));
  475. }
  476. /**
  477. * Tests indexBy with a deep property
  478. *
  479. * @return void
  480. */
  481. public function testIndexByDeep()
  482. {
  483. $items = [
  484. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  485. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  486. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  487. ];
  488. $collection = new Collection($items);
  489. $grouped = $collection->indexBy('thing.parent_id');
  490. $expected = [
  491. 10 => ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  492. 11 => ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  493. ];
  494. $this->assertEquals($expected, iterator_to_array($grouped));
  495. }
  496. /**
  497. * Tests countBy
  498. *
  499. * @return void
  500. */
  501. public function testCountBy()
  502. {
  503. $items = [
  504. ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  505. ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  506. ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  507. ];
  508. $collection = new Collection($items);
  509. $grouped = $collection->countBy('parent_id');
  510. $expected = [
  511. 10 => 2,
  512. 11 => 1
  513. ];
  514. $this->assertEquals($expected, iterator_to_array($grouped));
  515. $this->assertInstanceOf('Cake\Collection\Collection', $grouped);
  516. $grouped = $collection->countBy(function ($element) {
  517. return $element['parent_id'];
  518. });
  519. $this->assertEquals($expected, iterator_to_array($grouped));
  520. }
  521. /**
  522. * Tests shuffle
  523. *
  524. * @return void
  525. */
  526. public function testShuffle()
  527. {
  528. $data = [1, 2, 3, 4];
  529. $collection = (new Collection($data))->shuffle();
  530. $this->assertEquals(count($data), count(iterator_to_array($collection)));
  531. foreach ($collection as $value) {
  532. $this->assertContains($value, $data);
  533. }
  534. }
  535. /**
  536. * Tests sample
  537. *
  538. * @return void
  539. */
  540. public function testSample()
  541. {
  542. $data = [1, 2, 3, 4];
  543. $collection = (new Collection($data))->sample(2);
  544. $this->assertEquals(2, count(iterator_to_array($collection)));
  545. foreach ($collection as $value) {
  546. $this->assertContains($value, $data);
  547. }
  548. }
  549. /**
  550. * Test toArray method
  551. *
  552. * @return void
  553. */
  554. public function testToArray()
  555. {
  556. $data = [1, 2, 3, 4];
  557. $collection = new Collection($data);
  558. $this->assertEquals($data, $collection->toArray());
  559. }
  560. /**
  561. * Test toList method
  562. *
  563. * @return void
  564. */
  565. public function testToList()
  566. {
  567. $data = [100 => 1, 300 => 2, 500 => 3, 1 => 4];
  568. $collection = new Collection($data);
  569. $this->assertEquals(array_values($data), $collection->toList());
  570. }
  571. /**
  572. * Test json encoding
  573. *
  574. * @return void
  575. */
  576. public function testToJson()
  577. {
  578. $data = [1, 2, 3, 4];
  579. $collection = new Collection($data);
  580. $this->assertEquals(json_encode($data), json_encode($collection));
  581. }
  582. /**
  583. * Tests that only arrays and Traversables are allowed in the constructor
  584. *
  585. * @expectedException \InvalidArgumentException
  586. * @expectedExceptionMessage Only an array or \Traversable is allowed for Collection
  587. * @return void
  588. */
  589. public function testInvalidConstructorArgument()
  590. {
  591. new Collection('Derp');
  592. }
  593. /**
  594. * Tests that issuing a count will throw an exception
  595. *
  596. * @expectedException \LogicException
  597. * @return void
  598. */
  599. public function testCollectionCount()
  600. {
  601. $data = [1, 2, 3, 4];
  602. $collection = new Collection($data);
  603. $collection->count();
  604. }
  605. /**
  606. * Tests take method
  607. *
  608. * @return void
  609. */
  610. public function testTake()
  611. {
  612. $data = [1, 2, 3, 4];
  613. $collection = new Collection($data);
  614. $taken = $collection->take(2);
  615. $this->assertEquals([1, 2], $taken->toArray());
  616. $taken = $collection->take(3);
  617. $this->assertEquals([1, 2, 3], $taken->toArray());
  618. $taken = $collection->take(500);
  619. $this->assertEquals([1, 2, 3, 4], $taken->toArray());
  620. $taken = $collection->take(1);
  621. $this->assertEquals([1], $taken->toArray());
  622. $taken = $collection->take();
  623. $this->assertEquals([1], $taken->toArray());
  624. $taken = $collection->take(2, 2);
  625. $this->assertEquals([2 => 3, 3 => 4], $taken->toArray());
  626. }
  627. /**
  628. * Tests match
  629. *
  630. * @return void
  631. */
  632. public function testMatch()
  633. {
  634. $items = [
  635. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  636. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  637. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  638. ];
  639. $collection = new Collection($items);
  640. $matched = $collection->match(['thing.parent_id' => 10, 'name' => 'baz']);
  641. $this->assertEquals([2 => $items[2]], $matched->toArray());
  642. $matched = $collection->match(['thing.parent_id' => 10]);
  643. $this->assertEquals(
  644. [0 => $items[0], 2 => $items[2]],
  645. $matched->toArray()
  646. );
  647. $matched = $collection->match(['thing.parent_id' => 500]);
  648. $this->assertEquals([], $matched->toArray());
  649. $matched = $collection->match(['parent_id' => 10, 'name' => 'baz']);
  650. $this->assertEquals([], $matched->toArray());
  651. }
  652. /**
  653. * Tests firstMatch
  654. *
  655. * @return void
  656. */
  657. public function testFirstMatch()
  658. {
  659. $items = [
  660. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  661. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  662. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  663. ];
  664. $collection = new Collection($items);
  665. $matched = $collection->firstMatch(['thing.parent_id' => 10]);
  666. $this->assertEquals(
  667. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  668. $matched
  669. );
  670. $matched = $collection->firstMatch(['thing.parent_id' => 10, 'name' => 'baz']);
  671. $this->assertEquals(
  672. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  673. $matched
  674. );
  675. }
  676. /**
  677. * Tests the append method
  678. *
  679. * @return void
  680. */
  681. public function testAppend()
  682. {
  683. $collection = new Collection([1, 2, 3]);
  684. $combined = $collection->append([4, 5, 6]);
  685. $this->assertEquals([1, 2, 3, 4, 5, 6], $combined->toArray(false));
  686. $collection = new Collection(['a' => 1, 'b' => 2]);
  687. $combined = $collection->append(['c' => 3, 'a' => 4]);
  688. $this->assertEquals(['a' => 4, 'b' => 2, 'c' => 3], $combined->toArray());
  689. }
  690. /**
  691. * Tests the append method with iterator
  692. */
  693. public function testAppendIterator()
  694. {
  695. $collection = new Collection([1, 2, 3]);
  696. $iterator = new ArrayIterator([4, 5, 6]);
  697. $combined = $collection->append($iterator);
  698. $this->assertEquals([1, 2, 3, 4, 5, 6], $combined->toList());
  699. }
  700. public function testAppendNotCollectionInstance()
  701. {
  702. $collection = new TestCollection([1, 2, 3]);
  703. $combined = $collection->append([4, 5, 6]);
  704. $this->assertEquals([1, 2, 3, 4, 5, 6], $combined->toList());
  705. }
  706. /**
  707. * Tests that by calling compile internal iteration operations are not done
  708. * more than once
  709. *
  710. * @return void
  711. */
  712. public function testCompile()
  713. {
  714. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  715. $collection = new Collection($items);
  716. $callable = $this->getMockBuilder(\StdClass::class)
  717. ->setMethods(['__invoke'])
  718. ->getMock();
  719. $callable->expects($this->at(0))
  720. ->method('__invoke')
  721. ->with(1, 'a')
  722. ->will($this->returnValue(4));
  723. $callable->expects($this->at(1))
  724. ->method('__invoke')
  725. ->with(2, 'b')
  726. ->will($this->returnValue(5));
  727. $callable->expects($this->at(2))
  728. ->method('__invoke')
  729. ->with(3, 'c')
  730. ->will($this->returnValue(6));
  731. $compiled = $collection->map($callable)->compile();
  732. $this->assertEquals(['a' => 4, 'b' => 5, 'c' => 6], $compiled->toArray());
  733. $this->assertEquals(['a' => 4, 'b' => 5, 'c' => 6], $compiled->toArray());
  734. }
  735. /**
  736. * Tests converting a non rewindable iterator into a rewindable one using
  737. * the buffered method.
  738. *
  739. * @return void
  740. */
  741. public function testBuffered()
  742. {
  743. $items = new NoRewindIterator(new ArrayIterator(['a' => 4, 'b' => 5, 'c' => 6]));
  744. $buffered = (new Collection($items))->buffered();
  745. $this->assertEquals(['a' => 4, 'b' => 5, 'c' => 6], $buffered->toArray());
  746. $this->assertEquals(['a' => 4, 'b' => 5, 'c' => 6], $buffered->toArray());
  747. }
  748. /**
  749. * Tests the combine method
  750. *
  751. * @return void
  752. */
  753. public function testCombine()
  754. {
  755. $items = [
  756. ['id' => 1, 'name' => 'foo', 'parent' => 'a'],
  757. ['id' => 2, 'name' => 'bar', 'parent' => 'b'],
  758. ['id' => 3, 'name' => 'baz', 'parent' => 'a']
  759. ];
  760. $collection = (new Collection($items))->combine('id', 'name');
  761. $expected = [1 => 'foo', 2 => 'bar', 3 => 'baz'];
  762. $this->assertEquals($expected, $collection->toArray());
  763. $expected = ['foo' => 1, 'bar' => 2, 'baz' => 3];
  764. $collection = (new Collection($items))->combine('name', 'id');
  765. $this->assertEquals($expected, $collection->toArray());
  766. $collection = (new Collection($items))->combine('id', 'name', 'parent');
  767. $expected = ['a' => [1 => 'foo', 3 => 'baz'], 'b' => [2 => 'bar']];
  768. $this->assertEquals($expected, $collection->toArray());
  769. $expected = [
  770. '0-1' => ['foo-0-1' => '0-1-foo'],
  771. '1-2' => ['bar-1-2' => '1-2-bar'],
  772. '2-3' => ['baz-2-3' => '2-3-baz']
  773. ];
  774. $collection = (new Collection($items))->combine(
  775. function ($value, $key) {
  776. return $value['name'] . '-' . $key;
  777. },
  778. function ($value, $key) {
  779. return $key . '-' . $value['name'];
  780. },
  781. function ($value, $key) {
  782. return $key . '-' . $value['id'];
  783. }
  784. );
  785. $this->assertEquals($expected, $collection->toArray());
  786. $collection = (new Collection($items))->combine('id', 'crazy');
  787. $this->assertEquals([1 => null, 2 => null, 3 => null], $collection->toArray());
  788. }
  789. /**
  790. * Tests the nest method with only one level
  791. *
  792. * @return void
  793. */
  794. public function testNest()
  795. {
  796. $items = [
  797. ['id' => 1, 'parent_id' => null],
  798. ['id' => 2, 'parent_id' => 1],
  799. ['id' => 3, 'parent_id' => 1],
  800. ['id' => 4, 'parent_id' => 1],
  801. ['id' => 5, 'parent_id' => 6],
  802. ['id' => 6, 'parent_id' => null],
  803. ['id' => 7, 'parent_id' => 1],
  804. ['id' => 8, 'parent_id' => 6],
  805. ['id' => 9, 'parent_id' => 6],
  806. ['id' => 10, 'parent_id' => 6]
  807. ];
  808. $collection = (new Collection($items))->nest('id', 'parent_id');
  809. $expected = [
  810. [
  811. 'id' => 1,
  812. 'parent_id' => null,
  813. 'children' => [
  814. ['id' => 2, 'parent_id' => 1, 'children' => []],
  815. ['id' => 3, 'parent_id' => 1, 'children' => []],
  816. ['id' => 4, 'parent_id' => 1, 'children' => []],
  817. ['id' => 7, 'parent_id' => 1, 'children' => []]
  818. ]
  819. ],
  820. [
  821. 'id' => 6,
  822. 'parent_id' => null,
  823. 'children' => [
  824. ['id' => 5, 'parent_id' => 6, 'children' => []],
  825. ['id' => 8, 'parent_id' => 6, 'children' => []],
  826. ['id' => 9, 'parent_id' => 6, 'children' => []],
  827. ['id' => 10, 'parent_id' => 6, 'children' => []]
  828. ]
  829. ]
  830. ];
  831. $this->assertEquals($expected, $collection->toArray());
  832. }
  833. /**
  834. * Tests the nest method with alternate nesting key
  835. *
  836. * @return void
  837. */
  838. public function testNestAlternateNestingKey()
  839. {
  840. $items = [
  841. ['id' => 1, 'parent_id' => null],
  842. ['id' => 2, 'parent_id' => 1],
  843. ['id' => 3, 'parent_id' => 1],
  844. ['id' => 4, 'parent_id' => 1],
  845. ['id' => 5, 'parent_id' => 6],
  846. ['id' => 6, 'parent_id' => null],
  847. ['id' => 7, 'parent_id' => 1],
  848. ['id' => 8, 'parent_id' => 6],
  849. ['id' => 9, 'parent_id' => 6],
  850. ['id' => 10, 'parent_id' => 6]
  851. ];
  852. $collection = (new Collection($items))->nest('id', 'parent_id', 'nodes');
  853. $expected = [
  854. [
  855. 'id' => 1,
  856. 'parent_id' => null,
  857. 'nodes' => [
  858. ['id' => 2, 'parent_id' => 1, 'nodes' => []],
  859. ['id' => 3, 'parent_id' => 1, 'nodes' => []],
  860. ['id' => 4, 'parent_id' => 1, 'nodes' => []],
  861. ['id' => 7, 'parent_id' => 1, 'nodes' => []]
  862. ]
  863. ],
  864. [
  865. 'id' => 6,
  866. 'parent_id' => null,
  867. 'nodes' => [
  868. ['id' => 5, 'parent_id' => 6, 'nodes' => []],
  869. ['id' => 8, 'parent_id' => 6, 'nodes' => []],
  870. ['id' => 9, 'parent_id' => 6, 'nodes' => []],
  871. ['id' => 10, 'parent_id' => 6, 'nodes' => []]
  872. ]
  873. ]
  874. ];
  875. $this->assertEquals($expected, $collection->toArray());
  876. }
  877. /**
  878. * Tests the nest method with more than one level
  879. *
  880. * @return void
  881. */
  882. public function testNestMultiLevel()
  883. {
  884. $items = [
  885. ['id' => 1, 'parent_id' => null],
  886. ['id' => 2, 'parent_id' => 1],
  887. ['id' => 3, 'parent_id' => 2],
  888. ['id' => 4, 'parent_id' => 2],
  889. ['id' => 5, 'parent_id' => 3],
  890. ['id' => 6, 'parent_id' => null],
  891. ['id' => 7, 'parent_id' => 3],
  892. ['id' => 8, 'parent_id' => 4],
  893. ['id' => 9, 'parent_id' => 6],
  894. ['id' => 10, 'parent_id' => 6]
  895. ];
  896. $collection = (new Collection($items))->nest('id', 'parent_id', 'nodes');
  897. $expected = [
  898. [
  899. 'id' => 1,
  900. 'parent_id' => null,
  901. 'nodes' => [
  902. [
  903. 'id' => 2,
  904. 'parent_id' => 1,
  905. 'nodes' => [
  906. [
  907. 'id' => 3,
  908. 'parent_id' => 2,
  909. 'nodes' => [
  910. ['id' => 5, 'parent_id' => 3, 'nodes' => []],
  911. ['id' => 7, 'parent_id' => 3, 'nodes' => []]
  912. ]
  913. ],
  914. [
  915. 'id' => 4,
  916. 'parent_id' => 2,
  917. 'nodes' => [
  918. ['id' => 8, 'parent_id' => 4, 'nodes' => []]
  919. ]
  920. ]
  921. ]
  922. ]
  923. ]
  924. ],
  925. [
  926. 'id' => 6,
  927. 'parent_id' => null,
  928. 'nodes' => [
  929. ['id' => 9, 'parent_id' => 6, 'nodes' => []],
  930. ['id' => 10, 'parent_id' => 6, 'nodes' => []]
  931. ]
  932. ]
  933. ];
  934. $this->assertEquals($expected, $collection->toArray());
  935. }
  936. /**
  937. * Tests the nest method with more than one level
  938. *
  939. * @return void
  940. */
  941. public function testNestMultiLevelAlternateNestingKey()
  942. {
  943. $items = [
  944. ['id' => 1, 'parent_id' => null],
  945. ['id' => 2, 'parent_id' => 1],
  946. ['id' => 3, 'parent_id' => 2],
  947. ['id' => 4, 'parent_id' => 2],
  948. ['id' => 5, 'parent_id' => 3],
  949. ['id' => 6, 'parent_id' => null],
  950. ['id' => 7, 'parent_id' => 3],
  951. ['id' => 8, 'parent_id' => 4],
  952. ['id' => 9, 'parent_id' => 6],
  953. ['id' => 10, 'parent_id' => 6]
  954. ];
  955. $collection = (new Collection($items))->nest('id', 'parent_id');
  956. $expected = [
  957. [
  958. 'id' => 1,
  959. 'parent_id' => null,
  960. 'children' => [
  961. [
  962. 'id' => 2,
  963. 'parent_id' => 1,
  964. 'children' => [
  965. [
  966. 'id' => 3,
  967. 'parent_id' => 2,
  968. 'children' => [
  969. ['id' => 5, 'parent_id' => 3, 'children' => []],
  970. ['id' => 7, 'parent_id' => 3, 'children' => []]
  971. ]
  972. ],
  973. [
  974. 'id' => 4,
  975. 'parent_id' => 2,
  976. 'children' => [
  977. ['id' => 8, 'parent_id' => 4, 'children' => []]
  978. ]
  979. ]
  980. ]
  981. ]
  982. ]
  983. ],
  984. [
  985. 'id' => 6,
  986. 'parent_id' => null,
  987. 'children' => [
  988. ['id' => 9, 'parent_id' => 6, 'children' => []],
  989. ['id' => 10, 'parent_id' => 6, 'children' => []]
  990. ]
  991. ]
  992. ];
  993. $this->assertEquals($expected, $collection->toArray());
  994. }
  995. /**
  996. * Tests the nest method with more than one level
  997. *
  998. * @return void
  999. */
  1000. public function testNestObjects()
  1001. {
  1002. $items = [
  1003. new ArrayObject(['id' => 1, 'parent_id' => null]),
  1004. new ArrayObject(['id' => 2, 'parent_id' => 1]),
  1005. new ArrayObject(['id' => 3, 'parent_id' => 2]),
  1006. new ArrayObject(['id' => 4, 'parent_id' => 2]),
  1007. new ArrayObject(['id' => 5, 'parent_id' => 3]),
  1008. new ArrayObject(['id' => 6, 'parent_id' => null]),
  1009. new ArrayObject(['id' => 7, 'parent_id' => 3]),
  1010. new ArrayObject(['id' => 8, 'parent_id' => 4]),
  1011. new ArrayObject(['id' => 9, 'parent_id' => 6]),
  1012. new ArrayObject(['id' => 10, 'parent_id' => 6])
  1013. ];
  1014. $collection = (new Collection($items))->nest('id', 'parent_id');
  1015. $expected = [
  1016. new ArrayObject([
  1017. 'id' => 1,
  1018. 'parent_id' => null,
  1019. 'children' => [
  1020. new ArrayObject([
  1021. 'id' => 2,
  1022. 'parent_id' => 1,
  1023. 'children' => [
  1024. new ArrayObject([
  1025. 'id' => 3,
  1026. 'parent_id' => 2,
  1027. 'children' => [
  1028. new ArrayObject(['id' => 5, 'parent_id' => 3, 'children' => []]),
  1029. new ArrayObject(['id' => 7, 'parent_id' => 3, 'children' => []])
  1030. ]
  1031. ]),
  1032. new ArrayObject([
  1033. 'id' => 4,
  1034. 'parent_id' => 2,
  1035. 'children' => [
  1036. new ArrayObject(['id' => 8, 'parent_id' => 4, 'children' => []])
  1037. ]
  1038. ])
  1039. ]
  1040. ])
  1041. ]
  1042. ]),
  1043. new ArrayObject([
  1044. 'id' => 6,
  1045. 'parent_id' => null,
  1046. 'children' => [
  1047. new ArrayObject(['id' => 9, 'parent_id' => 6, 'children' => []]),
  1048. new ArrayObject(['id' => 10, 'parent_id' => 6, 'children' => []])
  1049. ]
  1050. ])
  1051. ];
  1052. $this->assertEquals($expected, $collection->toArray());
  1053. }
  1054. /**
  1055. * Tests the nest method with more than one level
  1056. *
  1057. * @return void
  1058. */
  1059. public function testNestObjectsAlternateNestingKey()
  1060. {
  1061. $items = [
  1062. new ArrayObject(['id' => 1, 'parent_id' => null]),
  1063. new ArrayObject(['id' => 2, 'parent_id' => 1]),
  1064. new ArrayObject(['id' => 3, 'parent_id' => 2]),
  1065. new ArrayObject(['id' => 4, 'parent_id' => 2]),
  1066. new ArrayObject(['id' => 5, 'parent_id' => 3]),
  1067. new ArrayObject(['id' => 6, 'parent_id' => null]),
  1068. new ArrayObject(['id' => 7, 'parent_id' => 3]),
  1069. new ArrayObject(['id' => 8, 'parent_id' => 4]),
  1070. new ArrayObject(['id' => 9, 'parent_id' => 6]),
  1071. new ArrayObject(['id' => 10, 'parent_id' => 6])
  1072. ];
  1073. $collection = (new Collection($items))->nest('id', 'parent_id', 'nodes');
  1074. $expected = [
  1075. new ArrayObject([
  1076. 'id' => 1,
  1077. 'parent_id' => null,
  1078. 'nodes' => [
  1079. new ArrayObject([
  1080. 'id' => 2,
  1081. 'parent_id' => 1,
  1082. 'nodes' => [
  1083. new ArrayObject([
  1084. 'id' => 3,
  1085. 'parent_id' => 2,
  1086. 'nodes' => [
  1087. new ArrayObject(['id' => 5, 'parent_id' => 3, 'nodes' => []]),
  1088. new ArrayObject(['id' => 7, 'parent_id' => 3, 'nodes' => []])
  1089. ]
  1090. ]),
  1091. new ArrayObject([
  1092. 'id' => 4,
  1093. 'parent_id' => 2,
  1094. 'nodes' => [
  1095. new ArrayObject(['id' => 8, 'parent_id' => 4, 'nodes' => []])
  1096. ]
  1097. ])
  1098. ]
  1099. ])
  1100. ]
  1101. ]),
  1102. new ArrayObject([
  1103. 'id' => 6,
  1104. 'parent_id' => null,
  1105. 'nodes' => [
  1106. new ArrayObject(['id' => 9, 'parent_id' => 6, 'nodes' => []]),
  1107. new ArrayObject(['id' => 10, 'parent_id' => 6, 'nodes' => []])
  1108. ]
  1109. ])
  1110. ];
  1111. $this->assertEquals($expected, $collection->toArray());
  1112. }
  1113. /**
  1114. * Tests insert
  1115. *
  1116. * @return void
  1117. */
  1118. public function testInsert()
  1119. {
  1120. $items = [['a' => 1], ['b' => 2]];
  1121. $collection = new Collection($items);
  1122. $iterator = $collection->insert('c', [3, 4]);
  1123. $this->assertInstanceOf('Cake\Collection\Iterator\InsertIterator', $iterator);
  1124. $this->assertEquals(
  1125. [['a' => 1, 'c' => 3], ['b' => 2, 'c' => 4]],
  1126. iterator_to_array($iterator)
  1127. );
  1128. }
  1129. /**
  1130. * Provider for testing each of the directions for listNested
  1131. *
  1132. * @return void
  1133. */
  1134. public function nestedListProvider()
  1135. {
  1136. return [
  1137. ['desc', [1, 2, 3, 5, 7, 4, 8, 6, 9, 10]],
  1138. ['asc', [5, 7, 3, 8, 4, 2, 1, 9, 10, 6]],
  1139. ['leaves', [5, 7, 8, 9, 10]]
  1140. ];
  1141. }
  1142. /**
  1143. * Tests the listNested method with the default 'children' nesting key
  1144. *
  1145. * @dataProvider nestedListProvider
  1146. * @return void
  1147. */
  1148. public function testListNested($dir, $expected)
  1149. {
  1150. $items = [
  1151. ['id' => 1, 'parent_id' => null],
  1152. ['id' => 2, 'parent_id' => 1],
  1153. ['id' => 3, 'parent_id' => 2],
  1154. ['id' => 4, 'parent_id' => 2],
  1155. ['id' => 5, 'parent_id' => 3],
  1156. ['id' => 6, 'parent_id' => null],
  1157. ['id' => 7, 'parent_id' => 3],
  1158. ['id' => 8, 'parent_id' => 4],
  1159. ['id' => 9, 'parent_id' => 6],
  1160. ['id' => 10, 'parent_id' => 6]
  1161. ];
  1162. $collection = (new Collection($items))->nest('id', 'parent_id')->listNested($dir);
  1163. $this->assertEquals($expected, $collection->extract('id')->toArray(false));
  1164. }
  1165. /**
  1166. * Tests using listNested with a different nesting key
  1167. *
  1168. * @return void
  1169. */
  1170. public function testListNestedCustomKey()
  1171. {
  1172. $items = [
  1173. ['id' => 1, 'stuff' => [['id' => 2, 'stuff' => [['id' => 3]]]]],
  1174. ['id' => 4, 'stuff' => [['id' => 5]]]
  1175. ];
  1176. $collection = (new Collection($items))->listNested('desc', 'stuff');
  1177. $this->assertEquals(range(1, 5), $collection->extract('id')->toArray(false));
  1178. }
  1179. /**
  1180. * Tests flattening the collection using a custom callable function
  1181. *
  1182. * @return void
  1183. */
  1184. public function testListNestedWithCallable()
  1185. {
  1186. $items = [
  1187. ['id' => 1, 'stuff' => [['id' => 2, 'stuff' => [['id' => 3]]]]],
  1188. ['id' => 4, 'stuff' => [['id' => 5]]]
  1189. ];
  1190. $collection = (new Collection($items))->listNested('desc', function ($item) {
  1191. return isset($item['stuff']) ? $item['stuff'] : [];
  1192. });
  1193. $this->assertEquals(range(1, 5), $collection->extract('id')->toArray(false));
  1194. }
  1195. /**
  1196. * Tests the sumOf method
  1197. *
  1198. * @return void
  1199. */
  1200. public function testSumOf()
  1201. {
  1202. $items = [
  1203. ['invoice' => ['total' => 100]],
  1204. ['invoice' => ['total' => 200]]
  1205. ];
  1206. $this->assertEquals(300, (new Collection($items))->sumOf('invoice.total'));
  1207. $sum = (new Collection($items))->sumOf(function ($v) {
  1208. return $v['invoice']['total'] * 2;
  1209. });
  1210. $this->assertEquals(600, $sum);
  1211. }
  1212. /**
  1213. * Tests the stopWhen method with a callable
  1214. *
  1215. * @return void
  1216. */
  1217. public function testStopWhenCallable()
  1218. {
  1219. $items = [10, 20, 40, 10, 5];
  1220. $collection = (new Collection($items))->stopWhen(function ($v) {
  1221. return $v > 20;
  1222. });
  1223. $this->assertEquals([10, 20], $collection->toArray());
  1224. }
  1225. /**
  1226. * Tests the stopWhen method with a matching array
  1227. *
  1228. * @return void
  1229. */
  1230. public function testStopWhenWithArray()
  1231. {
  1232. $items = [
  1233. ['foo' => 'bar'],
  1234. ['foo' => 'baz'],
  1235. ['foo' => 'foo']
  1236. ];
  1237. $collection = (new Collection($items))->stopWhen(['foo' => 'baz']);
  1238. $this->assertEquals([['foo' => 'bar']], $collection->toArray());
  1239. }
  1240. /**
  1241. * Tests the unfold method
  1242. *
  1243. * @return void
  1244. */
  1245. public function testUnfold()
  1246. {
  1247. $items = [
  1248. [1, 2, 3, 4],
  1249. [5, 6],
  1250. [7, 8]
  1251. ];
  1252. $collection = (new Collection($items))->unfold();
  1253. $this->assertEquals(range(1, 8), $collection->toArray(false));
  1254. $items = [
  1255. [1, 2],
  1256. new Collection([3, 4])
  1257. ];
  1258. $collection = (new Collection($items))->unfold();
  1259. $this->assertEquals(range(1, 4), $collection->toArray(false));
  1260. }
  1261. /**
  1262. * Tests the unfold method with empty levels
  1263. *
  1264. * @return void
  1265. */
  1266. public function testUnfoldEmptyLevels()
  1267. {
  1268. $items = [[], [1, 2], []];
  1269. $collection = (new Collection($items))->unfold();
  1270. $this->assertEquals(range(1, 2), $collection->toArray(false));
  1271. $items = [];
  1272. $collection = (new Collection($items))->unfold();
  1273. $this->assertEmpty($collection->toArray(false));
  1274. }
  1275. /**
  1276. * Tests the unfold when passing a callable
  1277. *
  1278. * @return void
  1279. */
  1280. public function testUnfoldWithCallable()
  1281. {
  1282. $items = [1, 2, 3];
  1283. $collection = (new Collection($items))->unfold(function ($item) {
  1284. return range($item, $item * 2);
  1285. });
  1286. $expected = [1, 2, 2, 3, 4, 3, 4, 5, 6];
  1287. $this->assertEquals($expected, $collection->toArray(false));
  1288. }
  1289. /**
  1290. * Tests the through() method
  1291. *
  1292. * @return void
  1293. */
  1294. public function testThrough()
  1295. {
  1296. $items = [1, 2, 3];
  1297. $collection = (new Collection($items))->through(function ($collection) {
  1298. return $collection->append($collection->toList());
  1299. });
  1300. $this->assertEquals([1, 2, 3, 1, 2, 3], $collection->toList());
  1301. }
  1302. /**
  1303. * Tests the through method when it returns an array
  1304. *
  1305. * @return void
  1306. */
  1307. public function testThroughReturnArray()
  1308. {
  1309. $items = [1, 2, 3];
  1310. $collection = (new Collection($items))->through(function ($collection) {
  1311. $list = $collection->toList();
  1312. return array_merge($list, $list);
  1313. });
  1314. $this->assertEquals([1, 2, 3, 1, 2, 3], $collection->toList());
  1315. }
  1316. /**
  1317. * Tests that the sortBy method does not die when something that is not a
  1318. * collection is passed
  1319. *
  1320. * @return void
  1321. */
  1322. public function testComplexSortBy()
  1323. {
  1324. $results = collection([3, 7])
  1325. ->unfold(function ($value) {
  1326. return [
  1327. ['sorting' => $value * 2],
  1328. ['sorting' => $value * 2]
  1329. ];
  1330. })
  1331. ->sortBy('sorting')
  1332. ->extract('sorting')
  1333. ->toList();
  1334. $this->assertEquals([14, 14, 6, 6], $results);
  1335. }
  1336. /**
  1337. * Tests __debugInfo() or debug() usage
  1338. *
  1339. * @return void
  1340. */
  1341. public function testDebug()
  1342. {
  1343. $items = [1, 2, 3];
  1344. $collection = new Collection($items);
  1345. $result = $collection->__debugInfo();
  1346. $expected = [
  1347. 'count' => 3,
  1348. ];
  1349. $this->assertSame($expected, $result);
  1350. // Calling it again will rewind
  1351. $result = $collection->__debugInfo();
  1352. $expected = [
  1353. 'count' => 3,
  1354. ];
  1355. $this->assertSame($expected, $result);
  1356. // Make sure it also works with non rewindable iterators
  1357. $iterator = new NoRewindIterator(new ArrayIterator($items));
  1358. $collection = new Collection($iterator);
  1359. $result = $collection->__debugInfo();
  1360. $expected = [
  1361. 'count' => 3,
  1362. ];
  1363. $this->assertSame($expected, $result);
  1364. // Calling it again will in this case not rewind
  1365. $result = $collection->__debugInfo();
  1366. $expected = [
  1367. 'count' => 0,
  1368. ];
  1369. $this->assertSame($expected, $result);
  1370. }
  1371. /**
  1372. * Tests the isEmpty() method
  1373. *
  1374. * @return void
  1375. */
  1376. public function testIsEmpty()
  1377. {
  1378. $collection = new Collection([1, 2, 3]);
  1379. $this->assertFalse($collection->isEmpty());
  1380. $collection = $collection->map(function () {
  1381. return null;
  1382. });
  1383. $this->assertFalse($collection->isEmpty());
  1384. $collection = $collection->filter();
  1385. $this->assertTrue($collection->isEmpty());
  1386. }
  1387. /**
  1388. * Tests the isEmpty() method does not consume data
  1389. * from buffered iterators.
  1390. *
  1391. * @return void
  1392. */
  1393. public function testIsEmptyDoesNotConsume()
  1394. {
  1395. $array = new \ArrayIterator([1, 2, 3]);
  1396. $inner = new \Cake\Collection\Iterator\BufferedIterator($array);
  1397. $collection = new Collection($inner);
  1398. $this->assertFalse($collection->isEmpty());
  1399. $this->assertCount(3, $collection->toArray());
  1400. }
  1401. /**
  1402. * Tests the zip() method
  1403. *
  1404. * @return void
  1405. */
  1406. public function testZip()
  1407. {
  1408. $collection = new Collection([1, 2]);
  1409. $zipped = $collection->zip([3, 4]);
  1410. $this->assertEquals([[1, 3], [2, 4]], $zipped->toList());
  1411. $collection = new Collection([1, 2]);
  1412. $zipped = $collection->zip([3]);
  1413. $this->assertEquals([[1, 3]], $zipped->toList());
  1414. $collection = new Collection([1, 2]);
  1415. $zipped = $collection->zip([3, 4], [5, 6], [7, 8], [9, 10, 11]);
  1416. $this->assertEquals([
  1417. [1, 3, 5, 7, 9],
  1418. [2, 4, 6, 8, 10]
  1419. ], $zipped->toList());
  1420. }
  1421. /**
  1422. * Tests the zipWith() method
  1423. *
  1424. * @return void
  1425. */
  1426. public function testZipWith()
  1427. {
  1428. $collection = new Collection([1, 2]);
  1429. $zipped = $collection->zipWith([3, 4], function ($a, $b) {
  1430. return $a * $b;
  1431. });
  1432. $this->assertEquals([3, 8], $zipped->toList());
  1433. $zipped = $collection->zipWith([3, 4], [5, 6, 7], function () {
  1434. return array_sum(func_get_args());
  1435. });
  1436. $this->assertEquals([9, 12], $zipped->toList());
  1437. }
  1438. /**
  1439. * Tests the skip() method
  1440. *
  1441. * @return void
  1442. */
  1443. public function testSkip()
  1444. {
  1445. $collection = new Collection([1, 2, 3, 4, 5]);
  1446. $this->assertEquals([3, 4, 5], $collection->skip(2)->toList());
  1447. $this->assertEquals([5], $collection->skip(4)->toList());
  1448. }
  1449. /**
  1450. * Tests the last() method
  1451. *
  1452. * @return void
  1453. */
  1454. public function testLast()
  1455. {
  1456. $collection = new Collection([1, 2, 3]);
  1457. $this->assertEquals(3, $collection->last());
  1458. $collection = $collection->map(function ($e) {
  1459. return $e * 2;
  1460. });
  1461. $this->assertEquals(6, $collection->last());
  1462. }
  1463. /**
  1464. * Tests the last() method when on an empty collection
  1465. *
  1466. * @return void
  1467. */
  1468. public function testLAstWithEmptyCollection()
  1469. {
  1470. $collection = new Collection([]);
  1471. $this->assertNull($collection->last());
  1472. }
  1473. /**
  1474. * Tests sumOf with no parameters
  1475. *
  1476. * @return void
  1477. */
  1478. public function testSumOfWithIdentity()
  1479. {
  1480. $collection = new Collection([1, 2, 3]);
  1481. $this->assertEquals(6, $collection->sumOf());
  1482. $collection = new Collection(['a' => 1, 'b' => 4, 'c' => 6]);
  1483. $this->assertEquals(11, $collection->sumOf());
  1484. }
  1485. /**
  1486. * Tests using extract with the {*} notation
  1487. *
  1488. * @return void
  1489. */
  1490. public function testUnfoldedExtract()
  1491. {
  1492. $items = [
  1493. ['comments' => [['id' => 1], ['id' => 2]]],
  1494. ['comments' => [['id' => 3], ['id' => 4]]],
  1495. ['comments' => [['id' => 7], ['nope' => 8]]],
  1496. ];
  1497. $extracted = (new Collection($items))->extract('comments.{*}.id');
  1498. $this->assertEquals([1, 2, 3, 4, 7, null], $extracted->toArray());
  1499. $items = [
  1500. [
  1501. 'comments' => [
  1502. [
  1503. 'voters' => [['id' => 1], ['id' => 2]]
  1504. ]
  1505. ]
  1506. ],
  1507. [
  1508. 'comments' => [
  1509. [
  1510. 'voters' => [['id' => 3], ['id' => 4]]
  1511. ]
  1512. ]
  1513. ],
  1514. [
  1515. 'comments' => [
  1516. [
  1517. 'voters' => [['id' => 5], ['nope' => 'fail'], ['id' => 6]]
  1518. ]
  1519. ]
  1520. ],
  1521. [
  1522. 'comments' => [
  1523. [
  1524. 'not_voters' => [['id' => 5]]
  1525. ]
  1526. ]
  1527. ],
  1528. ['not_comments' => []]
  1529. ];
  1530. $extracted = (new Collection($items))->extract('comments.{*}.voters.{*}.id');
  1531. $expected = [1, 2, 3, 4, 5, null, 6];
  1532. $this->assertEquals($expected, $extracted->toArray());
  1533. $this->assertEquals($expected, $extracted->toList());
  1534. }
  1535. /**
  1536. * Tests serializing a simple collection
  1537. *
  1538. * @return void
  1539. */
  1540. public function testSerializeSimpleCollection()
  1541. {
  1542. $collection = new Collection([1, 2, 3]);
  1543. $selialized = serialize($collection);
  1544. $unserialized = unserialize($selialized);
  1545. $this->assertEquals($collection->toList(), $unserialized->toList());
  1546. $this->assertEquals($collection->toArray(), $unserialized->toArray());
  1547. }
  1548. /**
  1549. * Tests serialization when using append
  1550. *
  1551. * @return void
  1552. */
  1553. public function testSerializeWithAppendIterators()
  1554. {
  1555. $collection = new Collection([1, 2, 3]);
  1556. $collection = $collection->append(['a' => 4, 'b' => 5, 'c' => 6]);
  1557. $selialized = serialize($collection);
  1558. $unserialized = unserialize($selialized);
  1559. $this->assertEquals($collection->toList(), $unserialized->toList());
  1560. $this->assertEquals($collection->toArray(), $unserialized->toArray());
  1561. }
  1562. /**
  1563. * Tests serialization when using nested iterators
  1564. *
  1565. * @return void
  1566. */
  1567. public function testSerializeWithNestedIterators()
  1568. {
  1569. $collection = new Collection([1, 2, 3]);
  1570. $collection = $collection->map(function ($e) {
  1571. return $e * 3;
  1572. });
  1573. $collection = $collection->groupBy(function ($e) {
  1574. return $e % 2;
  1575. });
  1576. $selialized = serialize($collection);
  1577. $unserialized = unserialize($selialized);
  1578. $this->assertEquals($collection->toList(), $unserialized->toList());
  1579. $this->assertEquals($collection->toArray(), $unserialized->toArray());
  1580. }
  1581. /**
  1582. * Tests serializing a zip() call
  1583. *
  1584. * @return void
  1585. */
  1586. public function testSerializeWithZipIterator()
  1587. {
  1588. $collection = new Collection([4, 5]);
  1589. $collection = $collection->zip([1, 2]);
  1590. $selialized = serialize($collection);
  1591. $unserialized = unserialize($selialized);
  1592. $this->assertEquals($collection->toList(), $unserialized->toList());
  1593. }
  1594. /**
  1595. * Tests the chunk method with exact chunks
  1596. *
  1597. * @return void
  1598. */
  1599. public function testChunk()
  1600. {
  1601. $collection = new Collection(range(1, 10));
  1602. $chunked = $collection->chunk(2)->toList();
  1603. $expected = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]];
  1604. $this->assertEquals($expected, $chunked);
  1605. }
  1606. /**
  1607. * Tests the chunk method with overflowing chunk size
  1608. *
  1609. * @return void
  1610. */
  1611. public function testChunkOverflow()
  1612. {
  1613. $collection = new Collection(range(1, 11));
  1614. $chunked = $collection->chunk(2)->toList();
  1615. $expected = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]];
  1616. $this->assertEquals($expected, $chunked);
  1617. }
  1618. /**
  1619. * Tests the chunk method with non-scalar items
  1620. *
  1621. * @return void
  1622. */
  1623. public function testChunkNested()
  1624. {
  1625. $collection = new Collection([1, 2, 3, [4, 5], 6, [7, [8, 9], 10], 11]);
  1626. $chunked = $collection->chunk(2)->toList();
  1627. $expected = [[1, 2], [3, [4, 5]], [6, [7, [8, 9], 10]], [11]];
  1628. $this->assertEquals($expected, $chunked);
  1629. }
  1630. /**
  1631. * Tests the chunk method with preserved keys
  1632. *
  1633. * @return void
  1634. */
  1635. public function testChunkPreserveKeys()
  1636. {
  1637. $collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7]);
  1638. $chunked = $collection->chunk(2, true)->toList();
  1639. $expected = [['a' => 1, 'b' => 2], ['c' => 3, 'd' => 4], ['e' => 5, 'f' => 6], ['g' => 7]];
  1640. $this->assertEquals($expected, $chunked);
  1641. }
  1642. /**
  1643. * Tests cartesianProduct
  1644. *
  1645. * @return void
  1646. */
  1647. public function testCartesianProduct()
  1648. {
  1649. $collection = new Collection([]);
  1650. $result = $collection->cartesianProduct();
  1651. $expected = [];
  1652. $this->assertEquals($expected, $result->toList());
  1653. $collection = new Collection([['A', 'B', 'C'], [1, 2, 3]]);
  1654. $result = $collection->cartesianProduct();
  1655. $expected = [
  1656. ['A', 1],
  1657. ['A', 2],
  1658. ['A', 3],
  1659. ['B', 1],
  1660. ['B', 2],
  1661. ['B', 3],
  1662. ['C', 1],
  1663. ['C', 2],
  1664. ['C', 3],
  1665. ];
  1666. $this->assertEquals($expected, $result->toList());
  1667. $collection = new Collection([[1, 2, 3], ['A', 'B', 'C'], ['a', 'b', 'c']]);
  1668. $result = $collection->cartesianProduct(function ($value) {
  1669. return [strval($value[0]) . $value[1] . $value[2]];
  1670. }, function ($value) {
  1671. return $value[0] >= 2;
  1672. });
  1673. $expected = [
  1674. ['2Aa'],
  1675. ['2Ab'],
  1676. ['2Ac'],
  1677. ['2Ba'],
  1678. ['2Bb'],
  1679. ['2Bc'],
  1680. ['2Ca'],
  1681. ['2Cb'],
  1682. ['2Cc'],
  1683. ['3Aa'],
  1684. ['3Ab'],
  1685. ['3Ac'],
  1686. ['3Ba'],
  1687. ['3Bb'],
  1688. ['3Bc'],
  1689. ['3Ca'],
  1690. ['3Cb'],
  1691. ['3Cc'],
  1692. ];
  1693. $this->assertEquals($expected, $result->toList());
  1694. $collection = new Collection([['1', '2', '3', '4'], ['A', 'B', 'C'], ['name', 'surname', 'telephone']]);
  1695. $result = $collection->cartesianProduct(function ($value) {
  1696. return [$value[0] => [$value[1] => $value[2]]];
  1697. }, function ($value) {
  1698. return $value[2] !== 'surname';
  1699. });
  1700. $expected = [
  1701. [1 => ['A' => 'name']],
  1702. [1 => ['A' => 'telephone']],
  1703. [1 => ['B' => 'name']],
  1704. [1 => ['B' => 'telephone']],
  1705. [1 => ['C' => 'name']],
  1706. [1 => ['C' => 'telephone']],
  1707. [2 => ['A' => 'name']],
  1708. [2 => ['A' => 'telephone']],
  1709. [2 => ['B' => 'name']],
  1710. [2 => ['B' => 'telephone']],
  1711. [2 => ['C' => 'name']],
  1712. [2 => ['C' => 'telephone']],
  1713. [3 => ['A' => 'name']],
  1714. [3 => ['A' => 'telephone']],
  1715. [3 => ['B' => 'name']],
  1716. [3 => ['B' => 'telephone']],
  1717. [3 => ['C' => 'name']],
  1718. [3 => ['C' => 'telephone']],
  1719. [4 => ['A' => 'name']],
  1720. [4 => ['A' => 'telephone']],
  1721. [4 => ['B' => 'name']],
  1722. [4 => ['B' => 'telephone']],
  1723. [4 => ['C' => 'name']],
  1724. [4 => ['C' => 'telephone']],
  1725. ];
  1726. $this->assertEquals($expected, $result->toList());
  1727. $collection = new Collection([
  1728. [
  1729. 'name1' => 'alex',
  1730. 'name2' => 'kostas',
  1731. 0 => 'leon',
  1732. ],
  1733. [
  1734. 'val1' => 'alex@example.com',
  1735. 24 => 'kostas@example.com',
  1736. 'val2' => 'leon@example.com',
  1737. ],
  1738. ]);
  1739. $result = $collection->cartesianProduct();
  1740. $expected = [
  1741. ['alex', 'alex@example.com'],
  1742. ['alex', 'kostas@example.com'],
  1743. ['alex', 'leon@example.com'],
  1744. ['kostas', 'alex@example.com'],
  1745. ['kostas', 'kostas@example.com'],
  1746. ['kostas', 'leon@example.com'],
  1747. ['leon', 'alex@example.com'],
  1748. ['leon', 'kostas@example.com'],
  1749. ['leon', 'leon@example.com'],
  1750. ];
  1751. $this->assertEquals($expected, $result->toList());
  1752. }
  1753. /**
  1754. * Tests that an exception is thrown if the cartesian product is called with multidimensional arrays
  1755. *
  1756. * @expectedException \LogicException
  1757. * @return void
  1758. */
  1759. public function testCartesianProductMultidimensionalArray()
  1760. {
  1761. $collection = new Collection([
  1762. [
  1763. 'names' => [
  1764. 'alex', 'kostas', 'leon'
  1765. ]
  1766. ],
  1767. [
  1768. 'locations' => [
  1769. 'crete', 'london', 'paris'
  1770. ]
  1771. ],
  1772. ]);
  1773. $result = $collection->cartesianProduct();
  1774. }
  1775. public function testTranspose()
  1776. {
  1777. $collection = new Collection([
  1778. ['Products', '2012', '2013', '2014'],
  1779. ['Product A', '200', '100', '50'],
  1780. ['Product B', '300', '200', '100'],
  1781. ['Product C', '400', '300', '200'],
  1782. ]);
  1783. $transposed = $collection->transpose();
  1784. $expected = [
  1785. ['Products', 'Product A', 'Product B', 'Product C'],
  1786. ['2012', '200', '300', '400'],
  1787. ['2013', '100', '200', '300'],
  1788. ['2014', '50', '100', '200'],
  1789. ];
  1790. $this->assertEquals($expected, $transposed->toList());
  1791. }
  1792. /**
  1793. * Tests that provided arrays do not have even length
  1794. *
  1795. * @expectedException \LogicException
  1796. * @return void
  1797. */
  1798. public function testTransposeUnEvenLengthShouldThrowException()
  1799. {
  1800. $collection = new Collection([
  1801. ['Products', '2012', '2013', '2014'],
  1802. ['Product A', '200', '100', '50'],
  1803. ['Product B', '300'],
  1804. ['Product C', '400', '300'],
  1805. ]);
  1806. $collection->transpose();
  1807. }
  1808. /**
  1809. * Tests the chunk method with preserved keys
  1810. *
  1811. * @return void
  1812. */
  1813. public function testChunkPreserveKeys()
  1814. {
  1815. $collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7]);
  1816. $chunked = $collection->chunk(2, true)->toList();
  1817. $expected = [['a' => 1, 'b' => 2], ['c' => 3, 'd' => 4], ['e' => 5, 'f' => 6], ['g' => 7]];
  1818. $this->assertEquals($expected, $chunked);
  1819. }
  1820. }