Default content * - Default slot content * - Scoped content with {context.key} * * Features: * - Named slots (header, footer, sidebar, etc.) * - Default (unnamed) slots * - Scoped slots with context injection * - Default content fallbacks * - Validation of required slots * - Integration with SlotManager and SupportsSlots * * Example: * ```html * * Default Header * Main Content * * ``` */ final readonly class SlotProcessor implements DomProcessor { public function __construct( private SlotManager $slotManager ) { } public function process(DomWrapper $dom, RenderContext $context): DomWrapper { // Check if component supports the new Slot System $component = $context->component ?? null; if ($component instanceof SupportsSlots) { return $this->processWithSlotSystem($dom, $context, $component); } // Fallback to legacy slot processing return $this->processLegacySlots($dom, $context); } /** * Process slots using the new Slot System with SlotManager */ private function processWithSlotSystem( DomWrapper $dom, RenderContext $context, SupportsSlots $component ): DomWrapper { // Extract provided slots from context $providedSlots = $this->extractProvidedSlotsFromContext($context); // Validate slots $errors = $this->slotManager->validateSlots($component, $providedSlots); if (! empty($errors)) { $this->handleValidationErrors($errors, $context); } // Process each slot element in the template foreach ($dom->querySelectorAll('slot') as $slotNode) { $slotName = $slotNode->getAttribute('name') ?: 'default'; // Find slot definition $definition = $this->slotManager->getSlotDefinition($component, $slotName); if ($definition === null) { // Unknown slot, keep default content continue; } // Resolve slot content using SlotManager $resolvedContent = $this->slotManager->resolveSlotContent( component: $component, definition: $definition, providedContents: $providedSlots ); // Replace slot node with resolved content $this->replaceSlotNode($dom, $slotNode, $resolvedContent); } return $dom; } /** * Legacy slot processing for backward compatibility */ private function processLegacySlots(DomWrapper $dom, RenderContext $context): DomWrapper { foreach ($dom->querySelectorAll('slot[name]') as $slotNode) { $slotName = $slotNode->getAttribute('name'); $html = $context->slots[$slotName] ?? null; $replacement = $dom->createDocumentFragment(); if ($html !== null) { @$replacement->appendXML($html); } else { // Fallback-Inhalt erhalten (die inneren Nodes des slot-Tags) foreach ($slotNode->childNodes as $child) { $replacement->appendChild($child->cloneNode(true)); } } $slotNode->parentNode?->replaceChild($replacement, $slotNode); } return $dom; } /** * Extract SlotContent objects from RenderContext * * @return array */ private function extractProvidedSlotsFromContext(RenderContext $context): array { $slots = []; // Check if context has slot data (legacy format) if (isset($context->slots) && is_array($context->slots)) { foreach ($context->slots as $name => $content) { $slots[] = SlotContent::named( slotName: $name, content: $content ); } } // Check if context has SlotContent objects (new format) if (isset($context->slotContents) && is_array($context->slotContents)) { foreach ($context->slotContents as $slotContent) { if ($slotContent instanceof SlotContent) { $slots[] = $slotContent; } } } return $slots; } /** * Replace slot node with resolved content */ private function replaceSlotNode(DomWrapper $dom, \DOMElement $slotNode, string $content): void { $replacement = $dom->createDocumentFragment(); if (! empty($content)) { @$replacement->appendXML($content); } $slotNode->parentNode?->replaceChild($replacement, $slotNode); } /** * Handle slot validation errors */ private function handleValidationErrors(array $errors, RenderContext $context): void { $errorMessage = "Slot validation failed:\n" . implode("\n", $errors); // In development, we might want to show errors // In production, log them $isDevelopment = ($context->environment ?? 'production') === 'development'; if ($isDevelopment) { // Store errors in context for debugging $context->slotValidationErrors = $errors; } // Always log errors error_log($errorMessage); } }