refactor(kernel): unify HTTP and CLI application lifecycle
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KTXC\Runtime\Console;
|
||||
|
||||
use KTXC\Application\Execution\ExecutionDescriptor;
|
||||
use KTXC\Application\Execution\ExecutionOutcome;
|
||||
use KTXC\Kernel;
|
||||
use KTXC\KernelInterface;
|
||||
use KTXC\Module\ModuleManager;
|
||||
use KTXF\Module\ModuleConsoleInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Console\Application as ConsoleApplication;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\LazyCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
final class ConsoleRuntime
|
||||
{
|
||||
public function __construct(
|
||||
private readonly KernelInterface $kernel,
|
||||
) {
|
||||
}
|
||||
|
||||
public function run(
|
||||
?InputInterface $input = null,
|
||||
?OutputInterface $output = null,
|
||||
): int
|
||||
{
|
||||
$scope = $this->kernel->beginExecution(ExecutionDescriptor::cli());
|
||||
$outcome = ExecutionOutcome::incomplete();
|
||||
|
||||
try {
|
||||
$exitCode = $this->application()->run($input, $output);
|
||||
$outcome = ExecutionOutcome::success($exitCode);
|
||||
|
||||
return $exitCode;
|
||||
} catch (\Throwable $error) {
|
||||
$outcome = ExecutionOutcome::failure($error);
|
||||
throw $error;
|
||||
} finally {
|
||||
$this->kernel->terminateExecution($scope, $outcome);
|
||||
}
|
||||
}
|
||||
|
||||
private function application(): ConsoleApplication
|
||||
{
|
||||
$container = $this->kernel->container();
|
||||
$console = new ConsoleApplication('Vallarx Console', Kernel::VERSION);
|
||||
$console->setAutoExit(false);
|
||||
|
||||
/** @var ModuleManager $moduleManager */
|
||||
$moduleManager = $container->get(ModuleManager::class);
|
||||
foreach ($moduleManager->list() as $module) {
|
||||
$instance = $module->instance();
|
||||
if (!$instance instanceof ModuleConsoleInterface) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($instance->registerCI() as $commandClass) {
|
||||
$this->registerLazyCommand($console, $container, $commandClass);
|
||||
}
|
||||
}
|
||||
|
||||
return $console;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $commandClass
|
||||
*/
|
||||
private function registerLazyCommand(
|
||||
ConsoleApplication $console,
|
||||
ContainerInterface $container,
|
||||
string $commandClass,
|
||||
): void {
|
||||
if (!class_exists($commandClass)) {
|
||||
throw new \RuntimeException("Command class not found: {$commandClass}");
|
||||
}
|
||||
|
||||
$reflection = new \ReflectionClass($commandClass);
|
||||
$attributes = $reflection->getAttributes(AsCommand::class);
|
||||
if ($attributes === []) {
|
||||
throw new \RuntimeException("Command {$commandClass} is missing #[AsCommand].");
|
||||
}
|
||||
|
||||
$attribute = $attributes[0]->newInstance();
|
||||
$console->add(new LazyCommand(
|
||||
$attribute->name,
|
||||
[],
|
||||
$attribute->description ?? '',
|
||||
$attribute->hidden ?? false,
|
||||
static fn() => $container->get($commandClass),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KTXC\Runtime\Http;
|
||||
|
||||
use KTXC\Application\Execution\ExecutionDescriptor;
|
||||
use KTXC\Application\Execution\ExecutionOutcome;
|
||||
use KTXC\Http\Middleware\AuthenticationMiddleware;
|
||||
use KTXC\Http\Middleware\FirewallMiddleware;
|
||||
use KTXC\Http\Middleware\MiddlewarePipeline;
|
||||
use KTXC\Http\Middleware\RouterMiddleware;
|
||||
use KTXC\Http\Middleware\TenantMiddleware;
|
||||
use KTXC\Http\Request\Request;
|
||||
use KTXC\Http\Response\Response;
|
||||
use KTXC\KernelInterface;
|
||||
|
||||
final class HttpRuntime
|
||||
{
|
||||
public function __construct(
|
||||
private readonly KernelInterface $kernel,
|
||||
private readonly bool $debug,
|
||||
) {
|
||||
}
|
||||
|
||||
public function run(?Request $request = null): Response
|
||||
{
|
||||
return $this->execute(
|
||||
$request ?? Request::createFromGlobals(),
|
||||
send: true,
|
||||
);
|
||||
}
|
||||
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
return $this->execute($request, send: false);
|
||||
}
|
||||
|
||||
private function execute(Request $request, bool $send): Response
|
||||
{
|
||||
$scope = null;
|
||||
$outcome = ExecutionOutcome::incomplete();
|
||||
|
||||
try {
|
||||
$scope = $this->kernel->beginExecution(ExecutionDescriptor::http());
|
||||
$response = $this->pipeline()->handle($request);
|
||||
$outcome = ExecutionOutcome::success($response);
|
||||
if ($send) {
|
||||
$response->send();
|
||||
}
|
||||
|
||||
return $response;
|
||||
} catch (\Throwable $error) {
|
||||
$response = $this->errorResponse($error);
|
||||
$outcome = ExecutionOutcome::failure($error, $response);
|
||||
if ($send) {
|
||||
$response->send();
|
||||
}
|
||||
|
||||
return $response;
|
||||
} finally {
|
||||
if ($scope !== null) {
|
||||
$this->kernel->terminateExecution($scope, $outcome);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function pipeline(): MiddlewarePipeline
|
||||
{
|
||||
$pipeline = new MiddlewarePipeline($this->kernel->container());
|
||||
$pipeline->pipe(TenantMiddleware::class);
|
||||
$pipeline->pipe(FirewallMiddleware::class);
|
||||
$pipeline->pipe(AuthenticationMiddleware::class);
|
||||
$pipeline->pipe(RouterMiddleware::class);
|
||||
|
||||
return $pipeline;
|
||||
}
|
||||
|
||||
private function errorResponse(\Throwable $error): Response
|
||||
{
|
||||
error_log(sprintf(
|
||||
'Application error: %s in %s:%d',
|
||||
$error->getMessage(),
|
||||
$error->getFile(),
|
||||
$error->getLine(),
|
||||
));
|
||||
|
||||
$content = $this->debug
|
||||
? '<pre>' . htmlspecialchars((string) $error) . '</pre>'
|
||||
: 'An error occurred. Please try again later.';
|
||||
|
||||
return new Response($content, Response::HTTP_INTERNAL_SERVER_ERROR, [
|
||||
'Content-Type' => 'text/html; charset=UTF-8',
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user