modulesPath === '') { $this->modulesPath = base_path('modules'); } } public function discover(): void { $this->modules = []; $this->discovered = true; if (! is_dir($this->modulesPath)) { return; } $dirs = glob($this->modulesPath . '/*/module.json'); if ($dirs === false) { return; } foreach ($dirs as $manifestPath) { $json = file_get_contents($manifestPath); if ($json === false) { continue; } $manifest = json_decode($json, true); if (! is_array($manifest) || empty($manifest['name'])) { Log::warning('ModuleManager: invalid module.json', ['path' => $manifestPath]); continue; } $slug = basename(dirname($manifestPath)); $manifest['slug'] = $slug; $manifest['path'] = dirname($manifestPath); $this->modules[$slug] = $manifest; Log::info('ModuleManager: module discovered', [ 'slug' => $slug, 'version' => $manifest['version'] ?? 'unknown', ]); } } public function registerAutoloader(string $slug, string $namespace, string $path): void { $namespace = rtrim($namespace, '\\') . '\\'; $path = rtrim($path, '/') . '/'; spl_autoload_register(function (string $class) use ($namespace, $path) { if (! str_starts_with($class, $namespace)) { return; } $relative = substr($class, strlen($namespace)); $file = $path . str_replace('\\', '/', $relative) . '.php'; if (file_exists($file)) { require_once $file; } }); } public function boot(): void { if (! $this->discovered) { $this->discover(); } foreach ($this->modules as $slug => $manifest) { if (! $this->isEnabled($slug)) { continue; } $namespace = $manifest['namespace'] ?? null; $srcPath = ($manifest['path'] ?? '') . '/src'; if ($namespace && is_dir($srcPath)) { $this->registerAutoloader($slug, $namespace, $srcPath); } $providerClass = $manifest['service_provider'] ?? null; if (! $providerClass) { continue; } try { if (! class_exists($providerClass)) { Log::error('ModuleManager: ServiceProvider class not found', [ 'slug' => $slug, 'class' => $providerClass, ]); $this->autoDisable($slug, "ServiceProvider class not found: {$providerClass}"); continue; } $provider = new $providerClass($this->app); if (! $provider instanceof ServiceProvider) { Log::error('ModuleManager: class is not a ServiceProvider', [ 'slug' => $slug, 'class' => $providerClass, ]); $this->autoDisable($slug, "Class is not a ServiceProvider: {$providerClass}"); continue; } $provider->register(); $provider->boot(); Log::info('ModuleManager: module booted', [ 'slug' => $slug, 'version' => $manifest['version'] ?? 'unknown', ]); } catch (\Throwable $e) { Log::error('ModuleManager: module boot failed', [ 'slug' => $slug, 'exception' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); $this->autoDisable($slug, $e->getMessage()); } } } public function enable(string $slug): bool { if (! isset($this->modules[$slug])) { return false; } $this->disabledModules = array_diff($this->disabledModules, [$slug]); return true; } public function disable(string $slug): bool { if (! isset($this->modules[$slug])) { return false; } if (! in_array($slug, $this->disabledModules, true)) { $this->disabledModules[] = $slug; } Log::info('ModuleManager: module disabled', ['slug' => $slug]); return true; } public function isEnabled(string $slug): bool { return isset($this->modules[$slug]) && ! in_array($slug, $this->disabledModules, true); } public function getManifest(string $slug): ?array { return $this->modules[$slug] ?? null; } public function getAll(): array { return $this->modules; } public function getDisabled(): array { return $this->disabledModules; } protected function autoDisable(string $slug, string $reason): void { if (! in_array($slug, $this->disabledModules, true)) { $this->disabledModules[] = $slug; } Log::warning('ModuleManager: module auto-disabled', [ 'slug' => $slug, 'reason' => $reason, ]); } }