modulesPath = rtrim($modulesPath, '/'); } /** * Registers the autoloader for module classes * Pattern: Modules\{Module}\{Class} -> modules/{module}/classes/{Class}.php */ public function registerAutoload(): void { spl_autoload_register(function ($class) { // Base prefix $prefix = 'Modules\\'; // Checks if the class uses the prefix $len = strlen($prefix); if (strncmp($prefix, $class, $len) !== 0) { return; } // Gets the relative class name without the prefix $relativeClass = substr($class, $len); // Separates the module name (first part of the namespace) $parts = explode('\\', $relativeClass); $moduleName = array_shift($parts); if (empty($moduleName)) { return; } // Converts the module name to lowercase to match the folder $moduleDir = strtolower($moduleName); // Rebuilds the class path $classPath = implode('/', $parts); // File path $file = $this->modulesPath . '/' . $moduleDir . '/classes/' . $classPath . '.php'; // If the file exists, load it if (file_exists($file)) { require $file; } }); } /** * Loads all modules */ public function loadAll(): void { $dirs = glob($this->modulesPath . '/*', GLOB_ONLYDIR); foreach ($dirs as $dir) { $moduleName = basename($dir); $this->load($moduleName); } } /** * Loads a specific module */ public function load(string $moduleName): void { if (isset($this->loaded[$moduleName])) { return; // already loaded } $modulePath = $this->modulesPath . '/' . $moduleName; // Checks if it exists if (!is_dir($modulePath)) { throw new Exception("Módulo '{$moduleName}' não encontrado."); } // Checks module.json $jsonFile = $modulePath . '/module.json'; if (!file_exists($jsonFile)) { throw new Exception("O módulo '{$moduleName}' não possui um module.json válido."); } $config = json_decode(file_get_contents($jsonFile), true); if (!is_array($config)) { throw new Exception("module.json inválido no módulo '{$moduleName}'."); } $this->modules[$moduleName] = $config; // Load Service Providers $this->registerProviders($config); // Load Routes $this->registerRoutes($moduleName, $modulePath, $config); // Register Views and Locales $this->registerResources($moduleName, $modulePath, $config); $this->loaded[$moduleName] = true; } /** * Registers Service Providers */ protected function registerProviders(array $config): void { if (!isset($config['providers'])) { return; } foreach ($config['providers'] as $class) { if (class_exists($class)) { $provider = new $class(); if (method_exists($provider, 'register')) { $provider->register(); } if (method_exists($provider, 'boot')) { $provider->boot(); } } } } /** * Loads route files */ protected function registerRoutes(string $module, string $path, array $config): void { if (!isset($config['routes'])) { return; } // Get the application instance to register routes $app = \Kernel\Application::getInstance(); if (isset($config['routes']['web'])) { $file = $path . '/' . $config['routes']['web']; if (file_exists($file)) { $routeLoader = require $file; if (is_callable($routeLoader) && $app) { $routeLoader($app); } } } if (isset($config['routes']['api'])) { $file = $path . '/' . $config['routes']['api']; if (file_exists($file)) { $routeLoader = require $file; if (is_callable($routeLoader) && $app) { $routeLoader($app); } } } } /** * Registers views and translations */ protected function registerResources(string $module, string $path, array $config): void { // Views if (isset($config['views'])) { $viewsPath = $path . '/' . $config['views']; if (is_dir($viewsPath)) { View::addPath($module, $viewsPath); } } // Locales if (isset($config['locales'])) { $localesPath = $path . '/' . $config['locales']; if (is_dir($localesPath)) { Translator::addNamespace($module, $localesPath); } } } /** * Returns the list of loaded modules */ public function getModules(): array { return $this->modules; } }