Files
michaelschiemer/src/Framework/View/Processors/SlotProcessor.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

194 lines
5.8 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\View\Processors;
use App\Framework\LiveComponents\Contracts\SupportsSlots;
use App\Framework\LiveComponents\SlotManager;
use App\Framework\LiveComponents\ValueObjects\SlotContent;
use App\Framework\Template\Processing\DomProcessor;
use App\Framework\View\DomWrapper;
use App\Framework\View\RenderContext;
/**
* Slot Processor for Template System
*
* Processes slot tags in templates and resolves them using the SlotManager.
*
* Syntax:
* - <slot name="header">Default content</slot>
* - <slot>Default slot content</slot>
* - <slot name="content" scope="true">Scoped content with {context.key}</slot>
*
* 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
* <component name="card">
* <slot name="header">Default Header</slot>
* <slot>Main Content</slot>
* </component>
* ```
*/
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<SlotContent>
*/
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);
}
}