Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
102 / 102
98.90% covered (success)
98.90%
90 / 91
45.35% covered (danger)
45.35%
39 / 86
96.55% covered (success)
96.55%
28 / 29
CRAP
0.00% covered (danger)
0.00%
0 / 1
App
100.00% covered (success)
100.00%
102 / 102
98.90% covered (success)
98.90%
90 / 91
45.35% covered (danger)
45.35%
39 / 86
100.00% covered (success)
100.00%
29 / 29
475.56
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 load
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 router
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 factory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addRoute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 group
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 staticRoute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMiddleware
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 errorHandler
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
3 / 3
50.00% covered (danger)
50.00%
1 / 2
100.00% covered (success)
100.00%
1 / 1
2.50
 emitter
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 middleware
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 before
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 after
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 logger
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 container
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 register
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 initializeContainer
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 teardown
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 handle
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
5 / 5
0.00% covered (danger)
0.00%
0 / 3
100.00% covered (success)
100.00%
1 / 1
2
 run
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
9 / 9
0.00% covered (danger)
0.00%
0 / 6
100.00% covered (success)
100.00%
1 / 1
3
 serve
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
5 / 5
50.00% covered (danger)
50.00%
2 / 4
100.00% covered (success)
100.00%
1 / 1
4.12
 reusable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 respond
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 endRequest
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
3 / 3
50.00% covered (danger)
50.00%
1 / 2
100.00% covered (success)
100.00%
1 / 1
2.50
 finish
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
13 / 13
13.64% covered (danger)
13.64%
3 / 22
100.00% covered (success)
100.00%
1 / 1
21.10
 report
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
12 / 12
33.33% covered (danger)
33.33%
4 / 12
100.00% covered (success)
100.00%
1 / 1
12.41
 recordServerException
