register([ * app_path('Core'), * app_path('Mod'), * app_path('Website'), * ]); * * // Query registered modules: * $events = $registry->getEvents(); * $modules = $registry->getModules(); * $listeners = $registry->getListenersFor(WebRoutesRegistering::class); * ``` * * ## Adding Paths After Initial Registration * * Use `addPaths()` to register additional module directories after the initial * registration (e.g., for dynamically loaded plugins): * * ```php * $registry->addPaths([base_path('plugins/custom-module')]); * ``` * * * @see ModuleScanner For the discovery mechanism * @see LazyModuleListener For the lazy-loading wrapper */ class ModuleRegistry { /** * Event-to-module mappings discovered by the scanner. * * Structure: [EventClass => [ModuleClass => ['method' => string, 'priority' => int]]] * * @var array> */ private array $mappings = []; /** * Whether initial registration has been performed. */ private bool $registered = false; /** * Create a new ModuleRegistry instance. * * @param ModuleScanner $scanner The scanner used to discover module listeners */ public function __construct( private ModuleScanner $scanner ) {} /** * Scan paths and register lazy listeners for all declared events. * * Listeners are sorted by priority (highest first) before registration. * * @param array $paths Directories containing modules */ public function register(array $paths): void { if ($this->registered) { return; } $this->mappings = $this->scanner->scan($paths); foreach ($this->mappings as $event => $listeners) { $sorted = $this->sortByPriority($listeners); foreach ($sorted as $moduleClass => $config) { Event::listen($event, new LazyModuleListener($moduleClass, $config['method'])); } } $this->registered = true; } /** * Sort listeners by priority (highest first). * * @param array $listeners * @return array */ private function sortByPriority(array $listeners): array { uasort($listeners, fn ($a, $b) => $b['priority'] <=> $a['priority']); return $listeners; } /** * Get all scanned mappings. * * @return array> Event => [Module => config] */ public function getMappings(): array { return $this->mappings; } /** * Get modules that listen to a specific event. * * @return array Module => config */ public function getListenersFor(string $event): array { return $this->mappings[$event] ?? []; } /** * Check if registration has been performed. */ public function isRegistered(): bool { return $this->registered; } /** * Get all events that have listeners. * * @return array */ public function getEvents(): array { return array_keys($this->mappings); } /** * Get all modules that have declared listeners. * * @return array */ public function getModules(): array { $modules = []; foreach ($this->mappings as $listeners) { foreach (array_keys($listeners) as $module) { $modules[$module] = true; } } return array_keys($modules); } /** * Add additional paths to scan and register. * * Used by packages to register their module paths. * Note: Priority ordering only applies within the newly added paths. * For full priority control, use register() with all paths. * * @param array $paths Directories containing modules */ public function addPaths(array $paths): void { $newMappings = $this->scanner->scan($paths); foreach ($newMappings as $event => $listeners) { $sorted = $this->sortByPriority($listeners); foreach ($sorted as $moduleClass => $config) { // Skip if already registered if (isset($this->mappings[$event][$moduleClass])) { continue; } $this->mappings[$event][$moduleClass] = $config; Event::listen($event, new LazyModuleListener($moduleClass, $config['method'])); } } } }