Skip to content

Commit 15cd46e

Browse files
authored
feat: add defer helper (#624)
1 parent bf7ff15 commit 15cd46e

File tree

9 files changed

+136
-1
lines changed

9 files changed

+136
-1
lines changed

src/Tempest/Console/src/ConsoleApplication.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public function run(): void
5454
try {
5555
$exitCode = ($this->container->get(ExecuteConsoleCommand::class))($this->argumentBag->getCommandName());
5656

57-
exit($exitCode->value);
57+
$this->container->get(Kernel::class)->shutdown($exitCode->value);
5858
} catch (Throwable $throwable) {
5959
foreach ($this->appConfig->exceptionHandlers as $exceptionHandler) {
6060
$exceptionHandler->handle($throwable);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Core;
6+
7+
use Closure;
8+
use Tempest\Container\Singleton;
9+
10+
#[Singleton]
11+
final class DeferredTasks
12+
{
13+
private array $tasks = [];
14+
15+
public function add(Closure $task): void
16+
{
17+
$this->tasks[] = $task;
18+
}
19+
20+
public function getTasks(): array
21+
{
22+
return $this->tasks;
23+
}
24+
}

src/Tempest/Core/src/Kernel.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Dotenv\Dotenv;
88
use Tempest\Container\Container;
99
use Tempest\Container\GenericContainer;
10+
use Tempest\Core\Kernel\FinishDeferredTasks;
1011
use Tempest\Core\Kernel\LoadConfig;
1112
use Tempest\Core\Kernel\LoadDiscoveryClasses;
1213
use Tempest\Core\Kernel\LoadDiscoveryLocations;
@@ -49,6 +50,15 @@ public static function boot(string $root, ?Container $container = null): self
4950
);
5051
}
5152

53+
public function shutdown(int|string $status = ''): never
54+
{
55+
$this
56+
->finishDeferredTasks()
57+
->event(KernelEvent::SHUTDOWN);
58+
59+
exit($status);
60+
}
61+
5262
private function createContainer(): Container
5363
{
5464
$container = new GenericContainer();
@@ -130,6 +140,13 @@ private function loadExceptionHandler(): self
130140
return $this;
131141
}
132142

143+
private function finishDeferredTasks(): self
144+
{
145+
($this->container->get(FinishDeferredTasks::class))();
146+
147+
return $this;
148+
}
149+
133150
private function event(object $event): self
134151
{
135152
if (interface_exists(EventBus::class)) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Core\Kernel;
6+
7+
use Tempest\Core\DeferredTasks;
8+
9+
final readonly class FinishDeferredTasks
10+
{
11+
public function __construct(
12+
private DeferredTasks $deferredTasks,
13+
) {
14+
}
15+
16+
public function __invoke(): void
17+
{
18+
foreach ($this->deferredTasks->getTasks() as $task) {
19+
$task();
20+
}
21+
}
22+
}

src/Tempest/Core/src/functions.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
declare(strict_types=1);
44

55
namespace Tempest {
6+
7+
use Closure;
8+
use Tempest\Core\DeferredTasks;
9+
610
function path(string ...$parts): string
711
{
812
$path = implode('/', $parts);
@@ -29,4 +33,9 @@ function env(string $key, mixed $default = null): mixed
2933
default => $value,
3034
};
3135
}
36+
37+
function defer(Closure $closure): void
38+
{
39+
get(DeferredTasks::class)->add($closure);
40+
}
3241
}

src/Tempest/Http/src/GenericResponseSender.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ public function send(Response $response): Response
2727
$this->sendContent($response);
2828
ob_end_flush();
2929

30+
if (function_exists('fastcgi_finish_request')) {
31+
fastcgi_finish_request();
32+
}
33+
3034
return $response;
3135
}
3236

src/Tempest/Http/src/HttpApplication.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public function run(): void
5656
);
5757

5858
$this->container->get(Session::class)->cleanup();
59+
60+
$this->container->get(Kernel::class)->shutdown();
5961
} catch (Throwable $throwable) {
6062
foreach ($this->container->get(AppConfig::class)->exceptionHandlers as $exceptionHandler) {
6163
$exceptionHandler->handle($throwable);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Tempest\Fixtures\Controllers;
6+
7+
use function Tempest\defer;
8+
use Tempest\Http\Get;
9+
use Tempest\Http\Response;
10+
use Tempest\Http\Responses\Ok;
11+
12+
final class DeferController
13+
{
14+
public static bool $executed = false;
15+
16+
#[Get('/defer')]
17+
public function __invoke(): Response
18+
{
19+
defer(function (): void {
20+
// ll('defer start');
21+
// sleep(2);
22+
// ll('defer done');
23+
self::$executed = true;
24+
});
25+
26+
return new Ok('ok');
27+
}
28+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Tempest\Integration\Core;
6+
7+
use Tempest\Core\Kernel\FinishDeferredTasks;
8+
use function Tempest\uri;
9+
use Tests\Tempest\Fixtures\Controllers\DeferController;
10+
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
11+
12+
/**
13+
* @internal
14+
*/
15+
final class DeferredTasksTest extends FrameworkIntegrationTestCase
16+
{
17+
public function test_deferred_tasks_are_executed(): void
18+
{
19+
DeferController::$executed = false;
20+
21+
$this->http
22+
->get(uri(DeferController::class))
23+
->assertOk();
24+
25+
$this->container->get(FinishDeferredTasks::class)();
26+
27+
$this->assertTrue(DeferController::$executed);
28+
}
29+
}

0 commit comments

Comments
 (0)