100.00% covered (success)
100.00%
2 / 2
83.33% covered (warning)
83.33%
5 / 6
25.00% covered (danger)
25.00%
1 / 4
100.00% covered (success)
100.00%
1 / 1
3.69
 emitFailure
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
7 / 7
50.00% covered (danger)
50.00%
4 / 8
100.00% covered (success)
100.00%
1 / 1
6.00
1<?php
2
3declare(strict_types=1);
4
5namespace Celema\Core;
6
7use Celema\Container\Container;
8use Celema\Container\Entry;
9use Celema\Container\Exception\ResetFailed;
10use Celema\Core\Emitter\Emitter;
11use Celema\Core\Emitter\Fallback;
12use Celema\Core\Emitter\Sapi;
13use Celema\Core\Error\Handler as ErrorHandler;
14use Celema\Core\Error\Log;
15use Celema\Core\Factory\Factory;
16use Celema\Core\Factory\Nyholm;
17use Celema\Core\Runtime\FrankenPhpWorker;
18use Celema\Router\AddsRoutes;
19use Celema\Router\After;
20use Celema\Router\Before;
21use Celema\Router\Dispatcher;
22use Celema\Router\Route;
23use Celema\Router\RouteAdder;
24use Celema\Router\Router;
25use Celema\Router\RoutingHandler;
26use Celema\Server\Console as ServerConsole;
27use Closure;
28use Override;
29use Psr\Container\ContainerInterface as PsrContainer;
30use Psr\Http\Message\ResponseInterface as Response;
31use Psr\Http\Message\ServerRequestInterface as Request;
32use Psr\Http\Server\MiddlewareInterface as Middleware;
33use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
34use Psr\Log\LoggerInterface as Logger;
35use Psr\Log\LogLevel;
36use Throwable;
37
38/** @api */
39class App implements RouteAdder, RequestHandler
40{
41    use AddsRoutes;
42
43    protected readonly Dispatcher $dispatcher;
44    protected ?ErrorHandler $errorHandler = null;
45    protected Emitter $emitter;
46
47    /** @var list<Closure(): void> */
48    protected array $teardownHooks = [];
49
50    /**
51     * False once a request left state behind that the app cannot vouch for:
52     * a throwable escaped the request, or its teardown failed.
53     */
54    protected bool $reusable = true;
55
56    public function __construct(
57        protected readonly Factory $factory,
58        protected readonly Router $router,
59        protected readonly Container $container,
60    ) {
61        $this->dispatcher = new Dispatcher();
62        $this->emitter = new Sapi();
63        $this->initializeContainer();
64    }
65
66    public function load(Plugin $plugin): void
67    {
68        $plugin->load($this);
69    }
70
71    public static function create(?PsrContainer $container = null): self
72    {
73        return new self(
74            new Nyholm(),
75            new Router(),
76            new Container(container: $container),
77        );
78    }
79
80    public function router(): Router
81    {
82        return $this->router;
83    }
84
85    public function factory(): Factory
86    {
87        return $this->factory;
88    }
89
90    #[Override]
91    public function addRoute(Route $route): Route
92    {
93        return $this->router->addRoute($route);
94    }
95
96    #[Override]
97    public function group(
98        string $patternPrefix,
99        Closure $createClosure,
100        string $namePrefix = '',
101    ): void {
102        $this->router->group($patternPrefix, $createClosure, $namePrefix);
103    }
104
105    public function staticRoute(
106        string $prefix,
107        string $path,
108        string $name = '',
109    ): void {
110        $this->router->addStatic($prefix, $path, $name);
111    }
112
113    /** @return list<Middleware> */
114    public function getMiddleware(): array
115    {
116        return $this->dispatcher->getMiddleware();
117    }
118
119    public function errorHandler(?ErrorHandler $handler = null): ?ErrorHandler
120    {
121        if ($handler !== null) {
122            $this->errorHandler = $handler;
123        }
124
125        return $this->errorHandler;
126    }
127
128    public function emitter(?Emitter $emitter = null): Emitter
129    {
130        if ($emitter !== null) {
131            $this->emitter = $emitter;
132        }
133
134        return $this->emitter;
135    }
136
137    public function middleware(Middleware ...$middleware): void
138    {
139        $this->dispatcher->middleware(...$middleware);
140    }
141
142    public function before(Before $beforeHandler): static
143    {
144        $this->dispatcher->before($beforeHandler);
145
146        return $this;
147    }
148
149    public function after(After $afterHandler): static
150    {
151        $this->dispatcher->after($afterHandler);
152
153        return $this;
154    }
155
156    public function logger(Logger|callable $logger): void
157    {
158        if ($logger instanceof Logger) {
159            $this->container->add(Logger::class, $logger);
160        } else {
161            $this->container->add(Logger::class, Closure::fromCallable($logger));
162        }
163    }
164
165    public function container(): Container
166    {
167        return $this->container;
168    }
169
170    /**
171     * @param non-empty-string $key
172     * @param class-string|object $value
173     */
174    public function register(string $key, object|string $value): Entry
175    {
176        return $this->container->add($key, $value);
177    }
178
179    public function initializeContainer(): void
180    {
181        $this->container->add(Router::class, $this->router);
182        $this->container->add($this->router::class, $this->router);
183
184        $this->container->add(Factory::class, $this->factory);
185        $this->container->add($this->factory::class, $this->factory);
186    }
187
188    /**
189     * Registers a callback that runs after every request, once the request's
190     * container scope was reset: in run() after the response was emitted and,
191     * where the runtime allows, the request ended, so the client does not
192     * wait for it; in handle() before the response is returned. Use it to
193     * bring resources that outlive a request back to a clean state, such as
194     * rolling back a database transaction left open, or for work the client
195     * need not wait for.
196     *
197     * Callbacks run in registration order. A failing callback does not stop
198     * the others; failures are logged and the app is no longer reusable.
199     *
200     * @param Closure(): void $hook
201     */
202    public function teardown(Closure $hook): void
203    {
204        $this->teardownHooks[] = $hook;
205    }
206
207    /**
208     * Handles the request in its own container scope without emitting the
209     * response. The scope is reset and the teardown hooks have run when the
210     * response is returned, so its body must not depend on scoped services.
211     */
212    #[Override]
213    public function handle(Request $request): Response
214    {
215        $scope = $this->container->scope();
216
217        try {
218            return $this->respond($request, $scope);
219        } catch (Throwable $e) {
220            $this->reusable = false;
221
222            throw $e;
223        } finally {
224            $this->finish($scope, $request);
225        }
226    }
227
228    /**
229     * Handles exactly one request in its own container scope, emits the
230     * response, ends the request, then runs the teardown. Throwables that
231     * escape the error handler or the emitter are logged and answered with a
232     * minimal 500 response if nothing was sent yet; they do not leave this
233     * method.
234     */
235    public function run(?Request $request = null): Response|false
236    {
237        $bufferLevel = ob_get_level();
238        $scope = $this->container->scope();
239
240        try {
241            $request ??= $this->factory->serverRequest();
242            $response = $this->respond($request, $scope);
243
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
245        } catch (Throwable $e) {
246            $this->reusable = false;
247            $this->report('Unhandled exception', $e, $request);
248            $this->recordServerException($e);
249            // The method is unknown if creating the request failed.
250            $this->emitFailure($bufferLevel, $e, $request?->getMethod() === 'HEAD');
251
252            return false;
253        } finally {
254            $this->endRequest();
255            $this->finish($scope, $request);
256        }
257    }
258
259    /**
260     * Runs the app in the runtime that started the script: as a FrankenPHP
261     * worker, which handles requests until it retires, or for exactly one
262     * request (PHP-FPM, FrankenPHP's classic mode, the CLI server).
263     *
264     * Register everything before calling it. The first request seals the
265     * container, so later registrations fail.
266     *
267     * @param ?int $maxRequests Worker only: retire after this many requests; 0 never.
268     *     Defaults to the CELEMA_WORKER_MAX_REQUESTS environment variable, else 0.
269     * @param ?int $maxMemory Worker only: retire above this many bytes of memory; 0 never.
270     *     Defaults to the CELEMA_WORKER_MAX_MEMORY environment variable, else 80 % of memory_limit.
271     */
272    public function serve(?int $maxRequests = null, ?int $maxMemory = null): Response|false
273    {
274        if (isset($_SERVER['FRANKENPHP_WORKER']) && function_exists('frankenphp_handle_request')) {
275            return FrankenPhpWorker::configure(
276                frankenphp_handle_request(...),
277                $maxRequests,
278                $maxMemory,
279            )->serve($this);
280        }
281
282        return $this->run();
283    }
284
285    /**
286     * Whether the app may handle further requests in the same process.
287     *
288     * @internal
289     */
290    public function reusable(): bool
291    {
292        return $this->reusable;
293    }
294
295    protected function respond(Request $request, Container $scope): Response
296    {
297        $handler = new RoutingHandler(
298            $this->router,
299            $this->dispatcher,
300            $scope,
301        );
302
303        return $this->errorHandler
304            ? $this->errorHandler->process($request, $handler)
305            : $handler->handle($request);
306    }
307
308    /**
309     * Sends the emitted response to the client and closes the connection;
310     * the script goes on with the teardown. PHP-FPM and FrankenPHP, in both
311     * modes, provide the function; elsewhere the client waits for the teardown.
312     */
313    protected function endRequest(): void
314    {
315        if (function_exists('fastcgi_finish_request')) {
316            fastcgi_finish_request();
317        }
318    }
319
320    /**
321     * Resets the request scope, then runs the teardown hooks. Every step is
322     * attempted; failures are logged and make the app unusable for further
323     * requests, but never replace the request's response or exception.
324     */
325    protected function finish(Container $scope, ?Request $request): void
326    {
327        $failures = [];
328
329        try {
330            $scope->reset();
331        } catch (ResetFailed $e) {
332            $failures = $e->failures;
333        }
334
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
348
349    /**
350     * Logs a failure the error handler did not answer, naming the request
351     * it belongs to. The request is unknown when creating it failed.
352     */
353    protected function report(string $message, Throwable $exception, ?Request $request): void
354    {
355        try {
356            /** @var mixed $logger */
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
358        } catch (Throwable $e) {
359            // A logger that cannot be resolved must not keep the failure from
360            // being answered or the request from being torn down.
361            error_log('Logging failed: ' . (string) $e);
362            $logger = null;
363        }
364
365        $values = [];
366
367        if ($request !== null) {
368            $message .= ' for {method} {path}';
369            $values = Log::request($request);
370        }
371
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
373    }
374
375    protected function recordServerException(Throwable $exception): void
376    {
377        // Shows the exception in the celema/server request log, as the error
378        // handler does for the exceptions it renders.
379        if (class_exists(ServerConsole::class)) {
380            ServerConsole::recordException($exception, trace: true);
381        }
382    }
383
384    /**
385     * Answers with a minimal 500 response if the failed request sent nothing
386     * yet. Like PHP for an uncaught exception, it shows the exception only
387     * while `display_errors` sends errors to the output, as in development.
388     */
389    protected function emitFailure(int $bufferLevel, Throwable $exception, bool $withoutBody = false): void
390    {
391        $body = '500 Internal Server Error';
392        $display = strtolower((string) ini_get('display_errors'));
393
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
395            $body .= "\n\n" . (string) $exception;
396        }
397
398        $response = $this->factory->response(500)->withHeader('Content-Type', 'text/plain; charset=utf-8');
399        $response->getBody()->write($body);
400
401        Fallback::emit($this->emitter, $response, $bufferLevel, $withoutBody);
402    }
403}

