CollectionTest.php 86 KB

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