*/ public readonly array $routes; /** @var list */ public readonly array $publicPaths; /** @var list Container registrar FQCNs */ public readonly array $containerRegistrars; /** @var array Keyed by slot name (e.g. 'aside.tab_panel') */ public readonly array $uiSlots; /** @var list SearchResourceProvider FQCNs */ public readonly array $searchResources; /** @var array> Asset group name → file list */ public readonly array $assetGroups; /** @var list */ public readonly array $schedulerJobs; /** @var list LayoutContextProvider FQCNs */ public readonly array $layoutContextProviders; /** @var list SessionProvider FQCNs */ public readonly array $sessionProviders; /** @var list Permission keys (e.g. 'address_book.view') */ public readonly array $permissions; public readonly ?string $migrationsPath; public readonly string $basePath; /** * @param array $raw The manifest array returned by module.php * @param string $basePath Absolute path to the module directory */ private function __construct(array $raw, string $basePath) { $this->basePath = $basePath; // — required fields ———————————————————————————— if (!isset($raw['id']) || !is_string($raw['id']) || trim($raw['id']) === '') { throw new InvalidArgumentException('Module manifest must have a non-empty string "id".'); } $this->id = trim($raw['id']); $this->version = isset($raw['version']) && is_string($raw['version']) ? trim($raw['version']) : '0.0.0'; $this->enabledByDefault = (bool) ($raw['enabled_by_default'] ?? false); $this->loadOrder = (int) ($raw['load_order'] ?? 100); // — optional contribution arrays ——————————————— $this->routes = self::arrayOf($raw, 'routes'); $this->publicPaths = self::listOf($raw, 'public_paths'); $this->containerRegistrars = self::listOf($raw, 'container_registrars'); $this->uiSlots = is_array($raw['ui_slots'] ?? null) ? $raw['ui_slots'] : []; $this->searchResources = self::listOf($raw, 'search_resources'); $this->assetGroups = is_array($raw['asset_groups'] ?? null) ? $raw['asset_groups'] : []; $this->schedulerJobs = self::arrayOf($raw, 'scheduler_jobs'); $this->layoutContextProviders = self::listOf($raw, 'layout_context_providers'); $this->sessionProviders = self::listOf($raw, 'session_providers'); $this->permissions = self::listOf($raw, 'permissions'); $migrationsPath = $raw['migrations_path'] ?? null; $this->migrationsPath = is_string($migrationsPath) && trim($migrationsPath) !== '' ? rtrim($basePath, '/') . '/' . ltrim(trim($migrationsPath), '/') : null; } /** * @param array $raw */ public static function fromArray(array $raw, string $basePath): self { return new self($raw, $basePath); } /** * @return list */ private static function listOf(array $raw, string $key): array { $value = $raw[$key] ?? []; return is_array($value) ? array_values($value) : []; } /** * @return array */ private static function arrayOf(array $raw, string $key): array { $value = $raw[$key] ?? []; return is_array($value) ? $value : []; } }