From dc335a8334e213d2d1aa6b6d3dcccd8a636c2dfd Mon Sep 17 00:00:00 2001 From: Divarion-D Date: Wed, 24 Jun 2026 22:23:42 +0300 Subject: [PATCH] =?UTF-8?q?chore(psr4):=20phase=203=20(Core)=20=E2=80=94?= =?UTF-8?q?=20namespace=20Core/Enum=20+=20Core/Exception?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Core/Enum → XcVm\Core\Enum (BootContext, ModuleState, ServerEnvironment). - Core/Exception → XcVm\Core\Exception (XcVmException) + \Container (ContainerException, CircularDependencyException, ServiceCreationException) + \Module (ModuleException + the 4 module exceptions). The hierarchy resolves: XcVmException extends \RuntimeException; the Container/Module exceptions extend XcVmException via use; ContainerException keeps its PSR ContainerExceptionInterface. - Add use to referrers across src/ and tests/; fix the leading-backslash refs qualified in earlier hub commits (\ModuleState, \ServerEnvironment, \XcVmException, \ContainerException, \ModuleLoadException, ...) to their FQCNs. - phpstan-baseline.neon regenerated (292→292). Verified: php -l clean; PHPStan no errors (first pass); PHPUnit 295/295; leading-backslash re-sweep clean. --- src/Core/Container/Psr/NotFoundException.php | 3 +- src/Core/Container/ServiceContainer.php | 18 +++++--- src/Core/Enum/BootContext.php | 2 + src/Core/Enum/ModuleState.php | 2 + src/Core/Enum/ServerEnvironment.php | 2 + .../Container/CircularDependencyException.php | 2 + .../Container/ContainerException.php | 3 ++ .../Container/ServiceCreationException.php | 2 + .../Exception/Module/ModuleCycleException.php | 2 + src/Core/Exception/Module/ModuleException.php | 3 ++ .../Exception/Module/ModuleLoadException.php | 2 + .../Module/ModuleManifestException.php | 2 + .../Module/ModuleNotFoundException.php | 2 + src/Core/Exception/XcVmException.php | 6 ++- src/Core/Module/ModuleLoader.php | 46 +++++++++++-------- src/Core/Module/ModuleManager.php | 37 ++++++++------- src/bootstrap.php | 1 + tests/Unit/CoreEnumTest.php | 2 + tests/Unit/ExceptionHierarchyTest.php | 9 ++++ tests/Unit/InterfaceContractTest.php | 1 + tests/Unit/ModuleStateEnumTest.php | 1 + 21 files changed, 101 insertions(+), 47 deletions(-) diff --git a/src/Core/Container/Psr/NotFoundException.php b/src/Core/Container/Psr/NotFoundException.php index cfcbdaca..8bb374d9 100644 --- a/src/Core/Container/Psr/NotFoundException.php +++ b/src/Core/Container/Psr/NotFoundException.php @@ -1,6 +1,7 @@ protectedServices, true)) { - throw new \ContainerException( + throw new \XcVm\Core\Exception\Container\ContainerException( "ServiceContainer: сервис '{$id}' защищён от декорирования модулями." ); } if (!isset($this->factories[$id]) && !array_key_exists($id, $this->resolved)) { - throw new \ContainerException( + throw new \XcVm\Core\Exception\Container\ContainerException( "ServiceContainer: невозможно декорировать незарегистрированный сервис '{$id}'." ); } @@ -299,12 +303,12 @@ class ServiceContainer implements ContainerInterface { try { $service = call_user_func($this->factories[$id], $this); $service = $this->applyDecorators($id, $service); - } catch (\XcVmException $e) { + } catch (\XcVm\Core\Exception\XcVmException $e) { unset($this->creating[$id]); throw $e; } catch (\Exception $e) { unset($this->creating[$id]); - throw new \ServiceCreationException( + throw new \XcVm\Core\Exception\Container\ServiceCreationException( "ServiceContainer: ошибка при создании сервиса '{$id}': " . $e->getMessage(), 0, $e @@ -489,14 +493,14 @@ class ServiceContainer implements ContainerInterface { // ───────────────────────────────────────────────────────── /** - * Throw a \CircularDependencyException describing the resolution chain. + * Throw a \XcVm\Core\Exception\Container\CircularDependencyException describing the resolution chain. * * @param string $id Service id whose creation closed the cycle. * @return never - * @throws \CircularDependencyException Always. + * @throws \XcVm\Core\Exception\Container\CircularDependencyException Always. */ private function throwCircularDependency(string $id): never { - throw new \CircularDependencyException( + throw new \XcVm\Core\Exception\Container\CircularDependencyException( "ServiceContainer: циклическая зависимость при создании сервиса '{$id}'. " . "Цепочка: " . implode(' → ', array_keys($this->creating)) . " → {$id}" ); diff --git a/src/Core/Enum/BootContext.php b/src/Core/Enum/BootContext.php index 146675ba..ba6b24a7 100644 --- a/src/Core/Enum/BootContext.php +++ b/src/Core/Enum/BootContext.php @@ -1,5 +1,7 @@ load($name, $modulePath)) { - throw new \ModuleLoadException("ModuleLoader: failed to load module '{$name}'"); + throw new \XcVm\Core\Exception\Module\ModuleLoadException("ModuleLoader: failed to load module '{$name}'"); } $this->manifests[$name] = $discovered[$name]['manifest']; @@ -344,11 +350,11 @@ class ModuleLoader { * with their paths and normalized manifest data. * * @param array $jsonFiles Array of full paths to module.json files. - * @param \ServerEnvironment $currentEnvironment Current server environment. + * @param \XcVm\Core\Enum\ServerEnvironment $currentEnvironment Current server environment. * @return array Associative array of discovered modules: name => [path, manifest]. * @throws \RuntimeException If manifest has invalid environment value or JSON is malformed. */ - protected function discoverModules(array $jsonFiles, \ServerEnvironment $currentEnvironment): array { + protected function discoverModules(array $jsonFiles, \XcVm\Core\Enum\ServerEnvironment $currentEnvironment): array { $discovered = []; foreach ($jsonFiles as $jsonFile) { @@ -371,7 +377,7 @@ class ModuleLoader { } if (!in_array($manifest['environment'], ['main', 'lb', 'any'], true)) { - throw new \ModuleManifestException("ModuleLoader: invalid environment in module.json for module {$name}"); + throw new \XcVm\Core\Exception\Module\ModuleManifestException("ModuleLoader: invalid environment in module.json for module {$name}"); } // Filter by environment: skip if module is for different environment (skip lb-only on main, etc) @@ -447,11 +453,11 @@ class ModuleLoader { * * Checks SERVER_TYPE constant. Returns LoadBalancer if set to 'lb' (case-insensitive), else Main. */ - protected function getCurrentEnvironment(): \ServerEnvironment { + protected function getCurrentEnvironment(): \XcVm\Core\Enum\ServerEnvironment { if (defined('SERVER_TYPE') && strtolower((string) constant('SERVER_TYPE')) === 'lb') { - return \ServerEnvironment::LoadBalancer; + return \XcVm\Core\Enum\ServerEnvironment::LoadBalancer; } - return \ServerEnvironment::Main; + return \XcVm\Core\Enum\ServerEnvironment::Main; } /** @@ -475,17 +481,17 @@ class ModuleLoader { $manifest = json_decode((string) $raw, true); if (!is_array($manifest)) { - throw new \ModuleManifestException("ModuleLoader: invalid JSON in module manifest for module {$name}"); + throw new \XcVm\Core\Exception\Module\ModuleManifestException("ModuleLoader: invalid JSON in module manifest for module {$name}"); } $normalizeDepArray = function (mixed $raw, string $field) use ($name): array { if (!is_array($raw)) { - throw new \ModuleManifestException("ModuleLoader: {$field} must be array for module {$name}"); + throw new \XcVm\Core\Exception\Module\ModuleManifestException("ModuleLoader: {$field} must be array for module {$name}"); } $result = []; foreach ($raw as $dep) { if (!is_string($dep) || trim($dep) === '') { - throw new \ModuleManifestException("ModuleLoader: {$field} names must be non-empty strings for module {$name}"); + throw new \XcVm\Core\Exception\Module\ModuleManifestException("ModuleLoader: {$field} names must be non-empty strings for module {$name}"); } $result[] = trim($dep); } @@ -573,12 +579,12 @@ class ModuleLoader { // Currently visiting = cycle detected $cycle = array_slice($stack, array_search($name, $stack, true) ?: 0); $cycle[] = $name; - throw new \ModuleCycleException('ModuleLoader: cyclic module dependency detected: ' . implode(' -> ', $cycle)); + throw new \XcVm\Core\Exception\Module\ModuleCycleException('ModuleLoader: cyclic module dependency detected: ' . implode(' -> ', $cycle)); } } if (!isset($discovered[$name])) { - throw new \ModuleLoadException("ModuleLoader: unknown module in dependency graph: {$name}"); + throw new \XcVm\Core\Exception\Module\ModuleLoadException("ModuleLoader: unknown module in dependency graph: {$name}"); } // Mark as currently visiting @@ -588,7 +594,7 @@ class ModuleLoader { // Required dependencies — throw if missing foreach ($discovered[$name]['manifest']['dependencies'] as $dependency) { if (!isset($discovered[$dependency])) { - throw new \ModuleNotFoundException("ModuleLoader: module {$name} requires missing dependency {$dependency}"); + throw new \XcVm\Core\Exception\Module\ModuleNotFoundException("ModuleLoader: module {$name} requires missing dependency {$dependency}"); } $this->visitDependencyNode($dependency, $discovered, $state, $order, $stack); } @@ -732,28 +738,28 @@ class ModuleLoader { } /** - * Resolve the effective \ModuleState for a module from its overrides entry. + * Resolve the effective \XcVm\Core\Enum\ModuleState for a module from its overrides entry. * * Reads both the new 'state' key and the legacy 'enabled' bool key so that * existing config/modules.php files continue to work without migration. * * @param string $name Module name or directory name. - * @return \ModuleState + * @return \XcVm\Core\Enum\ModuleState */ - private function resolveState(string $name): \ModuleState { + private function resolveState(string $name): \XcVm\Core\Enum\ModuleState { $entry = $this->overrides[$name] ?? null; if ($entry === null) { - return \ModuleState::Enabled; + return \XcVm\Core\Enum\ModuleState::Enabled; } // New key takes precedence. if (isset($entry['state'])) { - return \ModuleState::fromRaw($entry['state']); + return \XcVm\Core\Enum\ModuleState::fromRaw($entry['state']); } // Legacy bool key. if (array_key_exists('enabled', $entry)) { - return \ModuleState::fromRaw($entry['enabled']); + return \XcVm\Core\Enum\ModuleState::fromRaw($entry['enabled']); } - return \ModuleState::Enabled; + return \XcVm\Core\Enum\ModuleState::Enabled; } /** diff --git a/src/Core/Module/ModuleManager.php b/src/Core/Module/ModuleManager.php index 21fc00a8..02a0e0ea 100644 --- a/src/Core/Module/ModuleManager.php +++ b/src/Core/Module/ModuleManager.php @@ -1,6 +1,9 @@ Module list. + * @return array Module list. */ public function listModules(): array { $overrides = $this->readOverrides(); @@ -86,7 +89,7 @@ class ModuleManager { foreach ($jsonFiles as $jsonFile) { $name = basename(dirname($jsonFile)); $meta = json_decode((string) @file_get_contents($jsonFile), true) ?: []; - $state = \ModuleState::fromRaw($overrides[$name]['state'] ?? ($overrides[$name]['enabled'] ?? null)); + $state = \XcVm\Core\Enum\ModuleState::fromRaw($overrides[$name]['state'] ?? ($overrides[$name]['enabled'] ?? null)); $items[] = [ 'name' => $name, @@ -128,7 +131,7 @@ class ModuleManager { $name = $this->sanitizeModuleName($name); $module = $this->loadModuleInstance($name); - $this->setState($name, \ModuleState::Installing); + $this->setState($name, \XcVm\Core\Enum\ModuleState::Installing); try { $db = $this->getDb(); @@ -141,11 +144,11 @@ class ModuleManager { $module->install(); } } catch (\Throwable $e) { - $this->setState($name, \ModuleState::Failed); + $this->setState($name, \XcVm\Core\Enum\ModuleState::Failed); throw $e; } - $this->setState($name, \ModuleState::Enabled); + $this->setState($name, \XcVm\Core\Enum\ModuleState::Enabled); // Version priority: explicit $version (platform installs pass the // authoritative SaaS release version) → module.json manifest → the // module's hardcoded getVersion() (which can drift from the manifest). @@ -169,7 +172,7 @@ class ModuleManager { $module = $this->loadModuleInstance($name); $module->uninstall(); $this->clearInstalledVersion($name); - $this->setState($name, \ModuleState::Disabled); + $this->setState($name, \XcVm\Core\Enum\ModuleState::Disabled); } /** @@ -215,10 +218,10 @@ class ModuleManager { * When state is anything else the string value is persisted as 'state'. * * @param string $name Module name. - * @param \ModuleState $state Target lifecycle state. + * @param \XcVm\Core\Enum\ModuleState $state Target lifecycle state. * @return void */ - public function setState(string $name, \ModuleState $state): void { + public function setState(string $name, \XcVm\Core\Enum\ModuleState $state): void { $name = $this->sanitizeModuleName($name); $overrides = $this->readOverrides(); @@ -229,7 +232,7 @@ class ModuleManager { // Remove any legacy bool 'enabled' key — we use 'state' now. unset($overrides[$name]['enabled']); - if ($state === \ModuleState::Enabled) { + if ($state === \XcVm\Core\Enum\ModuleState::Enabled) { // Enabled is the default: clean up the key so the file stays minimal. unset($overrides[$name]['state']); if (empty($overrides[$name])) { @@ -245,14 +248,14 @@ class ModuleManager { /** * Enable or disable a module in config/modules.php. * - * @deprecated Use setState(name, \ModuleState::Enabled / \ModuleState::Disabled) instead. + * @deprecated Use setState(name, \XcVm\Core\Enum\ModuleState::Enabled / \XcVm\Core\Enum\ModuleState::Disabled) instead. * * @param string $name Module name. * @param bool $enabled True to enable, false to disable. * @return void */ public function setEnabled(string $name, bool $enabled): void { - $this->setState($name, $enabled ? \ModuleState::Enabled : \ModuleState::Disabled); + $this->setState($name, $enabled ? \XcVm\Core\Enum\ModuleState::Enabled : \XcVm\Core\Enum\ModuleState::Disabled); } /** @@ -353,7 +356,7 @@ class ModuleManager { $this->storeModuleArchive($zipFilePath, $moduleName, $version); $this->recordInstalledVersion($moduleName, $version); - $this->setState($moduleName, \ModuleState::Enabled); + $this->setState($moduleName, \XcVm\Core\Enum\ModuleState::Enabled); $this->hotReloadSafe($moduleName, $targetDir); return $moduleName; @@ -741,7 +744,7 @@ class ModuleManager { if ($prevVersion !== null) { $this->recordInstalledVersion($slug, $prevVersion); } - $this->setState($slug, \ModuleState::Enabled); + $this->setState($slug, \XcVm\Core\Enum\ModuleState::Enabled); return true; } @@ -770,7 +773,7 @@ class ModuleManager { )); $this->recordInstalledVersion($slug, (string) ($result['version'] ?: $version)); - $this->setState($slug, \ModuleState::Enabled); + $this->setState($slug, \XcVm\Core\Enum\ModuleState::Enabled); $this->hotReloadSafe($slug, $result['path']); } @@ -941,12 +944,12 @@ class ModuleManager { $loader = new ModuleLoader(); $ok = $loader->load($name, $this->modulesPath . '/' . $name); if (!$ok) { - throw new \ModuleNotFoundException('Cannot load module: ' . $name); + throw new \XcVm\Core\Exception\Module\ModuleNotFoundException('Cannot load module: ' . $name); } $module = $loader->getModule($name); if (!$module) { - throw new \ModuleNotFoundException('Module instance is not available: ' . $name); + throw new \XcVm\Core\Exception\Module\ModuleNotFoundException('Module instance is not available: ' . $name); } return $module; @@ -962,7 +965,7 @@ class ModuleManager { private function sanitizeModuleName(string $name): string { $name = trim((string) $name); if (!preg_match('/^[a-z0-9][a-z0-9\-]*$/', $name)) { - throw new \ModuleException('Invalid module name.'); + throw new \XcVm\Core\Exception\Module\ModuleException('Invalid module name.'); } return $name; } diff --git a/src/bootstrap.php b/src/bootstrap.php index c74066f1..12731fe7 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -54,6 +54,7 @@ */ declare(strict_types=0); +use XcVm\Core\Enum\BootContext; use XcVm\Core\Events\EventDispatcher; use XcVm\Core\Container\ServiceContainer; use XcVm\Core\Database\Database; diff --git a/tests/Unit/CoreEnumTest.php b/tests/Unit/CoreEnumTest.php index cb2ac273..0c0c4eaa 100644 --- a/tests/Unit/CoreEnumTest.php +++ b/tests/Unit/CoreEnumTest.php @@ -1,5 +1,7 @@