forked from fa/breadcrumb-the-shire
116 lines
4.2 KiB
PHP
116 lines
4.2 KiB
PHP
|
|
<?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 : [];
|
||
|
|
}
|
||
|
|
}
|