initialized = false; $this->booted = false; $this->container = null; } private function initialize(): void { if ($this->debug) { $this->startTime = microtime(true); } if ($this->debug && !isset($_ENV['SHELL_VERBOSITY']) && !isset($_SERVER['SHELL_VERBOSITY'])) { if (\function_exists('putenv')) { putenv('SHELL_VERBOSITY=3'); } $_ENV['SHELL_VERBOSITY'] = 3; $_SERVER['SHELL_VERBOSITY'] = 3; } $container = $this->initializeContainer(); $this->container = $container; $this->initialized = true; } public function boot(): void { if (!$this->initialized) { $this->initialize(); } if (!$this->booted) { /** @var ModuleManager $moduleManager */ $moduleManager = $this->container->get(ModuleManager::class); $moduleManager->modulesBoot(); $this->booted = true; } } public function reboot(): void { $this->shutdown(); $this->boot(); } public function shutdown(): void { if (false === $this->initialized) { return; } $this->initialized = false; $this->booted = false; $this->container = null; } public function handle(Request $request): Response { if (!$this->booted) { $this->boot(); } /** @var SessionTenant $sessionTenant */ $sessionTenant = $this->container->get(SessionTenant::class); $sessionTenant->configure($request->getHost()); if (!$sessionTenant->configured() && !$sessionTenant->enabled()) { return new Response(Response::$statusTexts[Response::HTTP_UNAUTHORIZED], Response::HTTP_UNAUTHORIZED); } /** @var FirewallService $firewall */ $firewall = $this->container->get(FirewallService::class); if (!$firewall->authorized($request)) { return new Response(Response::$statusTexts[Response::HTTP_FORBIDDEN], Response::HTTP_FORBIDDEN); } /** @var Router $router */ $router = $this->container->get(Router::class); if ($router) { $match = $router->match($request); if ($match instanceof Route) { /** @var SecurityService $securityService */ $securityService = $this->container->get(SecurityService::class); $identity = $securityService->authenticate($request); if ($match->authenticated && $identity === null) { return new Response(Response::$statusTexts[Response::HTTP_UNAUTHORIZED], Response::HTTP_UNAUTHORIZED); } if ($identity) { /** @var SessionIdentity $sessionIdentity */ $sessionIdentity = $this->container->get(SessionIdentity::class); $sessionIdentity->initialize($identity, true); } $response = $router->dispatch($match, $request); if ($response instanceof Response) { return $response; } } } return new Response(Response::$statusTexts[Response::HTTP_NOT_FOUND], Response::HTTP_NOT_FOUND); } /** * Process deferred events at the end of the request */ public function processEvents(): void { try { if ($this->container && $this->container->has(EventBus::class)) { /** @var EventBus $eventBus */ $eventBus = $this->container->get(EventBus::class); $eventBus->processDeferred(); } } catch (\Throwable $e) { error_log('Event processing error: ' . $e->getMessage()); } } /** * Returns the kernel parameters. * * @return array */ protected function parameters(): array { return [ 'kernel.project_dir' => realpath($this->folderRoot()) ?: $this->folderRoot(), 'kernel.environment' => $this->environment, 'kernel.runtime_environment' => '%env(default:kernel.environment:APP_RUNTIME_ENV)%', 'kernel.runtime_mode' => '%env(query_string:default:container.runtime_mode:APP_RUNTIME_MODE)%', 'kernel.runtime_mode.web' => '%env(bool:default::key:web:default:kernel.runtime_mode:)%', 'kernel.runtime_mode.cli' => '%env(not:default:kernel.runtime_mode.web:)%', 'kernel.runtime_mode.worker' => '%env(bool:default::key:worker:default:kernel.runtime_mode:)%', 'kernel.debug' => $this->debug, 'kernel.build_dir' => realpath($this->getBuildDir()) ?: $this->getBuildDir(), 'kernel.cache_dir' => realpath($this->getCacheDir()) ?: $this->getCacheDir(), 'kernel.logs_dir' => realpath($this->getLogDir()) ?: $this->getLogDir(), 'kernel.charset' => $this->getCharset(), ]; } public function environment(): string { return $this->environment; } public function debug(): bool { return $this->debug; } public function container(): ContainerInterface { if (!$this->container) { throw new \LogicException('Cannot retrieve the container from a non-booted kernel.'); } return $this->container; } public function getStartTime(): float { return $this->debug && null !== $this->startTime ? $this->startTime : -\INF; } /** * Gets the application root dir (path of the project's composer file). */ public function folderRoot(): string { if (!isset($this->projectDir)) { $r = new \ReflectionObject($this); if (!is_file($dir = $r->getFileName())) { throw new \LogicException(\sprintf('Cannot auto-detect project dir for kernel of class "%s".', $r->name)); } $dir = $rootDir = \dirname($dir); while (!is_file($dir.'/composer.json')) { if ($dir === \dirname($dir)) { return $this->projectDir = $rootDir; } $dir = \dirname($dir); } $this->projectDir = $dir; } return $this->projectDir; } /** * Gets the path to the configuration directory. */ private function getConfigDir(): string { return $this->folderRoot().'/config'; } public function getCacheDir(): string { return $this->folderRoot().'/var/cache/'.$this->environment; } public function getBuildDir(): string { // Returns $this->getCacheDir() for backward compatibility return $this->getCacheDir(); } public function getLogDir(): string { return $this->folderRoot().'/var/log'; } public function getCharset(): string { return 'UTF-8'; } /** * Gets a new container builder instance used to build the service container. */ protected function containerBuilder(): Builder { return new Builder(Container::class); } /** * Initializes the service container */ protected function initializeContainer(): Container { $container = $this->buildContainer(); $container->set('kernel', $this); return $container; } /** * Builds the service container. * * @throws \RuntimeException */ protected function buildContainer(): Container { $builder = $this->containerBuilder(); $builder->useAutowiring(true); $builder->useAttributes(true); $builder->addDefinitions($this->parameters()); $this->configureContainer($builder); return $builder->build(); } protected function configureContainer(Builder $builder): void { $builder->addDefinitions($this->getConfigDir() . '/system.php'); // Service definitions $logDir = $this->getLogDir(); $projectDir = $this->folderRoot(); $builder->addDefinitions([ LoggerInterface::class => function() use ($logDir) { return new FileLogger($logDir); }, // EventBus as singleton for consistent event handling EventBus::class => \DI\create(EventBus::class), // Ephemeral Cache - for short-lived data (sessions, rate limits, challenges) EphemeralCacheInterface::class => function(ContainerInterface $c) use ($projectDir) { $storeType = $c->has('cache.ephemeral') ? $c->get('cache.ephemeral') : 'file'; $storeMap = [ 'file' => FileEphemeralCache::class, // 'redis' => RedisEphemeralCache::class, ]; $storeClass = $storeMap[$storeType] ?? $storeType; if (!class_exists($storeClass)) { throw new \RuntimeException("Ephemeral cache store not found: {$storeClass}"); } $cache = new $storeClass($projectDir); // Set tenant/user context if available if ($c->has(SessionTenant::class)) { $tenant = $c->get(SessionTenant::class); $cache->setTenantContext($tenant->identifier()); } if ($c->has(SessionIdentity::class)) { $identity = $c->get(SessionIdentity::class); $cache->setUserContext($identity->identifier()); } return $cache; }, // Persistent Cache - for long-lived data (routes, modules, compiled configs) PersistentCacheInterface::class => function(ContainerInterface $c) use ($projectDir) { $storeType = $c->has('cache.persistent') ? $c->get('cache.persistent') : 'file'; $storeMap = [ 'file' => FilePersistentCache::class, // 'database' => DatabasePersistentCache::class, ]; $storeClass = $storeMap[$storeType] ?? $storeType; if (!class_exists($storeClass)) { throw new \RuntimeException("Persistent cache store not found: {$storeClass}"); } $cache = new $storeClass($projectDir); // Set tenant/user context if available if ($c->has(SessionTenant::class)) { $tenant = $c->get(SessionTenant::class); $cache->setTenantContext($tenant->identifier()); } if ($c->has(SessionIdentity::class)) { $identity = $c->get(SessionIdentity::class); $cache->setUserContext($identity->identifier()); } return $cache; }, // Blob Cache - for binary/media data (previews, thumbnails) BlobCacheInterface::class => function(ContainerInterface $c) use ($projectDir) { $storeType = $c->has('cache.blob') ? $c->get('cache.blob') : 'file'; $storeMap = [ 'file' => FileBlobCache::class, // 's3' => S3BlobCache::class, ]; $storeClass = $storeMap[$storeType] ?? $storeType; if (!class_exists($storeClass)) { throw new \RuntimeException("Blob cache store not found: {$storeClass}"); } $cache = new $storeClass($projectDir); // Set tenant/user context if available if ($c->has(SessionTenant::class)) { $tenant = $c->get(SessionTenant::class); $cache->setTenantContext($tenant->identifier()); } if ($c->has(SessionIdentity::class)) { $identity = $c->get(SessionIdentity::class); $cache->setUserContext($identity->identifier()); } return $cache; }, ]); } }