Files
michaelschiemer/docs/claude/livecomponents-best-practices.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

11 KiB

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

// Component
public function getRenderData(): ComponentRenderData
{
    return new ComponentRenderData('user-card', [
        'user' => $this->user,
        'permissions' => $this->permissions
    ]);
}
<!-- Template mit komplexer Logik -->
{{#if user.role}}
    {{#if user.role === 'admin'}}
        {{#if user.isActive}}
            <span class="badge">{{permissions.length}} Permissions</span>
        {{/if}}
    {{/if}}
{{/if}}

Probleme:

  • Logik im Template schwer testbar
  • Template-Syntax unterstützt keine Vergleichsoperatoren
  • Keine Type Safety
  • Schwer zu debuggen

Best Practice

// 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' : '');
}
<!-- Template - nur Darstellung -->
{{#if showAdminBadge}}
    <span class="badge {{badgeClass}}">{{badgeText}}</span>
{{/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:

{{#if user.orders.length > 0}}
    <div>User has {{user.orders.length}} orders</div>
{{/if}}

Best Practice:

// 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' : '');
}
<!-- Template -->
{{#if hasOrders}}
    <div>{{orderText}}</div>
{{/if}}

Beispiel 2: Formatierung & Berechnungen

Anti-Pattern:

<div class="price">€ {{price * 1.19}}</div>
<div class="date">{{created.format('d.m.Y')}}</div>

Best Practice:

// 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, ',', '.');
}
<!-- Template -->
<div class="price">{{priceDisplay}}</div>
<div class="date">{{formattedDate}}</div>

Beispiel 3: CSS-Klassen basierend auf Status

Anti-Pattern:

<div class="status {{status === 'active' ? 'status-active' : 'status-inactive'}}">
    {{status}}
</div>

Best Practice:

// 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 => '?'
    };
}
<!-- Template -->
<div class="status {{statusClass}}">
    <span class="icon">{{statusIcon}}</span>
    {{statusText}}
</div>

Beispiel 4: Listen mit berechneten Werten

Anti-Pattern:

{{#each items}}
    <div class="item">
        {{name}} - {{price * quantity}} €
        {{#if inStock && quantity > 0}}
            <span class="available">Available</span>
        {{/if}}
    </div>
{{/each}}

Best Practice:

// 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, ',', '.') . ' €';
}
<!-- Template -->
{{#each items}}
    <div class="item {{itemClass}}">
        {{name}} - {{totalPrice}}
        {{#if showAvailable}}
            <span class="available">Available</span>
        {{/if}}
    </div>
{{/each}}

LiveComponent-Spezifische Patterns

Pattern 1: Event-Daten vorbereiten

// 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

// 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'
    ]);
}
<!-- Template -->
<div class="counter">
    <h2>Count: {{count}}</h2>

    {{#if canDecrement}}
        <button data-live-action="decrement" class="{{decrementClass}}">
            - Decrement
        </button>
    {{/if}}

    {{#if canIncrement}}
        <button data-live-action="increment" class="{{incrementClass}}">
            + Increment
        </button>
    {{/if}}

    {{#if showReset}}
        <button data-live-action="reset" class="btn-secondary">
            Reset
        </button>
    {{/if}}
</div>

Pattern 3: Cache-optimierte Datenvorbereitung

// 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:

{{variableName}}
{{object.property}}
{{array.0.name}}

Conditionals:

{{#if condition}}
    Content when true
{{/if}}

{{#if condition}}
    True content
{{else}}
    False content
{{/if}}

Loops:

{{#each items}}
    {{name}} - {{value}}
{{/each}}

Nested Templates:

{{#if user}}
    {{#each user.orders}}
        <div>Order {{id}}: {{total}}</div>
    {{/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.