# LiveComponents Best Practices ## Template-System Philosophie **Grundprinzip**: Templates sollten **nur für die Darstellung** zuständig sein, nicht für Logik. ### ✅ Was Templates können - **Variable Substitution**: `{{variableName}}` - **Conditional Rendering**: `{{#if condition}}...{{/if}}` - **Loops**: `{{#each items}}...{{/each}}` - **Nested Properties**: `{{user.name}}`, `{{item.value}}` ### ❌ Was Templates NICHT können - Komplexe Expressions: `{{user.role === 'admin' && user.active}}` - Berechnungen: `{{count * 2}}`, `{{items.length}}` - Method Calls: `{{formatDate(created)}}`, `{{user.getName()}}` - Vergleichsoperatoren in Platzhaltern: `{{price > 100}}` ## Best Practice: Daten im Component vorbereiten ### Anti-Pattern ❌ ```php // Component public function getRenderData(): ComponentRenderData { return new ComponentRenderData('user-card', [ 'user' => $this->user, 'permissions' => $this->permissions ]); } ``` ```html {{#if user.role}} {{#if user.role === 'admin'}} {{#if user.isActive}} {{permissions.length}} Permissions {{/if}} {{/if}} {{/if}} ``` **Probleme:** - ❌ Logik im Template schwer testbar - ❌ Template-Syntax unterstützt keine Vergleichsoperatoren - ❌ Keine Type Safety - ❌ Schwer zu debuggen ### Best Practice ✅ ```php // Component - Daten vollständig vorbereiten public function getRenderData(): ComponentRenderData { return new ComponentRenderData('user-card', [ 'user' => $this->user, 'showAdminBadge' => $this->shouldShowAdminBadge(), 'permissionCount' => $this->getPermissionCount(), 'badgeClass' => $this->getBadgeClass(), 'badgeText' => $this->getBadgeText() ]); } private function shouldShowAdminBadge(): bool { return $this->user->role === 'admin' && $this->user->isActive; } private function getPermissionCount(): int { return count($this->permissions); } private function getBadgeClass(): string { return $this->user->isActive ? 'badge-success' : 'badge-secondary'; } private function getBadgeText(): string { $count = $this->getPermissionCount(); return "{$count} Permission" . ($count !== 1 ? 's' : ''); } ``` ```html {{#if showAdminBadge}} {{badgeText}} {{/if}} ``` **Vorteile:** - ✅ Business-Logik testbar in Component - ✅ Template einfach und lesbar - ✅ Type Safety durch PHP - ✅ Einfach zu debuggen - ✅ Wiederverwendbare Component-Methoden ## Praktische Beispiele ### Beispiel 1: Conditional Rendering **❌ Anti-Pattern:** ```html {{#if user.orders.length > 0}}
User has {{user.orders.length}} orders
{{/if}} ``` **✅ Best Practice:** ```php // Component public function getRenderData(): ComponentRenderData { return new ComponentRenderData('user-summary', [ 'hasOrders' => $this->hasOrders(), 'orderCount' => $this->getOrderCount(), 'orderText' => $this->getOrderText() ]); } private function hasOrders(): bool { return count($this->user->orders) > 0; } private function getOrderCount(): int { return count($this->user->orders); } private function getOrderText(): string { $count = $this->getOrderCount(); return "User has {$count} order" . ($count !== 1 ? 's' : ''); } ``` ```html {{#if hasOrders}}
{{orderText}}
{{/if}} ``` ### Beispiel 2: Formatierung & Berechnungen **❌ Anti-Pattern:** ```html
€ {{price * 1.19}}
{{created.format('d.m.Y')}}
``` **✅ Best Practice:** ```php // Component public function getRenderData(): ComponentRenderData { return new ComponentRenderData('product-card', [ 'priceWithTax' => $this->getPriceWithTax(), 'formattedDate' => $this->getFormattedDate(), 'priceDisplay' => $this->getPriceDisplay() ]); } private function getPriceWithTax(): float { return $this->price * 1.19; } private function getFormattedDate(): string { return $this->created->format('d.m.Y'); } private function getPriceDisplay(): string { return '€ ' . number_format($this->getPriceWithTax(), 2, ',', '.'); } ``` ```html
{{priceDisplay}}
{{formattedDate}}
``` ### Beispiel 3: CSS-Klassen basierend auf Status **❌ Anti-Pattern:** ```html
{{status}}
``` **✅ Best Practice:** ```php // Component public function getRenderData(): ComponentRenderData { return new ComponentRenderData('status-badge', [ 'statusClass' => $this->getStatusClass(), 'statusText' => $this->getStatusText(), 'statusIcon' => $this->getStatusIcon() ]); } private function getStatusClass(): string { return match($this->status) { 'active' => 'status-active', 'pending' => 'status-pending', 'inactive' => 'status-inactive', default => 'status-unknown' }; } private function getStatusText(): string { return ucfirst($this->status); } private function getStatusIcon(): string { return match($this->status) { 'active' => '✓', 'pending' => '⏳', 'inactive' => '✗', default => '?' }; } ``` ```html
{{statusIcon}} {{statusText}}
``` ### Beispiel 4: Listen mit berechneten Werten **❌ Anti-Pattern:** ```html {{#each items}}
{{name}} - {{price * quantity}} € {{#if inStock && quantity > 0}} Available {{/if}}
{{/each}} ``` **✅ Best Practice:** ```php // Component public function getRenderData(): ComponentRenderData { return new ComponentRenderData('order-items', [ 'items' => $this->prepareItems() ]); } private function prepareItems(): array { return array_map(function($item) { return [ 'name' => $item->name, 'totalPrice' => $this->formatPrice($item->price * $item->quantity), 'showAvailable' => $item->inStock && $item->quantity > 0, 'itemClass' => $item->inStock ? 'item-available' : 'item-unavailable' ]; }, $this->items); } private function formatPrice(float $price): string { return number_format($price, 2, ',', '.') . ' €'; } ``` ```html {{#each items}}
{{name}} - {{totalPrice}} {{#if showAvailable}} Available {{/if}}
{{/each}} ``` ## LiveComponent-Spezifische Patterns ### Pattern 1: Event-Daten vorbereiten ```php // Component public function increment(): ComponentUpdate { $oldValue = $this->initialData['count']; $newValue = $oldValue + 1; return new ComponentUpdate( newState: ['count' => $newValue], events: [ new ComponentEvent( name: 'counter:changed', data: [ 'old_value' => $oldValue, 'new_value' => $newValue, 'change' => '+1', 'isEven' => $newValue % 2 === 0, 'isMilestone' => $newValue % 10 === 0 ] ) ] ); } ``` ### Pattern 2: Conditional Actions basierend auf State ```php // Component public function getRenderData(): ComponentRenderData { $count = $this->initialData['count']; return new ComponentRenderData('counter', [ 'count' => $count, 'canDecrement' => $count > 0, 'canIncrement' => $count < 100, 'showReset' => $count !== 0, 'decrementClass' => $count > 0 ? 'btn-danger' : 'btn-disabled', 'incrementClass' => $count < 100 ? 'btn-success' : 'btn-disabled' ]); } ``` ```html

Count: {{count}}

{{#if canDecrement}} {{/if}} {{#if canIncrement}} {{/if}} {{#if showReset}} {{/if}}
``` ### Pattern 3: Cache-optimierte Datenvorbereitung ```php // Component mit Caching final readonly class StatsComponent implements LiveComponentContract, Cacheable { public function getRenderData(): ComponentRenderData { // Teure Berechnung einmal durchführen $stats = $this->computeExpensiveStats(); // Alle Darstellungs-Daten vorbereiten return new ComponentRenderData('stats', [ 'stats' => $stats, 'totalUsers' => number_format($stats['total_users'], 0, ',', '.'), 'activeSessionsText' => $this->getActiveSessionsText($stats['active_sessions']), 'revenueFormatted' => $this->formatRevenue($stats['revenue']), 'showAlert' => $stats['total_users'] > 5000, 'alertClass' => $stats['total_users'] > 5000 ? 'alert-warning' : 'alert-info' ]); } private function getActiveSessionsText(int $count): string { return "{$count} active session" . ($count !== 1 ? 's' : ''); } private function formatRevenue(int $revenue): string { return '€ ' . number_format($revenue, 2, ',', '.'); } } ``` ## Template-System Referenz ### Unterstützte Syntax **Variable Substitution:** ```html {{variableName}} {{object.property}} {{array.0.name}} ``` **Conditionals:** ```html {{#if condition}} Content when true {{/if}} {{#if condition}} True content {{else}} False content {{/if}} ``` **Loops:** ```html {{#each items}} {{name}} - {{value}} {{/each}} ``` **Nested Templates:** ```html {{#if user}} {{#each user.orders}}
Order {{id}}: {{total}}
{{/each}} {{/if}} ``` ## Zusammenfassung **Goldene Regeln:** 1. ✅ **Bereite alle Daten im Component vor** - keine Logik im Template 2. ✅ **Verwende aussagekräftige Property-Namen** - `showAdminBadge` statt `isAdminAndActive` 3. ✅ **Formatiere Daten in PHP** - `priceFormatted` statt Berechnung im Template 4. ✅ **CSS-Klassen vorbereiten** - `statusClass` statt Conditional im Template 5. ✅ **Boolean Flags für Conditionals** - `hasOrders` statt `orders.length > 0` 6. ✅ **Listen vorverarbeiten** - Arrays mit allen Display-Daten vorbereiten 7. ✅ **Teste Component-Logik** - nicht Template-Rendering **Das Template-System ist bewusst einfach gehalten, um saubere Separation of Concerns zu fördern.**