LazyCollection.php 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585
  1. <?php
  2. namespace Illuminate\Support;
  3. use ArrayIterator;
  4. use Closure;
  5. use DateTimeInterface;
  6. use Illuminate\Contracts\Support\CanBeEscapedWhenCastToString;
  7. use Illuminate\Support\Traits\EnumeratesValues;
  8. use Illuminate\Support\Traits\Macroable;
  9. use IteratorAggregate;
  10. use stdClass;
  11. class LazyCollection implements CanBeEscapedWhenCastToString, Enumerable
  12. {
  13. use EnumeratesValues, Macroable;
  14. /**
  15. * The source from which to generate items.
  16. *
  17. * @var callable|static
  18. */
  19. public $source;
  20. /**
  21. * Create a new lazy collection instance.
  22. *
  23. * @param mixed $source
  24. * @return void
  25. */
  26. public function __construct($source = null)
  27. {
  28. if ($source instanceof Closure || $source instanceof self) {
  29. $this->source = $source;
  30. } elseif (is_null($source)) {
  31. $this->source = static::empty();
  32. } else {
  33. $this->source = $this->getArrayableItems($source);
  34. }
  35. }
  36. /**
  37. * Create a collection with the given range.
  38. *
  39. * @param int $from
  40. * @param int $to
  41. * @return static
  42. */
  43. public static function range($from, $to)
  44. {
  45. return new static(function () use ($from, $to) {
  46. if ($from <= $to) {
  47. for (; $from <= $to; $from++) {
  48. yield $from;
  49. }
  50. } else {
  51. for (; $from >= $to; $from--) {
  52. yield $from;
  53. }
  54. }
  55. });
  56. }
  57. /**
  58. * Get all items in the enumerable.
  59. *
  60. * @return array
  61. */
  62. public function all()
  63. {
  64. if (is_array($this->source)) {
  65. return $this->source;
  66. }
  67. return iterator_to_array($this->getIterator());
  68. }
  69. /**
  70. * Eager load all items into a new lazy collection backed by an array.
  71. *
  72. * @return static
  73. */
  74. public function eager()
  75. {
  76. return new static($this->all());
  77. }
  78. /**
  79. * Cache values as they're enumerated.
  80. *
  81. * @return static
  82. */
  83. public function remember()
  84. {
  85. $iterator = $this->getIterator();
  86. $iteratorIndex = 0;
  87. $cache = [];
  88. return new static(function () use ($iterator, &$iteratorIndex, &$cache) {
  89. for ($index = 0; true; $index++) {
  90. if (array_key_exists($index, $cache)) {
  91. yield $cache[$index][0] => $cache[$index][1];
  92. continue;
  93. }
  94. if ($iteratorIndex < $index) {
  95. $iterator->next();
  96. $iteratorIndex++;
  97. }
  98. if (! $iterator->valid()) {
  99. break;
  100. }
  101. $cache[$index] = [$iterator->key(), $iterator->current()];
  102. yield $cache[$index][0] => $cache[$index][1];
  103. }
  104. });
  105. }
  106. /**
  107. * Get the average value of a given key.
  108. *
  109. * @param callable|string|null $callback
  110. * @return mixed
  111. */
  112. public function avg($callback = null)
  113. {
  114. return $this->collect()->avg($callback);
  115. }
  116. /**
  117. * Get the median of a given key.
  118. *
  119. * @param string|array|null $key
  120. * @return mixed
  121. */
  122. public function median($key = null)
  123. {
  124. return $this->collect()->median($key);
  125. }
  126. /**
  127. * Get the mode of a given key.
  128. *
  129. * @param string|array|null $key
  130. * @return array|null
  131. */
  132. public function mode($key = null)
  133. {
  134. return $this->collect()->mode($key);
  135. }
  136. /**
  137. * Collapse the collection of items into a single array.
  138. *
  139. * @return static
  140. */
  141. public function collapse()
  142. {
  143. return new static(function () {
  144. foreach ($this as $values) {
  145. if (is_array($values) || $values instanceof Enumerable) {
  146. foreach ($values as $value) {
  147. yield $value;
  148. }
  149. }
  150. }
  151. });
  152. }
  153. /**
  154. * Determine if an item exists in the enumerable.
  155. *
  156. * @param mixed $key
  157. * @param mixed $operator
  158. * @param mixed $value
  159. * @return bool
  160. */
  161. public function contains($key, $operator = null, $value = null)
  162. {
  163. if (func_num_args() === 1 && $this->useAsCallable($key)) {
  164. $placeholder = new stdClass;
  165. return $this->first($key, $placeholder) !== $placeholder;
  166. }
  167. if (func_num_args() === 1) {
  168. $needle = $key;
  169. foreach ($this as $value) {
  170. if ($value == $needle) {
  171. return true;
  172. }
  173. }
  174. return false;
  175. }
  176. return $this->contains($this->operatorForWhere(...func_get_args()));
  177. }
  178. /**
  179. * Determine if an item is not contained in the enumerable.
  180. *
  181. * @param mixed $key
  182. * @param mixed $operator
  183. * @param mixed $value
  184. * @return bool
  185. */
  186. public function doesntContain($key, $operator = null, $value = null)
  187. {
  188. return ! $this->contains(...func_get_args());
  189. }
  190. /**
  191. * Cross join the given iterables, returning all possible permutations.
  192. *
  193. * @param array ...$arrays
  194. * @return static
  195. */
  196. public function crossJoin(...$arrays)
  197. {
  198. return $this->passthru('crossJoin', func_get_args());
  199. }
  200. /**
  201. * Count the number of items in the collection by a field or using a callback.
  202. *
  203. * @param callable|string $countBy
  204. * @return static
  205. */
  206. public function countBy($countBy = null)
  207. {
  208. $countBy = is_null($countBy)
  209. ? $this->identity()
  210. : $this->valueRetriever($countBy);
  211. return new static(function () use ($countBy) {
  212. $counts = [];
  213. foreach ($this as $key => $value) {
  214. $group = $countBy($value, $key);
  215. if (empty($counts[$group])) {
  216. $counts[$group] = 0;
  217. }
  218. $counts[$group]++;
  219. }
  220. yield from $counts;
  221. });
  222. }
  223. /**
  224. * Get the items that are not present in the given items.
  225. *
  226. * @param mixed $items
  227. * @return static
  228. */
  229. public function diff($items)
  230. {
  231. return $this->passthru('diff', func_get_args());
  232. }
  233. /**
  234. * Get the items that are not present in the given items, using the callback.
  235. *
  236. * @param mixed $items
  237. * @param callable $callback
  238. * @return static
  239. */
  240. public function diffUsing($items, callable $callback)
  241. {
  242. return $this->passthru('diffUsing', func_get_args());
  243. }
  244. /**
  245. * Get the items whose keys and values are not present in the given items.
  246. *
  247. * @param mixed $items
  248. * @return static
  249. */
  250. public function diffAssoc($items)
  251. {
  252. return $this->passthru('diffAssoc', func_get_args());
  253. }
  254. /**
  255. * Get the items whose keys and values are not present in the given items, using the callback.
  256. *
  257. * @param mixed $items
  258. * @param callable $callback
  259. * @return static
  260. */
  261. public function diffAssocUsing($items, callable $callback)
  262. {
  263. return $this->passthru('diffAssocUsing', func_get_args());
  264. }
  265. /**
  266. * Get the items whose keys are not present in the given items.
  267. *
  268. * @param mixed $items
  269. * @return static
  270. */
  271. public function diffKeys($items)
  272. {
  273. return $this->passthru('diffKeys', func_get_args());
  274. }
  275. /**
  276. * Get the items whose keys are not present in the given items, using the callback.
  277. *
  278. * @param mixed $items
  279. * @param callable $callback
  280. * @return static
  281. */
  282. public function diffKeysUsing($items, callable $callback)
  283. {
  284. return $this->passthru('diffKeysUsing', func_get_args());
  285. }
  286. /**
  287. * Retrieve duplicate items.
  288. *
  289. * @param callable|string|null $callback
  290. * @param bool $strict
  291. * @return static
  292. */
  293. public function duplicates($callback = null, $strict = false)
  294. {
  295. return $this->passthru('duplicates', func_get_args());
  296. }
  297. /**
  298. * Retrieve duplicate items using strict comparison.
  299. *
  300. * @param callable|string|null $callback
  301. * @return static
  302. */
  303. public function duplicatesStrict($callback = null)
  304. {
  305. return $this->passthru('duplicatesStrict', func_get_args());
  306. }
  307. /**
  308. * Get all items except for those with the specified keys.
  309. *
  310. * @param mixed $keys
  311. * @return static
  312. */
  313. public function except($keys)
  314. {
  315. return $this->passthru('except', func_get_args());
  316. }
  317. /**
  318. * Run a filter over each of the items.
  319. *
  320. * @param callable|null $callback
  321. * @return static
  322. */
  323. public function filter(callable $callback = null)
  324. {
  325. if (is_null($callback)) {
  326. $callback = function ($value) {
  327. return (bool) $value;
  328. };
  329. }
  330. return new static(function () use ($callback) {
  331. foreach ($this as $key => $value) {
  332. if ($callback($value, $key)) {
  333. yield $key => $value;
  334. }
  335. }
  336. });
  337. }
  338. /**
  339. * Get the first item from the enumerable passing the given truth test.
  340. *
  341. * @param callable|null $callback
  342. * @param mixed $default
  343. * @return mixed
  344. */
  345. public function first(callable $callback = null, $default = null)
  346. {
  347. $iterator = $this->getIterator();
  348. if (is_null($callback)) {
  349. if (! $iterator->valid()) {
  350. return value($default);
  351. }
  352. return $iterator->current();
  353. }
  354. foreach ($iterator as $key => $value) {
  355. if ($callback($value, $key)) {
  356. return $value;
  357. }
  358. }
  359. return value($default);
  360. }
  361. /**
  362. * Get a flattened list of the items in the collection.
  363. *
  364. * @param int $depth
  365. * @return static
  366. */
  367. public function flatten($depth = INF)
  368. {
  369. $instance = new static(function () use ($depth) {
  370. foreach ($this as $item) {
  371. if (! is_array($item) && ! $item instanceof Enumerable) {
  372. yield $item;
  373. } elseif ($depth === 1) {
  374. yield from $item;
  375. } else {
  376. yield from (new static($item))->flatten($depth - 1);
  377. }
  378. }
  379. });
  380. return $instance->values();
  381. }
  382. /**
  383. * Flip the items in the collection.
  384. *
  385. * @return static
  386. */
  387. public function flip()
  388. {
  389. return new static(function () {
  390. foreach ($this as $key => $value) {
  391. yield $value => $key;
  392. }
  393. });
  394. }
  395. /**
  396. * Get an item by key.
  397. *
  398. * @param mixed $key
  399. * @param mixed $default
  400. * @return mixed
  401. */
  402. public function get($key, $default = null)
  403. {
  404. if (is_null($key)) {
  405. return;
  406. }
  407. foreach ($this as $outerKey => $outerValue) {
  408. if ($outerKey == $key) {
  409. return $outerValue;
  410. }
  411. }
  412. return value($default);
  413. }
  414. /**
  415. * Group an associative array by a field or using a callback.
  416. *
  417. * @param array|callable|string $groupBy
  418. * @param bool $preserveKeys
  419. * @return static
  420. */
  421. public function groupBy($groupBy, $preserveKeys = false)
  422. {
  423. return $this->passthru('groupBy', func_get_args());
  424. }
  425. /**
  426. * Key an associative array by a field or using a callback.
  427. *
  428. * @param callable|string $keyBy
  429. * @return static
  430. */
  431. public function keyBy($keyBy)
  432. {
  433. return new static(function () use ($keyBy) {
  434. $keyBy = $this->valueRetriever($keyBy);
  435. foreach ($this as $key => $item) {
  436. $resolvedKey = $keyBy($item, $key);
  437. if (is_object($resolvedKey)) {
  438. $resolvedKey = (string) $resolvedKey;
  439. }
  440. yield $resolvedKey => $item;
  441. }
  442. });
  443. }
  444. /**
  445. * Determine if an item exists in the collection by key.
  446. *
  447. * @param mixed $key
  448. * @return bool
  449. */
  450. public function has($key)
  451. {
  452. $keys = array_flip(is_array($key) ? $key : func_get_args());
  453. $count = count($keys);
  454. foreach ($this as $key => $value) {
  455. if (array_key_exists($key, $keys) && --$count == 0) {
  456. return true;
  457. }
  458. }
  459. return false;
  460. }
  461. /**
  462. * Determine if any of the keys exist in the collection.
  463. *
  464. * @param mixed $key
  465. * @return bool
  466. */
  467. public function hasAny($key)
  468. {
  469. $keys = array_flip(is_array($key) ? $key : func_get_args());
  470. foreach ($this as $key => $value) {
  471. if (array_key_exists($key, $keys)) {
  472. return true;
  473. }
  474. }
  475. return false;
  476. }
  477. /**
  478. * Concatenate values of a given key as a string.
  479. *
  480. * @param string $value
  481. * @param string|null $glue
  482. * @return string
  483. */
  484. public function implode($value, $glue = null)
  485. {
  486. return $this->collect()->implode(...func_get_args());
  487. }
  488. /**
  489. * Intersect the collection with the given items.
  490. *
  491. * @param mixed $items
  492. * @return static
  493. */
  494. public function intersect($items)
  495. {
  496. return $this->passthru('intersect', func_get_args());
  497. }
  498. /**
  499. * Intersect the collection with the given items by key.
  500. *
  501. * @param mixed $items
  502. * @return static
  503. */
  504. public function intersectByKeys($items)
  505. {
  506. return $this->passthru('intersectByKeys', func_get_args());
  507. }
  508. /**
  509. * Determine if the items are empty or not.
  510. *
  511. * @return bool
  512. */
  513. public function isEmpty()
  514. {
  515. return ! $this->getIterator()->valid();
  516. }
  517. /**
  518. * Determine if the collection contains a single item.
  519. *
  520. * @return bool
  521. */
  522. public function containsOneItem()
  523. {
  524. return $this->take(2)->count() === 1;
  525. }
  526. /**
  527. * Join all items from the collection using a string. The final items can use a separate glue string.
  528. *
  529. * @param string $glue
  530. * @param string $finalGlue
  531. * @return string
  532. */
  533. public function join($glue, $finalGlue = '')
  534. {
  535. return $this->collect()->join(...func_get_args());
  536. }
  537. /**
  538. * Get the keys of the collection items.
  539. *
  540. * @return static
  541. */
  542. public function keys()
  543. {
  544. return new static(function () {
  545. foreach ($this as $key => $value) {
  546. yield $key;
  547. }
  548. });
  549. }
  550. /**
  551. * Get the last item from the collection.
  552. *
  553. * @param callable|null $callback
  554. * @param mixed $default
  555. * @return mixed
  556. */
  557. public function last(callable $callback = null, $default = null)
  558. {
  559. $needle = $placeholder = new stdClass;
  560. foreach ($this as $key => $value) {
  561. if (is_null($callback) || $callback($value, $key)) {
  562. $needle = $value;
  563. }
  564. }
  565. return $needle === $placeholder ? value($default) : $needle;
  566. }
  567. /**
  568. * Get the values of a given key.
  569. *
  570. * @param string|array $value
  571. * @param string|null $key
  572. * @return static
  573. */
  574. public function pluck($value, $key = null)
  575. {
  576. return new static(function () use ($value, $key) {
  577. [$value, $key] = $this->explodePluckParameters($value, $key);
  578. foreach ($this as $item) {
  579. $itemValue = data_get($item, $value);
  580. if (is_null($key)) {
  581. yield $itemValue;
  582. } else {
  583. $itemKey = data_get($item, $key);
  584. if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
  585. $itemKey = (string) $itemKey;
  586. }
  587. yield $itemKey => $itemValue;
  588. }
  589. }
  590. });
  591. }
  592. /**
  593. * Run a map over each of the items.
  594. *
  595. * @param callable $callback
  596. * @return static
  597. */
  598. public function map(callable $callback)
  599. {
  600. return new static(function () use ($callback) {
  601. foreach ($this as $key => $value) {
  602. yield $key => $callback($value, $key);
  603. }
  604. });
  605. }
  606. /**
  607. * Run a dictionary map over the items.
  608. *
  609. * The callback should return an associative array with a single key/value pair.
  610. *
  611. * @param callable $callback
  612. * @return static
  613. */
  614. public function mapToDictionary(callable $callback)
  615. {
  616. return $this->passthru('mapToDictionary', func_get_args());
  617. }
  618. /**
  619. * Run an associative map over each of the items.
  620. *
  621. * The callback should return an associative array with a single key/value pair.
  622. *
  623. * @param callable $callback
  624. * @return static
  625. */
  626. public function mapWithKeys(callable $callback)
  627. {
  628. return new static(function () use ($callback) {
  629. foreach ($this as $key => $value) {
  630. yield from $callback($value, $key);
  631. }
  632. });
  633. }
  634. /**
  635. * Merge the collection with the given items.
  636. *
  637. * @param mixed $items
  638. * @return static
  639. */
  640. public function merge($items)
  641. {
  642. return $this->passthru('merge', func_get_args());
  643. }
  644. /**
  645. * Recursively merge the collection with the given items.
  646. *
  647. * @param mixed $items
  648. * @return static
  649. */
  650. public function mergeRecursive($items)
  651. {
  652. return $this->passthru('mergeRecursive', func_get_args());
  653. }
  654. /**
  655. * Create a collection by using this collection for keys and another for its values.
  656. *
  657. * @param mixed $values
  658. * @return static
  659. */
  660. public function combine($values)
  661. {
  662. return new static(function () use ($values) {
  663. $values = $this->makeIterator($values);
  664. $errorMessage = 'Both parameters should have an equal number of elements';
  665. foreach ($this as $key) {
  666. if (! $values->valid()) {
  667. trigger_error($errorMessage, E_USER_WARNING);
  668. break;
  669. }
  670. yield $key => $values->current();
  671. $values->next();
  672. }
  673. if ($values->valid()) {
  674. trigger_error($errorMessage, E_USER_WARNING);
  675. }
  676. });
  677. }
  678. /**
  679. * Union the collection with the given items.
  680. *
  681. * @param mixed $items
  682. * @return static
  683. */
  684. public function union($items)
  685. {
  686. return $this->passthru('union', func_get_args());
  687. }
  688. /**
  689. * Create a new collection consisting of every n-th element.
  690. *
  691. * @param int $step
  692. * @param int $offset
  693. * @return static
  694. */
  695. public function nth($step, $offset = 0)
  696. {
  697. return new static(function () use ($step, $offset) {
  698. $position = 0;
  699. foreach ($this->slice($offset) as $item) {
  700. if ($position % $step === 0) {
  701. yield $item;
  702. }
  703. $position++;
  704. }
  705. });
  706. }
  707. /**
  708. * Get the items with the specified keys.
  709. *
  710. * @param mixed $keys
  711. * @return static
  712. */
  713. public function only($keys)
  714. {
  715. if ($keys instanceof Enumerable) {
  716. $keys = $keys->all();
  717. } elseif (! is_null($keys)) {
  718. $keys = is_array($keys) ? $keys : func_get_args();
  719. }
  720. return new static(function () use ($keys) {
  721. if (is_null($keys)) {
  722. yield from $this;
  723. } else {
  724. $keys = array_flip($keys);
  725. foreach ($this as $key => $value) {
  726. if (array_key_exists($key, $keys)) {
  727. yield $key => $value;
  728. unset($keys[$key]);
  729. if (empty($keys)) {
  730. break;
  731. }
  732. }
  733. }
  734. }
  735. });
  736. }
  737. /**
  738. * Push all of the given items onto the collection.
  739. *
  740. * @param iterable $source
  741. * @return static
  742. */
  743. public function concat($source)
  744. {
  745. return (new static(function () use ($source) {
  746. yield from $this;
  747. yield from $source;
  748. }))->values();
  749. }
  750. /**
  751. * Get one or a specified number of items randomly from the collection.
  752. *
  753. * @param int|null $number
  754. * @return static|mixed
  755. *
  756. * @throws \InvalidArgumentException
  757. */
  758. public function random($number = null)
  759. {
  760. $result = $this->collect()->random(...func_get_args());
  761. return is_null($number) ? $result : new static($result);
  762. }
  763. /**
  764. * Replace the collection items with the given items.
  765. *
  766. * @param mixed $items
  767. * @return static
  768. */
  769. public function replace($items)
  770. {
  771. return new static(function () use ($items) {
  772. $items = $this->getArrayableItems($items);
  773. foreach ($this as $key => $value) {
  774. if (array_key_exists($key, $items)) {
  775. yield $key => $items[$key];
  776. unset($items[$key]);
  777. } else {
  778. yield $key => $value;
  779. }
  780. }
  781. foreach ($items as $key => $value) {
  782. yield $key => $value;
  783. }
  784. });
  785. }
  786. /**
  787. * Recursively replace the collection items with the given items.
  788. *
  789. * @param mixed $items
  790. * @return static
  791. */
  792. public function replaceRecursive($items)
  793. {
  794. return $this->passthru('replaceRecursive', func_get_args());
  795. }
  796. /**
  797. * Reverse items order.
  798. *
  799. * @return static
  800. */
  801. public function reverse()
  802. {
  803. return $this->passthru('reverse', func_get_args());
  804. }
  805. /**
  806. * Search the collection for a given value and return the corresponding key if successful.
  807. *
  808. * @param mixed $value
  809. * @param bool $strict
  810. * @return mixed
  811. */
  812. public function search($value, $strict = false)
  813. {
  814. $predicate = $this->useAsCallable($value)
  815. ? $value
  816. : function ($item) use ($value, $strict) {
  817. return $strict ? $item === $value : $item == $value;
  818. };
  819. foreach ($this as $key => $item) {
  820. if ($predicate($item, $key)) {
  821. return $key;
  822. }
  823. }
  824. return false;
  825. }
  826. /**
  827. * Shuffle the items in the collection.
  828. *
  829. * @param int|null $seed
  830. * @return static
  831. */
  832. public function shuffle($seed = null)
  833. {
  834. return $this->passthru('shuffle', func_get_args());
  835. }
  836. /**
  837. * Create chunks representing a "sliding window" view of the items in the collection.
  838. *
  839. * @param int $size
  840. * @param int $step
  841. * @return static
  842. */
  843. public function sliding($size = 2, $step = 1)
  844. {
  845. return new static(function () use ($size, $step) {
  846. $iterator = $this->getIterator();
  847. $chunk = [];
  848. while ($iterator->valid()) {
  849. $chunk[$iterator->key()] = $iterator->current();
  850. if (count($chunk) == $size) {
  851. yield tap(new static($chunk), function () use (&$chunk, $step) {
  852. $chunk = array_slice($chunk, $step, null, true);
  853. });
  854. // If the $step between chunks is bigger than each chunk's $size
  855. // we will skip the extra items (which should never be in any
  856. // chunk) before we continue to the next chunk in the loop.
  857. if ($step > $size) {
  858. $skip = $step - $size;
  859. for ($i = 0; $i < $skip && $iterator->valid(); $i++) {
  860. $iterator->next();
  861. }
  862. }
  863. }
  864. $iterator->next();
  865. }
  866. });
  867. }
  868. /**
  869. * Skip the first {$count} items.
  870. *
  871. * @param int $count
  872. * @return static
  873. */
  874. public function skip($count)
  875. {
  876. return new static(function () use ($count) {
  877. $iterator = $this->getIterator();
  878. while ($iterator->valid() && $count--) {
  879. $iterator->next();
  880. }
  881. while ($iterator->valid()) {
  882. yield $iterator->key() => $iterator->current();
  883. $iterator->next();
  884. }
  885. });
  886. }
  887. /**
  888. * Skip items in the collection until the given condition is met.
  889. *
  890. * @param mixed $value
  891. * @return static
  892. */
  893. public function skipUntil($value)
  894. {
  895. $callback = $this->useAsCallable($value) ? $value : $this->equality($value);
  896. return $this->skipWhile($this->negate($callback));
  897. }
  898. /**
  899. * Skip items in the collection while the given condition is met.
  900. *
  901. * @param mixed $value
  902. * @return static
  903. */
  904. public function skipWhile($value)
  905. {
  906. $callback = $this->useAsCallable($value) ? $value : $this->equality($value);
  907. return new static(function () use ($callback) {
  908. $iterator = $this->getIterator();
  909. while ($iterator->valid() && $callback($iterator->current(), $iterator->key())) {
  910. $iterator->next();
  911. }
  912. while ($iterator->valid()) {
  913. yield $iterator->key() => $iterator->current();
  914. $iterator->next();
  915. }
  916. });
  917. }
  918. /**
  919. * Get a slice of items from the enumerable.
  920. *
  921. * @param int $offset
  922. * @param int|null $length
  923. * @return static
  924. */
  925. public function slice($offset, $length = null)
  926. {
  927. if ($offset < 0 || $length < 0) {
  928. return $this->passthru('slice', func_get_args());
  929. }
  930. $instance = $this->skip($offset);
  931. return is_null($length) ? $instance : $instance->take($length);
  932. }
  933. /**
  934. * Split a collection into a certain number of groups.
  935. *
  936. * @param int $numberOfGroups
  937. * @return static
  938. */
  939. public function split($numberOfGroups)
  940. {
  941. return $this->passthru('split', func_get_args());
  942. }
  943. /**
  944. * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception.
  945. *
  946. * @param mixed $key
  947. * @param mixed $operator
  948. * @param mixed $value
  949. * @return mixed
  950. *
  951. * @throws \Illuminate\Support\ItemNotFoundException
  952. * @throws \Illuminate\Support\MultipleItemsFoundException
  953. */
  954. public function sole($key = null, $operator = null, $value = null)
  955. {
  956. $filter = func_num_args() > 1
  957. ? $this->operatorForWhere(...func_get_args())
  958. : $key;
  959. return $this
  960. ->when($filter)
  961. ->filter($filter)
  962. ->take(2)
  963. ->collect()
  964. ->sole();
  965. }
  966. /**
  967. * Get the first item in the collection but throw an exception if no matching items exist.
  968. *
  969. * @param mixed $key
  970. * @param mixed $operator
  971. * @param mixed $value
  972. * @return mixed
  973. *
  974. * @throws \Illuminate\Support\ItemNotFoundException
  975. */
  976. public function firstOrFail($key = null, $operator = null, $value = null)
  977. {
  978. $filter = func_num_args() > 1
  979. ? $this->operatorForWhere(...func_get_args())
  980. : $key;
  981. return $this
  982. ->when($filter)
  983. ->filter($filter)
  984. ->take(1)
  985. ->collect()
  986. ->firstOrFail();
  987. }
  988. /**
  989. * Chunk the collection into chunks of the given size.
  990. *
  991. * @param int $size
  992. * @return static
  993. */
  994. public function chunk($size)
  995. {
  996. if ($size <= 0) {
  997. return static::empty();
  998. }
  999. return new static(function () use ($size) {
  1000. $iterator = $this->getIterator();
  1001. while ($iterator->valid()) {
  1002. $chunk = [];
  1003. while (true) {
  1004. $chunk[$iterator->key()] = $iterator->current();
  1005. if (count($chunk) < $size) {
  1006. $iterator->next();
  1007. if (! $iterator->valid()) {
  1008. break;
  1009. }
  1010. } else {
  1011. break;
  1012. }
  1013. }
  1014. yield new static($chunk);
  1015. $iterator->next();
  1016. }
  1017. });
  1018. }
  1019. /**
  1020. * Split a collection into a certain number of groups, and fill the first groups completely.
  1021. *
  1022. * @param int $numberOfGroups
  1023. * @return static
  1024. */
  1025. public function splitIn($numberOfGroups)
  1026. {
  1027. return $this->chunk(ceil($this->count() / $numberOfGroups));
  1028. }
  1029. /**
  1030. * Chunk the collection into chunks with a callback.
  1031. *
  1032. * @param callable $callback
  1033. * @return static
  1034. */
  1035. public function chunkWhile(callable $callback)
  1036. {
  1037. return new static(function () use ($callback) {
  1038. $iterator = $this->getIterator();
  1039. $chunk = new Collection;
  1040. if ($iterator->valid()) {
  1041. $chunk[$iterator->key()] = $iterator->current();
  1042. $iterator->next();
  1043. }
  1044. while ($iterator->valid()) {
  1045. if (! $callback($iterator->current(), $iterator->key(), $chunk)) {
  1046. yield new static($chunk);
  1047. $chunk = new Collection;
  1048. }
  1049. $chunk[$iterator->key()] = $iterator->current();
  1050. $iterator->next();
  1051. }
  1052. if ($chunk->isNotEmpty()) {
  1053. yield new static($chunk);
  1054. }
  1055. });
  1056. }
  1057. /**
  1058. * Sort through each item with a callback.
  1059. *
  1060. * @param callable|null|int $callback
  1061. * @return static
  1062. */
  1063. public function sort($callback = null)
  1064. {
  1065. return $this->passthru('sort', func_get_args());
  1066. }
  1067. /**
  1068. * Sort items in descending order.
  1069. *
  1070. * @param int $options
  1071. * @return static
  1072. */
  1073. public function sortDesc($options = SORT_REGULAR)
  1074. {
  1075. return $this->passthru('sortDesc', func_get_args());
  1076. }
  1077. /**
  1078. * Sort the collection using the given callback.
  1079. *
  1080. * @param callable|string $callback
  1081. * @param int $options
  1082. * @param bool $descending
  1083. * @return static
  1084. */
  1085. public function sortBy($callback, $options = SORT_REGULAR, $descending = false)
  1086. {
  1087. return $this->passthru('sortBy', func_get_args());
  1088. }
  1089. /**
  1090. * Sort the collection in descending order using the given callback.
  1091. *
  1092. * @param callable|string $callback
  1093. * @param int $options
  1094. * @return static
  1095. */
  1096. public function sortByDesc($callback, $options = SORT_REGULAR)
  1097. {
  1098. return $this->passthru('sortByDesc', func_get_args());
  1099. }
  1100. /**
  1101. * Sort the collection keys.
  1102. *
  1103. * @param int $options
  1104. * @param bool $descending
  1105. * @return static
  1106. */
  1107. public function sortKeys($options = SORT_REGULAR, $descending = false)
  1108. {
  1109. return $this->passthru('sortKeys', func_get_args());
  1110. }
  1111. /**
  1112. * Sort the collection keys in descending order.
  1113. *
  1114. * @param int $options
  1115. * @return static
  1116. */
  1117. public function sortKeysDesc($options = SORT_REGULAR)
  1118. {
  1119. return $this->passthru('sortKeysDesc', func_get_args());
  1120. }
  1121. /**
  1122. * Sort the collection keys using a callback.
  1123. *
  1124. * @param callable $callback
  1125. * @return static
  1126. */
  1127. public function sortKeysUsing(callable $callback)
  1128. {
  1129. return $this->passthru('sortKeysUsing', func_get_args());
  1130. }
  1131. /**
  1132. * Take the first or last {$limit} items.
  1133. *
  1134. * @param int $limit
  1135. * @return static
  1136. */
  1137. public function take($limit)
  1138. {
  1139. if ($limit < 0) {
  1140. return $this->passthru('take', func_get_args());
  1141. }
  1142. return new static(function () use ($limit) {
  1143. $iterator = $this->getIterator();
  1144. while ($limit--) {
  1145. if (! $iterator->valid()) {
  1146. break;
  1147. }
  1148. yield $iterator->key() => $iterator->current();
  1149. if ($limit) {
  1150. $iterator->next();
  1151. }
  1152. }
  1153. });
  1154. }
  1155. /**
  1156. * Take items in the collection until the given condition is met.
  1157. *
  1158. * @param mixed $value
  1159. * @return static
  1160. */
  1161. public function takeUntil($value)
  1162. {
  1163. $callback = $this->useAsCallable($value) ? $value : $this->equality($value);
  1164. return new static(function () use ($callback) {
  1165. foreach ($this as $key => $item) {
  1166. if ($callback($item, $key)) {
  1167. break;
  1168. }
  1169. yield $key => $item;
  1170. }
  1171. });
  1172. }
  1173. /**
  1174. * Take items in the collection until a given point in time.
  1175. *
  1176. * @param \DateTimeInterface $timeout
  1177. * @return static
  1178. */
  1179. public function takeUntilTimeout(DateTimeInterface $timeout)
  1180. {
  1181. $timeout = $timeout->getTimestamp();
  1182. return $this->takeWhile(function () use ($timeout) {
  1183. return $this->now() < $timeout;
  1184. });
  1185. }
  1186. /**
  1187. * Take items in the collection while the given condition is met.
  1188. *
  1189. * @param mixed $value
  1190. * @return static
  1191. */
  1192. public function takeWhile($value)
  1193. {
  1194. $callback = $this->useAsCallable($value) ? $value : $this->equality($value);
  1195. return $this->takeUntil(function ($item, $key) use ($callback) {
  1196. return ! $callback($item, $key);
  1197. });
  1198. }
  1199. /**
  1200. * Pass each item in the collection to the given callback, lazily.
  1201. *
  1202. * @param callable $callback
  1203. * @return static
  1204. */
  1205. public function tapEach(callable $callback)
  1206. {
  1207. return new static(function () use ($callback) {
  1208. foreach ($this as $key => $value) {
  1209. $callback($value, $key);
  1210. yield $key => $value;
  1211. }
  1212. });
  1213. }
  1214. /**
  1215. * Convert a flatten "dot" notation array into an expanded array.
  1216. *
  1217. * @return static
  1218. */
  1219. public function undot()
  1220. {
  1221. return $this->passthru('undot', []);
  1222. }
  1223. /**
  1224. * Return only unique items from the collection array.
  1225. *
  1226. * @param string|callable|null $key
  1227. * @param bool $strict
  1228. * @return static
  1229. */
  1230. public function unique($key = null, $strict = false)
  1231. {
  1232. $callback = $this->valueRetriever($key);
  1233. return new static(function () use ($callback, $strict) {
  1234. $exists = [];
  1235. foreach ($this as $key => $item) {
  1236. if (! in_array($id = $callback($item, $key), $exists, $strict)) {
  1237. yield $key => $item;
  1238. $exists[] = $id;
  1239. }
  1240. }
  1241. });
  1242. }
  1243. /**
  1244. * Reset the keys on the underlying array.
  1245. *
  1246. * @return static
  1247. */
  1248. public function values()
  1249. {
  1250. return new static(function () {
  1251. foreach ($this as $item) {
  1252. yield $item;
  1253. }
  1254. });
  1255. }
  1256. /**
  1257. * Zip the collection together with one or more arrays.
  1258. *
  1259. * e.g. new LazyCollection([1, 2, 3])->zip([4, 5, 6]);
  1260. * => [[1, 4], [2, 5], [3, 6]]
  1261. *
  1262. * @param mixed ...$items
  1263. * @return static
  1264. */
  1265. public function zip($items)
  1266. {
  1267. $iterables = func_get_args();
  1268. return new static(function () use ($iterables) {
  1269. $iterators = Collection::make($iterables)->map(function ($iterable) {
  1270. return $this->makeIterator($iterable);
  1271. })->prepend($this->getIterator());
  1272. while ($iterators->contains->valid()) {
  1273. yield new static($iterators->map->current());
  1274. $iterators->each->next();
  1275. }
  1276. });
  1277. }
  1278. /**
  1279. * Pad collection to the specified length with a value.
  1280. *
  1281. * @param int $size
  1282. * @param mixed $value
  1283. * @return static
  1284. */
  1285. public function pad($size, $value)
  1286. {
  1287. if ($size < 0) {
  1288. return $this->passthru('pad', func_get_args());
  1289. }
  1290. return new static(function () use ($size, $value) {
  1291. $yielded = 0;
  1292. foreach ($this as $index => $item) {
  1293. yield $index => $item;
  1294. $yielded++;
  1295. }
  1296. while ($yielded++ < $size) {
  1297. yield $value;
  1298. }
  1299. });
  1300. }
  1301. /**
  1302. * Get the values iterator.
  1303. *
  1304. * @return \Traversable
  1305. */
  1306. #[\ReturnTypeWillChange]
  1307. public function getIterator()
  1308. {
  1309. return $this->makeIterator($this->source);
  1310. }
  1311. /**
  1312. * Count the number of items in the collection.
  1313. *
  1314. * @return int
  1315. */
  1316. #[\ReturnTypeWillChange]
  1317. public function count()
  1318. {
  1319. if (is_array($this->source)) {
  1320. return count($this->source);
  1321. }
  1322. return iterator_count($this->getIterator());
  1323. }
  1324. /**
  1325. * Make an iterator from the given source.
  1326. *
  1327. * @param mixed $source
  1328. * @return \Traversable
  1329. */
  1330. protected function makeIterator($source)
  1331. {
  1332. if ($source instanceof IteratorAggregate) {
  1333. return $source->getIterator();
  1334. }
  1335. if (is_array($source)) {
  1336. return new ArrayIterator($source);
  1337. }
  1338. return $source();
  1339. }
  1340. /**
  1341. * Explode the "value" and "key" arguments passed to "pluck".
  1342. *
  1343. * @param string|array $value
  1344. * @param string|array|null $key
  1345. * @return array
  1346. */
  1347. protected function explodePluckParameters($value, $key)
  1348. {
  1349. $value = is_string($value) ? explode('.', $value) : $value;
  1350. $key = is_null($key) || is_array($key) ? $key : explode('.', $key);
  1351. return [$value, $key];
  1352. }
  1353. /**
  1354. * Pass this lazy collection through a method on the collection class.
  1355. *
  1356. * @param string $method
  1357. * @param array $params
  1358. * @return static
  1359. */
  1360. protected function passthru($method, array $params)
  1361. {
  1362. return new static(function () use ($method, $params) {
  1363. yield from $this->collect()->$method(...$params);
  1364. });
  1365. }
  1366. /**
  1367. * Get the current time.
  1368. *
  1369. * @return int
  1370. */
  1371. protected function now()
  1372. {
  1373. return time();
  1374. }
  1375. }