refactor: remove slot-type validation from ModuleRegistry

Drop UI_SLOT_REQUIRED_KEYS constant and validateUiSlotContributionByType()
(-97 lines). Slot field requirements are now enforced only by architecture
tests (testUiSlotContributionsHaveRequiredKeys, testPanelTemplatesExist)
and by the rendering templates themselves. The registry stays responsible
for key uniqueness, ordering, and module_id tagging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 08:36:37 +01:00
parent cb27fe090d
commit 0b7b27409d

View File

@@ -16,17 +16,6 @@ use RuntimeException;
*/
final class ModuleRegistry
{
/** @var array<string, list<string>> */
private const UI_SLOT_REQUIRED_KEYS = [
'aside.tab_panel' => ['key', 'label', 'icon', 'permission', 'panel_template'],
'search.resource_item' => ['key', 'label', 'base_url', 'permission'],
'user.edit.aside_action' => ['key', 'type', 'label', 'permission'],
'topbar.right_item' => ['key', 'template'],
'layout.body_end_template' => ['key', 'template'],
'layout.head_style' => ['key', 'path'],
'runtime.component' => ['key', 'script'],
];
/** @var array<string, ModuleManifest> keyed by module id */
private array $modules = [];
@@ -485,17 +474,6 @@ final class ModuleRegistry
}
}
foreach (self::UI_SLOT_REQUIRED_KEYS[$slotName] ?? [] as $requiredKey) {
$requiredValue = trim((string) ($rawContribution[$requiredKey] ?? ''));
if ($requiredValue === '') {
throw new RuntimeException(
"UI slot contribution for slot '{$slotName}' in module '{$moduleId}' is missing required key '{$requiredKey}'."
);
}
}
$this->validateUiSlotContributionByType($slotName, $rawContribution, $moduleId, $key);
$normalized = $rawContribution;
$normalized['key'] = $key;
$normalized['order'] = (int) ($rawContribution['order'] ?? 100);
@@ -505,81 +483,6 @@ final class ModuleRegistry
return $normalized;
}
/**
* @param array<string, mixed> $rawContribution
*/
private function validateUiSlotContributionByType(
string $slotName,
array $rawContribution,
string $moduleId,
string $slotKey
): void {
if (in_array($slotName, ['aside.tab_panel', 'topbar.right_item', 'layout.body_end_template'], true)) {
$templatePath = trim((string) ($rawContribution['panel_template'] ?? $rawContribution['template'] ?? ''));
if ($templatePath === '' || !is_file($templatePath)) {
throw new RuntimeException(
"UI slot contribution '{$slotKey}' for slot '{$slotName}' in module '{$moduleId}' references missing template '{$templatePath}'."
);
}
}
if ($slotName === 'layout.head_style') {
$path = trim((string) ($rawContribution['path'] ?? ''));
if ($path === '' || str_contains($path, '..')) {
throw new RuntimeException(
"UI slot contribution '{$slotKey}' for slot 'layout.head_style' in module '{$moduleId}' has invalid path '{$path}'."
);
}
}
if ($slotName === 'user.edit.aside_action') {
$type = strtolower(trim((string) ($rawContribution['type'] ?? 'link')));
if ($type !== 'link' && $type !== 'form') {
throw new RuntimeException(
"UI slot contribution '{$slotKey}' for slot 'user.edit.aside_action' in module '{$moduleId}' has invalid type '{$type}'."
);
}
if ($type === 'form') {
$action = trim((string) ($rawContribution['action'] ?? ''));
if ($action === '') {
throw new RuntimeException(
"UI slot contribution '{$slotKey}' for slot 'user.edit.aside_action' in module '{$moduleId}' requires non-empty 'action' for type=form."
);
}
} else {
$href = trim((string) ($rawContribution['href'] ?? ''));
if ($href === '') {
throw new RuntimeException(
"UI slot contribution '{$slotKey}' for slot 'user.edit.aside_action' in module '{$moduleId}' requires non-empty 'href' for type=link."
);
}
}
}
if ($slotName === 'runtime.component') {
$script = trim((string) ($rawContribution['script'] ?? ''));
if ($script === '' || str_contains($script, '..')) {
throw new RuntimeException(
"UI slot contribution '{$slotKey}' for slot 'runtime.component' in module '{$moduleId}' has invalid script '{$script}'."
);
}
$phase = strtolower(trim((string) ($rawContribution['phase'] ?? 'late')));
if (!in_array($phase, ['early', 'default', 'late'], true)) {
throw new RuntimeException(
"UI slot contribution '{$slotKey}' for slot 'runtime.component' in module '{$moduleId}' has invalid phase '{$phase}'."
);
}
$configPath = trim((string) ($rawContribution['config_path'] ?? ''));
if ($configPath !== '' && !preg_match('/^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z0-9_]+)+$/', $configPath)) {
throw new RuntimeException(
"UI slot contribution '{$slotKey}' for slot 'runtime.component' in module '{$moduleId}' has invalid config_path '{$configPath}'."
);
}
}
}
// ─── Getters ────────────────────────────────────────────────────────
/** @return array<string, ModuleManifest> */