- 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.
19 KiB
XComponentProcessor - Unified Component Syntax
Einheitliche <x-*> Syntax für LiveComponents und HTML Components im Custom PHP Framework.
Übersicht
Der XComponentProcessor ist ein DOM-Processor, der eine einheitliche Syntax für zwei verschiedene Component-Typen bietet:
- LiveComponents (Interaktiv/Stateful) - Komponenten mit #[LiveComponent] Attribut
- HTML Components (Statisch) - Komponenten mit #[ComponentName] Attribut
Unified Syntax: <x-componentname>
<!-- LiveComponent (Interactive) -->
<x-datatable id="users" page="1" pageSize="25" />
<!-- HTML Component (Static) -->
<x-button variant="primary">Click me</x-button>
Architektur
Component Auto-Detection Flow
<x-componentname> Syntax
↓
XComponentProcessor
↓
┌─────┴─────┐
↓ ↓
LiveComponent? HTML Component?
↓ ↓
YES NO
↓ ↓
Process as Process as
Interactive Static
Detection Priority:
- LiveComponent prüfen (via
ComponentRegistry::isRegistered()) - HTML Component prüfen (via
HtmlComponentRegistry::has()) - Not Found → Helpful error message
Klassen-Struktur
final readonly class XComponentProcessor implements DomProcessor
{
public function __construct(
private ComponentRegistry $liveComponentRegistry,
private HtmlComponentRegistry $htmlComponentRegistry,
private ComponentMetadataCache $metadataCache,
private DomComponentService $componentService
) {}
public function process(DomWrapper $dom, RenderContext $context): DomWrapper
{
// Find all <x-*> elements
$xComponents = $this->findXComponents($dom);
foreach ($xComponents as $element) {
try {
$this->processXComponent($dom, $element, $context);
} catch (\Throwable $e) {
$this->handleProcessingError($dom, $element, $e);
}
}
return $dom;
}
}
LiveComponents (Interactive)
Verwendung
<!-- LiveComponent mit Props -->
<x-datatable
id="users-table"
page="1"
pageSize="25"
sortBy="created_at"
sortOrder="desc"
/>
<!-- LiveComponent ohne explizite ID (auto-generiert) -->
<x-counter initialValue="0" />
<!-- Mit JSON Props -->
<x-chart
data='[{"x":1,"y":10},{"x":2,"y":20}]'
options='{"type":"line","legend":true}'
/>
LiveComponent Definition
use App\Framework\LiveComponents\Attributes\LiveComponent;
use App\Framework\LiveComponents\Attributes\LiveProp;
#[LiveComponent(name: 'datatable')]
final class DataTableComponent
{
#[LiveProp]
public int $page = 1;
#[LiveProp]
public int $pageSize = 25;
#[LiveProp]
public string $sortBy = 'id';
#[LiveProp]
public string $sortOrder = 'asc';
public function render(): RenderData
{
$data = $this->loadData();
return new RenderData(
template: 'components/datatable',
data: [
'rows' => $data,
'currentPage' => $this->page,
'totalPages' => $this->calculateTotalPages()
]
);
}
}
Processing Flow
<x-datatable id="users" page="1" pageSize="25" />
↓
1. Extract component name: "datatable"
2. Check ComponentRegistry: isRegistered("datatable") → true
3. Extract props: ["id" => "users", "page" => "1", "pageSize" => "25"]
4. Type coercion: ["id" => "users", "page" => 1, "pageSize" => 25]
5. Validate props gegen ComponentMetadata
6. Create ComponentId: ComponentId::create("datatable", "users")
7. Create ComponentData: ComponentData::fromArray(["page" => 1, "pageSize" => 25])
8. Resolve component: $registry->resolve($componentId, $initialState)
9. Render with wrapper: $registry->renderWithWrapper($component)
10. Replace <x-datatable> with rendered HTML
↓
<div data-component-id="datatable:users" data-component-state="...">
<!-- Rendered component HTML -->
</div>
Type Coercion (LiveComponents only)
Der XComponentProcessor führt automatische Type Coercion für LiveComponent-Props durch:
// Input: HTML Attributes (always strings)
<x-component
stringProp="text"
intProp="123"
floatProp="12.5"
boolTrue="true"
boolFalse="false"
nullProp="null"
arrayProp="[1,2,3]"
objectProp='{"key":"value"}'
/>
// Output: Coerced PHP types
[
'stringProp' => 'text', // string
'intProp' => 123, // int
'floatProp' => 12.5, // float
'boolTrue' => true, // bool
'boolFalse' => false, // bool
'nullProp' => null, // null
'arrayProp' => [1, 2, 3], // array
'objectProp' => ['key' => 'value'] // array
]
Coercion Rules:
"true"/"false"→bool"null"→null"123"→int"12.5"→float"[...]"/"{...}"→ JSON decode- Alles andere →
string
Prop Validation
// Component Definition
#[LiveComponent(name: 'counter')]
final class CounterComponent
{
#[LiveProp]
public int $initialValue = 0;
#[LiveProp]
public int $step = 1;
}
// ✅ Valid Usage
<x-counter initialValue="10" step="2" />
// ❌ Invalid: Unknown prop 'invalidProp'
<x-counter invalidProp="test" />
// → Development: Error message
// → Production: Silent removal
// ❌ Invalid: Wrong type (caught at component level)
<x-counter initialValue="not-a-number" />
// → Type coercion to string, component validation throws error
Validation Error (Development Mode):
<div style="border:2px solid red;padding:1rem;background:#fee;color:#c00;">
<strong>XComponentProcessor Error:</strong><br>
<pre>LiveComponent 'counter' has no property 'invalidProp'.
Available properties: initialValue, step</pre>
<small>Component: x-counter</small>
</div>
HTML Components (Static)
Verwendung
<!-- Static Button Component -->
<x-button variant="primary">Click me</x-button>
<!-- Card Component with content -->
<x-card title="User Profile">
<p>User information goes here</p>
</x-card>
<!-- Badge Component -->
<x-badge color="blue">New</x-badge>
HTML Component Definition
use App\Framework\View\Attributes\ComponentName;
#[ComponentName('button')]
final readonly class ButtonComponent
{
public function render(string $content, array $attributes): string
{
$variant = $attributes['variant'] ?? 'default';
$class = "btn btn-{$variant}";
return "<button class=\"{$class}\">{$content}</button>";
}
}
Processing Flow
<x-button variant="primary">Click me</x-button>
↓
1. Extract component name: "button"
2. Check ComponentRegistry: isRegistered("button") → false
3. Check HtmlComponentRegistry: has("button") → true
4. Extract content: "Click me"
5. Extract attributes: ["variant" => "primary"] (as strings)
6. Render: $registry->render("button", "Click me", ["variant" => "primary"])
7. Replace <x-button> with rendered HTML
↓
<button class="btn btn-primary">Click me</button>
Unterschiede zu LiveComponents
HTML Components:
- ❌ Keine Type Coercion - alle Attribute bleiben strings
- ❌ Keine Prop Validation - Component entscheidet selbst
- ❌ Kein State Management - rein statisches Rendering
- ❌ Keine ComponentId/ComponentData - nur template rendering
- ✅ Schneller - keine Metadata-Prüfung, kein State-Handling
- ✅ Einfacher - nur
render(content, attributes)Method
LiveComponents:
- ✅ Type Coercion für alle Props
- ✅ Prop Validation gegen Metadata
- ✅ State Management mit ComponentData
- ✅ Interactive mit AJAX/WebSocket updates
- ⚠️ Komplexer - mehr Overhead für Metadata/State
Component Registration
LiveComponent Registration
// Automatisch via #[LiveComponent] Attribute Discovery
use App\Framework\LiveComponents\Attributes\LiveComponent;
#[LiveComponent(name: 'datatable')]
final class DataTableComponent implements LiveComponentContract
{
// Component implementation
}
// Framework registriert automatisch bei Bootstrap:
// ComponentRegistry::register('datatable', DataTableComponent::class)
HTML Component Registration
// Automatisch via #[ComponentName] Attribute Discovery
use App\Framework\View\Attributes\ComponentName;
#[ComponentName('button')]
final readonly class ButtonComponent
{
public function render(string $content, array $attributes): string
{
// Rendering logic
}
}
// Framework registriert automatisch:
// HtmlComponentRegistry::register('button', ButtonComponent::class)
Error Handling
Component Not Found
<x-unknown-component />
Development Mode:
<div style="border:2px solid red;padding:1rem;background:#fee;color:#c00;">
<strong>XComponentProcessor Error:</strong><br>
<pre>Unknown component: <x-unknown-component>
Available LiveComponents: datatable, counter, chart
Available HTML Components: button, card, badge</pre>
<small>Component: x-unknown-component</small>
</div>
Production Mode:
- Element wird silently removed
- Kein Error-HTML im Output
- Optional: Logging via
error_log()
Invalid Props (LiveComponents)
<x-counter invalidProp="test" />
Development Mode:
<div style="border:2px solid red;">
<strong>XComponentProcessor Error:</strong><br>
<pre>LiveComponent 'counter' has no property 'invalidProp'.
Available properties: initialValue, step</pre>
<small>Component: x-counter</small>
</div>
Production Mode:
- Component wird nicht gerendert
- Silent removal
- Optional: Logging
Rendering Errors
Wenn ComponentRegistry::renderWithWrapper() oder HtmlComponentRegistry::render() fehlschlägt:
Development Mode: Error message with stack trace Production Mode: Silent removal + logging
Auto-Detection Priority
Wenn Component in BEIDEN Registries existiert:
LiveComponent Priority > HTML Component
Beispiel:
// BOTH registered:
#[LiveComponent(name: 'button')]
class ButtonLiveComponent { ... }
#[ComponentName('button')]
class ButtonHtmlComponent { ... }
// Usage:
<x-button id="my-button" />
// → Processed as LiveComponent (Priority!)
Best Practice: Verwende unterschiedliche Namen für LiveComponents und HTML Components, um Verwirrung zu vermeiden.
Performance Characteristics
LiveComponent Processing
- Prop Extraction: O(n) wo n = Anzahl Attribute
- Type Coercion: O(n) wo n = Anzahl Props
- Prop Validation: O(n) wo n = Anzahl Props (Metadata lookup cached)
- Component Resolution: Container DI Resolution
- Rendering: Depends on component logic
Durchschnittliche Latenz: ~5-15ms pro LiveComponent
HTML Component Processing
- Attribute Extraction: O(n) wo n = Anzahl Attribute
- Content Extraction: O(1)
- Rendering: Component-spezifische Logic
Durchschnittliche Latenz: ~1-5ms pro HTML Component
Optimization Strategies
// 1. ComponentMetadataCache nutzen (automatisch)
$metadata = $this->metadataCache->get($className);
// 2. Registry Lookups cachen (automatisch)
if ($this->liveComponentRegistry->isRegistered($name)) { ... }
// 3. Batch-Processing für multiple Components
foreach ($xComponents as $element) {
$this->processXComponent($dom, $element, $context);
}
Framework Integration
DomProcessingPipeline Integration
// In TemplateRendererInitializer
$domProcessors = [
ForProcessor::class, // Priority 100
IfProcessor::class, // Priority 200
XComponentProcessor::class, // Priority 300 ← Here
ComponentProcessor::class, // Priority 400 (legacy <component>)
IncludeProcessor::class, // Priority 500
// ...
];
Processing Order:
<for>loops werden zuerst verarbeitet<if>conditionals danach<x-*>Components werden resolved- Legacy
<component>tags (fallback) <include>templates einbinden
RenderContext Integration
$context = RenderContext::create([
'users' => $users,
'currentPage' => $page
]);
// XComponentProcessor hat Zugriff auf $context->data
// Aber: Props werden via Attribute übergeben, nicht via Context
Testing
Unit Tests
Der XComponentProcessor hat umfassende Test-Coverage:
describe('XComponentProcessor', function () {
describe('LiveComponent Processing', function () {
it('processes LiveComponent with basic props');
it('coerces prop types correctly');
it('validates props against ComponentMetadata');
it('generates unique ID if not provided');
});
describe('HTML Component Processing', function () {
it('processes HTML Component');
it('extracts content from HTML Component');
});
describe('Error Handling', function () {
it('shows helpful error when component not found');
it('removes component silently in production');
});
describe('Auto-Detection Priority', function () {
it('prioritizes LiveComponent over HTML Component');
});
});
Test File: tests/Unit/Framework/View/Processors/XComponentProcessorTest.php
Integration Testing
// Full Template Processing mit XComponentProcessor
$template = <<<HTML
<html>
<body>
<h1>Dashboard</h1>
<x-datatable id="users" page="1" />
<x-button variant="primary">Add User</x-button>
</body>
</html>
HTML;
$context = RenderContext::create([]);
$rendered = $this->templateProcessor->render($context, $template);
// Assert: LiveComponent rendered with data-component-id
expect($rendered)->toContain('data-component-id="datatable:users"');
// Assert: HTML Component rendered as button
expect($rendered)->toContain('<button class="btn btn-primary">');
Best Practices
1. Naming Conventions
// ✅ Good: Clear, descriptive names
#[LiveComponent(name: 'user-datatable')]
#[LiveComponent(name: 'product-filter')]
#[ComponentName('primary-button')]
// ❌ Bad: Generic or conflicting names
#[LiveComponent(name: 'table')] // Too generic
#[ComponentName('button')] // Conflicts with LiveComponent 'button'
2. Prop Design (LiveComponents)
// ✅ Good: Specific, typed props
#[LiveComponent(name: 'datatable')]
final class DataTableComponent
{
#[LiveProp]
public int $page = 1;
#[LiveProp]
public int $pageSize = 25;
#[LiveProp]
public string $sortBy = 'id';
}
// ❌ Bad: Generic array props (lose type safety)
#[LiveProp]
public array $config = []; // What keys? What types?
3. ID Management
// ✅ Good: Explicit IDs for wichtige Components
<x-datatable id="users-table" page="1" />
<x-datatable id="products-table" page="1" />
// ⚠️ OK: Auto-generated IDs für einmalige Components
<x-counter initialValue="0" />
// → "counter-a3f2d8e1" (auto-generated)
// ❌ Bad: Duplicate IDs
<x-counter id="counter-1" />
<x-counter id="counter-1" /> // ID collision!
4. Content vs Props
// ✅ HTML Component: Content via innerHTML
<x-button variant="primary">Click me</x-button>
// ✅ LiveComponent: Data via Props
<x-datatable data='[{"id":1,"name":"Alice"}]' />
// ❌ Bad: Mixing approaches
<x-datatable>
<!-- Content ignored for LiveComponents! -->
Some data here
</x-datatable>
5. Error Handling
// ✅ Good: Graceful error handling in component
#[LiveComponent(name: 'chart')]
final class ChartComponent
{
#[LiveProp]
public array $data = [];
public function render(): RenderData
{
if (empty($this->data)) {
return new RenderData(
template: 'components/chart-empty',
data: ['message' => 'No data available']
);
}
return new RenderData(
template: 'components/chart',
data: ['chartData' => $this->processData()]
);
}
}
// ❌ Bad: Throwing exceptions ohne Fallback
public function render(): RenderData
{
if (empty($this->data)) {
throw new \Exception('No data!'); // User sieht error!
}
// ...
}
Migration Guide
Von Legacy <component> zu <x-*> Syntax
Vorher (Legacy Syntax):
<component name="button" variant="primary">Click me</component>
Nachher (Unified Syntax):
<x-button variant="primary">Click me</x-button>
Migration Steps:
-
HTML Components: Einfach Syntax ändern
<!-- Old --> <component name="card" title="Profile">Content</component> <!-- New --> <x-card title="Profile">Content</x-card> -
LiveComponents: Prüfen ob #[LiveComponent] Attribute vorhanden
// Ensure component is registered #[LiveComponent(name: 'datatable')] final class DataTableComponent { ... } -
Props: Gleiche Attribute-Namen funktionieren
<!-- Old --> <component name="datatable" page="1" pageSize="25" /> <!-- New --> <x-datatable page="1" pageSize="25" />
Troubleshooting
Problem: Component wird nicht gefunden
Unknown component: <x-my-component>
Available LiveComponents: datatable, counter
Available HTML Components: button, card
Lösung:
- Prüfe Attribute:
#[LiveComponent(name: 'my-component')]oder#[ComponentName('my-component')] - Prüfe Auto-Discovery:
php console.php framework:discover - Cache leeren:
php console.php cache:clear
Problem: Props werden nicht übergeben
<x-counter initialValue="10" />
<!-- Component erhält initialValue nicht -->
Lösung:
- Prüfe
#[LiveProp]Attribute auf Property - Prüfe ComponentMetadata Cache
- Development Mode aktivieren für Validation Errors
Problem: Type Coercion funktioniert nicht
<x-chart data="[1,2,3]" />
<!-- Component erhält string statt array -->
Lösung:
- HTML Component? → Keine Type Coercion, nur LiveComponents!
- JSON valid? → Teste mit
json_decode('[1,2,3]') - Alternativ: Props als separate Attribute
Problem: Component rendered nicht in Production
Ursache: Silent error handling in production mode
Lösung:
- Development Mode aktivieren:
APP_ENV=development - Error logs prüfen
- Component-Validation durchführen
Framework-Compliance
Der XComponentProcessor folgt allen Framework-Patterns:
✅ readonly class - Unveränderlichkeit
✅ final - Keine Vererbung
✅ Composition - Dependencies via Constructor Injection
✅ Interface Implementation - DomProcessor Interface
✅ Value Objects - ComponentId, ComponentData, RenderData
✅ Attribute Discovery - Automatische Component Registration
✅ Error Handling - Graceful degradation (dev/prod modes)
✅ Type Safety - Strict types via declare(strict_types=1)
Zusammenfassung
XComponentProcessor bietet:
- ✅ Unified Syntax -
<x-*>für alle Component-Typen - ✅ Auto-Detection - LiveComponent vs HTML Component
- ✅ Type Coercion - Automatische Typ-Konvertierung (LiveComponents)
- ✅ Prop Validation - Gegen ComponentMetadata (LiveComponents)
- ✅ Error Handling - Development vs Production Modes
- ✅ Performance - Optimized mit Caching
- ✅ Framework-Compliant - Alle Patterns korrekt umgesetzt
Use Cases:
- Interactive Components: DataTables, Forms, Counters, Charts
- Static Components: Buttons, Cards, Badges, Icons
- Unified Developer Experience: Eine Syntax für beide Typen
- Progressive Enhancement: Start mit HTML Component, upgrade zu LiveComponent