Paths

Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once. Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

App->__construct
57        protected readonly Factory $factory,
58        protected readonly Router $router,
59        protected readonly Container $container,
60    ) {
61        $this->dispatcher = new Dispatcher();
62        $this->emitter = new Sapi();
63        $this->initializeContainer();
64    }
App->addRoute
91    public function addRoute(Route $route): Route
92    {
93        return $this->router->addRoute($route);
94    }
App->after
149    public function after(After $afterHandler): static
150    {
151        $this->dispatcher->after($afterHandler);
152
153        return $this;
154    }
App->before
142    public function before(Before $beforeHandler): static
143    {
144        $this->dispatcher->before($beforeHandler);
145
146        return $this;
147    }
App->container
167        return $this->container;
168    }
App->create
71    public static function create(?PsrContainer $container = null): self
72    {
73        return new self(
74            new Nyholm(),
75            new Router(),
76            new Container(container: $container),
77        );
78    }
App->emitFailure
389    protected function emitFailure(int $bufferLevel, Throwable $exception, bool $withoutBody = false): void
390    {
391        $body = '500 Internal Server Error';
392        $display = strtolower((string) ini_get('display_errors'));
393
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
395            $body .= "\n\n" . (string) $exception;
396        }
397
398        $response = $this->factory->response(500)->withHeader('Content-Type', 'text/plain; charset=utf-8');
 
