CollectionTest.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since CakePHP(tm) v 3.0.0
  13. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  14. */
  15. namespace Cake\Test\TestCase\Collection;
  16. use Cake\Collection\Collection;
  17. use Cake\TestSuite\TestCase;
  18. /**
  19. * CollectionTest
  20. *
  21. */
  22. class CollectionTest extends TestCase {
  23. /**
  24. * Tests that it is possible to convert an array into a collection
  25. *
  26. * @return void
  27. */
  28. public function testArrayIsWrapped() {
  29. $items = [1, 2, 3];
  30. $collection = new Collection($items);
  31. $this->assertEquals($items, iterator_to_array($collection));
  32. }
  33. /**
  34. * Tests that it is possible to convert an iterator into a collection
  35. *
  36. * @return void
  37. */
  38. public function testIteratorIsWrapped() {
  39. $items = new \ArrayObject([1, 2, 3]);
  40. $collection = new Collection($items);
  41. $this->assertEquals(iterator_to_array($items), iterator_to_array($collection));
  42. }
  43. /**
  44. * Test running a method over all elements in the collection
  45. *
  46. * @return void
  47. */
  48. public function testEeach() {
  49. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  50. $collection = new Collection($items);
  51. $callable = $this->getMock('stdClass', ['__invoke']);
  52. $callable->expects($this->at(0))
  53. ->method('__invoke')
  54. ->with(1, 'a');
  55. $callable->expects($this->at(1))
  56. ->method('__invoke')
  57. ->with(2, 'b');
  58. $callable->expects($this->at(2))
  59. ->method('__invoke')
  60. ->with(3, 'c');
  61. $collection->each($callable);
  62. }
  63. /**
  64. * Tests that it is possible to chain filter() as it returns a collection object
  65. *
  66. * @return void
  67. */
  68. public function testFilterChaining() {
  69. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  70. $collection = new Collection($items);
  71. $callable = $this->getMock('stdClass', ['__invoke']);
  72. $callable->expects($this->once())
  73. ->method('__invoke')
  74. ->with(3, 'c');
  75. $filtered = $collection->filter(function ($value, $key, $iterator) {
  76. return $value > 2;
  77. });
  78. $this->assertInstanceOf('\Cake\Collection\Collection', $filtered);
  79. $filtered->each($callable);
  80. }
  81. /**
  82. * Tests reject
  83. *
  84. * @return void
  85. */
  86. public function testReject() {
  87. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  88. $collection = new Collection($items);
  89. $result = $collection->reject(function ($v, $k, $items) use ($collection) {
  90. $this->assertSame($collection, $items);
  91. return $v > 2;
  92. });
  93. $this->assertEquals(['a' => 1, 'b' => 2], iterator_to_array($result));
  94. $this->assertInstanceOf('\Cake\Collection\Collection', $result);
  95. }
  96. /**
  97. * Tests every when the callback returns true for all elements
  98. *
  99. * @return void
  100. */
  101. public function testEveryReturnTrue() {
  102. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  103. $collection = new Collection($items);
  104. $callable = $this->getMock('stdClass', ['__invoke']);
  105. $callable->expects($this->at(0))
  106. ->method('__invoke')
  107. ->with(1, 'a')
  108. ->will($this->returnValue(true));
  109. $callable->expects($this->at(1))
  110. ->method('__invoke')
  111. ->with(2, 'b')
  112. ->will($this->returnValue(true));
  113. $callable->expects($this->at(2))
  114. ->method('__invoke')
  115. ->with(3, 'c')
  116. ->will($this->returnValue(true));
  117. $this->assertTrue($collection->every($callable));
  118. }
  119. /**
  120. * Tests every when the callback returns false for one of the elements
  121. *
  122. * @return void
  123. */
  124. public function testEveryReturnFalse() {
  125. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  126. $collection = new Collection($items);
  127. $callable = $this->getMock('stdClass', ['__invoke']);
  128. $callable->expects($this->at(0))
  129. ->method('__invoke')
  130. ->with(1, 'a')
  131. ->will($this->returnValue(true));
  132. $callable->expects($this->at(1))
  133. ->method('__invoke')
  134. ->with(2, 'b')
  135. ->will($this->returnValue(false));
  136. $callable->expects($this->exactly(2))->method('__invoke');
  137. $this->assertFalse($collection->every($callable));
  138. }
  139. /**
  140. * Tests some() when one of the calls return true
  141. *
  142. * @return void
  143. */
  144. public function testSomeReturnTrue() {
  145. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  146. $collection = new Collection($items);
  147. $callable = $this->getMock('stdClass', ['__invoke']);
  148. $callable->expects($this->at(0))
  149. ->method('__invoke')
  150. ->with(1, 'a')
  151. ->will($this->returnValue(false));
  152. $callable->expects($this->at(1))
  153. ->method('__invoke')
  154. ->with(2, 'b')
  155. ->will($this->returnValue(true));
  156. $callable->expects($this->exactly(2))->method('__invoke');
  157. $this->assertTrue($collection->some($callable));
  158. }
  159. /**
  160. * Tests some() when none of the calls return true
  161. *
  162. * @return void
  163. */
  164. public function testSomeReturnFalse() {
  165. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  166. $collection = new Collection($items);
  167. $callable = $this->getMock('stdClass', ['__invoke']);
  168. $callable->expects($this->at(0))
  169. ->method('__invoke')
  170. ->with(1, 'a')
  171. ->will($this->returnValue(false));
  172. $callable->expects($this->at(1))
  173. ->method('__invoke')
  174. ->with(2, 'b')
  175. ->will($this->returnValue(false));
  176. $callable->expects($this->at(2))
  177. ->method('__invoke')
  178. ->with(3, 'c')
  179. ->will($this->returnValue(false));
  180. $this->assertFalse($collection->some($callable));
  181. }
  182. /**
  183. * Tests contains
  184. *
  185. * @return void
  186. */
  187. public function testContains() {
  188. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  189. $collection = new Collection($items);
  190. $this->assertTrue($collection->contains(2));
  191. $this->assertTrue($collection->contains(1));
  192. $this->assertFalse($collection->contains(10));
  193. $this->assertFalse($collection->contains('2'));
  194. }
  195. /**
  196. * Tests map
  197. *
  198. * @return void
  199. */
  200. public function testMap() {
  201. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  202. $collection = new Collection($items);
  203. $map = $collection->map(function($v, $k, $it) use ($collection) {
  204. $this->assertSame($collection, $it);
  205. return $v * $v;
  206. });
  207. $this->assertInstanceOf('\Cake\Collection\Iterator\ReplaceIterator', $map);
  208. $this->assertEquals(['a' => 1, 'b' => 4, 'c' => 9], iterator_to_array($map));
  209. }
  210. /**
  211. * Tests reduce
  212. *
  213. * @return void
  214. */
  215. public function testReduce() {
  216. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  217. $collection = new Collection($items);
  218. $callable = $this->getMock('stdClass', ['__invoke']);
  219. $callable->expects($this->at(0))
  220. ->method('__invoke')
  221. ->with(10, 1, 'a')
  222. ->will($this->returnValue(11));
  223. $callable->expects($this->at(1))
  224. ->method('__invoke')
  225. ->with(11, 2, 'b')
  226. ->will($this->returnValue(13));
  227. $callable->expects($this->at(2))
  228. ->method('__invoke')
  229. ->with(13, 3, 'c')
  230. ->will($this->returnValue(16));
  231. $this->assertEquals(16, $collection->reduce($callable, 10));
  232. }
  233. /**
  234. * Tests extract
  235. *
  236. * @return void
  237. */
  238. public function testExtract() {
  239. $items = [['a' => ['b' => ['c' => 1]]], 2];
  240. $collection = new Collection($items);
  241. $map = $collection->extract('a.b.c');
  242. $this->assertInstanceOf('\Cake\Collection\Iterator\ExtractIterator', $map);
  243. $this->assertEquals([1, null], iterator_to_array($map));
  244. }
  245. /**
  246. * Tests sort
  247. *
  248. * @return void
  249. */
  250. public function testSortString() {
  251. $items = [
  252. ['a' => ['b' => ['c' => 4]]],
  253. ['a' => ['b' => ['c' => 10]]],
  254. ['a' => ['b' => ['c' => 6]]]
  255. ];
  256. $collection = new Collection($items);
  257. $map = $collection->sortBy('a.b.c');
  258. $this->assertInstanceOf('\Cake\Collection\Collection', $map);
  259. $expected = [
  260. 2 => ['a' => ['b' => ['c' => 10]]],
  261. 1 => ['a' => ['b' => ['c' => 6]]],
  262. 0 => ['a' => ['b' => ['c' => 4]]],
  263. ];
  264. $this->assertEquals($expected, iterator_to_array($map));
  265. }
  266. /**
  267. * Tests max
  268. *
  269. * @return void
  270. */
  271. public function testMax() {
  272. $items = [
  273. ['a' => ['b' => ['c' => 4]]],
  274. ['a' => ['b' => ['c' => 10]]],
  275. ['a' => ['b' => ['c' => 6]]]
  276. ];
  277. $collection = new Collection($items);
  278. $this->assertEquals(['a' => ['b' => ['c' => 10]]], $collection->max('a.b.c'));
  279. $callback = function($e) {
  280. return sin($e['a']['b']['c']);
  281. };
  282. $this->assertEquals(['a' => ['b' => ['c' => 4]]], $collection->max($callback));
  283. }
  284. /**
  285. * Tests min
  286. *
  287. * @return void
  288. */
  289. public function testMin() {
  290. $items = [
  291. ['a' => ['b' => ['c' => 4]]],
  292. ['a' => ['b' => ['c' => 10]]],
  293. ['a' => ['b' => ['c' => 6]]]
  294. ];
  295. $collection = new Collection($items);
  296. $this->assertEquals(['a' => ['b' => ['c' => 4]]], $collection->min('a.b.c'));
  297. }
  298. /**
  299. * Tests groupBy
  300. *
  301. * @return void
  302. */
  303. public function testGroupBy() {
  304. $items = [
  305. ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  306. ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  307. ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  308. ];
  309. $collection = new Collection($items);
  310. $grouped = $collection->groupBy('parent_id');
  311. $expected = [
  312. 10 => [
  313. ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  314. ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  315. ],
  316. 11 => [
  317. ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  318. ]
  319. ];
  320. $this->assertEquals($expected, iterator_to_array($grouped));
  321. $this->assertInstanceOf('\Cake\Collection\Collection', $grouped);
  322. $grouped = $collection->groupBy(function($element) {
  323. return $element['parent_id'];
  324. });
  325. $this->assertEquals($expected, iterator_to_array($grouped));
  326. }
  327. /**
  328. * Tests grouping by a deep key
  329. *
  330. * @return void
  331. */
  332. public function testGroupByDeepKey() {
  333. $items = [
  334. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  335. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  336. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  337. ];
  338. $collection = new Collection($items);
  339. $grouped = $collection->groupBy('thing.parent_id');
  340. $expected = [
  341. 10 => [
  342. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  343. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  344. ],
  345. 11 => [
  346. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  347. ]
  348. ];
  349. $this->assertEquals($expected, iterator_to_array($grouped));
  350. }
  351. /**
  352. * Tests indexBy
  353. *
  354. * @return void
  355. */
  356. public function testIndexBy() {
  357. $items = [
  358. ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  359. ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  360. ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  361. ];
  362. $collection = new Collection($items);
  363. $grouped = $collection->indexBy('id');
  364. $expected = [
  365. 1 => ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  366. 3 => ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  367. 2 => ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  368. ];
  369. $this->assertEquals($expected, iterator_to_array($grouped));
  370. $this->assertInstanceOf('\Cake\Collection\Collection', $grouped);
  371. $grouped = $collection->indexBy(function($element) {
  372. return $element['id'];
  373. });
  374. $this->assertEquals($expected, iterator_to_array($grouped));
  375. }
  376. /**
  377. * Tests indexBy with a deep property
  378. *
  379. * @return void
  380. */
  381. public function testIndexByDeep() {
  382. $items = [
  383. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  384. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  385. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  386. ];
  387. $collection = new Collection($items);
  388. $grouped = $collection->indexBy('thing.parent_id');
  389. $expected = [
  390. 10 => ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  391. 11 => ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  392. ];
  393. $this->assertEquals($expected, iterator_to_array($grouped));
  394. }
  395. /**
  396. * Tests countBy
  397. *
  398. * @return void
  399. */
  400. public function testCountBy() {
  401. $items = [
  402. ['id' => 1, 'name' => 'foo', 'parent_id' => 10],
  403. ['id' => 2, 'name' => 'bar', 'parent_id' => 11],
  404. ['id' => 3, 'name' => 'baz', 'parent_id' => 10],
  405. ];
  406. $collection = new Collection($items);
  407. $grouped = $collection->countBy('parent_id');
  408. $expected = [
  409. 10 => 2,
  410. 11 => 1
  411. ];
  412. $this->assertEquals($expected, iterator_to_array($grouped));
  413. $this->assertInstanceOf('\Cake\Collection\Collection', $grouped);
  414. $grouped = $collection->countBy(function($element) {
  415. return $element['parent_id'];
  416. });
  417. $this->assertEquals($expected, iterator_to_array($grouped));
  418. }
  419. /**
  420. * Tests shuffle
  421. *
  422. * @return void
  423. */
  424. public function testShuffle() {
  425. $data = [1, 2, 3, 4];
  426. $collection = (new Collection($data))->shuffle();
  427. $this->assertEquals(count($data), count(iterator_to_array($collection)));
  428. foreach ($collection as $value) {
  429. $this->assertContains($value, $data);
  430. }
  431. }
  432. /**
  433. * Tests sample
  434. *
  435. * @return void
  436. */
  437. public function testSample() {
  438. $data = [1, 2, 3, 4];
  439. $collection = (new Collection($data))->sample(2);
  440. $this->assertEquals(2, count(iterator_to_array($collection)));
  441. foreach ($collection as $value) {
  442. $this->assertContains($value, $data);
  443. }
  444. }
  445. /**
  446. * Test toArray method
  447. *
  448. * @return void
  449. */
  450. public function testToArray() {
  451. $data = [1, 2, 3, 4];
  452. $collection = new Collection($data);
  453. $this->assertEquals($data, $collection->toArray());
  454. }
  455. /**
  456. * Test json enconding
  457. *
  458. * @return void
  459. */
  460. public function testToJson() {
  461. $data = [1, 2, 3, 4];
  462. $collection = new Collection($data);
  463. $this->assertEquals(json_encode($data), json_encode($collection));
  464. }
  465. /**
  466. * Tests that only arrays and Traversables are allowed in the constructor
  467. *
  468. * @expectedException \InvalidArgumentException
  469. * @expectedExceptionMessage Only array or \Traversable are allowed for Collection
  470. * @return void
  471. */
  472. public function testInvalidConstructorArgument() {
  473. new Collection('Derp');
  474. }
  475. /**
  476. * Tests take method
  477. *
  478. * @return void
  479. */
  480. public function testTake() {
  481. $data = [1, 2, 3, 4];
  482. $collection = new Collection($data);
  483. $taken = $collection->take(2);
  484. $this->assertEquals([1, 2], $taken->toArray());
  485. $taken = $collection->take(3);
  486. $this->assertEquals([1, 2, 3], $taken->toArray());
  487. $taken = $collection->take(500);
  488. $this->assertEquals([1, 2, 3, 4], $taken->toArray());
  489. $taken = $collection->take(1);
  490. $this->assertEquals([1], $taken->toArray());
  491. $taken = $collection->take();
  492. $this->assertEquals([1], $taken->toArray());
  493. $taken = $collection->take(2, 2);
  494. $this->assertEquals([2 => 3, 3 => 4], $taken->toArray());
  495. }
  496. /**
  497. * Tests match
  498. *
  499. * @return void
  500. */
  501. public function testMatch() {
  502. $items = [
  503. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  504. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  505. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  506. ];
  507. $collection = new Collection($items);
  508. $matched = $collection->match(['thing.parent_id' => 10, 'name' => 'baz']);
  509. $this->assertEquals([2 => $items[2]], $matched->toArray());
  510. $matched = $collection->match(['thing.parent_id' => 10]);
  511. $this->assertEquals(
  512. [0 => $items[0], 2 => $items[2]],
  513. $matched->toArray()
  514. );
  515. $matched = $collection->match(['thing.parent_id' => 500]);
  516. $this->assertEquals([], $matched->toArray());
  517. $matched = $collection->match(['parent_id' => 10, 'name' => 'baz']);
  518. $this->assertEquals([], $matched->toArray());
  519. }
  520. /**
  521. * Tests firstMatch
  522. *
  523. * @return void
  524. */
  525. public function testFirstMatch() {
  526. $items = [
  527. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  528. ['id' => 2, 'name' => 'bar', 'thing' => ['parent_id' => 11]],
  529. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  530. ];
  531. $collection = new Collection($items);
  532. $matched = $collection->firstMatch(['thing.parent_id' => 10]);
  533. $this->assertEquals(
  534. ['id' => 1, 'name' => 'foo', 'thing' => ['parent_id' => 10]],
  535. $matched
  536. );
  537. $matched = $collection->firstMatch(['thing.parent_id' => 10, 'name' => 'baz']);
  538. $this->assertEquals(
  539. ['id' => 3, 'name' => 'baz', 'thing' => ['parent_id' => 10]],
  540. $matched
  541. );
  542. }
  543. /**
  544. * Tests the append method
  545. *
  546. * @return void
  547. */
  548. public function testAppend() {
  549. $collection = new Collection([1, 2, 3]);
  550. $combined = $collection->append([4, 5, 6]);
  551. $this->assertEquals([1, 2, 3, 4, 5, 6], $combined->toArray(false));
  552. $collection = new Collection(['a' => 1, 'b' => 2]);
  553. $combined = $collection->append(['c' => 3, 'a' => 4]);
  554. $this->assertEquals(['a' => 4, 'b' => 2, 'c' => 3], $combined->toArray());
  555. }
  556. /**
  557. * Tests that by calling compile internal iteration operations are not done
  558. * more than once
  559. *
  560. * @return void
  561. */
  562. public function testCompile() {
  563. $items = ['a' => 1, 'b' => 2, 'c' => 3];
  564. $collection = new Collection($items);
  565. $callable = $this->getMock('stdClass', ['__invoke']);
  566. $callable->expects($this->at(0))
  567. ->method('__invoke')
  568. ->with(1, 'a')
  569. ->will($this->returnValue(4));
  570. $callable->expects($this->at(1))
  571. ->method('__invoke')
  572. ->with(2, 'b')
  573. ->will($this->returnValue(5));
  574. $callable->expects($this->at(2))
  575. ->method('__invoke')
  576. ->with(3, 'c')
  577. ->will($this->returnValue(6));
  578. $compiled = $collection->map($callable)->compile();
  579. $this->assertEquals(['a' => 4, 'b' => 5, 'c' => 6], $compiled->toArray());
  580. $this->assertEquals(['a' => 4, 'b' => 5, 'c' => 6], $compiled->toArray());
  581. }
  582. }