+ * Module to register. + *
+ * + * @throws \FireHub\Module\Exception\ModuleAlreadyRegisteredException If the module is already registered. + * @throws \FireHub\Module\Exception\ModuleManagerFrozenException If the manager is frozen. + * + * @return self The manager instance. + */ + public function register (Module $module):self { + + if ($this->frozen) + throw new ModuleManagerFrozenException; + + $class = $module::class; + + if (isset($this->modules[$class])) + throw new ModuleAlreadyRegisteredException; + + $this->modules[$class] = $module; + + return $this; + + + } + + /** + * ### Checks whether a module is registered + * @since 1.0.0 + * + * @param class-string<\FireHub\Core\Boundary\Module> $class+ * Module class name. + *
+ * + * @return bool True if the module is registered. + */ + public function has (string $class):bool { + + return isset($this->modules[$class]); + + } + + /** + * ### Gets a registered module + * @since 1.0.0 + * + * @param class-string<\FireHub\Core\Boundary\Module> $class+ * Module class name. + *
+ * + * @throws \FireHub\Module\Exception\ModuleNotFoundException If the module is not registered. + * + * @return \FireHub\Core\Boundary\Module Registered module. + */ + public function get (string $class):Module { + + return $this->modules[$class] + ?? throw new ModuleNotFoundException; + + } + + /** + * ### Gets all registered modules + * @since 1.0.0 + * + * @return array+ * Service registrar. + *
+ * + * @throws \FireHub\Module\Exception\ModuleDependencyNotFoundException If a required module is not registered. + * @throws \FireHub\Module\Exception\CircularModuleDependencyException If circular dependencies are detected. + * + * @return void + */ + public function registerServices (Registrar $registrar):void { + + foreach ($this->ordered() as $module) { + + if ($module instanceof RegistersServices) + $module->register($registrar); + + } + + } + + /** + * ### Freezes the module registry + * + * Prevents further module registration while preserving access to previously registered modules. + * @since 1.0.0 + * + * @return void + */ + public function freeze ():void { + + $this->frozen = true; + + } + + /** + * ### Visits a module dependency graph + * + * Performs a depth-first traversal to resolve module ordering and detect circular dependencies. + * @since 1.0.0 + * + * @uses \FireHub\Core\Boundary\Module\Dependencies::dependencies() Gets the module dependencies. + * + * @param \FireHub\Core\Boundary\Module $module+ * Module to visit. + *
+ * @param array+ * Module visitation states. + *
+ * @param list<\FireHub\Core\Boundary\Module> $ordered+ * Resolved module order. + *
+ * + * @throws \FireHub\Module\Exception\ModuleDependencyNotFoundException If a required module is not registered. + * @throws \FireHub\Module\Exception\CircularModuleDependencyException If circular dependencies are detected. + * + * @return void + */ + private function visit (Module $module, array &$states, array &$ordered):void { + + $class = $module::class; + + $state = $states[$class] ?? 0; + + if ($state === 2) return; + + if ($state === 1) + throw new CircularModuleDependencyException( + context: [ + 'module' => $class, + ] + ); + + $states[$class] = 1; + + if ($module instanceof Dependencies) { + + foreach ($module->dependencies() as $dependency) { + + if (!isset($this->modules[$dependency])) + throw new ModuleDependencyNotFoundException( + context: [ + 'dependency' => $dependency, + ] + ); + + $this->visit($this->modules[$dependency], $states, $ordered); + + } + + } + + $states[$class] = 2; + + $ordered[] = $module; + + } + +} \ No newline at end of file