Skip to content
Merged
27 changes: 6 additions & 21 deletions packages/console/src/ConsoleApplication.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,14 @@
use Tempest\Console\Actions\ExecuteConsoleCommand;
use Tempest\Console\Input\ConsoleArgumentBag;
use Tempest\Container\Container;
use Tempest\Core\AppConfig;
use Tempest\Core\Application;
use Tempest\Core\Kernel;
use Tempest\Core\Tempest;
use Tempest\Log\Channels\AppendLogChannel;
use Tempest\Log\LogConfig;
use Throwable;

use function Tempest\Support\path;

final readonly class ConsoleApplication implements Application
{
public function __construct(
private Container $container,
private AppConfig $appConfig,
private ConsoleArgumentBag $argumentBag,
) {}

Expand All @@ -44,22 +37,14 @@ public static function boot(

public function run(): void
{
try {
$exitCode = $this->container->get(ExecuteConsoleCommand::class)($this->argumentBag->getCommandName());

$exitCode = is_int($exitCode) ? $exitCode : $exitCode->value;
$exitCode = $this->container->get(ExecuteConsoleCommand::class)($this->argumentBag->getCommandName());

if ($exitCode < 0 || $exitCode > 255) {
throw new InvalidExitCode($exitCode);
}
$exitCode = is_int($exitCode) ? $exitCode : $exitCode->value;

$this->container->get(Kernel::class)->shutdown($exitCode);
} catch (Throwable $throwable) {
foreach ($this->appConfig->errorHandlers as $exceptionHandler) {
$exceptionHandler->handleException($throwable);
}

throw $throwable;
if ($exitCode < 0 || $exitCode > 255) {
throw new InvalidExitCode($exitCode);
}

$this->container->get(Kernel::class)->shutdown($exitCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,69 +8,70 @@
use Tempest\Console\ExitCode;
use Tempest\Console\HasExitCode;
use Tempest\Console\Input\ConsoleArgumentBag;
use Tempest\Container\Container;
use Tempest\Container\Tag;
use Tempest\Core\ErrorHandler;
use Tempest\Core\AppConfig;
use Tempest\Core\ExceptionHandler;
use Tempest\Core\Kernel;
use Tempest\Highlight\Escape;
use Tempest\Highlight\Highlighter;
use Throwable;

use function Tempest\Support\str;

final readonly class ConsoleErrorHandler implements ErrorHandler
final readonly class ConsoleExceptionHandler implements ExceptionHandler
{
public function __construct(
private AppConfig $appConfig,
private Container $container,
private Kernel $kernel,
#[Tag('console')]
private Highlighter $highlighter,
private Console $console,
private ConsoleArgumentBag $argumentBag,
) {}

public function handleException(Throwable $throwable): void
public function handle(Throwable $throwable): void
{
ll(exception: $throwable->getMessage());

$this->console
->writeln()
->error($throwable::class)
->when(
condition: $throwable->getMessage(),
callback: fn (Console $console) => $console->error($throwable->getMessage()),
)
->writeln()
->writeln('In ' . $this->formatFileWithLine($throwable->getFile() . ':' . $throwable->getLine()))
->writeln($this->getSnippet($throwable->getFile(), $throwable->getLine()))
->writeln();

if ($this->argumentBag->get('-v') !== null) {
foreach ($throwable->getTrace() as $i => $trace) {
$this->console->writeln("<style='bold fg-blue'>#{$i}</style> " . $this->formatTrace($trace));
try {
foreach ($this->appConfig->exceptionProcessors as $processor) {
$handler = $this->container->get($processor);
$throwable = $handler->process($throwable);
}

$this->console->writeln();
} else {
$this->console
->writeln('<style="fg-blue bold">#0</style> ' . $this->formatTrace($throwable->getTrace()[0]))
->writeln('<style="fg-blue bold">#1</style> ' . $this->formatTrace($throwable->getTrace()[1]))
->writeln()
->writeln(' <style="dim">Run with -v to show more.</style>')
->error($throwable::class)
->when(
condition: $throwable->getMessage(),
callback: fn (Console $console) => $console->error($throwable->getMessage()),
)
->writeln()
->writeln('In ' . $this->formatFileWithLine($throwable->getFile() . ':' . $throwable->getLine()))
->writeln($this->getSnippet($throwable->getFile(), $throwable->getLine()))
->writeln();
}

$exitCode = ($throwable instanceof HasExitCode) ? $throwable->getExitCode() : ExitCode::ERROR;

$this->kernel->shutdown($exitCode->value);
}

public function handleError(int $errNo, string $errstr, string $errFile, int $errLine): void
{
ll(error: $errstr);
if ($this->argumentBag->get('-v') !== null) {
foreach ($throwable->getTrace() as $i => $trace) {
$this->console->writeln("<style='bold fg-blue'>#{$i}</style> " . $this->formatTrace($trace));
}

$this->console->writeln();
} else {
$this->console
->writeln('<style="fg-blue bold">#0</style> ' . $this->formatTrace($throwable->getTrace()[0]))
->writeln('<style="fg-blue bold">#1</style> ' . $this->formatTrace($throwable->getTrace()[1]))
->writeln()
->writeln(' <style="dim">Run with -v to show more.</style>')
->writeln();
}
} finally {
$exitCode = ($throwable instanceof HasExitCode)
? $throwable->getExitCode()
: ExitCode::ERROR;

$this->console
->writeln()
->error($errstr)
->writeln($this->getSnippet($errFile, $errLine));
$this->kernel->shutdown($exitCode->value);
}
}

private function getSnippet(string $file, int $lineNumber): string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ public function initialize(Container $container): ConsoleApplication
{
$application = new ConsoleApplication(
container: $container,
appConfig: $container->get(AppConfig::class),
argumentBag: $container->get(ConsoleArgumentBag::class),
);

Expand Down
4 changes: 2 additions & 2 deletions packages/console/src/Testing/ConsoleTester.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use Tempest\Console\Actions\ExecuteConsoleCommand;
use Tempest\Console\Components\InteractiveComponentRenderer;
use Tempest\Console\Console;
use Tempest\Console\Exceptions\ConsoleErrorHandler;
use Tempest\Console\Exceptions\ConsoleExceptionHandler;
use Tempest\Console\ExitCode;
use Tempest\Console\GenericConsole;
use Tempest\Console\Input\ConsoleArgumentBag;
Expand Down Expand Up @@ -76,7 +76,7 @@ public function call(string|Closure|array $command, string|array $arguments = []
$clone->container->singleton(Console::class, $console);

$appConfig = $this->container->get(AppConfig::class);
$appConfig->errorHandlers[] = $clone->container->get(ConsoleErrorHandler::class);
$appConfig->exceptionProcessors[] = $clone->container->get(ConsoleExceptionHandler::class);

$clone->output = $memoryOutputBuffer;
$clone->input = $memoryInputBuffer;
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/AppConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Tempest\Core;

use Tempest\Core\Exceptions\LogExceptionProcessor;

use function Tempest\env;

final class AppConfig
Expand All @@ -16,10 +18,8 @@ public function __construct(
?Environment $environment = null,
?string $baseUri = null,

/** @var \Tempest\Core\ErrorHandler[] */
public array $errorHandlers = [
// …,
],
/** @var class-string<\Tempest\Core\ExceptionProcessor>[] */
public array $exceptionProcessors = [],
) {
$this->environment = $environment ?? Environment::fromEnv();

Expand Down
14 changes: 0 additions & 14 deletions packages/core/src/ErrorHandler.php

This file was deleted.

15 changes: 15 additions & 0 deletions packages/core/src/ExceptionHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Tempest\Core;

use Throwable;

interface ExceptionHandler
{
/**
* Handles the given exception.
*/
public function handle(Throwable $throwable): void;
}
20 changes: 20 additions & 0 deletions packages/core/src/ExceptionHandlerInitializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Tempest\Core;

use Tempest\Console\Exceptions\ConsoleExceptionHandler;
use Tempest\Container\Container;
use Tempest\Container\Initializer;
use Tempest\Router\Exceptions\HttpExceptionHandler;

final class ExceptionHandlerInitializer implements Initializer
{
public function initialize(Container $container): ExceptionHandler
{
if (PHP_SAPI === 'cli') {
return $container->get(ConsoleExceptionHandler::class);
}

return $container->get(HttpExceptionHandler::class);
}
}
13 changes: 13 additions & 0 deletions packages/core/src/ExceptionProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Tempest\Core;

use Throwable;

interface ExceptionProcessor
{
/**
* Processes the given exception.
*/
public function process(Throwable $throwable): Throwable;
}
34 changes: 34 additions & 0 deletions packages/core/src/ExceptionProcessorDiscovery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Tempest\Core;

use Tempest\Core\AppConfig;
use Tempest\Discovery\Discovery;
use Tempest\Discovery\DiscoveryLocation;
use Tempest\Discovery\IsDiscovery;
use Tempest\Reflection\ClassReflector;

final class ExceptionProcessorDiscovery implements Discovery
{
use IsDiscovery;

public function __construct(
private readonly AppConfig $appConfig,
) {}

public function discover(DiscoveryLocation $location, ClassReflector $class): void
{
if ($class->implements(ExceptionProcessor::class)) {
$this->discoveryItems->add($location, $class->getName());
}
}

public function apply(): void
{
foreach ($this->discoveryItems as $className) {
$this->appConfig->exceptionProcessors[] = $className;
}
}
}
23 changes: 12 additions & 11 deletions packages/core/src/FrameworkKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
namespace Tempest\Core;

use Dotenv\Dotenv;
use Tempest\Console\Exceptions\ConsoleErrorHandler;
use Tempest\Container\Container;
use Tempest\Container\GenericContainer;
use Tempest\Core\Exceptions\WhoopsErrorHandler;
use Tempest\Core\Kernel\FinishDeferredTasks;
use Tempest\Core\Kernel\LoadConfig;
use Tempest\Core\Kernel\LoadDiscoveryClasses;
use Tempest\Core\Kernel\LoadDiscoveryLocations;
use Tempest\Core\Kernel\RegisterEmergencyExceptionHandler;
use Tempest\Core\ShellExecutors\GenericShellExecutor;
use Tempest\EventBus\EventBus;

Expand Down Expand Up @@ -203,7 +202,7 @@ public function registerEmergencyErrorHandler(): self
// In development, we want to register a developer-friendly error
// handler as soon as possible to catch any kind of exception.
if (PHP_SAPI !== 'cli' && ! $environment->isProduction()) {
new WhoopsErrorHandler($this->container)->register();
new RegisterEmergencyExceptionHandler($this->container)->register();
}

return $this;
Expand All @@ -218,14 +217,16 @@ public function registerErrorHandler(): self
return $this;
}

// We already have a non-CLI error handler.
if (PHP_SAPI !== 'cli') {
return $this;
}

$handler = $this->container->get(ConsoleErrorHandler::class);
set_exception_handler($handler->handleException(...));
set_error_handler($handler->handleError(...)); // @phpstan-ignore-line
$handler = $this->container->get(ExceptionHandler::class);
set_exception_handler($handler->handle(...));
set_error_handler(fn (int $code, string $message, string $filename, int $line) => $handler->handle(
new \ErrorException(
message: $message,
code: $code,
filename: $filename,
line: $line,
),
));

return $this;
}
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/HasContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Tempest\Core;

interface HasContext
{
/**
* Provides context for the exception-handling pipeline.
*/
public function context(): iterable;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Tempest\Core\Exceptions;
namespace Tempest\Core\Kernel;

use Tempest\Container\Container;
use Tempest\Http\Request;
Expand All @@ -9,7 +9,7 @@
use Whoops\Handler\PrettyPageHandler;
use Whoops\Run;

final readonly class WhoopsErrorHandler
final readonly class RegisterEmergencyExceptionHandler
{
public function __construct(
private Container $container,
Expand Down
Loading
Loading