refactor(kernel): unify HTTP and CLI application lifecycle

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-07-27 00:48:11 -04:00
parent 98826143a8
commit 65c1b5fb75
67 changed files with 2737 additions and 1070 deletions
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace KTXC\Application\Execution;
final readonly class ExecutionContext
{
public function __construct(
public RuntimeType $runtime,
public string $executionId,
public string $correlationId,
public ?string $causationId = null,
public ?string $requestId = null,
public ?string $operationName = null,
public array $traceMetadata = [],
) {
}
public static function fromDescriptor(ExecutionDescriptor $descriptor): self
{
return new self(
$descriptor->runtime,
$descriptor->executionId,
$descriptor->correlationId,
operationName: $descriptor->operationName,
);
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace KTXC\Application\Execution;
final readonly class ExecutionDescriptor
{
public function __construct(
public RuntimeType $runtime,
public string $executionId,
public string $correlationId,
public ?string $operationName = null,
) {
}
public static function http(?string $operationName = null): self
{
$id = self::id();
return new self(RuntimeType::HTTP, $id, $id, $operationName);
}
public static function cli(?string $operationName = null): self
{
$id = self::id();
return new self(RuntimeType::CLI, $id, $id, $operationName);
}
private static function id(): string
{
return bin2hex(random_bytes(16));
}
}
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace KTXC\Application\Execution;
final readonly class ExecutionOutcome
{
private function __construct(
public bool $successful,
public mixed $result = null,
public ?\Throwable $error = null,
) {
}
public static function success(mixed $result = null): self
{
return new self(true, $result);
}
public static function failure(\Throwable $error, mixed $result = null): self
{
return new self(false, $result, $error);
}
public static function incomplete(): self
{
return new self(false);
}
}
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace KTXC\Application\Execution;
use KTXC\Context\IdentityContext;
use KTXC\Context\TenantContext;
final class ExecutionScope
{
private bool $terminated = false;
public function __construct(
public readonly ExecutionDescriptor $descriptor,
public readonly ExecutionContext $context,
private readonly TenantContext $tenantContext,
private readonly IdentityContext $identityContext,
) {
$this->tenantContext->clear();
$this->identityContext->clear();
}
public function markTerminated(): void
{
if ($this->terminated) {
throw new \LogicException('The execution scope has already been terminated.');
}
$this->terminated = true;
}
public function terminated(): bool
{
return $this->terminated;
}
public function dispose(): void
{
$this->identityContext->clear();
$this->tenantContext->clear();
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace KTXC\Application\Execution;
enum RuntimeType: string
{
case HTTP = 'http';
case CLI = 'cli';
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace KTXC\Application\Execution;
final readonly class TerminationReport
{
/**
* @param list<\Throwable> $failures
*/
public function __construct(
public int $deferredProcessed = 0,
public int $deferredRemaining = 0,
public array $failures = [],
public bool $deadlineExceeded = false,
public bool $limitExceeded = false,
) {
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace KTXC\Application;
final readonly class KernelOptions
{
public function __construct(
public ProjectPaths $paths,
public string $environment = 'prod',
public bool $debug = false,
) {
if ($this->environment === '') {
throw new \InvalidArgumentException('Kernel environment cannot be empty.');
}
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace KTXC\Application;
final readonly class ProjectPaths
{
private function __construct(
public string $project,
) {
}
public static function resolve(string $start): self
{
$directory = is_file($start) ? dirname($start) : $start;
$directory = rtrim($directory, DIRECTORY_SEPARATOR);
while ($directory !== dirname($directory)) {
if (is_file($directory . '/composer.json')) {
return new self(realpath($directory) ?: $directory);
}
$directory = dirname($directory);
}
throw new \InvalidArgumentException("Unable to resolve project root from {$start}.");
}
public function configuration(): string
{
return $this->project . '/config';
}
public function modules(): string
{
return $this->project . '/modules';
}
public function cache(string $environment): string
{
return $this->project . '/var/cache/' . $environment;
}
public function logs(): string
{
return $this->project . '/var/log';
}
public function runtime(): string
{
return $this->project . '/var';
}
}