Files
michaelschiemer/docs/claude/xcomponent-processor.md
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

765 lines
19 KiB
Markdown

# 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:
1. **LiveComponents** (Interaktiv/Stateful) - Komponenten mit #[LiveComponent] Attribut
2. **HTML Components** (Statisch) - Komponenten mit #[ComponentName] Attribut
**Unified Syntax**: `<x-componentname>`
```html
<!-- 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**:
1. **LiveComponent** prüfen (via `ComponentRegistry::isRegistered()`)
2. **HTML Component** prüfen (via `HtmlComponentRegistry::has()`)
3. **Not Found** → Helpful error message
### Klassen-Struktur
```php
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
```html
<!-- 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
```php
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:
```php
// 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
```php
// 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)**:
```html
<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
```html
<!-- 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
```php
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
```php
// 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
```php
// 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
```html
<x-unknown-component />
```
**Development Mode**:
```html
<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)
```html
<x-counter invalidProp="test" />
```
**Development Mode**:
```html
<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**:
```php
// 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
```php
// 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
```php
// 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**:
1. `<for>` loops werden zuerst verarbeitet
2. `<if>` conditionals danach
3. **`<x-*>` Components** werden resolved
4. Legacy `<component>` tags (fallback)
5. `<include>` templates einbinden
### RenderContext Integration
```php
$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**:
```php
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
```php
// 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
```php
// ✅ 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)
```php
// ✅ 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
```php
// ✅ 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
```php
// ✅ 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
```php
// ✅ 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):
```html
<component name="button" variant="primary">Click me</component>
```
**Nachher** (Unified Syntax):
```html
<x-button variant="primary">Click me</x-button>
```
**Migration Steps**:
1. **HTML Components**: Einfach Syntax ändern
```html
<!-- Old -->
<component name="card" title="Profile">Content</component>
<!-- New -->
<x-card title="Profile">Content</x-card>
```
2. **LiveComponents**: Prüfen ob #[LiveComponent] Attribute vorhanden
```php
// Ensure component is registered
#[LiveComponent(name: 'datatable')]
final class DataTableComponent { ... }
```
3. **Props**: Gleiche Attribute-Namen funktionieren
```html
<!-- 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**:
1. Prüfe Attribute: `#[LiveComponent(name: 'my-component')]` oder `#[ComponentName('my-component')]`
2. Prüfe Auto-Discovery: `php console.php framework:discover`
3. Cache leeren: `php console.php cache:clear`
### Problem: Props werden nicht übergeben
```html
<x-counter initialValue="10" />
<!-- Component erhält initialValue nicht -->
```
**Lösung**:
1. Prüfe `#[LiveProp]` Attribute auf Property
2. Prüfe ComponentMetadata Cache
3. Development Mode aktivieren für Validation Errors
### Problem: Type Coercion funktioniert nicht
```html
<x-chart data="[1,2,3]" />
<!-- Component erhält string statt array -->
```
**Lösung**:
1. **HTML Component?** → Keine Type Coercion, nur LiveComponents!
2. JSON valid? → Teste mit `json_decode('[1,2,3]')`
3. Alternativ: Props als separate Attribute
### Problem: Component rendered nicht in Production
**Ursache**: Silent error handling in production mode
**Lösung**:
1. Development Mode aktivieren: `APP_ENV=development`
2. Error logs prüfen
3. 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