refactor: simplify module platform — drop custom autoloader, layout_capabilities indirection, and absolute template paths
- Replace ModuleAutoloader with Composer ClassLoader (addPsr4 via spl_autoload_functions) - Eliminate layout_capabilities mapping; UI slots reference ability strings directly - Resolve relative template paths in ModuleRegistry instead of baking __DIR__ into manifests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace MintyPHP\App\Module;
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
@@ -66,9 +67,6 @@ final class ModuleRegistry
|
||||
/** @var list<class-string> merged authorization policy FQCNs */
|
||||
private array $mergedAuthorizationPolicies = [];
|
||||
|
||||
/** @var array<string, string> merged layout capabilities: UI-key → ability */
|
||||
private array $mergedLayoutCapabilities = [];
|
||||
|
||||
/**
|
||||
* @var list<array{
|
||||
* job_key: string,
|
||||
@@ -154,8 +152,9 @@ final class ModuleRegistry
|
||||
return $a->loadOrder <=> $b->loadOrder ?: strcmp($a->id, $b->id);
|
||||
});
|
||||
|
||||
// Allow module-local classes (modules/<id>/lib/...) to autoload.
|
||||
ModuleAutoloader::register($manifests);
|
||||
// Register module lib dirs with Composer's PSR-4 autoloader so that
|
||||
// module classes (MintyPHP\Module\<Name>\*) resolve without a custom loader.
|
||||
self::registerModuleAutoloading($manifests);
|
||||
|
||||
// Register and merge
|
||||
foreach ($manifests as $manifest) {
|
||||
@@ -192,6 +191,33 @@ final class ModuleRegistry
|
||||
return array_values(array_unique(array_filter($fromConfig, static fn (string $id): bool => trim($id) !== '')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register module lib/ directories with Composer's ClassLoader.
|
||||
*
|
||||
* @param list<ModuleManifest> $manifests
|
||||
*/
|
||||
private static function registerModuleAutoloading(array $manifests): void
|
||||
{
|
||||
$composerLoader = null;
|
||||
foreach (spl_autoload_functions() as $autoloader) {
|
||||
if (is_array($autoloader) && $autoloader[0] instanceof ClassLoader) {
|
||||
$composerLoader = $autoloader[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($composerLoader === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($manifests as $manifest) {
|
||||
$libDir = rtrim($manifest->basePath, '/') . '/lib';
|
||||
if (is_dir($libDir)) {
|
||||
$composerLoader->addPsr4('MintyPHP\\', [$libDir]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function registerModule(ModuleManifest $manifest): void
|
||||
{
|
||||
if (isset($this->modules[$manifest->id])) {
|
||||
@@ -240,7 +266,7 @@ final class ModuleRegistry
|
||||
}
|
||||
$contributionList = is_array($contributions) ? $contributions : [$contributions];
|
||||
foreach ($contributionList as $contribution) {
|
||||
$normalizedContribution = $this->normalizeUiSlotContribution($slotName, $contribution, $manifest->id);
|
||||
$normalizedContribution = $this->normalizeUiSlotContribution($slotName, $contribution, $manifest->id, $manifest->basePath);
|
||||
$key = (string) $normalizedContribution['key'];
|
||||
foreach ($this->mergedUiSlots[$slotName] as $existing) {
|
||||
$existingKey = is_array($existing) ? ($existing['key'] ?? null) : null;
|
||||
@@ -339,23 +365,6 @@ final class ModuleRegistry
|
||||
$this->mergedAuthorizationPolicies[] = $policyClass;
|
||||
}
|
||||
|
||||
// — Merge layout capabilities (fail-fast on duplicate key) ———
|
||||
foreach ($manifest->layoutCapabilities as $uiKey => $ability) {
|
||||
$uiKey = trim((string) $uiKey);
|
||||
$ability = trim((string) $ability);
|
||||
if ($uiKey === '' || $ability === '') {
|
||||
throw new RuntimeException(
|
||||
"Layout capability in module '{$manifest->id}' must have non-empty key and ability."
|
||||
);
|
||||
}
|
||||
if (isset($this->mergedLayoutCapabilities[$uiKey])) {
|
||||
throw new RuntimeException(
|
||||
"Layout capability conflict: key '{$uiKey}' from module '{$manifest->id}' is already registered."
|
||||
);
|
||||
}
|
||||
$this->mergedLayoutCapabilities[$uiKey] = $ability;
|
||||
}
|
||||
|
||||
// — Merge scheduler jobs —————————————————————————
|
||||
foreach ($manifest->schedulerJobs as $schedulerJob) {
|
||||
$jobKey = trim((string) $schedulerJob['job_key']);
|
||||
@@ -450,7 +459,7 @@ final class ModuleRegistry
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function normalizeUiSlotContribution(string $slotName, mixed $rawContribution, string $moduleId): array
|
||||
private function normalizeUiSlotContribution(string $slotName, mixed $rawContribution, string $moduleId, string $basePath): array
|
||||
{
|
||||
if (!is_array($rawContribution)) {
|
||||
throw new RuntimeException(
|
||||
@@ -465,6 +474,17 @@ final class ModuleRegistry
|
||||
);
|
||||
}
|
||||
|
||||
// Resolve relative template paths to absolute using the module's basePath.
|
||||
foreach (['panel_template', 'template'] as $templateKey) {
|
||||
if (!isset($rawContribution[$templateKey])) {
|
||||
continue;
|
||||
}
|
||||
$templatePath = trim((string) $rawContribution[$templateKey]);
|
||||
if ($templatePath !== '' && !str_starts_with($templatePath, '/')) {
|
||||
$rawContribution[$templateKey] = rtrim($basePath, '/') . '/' . $templatePath;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (self::UI_SLOT_REQUIRED_KEYS[$slotName] ?? [] as $requiredKey) {
|
||||
$requiredValue = trim((string) ($rawContribution[$requiredKey] ?? ''));
|
||||
if ($requiredValue === '') {
|
||||
@@ -660,10 +680,26 @@ final class ModuleRegistry
|
||||
return $this->mergedAuthorizationPolicies;
|
||||
}
|
||||
|
||||
/** @return array<string, string> UI-capability-key → ability-string */
|
||||
public function getLayoutCapabilities(): array
|
||||
/**
|
||||
* Collect unique ability strings from all module UI slot contributions.
|
||||
*
|
||||
* Used by UiAccessService to resolve module abilities into the layout auth map.
|
||||
* Each ability maps to itself (key = ability, value = ability).
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getModuleUiAbilities(): array
|
||||
{
|
||||
return $this->mergedLayoutCapabilities;
|
||||
$abilities = [];
|
||||
foreach ($this->mergedUiSlots as $items) {
|
||||
foreach ($items as $item) {
|
||||
$ability = trim((string) ($item['permission'] ?? ''));
|
||||
if ($ability !== '') {
|
||||
$abilities[$ability] = $ability;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $abilities;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user