['title' => 'Requête invalide', 'message' => 'La requête envoyée est invalide.'], 403 => ['title' => 'Accès refusé', 'message' => 'Tu n\u2019as pas accès à cette ressource.'], 404 => ['title' => 'Page introuvable', 'message' => 'La page demandée est introuvable.'], default => ['title' => 'Erreur serveur', 'message' => 'Une erreur est survenue.'], }; } function app_bootstrap_logging(): void { $dir = rtrim((string) Base::instance()->get('LOGS'), '/\\') . DIRECTORY_SEPARATOR; app_ensure_dir($dir); ini_set('log_errors', '1'); ini_set('error_log', $dir . 'php-error.log'); ini_set('display_errors', app_is_prod() ? '0' : '1'); error_reporting(E_ALL); } function app_request_summary(): string { $f3 = Base::instance(); return sprintf( 'request=%s %s ip=%s', (string) ($f3->get('VERB') ?? 'CLI'), (string) ($f3->get('URI') ?? '/'), (string) ($f3->get('IP') ?? '0.0.0.0') ); } function app_write_log(string $fileName, string $line): void { (new Log($fileName))->write($line); } function app_log_error(int $code, string $status, string $text, ?Throwable $exception = null): void { if ($code === 404) { return; } $level = $code >= 500 ? 'error' : ($code >= 400 ? 'warning' : 'info'); $parts = [ sprintf('level=%s code=%d status="%s"', $level, $code, $status), app_request_summary(), ]; if ($text !== '') { $parts[] = 'message="' . str_replace(["\n", '"'], ['\\n', '\\"'], $text) . '"'; } if ($exception !== null) { $parts[] = sprintf('exception="%s" file="%s:%d"', $exception::class, $exception->getFile(), $exception->getLine()); } app_write_log('app.log', implode(' | ', $parts)); } function app_render_error_json(int $code): void { $f3 = Base::instance(); $meta = app_error_meta($code); while (ob_get_level() > 0) { ob_end_clean(); } $f3->status($code); $f3->expire(0); header('Content-Type: application/json; charset=UTF-8'); echo json_encode( ['error' => ['code' => $code, 'title' => $meta['title'], 'message' => $meta['message']]], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); } function app_render_error_fallback(int $code): void { $f3 = Base::instance(); $meta = app_error_meta($code); $base = rtrim((string) $f3->get('BASE'), '/'); while (ob_get_level() > 0) { ob_end_clean(); } $f3->status($code); $f3->expire(0); if (!headers_sent()) { header('Content-Type: text/html; charset=UTF-8'); header('Cache-Control: no-cache, no-store, must-revalidate'); } $title = htmlspecialchars((string) $meta['title'], ENT_QUOTES, 'UTF-8'); $message = htmlspecialchars((string) $meta['message'], ENT_QUOTES, 'UTF-8'); $href = htmlspecialchars($base . '/', ENT_QUOTES, 'UTF-8'); echo '' . $title . '

' . $title . '

' . $message . '

Retour à l\'accueil

'; } function app_bootstrap_errors(Base $f3): void { if (app_is_prod()) { register_shutdown_function(function (): void { $error = error_get_last(); if ($error === null) { return; } $fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR]; if (!in_array($error['type'] ?? 0, $fatalTypes, true)) { return; } app_render_error_fallback(500); }); } $f3->set('ONERROR', function (Base $f3): void { $code = (int) ($f3->get('ERROR.code') ?? 500); $status = (string) ($f3->get('ERROR.status') ?? 'Internal Server Error'); $text = (string) ($f3->get('ERROR.text') ?? ''); if (!app_is_prod() && (int) $f3->get('DEBUG') > 0) { $f3->status($code > 0 ? $code : 500); echo $text; return; } $code = $code > 0 ? $code : 500; app_log_error($code, $status, $text); if ($f3->get('AJAX')) { app_render_error_json($code); return; } $f3->expire(0); $f3->status($code); $meta = app_error_meta($code); $f3->mset([ 'errorCode' => $code, 'errorTitle' => $meta['title'], 'errorMessage' => $meta['message'], ]); try { echo Template::instance()->render('errors/error.html'); } catch (Throwable $exception) { app_log_error(500, 'Internal Server Error', 'Error template rendering failed.', $exception); app_render_error_fallback($code); } }); }