Skip to content

Commit c07036e

Browse files
authored
[12.x] Add withHeartbeat method to LazyCollection (#56477)
1 parent a89e514 commit c07036e

File tree

3 files changed

+100
-0
lines changed

3 files changed

+100
-0
lines changed

src/Illuminate/Collections/LazyCollection.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use ArrayIterator;
66
use Closure;
7+
use DateInterval;
8+
use DateTimeImmutable;
79
use DateTimeInterface;
810
use Generator;
911
use Illuminate\Contracts\Support\CanBeEscapedWhenCastToString;
@@ -1763,6 +1765,42 @@ public function values()
17631765
});
17641766
}
17651767

1768+
/**
1769+
* Run the given callback every time the interval has passed.
1770+
*
1771+
* @return static<TKey, TValue>
1772+
*/
1773+
public function withHeartbeat(DateInterval|int $interval, callable $callback)
1774+
{
1775+
$seconds = is_int($interval) ? $interval : $this->intervalSeconds($interval);
1776+
1777+
return new static(function () use ($seconds, $callback) {
1778+
$start = $this->now();
1779+
1780+
foreach ($this as $key => $value) {
1781+
$now = $this->now();
1782+
1783+
if (($now - $start) >= $seconds) {
1784+
$callback();
1785+
1786+
$start = $now;
1787+
}
1788+
1789+
yield $key => $value;
1790+
}
1791+
});
1792+
}
1793+
1794+
/**
1795+
* Get the total seconds from the given interval.
1796+
*/
1797+
protected function intervalSeconds(DateInterval $interval): int
1798+
{
1799+
$start = new DateTimeImmutable();
1800+
1801+
return $start->add($interval)->getTimestamp() - $start->getTimestamp();
1802+
}
1803+
17661804
/**
17671805
* Zip the collection together with one or more arrays.
17681806
*

tests/Support/SupportLazyCollectionIsLazyTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,6 +1706,21 @@ public function testWhereStrictIsLazy()
17061706
});
17071707
}
17081708

1709+
public function testWithHeartbeatIsLazy()
1710+
{
1711+
$this->assertDoesNotEnumerate(function ($collection) {
1712+
$collection->withHeartbeat(1, function () {
1713+
// Heartbeat callback
1714+
});
1715+
});
1716+
1717+
$this->assertEnumeratesOnce(function ($collection) {
1718+
$collection->withHeartbeat(1, function () {
1719+
// Heartbeat callback
1720+
})->all();
1721+
});
1722+
}
1723+
17091724
public function testWrapIsLazy()
17101725
{
17111726
$this->assertDoesNotEnumerate(function ($collection) {

tests/Support/SupportLazyCollectionTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,4 +447,51 @@ public function testDot()
447447

448448
$this->assertEquals($expected, $dotted->all());
449449
}
450+
451+
public function testWithHeartbeat()
452+
{
453+
$start = Carbon::create(2000, 1, 1);
454+
$after2Minutes = $start->copy()->addMinutes(2);
455+
$after5Minutes = $start->copy()->addMinutes(5);
456+
$after7Minutes = $start->copy()->addMinutes(7);
457+
$after11Minutes = $start->copy()->addMinutes(11);
458+
459+
Carbon::setTestNow($start);
460+
461+
$output = new Collection();
462+
463+
$numbers = LazyCollection::range(1, 10)
464+
465+
// Move the clock to possibly trigger the heartbeat...
466+
->tapEach(fn ($number) => Carbon::setTestNow(
467+
match ($number) {
468+
3 => $after2Minutes,
469+
4 => $after5Minutes,
470+
6 => $after7Minutes,
471+
9 => $after11Minutes,
472+
default => Carbon::now(),
473+
}
474+
))
475+
476+
// Push the current date to `output` when heartbeat is triggered...
477+
->withHeartbeat(Duration::minutes(5), fn () => $output[] = Carbon::now())
478+
479+
// Push every number onto `output` as it's enumerated...
480+
->tapEach(fn ($number) => $output[] = $number)->all();
481+
482+
$this->assertEquals(range(1, 10), $numbers);
483+
484+
$this->assertEquals(
485+
[
486+
1, 2, 3,
487+
$after5Minutes,
488+
4, 5, 6, 7, 8,
489+
$after11Minutes,
490+
9, 10,
491+
],
492+
$output->all(),
493+
);
494+
495+
Carbon::setTestNow();
496+
}
450497
}

0 commit comments

Comments
 (0)