$data */ protected function view(string $name, array $data = []): void { $data['csrfField'] = $this->csrfField(); $data['csrfToken'] = $this->csrfToken(); $data['flashSuccess'] = $this->consumeFlash('success'); $data['flashError'] = $this->consumeFlash('error'); $__viewPath = VIEW_PATH . '/' . str_replace('.', '/', $name) . '.php'; extract($data); if (file_exists($__viewPath)) { require $__viewPath; } else { throw new \Exception("View not found: {$name}"); } } /** * Render a partial template (for HTMX responses). * * @param array $data */ protected function partial(string $name, array $data = []): void { $data['csrfField'] = $this->csrfField(); $data['csrfToken'] = $this->csrfToken(); extract($data); $paths = [ VIEW_PATH . '/' . str_replace('.', '/', $name) . '.php', VIEW_PATH . '/partials/' . str_replace('.', '/', $name) . '.php', ]; foreach ($paths as $file) { if (file_exists($file)) { require $file; return; } } throw new \Exception("Partial not found: {$name}"); } /** * Consume flash message from session. */ protected function consumeFlash(string $key): ?string { $value = $_SESSION[$key] ?? null; if ($value !== null) { unset($_SESSION[$key]); } return $value; } /** * Set flash message in session. */ protected function flash(string $key, string $message): void { $_SESSION[$key] = $message; } }