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:
@@ -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> */
|
||||
|
||||
Reference in New Issue
Block a user