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