Files
breadcrumb-the-shire/lib/App/Module/ModuleManifest.php

116 lines
4.2 KiB
PHP
Raw Normal View History

<?php
namespace MintyPHP\App\Module;
use InvalidArgumentException;
/**
* Immutable value object describing a single module's contributions.
*
* Each module ships a `module.php` that returns an associative array.
* ModuleManifest validates and normalises that array into a typed contract.
*/
final class ModuleManifest
{
public readonly string $id;
public readonly string $version;
public readonly bool $enabledByDefault;
public readonly int $loadOrder;
/** @var array<int, array{path: string, target: string, public?: bool}> */
public readonly array $routes;
/** @var list<string> */
public readonly array $publicPaths;
/** @var list<class-string> Container registrar FQCNs */
public readonly array $containerRegistrars;
/** @var array<string, mixed> Keyed by slot name (e.g. 'aside.tab_panel') */
public readonly array $uiSlots;
/** @var list<class-string> SearchResourceProvider FQCNs */
public readonly array $searchResources;
/** @var array<string, list<string>> Asset group name → file list */
public readonly array $assetGroups;
/** @var list<array{name: string, handler: string, schedule?: string}> */
public readonly array $schedulerJobs;
/** @var list<class-string> LayoutContextProvider FQCNs */
public readonly array $layoutContextProviders;
/** @var list<class-string> SessionProvider FQCNs */
public readonly array $sessionProviders;
/** @var list<string> Permission keys (e.g. 'address_book.view') */
public readonly array $permissions;
public readonly ?string $migrationsPath;
public readonly string $basePath;
/**
* @param array<string, mixed> $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<string, mixed> $raw
*/
public static function fromArray(array $raw, string $basePath): self
{
return new self($raw, $basePath);
}
/**
* @return list<mixed>
*/
private static function listOf(array $raw, string $key): array
{
$value = $raw[$key] ?? [];
return is_array($value) ? array_values($value) : [];
}
/**
* @return array<int|string, mixed>
*/
private static function arrayOf(array $raw, string $key): array
{
$value = $raw[$key] ?? [];
return is_array($value) ? $value : [];
}
}