fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
461
docs/livecomponents/livecomponents-best-practices.md
Normal file
461
docs/livecomponents/livecomponents-best-practices.md
Normal file
@@ -0,0 +1,461 @@
|
||||
# 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
|
||||
<!-- 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 ✅
|
||||
|
||||
```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
|
||||
<!-- 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:**
|
||||
```html
|
||||
{{#if user.orders.length > 0}}
|
||||
<div>User has {{user.orders.length}} orders</div>
|
||||
{{/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
|
||||
<!-- Template -->
|
||||
{{#if hasOrders}}
|
||||
<div>{{orderText}}</div>
|
||||
{{/if}}
|
||||
```
|
||||
|
||||
### Beispiel 2: Formatierung & Berechnungen
|
||||
|
||||
**❌ Anti-Pattern:**
|
||||
```html
|
||||
<div class="price">€ {{price * 1.19}}</div>
|
||||
<div class="date">{{created.format('d.m.Y')}}</div>
|
||||
```
|
||||
|
||||
**✅ 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
|
||||
<!-- Template -->
|
||||
<div class="price">{{priceDisplay}}</div>
|
||||
<div class="date">{{formattedDate}}</div>
|
||||
```
|
||||
|
||||
### Beispiel 3: CSS-Klassen basierend auf Status
|
||||
|
||||
**❌ Anti-Pattern:**
|
||||
```html
|
||||
<div class="status {{status === 'active' ? 'status-active' : 'status-inactive'}}">
|
||||
{{status}}
|
||||
</div>
|
||||
```
|
||||
|
||||
**✅ 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
|
||||
<!-- Template -->
|
||||
<div class="status {{statusClass}}">
|
||||
<span class="icon">{{statusIcon}}</span>
|
||||
{{statusText}}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Beispiel 4: Listen mit berechneten Werten
|
||||
|
||||
**❌ Anti-Pattern:**
|
||||
```html
|
||||
{{#each items}}
|
||||
<div class="item">
|
||||
{{name}} - {{price * quantity}} €
|
||||
{{#if inStock && quantity > 0}}
|
||||
<span class="available">Available</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/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
|
||||
<!-- 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
|
||||
|
||||
```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
|
||||
<!-- 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
|
||||
|
||||
```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}}
|
||||
<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.**
|
||||
Reference in New Issue
Block a user