rootDir = $this->resolveProjectRoot($rootDir); // Load configuration $this->config = $this->loadConfig(); // Determine environment and debug mode $environment = $environment ?? $this->config['environment'] ?? 'prod'; $debug = $debug ?? $this->config['debug'] ?? false; // Create kernel with configuration $this->kernel = new Kernel($environment, $debug, $this->config, $rootDir); } /** * Run the application - handle incoming request and send response */ public function run(): void { try { $request = Request::createFromGlobals(); $response = $this->handle($request); $response->send(); $this->terminate(); } catch (\Throwable $e) { // Last resort error handling for kernel initialization failures error_log('Application error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine()); $content = $this->kernel->debug() ? '
' . htmlspecialchars((string) $e) . '' : 'An error occurred. Please try again later.'; $response = new Response($content, Response::HTTP_INTERNAL_SERVER_ERROR, [ 'Content-Type' => 'text/html; charset=UTF-8', ]); $response->send(); exit(1); } } /** * Handle a request */ public function handle(Request $request): Response { return $this->kernel->handle($request); } /** * Terminate the application - process deferred events */ public function terminate(): void { $this->kernel->processEvents(); } /** * Get the kernel instance */ public function kernel(): Kernel { return $this->kernel; } /** * Get the container instance */ public function container(): ContainerInterface { return $this->kernel->container(); } /** * Get the application root directory */ public function rootDir(): string { return $this->rootDir; } /** * Get the modules directory */ public function moduleDir(): string { return $this->rootDir . '/modules'; } /** * Get configuration value */ public function config(?string $key = null, mixed $default = null): mixed { if ($key === null) { return $this->config; } // Support dot notation: 'database.uri' $keys = explode('.', $key); $value = $this->config; foreach ($keys as $k) { if (!is_array($value) || !array_key_exists($k, $value)) { return $default; } $value = $value[$k]; } return $value; } /** * Get environment */ public function environment(): string { return $this->kernel->environment(); } /** * Check if debug mode is enabled */ public function debug(): bool { return $this->kernel->debug(); } /** * Load configuration from config directory */ protected function loadConfig(): array { $configFile = $this->rootDir . '/config/system.php'; if (!file_exists($configFile)) { error_log('Configuration file not found: ' . $configFile); return []; } $config = include $configFile; if (!is_array($config)) { throw new \RuntimeException('Configuration file must return an array'); } return $config; } /** * Resolve the project root directory. * * Some entrypoints may pass the public/ directory or another subdirectory. * We walk up the directory tree until we find composer.json. */ private function resolveProjectRoot(string $startDir): string { $dir = rtrim($startDir, '/'); if ($dir === '') { return $startDir; } // If startDir is a file path, use its directory. if (is_file($dir)) { $dir = dirname($dir); } $current = $dir; while (true) { if (is_file($current . '/composer.json')) { return $current; } $parent = dirname($current); if ($parent === $current) { // Reached filesystem root return $dir; } $current = $parent; } } /** * Set the Composer ClassLoader instance */ public static function setComposerLoader($loader): void { self::$composerLoader = $loader; } /** * Get the Composer ClassLoader instance */ public static function getComposerLoader() { return self::$composerLoader; } }