398        $response = $this->factory->response(500)->withHeader('Content-Type', 'text/plain; charset=utf-8');
399        $response->getBody()->write($body);
400
401        Fallback::emit($this->emitter, $response, $bufferLevel, $withoutBody);
402    }
389    protected function emitFailure(int $bufferLevel, Throwable $exception, bool $withoutBody = false): void
390    {
391        $body = '500 Internal Server Error';
392        $display = strtolower((string) ini_get('display_errors'));
393
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
398        $response = $this->factory->response(500)->withHeader('Content-Type', 'text/plain; charset=utf-8');
399        $response->getBody()->write($body);
400
401        Fallback::emit($this->emitter, $response, $bufferLevel, $withoutBody);
402    }
389    protected function emitFailure(int $bufferLevel, Throwable $exception, bool $withoutBody = false): void
390    {
391        $body = '500 Internal Server Error';
392        $display = strtolower((string) ini_get('display_errors'));
393
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
395            $body .= "\n\n" . (string) $exception;
396        }
397
398        $response = $this->factory->response(500)->withHeader('Content-Type', 'text/plain; charset=utf-8');
 
398        $response = $this->factory->response(500)->withHeader('Content-Type', 'text/plain; charset=utf-8');
399        $response->getBody()->write($body);
400
401        Fallback::emit($this->emitter, $response, $bufferLevel, $withoutBody);
402    }
389    protected function emitFailure(int $bufferLevel, Throwable $exception, bool $withoutBody = false): void
390    {
391        $body = '500 Internal Server Error';
392        $display = strtolower((string) ini_get('display_errors'));
393
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
398        $response = $this->factory->response(500)->withHeader('Content-Type', 'text/plain; charset=utf-8');
399        $response->getBody()->write($body);
400
401        Fallback::emit($this->emitter, $response, $bufferLevel, $withoutBody);
402    }
389    protected function emitFailure(int $bufferLevel, Throwable $exception, bool $withoutBody = false): void
390    {
391        $body = '500 Internal Server Error';
392        $display = strtolower((string) ini_get('display_errors'));
393
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
395            $body .= "\n\n" . (string) $exception;
396        }
397
398        $response = $this->factory->response(500)->withHeader('Content-Type', 'text/plain; charset=utf-8');
 
