Skip to content

Commit 2a2c454

Browse files
authored
feat(core): add kernel interface (#924)
1 parent 0748aa9 commit 2a2c454

File tree

9 files changed

+236
-185
lines changed

9 files changed

+236
-185
lines changed

src/Tempest/Core/src/Commands/DiscoveryGenerateCommand.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Tempest\Container\Container;
1212
use Tempest\Container\GenericContainer;
1313
use Tempest\Core\DiscoveryCache;
14+
use Tempest\Core\FrameworkKernel;
1415
use Tempest\Core\Kernel;
1516
use Tempest\Core\Kernel\LoadDiscoveryClasses;
1617
use function Tempest\env;
@@ -91,7 +92,7 @@ public function resolveKernel(): Kernel
9192
$container = new GenericContainer();
9293
$container->singleton(Container::class, $container);
9394

94-
return new Kernel(
95+
return new FrameworkKernel(
9596
root: $this->kernel->root,
9697
discoveryLocations: $this->kernel->discoveryLocations,
9798
container: $container,
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
<?php
2+
3+
namespace Tempest\Core;
4+
5+
use Dotenv\Dotenv;
6+
use Tempest\Console\Exceptions\ConsoleErrorHandler;
7+
use Tempest\Container\Container;
8+
use Tempest\Container\GenericContainer;
9+
use Tempest\Core\Kernel\FinishDeferredTasks;
10+
use Tempest\Core\Kernel\LoadConfig;
11+
use Tempest\Core\Kernel\LoadDiscoveryClasses;
12+
use Tempest\Core\Kernel\LoadDiscoveryLocations;
13+
use Tempest\Core\ShellExecutors\GenericShellExecutor;
14+
use Tempest\EventBus\EventBus;
15+
use Tempest\Router\Exceptions\HttpProductionErrorHandler;
16+
use Whoops\Handler\PrettyPageHandler;
17+
use Whoops\Run;
18+
19+
final class FrameworkKernel implements Kernel
20+
{
21+
public readonly Container $container;
22+
23+
public bool $discoveryCache;
24+
25+
public array $discoveryClasses = [];
26+
27+
public function __construct(
28+
public string $root,
29+
/** @var \Tempest\Discovery\DiscoveryLocation[] $discoveryLocations */
30+
public array $discoveryLocations = [],
31+
?Container $container = null,
32+
) {
33+
$this->container = $container ?? $this->createContainer();
34+
}
35+
36+
public static function boot(
37+
string $root,
38+
array $discoveryLocations = [],
39+
?Container $container = null,
40+
): self {
41+
if (! defined('TEMPEST_START')) {
42+
define('TEMPEST_START', value: hrtime(true));
43+
}
44+
45+
return new self(
46+
root: $root,
47+
discoveryLocations: $discoveryLocations,
48+
container: $container,
49+
)
50+
->loadEnv()
51+
->registerKernelErrorHandler()
52+
->registerShutdownFunction()
53+
->registerKernel()
54+
->loadComposer()
55+
->loadDiscoveryLocations()
56+
->loadConfig()
57+
->loadDiscovery()
58+
->registerErrorHandler()
59+
->event(KernelEvent::BOOTED);
60+
}
61+
62+
public function shutdown(int|string $status = ''): never
63+
{
64+
$this
65+
->finishDeferredTasks()
66+
->event(KernelEvent::SHUTDOWN);
67+
68+
exit($status);
69+
}
70+
71+
public function createContainer(): Container
72+
{
73+
$container = new GenericContainer();
74+
75+
GenericContainer::setInstance($container);
76+
77+
$container->singleton(Container::class, fn () => $container);
78+
79+
return $container;
80+
}
81+
82+
public function loadComposer(): self
83+
{
84+
$composer = new Composer(
85+
root: $this->root,
86+
executor: new GenericShellExecutor(),
87+
)->load();
88+
89+
$this->container->singleton(Composer::class, $composer);
90+
91+
return $this;
92+
}
93+
94+
public function loadEnv(): self
95+
{
96+
$dotenv = Dotenv::createUnsafeImmutable($this->root);
97+
$dotenv->safeLoad();
98+
99+
return $this;
100+
}
101+
102+
public function registerKernel(): self
103+
{
104+
$this->container->singleton(Kernel::class, $this);
105+
106+
return $this;
107+
}
108+
109+
public function registerShutdownFunction(): self
110+
{
111+
// Fix for classes that don't have a proper PSR-4 namespace,
112+
// they break discovery with an unrecoverable error,
113+
// but you don't know why because PHP simply says "duplicate classname" instead of something reasonable.
114+
register_shutdown_function(function (): void {
115+
$error = error_get_last();
116+
117+
$message = $error['message'] ?? '';
118+
119+
if (str_contains($message, 'Cannot declare class')) {
120+
echo 'Does this class have the right namespace?' . PHP_EOL;
121+
}
122+
});
123+
124+
return $this;
125+
}
126+
127+
public function loadDiscoveryLocations(): self
128+
{
129+
$this->container->invoke(LoadDiscoveryLocations::class);
130+
131+
return $this;
132+
}
133+
134+
public function loadDiscovery(): self
135+
{
136+
$this->container->invoke(LoadDiscoveryClasses::class);
137+
138+
return $this;
139+
}
140+
141+
public function loadConfig(): self
142+
{
143+
$this->container->invoke(LoadConfig::class);
144+
145+
return $this;
146+
}
147+
148+
public function registerErrorHandler(): self
149+
{
150+
$appConfig = $this->container->get(AppConfig::class);
151+
152+
if ($appConfig->environment->isTesting()) {
153+
return $this;
154+
}
155+
156+
if (PHP_SAPI === 'cli') {
157+
$handler = $this->container->get(ConsoleErrorHandler::class);
158+
set_exception_handler($handler->handleException(...));
159+
set_error_handler($handler->handleError(...)); // @phpstan-ignore-line
160+
} elseif ($appConfig->environment->isProduction()) {
161+
$handler = $this->container->get(HttpProductionErrorHandler::class);
162+
set_exception_handler($handler->handleException(...));
163+
set_error_handler($handler->handleError(...)); // @phpstan-ignore-line
164+
}
165+
166+
return $this;
167+
}
168+
169+
public function finishDeferredTasks(): self
170+
{
171+
$this->container->invoke(FinishDeferredTasks::class);
172+
173+
return $this;
174+
}
175+
176+
public function event(object $event): self
177+
{
178+
if (interface_exists(EventBus::class)) {
179+
$this->container->get(EventBus::class)->dispatch($event);
180+
}
181+
182+
return $this;
183+
}
184+
185+
public function registerKernelErrorHandler(): self
186+
{
187+
$environment = Environment::fromEnv();
188+
189+
if ($environment->isTesting()) {
190+
return $this;
191+
}
192+
193+
if (PHP_SAPI !== 'cli' && $environment->isProduction()) {
194+
$handler = new HttpProductionErrorHandler();
195+
set_exception_handler($handler->handleException(...));
196+
set_error_handler($handler->handleError(...)); // @phpstan-ignore-line
197+
} elseif (PHP_SAPI !== 'cli') {
198+
$whoops = new Run();
199+
$whoops->pushHandler(new PrettyPageHandler());
200+
$whoops->register();
201+
}
202+
203+
return $this;
204+
}
205+
206+
}

0 commit comments

Comments
 (0)