Skip to content

Commit 9b31ecc

Browse files
innocenzibrendt
andauthored
feat(core): improve exception handling (#1203)
Co-authored-by: Brent Roose <[email protected]>
1 parent 0e324bf commit 9b31ecc

39 files changed

+682
-175
lines changed

packages/console/src/ConsoleApplication.php

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,14 @@
77
use Tempest\Console\Actions\ExecuteConsoleCommand;
88
use Tempest\Console\Input\ConsoleArgumentBag;
99
use Tempest\Container\Container;
10-
use Tempest\Core\AppConfig;
1110
use Tempest\Core\Application;
1211
use Tempest\Core\Kernel;
1312
use Tempest\Core\Tempest;
14-
use Tempest\Log\Channels\AppendLogChannel;
15-
use Tempest\Log\LogConfig;
16-
use Throwable;
17-
18-
use function Tempest\Support\path;
1913

2014
final readonly class ConsoleApplication implements Application
2115
{
2216
public function __construct(
2317
private Container $container,
24-
private AppConfig $appConfig,
2518
private ConsoleArgumentBag $argumentBag,
2619
) {}
2720

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

4538
public function run(): void
4639
{
47-
try {
48-
$exitCode = $this->container->get(ExecuteConsoleCommand::class)($this->argumentBag->getCommandName());
49-
50-
$exitCode = is_int($exitCode) ? $exitCode : $exitCode->value;
40+
$exitCode = $this->container->get(ExecuteConsoleCommand::class)($this->argumentBag->getCommandName());
5141

52-
if ($exitCode < 0 || $exitCode > 255) {
53-
throw new InvalidExitCode($exitCode);
54-
}
42+
$exitCode = is_int($exitCode) ? $exitCode : $exitCode->value;
5543

56-
$this->container->get(Kernel::class)->shutdown($exitCode);
57-
} catch (Throwable $throwable) {
58-
foreach ($this->appConfig->errorHandlers as $exceptionHandler) {
59-
$exceptionHandler->handleException($throwable);
60-
}
61-
62-
throw $throwable;
44+
if ($exitCode < 0 || $exitCode > 255) {
45+
throw new InvalidExitCode($exitCode);
6346
}
47+
48+
$this->container->get(Kernel::class)->shutdown($exitCode);
6449
}
6550
}

packages/console/src/Exceptions/ConsoleErrorHandler.php renamed to packages/console/src/Exceptions/ConsoleExceptionHandler.php

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,69 +8,70 @@
88
use Tempest\Console\ExitCode;
99
use Tempest\Console\HasExitCode;
1010
use Tempest\Console\Input\ConsoleArgumentBag;
11+
use Tempest\Container\Container;
1112
use Tempest\Container\Tag;
12-
use Tempest\Core\ErrorHandler;
13+
use Tempest\Core\AppConfig;
14+
use Tempest\Core\ExceptionHandler;
1315
use Tempest\Core\Kernel;
1416
use Tempest\Highlight\Escape;
1517
use Tempest\Highlight\Highlighter;
1618
use Throwable;
1719

1820
use function Tempest\Support\str;
1921

20-
final readonly class ConsoleErrorHandler implements ErrorHandler
22+
final readonly class ConsoleExceptionHandler implements ExceptionHandler
2123
{
2224
public function __construct(
25+
private AppConfig $appConfig,
26+
private Container $container,
2327
private Kernel $kernel,
2428
#[Tag('console')]
2529
private Highlighter $highlighter,
2630
private Console $console,
2731
private ConsoleArgumentBag $argumentBag,
2832
) {}
2933

30-
public function handleException(Throwable $throwable): void
34+
public function handle(Throwable $throwable): void
3135
{
32-
ll(exception: $throwable->getMessage());
33-
34-
$this->console
35-
->writeln()
36-
->error($throwable::class)
37-
->when(
38-
condition: $throwable->getMessage(),
39-
callback: fn (Console $console) => $console->error($throwable->getMessage()),
40-
)
41-
->writeln()
42-
->writeln('In ' . $this->formatFileWithLine($throwable->getFile() . ':' . $throwable->getLine()))
43-
->writeln($this->getSnippet($throwable->getFile(), $throwable->getLine()))
44-
->writeln();
45-
46-
if ($this->argumentBag->get('-v') !== null) {
47-
foreach ($throwable->getTrace() as $i => $trace) {
48-
$this->console->writeln("<style='bold fg-blue'>#{$i}</style> " . $this->formatTrace($trace));
36+
try {
37+
foreach ($this->appConfig->exceptionProcessors as $processor) {
38+
$handler = $this->container->get($processor);
39+
$throwable = $handler->process($throwable);
4940
}
5041

51-
$this->console->writeln();
52-
} else {
5342
$this->console
54-
->writeln('<style="fg-blue bold">#0</style> ' . $this->formatTrace($throwable->getTrace()[0]))
55-
->writeln('<style="fg-blue bold">#1</style> ' . $this->formatTrace($throwable->getTrace()[1]))
5643
->writeln()
57-
->writeln(' <style="dim">Run with -v to show more.</style>')
44+
->error($throwable::class)
45+
->when(
46+
condition: $throwable->getMessage(),
47+
callback: fn (Console $console) => $console->error($throwable->getMessage()),
48+
)
49+
->writeln()
50+
->writeln('In ' . $this->formatFileWithLine($throwable->getFile() . ':' . $throwable->getLine()))
51+
->writeln($this->getSnippet($throwable->getFile(), $throwable->getLine()))
5852
->writeln();
59-
}
60-
61-
$exitCode = ($throwable instanceof HasExitCode) ? $throwable->getExitCode() : ExitCode::ERROR;
62-
63-
$this->kernel->shutdown($exitCode->value);
64-
}
6553

66-
public function handleError(int $errNo, string $errstr, string $errFile, int $errLine): void
67-
{
68-
ll(error: $errstr);
54+
if ($this->argumentBag->get('-v') !== null) {
55+
foreach ($throwable->getTrace() as $i => $trace) {
56+
$this->console->writeln("<style='bold fg-blue'>#{$i}</style> " . $this->formatTrace($trace));
57+
}
58+
59+
$this->console->writeln();
60+
} else {
61+
$this->console
62+
->writeln('<style="fg-blue bold">#0</style> ' . $this->formatTrace($throwable->getTrace()[0]))
63+
->writeln('<style="fg-blue bold">#1</style> ' . $this->formatTrace($throwable->getTrace()[1]))
64+
->writeln()
65+
->writeln(' <style="dim">Run with -v to show more.</style>')
66+
->writeln();
67+
}
68+
} finally {
69+
$exitCode = ($throwable instanceof HasExitCode)
70+
? $throwable->getExitCode()
71+
: ExitCode::ERROR;
6972

70-
$this->console
71-
->writeln()
72-
->error($errstr)
73-
->writeln($this->getSnippet($errFile, $errLine));
73+
$this->kernel->shutdown($exitCode->value);
74+
}
7475
}
7576

7677
private function getSnippet(string $file, int $lineNumber): string

packages/console/src/Initializers/ConsoleApplicationInitializer.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public function initialize(Container $container): ConsoleApplication
1919
{
2020
$application = new ConsoleApplication(
2121
container: $container,
22-
appConfig: $container->get(AppConfig::class),
2322
argumentBag: $container->get(ConsoleArgumentBag::class),
2423
);
2524

packages/console/src/Testing/ConsoleTester.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Tempest\Console\Actions\ExecuteConsoleCommand;
1111
use Tempest\Console\Components\InteractiveComponentRenderer;
1212
use Tempest\Console\Console;
13-
use Tempest\Console\Exceptions\ConsoleErrorHandler;
13+
use Tempest\Console\Exceptions\ConsoleExceptionHandler;
1414
use Tempest\Console\ExitCode;
1515
use Tempest\Console\GenericConsole;
1616
use Tempest\Console\Input\ConsoleArgumentBag;
@@ -76,7 +76,7 @@ public function call(string|Closure|array $command, string|array $arguments = []
7676
$clone->container->singleton(Console::class, $console);
7777

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

8181
$clone->output = $memoryOutputBuffer;
8282
$clone->input = $memoryInputBuffer;

packages/core/src/AppConfig.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Tempest\Core;
66

7+
use Tempest\Core\Exceptions\LogExceptionProcessor;
8+
79
use function Tempest\env;
810

911
final class AppConfig
@@ -16,10 +18,8 @@ public function __construct(
1618
?Environment $environment = null,
1719
?string $baseUri = null,
1820

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

packages/core/src/ErrorHandler.php

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Core;
6+
7+
use Throwable;
8+
9+
interface ExceptionHandler
10+
{
11+
/**
12+
* Handles the given exception.
13+
*/
14+
public function handle(Throwable $throwable): void;
15+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Tempest\Core;
4+
5+
use Tempest\Console\Exceptions\ConsoleExceptionHandler;
6+
use Tempest\Container\Container;
7+
use Tempest\Container\Initializer;
8+
use Tempest\Router\Exceptions\HttpExceptionHandler;
9+
10+
final class ExceptionHandlerInitializer implements Initializer
11+
{
12+
public function initialize(Container $container): ExceptionHandler
13+
{
14+
if (PHP_SAPI === 'cli') {
15+
return $container->get(ConsoleExceptionHandler::class);
16+
}
17+
18+
return $container->get(HttpExceptionHandler::class);
19+
}
20+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Tempest\Core;
4+
5+
use Throwable;
6+
7+
interface ExceptionProcessor
8+
{
9+
/**
10+
* Processes the given exception.
11+
*/
12+
public function process(Throwable $throwable): Throwable;
13+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Core;
6+
7+
use Tempest\Core\AppConfig;
8+
use Tempest\Discovery\Discovery;
9+
use Tempest\Discovery\DiscoveryLocation;
10+
use Tempest\Discovery\IsDiscovery;
11+
use Tempest\Reflection\ClassReflector;
12+
13+
final class ExceptionProcessorDiscovery implements Discovery
14+
{
15+
use IsDiscovery;
16+
17+
public function __construct(
18+
private readonly AppConfig $appConfig,
19+
) {}
20+
21+
public function discover(DiscoveryLocation $location, ClassReflector $class): void
22+
{
23+
if ($class->implements(ExceptionProcessor::class)) {
24+
$this->discoveryItems->add($location, $class->getName());
25+
}
26+
}
27+
28+
public function apply(): void
29+
{
30+
foreach ($this->discoveryItems as $className) {
31+
$this->appConfig->exceptionProcessors[] = $className;
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)