Skip to content

Commit 7886b6e

Browse files
authored
feature: trackDeferred, deprecating addDeferredToTimer (#43)
* feature: trackDeferred, deprecating addDeferredToTimer closes #9 * build: php 8.2 compatibility
1 parent 669766f commit 7886b6e

4 files changed

Lines changed: 112 additions & 30 deletions

File tree

composer.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828

2929
"scripts": {
3030
"phpunit": "vendor/bin/phpunit --configuration phpunit.xml",
31-
"phpunit:coverage": "XDEBUG_MODE=coverage vendor/bin/phpunit --configuration phpunit.xml --coverage-text",
3231
"phpstan": "vendor/bin/phpstan analyse --level 6 src",
3332
"phpcs": "vendor/bin/phpcs src --standard=phpcs.xml",
3433
"phpmd": "vendor/bin/phpmd src/ text phpmd.xml",

composer.lock

Lines changed: 24 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Loop.php

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,37 @@ public function addTimer(Timer $timer):void {
4646
$this->timerList [] = $timer;
4747
}
4848

49+
/**
50+
* Track a Deferred within the loop lifecycle without attaching its
51+
* process callbacks to a Timer.
52+
*
53+
* This is useful for libraries that manage their own work scheduling but
54+
* still want the loop to halt when all tracked Deferred objects complete.
55+
*/
56+
public function trackDeferred(Deferred $deferred):void {
57+
if($this->isDeferredTracked($deferred)) {
58+
return;
59+
}
60+
61+
$deferred->onComplete(
62+
function() use ($deferred) {
63+
$this->removeDeferred($deferred);
64+
});
65+
66+
$this->activeDeferred[] = $deferred;
67+
}
68+
69+
/**
70+
* @deprecated Prefer trackDeferred() when only completion tracking is
71+
* required. This method remains for Deferred objects whose process
72+
* callbacks should still be attached to a Timer.
73+
*/
4974
public function addDeferredToTimer(
5075
Deferred $deferred,
5176
?Timer $timer = null
5277
):void {
5378
$timer = $timer ?? $this->timerList[0];
54-
79+
$this->trackDeferred($deferred);
5580
$deferred->onComplete(
5681
function() use ($deferred, $timer) {
5782
$this->removeDeferredFromTimer(
@@ -63,11 +88,13 @@ function() use ($deferred, $timer) {
6388
foreach($deferred->getProcessList() as $function) {
6489
$timer->addCallback($function);
6590
}
66-
67-
$this->activeDeferred[] = $deferred;
6891
}
6992

7093

94+
/**
95+
* @deprecated Prefer removeDeferred() when no Timer callbacks were attached
96+
* by addDeferredToTimer().
97+
*/
7198
public function removeDeferredFromTimer(
7299
Deferred $deferred,
73100
?Timer $timer = null
@@ -77,9 +104,14 @@ public function removeDeferredFromTimer(
77104
foreach($deferred->getProcessList() as $function) {
78105
$timer->removeCallback($function);
79106
}
107+
$this->removeDeferred($deferred);
108+
}
109+
110+
public function removeDeferred(Deferred $deferred):void {
80111
$activeDeferredIndex = array_search(
81112
$deferred,
82-
$this->activeDeferred
113+
$this->activeDeferred,
114+
true
83115
);
84116
if($activeDeferredIndex !== false) {
85117
unset($this->activeDeferred[$activeDeferredIndex]);
@@ -173,4 +205,8 @@ private function executeTimers(TimerOrder $timerOrder):void {
173205
$this->trigger($item["timer"]);
174206
}
175207
}
208+
209+
private function isDeferredTracked(Deferred $deferred):bool {
210+
return array_search($deferred, $this->activeDeferred, true) !== false;
211+
}
176212
}

test/phpunit/LoopTest.php

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,30 @@ public function testAddDeferredToTimerSpecificTimer() {
214214
$sut->addDeferredToTimer($deferred, $timer2);
215215
}
216216

217+
public function testTrackDeferredDoesNotAttachTimerCallbacks() {
218+
$deferredCompleteCallback = null;
219+
220+
$deferred = self::createMock(Deferred::class);
221+
$deferred->expects(self::once())
222+
->method("onComplete")
223+
->willReturnCallback(function(callable $cb) use(&$deferredCompleteCallback) {
224+
$deferredCompleteCallback = $cb;
225+
});
226+
$deferred->expects(self::never())
227+
->method("getProcessList");
228+
229+
$sut = new Loop();
230+
$sut->trackDeferred($deferred);
231+
232+
self::assertIsCallable($deferredCompleteCallback);
233+
}
234+
217235
public function testHaltWhenAllDeferredComplete() {
218236
$deferredCompleteCallback = null;
219237
$deferredProcess = function(){};
220238

221239
$deferred = self::createMock(Deferred::class);
222-
$deferred->expects(self::once())
240+
$deferred->expects(self::exactly(2))
223241
->method("onComplete")
224242
->willReturnCallback(function(callable $cb) use(&$deferredCompleteCallback) {
225243
$deferredCompleteCallback = $cb;
@@ -254,4 +272,33 @@ public function testHaltWhenAllDeferredComplete() {
254272
self::assertIsCallable($deferredCompleteCallback);
255273
call_user_func($deferredCompleteCallback);
256274
}
275+
276+
public function testHaltWhenTrackedDeferredComplete() {
277+
$deferredCompleteCallback = null;
278+
279+
$deferred = self::createMock(Deferred::class);
280+
$deferred->expects(self::once())
281+
->method("onComplete")
282+
->willReturnCallback(function(callable $cb) use(&$deferredCompleteCallback) {
283+
$deferredCompleteCallback = $cb;
284+
});
285+
$deferred->expects(self::never())
286+
->method("getProcessList");
287+
288+
/** @var MockObject|callable $haltCallback */
289+
$haltCallback = self::getMockBuilder(stdClass::class)
290+
->addMethods(["__invoke"])
291+
->getMock();
292+
$haltCallback->expects(self::once())
293+
->method("__invoke");
294+
295+
$sut = new Loop();
296+
$sut->addHaltCallback($haltCallback);
297+
$sut->haltWhenAllDeferredComplete();
298+
$sut->trackDeferred($deferred);
299+
$sut->run();
300+
301+
self::assertIsCallable($deferredCompleteCallback);
302+
call_user_func($deferredCompleteCallback);
303+
}
257304
}

0 commit comments

Comments
 (0)