398        $response = $this->factory->response(500)->withHeader('Content-Type', 'text/plain; charset=utf-8');
399        $response->getBody()->write($body);
400
401        Fallback::emit($this->emitter, $response, $bufferLevel, $withoutBody);
402    }
389    protected function emitFailure(int $bufferLevel, Throwable $exception, bool $withoutBody = false): void
390    {
391        $body = '500 Internal Server Error';
392        $display = strtolower((string) ini_get('display_errors'));
393
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
398        $response = $this->factory->response(500)->withHeader('Content-Type', 'text/plain; charset=utf-8');
399        $response->getBody()->write($body);
400
401        Fallback::emit($this->emitter, $response, $bufferLevel, $withoutBody);
402    }
389    protected function emitFailure(int $bufferLevel, Throwable $exception, bool $withoutBody = false): void
390    {
391        $body = '500 Internal Server Error';
392        $display = strtolower((string) ini_get('display_errors'));
393
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
395            $body .= "\n\n" . (string) $exception;
396        }
397
398        $response = $this->factory->response(500)->withHeader('Content-Type', 'text/plain; charset=utf-8');
 
398        $response = $this->factory->response(500)->withHeader('Content-Type', 'text/plain; charset=utf-8');
399        $response->getBody()->write($body);
400
401        Fallback::emit($this->emitter, $response, $bufferLevel, $withoutBody);
402    }
389    protected function emitFailure(int $bufferLevel, Throwable $exception, bool $withoutBody = false): void
390    {
391        $body = '500 Internal Server Error';
392        $display = strtolower((string) ini_get('display_errors'));
393
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
394        if ($display !== 'stderr' && filter_var($display, FILTER_VALIDATE_BOOL) || $display === 'stdout') {
 
398        $response = $this->factory->response(500)->withHeader('Content-Type', 'text/plain; charset=utf-8');
399        $response->getBody()->write($body);
400
401        Fallback::emit($this->emitter, $response, $bufferLevel, $withoutBody);
402    }
App->emitter
128    public function emitter(?Emitter $emitter = null): Emitter
129    {
130        if ($emitter !== null) {
 
131            $this->emitter = $emitter;
132        }
133
134        return $this->emitter;
 
134        return $this->emitter;
135    }
128    public function emitter(?Emitter $emitter = null): Emitter
129    {
130        if ($emitter !== null) {
 
134        return $this->emitter;
135    }
App->endRequest
315        if (function_exists('fastcgi_finish_request')) {
 
316            fastcgi_finish_request();
317        }
318    }
 
318    }
315        if (function_exists('fastcgi_finish_request')) {
 
318    }
App->errorHandler
119    public function errorHandler(?ErrorHandler $handler = null): ?ErrorHandler
120    {
121        if ($handler !== null) {
 
122            $this->errorHandler = $handler;
123        }
124
125        return $this->errorHandler;
 
125        return $this->errorHandler;
126    }
119    public function errorHandler(?ErrorHandler $handler = null): ?ErrorHandler
120    {
121        if ($handler !== null) {
 
125        return $this->errorHandler;
126    }
App->factory
87        return $this->factory;
88    }
App->finish
325    protected function finish(Container $scope, ?Request $request): void
326    {
327        $failures = [];
328
329        try {
330            $scope->reset();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
336            try {
337                $hook();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
325    protected function finish(Container $scope, ?Request $request): void
326    {
327        $failures = [];
328
329        try {
330            $scope->reset();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
336            try {
337                $hook();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
325    protected function finish(Container $scope, ?Request $request): void
326    {
327        $failures = [];
328
329        try {
330            $scope->reset();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
336            try {
337                $hook();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
325    protected function finish(Container $scope, ?Request $request): void
326    {
327        $failures = [];
328
329        try {
330            $scope->reset();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
325    protected function finish(Container $scope, ?Request $request): void
326    {
327        $failures = [];
328
329        try {
330            $scope->reset();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
325    protected function finish(Container $scope, ?Request $request): void
326    {
327        $failures = [];
328
329        try {
330            $scope->reset();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
325    protected function finish(Container $scope, ?Request $request): void
326    {
327        $failures = [];
328
329        try {
330            $scope->reset();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
325    protected function finish(Container $scope, ?Request $request): void
326    {
327        $failures = [];
328
329        try {
330            $scope->reset();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
325    protected function finish(Container $scope, ?Request $request): void
326    {
327        $failures = [];
328
329        try {
330            $scope->reset();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
331        } catch (ResetFailed $e) {
 
332            $failures = $e->failures;
333        }
334
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
336            try {
337                $hook();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
331        } catch (ResetFailed $e) {
 
332            $failures = $e->failures;
333        }
334
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
336            try {
337                $hook();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
331        } catch (ResetFailed $e) {
 
332            $failures = $e->failures;
333        }
334
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
336            try {
337                $hook();
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
331        } catch (ResetFailed $e) {
 
332            $failures = $e->failures;
333        }
334
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
331        } catch (ResetFailed $e) {
 
332            $failures = $e->failures;
333        }
334
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
331        } catch (ResetFailed $e) {
 
332            $failures = $e->failures;
333        }
334
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
331        } catch (ResetFailed $e) {
 
332            $failures = $e->failures;
333        }
334
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
331        } catch (ResetFailed $e) {
 
332            $failures = $e->failures;
333        }
334
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
331        } catch (ResetFailed $e) {
 
332            $failures = $e->failures;
333        }
334
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
338            } catch (Throwable $e) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
336            try {
337                $hook();
 
335        foreach ($this->teardownHooks as $hook) {
338            } catch (Throwable $e) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
338            } catch (Throwable $e) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
338            } catch (Throwable $e) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
 
335        foreach ($this->teardownHooks as $hook) {
336            try {
337                $hook();
338            } catch (Throwable $e) {
339                $failures[] = $e;
340            }
341        }
342
343        foreach ($failures as $failure) {
 
343        foreach ($failures as $failure) {
344            $this->reusable = false;
345            $this->report('Request teardown failed', $failure, $request);
346        }
347    }
App->getMiddleware
116        return $this->dispatcher->getMiddleware();
117    }
App->group
98        string $patternPrefix,
99        Closure $createClosure,
100        string $namePrefix = '',
101    ): void {
102        $this->router->group($patternPrefix, $createClosure, $namePrefix);
103    }
App->handle
213    public function handle(Request $request): Response
214    {
215        $scope = $this->container->scope();
216
217        try {
218            return $this->respond($request, $scope);
 
224            $this->finish($scope, $request);
225        }
226    }
213    public function handle(Request $request): Response
214    {
215        $scope = $this->container->scope();
216
217        try {
218            return $this->respond($request, $scope);
 
218            return $this->respond($request, $scope);
219        } catch (Throwable $e) {
 
220            $this->reusable = false;
221
222            throw $e;
App->initializeContainer
181        $this->container->add(Router::class, $this->router);
182        $this->container->add($this->router::class, $this->router);
183
184        $this->container->add(Factory::class, $this->factory);
185        $this->container->add($this->factory::class, $this->factory);
186    }
App->load
66    public function load(Plugin $plugin): void
67    {
68        $plugin->load($this);
69    }
App->logger
156    public function logger(Logger|callable $logger): void
157    {
158        if ($logger instanceof Logger) {
 
158        if ($logger instanceof Logger) {
159            $this->container->add(Logger::class, $logger);
 
163    }
156    public function logger(Logger|callable $logger): void
157    {
158        if ($logger instanceof Logger) {
 
161            $this->container->add(Logger::class, Closure::fromCallable($logger));
162        }
163    }
 
163    }
App->middleware
137    public function middleware(Middleware ...$middleware): void
138    {
139        $this->dispatcher->middleware(...$middleware);
140    }
App->recordServerException
375    protected function recordServerException(Throwable $exception): void
376    {
377        // Shows the exception in the celema/server request log, as the error
378        // handler does for the exceptions it renders.
379        if (class_exists(ServerConsole::class)) {
 
379        if (class_exists(ServerConsole::class)) {
 
379        if (class_exists(ServerConsole::class)) {
 
380            ServerConsole::recordException($exception, trace: true);
381        }
382    }
 
382    }
375    protected function recordServerException(Throwable $exception): void
376    {
377        // Shows the exception in the celema/server request log, as the error
378        // handler does for the exceptions it renders.
379        if (class_exists(ServerConsole::class)) {
 
379        if (class_exists(ServerConsole::class)) {
 
379        if (class_exists(ServerConsole::class)) {
 
382    }
375    protected function recordServerException(Throwable $exception): void
376    {
377        // Shows the exception in the celema/server request log, as the error
378        // handler does for the exceptions it renders.
379        if (class_exists(ServerConsole::class)) {
 
379        if (class_exists(ServerConsole::class)) {
 
379        if (class_exists(ServerConsole::class)) {
 
380            ServerConsole::recordException($exception, trace: true);
381        }
382    }
 
382    }
375    protected function recordServerException(Throwable $exception): void
376    {
377        // Shows the exception in the celema/server request log, as the error
378        // handler does for the exceptions it renders.
379        if (class_exists(ServerConsole::class)) {
 
379        if (class_exists(ServerConsole::class)) {
 
379        if (class_exists(ServerConsole::class)) {
 
382    }
App->register
174    public function register(string $key, object|string $value): Entry
175    {
176        return $this->container->add($key, $value);
177    }
App->report
353    protected function report(string $message, Throwable $exception, ?Request $request): void
354    {
355        try {
356            /** @var mixed $logger */
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
365        $values = [];
366
367        if ($request !== null) {
 
368            $message .= ' for {method} {path}';
369            $values = Log::request($request);
370        }
371
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
373    }
353    protected function report(string $message, Throwable $exception, ?Request $request): void
354    {
355        try {
356            /** @var mixed $logger */
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
365        $values = [];
366
367        if ($request !== null) {
 
368            $message .= ' for {method} {path}';
369            $values = Log::request($request);
370        }
371
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
373    }
353    protected function report(string $message, Throwable $exception, ?Request $request): void
354    {
355        try {
356            /** @var mixed $logger */
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
365        $values = [];
366
367        if ($request !== null) {
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
373    }
353    protected function report(string $message, Throwable $exception, ?Request $request): void
354    {
355        try {
356            /** @var mixed $logger */
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
365        $values = [];
366
367        if ($request !== null) {
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
373    }
353    protected function report(string $message, Throwable $exception, ?Request $request): void
354    {
355        try {
356            /** @var mixed $logger */
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
365        $values = [];
366
367        if ($request !== null) {
 
368            $message .= ' for {method} {path}';
369            $values = Log::request($request);
370        }
371
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
373    }
353    protected function report(string $message, Throwable $exception, ?Request $request): void
354    {
355        try {
356            /** @var mixed $logger */
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
365        $values = [];
366
367        if ($request !== null) {
 
368            $message .= ' for {method} {path}';
369            $values = Log::request($request);
370        }
371
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
373    }
353    protected function report(string $message, Throwable $exception, ?Request $request): void
354    {
355        try {
356            /** @var mixed $logger */
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
365        $values = [];
366
367        if ($request !== null) {
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
373    }
353    protected function report(string $message, Throwable $exception, ?Request $request): void
354    {
355        try {
356            /** @var mixed $logger */
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
357            $logger = $this->container->has(Logger::class) ? $this->container->get(Logger::class) : null;
 
365        $values = [];
366
367        if ($request !== null) {
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
373    }
358        } catch (Throwable $e) {
 
361            error_log('Logging failed: ' . (string) $e);
362            $logger = null;
363        }
364
365        $values = [];
 
365        $values = [];
366
367        if ($request !== null) {
 
368            $message .= ' for {method} {path}';
369            $values = Log::request($request);
370        }
371
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
373    }
358        } catch (Throwable $e) {
 
361            error_log('Logging failed: ' . (string) $e);
362            $logger = null;
363        }
364
365        $values = [];
 
365        $values = [];
366
367        if ($request !== null) {
 
368            $message .= ' for {method} {path}';
369            $values = Log::request($request);
370        }
371
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
373    }
358        } catch (Throwable $e) {
 
361            error_log('Logging failed: ' . (string) $e);
362            $logger = null;
363        }
364
365        $values = [];
 
365        $values = [];
366
367        if ($request !== null) {
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
373    }
358        } catch (Throwable $e) {
 
361            error_log('Logging failed: ' . (string) $e);
362            $logger = null;
363        }
364
365        $values = [];
 
365        $values = [];
366
367        if ($request !== null) {
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
 
372        Log::write($logger instanceof Logger ? $logger : null, LogLevel::CRITICAL, $message, $exception, $values);
373    }
App->respond
295    protected function respond(Request $request, Container $scope): Response
296    {
297        $handler = new RoutingHandler(
298            $this->router,
299            $this->dispatcher,
300            $scope,
301        );
302
303        return $this->errorHandler
 
304            ? $this->errorHandler->process($request, $handler)
 
305            : $handler->handle($request);
306    }
295    protected function respond(Request $request, Container $scope): Response
296    {
297        $handler = new RoutingHandler(
298            $this->router,
299            $this->dispatcher,
300            $scope,
301        );
302
303        return $this->errorHandler
 
305            : $handler->handle($request);
 
305            : $handler->handle($request);
306    }
App->reusable
292        return $this->reusable;
293    }
App->router
82        return $this->router;
83    }
App->run
235    public function run(?Request $request = null): Response|false
236    {
237        $bufferLevel = ob_get_level();
238        $scope = $this->container->scope();
239
240        try {
241            $request ??= $this->factory->serverRequest();
242            $response = $this->respond($request, $scope);
243
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
 
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
 
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
 
254            $this->endRequest();
255            $this->finish($scope, $request);
256        }
257    }
235    public function run(?Request $request = null): Response|false
236    {
237        $bufferLevel = ob_get_level();
238        $scope = $this->container->scope();
239
240        try {
241            $request ??= $this->factory->serverRequest();
242            $response = $this->respond($request, $scope);
243
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
 
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
 
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
 
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
235    public function run(?Request $request = null): Response|false
236    {
237        $bufferLevel = ob_get_level();
238        $scope = $this->container->scope();
239
240        try {
241            $request ??= $this->factory->serverRequest();
242            $response = $this->respond($request, $scope);
243
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
 
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
 
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
 
254            $this->endRequest();
255            $this->finish($scope, $request);
256        }
257    }
235    public function run(?Request $request = null): Response|false
236    {
237        $bufferLevel = ob_get_level();
238        $scope = $this->container->scope();
239
240        try {
241            $request ??= $this->factory->serverRequest();
242            $response = $this->respond($request, $scope);
243
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
 
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
 
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
 
244            return $this->emitter->emit($response, $request->getMethod() === 'HEAD') ? $response : false;
245        } catch (Throwable $e) {
 
246            $this->reusable = false;
247            $this->report('Unhandled exception', $e, $request);
248            $this->recordServerException($e);
249            // The method is unknown if creating the request failed.
250            $this->emitFailure($bufferLevel, $e, $request?->getMethod() === 'HEAD');
251
252            return false;
 
254            $this->endRequest();
255            $this->finish($scope, $request);
256        }
257    }
245        } catch (Throwable $e) {
 
246            $this->reusable = false;
247            $this->report('Unhandled exception', $e, $request);
248            $this->recordServerException($e);
249            // The method is unknown if creating the request failed.
250            $this->emitFailure($bufferLevel, $e, $request?->getMethod() === 'HEAD');
251
252            return false;
 
252            return false;
App->serve
272    public function serve(?int $maxRequests = null, ?int $maxMemory = null): Response|false
273    {
274        if (isset($_SERVER['FRANKENPHP_WORKER']) && function_exists('frankenphp_handle_request')) {
 
274        if (isset($_SERVER['FRANKENPHP_WORKER']) && function_exists('frankenphp_handle_request')) {
 
274        if (isset($_SERVER['FRANKENPHP_WORKER']) && function_exists('frankenphp_handle_request')) {
 
275            return FrankenPhpWorker::configure(
276                frankenphp_handle_request(...),
277                $maxRequests,
278                $maxMemory,
279            )->serve($this);
272    public function serve(?int $maxRequests = null, ?int $maxMemory = null): Response|false
273    {
274        if (isset($_SERVER['FRANKENPHP_WORKER']) && function_exists('frankenphp_handle_request')) {
 
274        if (isset($_SERVER['FRANKENPHP_WORKER']) && function_exists('frankenphp_handle_request')) {
 
274        if (isset($_SERVER['FRANKENPHP_WORKER']) && function_exists('frankenphp_handle_request')) {
 
282        return $this->run();
283    }
272    public function serve(?int $maxRequests = null, ?int $maxMemory = null): Response|false
273    {
274        if (isset($_SERVER['FRANKENPHP_WORKER']) && function_exists('frankenphp_handle_request')) {
 
274        if (isset($_SERVER['FRANKENPHP_WORKER']) && function_exists('frankenphp_handle_request')) {
 
275            return FrankenPhpWorker::configure(
276                frankenphp_handle_request(...),
277                $maxRequests,
278                $maxMemory,
279            )->serve($this);
272    public function serve(?int $maxRequests = null, ?int $maxMemory = null): Response|false
273    {
274        if (isset($_SERVER['FRANKENPHP_WORKER']) && function_exists('frankenphp_handle_request')) {
 
274        if (isset($_SERVER['FRANKENPHP_WORKER']) && function_exists('frankenphp_handle_request')) {
 
282        return $this->run();
283    }
App->staticRoute
106        string $prefix,
107        string $path,
108        string $name = '',
109    ): void {
110        $this->router->addStatic($prefix, $path, $name);
111    }
App->teardown
202    public function teardown(Closure $hook): void
203    {
204        $this->teardownHooks[] = $hook;
205    }