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

- 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:
2025-11-09 14:46:15 +01:00
parent 85c369e846
commit 36ef2a1e2c
1366 changed files with 104925 additions and 28719 deletions

76
docs/claude/README.md Normal file
View File

@@ -0,0 +1,76 @@
# Claude Documentation
Dieses Verzeichnis enthält AI-spezifische Dokumentation für Claude Code und andere AI-Agenten, die mit dem Framework arbeiten.
## Zweck
Die Dokumentation in diesem Verzeichnis ist speziell für AI-Agenten (wie Claude Code, Cursor AI, etc.) konzipiert und enthält:
- Framework-spezifische Kontexte und Personas
- MCP (Model Context Protocol) Integration
- Development Commands für AI-Agenten
- Coding Guidelines für AI-generierten Code
- Code-Generierungs-Beispiele
## Allgemeine Framework-Dokumentation
Für allgemeine Framework-Dokumentation, die sowohl für Entwickler als auch AI-Agenten relevant ist, siehe:
- [Framework Dokumentation](../README.md) - Hauptdokumentation
- [Features](../features/) - Feature-spezifische Dokumentation
- [Guides](../guides/) - Entwickleranleitungen
- [LiveComponents](../livecomponents/) - LiveComponents Dokumentation
## Wichtige Dokumentationen
### MCP Integration
- [MCP Integration](mcp-integration.md) - Model Context Protocol Server und Tools
### Framework Personas
- [Framework Personas](framework-personas.md) - AI Personas für Framework-Entwicklung
### Development Commands
- [Development Commands](development-commands.md) - Claude Code Commands und Workflows
### Coding Guidelines
- [Guidelines](guidelines.md) - AI Coding Guidelines und Best Practices
- [Architecture](architecture.md) - Framework-Architektur für AI-Agenten
- [Naming Conventions](naming-conventions.md) - Namenskonventionen
### Code Generation Examples
- [Examples](examples/) - Code-Generierungs-Beispiele
## Weitere AI-spezifische Dokumentationen
- [Performance Profiling](performance-profiling.md) - Performance-Analyse für AI-Agenten
- [Structured Logging](structured-logging.md) - Logging-Patterns
- [PHP 8.5 Integration](php85-framework-integration.md) - PHP 8.5 Features
- [PostgreSQL Features](postgresql-features.md) - PostgreSQL-spezifische Features
- [SSE System](sse-system.md) - Server-Sent Events
- [SSE Integration Guide](sse-integration-guide.md) - SSE Integration
- [Sockets Module](sockets-module.md) - Socket-Kommunikation
- [Magic Links System](magiclinks-system.md) - Magic Links Implementation
- [View Caching System](view-caching-system.md) - View-Caching
- [View Refactoring Plan](view-refactoring-plan.md) - View-Refactoring
- [X-Component Syntax](x-component-syntax.md) - X-Component Template Syntax
- [XComponent Processor](xcomponent-processor.md) - XComponent Processing
- [Animation System](animationsystem.md) - Animation Framework
- [Chips & Cookies](chips-cookies.md) - Cookie Management
- [Curl OOP API](curl-oop-api.md) - HTTP Client API
- [Deployment Architecture](deployment-architecture.md) - Deployment-Architektur
- [ML Framework Architecture](ml-framework-architecture.md) - Machine Learning Framework
- [POSIX System](posix-system.md) - POSIX-Integration
- [Typed String System](typed-string-system.md) - Typed String Value Objects
- [Framework Refactoring Recommendations](framework-refactoring-recommendations.md) - Refactoring-Empfehlungen
## Verwendung
Diese Dokumentation wird automatisch von AI-Agenten verwendet, die mit dem Framework arbeiten. Sie sollte nicht manuell bearbeitet werden, es sei denn, es handelt sich um AI-spezifische Kontexte oder Beispiele.
## Migration
Viele Dokumentationen wurden von diesem Verzeichnis in die allgemeine Dokumentationsstruktur migriert:
- Feature-Dokumentationen → `docs/features/`
- Guides → `docs/guides/`
- LiveComponents → `docs/livecomponents/`
Siehe [Dokumentationsanalyse](../DOCUMENTATION-ANALYSIS.md) für Details zur Reorganisation.

View File

@@ -0,0 +1,585 @@
# Animationssystem für Console-Modul
## Übersicht
Das Animationssystem bietet eine umfassende Lösung für Animationen im Console-Modul. Es unterstützt sowohl UI-Elemente in der TUI als auch Text-Animationen im Console-Output. Das System ist modular aufgebaut, erweiterbar und vollständig in den EventLoop integriert.
## Features
- **Mehrere Animationstypen**: Fade-In/Out, Slide, Typewriter, Marquee, Pulse
- **Keyframe-basierte Animationen**: Komplexe Animationen mit Easing-Functions
- **Composite Animationen**: Kombination mehrerer Animationen (parallel oder sequenziell)
- **EventLoop Integration**: Automatische Updates im Render-Loop
- **TUI & Console-Output**: Unterstützung für beide Anwendungsfälle
- **Factory & Builder Pattern**: Einfache Erstellung von Animationen
## Architektur
### Core-Komponenten
#### Animation Interface
Basis-Interface für alle Animationen:
```php
interface Animation
{
public function start(): void;
public function stop(): void;
public function pause(): void;
public function resume(): void;
public function update(float $deltaTime): bool;
public function isActive(): bool;
public function getProgress(): float;
public function getDuration(): float;
public function getDelay(): float;
public function isLooping(): bool;
public function onStart(callable $callback): self;
public function onComplete(callable $callback): self;
public function onPause(callable $callback): self;
public function onResume(callable $callback): self;
}
```
#### AnimationManager
Verwaltet mehrere Animationen gleichzeitig und aktualisiert sie automatisch:
```php
$animationManager = new AnimationManager();
// Animation hinzufügen
$animationManager->add($animation);
// Animationen aktualisieren (wird automatisch vom EventLoop aufgerufen)
$animationManager->update($deltaTime);
// Animation entfernen
$animationManager->remove($animation);
// Alle Animationen löschen
$animationManager->clear();
```
#### Easing Functions
Unterstützte Easing-Functions:
- `LINEAR` - Lineare Interpolation
- `EASE_IN` - Langsam starten
- `EASE_OUT` - Langsam enden
- `EASE_IN_OUT` - Langsam starten und enden
- `EASE_IN_QUAD` - Quadratische Beschleunigung
- `EASE_OUT_QUAD` - Quadratische Verzögerung
- `EASE_IN_OUT_QUAD` - Quadratische Beschleunigung und Verzögerung
- `BOUNCE` - Bounce-Effekt
- `ELASTIC` - Elastischer Effekt
## Animation-Typen
### FadeAnimation
Fade-In/Out Effekte für Text oder UI-Elemente:
```php
use App\Framework\Console\Animation\AnimationFactory;
use App\Framework\Console\Animation\EasingFunction;
// Fade-In
$fadeIn = AnimationFactory::fadeIn(1.0, EasingFunction::EASE_IN);
// Fade-Out
$fadeOut = AnimationFactory::fadeOut(1.0, EasingFunction::EASE_OUT);
// Custom Fade
$fade = AnimationFactory::fade(
duration: 2.0,
startOpacity: 0.0,
endOpacity: 1.0,
easing: EasingFunction::EASE_IN_OUT
);
```
### SlideAnimation
Slide-Effekte in verschiedene Richtungen:
```php
use App\Framework\Console\Animation\Types\SlideDirection;
// Von links
$slideFromLeft = AnimationFactory::slideInFromLeft(
distance: 20,
duration: 1.0
);
// Von rechts
$slideFromRight = AnimationFactory::slideInFromRight(
distance: 20,
duration: 1.0
);
// Custom Slide
$slide = AnimationFactory::slide(
direction: SlideDirection::UP,
distance: 10,
duration: 0.5,
easing: EasingFunction::EASE_OUT
);
```
### TypewriterAnimation
Typewriter-Effekt für Text:
```php
$typewriter = AnimationFactory::typewriter(
text: 'Hello, this is a typewriter animation!',
charactersPerSecond: 10.0
);
// Schnell
$fast = AnimationFactory::typewriter('Fast text', 20.0);
// Langsam
$slow = AnimationFactory::typewriter('Slow text', 5.0);
```
### MarqueeAnimation
Scrolling Text (Marquee):
```php
$marquee = AnimationFactory::marquee(
text: 'This is a scrolling marquee text',
width: 80,
speed: 1.0,
loop: true
);
// Schnell
$fastMarquee = AnimationFactory::marquee('Fast scrolling', 50, 5.0);
// Langsam
$slowMarquee = AnimationFactory::marquee('Slow scrolling', 50, 0.5);
```
### PulseAnimation
Pulsing-Effekte für Hervorhebungen:
```php
$pulse = AnimationFactory::pulse(
duration: 1.0,
scaleStart: 1.0,
scaleEnd: 1.2,
pulseSpeed: 2.0
);
// Sanft
$gentle = PulseAnimation::gentle(2.0);
// Stark
$strong = PulseAnimation::strong(1.0);
```
### KeyframeAnimation
Keyframe-basierte Animationen mit komplexen Interpolationen:
```php
use App\Framework\Console\Animation\AnimationFrame;
$keyframes = [
new AnimationFrame(0.0, 0, EasingFunction::EASE_IN),
new AnimationFrame(0.5, 100, EasingFunction::EASE_IN_OUT),
new AnimationFrame(1.0, 200, EasingFunction::EASE_OUT),
];
$keyframeAnimation = AnimationFactory::keyframe(
keyframes: $keyframes,
duration: 2.0,
loop: false
);
```
### CompositeAnimation
Kombination mehrerer Animationen:
```php
use App\Framework\Console\Animation\CompositeAnimation;
use App\Framework\Console\Animation\SequenceType;
$fadeIn = AnimationFactory::fadeIn(0.5);
$slide = AnimationFactory::slideInFromLeft(20, 0.5);
// Parallel (gleichzeitig)
$parallel = new CompositeAnimation(
animations: [$fadeIn, $slide],
sequenceType: SequenceType::PARALLEL,
loop: false
);
// Sequenziell (nacheinander)
$sequential = new CompositeAnimation(
animations: [$fadeIn, $slide],
sequenceType: SequenceType::SEQUENTIAL,
loop: false
);
```
### SpinnerAnimation
Animation-basierter Spinner:
```php
use App\Framework\Console\Animation\Types\SpinnerAnimation;
use App\Framework\Console\SpinnerStyle;
$spinner = AnimationFactory::spinner(
frames: SpinnerStyle::DOTS,
message: 'Loading...',
frameInterval: 0.1
);
// Oder direkt
$spinner = SpinnerAnimation::fromStyle(
style: SpinnerStyle::BARS,
message: 'Processing...',
frameInterval: 0.1
);
```
## Verwendung in Console-Output
### Einfache Text-Animationen
```php
use App\Framework\Console\ConsoleOutput;
$output = new ConsoleOutput();
// Fade-In
$output->animateFadeIn('Hello World!', 1.0);
// Fade-Out
$output->animateFadeOut('Goodbye!', 1.0);
// Typewriter
$output->animateTypewriter('This appears character by character', 10.0);
// Marquee
$output->animateMarquee('Scrolling text', 80, 1.0);
// Custom Animation
$customAnimation = AnimationFactory::fadeIn(2.0);
$output->animateText('Custom animated text', $customAnimation);
// Animationen aktualisieren
$output->updateAnimations(0.016); // ~60 FPS
```
### Animation Manager Zugriff
```php
$animationManager = $output->getAnimationManager();
// Animationen direkt hinzufügen
$animationManager->add($animation);
// Animationen aktualisieren
$animationManager->update($deltaTime);
```
## Verwendung in TUI
### TuiAnimationRenderer
```php
use App\Framework\Console\Animation\TuiAnimationRenderer;
use App\Framework\Console\Animation\AnimationFactory;
use App\Framework\Console\Animation\Types\SlideDirection;
$animationRenderer = $tuiRenderer->getAnimationRenderer();
if ($animationRenderer !== null) {
// Element animieren
$animationRenderer->fadeInElement('menu-item-1', 0.5);
$animationRenderer->slideInElement(
'button-1',
SlideDirection::LEFT,
20,
0.5
);
$animationRenderer->pulseElement('highlight-1', 1.0);
// Custom Animation
$customAnimation = AnimationFactory::fadeIn(1.0);
$animationRenderer->animateElement('element-id', $customAnimation);
// Animation stoppen
$animationRenderer->stopElement('element-id');
// Animationswerte abrufen
$opacity = $animationRenderer->getElementOpacity('element-id');
$position = $animationRenderer->getElementPosition('element-id');
$scale = $animationRenderer->getElementScale('element-id');
}
```
### Integration in TuiRenderer
Der `TuiRenderer` aktualisiert Animationen automatisch im Render-Loop:
```php
// In TuiRenderer::render()
if ($this->animationRenderer !== null) {
$this->animationRenderer->update(0.016); // ~60 FPS
}
```
## Factory & Builder Pattern
### AnimationFactory
Einfache Erstellung von Animationen:
```php
use App\Framework\Console\Animation\AnimationFactory;
$fadeIn = AnimationFactory::fadeIn(1.0);
$fadeOut = AnimationFactory::fadeOut(1.0);
$slide = AnimationFactory::slideInFromLeft(20, 1.0);
$typewriter = AnimationFactory::typewriter('Text', 10.0);
$marquee = AnimationFactory::marquee('Text', 80, 1.0);
$pulse = AnimationFactory::pulse(1.0);
$spinner = AnimationFactory::spinner(SpinnerStyle::DOTS, 'Loading...');
$keyframe = AnimationFactory::keyframe($keyframes, 2.0);
```
### AnimationBuilder
Fluent Builder für komplexe Konfigurationen:
```php
use App\Framework\Console\Animation\AnimationBuilder;
$animation = AnimationBuilder::fade(0.0, 1.0)
->duration(2.0)
->delay(0.5)
->loop(false)
->easing(EasingFunction::EASE_IN_OUT)
->onStart(function ($anim) {
echo "Animation started\n";
})
->onComplete(function ($anim) {
echo "Animation completed\n";
})
->onPause(function ($anim) {
echo "Animation paused\n";
})
->onResume(function ($anim) {
echo "Animation resumed\n";
})
->build();
```
## EventLoop Integration
Der `EventLoop` kann automatisch Animationen aktualisieren:
```php
use App\Framework\Console\Components\EventLoop\EventLoop;
use App\Framework\Console\Components\EventLoop\EventLoopConfig;
use App\Framework\Console\Animation\AnimationManager;
$animationManager = new AnimationManager();
$eventBuffer = new EventBuffer();
$config = new EventLoopConfig();
// EventLoop mit AnimationManager erstellen
$eventLoop = new EventLoop($eventBuffer, $config, $animationManager);
// Animationen werden automatisch aktualisiert
// Oder manuell:
$eventLoop->updateAnimations(0.016);
```
## Callbacks
Animationen unterstützen Callbacks für verschiedene Events:
```php
$animation = AnimationFactory::fadeIn(1.0)
->onStart(function (Animation $anim) {
echo "Animation started\n";
})
->onComplete(function (Animation $anim) {
echo "Animation completed\n";
})
->onPause(function (Animation $anim) {
echo "Animation paused\n";
})
->onResume(function (Animation $anim) {
echo "Animation resumed\n";
});
```
## Best Practices
### Performance
1. **Animation Manager wiederverwenden**: Erstellen Sie einen `AnimationManager` pro Anwendung und verwenden Sie ihn für alle Animationen.
2. **Animationen entfernen**: Entfernen Sie abgeschlossene Animationen, um Speicher zu sparen:
```php
$animation->onComplete(function ($anim) use ($manager) {
$manager->remove($anim);
});
```
3. **Update-Frequenz**: Aktualisieren Sie Animationen mit ~60 FPS (0.016 Sekunden):
```php
$animationManager->update(0.016);
```
### Code-Organisation
1. **Factory verwenden**: Verwenden Sie `AnimationFactory` für einfache Animationen statt direkte Instanziierung.
2. **Builder für komplexe Animationen**: Verwenden Sie `AnimationBuilder` für komplexe Konfigurationen.
3. **Keyframes für komplexe Animationen**: Verwenden Sie `KeyframeAnimation` für komplexe Animationen mit mehreren Zuständen.
### Fehlerbehandlung
```php
try {
$animation = AnimationFactory::fadeIn(1.0);
$animationManager->add($animation);
$animation->start();
} catch (\InvalidArgumentException $e) {
// Handle invalid animation parameters
error_log("Animation error: " . $e->getMessage());
}
```
## Beispiel: Demo Command
Ein vollständiges Beispiel finden Sie in:
`src/Framework/Console/Examples/AnimationDemoCommand.php`
Ausführen mit:
```bash
php console.php demo:animation
```
## Erweiterung
### Neue Animation-Typen hinzufügen
1. Erstellen Sie eine neue Klasse in `src/Framework/Console/Animation/Types/`
2. Implementieren Sie das `Animation` Interface oder erweitern Sie `BaseAnimation`
3. Implementieren Sie `updateAnimation(float $progress)` und `getCurrentValue()`
4. Fügen Sie Factory-Methoden zu `AnimationFactory` hinzu
Beispiel:
```php
final class CustomAnimation extends BaseAnimation
{
protected function updateAnimation(float $progress): void
{
// Custom animation logic
}
public function getCurrentValue(): mixed
{
// Return current animated value
}
}
```
## API-Referenz
### Animation Interface
- `start(): void` - Startet die Animation
- `stop(): void` - Stoppt die Animation
- `pause(): void` - Pausiert die Animation
- `resume(): void` - Setzt die Animation fort
- `update(float $deltaTime): bool` - Aktualisiert die Animation
- `isActive(): bool` - Prüft ob Animation aktiv ist
- `getProgress(): float` - Gibt Fortschritt zurück (0.0-1.0)
- `getDuration(): float` - Gibt Dauer zurück
- `getDelay(): float` - Gibt Verzögerung zurück
- `isLooping(): bool` - Prüft ob Animation loopt
- `onStart(callable $callback): self` - Setzt Start-Callback
- `onComplete(callable $callback): self` - Setzt Complete-Callback
- `onPause(callable $callback): self` - Setzt Pause-Callback
- `onResume(callable $callback): self` - Setzt Resume-Callback
### AnimationManager
- `add(Animation $animation): void` - Fügt Animation hinzu
- `remove(Animation $animation): void` - Entfernt Animation
- `update(float $deltaTime): void` - Aktualisiert alle Animationen
- `clear(): void` - Löscht alle Animationen
- `getActiveAnimations(): array` - Gibt aktive Animationen zurück
- `getActiveCount(): int` - Gibt Anzahl aktiver Animationen zurück
- `getTotalCount(): int` - Gibt Gesamtanzahl zurück
### ConsoleOutput
- `animateFadeIn(string $text, float $duration = 1.0): void`
- `animateFadeOut(string $text, float $duration = 1.0): void`
- `animateTypewriter(string $text, float $charactersPerSecond = 10.0): void`
- `animateMarquee(string $text, int $width = 80, float $speed = 1.0): void`
- `animateText(string $text, Animation $animation): void`
- `updateAnimations(float $deltaTime = 0.016): void`
- `getAnimationManager(): AnimationManager`
### TuiAnimationRenderer
- `animateElement(string $elementId, Animation $animation): void`
- `fadeInElement(string $elementId, float $duration = 0.5): void`
- `fadeOutElement(string $elementId, float $duration = 0.5): void`
- `pulseElement(string $elementId, float $duration = 1.0): void`
- `slideInElement(string $elementId, SlideDirection $direction, int $distance, float $duration = 0.5): void`
- `stopElement(string $elementId): void`
- `getElementOpacity(string $elementId): float`
- `getElementPosition(string $elementId): int`
- `getElementScale(string $elementId): float`
- `hasAnimation(string $elementId): bool`
- `update(float $deltaTime): void`
- `clear(): void`
## Troubleshooting
### Animationen laufen nicht
1. **AnimationManager prüfen**: Stellen Sie sicher, dass ein `AnimationManager` vorhanden ist.
2. **Update-Aufrufe**: Stellen Sie sicher, dass `update()` regelmäßig aufgerufen wird.
3. **Animation starten**: Rufen Sie `start()` auf der Animation auf.
### Performance-Probleme
1. **Zu viele Animationen**: Reduzieren Sie die Anzahl gleichzeitiger Animationen.
2. **Update-Frequenz**: Erhöhen Sie das Update-Intervall (z.B. 0.033 für 30 FPS).
3. **Abgeschlossene Animationen**: Entfernen Sie abgeschlossene Animationen.
### Animationen werden nicht angezeigt
1. **TUI-Integration**: Für TUI-Animationen muss `TuiAnimationRenderer` verwendet werden.
2. **Console-Output**: Für Text-Animationen muss `ConsoleOutput::animateText()` verwendet werden.
3. **Update-Loop**: Stellen Sie sicher, dass der Update-Loop läuft.
## Weitere Ressourcen
- **Plan-Dokument**: `console-modul-refactoring.plan.md`
- **Demo Command**: `src/Framework/Console/Examples/AnimationDemoCommand.php`
- **Framework Guidelines**: `docs/claude/guidelines.md`

File diff suppressed because it is too large Load Diff

View File

@@ -1,297 +0,0 @@
# Configuration Best Practices
Leitlinien für effektives Configuration Management im Custom PHP Framework.
## Problem: .env Bloat
**Symptom**: Zunehmende Anzahl von Environment-Variablen in `.env.example`
**Negative Auswirkungen**:
- Schwer zu überschauen, welche Variablen wirklich wichtig sind
- Viele Parameter, die Entwickler nie ändern werden
- Unklare Defaults für optionale Parameter
- Erhöhter Wartungsaufwand
## Lösung: Sensible Defaults + Minimal Configuration
### Prinzip 1: "Convention over Configuration"
**Regel**: Nur Werte, die Entwickler WIRKLICH ändern müssen oder wollen, gehören in `.env`
**Gute .env Kandidaten**:
- ✅ Credentials (DB_PASSWORD, API_KEYS)
- ✅ Umgebungs-spezifische Werte (APP_ENV, APP_URL)
- ✅ Infrastruktur-Konfiguration (DB_HOST, REDIS_HOST)
- ✅ Feature Toggles für Production (APP_DEBUG)
**Schlechte .env Kandidaten**:
- ❌ Performance Tuning Parameter (CACHE_TTL=60)
- ❌ Interne Framework-Defaults (CACHE_SIZE=100)
- ❌ Rarely Changed Feature Flags (FEATURE_ENABLED=true)
- ❌ Debug-only Flags (VERBOSE_LOGGING=false)
### Prinzip 2: Sensible Defaults im Code
**Vor der Refaktorierung** (4 .env Variablen):
```env
FILESYSTEM_VALIDATOR_CACHE=true # Rarely disabled
FILESYSTEM_VALIDATOR_CACHE_TTL=60 # Never changed
FILESYSTEM_VALIDATOR_CACHE_SIZE=100 # Never changed
FILESYSTEM_STORAGE_CACHE=true # Rarely disabled
```
**Nach der Refaktorierung** (1 .env Variable):
```env
# Filesystem Performance (caching enabled by default)
# Set to true only for debugging performance issues
# FILESYSTEM_DISABLE_CACHE=false
```
**Im Code** (FilesystemInitializer.php):
```php
// Sensible defaults hardcoded
$disableCache = filter_var($_ENV['FILESYSTEM_DISABLE_CACHE'] ?? 'false', FILTER_VALIDATE_BOOLEAN);
if ($disableCache) {
return $validator;
}
return new CachedFileValidator(
validator: $validator,
cacheTtl: 60, // Sensible default: 1 minute
maxCacheSize: 100 // Sensible default: 100 entries
);
```
**Vorteile**:
- ✅ 75% weniger .env Variablen (4 → 1)
- ✅ Klarere Intention: "disable only for debugging"
- ✅ Production-optimized by default
- ✅ Einfacher für neue Entwickler
### Prinzip 3: Opt-Out statt Opt-In für Performance
**Schlechtes Pattern** (Opt-In):
```env
FEATURE_CACHE=false # Muss explizit aktiviert werden
```
→ Entwickler vergessen es zu aktivieren → Performance-Probleme
**Gutes Pattern** (Opt-Out):
```env
# FEATURE_DISABLE_CACHE=false # Caching standardmäßig aktiv
```
→ Production-optimiert by default → Explizit deaktivieren nur für Debugging
### Prinzip 4: Gruppierung und Kommentare
**Schlechtes Beispiel**:
```env
CACHE_ENABLED=true
CACHE_TTL=60
CACHE_SIZE=100
DB_CACHE=true
FILE_CACHE=true
API_CACHE=true
```
**Gutes Beispiel**:
```env
# Cache System (optimized by default)
# Disable only for debugging: DISABLE_ALL_CACHES=true
# DISABLE_ALL_CACHES=false
# Database-specific cache tuning (advanced)
# DB_CACHE_TTL=3600
```
## Refactoring-Strategie
### Schritt 1: Identifiziere "Never Changed" Parameter
Prüfe in der Codebase:
```bash
# Suche nach $_ENV usage
grep -r "ENV\['" src/
# Identifiziere Parameter, die nie geändert werden
# Kandidaten für Hardcoding
```
### Schritt 2: Extrahiere Sensible Defaults
**Vor**:
```php
$ttl = (int) ($_ENV['CACHE_TTL'] ?? 60);
$size = (int) ($_ENV['CACHE_SIZE'] ?? 100);
```
**Nach**:
```php
// Sensible defaults based on production experience
const DEFAULT_CACHE_TTL = 60; // 1 minute
const DEFAULT_CACHE_SIZE = 100; // 100 entries
$ttl = self::DEFAULT_CACHE_TTL;
$size = self::DEFAULT_CACHE_SIZE;
```
### Schritt 3: Konsolidiere Related Flags
**Vor** (3 separate flags):
```env
FILESYSTEM_VALIDATOR_CACHE=true
FILESYSTEM_STORAGE_CACHE=true
FILESYSTEM_CACHE_CLEARSTATCACHE=true
```
**Nach** (1 master flag):
```env
# FILESYSTEM_DISABLE_CACHE=false
```
**Im Code**:
```php
$disableCache = filter_var($_ENV['FILESYSTEM_DISABLE_CACHE'] ?? 'false', FILTER_VALIDATE_BOOLEAN);
// Affects all filesystem caching
$useValidatorCache = !$disableCache;
$useStorageCache = !$disableCache;
$optimizeClearstatcache = !$disableCache;
```
### Schritt 4: Dokumentiere Advanced Tuning
Für Experten, die wirklich tunen wollen:
```php
// Advanced tuning (usually not needed)
$ttl = (int) ($_ENV['FILESYSTEM_VALIDATOR_CACHE_TTL'] ?? 60);
$size = (int) ($_ENV['FILESYSTEM_VALIDATOR_CACHE_SIZE'] ?? 100);
```
**In Dokumentation** (nicht in .env.example):
```markdown
## Advanced Configuration
For expert performance tuning, these environment variables can be used:
- `FILESYSTEM_VALIDATOR_CACHE_TTL`: Cache TTL in seconds (default: 60)
- `FILESYSTEM_VALIDATOR_CACHE_SIZE`: Max cache entries (default: 100)
⚠️ **Warning**: Only change these if you have measured performance issues.
```
## Entscheidungsbaum
```
Braucht diese Variable in .env?
├─ Ist es eine Credential? → ✅ JA
├─ Ist es umgebungs-spezifisch (dev/prod)? → ✅ JA
├─ Muss es für Production geändert werden? → ✅ JA
├─ Wird es von Entwicklern oft geändert? → ✅ JA
└─ Ansonsten:
├─ Ist es ein Performance Tuning Parameter? → ❌ NEIN (Sensible Default im Code)
├─ Ist es ein internes Detail? → ❌ NEIN (Hardcode im Code)
├─ Ist es ein Debug Flag? → ⚠️ MAYBE (Opt-Out Pattern erwägen)
└─ Ist es ein Feature Toggle? → ⚠️ MAYBE (Convention over Configuration)
```
## .env Organisation
### Empfohlene Struktur
```env
# ============================================================
# CORE APPLICATION
# ============================================================
APP_ENV=development
APP_DEBUG=true
APP_URL=https://localhost
# ============================================================
# DATABASE
# ============================================================
DB_HOST=db
DB_PORT=5432
DB_DATABASE=michaelschiemer
DB_USERNAME=postgres
DB_PASSWORD=your_secure_password
# ============================================================
# EXTERNAL SERVICES
# ============================================================
SPOTIFY_CLIENT_ID=your_client_id
SPOTIFY_CLIENT_SECRET=your_client_secret
# ============================================================
# PERFORMANCE & DEBUGGING (optimized by default)
# ============================================================
# Uncomment only for debugging:
# FILESYSTEM_DISABLE_CACHE=false
# DISABLE_QUERY_LOG=false
# VERBOSE_ERROR_PAGES=false
```
## Beispiele aus dem Framework
### ✅ Gutes Beispiel: Filesystem Performance
**Vorher** (4 Variablen):
```env
FILESYSTEM_VALIDATOR_CACHE=true
FILESYSTEM_VALIDATOR_CACHE_TTL=60
FILESYSTEM_VALIDATOR_CACHE_SIZE=100
FILESYSTEM_STORAGE_CACHE=true
```
**Nachher** (1 Variable):
```env
# FILESYSTEM_DISABLE_CACHE=false
```
**Einsparung**: 75% weniger Variablen, gleiche Funktionalität
### ❌ Anti-Pattern: Zu granulare Konfiguration
```env
# DON'T DO THIS
ENABLE_USER_CACHE=true
USER_CACHE_TTL=60
USER_CACHE_SIZE=100
ENABLE_POST_CACHE=true
POST_CACHE_TTL=120
POST_CACHE_SIZE=200
ENABLE_COMMENT_CACHE=true
COMMENT_CACHE_TTL=30
COMMENT_CACHE_SIZE=50
```
**Besser**:
```env
# Cache System (enabled by default with sensible defaults)
# DISABLE_ALL_CACHES=false
```
**Im Code** (wenn wirklich Tuning nötig):
```php
// Expert tuning available via environment, but not advertised
$userCacheTtl = (int) ($_ENV['USER_CACHE_TTL'] ?? 60);
$postCacheTtl = (int) ($_ENV['POST_CACHE_TTL'] ?? 120);
```
## Zusammenfassung
**Goldene Regel**: Jede .env Variable muss rechtfertigen, warum sie NICHT ein Sensible Default im Code sein kann.
**Checklist für neue .env Variablen**:
- [ ] Ist es eine Credential oder Secret? → .env
- [ ] Ist es umgebungs-spezifisch? → .env
- [ ] Ändern Entwickler es regelmäßig? → .env
- [ ] Ist es ein Performance Parameter? → Sensible Default im Code
- [ ] Ist es ein Debug Flag? → Opt-Out Pattern (commented out in .env.example)
- [ ] Ist es ein internes Detail? → Hardcode im Code, nicht konfigurierbar
**Ergebnis**: Übersichtliche .env.example, production-optimized by default, trotzdem flexibel für Experten.

File diff suppressed because it is too large Load Diff

View File

@@ -1,249 +0,0 @@
# Error Handling & Debugging
This guide covers error handling patterns and debugging strategies in the framework.
## Exception Handling
All custom exceptions in the framework must extend `FrameworkException` to ensure consistent error handling, logging, and recovery mechanisms.
### The FrameworkException System
The framework provides a sophisticated exception system with:
- **ExceptionContext**: Rich context information for debugging
- **ErrorCode**: Categorized error codes with recovery hints
- **RetryAfter**: Support for recoverable operations
- **Fluent Interface**: Easy context building
### Creating Custom Exceptions
```php
namespace App\Domain\User\Exceptions;
use App\Framework\Exception\FrameworkException;
use App\Framework\Exception\Core\DatabaseErrorCode;
use App\Framework\Exception\ExceptionContext;
final class UserNotFoundException extends FrameworkException
{
public static function byId(UserId $id): self
{
return self::create(
DatabaseErrorCode::ENTITY_NOT_FOUND,
"User with ID '{$id->toString()}' not found"
)->withData([
'user_id' => $id->toString(),
'search_type' => 'by_id'
]);
}
public static function byEmail(Email $email): self
{
$context = ExceptionContext::forOperation('user.lookup', 'UserRepository')
->withData(['email' => $email->getMasked()]);
return self::fromContext(
"User with email not found",
$context,
DatabaseErrorCode::ENTITY_NOT_FOUND
);
}
}
```
### Using ErrorCode Enums
The framework provides category-specific error code enums for better organization and type safety:
```php
use App\Framework\Exception\Core\DatabaseErrorCode;
use App\Framework\Exception\Core\AuthErrorCode;
use App\Framework\Exception\Core\HttpErrorCode;
use App\Framework\Exception\Core\SecurityErrorCode;
use App\Framework\Exception\Core\ValidationErrorCode;
// Database errors
DatabaseErrorCode::CONNECTION_FAILED
DatabaseErrorCode::QUERY_FAILED
DatabaseErrorCode::TRANSACTION_FAILED
DatabaseErrorCode::CONSTRAINT_VIOLATION
// Authentication errors
AuthErrorCode::CREDENTIALS_INVALID
AuthErrorCode::TOKEN_EXPIRED
AuthErrorCode::SESSION_EXPIRED
AuthErrorCode::ACCOUNT_LOCKED
// HTTP errors
HttpErrorCode::BAD_REQUEST
HttpErrorCode::NOT_FOUND
HttpErrorCode::METHOD_NOT_ALLOWED
HttpErrorCode::RATE_LIMIT_EXCEEDED
// Security errors
SecurityErrorCode::CSRF_TOKEN_INVALID
SecurityErrorCode::SQL_INJECTION_DETECTED
SecurityErrorCode::XSS_DETECTED
SecurityErrorCode::PATH_TRAVERSAL_DETECTED
// Validation errors
ValidationErrorCode::INVALID_INPUT
ValidationErrorCode::REQUIRED_FIELD_MISSING
ValidationErrorCode::BUSINESS_RULE_VIOLATION
ValidationErrorCode::INVALID_FORMAT
// Using error codes in exceptions:
throw FrameworkException::create(
DatabaseErrorCode::QUERY_FAILED,
"Failed to execute user query"
)->withContext(
ExceptionContext::forOperation('user.find', 'UserRepository')
->withData(['query' => 'SELECT * FROM users WHERE id = ?'])
->withDebug(['bind_params' => [$userId]])
);
```
### Exception Context Building
```php
// Method 1: Using factory methods
$exception = FrameworkException::forOperation(
'payment.process',
'PaymentService',
'Payment processing failed',
HttpErrorCode::BAD_GATEWAY
)->withData([
'amount' => $amount->toArray(),
'gateway' => 'stripe',
'customer_id' => $customerId
])->withMetadata([
'attempt' => 1,
'idempotency_key' => $idempotencyKey
]);
// Method 2: Building context separately
$context = ExceptionContext::empty()
->withOperation('order.validate', 'OrderService')
->withData([
'order_id' => $orderId,
'total' => $total->toDecimal()
])
->withDebug([
'validation_rules' => ['min_amount', 'max_items'],
'failed_rule' => 'min_amount'
]);
throw FrameworkException::fromContext(
'Order validation failed',
$context,
ValidationErrorCode::BUSINESS_RULE_VIOLATION
);
```
### Recoverable Exceptions
```php
// Creating recoverable exceptions with retry hints
final class RateLimitException extends FrameworkException
{
public static function exceeded(int $retryAfter): self
{
return self::create(
HttpErrorCode::RATE_LIMIT_EXCEEDED,
'API rate limit exceeded'
)->withRetryAfter($retryAfter)
->withData(['retry_after_seconds' => $retryAfter]);
}
}
// Using in code
try {
$response = $apiClient->request($endpoint);
} catch (RateLimitException $e) {
if ($e->isRecoverable()) {
$waitTime = $e->getRetryAfter();
// Schedule retry after $waitTime seconds
}
throw $e;
}
```
### Exception Categories
```php
// Check exception category for handling strategies
try {
$result = $operation->execute();
} catch (FrameworkException $e) {
if ($e->isCategory('AUTH')) {
// Handle authentication errors
return $this->redirectToLogin();
}
if ($e->isCategory('VAL')) {
// Handle validation errors
return $this->validationErrorResponse($e);
}
if ($e->isErrorCode(DatabaseErrorCode::CONNECTION_FAILED)) {
// Handle specific database connection errors
$this->notifyOps($e);
}
throw $e;
}
```
### Simple Exceptions for Quick Use
```php
// When you don't need the full context system
throw FrameworkException::simple('Quick error message');
// With previous exception
} catch (\PDOException $e) {
throw FrameworkException::simple(
'Database operation failed',
$e,
500
);
}
```
### Exception Data Sanitization
The framework automatically sanitizes sensitive data in exceptions:
```php
// Sensitive keys are automatically redacted
$exception->withData([
'username' => 'john@example.com',
'password' => 'secret123', // Will be logged as '[REDACTED]'
'api_key' => 'sk_live_...' // Will be logged as '[REDACTED]'
]);
```
### Best Practices
1. **Always extend FrameworkException** for custom exceptions
2. **Use ErrorCode enum** for categorizable errors
3. **Provide rich context** with operation, component, and data
4. **Use factory methods** for consistent exception creation
5. **Sanitize sensitive data** (automatic for common keys)
6. **Make exceptions domain-specific** (UserNotFoundException vs generic NotFoundException)
7. **Include recovery hints** for recoverable errors
## Logging Best Practices
TODO: Document logging patterns and levels
## Debug Strategies
TODO: Document debugging approaches and tools
## Error Recovery Patterns
TODO: Document error recovery and graceful degradation
## Common Error Scenarios
TODO: List common errors and solutions

View File

@@ -1,963 +0,0 @@
# Event System
This guide covers the event-driven architecture of the framework.
## Overview
The framework provides a sophisticated event system built on two core components:
- **EventBus**: Simple, lightweight interface for basic event dispatching
- **EventDispatcher**: Full-featured implementation with handler management, priorities, and propagation control
**Key Features**:
- Attribute-based event handler registration via `#[OnEvent]`
- Priority-based handler execution
- Event propagation control (stop propagation)
- Support for inheritance and interface-based event matching
- Automatic handler discovery via framework's attribute system
- Domain events for business logic decoupling
- Application lifecycle events for framework integration
## EventBus vs EventDispatcher
### EventBus Interface
**Purpose**: Simple contract for event dispatching
```php
interface EventBus
{
public function dispatch(object $event): void;
}
```
**When to use**:
- Simple event dispatching without return values
- Fire-and-forget events
- When you don't need handler results
- Dependency injection when you want interface-based type hints
**Example Usage**:
```php
final readonly class OrderService
{
public function __construct(
private EventBus $eventBus
) {}
public function createOrder(CreateOrderCommand $command): Order
{
$order = Order::create($command);
// Fire event without caring about results
$this->eventBus->dispatch(new OrderCreatedEvent($order));
return $order;
}
}
```
### EventDispatcher Implementation
**Purpose**: Full-featured event system with handler management and results
**Key Features**:
- Returns array of handler results
- Priority-based handler execution (highest priority first)
- Stop propagation support
- Manual handler registration via `addHandler()` or `listen()`
- Automatic handler discovery via `#[OnEvent]` attribute
- Inheritance and interface matching
**When to use**:
- Need handler return values for processing
- Want priority control over execution order
- Need to stop event propagation conditionally
- Require manual handler registration
- Building complex event workflows
**Example Usage**:
```php
final readonly class PaymentProcessor
{
public function __construct(
private EventDispatcher $dispatcher
) {}
public function processPayment(Payment $payment): PaymentResult
{
// Dispatch and collect results from all handlers
$results = $this->dispatcher->dispatch(
new PaymentProcessingEvent($payment)
);
// Process handler results
foreach ($results as $result) {
if ($result instanceof PaymentValidationFailure) {
throw new PaymentException($result->reason);
}
}
return new PaymentResult($payment, $results);
}
}
```
### Implementation Relationship
```php
// EventDispatcher implements EventBus interface
final class EventDispatcher implements EventBus
{
public function dispatch(object $event): array
{
// Full implementation with handler management
}
}
// In DI Container
$container->singleton(EventBus::class, EventDispatcher::class);
$container->singleton(EventDispatcher::class, EventDispatcher::class);
```
**Recommendation**: Use `EventDispatcher` for type hints when you need full features, use `EventBus` interface when you want loose coupling and don't need results.
## Domain Events
### What are Domain Events?
Domain Events represent significant state changes or occurrences in your business domain. They enable loose coupling between domain components.
**Characteristics**:
- Immutable (`readonly` classes)
- Named in past tense (OrderCreated, UserRegistered, PaymentCompleted)
- Contain only relevant domain data
- No business logic (pure data objects)
- Include timestamp for audit trail
### Creating Domain Events
```php
namespace App\Domain\Order\Events;
use App\Domain\Order\ValueObjects\OrderId;
use App\Framework\Core\ValueObjects\Timestamp;
final readonly class OrderCreatedEvent
{
public readonly Timestamp $occurredAt;
public function __construct(
public OrderId $orderId,
public UserId $userId,
public Money $total,
?Timestamp $occurredAt = null
) {
$this->occurredAt = $occurredAt ?? Timestamp::now();
}
}
```
**Best Practices**:
- Use Value Objects for event properties
- Include timestamp for audit trail
- Keep events focused (single responsibility)
- Version events for backward compatibility
- Document event payload in PHPDoc
### Framework Lifecycle Events
The framework provides built-in events for application lifecycle:
#### ApplicationBooted
**Triggered**: After application bootstrap completes
```php
final readonly class ApplicationBooted
{
public function __construct(
public \DateTimeImmutable $bootTime,
public string $environment,
public Version $version,
public \DateTimeImmutable $occurredAt = new \DateTimeImmutable()
) {}
}
```
**Use Cases**:
- Initialize services after boot
- Start background workers
- Setup scheduled tasks
- Warm caches
#### BeforeHandleRequest
**Triggered**: Before HTTP request processing
```php
final readonly class BeforeHandleRequest
{
public function __construct(
public Request $request,
public Timestamp $timestamp,
public array $context = []
) {}
}
```
**Use Cases**:
- Request logging
- Performance monitoring
- Security checks
- Request transformation
#### AfterHandleRequest
**Triggered**: After HTTP request processing
```php
final readonly class AfterHandleRequest
{
public function __construct(
public Request $request,
public Response $response,
public Duration $processingTime,
public Timestamp $timestamp,
public array $context = []
) {}
}
```
**Use Cases**:
- Response logging
- Performance metrics
- Analytics collection
- Cleanup operations
### Example: User Registration Event
```php
namespace App\Domain\User\Events;
final readonly class UserRegisteredEvent
{
public readonly Timestamp $occurredAt;
public function __construct(
public UserId $userId,
public Email $email,
public UserName $userName,
?Timestamp $occurredAt = null
) {
$this->occurredAt = $occurredAt ?? Timestamp::now();
}
}
// Dispatching the event
final readonly class UserService
{
public function __construct(
private UserRepository $repository,
private EventBus $eventBus
) {}
public function register(RegisterUserCommand $command): User
{
$user = User::create(
$command->email,
$command->userName,
$command->password
);
$this->repository->save($user);
// Dispatch domain event
$this->eventBus->dispatch(
new UserRegisteredEvent(
$user->id,
$user->email,
$user->userName
)
);
return $user;
}
}
```
## Event Handler Registration
### Attribute-Based Registration
**Primary Method**: Use `#[OnEvent]` attribute for automatic discovery
```php
use App\Framework\Core\Events\OnEvent;
final readonly class UserEventHandlers
{
public function __construct(
private EmailService $emailService,
private Logger $logger
) {}
#[OnEvent(priority: 100)]
public function sendWelcomeEmail(UserRegisteredEvent $event): void
{
$this->emailService->send(
to: $event->email,
template: 'welcome',
data: ['userName' => $event->userName->value]
);
}
#[OnEvent(priority: 50)]
public function logUserRegistration(UserRegisteredEvent $event): void
{
$this->logger->info('User registered', [
'user_id' => $event->userId->toString(),
'email' => $event->email->value,
'occurred_at' => $event->occurredAt->format('Y-m-d H:i:s')
]);
}
}
```
**Attribute Parameters**:
- `priority` (optional): Handler execution order (higher = earlier, default: 0)
- `stopPropagation` (optional): Stop execution after this handler (default: false)
### Priority-Based Execution
Handlers execute in **priority order** (highest to lowest):
```php
#[OnEvent(priority: 200)] // Executes first
public function criticalHandler(OrderCreatedEvent $event): void { }
#[OnEvent(priority: 100)] // Executes second
public function highPriorityHandler(OrderCreatedEvent $event): void { }
#[OnEvent(priority: 50)] // Executes third
public function normalHandler(OrderCreatedEvent $event): void { }
#[OnEvent] // Executes last (default priority: 0)
public function defaultHandler(OrderCreatedEvent $event): void { }
```
**Use Cases for Priorities**:
- **Critical (200+)**: Security checks, validation, fraud detection
- **High (100-199)**: Transaction logging, audit trail
- **Normal (50-99)**: Business logic, notifications
- **Low (0-49)**: Analytics, metrics, cleanup
### Stop Propagation
**Purpose**: Prevent subsequent handlers from executing
```php
#[OnEvent(priority: 100, stopPropagation: true)]
public function validatePayment(PaymentProcessingEvent $event): PaymentValidationResult
{
$result = $this->validator->validate($event->payment);
if (!$result->isValid()) {
// Stop propagation - no further handlers execute
return new PaymentValidationFailure($result->errors);
}
return new PaymentValidationSuccess();
}
#[OnEvent(priority: 50)]
public function processPayment(PaymentProcessingEvent $event): void
{
// This handler only executes if validation passes
// (previous handler didn't set stopPropagation result)
$this->gateway->charge($event->payment);
}
```
**Important**: `stopPropagation` stops execution **after** the current handler completes.
### Manual Handler Registration
**Alternative**: Register handlers programmatically
```php
final readonly class EventInitializer
{
public function __construct(
private EventDispatcher $dispatcher
) {}
#[Initializer]
public function registerHandlers(): void
{
// Using listen() method
$this->dispatcher->listen(
OrderCreatedEvent::class,
function (OrderCreatedEvent $event) {
// Handle event
},
priority: 100
);
// Using addHandler() method
$this->dispatcher->addHandler(
eventClass: UserRegisteredEvent::class,
handler: [$this->userService, 'onUserRegistered'],
priority: 50
);
}
}
```
**When to use manual registration**:
- Dynamic handler registration based on configuration
- Third-party library integration
- Closure-based handlers for simple cases
- Runtime handler modification
### Handler Discovery
The framework automatically discovers handlers marked with `#[OnEvent]`:
```php
// In Application Bootstrap
final readonly class EventSystemInitializer
{
#[Initializer]
public function initialize(EventDispatcher $dispatcher): void
{
// Automatic discovery finds all #[OnEvent] methods
$discoveredHandlers = $this->attributeScanner->findMethodsWithAttribute(
OnEvent::class
);
foreach ($discoveredHandlers as $handler) {
$dispatcher->addHandler(
eventClass: $handler->getEventClass(),
handler: [$handler->instance, $handler->method],
priority: $handler->attribute->priority ?? 0
);
}
}
}
```
**No Manual Setup Required**: Framework handles discovery automatically during initialization.
## Event Middleware
**Concept**: Middleware pattern for event processing (transform, filter, log, etc.)
### Custom Event Middleware
```php
interface EventMiddleware
{
public function process(object $event, callable $next): mixed;
}
final readonly class LoggingEventMiddleware implements EventMiddleware
{
public function __construct(
private Logger $logger
) {}
public function process(object $event, callable $next): mixed
{
$eventClass = get_class($event);
$startTime = microtime(true);
$this->logger->debug("Event dispatched: {$eventClass}");
try {
$result = $next($event);
$duration = (microtime(true) - $startTime) * 1000;
$this->logger->debug("Event processed: {$eventClass}", [
'duration_ms' => $duration
]);
return $result;
} catch (\Throwable $e) {
$this->logger->error("Event failed: {$eventClass}", [
'error' => $e->getMessage()
]);
throw $e;
}
}
}
```
### Middleware Pipeline
```php
final class EventDispatcherWithMiddleware
{
/** @var EventMiddleware[] */
private array $middleware = [];
public function addMiddleware(EventMiddleware $middleware): void
{
$this->middleware[] = $middleware;
}
public function dispatch(object $event): array
{
$pipeline = array_reduce(
array_reverse($this->middleware),
fn($next, $middleware) => fn($event) => $middleware->process($event, $next),
fn($event) => $this->eventDispatcher->dispatch($event)
);
return $pipeline($event);
}
}
```
### Validation Middleware
```php
final readonly class ValidationEventMiddleware implements EventMiddleware
{
public function __construct(
private ValidatorInterface $validator
) {}
public function process(object $event, callable $next): mixed
{
// Validate event before dispatching
$errors = $this->validator->validate($event);
if (!empty($errors)) {
throw new InvalidEventException(
"Event validation failed: " . implode(', ', $errors)
);
}
return $next($event);
}
}
```
## Async Event Processing
### Queue-Based Async Handling
```php
use App\Framework\Queue\Queue;
use App\Framework\Queue\ValueObjects\JobPayload;
final readonly class AsyncEventHandler
{
public function __construct(
private Queue $queue
) {}
#[OnEvent(priority: 50)]
public function processAsync(OrderCreatedEvent $event): void
{
// Dispatch to queue for async processing
$job = new ProcessOrderEmailJob(
orderId: $event->orderId,
userEmail: $event->userEmail
);
$this->queue->push(
JobPayload::immediate($job)
);
}
}
// Background Job
final readonly class ProcessOrderEmailJob
{
public function __construct(
public OrderId $orderId,
public Email $userEmail
) {}
public function handle(EmailService $emailService): void
{
// Process email asynchronously
$emailService->sendOrderConfirmation(
$this->orderId,
$this->userEmail
);
}
}
```
### Event Buffering Pattern
```php
final class EventBuffer
{
private array $bufferedEvents = [];
#[OnEvent]
public function bufferEvent(DomainEvent $event): void
{
$this->bufferedEvents[] = $event;
}
public function flush(EventDispatcher $dispatcher): void
{
foreach ($this->bufferedEvents as $event) {
$dispatcher->dispatch($event);
}
$this->bufferedEvents = [];
}
}
// Usage in transaction
try {
$this->entityManager->beginTransaction();
// Buffer events during transaction
$this->eventBuffer->bufferEvent(new OrderCreatedEvent(/* ... */));
$this->eventBuffer->bufferEvent(new InventoryReservedEvent(/* ... */));
$this->entityManager->commit();
// Flush events after successful commit
$this->eventBuffer->flush($this->dispatcher);
} catch (\Exception $e) {
$this->entityManager->rollback();
// Events are not flushed on rollback
throw $e;
}
```
## Event Best Practices
### 1. Event Naming
**✅ Good**: Past tense, descriptive
```php
OrderCreatedEvent
UserRegisteredEvent
PaymentCompletedEvent
InventoryReservedEvent
```
**❌ Bad**: Present/future tense, vague
```php
CreateOrderEvent
RegisterUserEvent
CompletePayment
ReserveInventory
```
### 2. Event Granularity
**✅ Focused Events**:
```php
final readonly class OrderCreatedEvent { /* ... */ }
final readonly class OrderShippedEvent { /* ... */ }
final readonly class OrderCancelledEvent { /* ... */ }
```
**❌ God Events**:
```php
final readonly class OrderEvent
{
public function __construct(
public string $action, // 'created', 'shipped', 'cancelled'
public Order $order
) {}
}
```
### 3. Immutability
**✅ Readonly Events**:
```php
final readonly class UserUpdatedEvent
{
public function __construct(
public UserId $userId,
public Email $newEmail
) {}
}
```
**❌ Mutable Events**:
```php
final class UserUpdatedEvent
{
public UserId $userId;
public Email $newEmail;
public function setEmail(Email $email): void
{
$this->newEmail = $email; // Bad: Events should be immutable
}
}
```
### 4. Handler Independence
**✅ Independent Handlers**:
```php
#[OnEvent]
public function sendEmail(OrderCreatedEvent $event): void
{
// Self-contained - doesn't depend on other handlers
$this->emailService->send(/* ... */);
}
#[OnEvent]
public function updateInventory(OrderCreatedEvent $event): void
{
// Independent - no shared state with email handler
$this->inventoryService->reserve(/* ... */);
}
```
**❌ Coupled Handlers**:
```php
private bool $emailSent = false;
#[OnEvent(priority: 100)]
public function sendEmail(OrderCreatedEvent $event): void
{
$this->emailService->send(/* ... */);
$this->emailSent = true; // Bad: Shared state
}
#[OnEvent(priority: 50)]
public function logEmailSent(OrderCreatedEvent $event): void
{
if ($this->emailSent) { // Bad: Depends on other handler
$this->logger->info('Email sent');
}
}
```
### 5. Error Handling
**✅ Graceful Degradation**:
```php
#[OnEvent]
public function sendNotification(OrderCreatedEvent $event): void
{
try {
$this->notificationService->send(/* ... */);
} catch (NotificationException $e) {
// Log error but don't fail the entire event dispatch
$this->logger->error('Notification failed', [
'order_id' => $event->orderId->toString(),
'error' => $e->getMessage()
]);
}
}
```
### 6. Avoid Circular Events
**❌ Circular Dependency**:
```php
#[OnEvent]
public function onOrderCreated(OrderCreatedEvent $event): void
{
// Bad: Dispatching event from event handler can cause loops
$this->eventBus->dispatch(new OrderProcessedEvent($event->orderId));
}
#[OnEvent]
public function onOrderProcessed(OrderProcessedEvent $event): void
{
// Bad: Creates circular dependency
$this->eventBus->dispatch(new OrderCreatedEvent(/* ... */));
}
```
**✅ Use Command/Service Layer**:
```php
#[OnEvent]
public function onOrderCreated(OrderCreatedEvent $event): void
{
// Good: Use service to perform additional work
$this->orderProcessingService->processOrder($event->orderId);
}
```
### 7. Performance Considerations
**Heavy Operations → Queue**:
```php
#[OnEvent]
public function generateInvoicePdf(OrderCreatedEvent $event): void
{
// Heavy operation - push to queue
$this->queue->push(
JobPayload::immediate(
new GenerateInvoiceJob($event->orderId)
)
);
}
```
**Light Operations → Synchronous**:
```php
#[OnEvent]
public function logOrderCreation(OrderCreatedEvent $event): void
{
// Light operation - execute immediately
$this->logger->info('Order created', [
'order_id' => $event->orderId->toString()
]);
}
```
## Common Patterns
### Event Sourcing Pattern
```php
final class OrderEventStore
{
#[OnEvent]
public function appendEvent(DomainEvent $event): void
{
$this->eventStore->append([
'event_type' => get_class($event),
'event_data' => json_encode($event),
'occurred_at' => $event->occurredAt->format('Y-m-d H:i:s'),
'aggregate_id' => $event->getAggregateId()
]);
}
}
```
### Saga Pattern
```php
final readonly class OrderSaga
{
#[OnEvent(priority: 100)]
public function onOrderCreated(OrderCreatedEvent $event): void
{
// Step 1: Reserve inventory
$this->inventoryService->reserve($event->items);
}
#[OnEvent(priority: 90)]
public function onInventoryReserved(InventoryReservedEvent $event): void
{
// Step 2: Process payment
$this->paymentService->charge($event->orderId);
}
#[OnEvent(priority: 80)]
public function onPaymentCompleted(PaymentCompletedEvent $event): void
{
// Step 3: Ship order
$this->shippingService->ship($event->orderId);
}
}
```
### CQRS Pattern
```php
// Command Side - Dispatches Events
final readonly class CreateOrderHandler
{
public function handle(CreateOrderCommand $command): Order
{
$order = Order::create($command);
$this->repository->save($order);
$this->eventBus->dispatch(
new OrderCreatedEvent($order)
);
return $order;
}
}
// Query Side - Maintains Read Model
#[OnEvent]
public function updateOrderReadModel(OrderCreatedEvent $event): void
{
$this->orderReadModel->insert([
'order_id' => $event->orderId->toString(),
'user_id' => $event->userId->toString(),
'total' => $event->total->toDecimal(),
'status' => 'created'
]);
}
```
## Framework Integration
### With Queue System
```php
#[OnEvent]
public function queueHeavyTask(OrderCreatedEvent $event): void
{
$this->queue->push(
JobPayload::immediate(
new ProcessOrderAnalyticsJob($event->orderId)
)
);
}
```
### With Scheduler
```php
#[OnEvent]
public function scheduleReminder(OrderCreatedEvent $event): void
{
$this->scheduler->schedule(
'send-order-reminder-' . $event->orderId,
OneTimeSchedule::at(Timestamp::now()->addDays(3)),
fn() => $this->emailService->sendReminder($event->orderId)
);
}
```
### With Cache System
```php
#[OnEvent]
public function invalidateCache(OrderCreatedEvent $event): void
{
$this->cache->forget(
CacheTag::fromString("user_{$event->userId}")
);
}
```
## Summary
The Event System provides:
-**Decoupled Architecture**: Loose coupling via events
-**Flexible Handling**: Priority-based, propagation control
-**Automatic Discovery**: `#[OnEvent]` attribute registration
-**Multiple Patterns**: Domain events, application events, async processing
-**Framework Integration**: Works with Queue, Scheduler, Cache
-**Testing Support**: Easy to unit test and integration test
-**Performance**: Efficient handler execution with priority optimization
**When to Use Events**:
- Decoupling domain logic from side effects (email, notifications)
- Cross-module communication without tight coupling
- Audit trail and event sourcing
- Async processing of non-critical operations
- CQRS and saga patterns
**When NOT to Use Events**:
- Synchronous business logic requiring immediate results
- Simple function calls (don't over-engineer)
- Performance-critical paths (events have overhead)
- Operations requiring transactional guarantees across handlers

View File

@@ -1,965 +0,0 @@
# Filesystem Patterns
Comprehensive guide to the Custom PHP Framework's Filesystem module.
## Overview
The Filesystem module provides a robust, type-safe, and security-focused abstraction for file operations. Built on framework principles of immutability, readonly classes, and value objects.
**Core Components**:
- `FileStorage` - Main filesystem operations interface
- `FileValidator` - Security-focused path and content validation
- `SerializerRegistry` - Automatic serializer detection and management
- `FileOperationContext` - Rich logging context for operations
- `TemporaryDirectory` - Safe temporary file management
---
## FileStorage
### Basic Usage
```php
use App\Framework\Filesystem\FileStorage;
use App\Framework\Filesystem\FileValidator;
use App\Framework\Logging\Logger;
// Simple storage without validation or logging
$storage = new FileStorage('/var/www/storage');
// Write file
$storage->put('documents/report.txt', 'File contents');
// Read file
$content = $storage->get('documents/report.txt');
// Check existence
if ($storage->exists('documents/report.txt')) {
// File exists
}
// Delete file
$storage->delete('documents/report.txt');
// Copy file
$storage->copy('source.txt', 'destination.txt');
// List directory
$files = $storage->files('documents');
```
### With Validator Integration
```php
// Create validator with security defaults
$validator = FileValidator::createDefault();
// Initialize storage with validator
$storage = new FileStorage(
baseDirectory: '/var/www/uploads',
validator: $validator
);
// All operations are now validated
try {
$storage->put('file.txt', 'content'); // Validates path, extension, size
} catch (FileValidationException $e) {
// Handle validation failure
}
```
### With Logger Integration
```php
use App\Framework\Logging\Logger;
$storage = new FileStorage(
baseDirectory: '/var/www/storage',
validator: FileValidator::createDefault(),
logger: $container->get(Logger::class)
);
// Operations are automatically logged with severity levels:
// - high severity: DELETE, MOVE → warning level
// - large operations (>10MB) → info level
// - normal operations → debug level
$storage->delete('important.txt'); // Logs as WARNING
```
---
## FileValidator
### Security-Focused Validation
The `FileValidator` provides multiple layers of security validation:
**1. Path Traversal Prevention**
- Detects `../`, `..\\`, URL-encoded variants
- Prevents null bytes in paths
- Optional base directory restriction
**2. Extension Filtering**
- Whitelist (allowedExtensions) - only specific extensions allowed
- Blacklist (blockedExtensions) - dangerous extensions blocked
- Case-insensitive matching
**3. File Size Limits**
- Maximum file size enforcement
- Uses `FileSize` value object for type safety
- Human-readable error messages
### Factory Methods
```php
// Default validator - blocks dangerous extensions, 100MB max
$validator = FileValidator::createDefault();
// Blocks: exe, bat, sh, cmd, com
// Max size: 100MB
// Strict validator - only whitelisted extensions allowed
$validator = FileValidator::createStrict(['txt', 'pdf', 'docx']);
// Only allows: txt, pdf, docx
// Max size: 50MB
// Upload validator - secure upload configuration
$validator = FileValidator::forUploads();
// Allows: jpg, jpeg, png, gif, pdf, txt, csv, json
// Blocks: exe, bat, sh, cmd, com, php, phtml
// Max size: 10MB
// Image validator - image files only
$validator = FileValidator::forImages();
// Allows: jpg, jpeg, png, gif, webp, svg
// Max size: 5MB
// Custom max size
$validator = FileValidator::forUploads(FileSize::fromMegabytes(50));
```
### Validation Methods
```php
// Individual validation methods
$validator->validatePath($path); // Path traversal, null bytes
$validator->validateExtension($path); // Extension whitelist/blacklist
$validator->validateFileSize($size); // Size limits
$validator->validateExists($path); // File existence
$validator->validateReadable($path); // Read permissions
$validator->validateWritable($path); // Write permissions
// Composite validation for common operations
$validator->validateRead($path); // Path + exists + readable
$validator->validateWrite($path, $size); // Path + extension + size + writable
$validator->validateUpload($path, $size); // Path + extension + size
// Query methods (non-throwing)
$isAllowed = $validator->isExtensionAllowed('pdf');
$allowedExts = $validator->getAllowedExtensions();
$blockedExts = $validator->getBlockedExtensions();
$maxSize = $validator->getMaxFileSize();
```
### Custom Validator Configuration
```php
use App\Framework\Core\ValueObjects\FileSize;
$validator = new FileValidator(
allowedExtensions: ['json', 'xml', 'yaml'],
blockedExtensions: null, // Don't use blacklist with whitelist
maxFileSize: FileSize::fromMegabytes(25),
baseDirectory: '/var/www/uploads' // Restrict to this directory
);
```
### Exception Handling
```php
use App\Framework\Filesystem\Exceptions\FileValidationException;
use App\Framework\Filesystem\Exceptions\FileNotFoundException;
use App\Framework\Filesystem\Exceptions\FilePermissionException;
try {
$validator->validateUpload('../../../etc/passwd', FileSize::fromKilobytes(1));
} catch (FileValidationException $e) {
// Path traversal detected
error_log($e->getMessage());
// "Path traversal attempt detected"
}
try {
$validator->validateUpload('malware.exe', FileSize::fromKilobytes(100));
} catch (FileValidationException $e) {
// Blocked extension
// "File extension '.exe' is blocked"
}
try {
$validator->validateRead('/nonexistent/file.txt');
} catch (FileNotFoundException $e) {
// File not found
}
try {
$validator->validateWrite('/readonly/path/file.txt');
} catch (FilePermissionException $e) {
// Permission denied
}
```
---
## SerializerRegistry
### Auto-Detection and Management
The `SerializerRegistry` automatically detects and manages file serializers based on extensions and MIME types.
### Default Registry
```php
use App\Framework\Filesystem\SerializerRegistry;
// Creates registry with common serializers pre-registered
$registry = SerializerRegistry::createDefault();
// Automatically includes:
// - JsonSerializer (.json, application/json)
// - XmlSerializer (.xml, application/xml, text/xml)
// - CsvSerializer (.csv, text/csv)
// - YamlSerializer (.yaml, .yml, application/yaml)
```
### Auto-Detection
```php
// Detect serializer from file path
$serializer = $registry->detectFromPath('/data/config.json');
// Returns: JsonSerializer
$serializer = $registry->detectFromPath('/exports/data.csv');
// Returns: CsvSerializer
// Get by extension
$serializer = $registry->getByExtension('xml');
// Get by MIME type
$serializer = $registry->getByMimeType('application/json');
```
### Custom Serializers
```php
// Implement Serializer interface
final readonly class TomlSerializer implements Serializer
{
public function serialize(mixed $data): string
{
return Toml::encode($data);
}
public function unserialize(string $data): mixed
{
return Toml::decode($data);
}
public function getSupportedExtensions(): array
{
return ['toml'];
}
public function getSupportedMimeTypes(): array
{
return ['application/toml'];
}
public function getName(): string
{
return 'toml';
}
}
// Register custom serializer
$registry->register(new TomlSerializer());
// Set as default serializer
$registry->setDefault(new TomlSerializer());
// Use auto-detection
$serializer = $registry->detectFromPath('config.toml');
```
### Registry Statistics
```php
$stats = $registry->getStatistics();
// Returns:
// [
// 'total_serializers' => 5,
// 'total_extensions' => 8,
// 'total_mime_types' => 6,
// 'has_default' => true,
// 'default_serializer' => 'json'
// ]
// Get all registered names
$names = $registry->getRegisteredNames();
// Returns: ['json', 'xml', 'csv', 'yaml', 'toml']
// List all serializers
$serializers = $registry->getAll();
```
---
## FileOperationContext
### Logging Context with Severity Levels
`FileOperationContext` provides rich metadata for logging filesystem operations with automatic severity classification.
### Severity Levels
**High Severity** (logged as WARNING):
- DELETE - File deletion
- DELETE_DIRECTORY - Directory deletion
- MOVE - File/directory move
**Medium Severity** (logged as INFO if large, DEBUG otherwise):
- WRITE - File write
- COPY - File copy
- CREATE_DIRECTORY - Directory creation
**Low Severity** (logged as DEBUG):
- READ - File read
- LIST_DIRECTORY - Directory listing
- GET_METADATA - Metadata retrieval
- All other read operations
### Factory Methods
```php
use App\Framework\Filesystem\ValueObjects\FileOperationContext;
use App\Framework\Filesystem\ValueObjects\FileOperation;
use App\Framework\Core\ValueObjects\FileSize;
// Simple operation
$context = FileOperationContext::forOperation(
FileOperation::DELETE,
'/path/to/file.txt'
);
// Operation with destination (copy, move)
$context = FileOperationContext::forOperationWithDestination(
FileOperation::COPY,
'/source/file.txt',
'/dest/file.txt'
);
// Write operation with size tracking
$context = FileOperationContext::forWrite(
'/path/to/file.txt',
FileSize::fromKilobytes(150),
userId: 'user123'
);
// Read operation with size tracking
$context = FileOperationContext::forRead(
'/path/to/file.txt',
FileSize::fromMegabytes(5)
);
```
### Context Enhancement
```php
// Add metadata
$context = $context->withMetadata([
'source' => 'upload',
'mime_type' => 'application/pdf',
'original_filename' => 'document.pdf'
]);
// Add user ID
$context = $context->withUserId('admin');
// Metadata merges with existing data
$context = $context->withMetadata(['key1' => 'value1'])
->withMetadata(['key2' => 'value2']);
// Results in: ['key1' => 'value1', 'key2' => 'value2']
```
### Context Queries
```php
// Check severity
if ($context->isHighSeverity()) {
// High severity operation (DELETE, MOVE, DELETE_DIRECTORY)
}
// Check operation type
if ($context->isWriteOperation()) {
// Write operation (WRITE, DELETE, MOVE, etc.)
}
// Check operation size
if ($context->isLargeOperation()) {
// Large operation (>10MB)
}
```
### Logging Integration
```php
// Convert to array for structured logging
$logData = $context->toArray();
// Returns:
// [
// 'operation' => 'write',
// 'operation_name' => 'file.write',
// 'path' => '/path/to/file.txt',
// 'timestamp' => '2025-01-15T10:30:00+00:00',
// 'severity' => 'medium',
// 'bytes_affected' => 153600,
// 'bytes_affected_human' => '150 KB',
// 'user_id' => 'user123',
// 'metadata' => ['source' => 'upload']
// ]
// Human-readable string
$message = $context->toString();
// Returns: "Write file contents, path: /path/to/file.txt, bytes: 150 KB, user: user123"
```
### Automatic Logging in FileStorage
```php
// FileStorage automatically logs operations based on severity:
private function logOperation(FileOperationContext $context): void
{
if ($this->logger === null) {
return;
}
$logContext = LogContext::fromArray($context->toArray());
if ($context->isHighSeverity()) {
// DELETE, MOVE, DELETE_DIRECTORY
$this->logger->framework->warning($context->toString(), $logContext);
} elseif ($context->isLargeOperation()) {
// Operations >10MB
$this->logger->framework->info($context->toString(), $logContext);
} else {
// Normal operations
$this->logger->framework->debug($context->toString(), $logContext);
}
}
```
---
## TemporaryDirectory
### Safe Temporary File Management
`TemporaryDirectory` provides automatic cleanup and safe handling of temporary files.
### Basic Usage
```php
use App\Framework\Filesystem\TemporaryDirectory;
// Create with auto-cleanup on destruct
$temp = TemporaryDirectory::create();
// Get path to temp directory
$path = $temp->path();
// Get FilePath for file in temp directory
$filePath = $temp->filePath('test.txt');
// Write files normally
file_put_contents($filePath->toString(), 'content');
// Auto-deletes on destruct
unset($temp); // Directory and all contents deleted
```
### Advanced Configuration
```php
// Custom name
$temp = TemporaryDirectory::create()
->name('my-temp-dir')
->force(); // Overwrite if exists
// Custom location
$temp = TemporaryDirectory::create()
->location('/custom/tmp')
->name('test-dir')
->force();
// Disable auto-delete
$temp = TemporaryDirectory::create()
->doNotDeleteAutomatically();
// Manual cleanup
$temp->empty(); // Empty directory contents
$temp->delete(); // Delete directory and contents
```
### Fluent Interface
```php
$temp = TemporaryDirectory::create()
->name('upload-processing')
->location('/var/tmp')
->force()
->create(); // Explicitly create
$processedFile = $temp->filePath('processed.txt');
```
---
## Best Practices
### 1. Always Use FileValidator for User Input
```php
// ❌ UNSAFE - no validation
$storage = new FileStorage('/uploads');
$storage->put($_FILES['file']['name'], file_get_contents($_FILES['file']['tmp_name']));
// ✅ SAFE - with validation
$validator = FileValidator::forUploads();
$storage = new FileStorage('/uploads', validator: $validator);
try {
$validator->validateUpload(
$_FILES['file']['name'],
FileSize::fromBytes($_FILES['file']['size'])
);
$storage->put($_FILES['file']['name'], file_get_contents($_FILES['file']['tmp_name']));
} catch (FileValidationException $e) {
// Handle validation error
}
```
### 2. Use Appropriate Validators
```php
// Image uploads
$validator = FileValidator::forImages(FileSize::fromMegabytes(5));
// Document uploads
$validator = FileValidator::forUploads(FileSize::fromMegabytes(20));
// Strict validation for config files
$validator = FileValidator::createStrict(['json', 'yaml', 'toml']);
```
### 3. Log High-Severity Operations
```php
// Always use logger for production systems
$storage = new FileStorage(
baseDirectory: '/var/www/storage',
validator: FileValidator::createDefault(),
logger: $logger // Critical for audit trail
);
// High-severity operations are automatically logged as WARNING
$storage->delete('/important/document.pdf');
```
### 4. Use TemporaryDirectory for Processing
```php
// Process uploads safely
$temp = TemporaryDirectory::create();
try {
// Extract archive to temp directory
$archive->extractTo($temp->path());
// Process files
foreach ($temp->files() as $file) {
$this->processFile($file);
}
// Move processed files to final location
$storage->copy($temp->filePath('result.txt'), '/final/result.txt');
} finally {
// Auto-cleanup on scope exit
unset($temp);
}
```
### 5. Combine SerializerRegistry with FileStorage
```php
$registry = SerializerRegistry::createDefault();
$storage = new FileStorage('/data', validator: FileValidator::createDefault());
// Auto-detect serializer and deserialize
$serializer = $registry->detectFromPath('config.json');
$data = $serializer->unserialize($storage->get('config.json'));
// Serialize and store
$content = $serializer->serialize(['key' => 'value']);
$storage->put('output.json', $content);
```
---
## Security Considerations
### Path Traversal Prevention
```php
// FileValidator blocks these automatically:
$validator->validatePath('../../../etc/passwd'); // ❌ BLOCKED
$validator->validatePath('..\\..\\windows\\system32'); // ❌ BLOCKED
$validator->validatePath('%2e%2e/etc/passwd'); // ❌ BLOCKED (URL-encoded)
$validator->validatePath("/path/with\0nullbyte"); // ❌ BLOCKED (null byte)
```
### Extension Filtering
```php
// Always use whitelist for user uploads
$validator = FileValidator::createStrict(['jpg', 'png', 'pdf']);
// Or use specialized validators
$validator = FileValidator::forImages(); // Images only
$validator = FileValidator::forUploads(); // Blocks dangerous extensions
// NEVER trust client-provided MIME types
// Use extension-based validation instead
```
### Base Directory Restriction
```php
// Restrict all operations to base directory
$validator = new FileValidator(
allowedExtensions: null,
blockedExtensions: ['exe', 'sh', 'bat'],
maxFileSize: FileSize::fromMegabytes(100),
baseDirectory: '/var/www/uploads' // Cannot escape this directory
);
// Attempts to escape are blocked
$validator->validatePath('/var/www/uploads/../../../etc/passwd'); // ❌ BLOCKED
```
---
## Performance Considerations
### Large File Operations
```php
// FileOperationContext detects large operations (>10MB)
if ($context->isLargeOperation()) {
// Triggers INFO-level logging instead of DEBUG
// Consider background processing for very large files
}
// Use streaming for large files
$storage->stream('large-file.mp4', function($stream) {
while (!feof($stream)) {
echo fread($stream, 8192);
}
});
```
### Caching Validator Results
```php
// Validator operations are fast, but for high-volume scenarios:
$isValid = $validator->isExtensionAllowed('pdf'); // Non-throwing check
if ($isValid) {
// Proceed with operation
} else {
// Reject early without exception overhead
}
```
---
## Phase 2 Performance Optimizations
The Filesystem module includes advanced performance optimizations achieved through caching strategies and syscall reduction.
**Framework Integration**: All performance optimizations are **automatically enabled by default** via `FilesystemInitializer` with sensible production settings. Disable only for debugging:
```env
# Filesystem Performance (caching enabled by default)
# Set to true only for debugging performance issues
# FILESYSTEM_DISABLE_CACHE=false
```
**Default Settings**:
- FileValidator caching: ENABLED (TTL: 60s, Max: 100 entries)
- FileStorage directory caching: ENABLED (session-based cache)
- clearstatcache() optimization: ENABLED (minimal syscalls)
### CachedFileValidator - Result Cache
**Performance Gain**: 99% faster for cached validations
**Description**: LRU cache decorator for FileValidator that caches validation results (both successes and failures) to avoid repeated expensive validation operations.
**Automatic Integration**: Enabled by default via `FilesystemInitializer` when resolving `FileValidator::class` from DI container. Disable only for debugging via `FILESYSTEM_DISABLE_CACHE=true` in `.env`.
**Features**:
- LRU eviction when cache size exceeds limit (default: 100 entries)
- Configurable TTL (default: 60 seconds)
- Caches path and extension validation (not file size/existence checks)
- Automatic cache invalidation on TTL expiry
- Cache statistics for monitoring
**Usage**:
```php
use App\Framework\Filesystem\CachedFileValidator;
use App\Framework\Filesystem\FileValidator;
$validator = FileValidator::createDefault();
$cachedValidator = new CachedFileValidator(
validator: $validator,
cacheTtl: 60, // 60 seconds TTL
maxCacheSize: 100 // Max 100 cached results
);
// First call - cache miss (validates fully)
$cachedValidator->validatePath('/path/to/file.txt');
// Second call - cache hit (99% faster)
$cachedValidator->validatePath('/path/to/file.txt');
// Get cache statistics
$stats = $cachedValidator->getCacheStats();
// ['path_cache_size' => 1, 'extension_cache_size' => 0, ...]
```
**What's Cached**:
- ✅ Path validation (traversal checks, null bytes)
- ✅ Extension validation (allowed/blocked lists)
- ✅ Composite validations (validateRead, validateWrite, validateUpload)
**What's NOT Cached**:
- ❌ File size validation (size can change)
- ❌ File existence checks (files can be created/deleted)
- ❌ Permission checks (permissions can change)
**Performance Characteristics**:
- Cache hit latency: <0.1ms
- Cache miss latency: ~1ms (original validation)
- Memory usage: ~100KB for 100 entries
### CachedFileStorage - Directory Cache
**Performance Gain**: 25% fewer syscalls for write operations
**Description**: Decorator for FileStorage that caches directory existence checks to reduce redundant `is_dir()` syscalls during write operations.
**Automatic Integration**: Enabled by default via `FilesystemInitializer` when resolving `Storage::class`, `FileStorage::class`, or any named storage (`filesystem.storage.*`) from DI container. Disable only for debugging via `FILESYSTEM_DISABLE_CACHE=true` in `.env`.
**Features**:
- Session-based cache (cleared on object destruction)
- Write-through caching (directories cached when created or verified)
- Conservative strategy (only caches successful operations)
- Parent directory recursive caching
- O(1) cache lookup performance
**Usage**:
```php
use App\Framework\Filesystem\CachedFileStorage;
use App\Framework\Filesystem\FileStorage;
$storage = new FileStorage('/var/www/storage');
$cachedStorage = new CachedFileStorage(
storage: $storage,
basePath: '/var/www/storage'
);
// First write - cache miss (checks directory exists)
$cachedStorage->put('nested/deep/file1.txt', 'content1');
// Second write to same directory - cache hit (skips is_dir check)
$cachedStorage->put('nested/deep/file2.txt', 'content2'); // 25% faster
// Get cache statistics
$stats = $cachedStorage->getCacheStats();
// ['cached_directories' => 2, 'cache_entries' => [...]]
```
**Cache Strategy**:
- Directories cached on first verification or creation
- Parent directories automatically cached (if `/a/b/c` exists, `/a/b` and `/a` are cached)
- Normalized paths (resolved symlinks, no trailing slashes)
- Hash-based cache keys for O(1) lookup
**Performance Characteristics**:
- Cache hit latency: <0.01ms (array lookup)
- Cache miss latency: ~1ms (is_dir syscall)
- Memory usage: ~50 bytes per cached directory
- Syscall reduction: 25% for repeated writes to same directories
### clearstatcache() Optimization
**Performance Gain**: ~1-2ms faster read operations
**Description**: Strategic placement of `clearstatcache()` calls - only before write operations where fresh stat info is critical, removed from read operations where stat cache is valid.
**Optimization Details**:
**Before Optimization**:
```php
// FileStorage::get() - UNNECESSARY
public function get(string $path): string
{
clearstatcache(true, $resolvedPath); // ❌ Unnecessary for reads
if (!is_file($resolvedPath)) {
throw new FileNotFoundException($path);
}
// Also in error handling
clearstatcache(true, $resolvedPath); // ❌ Unnecessary
if (!is_file($resolvedPath)) {
throw new FileNotFoundException($path);
}
return file_get_contents($resolvedPath);
}
```
**After Optimization**:
```php
// FileStorage::get() - Removed unnecessary clearstatcache
public function get(string $path): string
{
// ✅ No clearstatcache - stat cache is valid for reads
if (!is_file($resolvedPath)) {
throw new FileNotFoundException($path);
}
return file_get_contents($resolvedPath);
}
// FileStorage::put() - Added necessary clearstatcache
public function put(string $path, string $content): void
{
$dir = dirname($resolvedPath);
// ✅ Clear stat cache before directory check
clearstatcache(true, $dir);
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
file_put_contents($resolvedPath, $content);
}
```
**Performance Impact**:
- Read operations: ~1-2ms faster (removed 2x clearstatcache calls)
- Write operations: No performance impact (necessary clearstatcache added)
- Stat cache correctness: Maintained for write operations, valid for read operations
---
## Testing
### Unit Testing with Validators
```php
it('validates file uploads correctly', function () {
$validator = FileValidator::forUploads();
// Valid upload
$validator->validateUpload(
'/uploads/document.pdf',
FileSize::fromMegabytes(2)
);
expect(true)->toBeTrue(); // No exception
// Invalid - path traversal
try {
$validator->validateUpload(
'../../../etc/passwd',
FileSize::fromKilobytes(1)
);
expect(true)->toBeFalse('Should throw');
} catch (FileValidationException $e) {
expect($e->getMessage())->toContain('Path traversal');
}
});
```
### Integration Testing with FileStorage
```php
it('integrates validator with storage', function () {
$testDir = sys_get_temp_dir() . '/test_' . uniqid();
mkdir($testDir);
$validator = FileValidator::createStrict(['txt']);
$storage = new FileStorage($testDir, validator: $validator);
// Valid operation
$storage->put('allowed.txt', 'content');
expect($storage->exists('allowed.txt'))->toBeTrue();
// Invalid operation
try {
$storage->put('blocked.exe', 'malicious');
expect(true)->toBeFalse('Should throw');
} catch (FileValidationException $e) {
expect($e->getMessage())->toContain('not allowed');
}
// Cleanup
array_map('unlink', glob($testDir . '/*'));
rmdir($testDir);
});
```
---
## Summary
The Filesystem module provides:
**Type-Safe Operations** - Value objects throughout (FilePath, FileSize, FileOperation)
**Security-First** - Path traversal prevention, extension filtering, size limits
**Rich Logging** - Automatic severity classification, detailed context
**Auto-Detection** - Serializer registry with extension/MIME type mapping
**Immutable Design** - Readonly classes, transformation methods
**Framework Compliance** - Follows all framework architectural principles
**Key Integration Points**:
- Works with framework's Logger for audit trails
- Uses Value Objects (FileSize, Timestamp, FilePath)
- Event system integration available
- Queue system integration for async operations
- Cache integration for serializer registry
**Performance Optimizations**:
- **CachedFileValidator**: 99% faster validation for repeated paths (LRU cache)
- **CachedFileStorage**: 25% fewer syscalls for write operations (directory cache)
- **clearstatcache() Optimization**: 1-2ms faster read operations (strategic placement)
- Memory-efficient caching strategies
- O(1) cache lookup performance
**Production Ready**:
- Comprehensive test coverage (145 tests, 319 assertions)
- Security-focused validation
- Performance-optimized design with caching
- Detailed error messages
- Production logging with severity levels

View File

@@ -0,0 +1,735 @@
# JavaScript Modules Analysis & Recommendations
**Comprehensive Analysis of Existing JS Modules and Recommendations for New Modules and Refactorings**
This document provides a detailed analysis of the current JavaScript module ecosystem, identifies areas for improvement, and proposes new modules and refactorings.
---
## Table of Contents
1. [Current Module Overview](#current-module-overview)
2. [Module Quality Assessment](#module-quality-assessment)
3. [Recommended New Modules](#recommended-new-modules)
4. [Refactoring Recommendations](#refactoring-recommendations)
5. [Priority Matrix](#priority-matrix)
6. [Implementation Roadmap](#implementation-roadmap)
---
## Current Module Overview
### Module Categories
#### 1. **Core Framework Modules**
- `livecomponent/` - LiveComponents system (well-structured, modern)
- `ui/` - UI components (Modal, Dialog, Lightbox)
- `api-manager/` - Web API wrappers (comprehensive)
- `sse/` - Server-Sent Events client
#### 2. **Form & Input Modules**
- `form-handling/` - Form validation and submission
- `form-autosave.js` - Auto-save functionality
#### 3. **Navigation & Routing**
- `spa-router/` - Single Page Application router
#### 4. **Animation & Effects Modules**
- `canvas-animations/` - Canvas-based animations
- `scrollfx/` - Scroll-based animations
- `parallax/` - Parallax effects
- `smooth-scroll/` - Smooth scrolling
- `scroll-timeline/` - Scroll timeline animations
- `scroll-loop/` - Infinite scroll loops
- `scroll-dependent/` - Scroll-dependent effects
- `sticky-fade/` - Sticky fade effects
- `sticky-steps/` - Sticky step animations
- `inertia-scroll/` - Inertia scrolling
- `wheel-boost/` - Wheel boost effects
- `noise/` - Noise effects
#### 5. **Media & Image Modules**
- `image-manager/` - Image gallery, upload, modal
#### 6. **Utility Modules**
- `csrf-auto-refresh.js` - CSRF token management
- `hot-reload.js` - Hot reload functionality
- `performance-profiler/` - Performance profiling
- `webpush/` - Web Push notifications
#### 7. **Admin Modules**
- `admin/data-table.js` - Admin data tables
---
## Module Quality Assessment
### ✅ Well-Structured Modules (Keep as-is)
1. **livecomponent/** - Excellent structure, modern patterns
- ✅ Modular architecture
- ✅ TypeScript definitions
- ✅ Error handling
- ✅ Comprehensive documentation
2. **api-manager/** - Well-organized Web API wrappers
- ✅ Consistent API
- ✅ Feature detection
- ✅ Good separation of concerns
3. **form-handling/** - Solid form handling
- ✅ Clear separation (Handler, Validator, State)
- ✅ Progressive enhancement
- ⚠️ Could benefit from LiveComponent integration
4. **ui/** - Clean UI component system
- ✅ Reusable components
- ✅ Consistent API
- ⚠️ Could expand with more components
### ⚠️ Modules Needing Refactoring
1. **spa-router/** - Good but could be improved
- ⚠️ Mixed concerns (routing + transitions)
- ⚠️ Could better integrate with LiveComponents
- ⚠️ Module re-initialization could be cleaner
2. **form-autosave.js** - Standalone file
- ⚠️ Should be part of form-handling module
- ⚠️ No module system integration
3. **Scroll Animation Modules** - Too many separate modules
- ⚠️ `scrollfx/`, `parallax/`, `scroll-timeline/`, `scroll-loop/`, `scroll-dependent/`, `sticky-fade/`, `sticky-steps/` - Could be unified
- ⚠️ Duplicate functionality
- ⚠️ Inconsistent APIs
4. **image-manager/** - Good but could be enhanced
- ⚠️ Could integrate with LiveComponent file uploads
- ⚠️ EventEmitter pattern could be modernized
5. **performance-profiler/** - Standalone
- ⚠️ Could integrate with LiveComponent DevTools
- ⚠️ Should be part of development tools
### ❌ Modules Needing Major Refactoring
1. **Multiple Scroll Modules** - Consolidation needed
- ❌ 8+ separate scroll-related modules
- ❌ Inconsistent patterns
- ❌ Hard to maintain
2. **csrf-auto-refresh.js** - Standalone file
- ❌ Should be part of security module
- ❌ No module system integration
---
## Recommended New Modules
### 1. **State Management Module** (High Priority)
**Purpose**: Centralized state management for client-side state
**Features**:
- Reactive state store (similar to Redux/Vuex)
- State persistence (localStorage, sessionStorage)
- State synchronization across tabs
- Time-travel debugging
- Integration with LiveComponents
**Use Cases**:
- User preferences
- Shopping cart state
- UI state (sidebar open/closed, theme)
- Form drafts
**API Example**:
```javascript
import { StateManager } from './modules/state-manager/index.js';
const store = StateManager.create({
user: { name: '', email: '' },
cart: { items: [], total: 0 },
ui: { sidebarOpen: false }
});
// Reactive updates
store.subscribe('cart', (cart) => {
updateCartUI(cart);
});
// Actions
store.dispatch('cart.addItem', { id: 1, name: 'Product' });
```
**Priority**: High
**Effort**: Medium (2-3 days)
---
### 2. **Validation Module** (High Priority)
**Purpose**: Standalone validation system (not just forms)
**Features**:
- Field-level validation
- Schema-based validation (JSON Schema)
- Async validation
- Custom validation rules
- Integration with LiveComponents
- Integration with form-handling
**Use Cases**:
- Form validation
- API response validation
- User input validation
- Data transformation validation
**API Example**:
```javascript
import { Validator } from './modules/validation/index.js';
const validator = Validator.create({
email: {
type: 'email',
required: true,
message: 'Invalid email address'
},
age: {
type: 'number',
min: 18,
max: 100
}
});
const result = await validator.validate({ email: 'test@example.com', age: 25 });
```
**Priority**: High
**Effort**: Medium (2-3 days)
---
### 3. **Cache Manager Module** (Medium Priority)
**Purpose**: Intelligent caching for API responses and computed values
**Features**:
- Memory cache
- IndexedDB cache
- Cache invalidation strategies
- Cache warming
- Cache analytics
- Integration with RequestDeduplicator
**Use Cases**:
- API response caching
- Computed value caching
- Image caching
- Search result caching
**API Example**:
```javascript
import { CacheManager } from './modules/cache-manager/index.js';
const cache = CacheManager.create({
strategy: 'stale-while-revalidate',
ttl: 3600000 // 1 hour
});
// Cache API response
const data = await cache.get('users', async () => {
return await fetch('/api/users').then(r => r.json());
});
// Invalidate cache
cache.invalidate('users');
```
**Priority**: Medium
**Effort**: Medium (2-3 days)
---
### 4. **Event Bus Module** (Medium Priority)
**Purpose**: Centralized event system for cross-module communication
**Features**:
- Pub/sub pattern
- Namespaced events
- Event filtering
- Event history
- Integration with LiveComponents
- Integration with SSE
**Use Cases**:
- Component communication
- Module communication
- Global notifications
- Analytics events
**API Example**:
```javascript
import { EventBus } from './modules/event-bus/index.js';
const bus = EventBus.create();
// Subscribe
bus.on('user:logged-in', (user) => {
updateUI(user);
});
// Publish
bus.emit('user:logged-in', { id: 1, name: 'John' });
// Namespaced events
bus.on('livecomponent:action-executed', (data) => {
console.log('Action executed:', data);
});
```
**Priority**: Medium
**Effort**: Low (1-2 days)
---
### 5. **Storage Manager Module** (Low Priority)
**Purpose**: Unified storage interface (localStorage, sessionStorage, IndexedDB)
**Features**:
- Unified API for all storage types
- Automatic serialization
- Storage quotas
- Storage migration
- Storage analytics
**Note**: Partially exists in `api-manager/StorageManager`, but could be enhanced
**Priority**: Low (enhance existing)
**Effort**: Low (1 day)
---
### 6. **Router Enhancement Module** (Medium Priority)
**Purpose**: Enhanced routing with guards, middleware, and lazy loading
**Features**:
- Route guards (auth, permissions)
- Route middleware
- Lazy route loading
- Route transitions
- Route analytics
- Integration with LiveComponents
**API Example**:
```javascript
import { Router } from './modules/router/index.js';
const router = Router.create({
routes: [
{
path: '/dashboard',
component: 'DashboardComponent',
guard: 'auth',
middleware: ['analytics']
}
]
});
router.beforeEach((to, from, next) => {
if (requiresAuth(to) && !isAuthenticated()) {
next('/login');
} else {
next();
}
});
```
**Priority**: Medium
**Effort**: Medium (2-3 days)
---
### 7. **Animation System Module** (Low Priority)
**Purpose**: Unified animation system consolidating all scroll/animation modules
**Features**:
- Unified animation API
- Scroll-based animations
- Timeline animations
- Performance optimizations
- Integration with Web Animations API
**Consolidates**:
- `scrollfx/`
- `parallax/`
- `scroll-timeline/`
- `scroll-loop/`
- `scroll-dependent/`
- `sticky-fade/`
- `sticky-steps/`
- `canvas-animations/`
**Priority**: Low (refactoring existing)
**Effort**: High (5-7 days)
---
### 8. **Error Tracking Module** (High Priority)
**Purpose**: Centralized error tracking and reporting
**Features**:
- Error collection
- Error grouping
- Error reporting (to backend)
- Error analytics
- Integration with ErrorBoundary
- Source map support
**API Example**:
```javascript
import { ErrorTracker } from './modules/error-tracking/index.js';
const tracker = ErrorTracker.create({
endpoint: '/api/errors',
sampleRate: 1.0
});
// Automatic error tracking
tracker.init();
// Manual error reporting
tracker.captureException(new Error('Something went wrong'), {
context: { userId: 123 },
tags: { feature: 'checkout' }
});
```
**Priority**: High
**Effort**: Medium (2-3 days)
---
### 9. **Analytics Module** (Medium Priority)
**Purpose**: Unified analytics system
**Features**:
- Event tracking
- Page view tracking
- User behavior tracking
- Custom events
- Integration with LiveComponents
- Privacy-compliant (GDPR)
**API Example**:
```javascript
import { Analytics } from './modules/analytics/index.js';
const analytics = Analytics.create({
providers: ['google-analytics', 'custom']
});
analytics.track('purchase', {
value: 99.99,
currency: 'EUR',
items: [{ id: 'product-1', quantity: 1 }]
});
```
**Priority**: Medium
**Effort**: Medium (2-3 days)
---
### 10. **Internationalization (i18n) Module** (Low Priority)
**Purpose**: Internationalization and localization
**Features**:
- Translation management
- Pluralization
- Date/time formatting
- Number formatting
- Currency formatting
- Integration with LiveComponents
**API Example**:
```javascript
import { i18n } from './modules/i18n/index.js';
i18n.init({
locale: 'de-DE',
fallback: 'en-US',
translations: {
'de-DE': { 'welcome': 'Willkommen' },
'en-US': { 'welcome': 'Welcome' }
}
});
const message = i18n.t('welcome');
```
**Priority**: Low
**Effort**: Medium (2-3 days)
---
## Refactoring Recommendations
### 1. **Consolidate Scroll Animation Modules** (High Priority)
**Current State**: 8+ separate scroll-related modules
**Proposed Solution**: Create unified `animation-system/` module
**Benefits**:
- Single API for all animations
- Reduced bundle size
- Easier maintenance
- Better performance
- Consistent patterns
**Implementation**:
1. Create `animation-system/` module
2. Migrate functionality from existing modules
3. Maintain backward compatibility during transition
4. Deprecate old modules
5. Update documentation
**Priority**: High
**Effort**: High (5-7 days)
---
### 2. **Integrate form-autosave into form-handling** (Medium Priority)
**Current State**: Standalone `form-autosave.js` file
**Proposed Solution**: Add autosave functionality to `form-handling/` module
**Benefits**:
- Better organization
- Shared form state
- Consistent API
- Easier maintenance
**Implementation**:
1. Move autosave logic into `FormHandler`
2. Add autosave configuration options
3. Integrate with `FormState`
4. Update documentation
**Priority**: Medium
**Effort**: Low (1 day)
---
### 3. **Enhance SPA Router with LiveComponent Integration** (Medium Priority)
**Current State**: SPA Router works independently
**Proposed Solution**: Better integration with LiveComponents
**Benefits**:
- Automatic LiveComponent initialization after navigation
- Better state management
- Improved performance
- Unified API
**Implementation**:
1. Add LiveComponent auto-initialization
2. Integrate with LiveComponentManager
3. Handle component state during navigation
4. Update documentation
**Priority**: Medium
**Effort**: Medium (2-3 days)
---
### 4. **Create Security Module** (Medium Priority)
**Current State**: `csrf-auto-refresh.js` is standalone
**Proposed Solution**: Create `security/` module
**Features**:
- CSRF token management
- XSS protection helpers
- Content Security Policy helpers
- Security headers validation
**Implementation**:
1. Create `security/` module
2. Move CSRF logic
3. Add additional security features
4. Update documentation
**Priority**: Medium
**Effort**: Low (1-2 days)
---
### 5. **Integrate Performance Profiler with DevTools** (Low Priority)
**Current State**: Standalone `performance-profiler/` module
**Proposed Solution**: Integrate with LiveComponent DevTools
**Benefits**:
- Unified developer experience
- Better visualization
- Easier debugging
- Reduced bundle size (dev only)
**Implementation**:
1. Move profiler into DevTools
2. Integrate with LiveComponent profiling
3. Update UI
4. Update documentation
**Priority**: Low
**Effort**: Medium (2-3 days)
---
### 6. **Modernize Image Manager** (Low Priority)
**Current State**: Uses EventEmitter pattern
**Proposed Solution**: Modernize with ES6 classes and better integration
**Benefits**:
- Modern patterns
- Better TypeScript support
- Integration with LiveComponent file uploads
- Improved performance
**Implementation**:
1. Refactor to ES6 classes
2. Add TypeScript definitions
3. Integrate with LiveComponent file uploads
4. Update documentation
**Priority**: Low
**Effort**: Medium (2-3 days)
---
## Priority Matrix
### High Priority (Implement Soon)
1. **State Management Module** - Needed for complex applications
2. **Validation Module** - Reusable validation logic
3. **Error Tracking Module** - Production debugging
4. **Consolidate Scroll Animation Modules** - Maintenance burden
### Medium Priority (Implement Next)
1. **Cache Manager Module** - Performance optimization
2. **Event Bus Module** - Cross-module communication
3. **Router Enhancement Module** - Better routing features
4. **Analytics Module** - Business requirements
5. **Integrate form-autosave** - Code organization
6. **Enhance SPA Router** - Better integration
7. **Create Security Module** - Security best practices
### Low Priority (Nice to Have)
1. **Storage Manager Module** - Enhance existing
2. **Animation System Module** - Refactoring existing
3. **i18n Module** - Internationalization
4. **Integrate Performance Profiler** - Developer experience
5. **Modernize Image Manager** - Code quality
---
## Implementation Roadmap
### Phase 1: Foundation (Weeks 1-2)
1. **State Management Module** (3 days)
2. **Validation Module** (3 days)
3. **Error Tracking Module** (3 days)
4. **Event Bus Module** (2 days)
**Total**: ~11 days
### Phase 2: Integration & Refactoring (Weeks 3-4)
1. **Integrate form-autosave** (1 day)
2. **Enhance SPA Router** (3 days)
3. **Create Security Module** (2 days)
4. **Cache Manager Module** (3 days)
**Total**: ~9 days
### Phase 3: Advanced Features (Weeks 5-6)
1. **Router Enhancement Module** (3 days)
2. **Analytics Module** (3 days)
3. **Consolidate Scroll Animation Modules** (7 days)
**Total**: ~13 days
### Phase 4: Polish & Optimization (Weeks 7-8)
1. **Storage Manager Enhancement** (1 day)
2. **Integrate Performance Profiler** (3 days)
3. **Modernize Image Manager** (3 days)
4. **Documentation updates** (2 days)
**Total**: ~9 days
---
## Module Architecture Principles
### 1. **Consistency**
- All modules should follow the same structure
- Consistent naming conventions
- Consistent API patterns
### 2. **Modularity**
- Modules should be independent
- Clear dependencies
- Easy to test in isolation
### 3. **Integration**
- Modules should integrate well with LiveComponents
- Shared configuration
- Unified event system
### 4. **Performance**
- Lazy loading where possible
- Tree-shaking support
- Minimal bundle size
### 5. **Developer Experience**
- TypeScript definitions
- Comprehensive documentation
- Clear error messages
- DevTools integration
---
## Next Steps
1. **Review this analysis** with the team
2. **Prioritize modules** based on project needs
3. **Create detailed implementation plans** for high-priority modules
4. **Start with Phase 1** (Foundation modules)
5. **Iterate and refine** based on feedback
---
**Last Updated**: 2025-01-XX
**Status**: Draft - Pending Review

View File

@@ -1,893 +0,0 @@
# LiveComponent File Uploads
Komplette Dokumentation des File Upload Systems für LiveComponents mit Drag & Drop, Multi-File Support und Progress Tracking.
## Übersicht
Das File Upload System ermöglicht es, Dateien direkt in LiveComponents hochzuladen mit:
- **Drag & Drop Support** - Intuitive Dateiauswahl durch Ziehen & Ablegen
- **Multi-File Uploads** - Mehrere Dateien gleichzeitig hochladen
- **Progress Tracking** - Echtzeit-Fortschrittsanzeige pro Datei und gesamt
- **Preview Funktionalität** - Bild-Vorschauen und Datei-Icons
- **Client-Side Validation** - Validierung vor dem Upload (MIME-Type, Größe, Extension)
- **CSRF Protection** - Automatische CSRF-Token-Integration
- **Component State Management** - Nahtlose Integration mit LiveComponent State
## Architektur
```
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ FileUploadWidget │───▶│ ComponentFileUploader│───▶│ Backend Route │
│ (UI Component) │ │ (Upload Manager) │ │ /upload endpoint │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
│ │ │
Drop Zone UI Multi-File Queue State Update
File List Progress Tracking HTML Refresh
Progress Bars CSRF Handling Event Dispatch
```
### Komponenten
1. **Backend** (`src/Framework/LiveComponents/`)
- `Controllers/LiveComponentController::handleUpload()` - Upload Route Handler
- `LiveComponentHandler::handleUpload()` - Business Logic
- `Contracts/SupportsFileUpload` - Interface für uploadbare Components
- `ValueObjects/FileUploadProgress` - Progress Tracking VO
- `ValueObjects/UploadedComponentFile` - File Metadata VO
2. **Frontend** (`resources/js/modules/livecomponent/`)
- `ComponentFileUploader.js` - Core Upload Manager
- `FileUploadWidget.js` - Pre-built UI Component
- `DragDropZone` - Drag & Drop Handler
- `FileValidator` - Client-Side Validation
- `UploadProgress` - Progress Tracking
3. **Styling** (`resources/css/components/`)
- `file-upload-widget.css` - Complete UI Styling
## Backend Implementation
### Interface: SupportsFileUpload
LiveComponents, die Uploads unterstützen, müssen das `SupportsFileUpload` Interface implementieren:
```php
<?php
use App\Framework\LiveComponents\Contracts\SupportsFileUpload;
use App\Framework\Http\UploadedFile;
use App\Framework\LiveComponents\ComponentEventDispatcher;
use App\Framework\LiveComponents\ValueObjects\ComponentData;
final class DocumentUploadComponent extends AbstractLiveComponent implements SupportsFileUpload
{
private array $uploadedFiles = [];
/**
* Handle file upload
*/
public function handleUpload(UploadedFile $file, ?ComponentEventDispatcher $events = null): ComponentData
{
// 1. Validate file (already done by framework, but you can add custom validation)
if (!$this->isValidDocument($file)) {
throw new \InvalidArgumentException('Invalid document type');
}
// 2. Process uploaded file
$savedPath = $this->saveFile($file);
// 3. Update component state
$this->uploadedFiles[] = [
'name' => $file->getClientFilename(),
'path' => $savedPath,
'size' => $file->getSize(),
'uploaded_at' => time()
];
// 4. Dispatch events if needed
if ($events) {
$events->dispatch('file-uploaded', [
'filename' => $file->getClientFilename(),
'path' => $savedPath
]);
}
// 5. Return updated component data
return ComponentData::fromArray([
'uploaded_files' => $this->uploadedFiles
]);
}
/**
* Validate uploaded file
*/
public function validateUpload(UploadedFile $file): array
{
$errors = [];
// File type validation
$allowedTypes = $this->getAllowedMimeTypes();
if (!empty($allowedTypes) && !in_array($file->getClientMediaType(), $allowedTypes)) {
$errors[] = "File type {$file->getClientMediaType()} is not allowed";
}
// File size validation
$maxSize = $this->getMaxFileSize();
if ($file->getSize() > $maxSize) {
$errors[] = "File size exceeds maximum allowed size";
}
// Custom validation
if (!$this->isValidDocument($file)) {
$errors[] = "Invalid document format";
}
return $errors;
}
/**
* Get allowed MIME types
*/
public function getAllowedMimeTypes(): array
{
return [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'image/jpeg',
'image/png'
];
}
/**
* Get maximum file size in bytes
*/
public function getMaxFileSize(): int
{
return 10 * 1024 * 1024; // 10MB
}
private function isValidDocument(UploadedFile $file): bool
{
// Custom validation logic
return true;
}
private function saveFile(UploadedFile $file): string
{
// Save file to storage
$uploadDir = '/var/www/storage/uploads';
$filename = uniqid() . '_' . $file->getClientFilename();
$file->moveTo($uploadDir . '/' . $filename);
return $filename;
}
}
```
### Upload Endpoint
**Route**: `POST /live-component/{id}/upload`
**Request Format**:
```
Content-Type: multipart/form-data
file: <binary file data>
state: <JSON string of current component state>
params: <JSON string of additional parameters>
_csrf_token: <CSRF token>
```
**Response Format**:
```json
{
"success": true,
"html": "<updated component HTML>",
"state": {
"id": "document-upload:abc123",
"component": "DocumentUploadComponent",
"data": {
"uploaded_files": [...]
}
},
"events": [...],
"file": {
"name": "document.pdf",
"size": 1048576,
"type": "application/pdf"
}
}
```
**Error Response**:
```json
{
"success": false,
"error": "File validation failed",
"errors": [
"File type application/octet-stream is not allowed"
]
}
```
## Frontend Implementation
### Quick Start: FileUploadWidget
Der einfachste Weg, File Uploads zu implementieren, ist die Verwendung des `FileUploadWidget`:
```html
<!-- In your LiveComponent template -->
<div data-live-component="document-upload:abc123">
<!-- Upload Widget Container -->
<div id="upload-widget"></div>
</div>
<script type="module">
import { FileUploadWidget } from '/resources/js/modules/livecomponent/FileUploadWidget.js';
// Initialize widget
const widget = new FileUploadWidget(
document.getElementById('upload-widget'),
{
maxFileSize: 10 * 1024 * 1024, // 10MB
allowedMimeTypes: ['application/pdf', 'image/jpeg', 'image/png'],
allowedExtensions: ['pdf', 'jpg', 'jpeg', 'png'],
maxFiles: 5,
multiple: true,
autoUpload: true,
showPreviews: true,
showProgress: true
}
);
</script>
```
### Advanced: ComponentFileUploader
Für vollständige Kontrolle verwenden Sie direkt `ComponentFileUploader`:
```javascript
import { ComponentFileUploader } from '/resources/js/modules/livecomponent/ComponentFileUploader.js';
const componentElement = document.querySelector('[data-live-id="document-upload:abc123"]');
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
const uploader = new ComponentFileUploader(componentElement, {
// Configuration
maxFileSize: 10 * 1024 * 1024,
allowedMimeTypes: ['application/pdf', 'image/jpeg'],
maxFiles: 10,
autoUpload: true,
multiple: true,
maxConcurrentUploads: 2,
// UI Elements
dropZone: dropZone,
fileInput: fileInput,
// Callbacks
onFileAdded: ({ fileId, file, progress }) => {
console.log('File added:', file.name);
// Update UI
},
onUploadStart: ({ fileId, file }) => {
console.log('Upload started:', file.name);
},
onUploadProgress: ({ fileId, percentage, uploadSpeed, remainingTime }) => {
console.log(`Upload progress: ${percentage}%`);
// Update progress bar
},
onUploadComplete: ({ fileId, file, response }) => {
console.log('Upload complete:', file.name);
// Handle success
},
onUploadError: ({ fileId, file, error }) => {
console.error('Upload failed:', error);
// Handle error
},
onAllUploadsComplete: ({ totalFiles, successCount, errorCount }) => {
console.log(`All uploads complete: ${successCount}/${totalFiles} succeeded`);
}
});
// Programmatically add files
uploader.addFiles([file1, file2, file3]);
// Start uploads (if autoUpload is false)
uploader.uploadAll();
// Cancel all uploads
uploader.cancelAll();
// Get statistics
const stats = uploader.getStats();
console.log(`Progress: ${stats.overallProgress}%`);
```
### Custom Drag & Drop
```javascript
import { DragDropZone } from '/resources/js/modules/livecomponent/ComponentFileUploader.js';
const dropZone = new DragDropZone(document.getElementById('drop-area'), {
onFilesDropped: (files) => {
console.log('Files dropped:', files);
uploader.addFiles(files);
},
onDragEnter: () => {
console.log('Drag enter');
},
onDragLeave: () => {
console.log('Drag leave');
}
});
```
### Client-Side Validation
```javascript
import { FileValidator } from '/resources/js/modules/livecomponent/ComponentFileUploader.js';
const validator = new FileValidator({
maxFileSize: 5 * 1024 * 1024, // 5MB
allowedMimeTypes: ['image/jpeg', 'image/png'],
allowedExtensions: ['jpg', 'jpeg', 'png'],
minFileSize: 1024 // 1KB minimum
});
// Validate single file
const errors = validator.validate(file);
if (errors.length > 0) {
console.error('Validation errors:', errors);
}
// Quick validation
if (!validator.isValid(file)) {
console.error('File is not valid');
}
```
## Configuration Options
### ComponentFileUploader Options
```javascript
{
// File Constraints
maxFileSize: 10 * 1024 * 1024, // Maximum file size in bytes (default: 10MB)
allowedMimeTypes: [], // Array of allowed MIME types (empty = allow all)
allowedExtensions: [], // Array of allowed file extensions (empty = allow all)
maxFiles: 10, // Maximum number of files (default: 10)
// Upload Behavior
autoUpload: true, // Auto-upload on file add (default: true)
multiple: true, // Allow multiple files (default: true)
maxConcurrentUploads: 2, // Max concurrent uploads (default: 2)
// Endpoints
endpoint: '/live-component/{id}/upload', // Upload endpoint (default: auto-detected)
// UI Elements (optional)
dropZone: HTMLElement, // Drop zone element
fileInput: HTMLElement, // File input element
// Callbacks
onFileAdded: (data) => {}, // Called when file is added
onFileRemoved: (data) => {}, // Called when file is removed
onUploadStart: (data) => {}, // Called when upload starts
onUploadProgress: (data) => {}, // Called during upload
onUploadComplete: (data) => {}, // Called on upload success
onUploadError: (data) => {}, // Called on upload error
onAllUploadsComplete: (data) => {} // Called when all uploads are done
}
```
### FileUploadWidget Options
```javascript
{
// Inherits all ComponentFileUploader options, plus:
// UI Configuration
showPreviews: true, // Show image previews (default: true)
showProgress: true, // Show progress bars (default: true)
showFileList: true, // Show file list (default: true)
// Text Configuration
dropZoneText: 'Drag & drop files here or click to browse',
browseButtonText: 'Browse Files',
uploadButtonText: 'Upload All'
}
```
## Callback Data Structures
### onFileAdded
```javascript
{
fileId: 'unique-file-id',
file: File, // Native File object
progress: {
fileId: 'unique-file-id',
fileName: 'document.pdf',
fileSize: 1048576,
uploadedBytes: 0,
percentage: 0,
status: 'pending',
error: null,
uploadSpeed: 0,
remainingTime: 0
}
}
```
### onUploadProgress
```javascript
{
fileId: 'unique-file-id',
fileName: 'document.pdf',
fileSize: 1048576,
uploadedBytes: 524288, // Bytes uploaded so far
percentage: 50, // Upload percentage (0-100)
status: 'uploading',
uploadSpeed: 1048576, // Bytes per second
remainingTime: 0.5 // Seconds remaining
}
```
### onUploadComplete
```javascript
{
fileId: 'unique-file-id',
file: File,
response: {
success: true,
html: '<updated component HTML>',
state: {...},
events: [...],
file: {
name: 'document.pdf',
size: 1048576,
type: 'application/pdf'
}
}
}
```
### onUploadError
```javascript
{
fileId: 'unique-file-id',
file: File,
error: 'File validation failed'
}
```
## Use Cases & Examples
### Basic Image Upload
```php
final class ProfileImageUpload extends AbstractLiveComponent implements SupportsFileUpload
{
private ?string $profileImage = null;
public function handleUpload(UploadedFile $file, ?ComponentEventDispatcher $events = null): ComponentData
{
// Save image
$filename = $this->imageService->save($file, 'profiles');
// Update state
$this->profileImage = $filename;
return ComponentData::fromArray([
'profile_image' => $this->profileImage
]);
}
public function validateUpload(UploadedFile $file): array
{
$errors = [];
// Only allow images
if (!str_starts_with($file->getClientMediaType(), 'image/')) {
$errors[] = 'Only images are allowed';
}
// Max 2MB
if ($file->getSize() > 2 * 1024 * 1024) {
$errors[] = 'Image must be smaller than 2MB';
}
return $errors;
}
public function getAllowedMimeTypes(): array
{
return ['image/jpeg', 'image/png', 'image/webp'];
}
public function getMaxFileSize(): int
{
return 2 * 1024 * 1024;
}
}
```
### Multi-Document Upload with Progress
```javascript
import { ComponentFileUploader } from './ComponentFileUploader.js';
const uploader = new ComponentFileUploader(componentElement, {
maxFiles: 20,
maxFileSize: 50 * 1024 * 1024, // 50MB
allowedMimeTypes: [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
],
onUploadProgress: ({ fileId, percentage, uploadSpeed, remainingTime }) => {
// Update progress UI
const progressBar = document.querySelector(`[data-file-id="${fileId}"] .progress-bar`);
progressBar.style.width = `${percentage}%`;
const progressText = document.querySelector(`[data-file-id="${fileId}"] .progress-text`);
progressText.textContent = `${percentage}% - ${formatSpeed(uploadSpeed)} - ${formatTime(remainingTime)} remaining`;
},
onAllUploadsComplete: ({ successCount, errorCount }) => {
if (errorCount === 0) {
alert(`All ${successCount} files uploaded successfully!`);
} else {
alert(`${successCount} files uploaded, ${errorCount} failed`);
}
}
});
```
### Custom Validation Messages
```javascript
const validator = new FileValidator({
maxFileSize: 10 * 1024 * 1024,
allowedMimeTypes: ['application/pdf']
});
const errors = validator.validate(file);
// Translate errors
const translatedErrors = errors.map(error => {
if (error.includes('File size')) {
return 'Dateigröße überschreitet das Maximum';
} else if (error.includes('File type')) {
return 'Nur PDF-Dateien sind erlaubt';
}
return error;
});
if (translatedErrors.length > 0) {
showErrorMessages(translatedErrors);
}
```
## Security Considerations
### CSRF Protection
Das System verwendet automatisch CSRF-Tokens:
```javascript
// CSRF token wird automatisch aus Component-Element gelesen
const csrfToken = componentElement.dataset.csrfToken;
// Und in jedem Upload-Request gesendet
xhr.setRequestHeader('X-CSRF-Form-ID', csrfTokens.form_id);
xhr.setRequestHeader('X-CSRF-Token', csrfTokens.token);
```
### File Validation
**Backend Validation ist PFLICHT**:
```php
public function validateUpload(UploadedFile $file): array
{
$errors = [];
// 1. MIME type validation
$allowedTypes = $this->getAllowedMimeTypes();
if (!in_array($file->getClientMediaType(), $allowedTypes)) {
$errors[] = 'Invalid file type';
}
// 2. File extension validation
$extension = pathinfo($file->getClientFilename(), PATHINFO_EXTENSION);
if (!in_array(strtolower($extension), ['pdf', 'jpg', 'png'])) {
$errors[] = 'Invalid file extension';
}
// 3. File size validation
if ($file->getSize() > $this->getMaxFileSize()) {
$errors[] = 'File too large';
}
// 4. File content validation (check magic bytes)
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$actualMimeType = $finfo->file($file->getStream()->getMetadata('uri'));
if ($actualMimeType !== $file->getClientMediaType()) {
$errors[] = 'File content does not match declared type';
}
return $errors;
}
```
### Secure File Storage
```php
private function saveFile(UploadedFile $file): string
{
// 1. Generate secure filename (no user input)
$filename = bin2hex(random_bytes(16)) . '.' . $this->getSecureExtension($file);
// 2. Store outside web root
$uploadDir = '/var/www/storage/uploads';
// 3. Set restrictive permissions
$file->moveTo($uploadDir . '/' . $filename);
chmod($uploadDir . '/' . $filename, 0600);
return $filename;
}
private function getSecureExtension(UploadedFile $file): string
{
// Use MIME type to determine extension, not user-provided extension
return match ($file->getClientMediaType()) {
'application/pdf' => 'pdf',
'image/jpeg' => 'jpg',
'image/png' => 'png',
default => 'bin'
};
}
```
## Performance Optimization
### Concurrent Uploads
```javascript
const uploader = new ComponentFileUploader(componentElement, {
maxConcurrentUploads: 3, // Upload 3 files simultaneously
autoUpload: true
});
```
### Chunked Uploads (Large Files)
Für große Dateien sollten Chunked Uploads implementiert werden:
```javascript
// TODO: Chunked upload support (geplant für v2.0)
// Aktuell empfohlen: maxFileSize Limit für große Dateien
```
### Progress Throttling
```javascript
let lastProgressUpdate = 0;
onUploadProgress: ({ fileId, percentage }) => {
const now = Date.now();
// Only update UI every 100ms
if (now - lastProgressUpdate > 100) {
updateProgressBar(fileId, percentage);
lastProgressUpdate = now;
}
}
```
## Troubleshooting
### Problem: Upload schlägt fehl mit 413 (Request Entity Too Large)
**Lösung**: Erhöhe PHP Upload Limits:
```ini
; php.ini
upload_max_filesize = 50M
post_max_size = 50M
```
### Problem: CSRF Token fehlt
**Lösung**: Stelle sicher, dass Component CSRF Token hat:
```html
<div data-live-component="upload:123" data-csrf-token="<?= $csrfToken ?>">
...
</div>
```
### Problem: Uploads sind langsam
**Lösungen**:
1. Erhöhe `maxConcurrentUploads`
2. Implementiere Chunked Uploads
3. Komprimiere Dateien vor Upload (z.B. Bilder)
4. Verwende CDN für statische Assets
### Problem: Browser hängt bei vielen Dateien
**Lösung**: Limitiere `maxFiles` und zeige Queue-Status:
```javascript
const uploader = new ComponentFileUploader(componentElement, {
maxFiles: 20, // Limit gleichzeitig ausgewählte Dateien
maxConcurrentUploads: 2 // Aber nur 2 gleichzeitig hochladen
});
```
### Problem: Drag & Drop funktioniert nicht
**Lösung**: Prüfe Event Listener Setup:
```javascript
// Stelle sicher, dass Drop Zone Element existiert
const dropZone = document.getElementById('drop-zone');
if (!dropZone) {
console.error('Drop zone element not found');
}
// Prüfe CSS cursor
dropZone.style.cursor = 'pointer';
```
## Best Practices
### 1. Immer Backend-Validierung
```php
// ❌ Niemals nur Frontend-Validierung verlassen
// ✅ Immer Backend validateUpload() implementieren
```
### 2. Sichere Dateinamen
```php
// ❌ User-provided filenames verwenden
$file->moveTo('/uploads/' . $file->getClientFilename());
// ✅ Sichere, generierte Dateinamen
$file->moveTo('/uploads/' . bin2hex(random_bytes(16)) . '.pdf');
```
### 3. Progress Feedback
```javascript
// ✅ Immer Progress anzeigen für bessere UX
onUploadProgress: ({ percentage }) => {
updateProgressBar(percentage);
updateStatusText(`Uploading: ${percentage}%`);
}
```
### 4. Error Handling
```javascript
// ✅ User-freundliche Fehlermeldungen
onUploadError: ({ error }) => {
const userMessage = translateError(error);
showNotification(userMessage, 'error');
logError(error); // Log für Debugging
}
```
### 5. File Size Limits
```php
// ✅ Realistische Limits setzen
public function getMaxFileSize(): int
{
// 10MB für Dokumente
return 10 * 1024 * 1024;
// 5MB für Bilder
// return 5 * 1024 * 1024;
}
```
## Testing
### Unit Tests (Frontend)
```javascript
import { FileValidator } from './ComponentFileUploader.js';
describe('FileValidator', () => {
it('validates file size', () => {
const validator = new FileValidator({
maxFileSize: 1024 * 1024 // 1MB
});
const file = new File(['x'.repeat(2 * 1024 * 1024)], 'large.pdf');
const errors = validator.validate(file);
expect(errors.length).toBeGreaterThan(0);
expect(errors[0]).toContain('File size');
});
it('validates MIME type', () => {
const validator = new FileValidator({
allowedMimeTypes: ['application/pdf']
});
const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' });
const errors = validator.validate(file);
expect(errors.length).toBeGreaterThan(0);
expect(errors[0]).toContain('not allowed');
});
});
```
### Integration Tests (Backend)
```php
it('handles file upload successfully', function () {
$component = new DocumentUploadComponent();
$file = createUploadedFile('test.pdf', 'application/pdf', 1024);
$result = $component->handleUpload($file);
expect($result->toArray())->toHaveKey('uploaded_files');
});
it('validates file type', function () {
$component = new DocumentUploadComponent();
$file = createUploadedFile('test.exe', 'application/octet-stream', 1024);
$errors = $component->validateUpload($file);
expect($errors)->not->toBeEmpty();
});
```
## Zusammenfassung
Das File Upload System bietet:
-**Einfache Integration** - FileUploadWidget für schnellen Start
-**Flexible API** - ComponentFileUploader für vollständige Kontrolle
-**Drag & Drop** - Intuitive Dateiauswahl
-**Multi-File Support** - Mehrere Dateien gleichzeitig
-**Progress Tracking** - Echtzeit-Fortschrittsanzeige
-**Validation** - Client & Server-Side
-**Security** - CSRF Protection, sichere Dateinamen
-**Performance** - Concurrent Uploads, Queue Management
-**Responsive Design** - Mobile-optimiert
-**Dark Mode** - Automatische Theme-Unterstützung
**Framework Integration**:
- Value Objects für Type Safety
- Event System Integration
- Component State Management
- CSRF Token Handling
- Automatic HTML Refresh

View File

@@ -1,458 +0,0 @@
# LiveComponent FormBuilder Integration
Elegante Integration zwischen LiveComponent System und bestehendem FormBuilder.
## Architektur
```
┌─────────────────────────┐
│ MultiStepFormDefinition │ ← Value Object mit Steps
└────────────┬────────────┘
├─► FormStepDefinition ← Step-Info + Fields
└─► FormFieldDefinition ← Field-Config (text, email, etc.)
├─► FieldType (enum)
├─► FieldCondition (conditional rendering)
└─► StepValidator (validation logic)
┌─────────────────────────┐
│ MultiStepFormComponent │ ← Generic LiveComponent
└────────────┬────────────┘
└─► LiveFormBuilder ← Erweitert bestehenden FormBuilder
└─► FormBuilder (bestehend, wiederverwendet!)
```
## Verwendungsbeispiel
### 1. Form Definition erstellen (Deklarativ, Type-Safe)
```php
use App\Framework\LiveComponents\FormBuilder\MultiStepFormDefinition;
use App\Framework\LiveComponents\FormBuilder\FormStepDefinition;
use App\Framework\LiveComponents\FormBuilder\FormFieldDefinition;
use App\Framework\LiveComponents\FormBuilder\FieldCondition;
// Deklarative Form-Definition - kein Code-Duplikat!
$userRegistrationForm = new MultiStepFormDefinition(
steps: [
// Step 1: Personal Information
new FormStepDefinition(
title: 'Persönliche Informationen',
description: 'Bitte geben Sie Ihre persönlichen Daten ein',
fields: [
FormFieldDefinition::text(
name: 'first_name',
label: 'Vorname',
required: true
),
FormFieldDefinition::text(
name: 'last_name',
label: 'Nachname',
required: true
),
FormFieldDefinition::email(
name: 'email',
label: 'E-Mail Adresse',
required: true
)
],
validator: new PersonalInfoValidator()
),
// Step 2: Account Type
new FormStepDefinition(
title: 'Konto-Typ',
description: 'Wählen Sie Ihren Konto-Typ',
fields: [
FormFieldDefinition::radio(
name: 'account_type',
label: 'Account Type',
options: [
'personal' => 'Privatkonto',
'business' => 'Geschäftskonto'
],
required: true
),
// Conditional Field - nur bei Business
FormFieldDefinition::text(
name: 'company_name',
label: 'Firmenname',
required: true
)->showWhen(
FieldCondition::equals('account_type', 'business')
),
FormFieldDefinition::text(
name: 'vat_number',
label: 'USt-IdNr.',
placeholder: 'DE123456789'
)->showWhen(
FieldCondition::equals('account_type', 'business')
)
],
validator: new AccountTypeValidator()
),
// Step 3: Preferences
new FormStepDefinition(
title: 'Präferenzen',
description: 'Passen Sie Ihre Einstellungen an',
fields: [
FormFieldDefinition::checkbox(
name: 'newsletter',
label: 'Newsletter abonnieren'
),
FormFieldDefinition::select(
name: 'language',
label: 'Bevorzugte Sprache',
options: [
'en' => 'English',
'de' => 'Deutsch',
'fr' => 'Français'
],
defaultValue: 'de'
)
]
)
],
submitHandler: new UserRegistrationSubmitHandler()
);
```
### 2. Validator implementieren
```php
use App\Framework\LiveComponents\FormBuilder\StepValidator;
final readonly class PersonalInfoValidator implements StepValidator
{
public function validate(array $formData): array
{
$errors = [];
if (empty($formData['first_name'] ?? '')) {
$errors['first_name'] = 'Vorname ist erforderlich';
}
if (empty($formData['last_name'] ?? '')) {
$errors['last_name'] = 'Nachname ist erforderlich';
}
if (empty($formData['email'] ?? '')) {
$errors['email'] = 'E-Mail ist erforderlich';
} elseif (!filter_var($formData['email'], FILTER_VALIDATE_EMAIL)) {
$errors['email'] = 'Ungültige E-Mail Adresse';
}
return $errors;
}
}
final readonly class AccountTypeValidator implements StepValidator
{
public function validate(array $formData): array
{
$errors = [];
if (empty($formData['account_type'] ?? '')) {
$errors['account_type'] = 'Bitte wählen Sie einen Konto-Typ';
}
// Conditional validation für Business
if (($formData['account_type'] ?? '') === 'business') {
if (empty($formData['company_name'] ?? '')) {
$errors['company_name'] = 'Firmenname ist erforderlich';
}
}
return $errors;
}
}
```
### 3. Submit Handler implementieren
```php
use App\Framework\LiveComponents\FormBuilder\FormSubmitHandler;
use App\Framework\LiveComponents\FormBuilder\SubmitResult;
final readonly class UserRegistrationSubmitHandler implements FormSubmitHandler
{
public function __construct(
private UserService $userService
) {}
public function handle(array $formData): SubmitResult
{
try {
$user = $this->userService->registerUser(
firstName: $formData['first_name'],
lastName: $formData['last_name'],
email: $formData['email'],
accountType: $formData['account_type'],
companyName: $formData['company_name'] ?? null,
newsletter: ($formData['newsletter'] ?? 'no') === 'yes',
language: $formData['language'] ?? 'de'
);
return SubmitResult::success(
message: 'Registrierung erfolgreich!',
redirectUrl: '/dashboard',
data: ['user_id' => $user->id]
);
} catch (\Exception $e) {
return SubmitResult::failure(
message: 'Registrierung fehlgeschlagen: ' . $e->getMessage()
);
}
}
}
```
### 4. Controller Setup
```php
use App\Framework\Http\Attributes\Route;
use App\Framework\Http\Method;
use App\Framework\Http\ViewResult;
use App\Framework\LiveComponents\FormBuilder\MultiStepFormComponent;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
final readonly class UserRegistrationController
{
#[Route('/register', method: Method::GET)]
public function showRegistrationForm(): ViewResult
{
// Form Definition (könnte auch aus Container kommen)
$formDefinition = $this->createUserRegistrationForm();
// Component erstellen
$component = new MultiStepFormComponent(
id: ComponentId::generate('user-registration'),
formDefinition: $formDefinition
);
return new ViewResult(
template: 'pages/register',
data: [
'registration_form' => $component
]
);
}
private function createUserRegistrationForm(): MultiStepFormDefinition
{
return new MultiStepFormDefinition(
steps: [
// ... (wie oben)
],
submitHandler: new UserRegistrationSubmitHandler($this->userService)
);
}
}
```
### 5. Template Usage
```html
<!-- pages/register.view.php -->
<div class="registration-page">
<h1>Benutzerregistrierung</h1>
<!-- LiveComponent einbinden -->
<livecomponent name="multi-step-form" data="registration_form" />
</div>
```
## Vorteile dieser Lösung
### ✅ Kein Code-Duplikat
- Nutzt bestehenden `FormBuilder` aus View-Modul
- `LiveFormBuilder` erweitert nur mit LiveComponent-Features
- Keine doppelte Field-Rendering-Logik
### ✅ Type-Safe & Framework-Compliant
- Alle Value Objects: `FormFieldDefinition`, `FormStepDefinition`, `MultiStepFormDefinition`
- Readonly Classes überall
- Enums für `FieldType`
### ✅ Deklarativ statt Imperativ
- Form-Definition rein deklarativ (kein Code für Rendering)
- Klare Trennung: Definition vs. Rendering vs. Validation vs. Submission
### ✅ Conditional Fields eingebaut
```php
FormFieldDefinition::text('company_name', 'Firma', required: true)
->showWhen(FieldCondition::equals('account_type', 'business'))
```
### ✅ Wiederverwendbare Validators
```php
final readonly class EmailValidator implements StepValidator
{
public function validate(array $formData): array
{
// Wiederverwendbare Validation-Logic
}
}
```
### ✅ Testbar
```php
// Unit Test für Validator
it('validates email format', function () {
$validator = new PersonalInfoValidator();
$errors = $validator->validate(['email' => 'invalid']);
expect($errors)->toHaveKey('email');
});
// Integration Test für Component
it('moves to next step after validation', function () {
$component = new MultiStepFormComponent(
id: ComponentId::generate('test'),
formDefinition: $this->testFormDef
);
$result = $component->nextStep([
'first_name' => 'John',
'last_name' => 'Doe',
'email' => 'john@example.com'
]);
expect($result->get('current_step'))->toBe(2);
});
```
## Erweiterungsmöglichkeiten
### Custom Field Types
```php
// Neuen FieldType hinzufügen
enum FieldType: string
{
case TEXT = 'text';
case EMAIL = 'email';
// ... existing types
case DATE = 'date';
case PHONE = 'phone';
case CURRENCY = 'currency';
}
// LiveFormBuilder erweitern
final readonly class LiveFormBuilder
{
public function addLiveDateInput(
string $name,
string $label,
?string $value = null
): self {
// Implementation
}
}
```
### Multi-Field Conditions
```php
final readonly class AndCondition implements FieldConditionContract
{
public function __construct(
private FieldCondition $left,
private FieldCondition $right
) {}
public function matches(array $formData): bool
{
return $this->left->matches($formData)
&& $this->right->matches($formData);
}
}
// Usage
FormFieldDefinition::text('special_field', 'Special')
->showWhen(
new AndCondition(
FieldCondition::equals('account_type', 'business'),
FieldCondition::equals('country', 'DE')
)
);
```
### Custom Renderers
```php
interface FieldRenderer
{
public function render(FormFieldDefinition $field, mixed $value): string;
}
final readonly class CustomTextRenderer implements FieldRenderer
{
public function render(FormFieldDefinition $field, mixed $value): string
{
// Custom rendering logic
}
}
```
## Vergleich: Vorher vs. Nachher
### ❌ Vorher (Code-Duplikat)
```php
// Hardcoded Form in Component
public function getRenderData(): ComponentRenderData
{
return new ComponentRenderData(
templatePath: 'livecomponent-dynamic-form',
data: [
'form_first_name' => $formData['first_name'] ?? '',
'form_last_name' => $formData['last_name'] ?? '',
// ... 50 weitere Zeilen hardcoded mappings
]
);
}
```
### ✅ Nachher (Wiederverwendbar)
```php
// Deklarative Definition
$formDef = new MultiStepFormDefinition(
steps: [
new FormStepDefinition(
title: 'Personal Info',
fields: [
FormFieldDefinition::text('first_name', 'First Name'),
FormFieldDefinition::text('last_name', 'Last Name')
]
)
]
);
// Generic Component - keine Anpassungen nötig!
$component = new MultiStepFormComponent(
id: ComponentId::generate('my-form'),
formDefinition: $formDef
);
```
## Zusammenfassung
Diese Integration:
- ✅ Nutzt bestehenden `FormBuilder` (keine Code-Duplizierung)
- ✅ Erweitert ihn minimal für LiveComponent-Features
- ✅ Vollständig type-safe mit Value Objects
- ✅ Framework-compliant (readonly, final, composition)
- ✅ Deklarative Form-Definitionen
- ✅ Conditional Fields eingebaut
- ✅ Wiederverwendbare Validators
- ✅ Generische Component (kein Custom-Code pro Form)
- ✅ Einfach testbar
- ✅ Leicht erweiterbar

View File

@@ -1,681 +0,0 @@
# LiveComponent Lazy Loading
**Performance-Optimization durch Viewport-basiertes Component Loading**
## Übersicht
Das Lazy Loading System lädt LiveComponents erst, wenn sie im Browser-Viewport sichtbar werden. Dies reduziert die initiale Ladezeit erheblich, besonders bei Seiten mit vielen Components.
**Key Features:**
-**Viewport-Detection** - Intersection Observer API
- 🎯 **Priority-Based Loading** - High/Normal/Low Prioritäten
- 🔄 **Progressive Loading** - Sequentielle Queue-Verarbeitung
- 📊 **Loading States** - Placeholder → Loading → Loaded
- 🧹 **Automatic Cleanup** - Memory-efficient observer management
## Performance Benefits
| Metric | Without Lazy Loading | With Lazy Loading | Improvement |
|--------|---------------------|-------------------|-------------|
| Initial Page Load | 2500ms | 800ms | **68% faster** |
| Time to Interactive | 3200ms | 1100ms | **66% faster** |
| Initial JavaScript | 450KB | 120KB | **73% smaller** |
| Components Loaded | All (20) | Visible (3-5) | **75% fewer** |
| Memory Usage | 120MB | 35MB | **71% less** |
## Quick Start
### 1. Basic Lazy Component
```html
<!-- Regular Component (loads immediately) -->
<div data-live-component="notification-center:user-123"
data-state='{"notifications": []}'
data-csrf-token="...">
<!-- Component HTML -->
</div>
<!-- Lazy Component (loads on viewport entry) -->
<div data-live-component-lazy="notification-center:user-123"
data-lazy-threshold="0.1"
data-lazy-priority="normal"
data-lazy-placeholder="Loading notifications...">
</div>
```
**Key Differences:**
- Use `data-live-component-lazy` instead of `data-live-component`
- No initial HTML needed (placeholder will be shown)
- No `data-state` needed initially (server will provide it)
### 2. Priority Levels
```html
<!-- High Priority - Loads first -->
<div data-live-component-lazy="user-stats:123"
data-lazy-priority="high"
data-lazy-placeholder="Loading user stats...">
</div>
<!-- Normal Priority - Standard loading (default) -->
<div data-live-component-lazy="activity-feed:user-123"
data-lazy-priority="normal"
data-lazy-placeholder="Loading activities...">
</div>
<!-- Low Priority - Loads last -->
<div data-live-component-lazy="recommendations:user-123"
data-lazy-priority="low"
data-lazy-placeholder="Loading recommendations...">
</div>
```
**Priority Weights:**
- `high`: 3 (Critical components, always load first)
- `normal`: 2 (Standard components, default)
- `low`: 1 (Non-critical components, load last)
## Configuration Options
### Viewport Threshold
Controls when loading triggers (0.0 = top edge, 1.0 = fully visible):
```html
<!-- Load when 10% visible (default) -->
<div data-live-component-lazy="counter:demo"
data-lazy-threshold="0.1">
</div>
<!-- Load when 50% visible -->
<div data-live-component-lazy="chart:demo"
data-lazy-threshold="0.5">
</div>
<!-- Load when fully visible -->
<div data-live-component-lazy="video-player:demo"
data-lazy-threshold="1.0">
</div>
```
### Root Margin (Pre-loading)
Global configuration in LazyComponentLoader:
```javascript
this.defaultOptions = {
rootMargin: '50px', // Load 50px before entering viewport
threshold: 0.1 // Default threshold
};
```
**Effect:** Components start loading before user scrolls to them, creating seamless experience.
### Custom Placeholder
```html
<!-- Simple text placeholder -->
<div data-live-component-lazy="notifications:123"
data-lazy-placeholder="Loading your notifications...">
</div>
<!-- No placeholder (just loading indicator) -->
<div data-live-component-lazy="stats:123">
</div>
```
## Loading States
The system automatically manages 3 loading states:
### 1. Placeholder State
Shown immediately when component is registered:
```html
<div class="livecomponent-lazy-placeholder">
<div></div>
<div>Loading notifications...</div>
</div>
```
### 2. Loading State
Shown when component enters viewport and server request starts:
```html
<div class="livecomponent-lazy-loading">
<div class="spinner"></div>
<div>Loading component...</div>
</div>
```
### 3. Loaded State
Component HTML from server replaces placeholder:
```html
<div data-live-component="notification-center:user-123" ...>
<!-- Full component HTML with actions, state, etc. -->
</div>
```
## Backend Implementation
### Controller Route
```php
#[Route('/live-component/{id}/lazy-load', method: Method::GET)]
public function handleLazyLoad(string $id, HttpRequest $request): JsonResult
{
try {
$componentId = ComponentId::fromString($id);
// Resolve component with initial state
$component = $this->componentRegistry->resolve(
$componentId,
initialData: null
);
// Render component HTML with wrapper
$html = $this->componentRegistry->renderWithWrapper($component);
// Get component state
$componentData = $component->getData();
// Generate CSRF token for component
$csrfToken = $this->componentRegistry->generateCsrfToken($componentId);
return new JsonResult([
'success' => true,
'html' => $html,
'state' => $componentData->toArray(),
'csrf_token' => $csrfToken,
'component_id' => $componentId->toString()
]);
} catch (\Exception $e) {
return new JsonResult([
'success' => false,
'error' => $e->getMessage(),
'error_code' => 'LAZY_LOAD_FAILED'
], Status::INTERNAL_SERVER_ERROR);
}
}
```
### Response Format
```json
{
"success": true,
"html": "<div data-live-component=\"notification-center:user-123\" ...>...</div>",
"state": {
"notifications": [...]
},
"csrf_token": "abc123...",
"component_id": "notification-center:user-123"
}
```
## JavaScript API
### LazyComponentLoader Class
```javascript
import { LazyComponentLoader } from './LazyComponentLoader.js';
// Create loader instance
const lazyLoader = new LazyComponentLoader(liveComponentManager);
// Initialize system (scans DOM for lazy components)
lazyLoader.init();
// Register new lazy component dynamically
lazyLoader.registerLazyComponent(element);
// Unregister lazy component
lazyLoader.unregister(element);
// Get loading statistics
const stats = lazyLoader.getStats();
console.log(stats);
// {
// total: 10,
// loaded: 3,
// loading: 1,
// pending: 6,
// queued: 2
// }
// Cleanup and destroy
lazyLoader.destroy();
```
### Events
Listen for lazy loading events:
```javascript
// Component successfully loaded
document.addEventListener('livecomponent:lazy:loaded', (e) => {
console.log('Loaded:', e.detail.componentId);
// Custom post-load logic
});
// Component load failed
document.addEventListener('livecomponent:lazy:error', (e) => {
console.error('Error:', e.detail.componentId, e.detail.error);
// Error handling, retry logic, etc.
});
```
### Manual Loading
```javascript
// Force load a specific component
const config = lazyLoader.lazyComponents.get(element);
if (config && !config.loaded) {
await lazyLoader.loadComponent(config);
}
// Process loading queue immediately (bypasses delays)
await lazyLoader.processLoadingQueue();
```
## Architecture
### Loading Flow
```
┌─────────────────┐
│ DOM Scan │
│ (init) │
└────────┬────────┘
┌─────────────────┐
│ Register Lazy │
│ Components │
└────────┬────────┘
┌─────────────────┐
│ Show Placeholder│
└────────┬────────┘
┌─────────────────┐
│ Start Observing │
│ (Intersection │
│ Observer) │
└────────┬────────┘
┌─────────────────┐ No
│ Intersecting? ├─────────► Continue Observing
└────────┬────────┘
│ Yes
┌─────────────────┐
│ Queue Component │
│ (Priority-based)│
└────────┬────────┘
┌─────────────────┐
│ Process Queue │
│ (Sequential) │
└────────┬────────┘
┌─────────────────┐
│ Show Loading │
└────────┬────────┘
┌─────────────────┐
│ Fetch from │
│ Server │
│ GET /lazy-load │
└────────┬────────┘
┌─────────────────┐
│ Replace HTML │
│ Initialize │
│ LiveComponent │
└────────┬────────┘
┌─────────────────┐
│ Stop Observing │
│ Mark as Loaded │
└─────────────────┘
```
### Priority Queue System
```javascript
// When component enters viewport
queueComponentLoad(config) {
const priorityWeight = this.getPriorityWeight(config.priority);
this.loadingQueue.push({
config,
priority: priorityWeight,
timestamp: Date.now()
});
// Sort by priority (high to low), then timestamp (early to late)
this.loadingQueue.sort((a, b) => {
if (b.priority !== a.priority) {
return b.priority - a.priority;
}
return a.timestamp - b.timestamp;
});
this.processLoadingQueue();
}
```
**Queue Order Example:**
```
Queue: [
{ priority: 3, timestamp: 1000 }, // High, entered first
{ priority: 3, timestamp: 1100 }, // High, entered second
{ priority: 2, timestamp: 900 }, // Normal, entered early
{ priority: 1, timestamp: 1200 } // Low, entered last
]
```
## Use Cases
### 1. Long Landing Pages
```html
<!-- Above the fold - immediate -->
<div data-live-component="hero:banner">...</div>
<div data-live-component="features:overview">...</div>
<!-- Below the fold - lazy -->
<div data-live-component-lazy="testimonials:featured"
data-lazy-priority="normal">
</div>
<div data-live-component-lazy="pricing:table"
data-lazy-priority="low">
</div>
<div data-live-component-lazy="faq:list"
data-lazy-priority="low">
</div>
```
### 2. Dashboard with Widgets
```html
<!-- Critical widgets - high priority -->
<div data-live-component-lazy="user-stats:123"
data-lazy-priority="high"
data-lazy-threshold="0.1">
</div>
<div data-live-component-lazy="notifications:123"
data-lazy-priority="high"
data-lazy-threshold="0.1">
</div>
<!-- Secondary widgets - normal priority -->
<div data-live-component-lazy="activity-feed:123"
data-lazy-priority="normal">
</div>
<!-- Tertiary widgets - low priority -->
<div data-live-component-lazy="recommendations:123"
data-lazy-priority="low">
</div>
```
### 3. Infinite Scroll
```html
<!-- Initial visible items -->
<div data-live-component="product-card:1">...</div>
<div data-live-component="product-card:2">...</div>
<div data-live-component="product-card:3">...</div>
<!-- Lazy load next batch -->
<div data-live-component-lazy="product-card:4"
data-lazy-threshold="0.5">
</div>
<div data-live-component-lazy="product-card:5"
data-lazy-threshold="0.5">
</div>
```
### 4. Tab Panels
```html
<div class="tabs">
<!-- Active tab - immediate -->
<div class="tab-panel active">
<div data-live-component="profile:user-123">...</div>
</div>
<!-- Hidden tabs - lazy load when shown -->
<div class="tab-panel">
<div data-live-component-lazy="settings:user-123"
data-lazy-priority="normal">
</div>
</div>
<div class="tab-panel">
<div data-live-component-lazy="activity-history:user-123"
data-lazy-priority="low">
</div>
</div>
</div>
```
## Best Practices
### ✅ DO
**Use lazy loading for:**
- Below-the-fold components
- Secondary/tertiary content
- Heavy components (charts, tables, media)
- Non-critical features
- Infrequently accessed sections
**Priority Guidelines:**
- `high`: User-specific data, real-time updates
- `normal`: Standard content, common features
- `low`: Recommendations, suggestions, ads
**Threshold Guidelines:**
- `0.1`: Standard (load early for smooth UX)
- `0.5`: Conservative (load when half-visible)
- `1.0`: Aggressive (only when fully visible)
### ❌ DON'T
**Avoid lazy loading for:**
- Above-the-fold content
- Critical user interactions
- SEO-important content
- Small/lightweight components
**Anti-patterns:**
- Setting all components to `high` priority
- Using threshold `1.0` for everything
- Lazy loading components user immediately needs
- Over-complicating with too many priority levels
## Performance Optimization
### Server-Side
```php
// Cache component HTML for repeated lazy loads
public function handleLazyLoad(string $id): JsonResult
{
$cacheKey = "lazy_component_{$id}";
return $this->cache->remember($cacheKey, function() use ($id) {
$component = $this->componentRegistry->resolve(
ComponentId::fromString($id)
);
return new JsonResult([
'success' => true,
'html' => $this->componentRegistry->renderWithWrapper($component),
// ... other data
]);
}, Duration::fromMinutes(5));
}
```
### Client-Side
```javascript
// Adjust root margin for faster pre-loading
this.defaultOptions = {
rootMargin: '200px', // Start loading 200px early
threshold: 0.1
};
// Batch multiple components entering viewport simultaneously
// (Already implemented in processLoadingQueue)
```
## Troubleshooting
### Component doesn't load
**Check:**
1. `data-live-component-lazy` attribute present?
2. LazyLoader initialized? (`window.LiveComponent.lazyLoader`)
3. Element visible in viewport?
4. Network request succeeding? (check DevTools)
5. Server route `/live-component/{id}/lazy-load` working?
**Debug:**
```javascript
const stats = window.LiveComponent.lazyLoader.getStats();
console.log(stats); // Check pending/loading/loaded counts
// Check if component is registered
const config = window.LiveComponent.lazyLoader.lazyComponents.get(element);
console.log(config);
```
### Component loads too late/early
**Adjust threshold:**
```html
<!-- Load earlier (when 10% visible instead of 50%) -->
<div data-live-component-lazy="..."
data-lazy-threshold="0.1">
</div>
```
**Adjust root margin:**
```javascript
// Global configuration
this.defaultOptions.rootMargin = '100px'; // Load 100px before viewport
```
### Priority not working
**Check queue:**
```javascript
console.log(window.LiveComponent.lazyLoader.loadingQueue);
// Should be sorted by priority (high to low)
```
**Verify priority value:**
```html
<div data-lazy-priority="high"> ✅ Correct
<div data-lazy-priority="High"> ❌ Case-sensitive!
<div data-lazy-priority="urgent"> ❌ Only high/normal/low
```
## Testing
### Manual Testing
```bash
# Open test page in browser
php tests/debug/test-lazy-loading.php
# Navigate to http://localhost/tests/debug/test-lazy-loading.php
```
**Test Checklist:**
- [ ] Placeholders shown immediately
- [ ] Components load when scrolling into view
- [ ] High priority loads before low priority
- [ ] Loading spinner appears briefly
- [ ] Stats panel updates correctly
- [ ] Console logs show loading sequence
- [ ] No console errors
### Automated Testing
```javascript
describe('LazyComponentLoader', () => {
it('registers lazy components', () => {
const loader = new LazyComponentLoader(mockManager);
loader.init();
const lazyElements = document.querySelectorAll('[data-live-component-lazy]');
expect(loader.lazyComponents.size).toBe(lazyElements.length);
});
it('loads component on intersection', async () => {
const loader = new LazyComponentLoader(mockManager);
loader.init();
// Simulate intersection
const entry = { isIntersecting: true, target: lazyElement };
loader.handleIntersection([entry]);
// Wait for load
await new Promise(resolve => setTimeout(resolve, 100));
const config = loader.lazyComponents.get(lazyElement);
expect(config.loaded).toBe(true);
});
it('respects priority ordering', () => {
const loader = new LazyComponentLoader(mockManager);
loader.queueComponentLoad({ priority: 'low', ...lowConfig });
loader.queueComponentLoad({ priority: 'high', ...highConfig });
loader.queueComponentLoad({ priority: 'normal', ...normalConfig });
expect(loader.loadingQueue[0].config).toBe(highConfig);
expect(loader.loadingQueue[1].config).toBe(normalConfig);
expect(loader.loadingQueue[2].config).toBe(lowConfig);
});
});
```
## Summary
**Lazy Loading provides:**
-**68% faster** initial page load
-**73% smaller** initial JavaScript bundle
-**75% fewer** components loaded initially
-**71% less** memory usage
-**Seamless UX** with priority-based loading
-**Easy integration** with minimal code changes
**When to use:**
- Pages with 10+ components
- Below-the-fold content
- Heavy components (charts, tables, media)
- Secondary/tertiary features
**Implementation effort:****Low** - Just change `data-live-component` to `data-live-component-lazy`

View File

@@ -1,572 +0,0 @@
# LiveComponent Lifecycle Hooks
Dokumentation des Lifecycle Hook Systems für LiveComponents.
## Übersicht
Das Lifecycle Hook System bietet opt-in Callbacks für wichtige Lebenszyklus-Ereignisse einer LiveComponent:
- **`onMount()`**: Aufgerufen einmalig nach erster Erstellung (server-side)
- **`onUpdate()`**: Aufgerufen nach jeder State-Änderung (server-side)
- **`onDestroy()`**: Aufgerufen vor Entfernung aus DOM (client-side mit server-call)
## LifecycleAware Interface
Components müssen das `LifecycleAware` Interface implementieren um Lifecycle Hooks zu nutzen:
```php
use App\Framework\LiveComponents\Contracts\LifecycleAware;
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
#[LiveComponent(name: 'example')]
final readonly class ExampleComponent
implements LiveComponentContract, LifecycleAware
{
public function onMount(): void
{
// Initialization logic
}
public function onUpdate(): void
{
// React to state changes
}
public function onDestroy(): void
{
// Cleanup logic
}
}
```
**Wichtig**: Das Interface ist optional - Components die es nicht implementieren funktionieren weiterhin normal.
## Lifecycle Flow
```
1. Component Creation (Initial Page Load)
onMount() - called once
2. User Action (Button Click, Input Change)
Action Execution
State Validation
onUpdate() - called after state change
3. Component Removal (Navigate Away, Remove Element)
onDestroy() - called before removal
Client-Side Cleanup
```
## Hook Details
### onMount()
**Wann aufgerufen**: Einmalig nach erster Component-Erstellung (server-side)
**Trigger**: ComponentRegistry ruft Hook auf wenn `$state === null` (initial creation ohne Re-Hydration)
**Use Cases**:
- Timer oder Intervals starten
- Datenbank-Connections öffnen
- Events oder WebSockets subscriben
- Externe Libraries initialisieren
- Component Mount für Analytics loggen
- Background Processes starten
**Beispiel**:
```php
public function onMount(): void
{
// Log component initialization
error_log("TimerComponent mounted: {$this->id->toString()}");
// Initialize external resources
$this->cache->remember("timer_{$this->id}", fn() => time());
// Subscribe to events
$this->eventBus->subscribe('timer:tick', $this->handleTick(...));
}
```
**Wichtig**:
- Wird NUR bei initialer Erstellung aufgerufen (kein `$state` Parameter)
- Wird NICHT aufgerufen bei Re-Hydration mit existierendem State
- Fehler werden geloggt aber brechen Component-Erstellung nicht ab
### onUpdate()
**Wann aufgerufen**: Nach jeder Action die State aktualisiert (server-side)
**Trigger**: LiveComponentHandler ruft Hook nach State-Validierung auf in `handle()` und `handleUpload()` Methoden
**Use Cases**:
- Auf State-Änderungen reagieren
- Externe Ressourcen aktualisieren
- Mit externen Services synchronisieren
- State-Konsistenz validieren
- State-Transitions loggen
- Cache invalidieren
**Beispiel**:
```php
public function onUpdate(): void
{
$seconds = $this->data->get('seconds', 0);
$isRunning = $this->data->get('isRunning', false);
// Log state transitions
error_log("Timer updated: {$seconds}s, running: " . ($isRunning ? 'yes' : 'no'));
// Update external resources
if ($isRunning) {
$this->cache->set("timer_{$this->id}_last_active", time());
}
// Trigger side effects
if ($seconds >= 60) {
$this->eventBus->dispatch(new TimerReachedMinuteEvent($this->id));
}
}
```
**Wichtig**:
- Wird nach JEDER Action aufgerufen (auch wenn State unverändert bleibt)
- Wird nach State-Validierung aufgerufen (State ist garantiert valid)
- Fehler werden geloggt aber brechen Action nicht ab
### onDestroy()
**Wann aufgerufen**: Vor Component-Entfernung aus DOM (client-side mit server-call)
**Trigger**:
1. Client-Side MutationObserver erkennt DOM-Entfernung
2. JavaScript ruft `/live-component/{id}/destroy` Endpunkt auf
3. Server-Side Controller ruft `onDestroy()` Hook auf
**Use Cases**:
- Timers und Intervals stoppen
- Datenbank-Connections schließen
- Events unsubscriben
- Externe Ressourcen aufräumen
- State vor Removal persistieren
- Component-Entfernung loggen
**Beispiel**:
```php
public function onDestroy(): void
{
// Log component removal
error_log("TimerComponent destroyed: {$this->id->toString()}");
// Persist final state
$this->storage->save("timer_{$this->id}_final_state", $this->data->toArray());
// Cleanup subscriptions
$this->eventBus->unsubscribe('timer:tick');
// Close connections
$this->connection?->close();
}
```
**Wichtig**:
- Best-effort delivery via `navigator.sendBeacon` oder `fetch`
- Fehler brechen Destroy nicht ab (Component wird trotzdem entfernt)
- Kann fehlschlagen bei Page Unload (Browser beendet Request)
- Nur für kritisches Cleanup verwenden
## Client-Side Integration
### MutationObserver Setup
Der LiveComponentManager JavaScript-Code überwacht automatisch DOM-Entfernungen:
```javascript
setupLifecycleObserver(element, componentId) {
const observer = new MutationObserver((mutations) => {
if (!document.contains(element)) {
this.callDestroyHook(componentId);
observer.disconnect();
}
});
if (element.parentNode) {
observer.observe(element.parentNode, {
childList: true,
subtree: false
});
}
config.observer = observer;
}
```
### Server-Call mit navigator.sendBeacon
Best-effort delivery auch während Page Unload:
```javascript
async callDestroyHook(componentId) {
const payload = JSON.stringify({
state: currentState,
_csrf_token: csrfToken
});
const url = `/live-component/${componentId}/destroy`;
const blob = new Blob([payload], { type: 'application/json' });
// Try sendBeacon first for page unload reliability
if (navigator.sendBeacon && navigator.sendBeacon(url, blob)) {
console.log(`onDestroy() called via sendBeacon`);
} else {
// Fallback to fetch for normal removal
await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: payload
});
}
// Local cleanup
this.destroy(componentId);
}
```
## Error Handling
Alle Lifecycle Hooks haben robustes Error Handling:
```php
// In LiveComponentHandler
try {
$component->onMount();
} catch (\Throwable $e) {
// Log error but don't fail component creation
error_log("Lifecycle hook onMount() failed for " . get_class($component) . ": " . $e->getMessage());
}
```
**Best Practices**:
- Hooks sollten nie kritische Exceptions werfen
- Internes Error Handling in Hook-Implementierungen
- Logging für Debugging und Monitoring
- Graceful Degradation bei Hook-Fehlern
## Timer Component Beispiel
Vollständiges Beispiel einer Component mit allen Lifecycle Hooks:
```php
#[LiveComponent(name: 'timer')]
final readonly class TimerComponent
implements LiveComponentContract, LifecycleAware
{
private ComponentData $data;
public function __construct(
private ComponentId $id,
?ComponentData $initialData = null
) {
$this->data = $initialData ?? ComponentData::fromArray([
'seconds' => 0,
'isRunning' => false,
'startedAt' => null,
'logs' => []
]);
}
// Lifecycle Hooks
public function onMount(): void
{
error_log("TimerComponent mounted: {$this->id->toString()}");
$this->addLog('Component mounted - Timer ready');
}
public function onUpdate(): void
{
$seconds = $this->data->get('seconds', 0);
$isRunning = $this->data->get('isRunning', false);
error_log("TimerComponent updated: {$seconds}s, running: " .
($isRunning ? 'yes' : 'no'));
}
public function onDestroy(): void
{
error_log("TimerComponent destroyed: {$this->id->toString()}");
$this->addLog('Component destroyed - Cleanup completed');
}
// Actions
public function start(): ComponentData
{
$state = $this->data->toArray();
$state['isRunning'] = true;
$state['startedAt'] = time();
$this->addLog('Timer started', $state);
return ComponentData::fromArray($state);
}
public function stop(): ComponentData
{
$state = $this->data->toArray();
$state['isRunning'] = false;
$this->addLog('Timer stopped', $state);
return ComponentData::fromArray($state);
}
public function tick(): ComponentData
{
$state = $this->data->toArray();
if ($state['isRunning']) {
$state['seconds'] = ($state['seconds'] ?? 0) + 1;
}
return ComponentData::fromArray($state);
}
// Standard Interface Methods
public function getId(): ComponentId
{
return $this->id;
}
public function getData(): ComponentData
{
return $this->data;
}
public function getRenderData(): RenderData
{
return new RenderData(
templatePath: 'livecomponent-timer',
data: [
'seconds' => $this->data->get('seconds', 0),
'isRunning' => $this->data->get('isRunning', false),
'formattedTime' => $this->formatTime($this->data->get('seconds', 0)),
'logs' => $this->data->get('logs', [])
]
);
}
// Helper Methods
private function formatTime(int $seconds): string
{
$minutes = floor($seconds / 60);
$remainingSeconds = $seconds % 60;
return sprintf('%02d:%02d', $minutes, $remainingSeconds);
}
private function addLog(string $message, array &$state = null): void
{
if ($state === null) {
$state = $this->data->toArray();
}
$logs = $state['logs'] ?? [];
$logs[] = [
'time' => date('H:i:s'),
'message' => $message
];
// Keep only last 5 logs
$state['logs'] = array_slice($logs, -5);
}
}
```
## Best Practices
### 1. Minimal onMount() Logic
```php
// ✅ Good: Light initialization
public function onMount(): void
{
error_log("Component {$this->id} mounted");
$this->cache->set("mounted_{$this->id}", time(), 3600);
}
// ❌ Bad: Heavy computation
public function onMount(): void
{
$this->processAllData(); // Slow!
$this->generateReport(); // Blocks creation!
}
```
### 2. onUpdate() Performance
```php
// ✅ Good: Quick updates
public function onUpdate(): void
{
if ($this->data->get('isActive')) {
$this->cache->touch("active_{$this->id}");
}
}
// ❌ Bad: Heavy synchronous operations
public function onUpdate(): void
{
$this->syncWithExternalAPI(); // Slow!
$this->recalculateEverything(); // Blocks action!
}
```
### 3. Critical Cleanup in onDestroy()
```php
// ✅ Good: Essential cleanup
public function onDestroy(): void
{
$this->connection?->close();
$this->persistState();
}
// ❌ Bad: Nice-to-have cleanup
public function onDestroy(): void
{
$this->updateStatistics(); // May fail during page unload
$this->sendAnalytics(); // Not guaranteed to complete
}
```
### 4. Error Handling
```php
// ✅ Good: Internal error handling
public function onMount(): void
{
try {
$this->externalService->connect();
} catch (\Exception $e) {
error_log("Connection failed: " . $e->getMessage());
// Continue with degraded functionality
}
}
// ❌ Bad: Letting exceptions bubble up
public function onMount(): void
{
$this->externalService->connect(); // May throw!
// Breaks component creation if it fails
}
```
### 5. Idempotency
Hooks sollten idempotent sein (mehrfach ausführbar ohne Seiteneffekte):
```php
// ✅ Good: Idempotent
public function onMount(): void
{
if (!$this->cache->has("initialized_{$this->id}")) {
$this->cache->set("initialized_{$this->id}", true);
$this->initialize();
}
}
// ❌ Bad: Side effects on every call
public function onMount(): void
{
$this->counter++; // Breaks on re-hydration!
}
```
## Testing Lifecycle Hooks
```php
use Tests\Framework\LiveComponents\ComponentTestCase;
uses(ComponentTestCase::class);
beforeEach(function () {
$this->setUpComponentTest();
});
it('calls onMount on initial creation', function () {
$mountCalled = false;
$component = new class (...) implements LifecycleAware {
public function onMount(): void {
$this->mountCalled = true;
}
};
// Initial creation (no state)
$registry = $this->container->get(ComponentRegistry::class);
$resolved = $registry->resolve($component->getId(), null);
expect($mountCalled)->toBeTrue();
});
it('calls onUpdate after action', function () {
$updateCalled = false;
$component = new class (...) implements LifecycleAware {
public function onUpdate(): void {
$this->updateCalled = true;
}
};
$handler = $this->container->get(LiveComponentHandler::class);
$params = ActionParameters::fromArray([
'_csrf_token' => $this->generateCsrfToken($component)
]);
$handler->handle($component, 'action', $params);
expect($updateCalled)->toBeTrue();
});
```
## Demo
Eine vollständige Demo ist verfügbar unter:
- **URL**: https://localhost/livecomponent-timer
- **Component**: `src/Application/LiveComponents/Timer/TimerComponent.php`
- **Template**: `src/Framework/View/templates/livecomponent-timer.view.php`
Die Demo zeigt:
- Alle drei Lifecycle Hooks in Aktion
- Client-Side Tick Interval Management
- Lifecycle Log-Tracking
- Browser Console Logging für Hook-Aufrufe
## Zusammenfassung
Das Lifecycle Hook System bietet:
-**Opt-in Design**: Components können hooks nutzen ohne Breaking Changes
-**Server-Side Hooks**: onMount() und onUpdate() mit voller Backend-Integration
-**Client-Side Cleanup**: onDestroy() mit MutationObserver und sendBeacon
-**Robustes Error Handling**: Fehler brechen Lifecycle nicht ab
-**Best-Effort Delivery**: onDestroy() versucht Server-Call auch bei Page Unload
-**Framework-Integration**: Nahtlos integriert mit ComponentRegistry und LiveComponentHandler
**Use Cases**:
- Resource Management (Connections, Timers, Subscriptions)
- State Persistence und Synchronization
- Analytics und Logging
- External Service Integration
- Performance Monitoring

View File

@@ -1,717 +0,0 @@
# LiveComponents Nested Components System
Comprehensive guide for building nested component hierarchies with parent-child relationships, event bubbling, and state synchronization.
## Overview
The Nested Components System enables complex UI compositions through parent-child component relationships. Parents can manage global state while children handle localized behavior, with events bubbling up for coordination.
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Parent Component │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Global State Management │ │
│ │ • Manages list of items │ │
│ │ • Provides data to children │ │
│ │ • Handles child events │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────┴────────────┐ │
│ │ │ │
│ ┌─────────▼────────┐ ┌──────────▼────────┐ │
│ │ Child Component │ │ Child Component │ │
│ │ • Local State │ │ • Local State │ │
│ │ • Dispatch Events│ │ • Dispatch Events│ │
│ └──────────────────┘ └───────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## Core Concepts
### 1. Component Hierarchy
**ComponentHierarchy Value Object** - Represents parent-child relationships:
```php
use App\Framework\LiveComponents\ValueObjects\ComponentHierarchy;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
// Root component (no parent)
$rootHierarchy = ComponentHierarchy::root();
// depth=0, path=[]
// First-level child
$childHierarchy = ComponentHierarchy::fromParent(
parentId: ComponentId::fromString('parent:main'),
childId: ComponentId::fromString('child:1')
);
// depth=1, path=['parent:main', 'child:1']
// Add another level
$grandchildHierarchy = $childHierarchy->withChild(
ComponentId::fromString('grandchild:1')
);
// depth=2, path=['parent:main', 'child:1', 'grandchild:1']
```
**Hierarchy Queries:**
```php
$hierarchy->isRoot(); // true if no parent
$hierarchy->isChild(); // true if has parent
$hierarchy->getLevel(); // nesting depth (0, 1, 2, ...)
$hierarchy->isDescendantOf($componentId); // check ancestry
```
### 2. NestedComponentManager
**Server-Side Hierarchy Management:**
```php
use App\Framework\LiveComponents\NestedComponentManager;
$manager = new NestedComponentManager();
// Register root component
$parentId = ComponentId::fromString('todo-list:main');
$manager->registerHierarchy($parentId, ComponentHierarchy::root());
// Register child
$childId = ComponentId::fromString('todo-item:1');
$childHierarchy = ComponentHierarchy::fromParent($parentId, $childId);
$manager->registerHierarchy($childId, $childHierarchy);
// Query hierarchy
$manager->hasChildren($parentId); // true
$manager->getChildIds($parentId); // [ComponentId('todo-item:1')]
$manager->getParentId($childId); // ComponentId('todo-list:main')
$manager->isRoot($parentId); // true
$manager->getDepth($childId); // 1
// Get all ancestors/descendants
$ancestors = $manager->getAncestors($childId); // [parentId]
$descendants = $manager->getDescendants($parentId); // [childId]
// Statistics
$stats = $manager->getStats();
// ['total_components' => 2, 'root_components' => 1, ...]
```
### 3. SupportsNesting Interface
**Parent components must implement this interface:**
```php
use App\Framework\LiveComponents\Contracts\SupportsNesting;
interface SupportsNesting
{
/**
* Get list of child component IDs
*/
public function getChildComponents(): array;
/**
* Handle event from child component
*
* @return bool Return false to stop event bubbling, true to continue
*/
public function onChildEvent(ComponentId $childId, string $eventName, array $payload): bool;
/**
* Validate child component compatibility
*/
public function canHaveChild(ComponentId $childId): bool;
}
```
### 4. Event Bubbling
**Events flow from child to parent:**
```
┌─────────────────────────┐
│ Child Component │
│ • User clicks button │
│ • Dispatches event │
└───────────┬─────────────┘
│ Event Bubbles Up
┌─────────────────────────┐
│ Parent Component │
│ • Receives event │
│ • Updates state │
│ • Re-renders children │
└─────────────────────────┘
```
**Event Dispatcher:**
```php
use App\Framework\LiveComponents\NestedComponentEventDispatcher;
$dispatcher = new NestedComponentEventDispatcher();
// Child dispatches event
$dispatcher->dispatch(
componentId: ComponentId::fromString('todo-item:1'),
eventName: 'todo-completed',
payload: [
'todo_id' => '1',
'completed' => true
]
);
// Check dispatched events
$dispatcher->hasEvents(); // true
$dispatcher->count(); // 1
$events = $dispatcher->getEvents();
```
## Implementation Guide
### Creating a Parent Component
**1. Implement SupportsNesting:**
```php
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
use App\Framework\LiveComponents\Contracts\SupportsNesting;
use App\Framework\LiveComponents\Attributes\LiveComponent;
#[LiveComponent('todo-list')]
final readonly class TodoListComponent implements LiveComponentContract, SupportsNesting
{
private ComponentId $id;
private TodoListState $state;
public function __construct(
ComponentId $id,
?ComponentData $initialData = null,
array $todos = []
) {
$this->id = $id;
$this->state = $initialData
? TodoListState::fromComponentData($initialData)
: new TodoListState(todos: $todos);
}
// LiveComponentContract methods
public function getId(): ComponentId { return $this->id; }
public function getData(): ComponentData { return $this->state->toComponentData(); }
public function getRenderData(): ComponentRenderData { /* ... */ }
// SupportsNesting methods
public function getChildComponents(): array
{
// Return array of child component IDs
$childIds = [];
foreach ($this->state->todos as $todo) {
$childIds[] = "todo-item:{$todo['id']}";
}
return $childIds;
}
public function onChildEvent(ComponentId $childId, string $eventName, array $payload): bool
{
// Handle events from children
match ($eventName) {
'todo-completed' => $this->handleTodoCompleted($payload),
'todo-deleted' => $this->handleTodoDeleted($payload),
default => null
};
return true; // Continue bubbling
}
public function canHaveChild(ComponentId $childId): bool
{
// Only accept TodoItem components
return str_starts_with($childId->name, 'todo-item');
}
private function handleTodoCompleted(array $payload): void
{
$todoId = $payload['todo_id'];
$completed = $payload['completed'];
// Log or trigger side effects
error_log("Todo {$todoId} marked as " . ($completed ? 'completed' : 'active'));
// Note: State updates happen through Actions, not event handlers
// Event handlers are for logging, analytics, side effects
}
}
```
**2. Create Parent State:**
```php
final readonly class TodoListState
{
public function __construct(
public array $todos = [],
public string $filter = 'all'
) {}
public static function fromComponentData(ComponentData $data): self
{
$array = $data->toArray();
return new self(
todos: $array['todos'] ?? [],
filter: $array['filter'] ?? 'all'
);
}
public function toComponentData(): ComponentData
{
return ComponentData::fromArray([
'todos' => $this->todos,
'filter' => $this->filter
]);
}
public function withTodoAdded(array $todo): self
{
return new self(
todos: [...$this->todos, $todo],
filter: $this->filter
);
}
// More transformation methods...
}
```
**3. Create Parent Template:**
```html
<!-- todo-list.view.php -->
<div class="todo-list">
<!-- Parent UI -->
<h2>My Todos ({total_count})</h2>
<!-- Child Components -->
<for items="todos" as="todo">
<div
data-live-component="todo-item:{todo.id}"
data-parent-component="{component_id}"
data-nesting-depth="1"
>
<!-- TodoItemComponent renders here -->
</div>
</for>
</div>
```
### Creating a Child Component
**1. Implement Component with Event Dispatcher:**
```php
use App\Framework\LiveComponents\NestedComponentEventDispatcher;
#[LiveComponent('todo-item')]
final readonly class TodoItemComponent implements LiveComponentContract
{
private ComponentId $id;
private TodoItemState $state;
public function __construct(
ComponentId $id,
private NestedComponentEventDispatcher $eventDispatcher,
?ComponentData $initialData = null,
?array $todoData = null
) {
$this->id = $id;
$this->state = $initialData
? TodoItemState::fromComponentData($initialData)
: TodoItemState::fromTodoArray($todoData ?? []);
}
#[Action]
public function toggle(): ComponentData
{
$newState = $this->state->withToggled();
// Dispatch event to parent
$this->eventDispatcher->dispatch(
componentId: $this->id,
eventName: 'todo-completed',
payload: [
'todo_id' => $this->state->id,
'completed' => $newState->completed
]
);
return $newState->toComponentData();
}
#[Action]
public function delete(): ComponentData
{
// Dispatch delete event to parent
$this->eventDispatcher->dispatch(
componentId: $this->id,
eventName: 'todo-deleted',
payload: ['todo_id' => $this->state->id]
);
return $this->state->toComponentData();
}
}
```
**2. Create Child State:**
```php
final readonly class TodoItemState
{
public function __construct(
public string $id,
public string $title,
public bool $completed = false,
public int $createdAt = 0
) {}
public static function fromTodoArray(array $todo): self
{
return new self(
id: $todo['id'] ?? '',
title: $todo['title'] ?? '',
completed: $todo['completed'] ?? false,
createdAt: $todo['created_at'] ?? time()
);
}
public function withToggled(): self
{
return new self(
id: $this->id,
title: $this->title,
completed: !$this->completed,
createdAt: $this->createdAt
);
}
}
```
**3. Create Child Template:**
```html
<!-- todo-item.view.php -->
<div class="todo-item {completed|then:todo-item--completed}">
<button data-livecomponent-action="toggle">
<if condition="completed"></if>
</button>
<div class="todo-item__title">{title}</div>
<button data-livecomponent-action="delete"></button>
</div>
```
## Client-Side Integration
**Automatic Initialization:**
```javascript
// NestedComponentHandler automatically initializes with LiveComponentManager
import { LiveComponentManager } from './livecomponent/index.js';
// Scans DOM for nested components
const nestedHandler = liveComponentManager.nestedHandler;
// Get hierarchy info
const parentId = nestedHandler.getParentId('todo-item:1'); // 'todo-list:main'
const childIds = nestedHandler.getChildIds('todo-list:main'); // ['todo-item:1', ...]
// Event bubbling
nestedHandler.bubbleEvent('todo-item:1', 'todo-completed', {
todo_id: '1',
completed: true
});
// Statistics
const stats = nestedHandler.getStats();
// { total_components: 5, root_components: 1, max_nesting_depth: 2, ... }
```
## Best Practices
### 1. State Management
**✅ Parent owns the data:**
```php
// Parent manages list
private TodoListState $state; // Contains all todos
// Child manages display state only
private TodoItemState $state; // id, title, completed, isEditing
```
**❌ Don't duplicate state:**
```php
// Bad: Both parent and child store todo data
// This leads to synchronization issues
```
### 2. Event Handling
**✅ Use event handlers for side effects:**
```php
public function onChildEvent(ComponentId $childId, string $eventName, array $payload): bool
{
// ✅ Logging
error_log("Child event: {$eventName}");
// ✅ Analytics
$this->analytics->track($eventName, $payload);
// ✅ External system updates
$this->cache->invalidate($payload['todo_id']);
return true; // Continue bubbling
}
```
**❌ Don't modify state in event handlers:**
```php
// ❌ Bad: Event handlers shouldn't modify component state
// State changes happen through Actions that return new ComponentData
```
### 3. Child Compatibility
**✅ Validate child types:**
```php
public function canHaveChild(ComponentId $childId): bool
{
// Only accept specific component types
return str_starts_with($childId->name, 'todo-item');
}
```
### 4. Circular Dependencies
**✅ Framework automatically prevents:**
```php
// This will throw InvalidArgumentException:
$manager->registerHierarchy($componentId, $hierarchy);
// "Circular dependency detected: Component cannot be its own ancestor"
```
## Performance Considerations
### Hierarchy Depth
- **Recommended:** Max 3-4 levels deep
- **Reason:** Each level adds overhead for event bubbling
- **Alternative:** Flatten hierarchy when possible
### Event Bubbling
- **Cost:** O(depth) for each event
- **Optimization:** Stop bubbling early when not needed
- **Pattern:** Return `false` from `onChildEvent()` to stop
```php
public function onChildEvent(ComponentId $childId, string $eventName, array $payload): bool
{
if ($eventName === 'internal-event') {
// Handle locally, don't bubble further
return false;
}
// Let other events bubble
return true;
}
```
### State Synchronization
- **Pattern:** Parent as single source of truth
- **Benefit:** Avoids synchronization bugs
- **Trade-off:** More re-renders, but simpler logic
## Testing
### Unit Tests
```php
describe('NestedComponentManager', function () {
it('tracks parent-child relationships', function () {
$manager = new NestedComponentManager();
$parentId = ComponentId::fromString('parent:1');
$childId = ComponentId::fromString('child:1');
$manager->registerHierarchy($parentId, ComponentHierarchy::root());
$manager->registerHierarchy(
$childId,
ComponentHierarchy::fromParent($parentId, $childId)
);
expect($manager->hasChildren($parentId))->toBeTrue();
expect($manager->getParentId($childId))->toEqual($parentId);
});
it('prevents circular dependencies', function () {
$manager = new NestedComponentManager();
$id = ComponentId::fromString('self:1');
expect(fn() => $manager->registerHierarchy(
$id,
ComponentHierarchy::fromParent($id, $id)
))->toThrow(InvalidArgumentException::class);
});
});
```
### Integration Tests
```php
describe('TodoList with nested TodoItems', function () {
it('handles child events', function () {
$todoList = new TodoListComponent(
id: ComponentId::fromString('todo-list:test'),
todos: [
['id' => '1', 'title' => 'Test', 'completed' => false]
]
);
$childId = ComponentId::fromString('todo-item:1');
// Simulate child event
$result = $todoList->onChildEvent(
$childId,
'todo-completed',
['todo_id' => '1', 'completed' => true]
);
expect($result)->toBeTrue(); // Event bubbled successfully
});
});
```
## Troubleshooting
### Problem: Children not rendering
**Cause:** Missing `data-parent-component` attribute
**Solution:**
```html
<!-- ✅ Correct -->
<div
data-live-component="child:1"
data-parent-component="parent:main"
data-nesting-depth="1"
>
</div>
```
### Problem: Events not bubbling
**Cause:** Wrong ComponentId or event name
**Solution:**
```php
// ✅ Use exact component ID
$this->eventDispatcher->dispatch(
componentId: $this->id, // ✅ Correct: use component's own ID
eventName: 'todo-completed',
payload: [...]
);
```
### Problem: Circular dependency error
**Cause:** Component trying to be its own ancestor
**Solution:**
```php
// ❌ Wrong: Same component as parent and child
$hierarchy = ComponentHierarchy::fromParent($sameId, $sameId);
// ✅ Correct: Different components
$hierarchy = ComponentHierarchy::fromParent($parentId, $childId);
```
## Advanced Patterns
### Multi-Level Nesting
```php
// Grandparent → Parent → Child
$grandparent = ComponentHierarchy::root();
$parent = ComponentHierarchy::fromParent(
ComponentId::fromString('grandparent:1'),
ComponentId::fromString('parent:1')
);
$child = $parent->withChild(
ComponentId::fromString('child:1')
);
// depth=2, path=['grandparent:1', 'parent:1', 'child:1']
```
### Conditional Children
```php
public function getChildComponents(): array
{
// Only show children if filter matches
$filteredTodos = $this->state->getFilteredTodos();
return array_map(
fn($todo) => "todo-item:{$todo['id']}",
$filteredTodos
);
}
```
### Dynamic Child Addition
```php
#[Action]
public function addTodo(string $title): ComponentData
{
$newTodo = [
'id' => uniqid('todo_', true),
'title' => $title,
'completed' => false
];
// State includes new todo
$newState = $this->state->withTodoAdded($newTodo);
// Framework automatically creates child component
// based on getChildComponents() result
return $newState->toComponentData();
}
```
## Summary
**Nested Components enable:**
- ✅ Complex UI compositions
- ✅ Parent-child communication via events
- ✅ Hierarchical state management
- ✅ Reusable component patterns
- ✅ Type-safe relationships
**Key Classes:**
- `ComponentHierarchy` - Relationship value object
- `NestedComponentManager` - Server-side hierarchy
- `NestedComponentHandler` - Client-side hierarchy
- `NestedComponentEventDispatcher` - Event bubbling
- `SupportsNesting` - Parent component interface
**Next Steps:**
- Implement Slot System for flexible composition
- Add SSE integration for real-time updates
- Explore advanced caching strategies

View File

@@ -1,223 +0,0 @@
# LiveComponent Security Model
## CSRF-Schutz für LiveComponents
### Frage: Sollten wir CSRF grundsätzlich für LiveComponents deaktivieren?
**Antwort: Ja, aber mit alternativen Sicherheitsmaßnahmen.**
### Warum CSRF-Deaktivierung bei LiveComponents sinnvoll ist
#### 1. **Technische Inkompatibilität**
- LiveComponents senden **State per JSON**, nicht als Form-Data
- Traditionelle CSRF-Tokens in `<form>`-Elementen funktionieren nicht
- AJAX-Requests benötigen andere Token-Delivery-Mechanismen
- Token-Rotation würde LiveComponent-State invalidieren
#### 2. **Architekturelle Gründe**
- **Stateless Component Model**: Jeder Request enthält vollständigen State
- **Component-ID als Identifier**: Komponenten sind durch eindeutige IDs identifiziert
- **Action-basierte Security**: Actions werden explizit auf Component-Ebene validiert
- **Version Tracking**: Concurrent Update Detection durch Version-Nummern
#### 3. **Alternative Sicherheitsmaßnahmen**
LiveComponents haben ein **eigenes Sicherheitsmodell**:
```php
// ComponentAction mit Validierung
final readonly class ComponentAction
{
public function __construct(
public string $componentId, // Eindeutige Component-ID
public string $method, // Explizite Action-Methode
public array $params, // Validierte Parameter
public int $version // Concurrent Update Detection
) {}
}
```
### Implementierte Security-Layer für LiveComponents
#### 1. **Origin Validation**
```php
// SameSite Cookies + Origin Header Check
if ($request->headers->get('Origin') !== $expectedOrigin) {
throw new SecurityException('Invalid origin');
}
```
#### 2. **X-Requested-With Header**
```php
// AJAX-Request Verification
if ($request->headers->get('X-Requested-With') !== 'XMLHttpRequest') {
throw new SecurityException('Invalid request type');
}
```
#### 3. **Component State Integrity**
```php
// State Tampering Detection
$hash = hash_hmac('sha256', json_encode($state), $secretKey);
if (!hash_equals($hash, $providedHash)) {
throw new SecurityException('State tampering detected');
}
```
#### 4. **Version-based Concurrency Control**
```php
// Prevent Concurrent Update Issues
if ($currentVersion !== $expectedVersion) {
throw new ConcurrentUpdateException('State has changed');
}
```
### Middleware-Konfiguration
#### CSRF-Middleware Skip
```php
// src/Framework/Http/Middlewares/CsrfMiddleware.php
// Skip CSRF validation for API routes and LiveComponent AJAX endpoints
// LiveComponents use stateless, component-scoped security model instead
if (str_starts_with($request->path, '/api/') ||
str_starts_with($request->path, '/live-component/') ||
str_starts_with($request->path, '/livecomponent/')) {
return $next($context);
}
```
#### Honeypot-Middleware Skip
```php
// src/Framework/Http/Middlewares/HoneypotMiddleware.php
// Skip honeypot validation for API routes and LiveComponent AJAX endpoints
if (str_starts_with($request->path, '/api/') ||
str_starts_with($request->path, '/live-component/') ||
str_starts_with($request->path, '/livecomponent/')) {
return;
}
```
### LiveComponent Routes
```php
// Framework Route
#[Route('/live-component/{id}', method: Method::POST)]
public function handleAction(string $id, HttpRequest $request): JsonResult
// Upload Route
#[Route('/live-component/{id}/upload', method: Method::POST)]
public function handleUpload(string $id, HttpRequest $request): JsonResult
```
### Sicherheitsempfehlungen
#### ✅ DO (Implementiert):
1. **Origin Validation**: Same-Origin-Policy durchsetzen
2. **X-Requested-With Header**: AJAX-Requests validieren
3. **Component State Integrity**: State-Hashing implementieren
4. **Version Control**: Concurrent Updates erkennen
5. **Rate Limiting**: API-Rate-Limits für LiveComponent-Endpoints
6. **Session Validation**: Authentifizierte User-Sessions prüfen
#### ❌ DON'T:
1. **Keine traditionellen CSRF-Tokens** in LiveComponent-Requests
2. **Keine Honeypot-Felder** in JSON-Payloads
3. **Keine Token-Rotation** während LiveComponent-Sessions
4. **Keine Form-basierte Validierung** für AJAX-Endpoints
### Security Threat Model
#### Bedrohungen die WEITERHIN abgewehrt werden:
-**Session Hijacking**: Session-Cookie mit HttpOnly + Secure Flags
-**XSS Attacks**: Content Security Policy + Output Escaping
-**Man-in-the-Middle**: HTTPS-Only Communication
-**Replay Attacks**: Version-based Concurrency Detection
-**State Tampering**: HMAC State Integrity Validation
#### Bedrohungen die durch CSRF-Skip entstehen könnten:
- ⚠️ **Cross-Site Request Forgery**: Durch Origin Validation abgedeckt
- ⚠️ **Clickjacking**: Durch X-Frame-Options Header abgedeckt
- ⚠️ **JSON Hijacking**: Durch X-Requested-With Header abgedeckt
### Alternative Security Implementation
Für **kritische Actions** (z.B. Zahlungen, Account-Löschung):
```php
// Zusätzliche Action-Level Security
final class CriticalAction extends LiveComponent
{
public function deleteAccount(array $params): ComponentUpdate
{
// 1. Re-Authentication Check
if (!$this->session->recentlyAuthenticated()) {
throw new ReAuthenticationRequired();
}
// 2. Action-Specific Token
$actionToken = $params['action_token'] ?? null;
if (!$this->validateActionToken($actionToken)) {
throw new InvalidActionToken();
}
// 3. Rate Limiting
if ($this->rateLimiter->tooManyAttempts($this->userId)) {
throw new TooManyAttemptsException();
}
// 4. Execute Critical Action
$this->accountService->delete($this->userId);
return ComponentUpdate::withMessage('Account deleted');
}
}
```
### Testing Security
```php
// Security Test Cases
describe('LiveComponent Security', function () {
it('rejects requests without X-Requested-With header', function () {
$response = $this->post('/live-component/datatable:demo', [
'action' => 'sort'
]);
expect($response->status)->toBe(403);
});
it('validates component state integrity', function () {
$tamperedState = ['malicious' => 'data'];
$response = $this->post('/live-component/datatable:demo', [
'action' => 'sort',
'state' => $tamperedState
], [
'X-Requested-With' => 'XMLHttpRequest'
]);
expect($response->status)->toBe(400);
expect($response->json()['error'])->toContain('State tampering');
});
});
```
### Zusammenfassung
**CSRF und Honeypot sind für LiveComponents deaktiviert**, weil:
1.**Technisch inkompatibel** mit JSON-basiertem State Management
2.**Architektonisch unnötig** durch Component-scoped Security Model
3.**Durch alternative Maßnahmen ersetzt**: Origin Validation, State Integrity, Version Control
4.**Best Practice** in modernen JavaScript-Frameworks (React, Vue, Angular)
**Die Sicherheit wird gewährleistet durch:**
- Origin Validation (Same-Origin-Policy)
- X-Requested-With Header Validation
- Component State Integrity (HMAC)
- Version-based Concurrency Control
- Session Validation für authentifizierte Actions
- Optional: Action-Level Tokens für kritische Operations
Dies entspricht dem **Security-Model moderner Single-Page Applications** und ist die empfohlene Vorgehensweise für AJAX-basierte Component-Systeme.

File diff suppressed because it is too large Load Diff

View File

@@ -1,461 +0,0 @@
# 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.**

View File

@@ -1,683 +0,0 @@
# LiveComponents Caching System
Umfassende Dokumentation des LiveComponents Caching-Systems mit Performance Metrics.
## Übersicht
Das LiveComponents Caching-System bietet **mehrschichtige Caching-Strategien** für optimale Performance:
- **Component State Cache**: ~70% schnellere Initialisierung
- **Slot Content Cache**: ~60% schnellere Slot-Resolution
- **Template Fragment Cache**: ~80% schnellere Template-Rendering
Alle Caches unterstützen **automatische Performance-Metriken** über Decorator Pattern.
## Architektur
```
┌─────────────────────────────────────────────────────────────┐
│ LiveComponent Handler │
│ (Orchestriert Component Lifecycle) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Metrics-Aware Cache Decorators │
│ (Transparente Performance-Metriken-Sammlung) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ ComponentStateCache │ SlotContentCache │ TemplateFragmentCache│
│ (Core Cache Implementations) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Framework Cache Layer (SmartCache) │
│ (File/Redis/Database Driver) │
└─────────────────────────────────────────────────────────────┘
```
## Komponenten
### 1. Component State Cache
**Zweck**: Cached Component State zwischen Requests für schnellere Rehydration.
**Performance**: ~70% schnellere Component-Initialisierung
**Verwendung**:
```php
use App\Framework\LiveComponents\Cache\ComponentStateCache;
use App\Framework\Core\ValueObjects\Duration;
$cache = $container->get(ComponentStateCache::class);
// State speichern
$cache->store(
componentId: $componentId,
state: $componentState,
ttl: Duration::fromHours(1)
);
// State abrufen
$cachedState = $cache->retrieve($componentId, $currentState);
if ($cachedState !== null) {
// Cached state verwenden - ~70% schneller
$component->hydrateFromCache($cachedState);
} else {
// Fresh initialization
$component->initialize($freshState);
}
```
**Auto-TTL basierend auf Component-Typ**:
```php
// Auto-optimierte TTL
$cache->storeWithAutoTTL(
componentId: $componentId,
state: $state,
componentType: 'counter' // 5 Minuten TTL
);
/*
TTL-Strategien:
- counter, timer: 5 Minuten (frequent updates)
- chart, datatable: 30 Minuten (moderate updates)
- card, modal, layout: 2 Stunden (static-ish)
*/
```
### 2. Slot Content Cache
**Zweck**: Cached resolved Slot-Content für wiederverwendbare Komponenten.
**Performance**: ~60% schnellere Slot-Resolution
**Verwendung**:
```php
use App\Framework\LiveComponents\Cache\SlotContentCache;
$cache = $container->get(SlotContentCache::class);
// Single Slot speichern
$cache->storeResolvedContent(
componentId: $componentId,
slotName: 'header',
resolvedContent: $renderedHeaderHtml,
ttl: Duration::fromMinutes(30)
);
// Slot abrufen
$cached = $cache->getResolvedContent($componentId, 'header');
if ($cached !== null) {
return $cached; // ~60% schneller
}
```
**Batch Operations für Multiple Slots**:
```php
// Batch store - alle Slots in einem Call
$cache->storeBatch(
componentId: $componentId,
slots: [
'header' => $headerHtml,
'footer' => $footerHtml,
'sidebar' => $sidebarHtml
],
ttl: Duration::fromHours(1)
);
// Batch retrieve
$cachedSlots = $cache->getBatch($componentId, ['header', 'footer', 'sidebar']);
if (isset($cachedSlots['header'])) {
// Header aus Cache
}
```
**Content-Hash Based Invalidation**:
```php
// Automatische Invalidierung bei Content-Änderung
$cache->storeWithContentHash(
componentId: $componentId,
slotName: 'dynamic-content',
resolvedContent: $dynamicHtml,
ttl: Duration::fromHours(2)
);
// Wenn Content sich ändert, ändert sich Hash → alter Cache ungültig
```
### 3. Template Fragment Cache
**Zweck**: Cached gerenderte Template-Fragmente für wiederverwendbare UI-Komponenten.
**Performance**: ~80% schnellere Template-Rendering
**Verwendung**:
```php
use App\Framework\LiveComponents\Cache\TemplateFragmentCache;
$cache = $container->get(TemplateFragmentCache::class);
// Template Fragment speichern
$cache->store(
componentType: 'card',
renderedHtml: $renderedCardHtml,
data: ['title' => 'User Profile', 'userId' => 123],
variant: 'default',
ttl: Duration::fromHours(2)
);
// Template abrufen
$cached = $cache->get(
componentType: 'card',
data: ['title' => 'User Profile', 'userId' => 123],
variant: 'default'
);
```
**Remember Pattern**:
```php
// Eleganter: get from cache or execute callback
$renderedHtml = $cache->remember(
componentType: 'card',
data: $templateData,
callback: fn() => $this->templateRenderer->render('card.view.php', $templateData),
variant: 'compact',
ttl: Duration::fromHours(1)
);
```
**Static Templates** (keine Data-Variationen):
```php
// Für Layouts, Header, Footer - komplett statisch
$cache->storeStatic(
componentType: 'layout',
renderedHtml: $layoutShellHtml,
variant: 'default',
ttl: Duration::fromHours(24) // Lange TTL
);
$layoutHtml = $cache->getStatic('layout', 'default');
```
**Auto-TTL basierend auf Component-Typ**:
```php
$cache->storeWithAutoTTL(
componentType: 'header', // 24h TTL (static)
renderedHtml: $headerHtml,
data: $headerData,
variant: 'default'
);
```
### 4. Cache Invalidation Strategy
**Zweck**: Koordinierte Cache-Invalidierung über alle Cache-Layer.
**Verwendung**:
```php
use App\Framework\LiveComponents\Cache\CacheInvalidationStrategy;
$strategy = $container->get(CacheInvalidationStrategy::class);
// Komplettes Component invalidieren (State + Slots)
$result = $strategy->invalidateComponent($componentId);
if ($result->success) {
// ['state', 'slots'] invalidiert
}
// Nur Slots invalidieren
$result = $strategy->invalidateComponentSlots($componentId);
// Einzelner Slot
$result = $strategy->invalidateSlot($componentId, 'header');
// Template Type (alle Templates von Typ)
$result = $strategy->invalidateComponentType('card');
// Template Variant
$result = $strategy->invalidateVariant('card', 'compact');
```
**Smart State-Change Invalidation**:
```php
// Invalidiert nur betroffene Caches
$result = $strategy->invalidateOnStateChange(
componentId: $componentId,
oldState: $oldComponentState,
newState: $newComponentState
);
/*
Prüft State Keys die Slot-Rendering betreffen:
- sidebarWidth
- sidebarCollapsed
- isOpen
- padding
- theme
- variant
Nur wenn diese sich ändern → Slot Cache auch invalidieren
*/
```
**Bulk Invalidation**:
```php
// Viele Components auf einmal
$componentIds = [$id1, $id2, $id3, ...];
$result = $strategy->invalidateBulk($componentIds);
// Result enthält Success-Count
// reason: "bulk_invalidation:150/200" (150 von 200 erfolgreich)
```
**Nuclear Option** (mit Vorsicht!):
```php
// ALLE LiveComponent Caches löschen
$result = $strategy->clearAll();
// Use Cases:
// - Deployment
// - Major Framework Updates
// - Debug/Development
```
## Performance Metrics System
### Metrics Collector Setup
```php
use App\Framework\LiveComponents\Cache\CacheMetricsCollector;
use App\Framework\LiveComponents\Cache\MetricsAwareComponentStateCache;
// DI Container Setup
$metricsCollector = new CacheMetricsCollector();
$container->singleton(CacheMetricsCollector::class, $metricsCollector);
// Metrics-Aware Caches registrieren
$stateCache = new ComponentStateCache($frameworkCache);
$metricAwareStateCache = new MetricsAwareComponentStateCache(
$stateCache,
$metricsCollector
);
$container->singleton(ComponentStateCache::class, $metricAwareStateCache);
```
### Metrics Abrufen
```php
// Metrics für spezifischen Cache-Typ
$stateMetrics = $metricsCollector->getMetrics(CacheType::STATE);
echo "State Cache Hit Rate: " . $stateMetrics->hitRate->format(2); // "85.50%"
echo "Average Lookup Time: " . $stateMetrics->averageLookupTimeMs . "ms";
echo "Performance Grade: " . $stateMetrics->getPerformanceGrade(); // "A"
```
### Performance Summary
```php
$summary = $metricsCollector->getSummary();
/*
[
'overall' => [
'cache_type' => 'merged',
'hits' => 1500,
'misses' => 200,
'hit_rate' => '88.24%',
'miss_rate' => '11.76%',
'average_lookup_time_ms' => 0.523,
'total_size' => 450,
'invalidations' => 15,
'performance_grade' => 'A'
],
'by_type' => [
'state' => [...],
'slot' => [...],
'template' => [...]
],
'performance_assessment' => [
'state_cache' => [
'target' => '70.0%',
'actual' => '85.50%',
'meets_target' => true,
'grade' => 'A'
],
'slot_cache' => [
'target' => '60.0%',
'actual' => '72.30%',
'meets_target' => true,
'grade' => 'C'
],
'template_cache' => [
'target' => '80.0%',
'actual' => '91.20%',
'meets_target' => true,
'grade' => 'A'
],
'overall_grade' => 'A'
]
]
*/
```
### Performance Monitoring
```php
// Check ob Caches underperforming sind
if ($metricsCollector->hasPerformanceIssues()) {
$warnings = $metricsCollector->getPerformanceWarnings();
foreach ($warnings as $warning) {
$logger->warning($warning);
// "State cache hit rate (65.00%) below target (70.0%)"
}
// Alert Operations Team oder Auto-Tuning triggern
}
```
### Metrics Export für Monitoring Tools
```php
// Export für Prometheus, Grafana, etc.
$export = $metricsCollector->export();
/*
[
'timestamp' => 1678901234,
'metrics' => [
'overall' => [...],
'by_type' => [...],
'performance_assessment' => [...]
]
]
*/
// An Monitoring-Service senden
$monitoringService->sendMetrics($export);
```
## Integration Beispiele
### Beispiel 1: Card Component mit Caching
```php
final readonly class CardComponent
{
public function __construct(
private ComponentStateCache $stateCache,
private TemplateFragmentCache $templateCache,
private TemplateRenderer $renderer
) {}
public function render(ComponentId $componentId, ComponentState $state): string
{
// 1. Try State Cache
$cachedState = $this->stateCache->retrieve($componentId, $state);
if ($cachedState !== null) {
$state = $cachedState; // ~70% schneller
} else {
$this->stateCache->storeWithAutoTTL($componentId, $state, 'card');
}
// 2. Try Template Cache mit Remember Pattern
return $this->templateCache->remember(
componentType: 'card',
data: $state->toArray(),
callback: fn() => $this->renderer->render('card.view.php', $state->toArray()),
variant: $state->get('variant', 'default'),
ttl: Duration::fromHours(2)
);
}
}
```
### Beispiel 2: Nested Component mit Slot Caching
```php
final readonly class LayoutComponent
{
public function __construct(
private SlotContentCache $slotCache,
private SlotManager $slotManager
) {}
public function render(ComponentId $componentId, array $slots): string
{
// 1. Check Slot Cache - Batch Operation
$cachedSlots = $this->slotCache->getBatch(
$componentId,
array_keys($slots)
);
$resolvedSlots = [];
foreach ($slots as $slotName => $slotContent) {
// 2. Use cached oder resolve fresh
if (isset($cachedSlots[$slotName])) {
$resolvedSlots[$slotName] = $cachedSlots[$slotName];
} else {
$resolved = $this->slotManager->resolveSlot($slotName, $slotContent);
$resolvedSlots[$slotName] = $resolved;
// 3. Cache for next time
$this->slotCache->storeResolvedContent(
$componentId,
$slotName,
$resolved,
Duration::fromHours(1)
);
}
}
return $this->renderLayout($resolvedSlots);
}
}
```
### Beispiel 3: Dynamic Component mit Smart Invalidation
```php
final readonly class DynamicFormComponent
{
public function __construct(
private ComponentStateCache $stateCache,
private CacheInvalidationStrategy $invalidationStrategy
) {}
public function updateState(
ComponentId $componentId,
ComponentState $oldState,
ComponentState $newState
): void {
// 1. Smart invalidation - nur betroffene Caches
$result = $this->invalidationStrategy->invalidateOnStateChange(
$componentId,
$oldState,
$newState
);
// 2. Store new state
$this->stateCache->storeWithAutoTTL(
$componentId,
$newState,
'dynamic-form'
);
// Log invalidation result
$this->logger->info('Cache invalidated', [
'component_id' => $componentId->toString(),
'invalidated' => $result->invalidated,
'reason' => $result->reason
]);
}
}
```
## Performance Best Practices
### 1. Use Auto-TTL Methods
```php
// ✅ Good - Auto-optimierte TTL
$cache->storeWithAutoTTL($componentId, $state, 'counter');
// ❌ Avoid - Manual TTL kann suboptimal sein
$cache->store($componentId, $state, Duration::fromHours(24)); // Zu lange für counter
```
### 2. Batch Operations für Multiple Slots
```php
// ✅ Good - Batch Operation
$cache->storeBatch($componentId, [
'header' => $headerHtml,
'footer' => $footerHtml,
'sidebar' => $sidebarHtml
]);
// ❌ Avoid - Einzelne Calls
$cache->storeResolvedContent($componentId, 'header', $headerHtml);
$cache->storeResolvedContent($componentId, 'footer', $footerHtml);
$cache->storeResolvedContent($componentId, 'sidebar', $sidebarHtml);
```
### 3. Remember Pattern für Templates
```php
// ✅ Good - Remember Pattern
$html = $cache->remember($type, $data, fn() => $this->render($data));
// ❌ Avoid - Manual get/store
$html = $cache->get($type, $data);
if ($html === null) {
$html = $this->render($data);
$cache->store($type, $html, $data);
}
```
### 4. Smart Invalidation
```php
// ✅ Good - Smart invalidation
$strategy->invalidateOnStateChange($id, $old, $new);
// ❌ Avoid - Always invalidate all
$strategy->invalidateComponent($id); // Invalidiert auch wenn nicht nötig
```
### 5. Content-Hash Based Caching
```php
// ✅ Good - Auto-invalidation bei Content-Änderung
$cache->storeWithContentHash($id, $slotName, $content);
// ❌ Avoid - Manual invalidation tracking
$cache->storeResolvedContent($id, $slotName, $content);
// ... später manuell invalidieren müssen
```
## Monitoring Dashboard Beispiel
```php
final readonly class CacheMonitoringController
{
public function dashboard(Request $request): ViewResult
{
$summary = $this->metricsCollector->getSummary();
return new ViewResult('cache-dashboard', [
'overall_stats' => $summary['overall'],
'state_cache' => $summary['by_type']['state'],
'slot_cache' => $summary['by_type']['slot'],
'template_cache' => $summary['by_type']['template'],
'assessment' => $summary['performance_assessment'],
'warnings' => $this->metricsCollector->getPerformanceWarnings(),
'has_issues' => $this->metricsCollector->hasPerformanceIssues()
]);
}
}
```
## Troubleshooting
### Problem: Niedrige Hit Rate
**Symptom**: Hit Rate < Target (70%, 60%, 80%)
**Lösungen**:
1. **TTL zu kurz**: Erhöhe TTL mit Auto-TTL Methods
2. **Zu viele Invalidations**: Prüfe Smart Invalidation Logik
3. **Cache Size zu klein**: Erhöhe Cache Capacity
4. **Variant Explosion**: Reduziere Template Variants
### Problem: Hohe Average Lookup Time
**Symptom**: Lookup Time > 1ms
**Lösungen**:
1. **Cache Driver langsam**: Wechsle zu Redis statt File Cache
2. **Große Payloads**: Komprimiere cached Data
3. **Netzwerk Latency**: Use lokalen Cache statt remote
### Problem: Memory Issues
**Symptom**: Out of Memory Errors
**Lösungen**:
1. **Cache Size explodiert**: Implementiere LRU Eviction
2. **TTL zu lange**: Reduziere TTL für rarely-used Components
3. **Zu viele Variants**: Consolidate Template Variants
## Performance Benchmarks
Typische Performance-Werte im Production Environment:
| Operation | Without Cache | With Cache | Improvement |
|-----------|---------------|------------|-------------|
| Component Init | 5.2ms | 1.5ms | **71% faster** |
| Slot Resolution | 3.8ms | 1.5ms | **61% faster** |
| Template Render | 12.4ms | 2.1ms | **83% faster** |
| Full Component | 21.4ms | 5.1ms | **76% faster** |
**Cache Hit Rates** (Production):
- State Cache: 85-90%
- Slot Cache: 70-80%
- Template Cache: 90-95%
**Memory Usage**:
- Per Cached State: ~2KB
- Per Cached Slot: ~1KB
- Per Cached Template: ~5KB
- Total (10k components): ~80MB
## Zusammenfassung
Das LiveComponents Caching-System bietet:
**3-Layer Caching** (State, Slot, Template)
**~70-80% Performance-Steigerung**
**Automatische Metrics** via Decorator Pattern
**Smart Invalidation** basierend auf State Changes
**Flexible TTL-Strategien**
**Content-Hash Based Auto-Invalidation**
**Batch Operations** für Efficiency
**Remember Pattern** für einfache Nutzung
**Performance Monitoring** out-of-the-box
**Production-Ready** mit Type Safety
Framework-konform:
- Value Objects (CacheType, CacheMetrics, Percentage, Hash)
- Readonly Classes
- Immutable State
- Decorator Pattern
- Type Safety everywhere

View File

@@ -1,236 +0,0 @@
# LiveComponents DOM Badges System
**Visual component overlays for real-time monitoring and debugging.**
## Overview
The DOM Badges system provides visual overlays on LiveComponent elements, displaying component IDs, names, and activity counters directly on the page. This enables developers to quickly identify and debug components without opening the DevTools panel.
## Features
### Visual Component Identification
- **Component Name**: Shows the component's display name
- **Component ID**: Truncated ID (first 8 characters) for identification
- **Icon Indicator**: ⚡ icon for visual consistency
### Real-Time Activity Tracking
- **Action Counter**: Tracks number of actions executed on the component
- **Activity Animation**: Badge pulses with green highlight when actions are triggered
- **Live Updates**: Counter updates in real-time as actions execute
### Interactive Features
- **Click to Focus**: Click badge to open DevTools and focus on that component
- **Hover Highlight**: Hover over badge to highlight the component element with blue outline
- **DevTools Integration**: Clicking badge scrolls to component in tree and expands details
### Badge Management
- **Toggle Visibility**: Badge button in DevTools header to show/hide all badges
- **Auto-Positioning**: Badges automatically position at top-left of component elements
- **Dynamic Updates**: Badges track DOM changes and update positions accordingly
## Architecture
### Badge Data Structure
```javascript
{
badge: HTMLElement, // Badge DOM element
element: HTMLElement, // Component element
actionCount: number // Number of actions executed
}
```
### Badge Lifecycle
1. **Initialization**: `initializeDomBadges()` sets up MutationObserver
2. **Creation**: `createDomBadge()` creates badge for each component
3. **Positioning**: `positionBadge()` calculates fixed position
4. **Updates**: `updateBadgeActivity()` increments counter and triggers animation
5. **Cleanup**: `cleanupRemovedBadges()` removes badges for destroyed components
## Usage
### Automatic Badge Creation
Badges are automatically created when:
- DevTools is opened and components exist in DOM
- New components are added to the page (via MutationObserver)
- Badge visibility is toggled on
### Toggle Badge Visibility
```javascript
// Via DevTools UI: Click "⚡ Badges" button in header
// Programmatically
devTools.toggleBadges();
```
### Badge Interactions
**Click Badge**:
- Opens DevTools if closed
- Switches to Components tab
- Scrolls to component in tree
- Expands component details
**Hover Badge**:
- Shows blue outline around component element
- Highlights component boundaries for easy identification
## Styling
### Badge Appearance
- **Background**: Dark semi-transparent (#1e1e1e with 95% opacity)
- **Border**: Blue (#007acc) that changes to green (#4ec9b0) on hover
- **Backdrop Filter**: 4px blur for modern glass-morphism effect
- **Box Shadow**: Subtle shadow for depth
### Active Animation
```css
@keyframes badge-pulse {
0% { transform: scale(1); box-shadow: default }
50% { transform: scale(1.1); box-shadow: green glow }
100% { transform: scale(1); box-shadow: default }
}
```
### Color Coding
- **Component Name**: Blue (#569cd6) - VS Code variable color
- **Component ID**: Gray (#858585) - subdued identifier
- **Action Count**: Yellow (#dcdcaa) - VS Code function color
- **Active Border**: Green (#4ec9b0) - VS Code string color
## Technical Implementation
### MutationObserver Integration
```javascript
const observer = new MutationObserver(() => {
this.updateDomBadges();
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['data-component-id']
});
```
### Position Calculation
Badges use `fixed` positioning with `getBoundingClientRect()`:
```javascript
const rect = element.getBoundingClientRect();
badge.style.position = 'fixed';
badge.style.top = `${rect.top + window.scrollY}px`;
badge.style.left = `${rect.left + window.scrollX}px`;
```
### Activity Tracking Integration
```javascript
// In logAction() method
logAction(componentId, ...) {
// ... log action ...
// Update badge
this.updateBadgeActivity(componentId);
}
```
## Performance Considerations
### Efficient Updates
- **Deduplication**: Checks if badge already exists before creating
- **Position Updates**: Only updates position for existing badges
- **Cleanup**: Removes badges for destroyed components to prevent memory leaks
### Throttled DOM Operations
- **MutationObserver**: Batches DOM changes
- **Single Reflow**: Badge creation/positioning minimizes layout thrashing
- **Display None**: Hidden badges use `display: none` instead of removal
## Best Practices
### When to Use Badges
**Good Use Cases**:
- Debugging component layout and positioning
- Identifying which components are active on a page
- Tracking component action frequency
- Visual confirmation of component presence
**Avoid**:
- Production environments (only enabled in development)
- Pages with 50+ components (visual clutter)
- When precise layout debugging is needed (use browser DevTools instead)
### Badge Visibility Toggle
- Keep badges **enabled** when actively debugging components
- **Disable** badges when focusing on other DevTools tabs
- Badges automatically show when DevTools opens
## Integration with DevTools Features
### Component Tree
- Clicking badge navigates to component in tree
- Badge highlights component row with blue background
- Component details automatically expand
### Action Log
- Badge counter matches action log entries
- Activity animation syncs with action execution
- Both show real-time component behavior
### Event System
- Badges react to component lifecycle events
- Auto-created on `component:initialized`
- Auto-removed on `component:destroyed`
## Keyboard Shortcuts
No direct keyboard shortcuts for badges, but:
- **Ctrl+Shift+D**: Toggle DevTools (shows/creates badges)
- **Click Badge**: Focus component in DevTools
## Browser Compatibility
- **Modern Browsers**: Full support (Chrome, Firefox, Safari, Edge)
- **MutationObserver**: Required (IE11+ supported)
- **CSS Animations**: Fallback to no animation on older browsers
- **Backdrop Filter**: Graceful degradation without blur effect
## Troubleshooting
### Badges Not Appearing
1. Check if DevTools is enabled (`data-env="development"`)
2. Verify components have `data-component-id` attribute
3. Ensure badges are enabled (check button opacity in header)
4. Look for JavaScript errors in console
### Badges in Wrong Position
1. Check if component element has proper layout (not `display: none`)
2. Verify parent containers don't have `transform` CSS
3. Badge position updates on DOM mutations
4. Manually refresh badges with toggle button
### Performance Issues
1. Reduce number of active components
2. Disable badges when not actively debugging
3. Check MutationObserver frequency in console
4. Consider using Components tab instead for many components
## Future Enhancements
Potential improvements for DOM badges:
- **State Preview**: Show current component state in badge tooltip
- **Network Indicator**: Visual indicator when component is loading
- **Error State**: Red badge when component has errors
- **Badge Groups**: Collapsible badges for nested components
- **Custom Badge Colors**: User-configurable color schemes
- **Badge Size Options**: Small/Medium/Large badge sizes
- **Filter by Type**: Show only specific component types
## Summary
DOM badges provide a powerful visual debugging tool that complements the DevTools overlay. They enable:
- **Instant Component Identification**: See what's on the page without opening DevTools
- **Real-Time Activity Monitoring**: Track action execution as it happens
- **Quick Navigation**: Click to jump to component in DevTools
- **Visual Feedback**: Animations and highlights for enhanced UX
The badge system is designed to be non-intrusive, performant, and seamlessly integrated with the LiveComponent lifecycle and DevTools features.

View File

@@ -1,699 +0,0 @@
# LiveComponents Implementation Plan
**Datum**: 2025-10-07
**Status**: Draft für Review
## Executive Summary
Dieser Plan strukturiert die vorgeschlagenen Verbesserungen für das LiveComponents-Modul in umsetzbare Phasen mit klaren Prioritäten basierend auf Impact, Aufwand und Framework-Compliance.
---
## Priorisierungs-Matrix
### Scoring-System
- **Impact**: 1-5 (1=niedrig, 5=kritisch)
- **Effort**: 1-5 (1=wenige Stunden, 5=mehrere Wochen)
- **Framework Compliance**: 1-5 (1=neutral, 5=essentiell für Framework-Patterns)
- **Priority Score**: (Impact × 2 + Framework Compliance - Effort) / 3
---
## Phase 1: Critical Foundation (P0 - MUST HAVE)
### 1.1 State-Validierung & Serialization
**Impact**: 5 | **Effort**: 2 | **Framework Compliance**: 5 | **Priority**: 6.0
**Problem**:
- Kein Schema/Validierung für State
- Serialization-Fehler bei komplexen Objekten
- Sicherheitsrisiken durch unvalidierte State-Änderungen
**Lösung**:
```php
// State Schema Interface
interface StateSchema
{
public function validate(array $state): ValidationResult;
public function sanitize(array $state): array;
public function getSerializableFields(): array;
}
// Component mit Schema
final readonly class TodoListComponent implements LiveComponentContract
{
public function getStateSchema(): StateSchema
{
return new TodoListStateSchema([
'todos' => 'array<TodoItem>',
'filter' => 'enum:all|active|completed',
'page' => 'int:min=1'
]);
}
}
```
**Files to Create**:
- `src/Framework/LiveComponents/Contracts/StateSchema.php`
- `src/Framework/LiveComponents/StateValidation/StateValidator.php`
- `src/Framework/LiveComponents/StateValidation/ValidationResult.php`
- `src/Framework/LiveComponents/StateValidation/Sanitizer.php`
**Framework Pattern**: Value Objects für State, Validation mit expliziten Contracts
---
### 1.2 CSRF-Integration
**Impact**: 5 | **Effort**: 1 | **Framework Compliance**: 4 | **Priority**: 5.7
**Problem**:
- Keine CSRF-Protection für Component-Actions
- Security Gap
**Lösung**:
```php
// In LiveComponentHandler
public function handle(
LiveComponentContract $component,
string $method,
ActionParameters $params
): ComponentUpdate {
// CSRF-Check vor Action-Ausführung
if (!$this->csrfValidator->validate($params->getCsrfToken())) {
throw new CsrfTokenMismatchException();
}
// ... existing code
}
```
**Files to Modify**:
- `src/Framework/LiveComponents/LiveComponentHandler.php` (add CSRF check)
- `src/Framework/LiveComponents/ValueObjects/ActionParameters.php` (add csrfToken field)
- `resources/js/modules/livecomponent/index.js` (auto-include CSRF token)
**Framework Pattern**: Integration mit bestehendem CsrfMiddleware
---
### 1.3 Action Guards & Permissions
**Impact**: 5 | **Effort**: 2 | **Framework Compliance**: 4 | **Priority**: 5.3
**Problem**:
- Keine Permission-Checks auf Actions
- Jeder kann jede Action aufrufen
**Lösung**:
```php
#[Attribute(Attribute::TARGET_METHOD)]
final readonly class RequiresPermission
{
public function __construct(
public string $permission
) {}
}
// Usage
#[RequiresPermission('posts.delete')]
public function deletePost(string $postId): ComponentData
{
// Only executed if user has permission
}
```
**Files to Create**:
- `src/Framework/LiveComponents/Attributes/RequiresPermission.php`
- `src/Framework/LiveComponents/Security/ActionAuthorizationChecker.php`
**Framework Pattern**: Attribute-basierte Authorization wie bei Routes
---
### 1.4 Starke Fehlermeldungen
**Impact**: 4 | **Effort**: 2 | **Framework Compliance**: 4 | **Priority**: 4.0
**Problem**:
- Generische Fehlermeldungen
- Schwer zu debuggen
**Lösung**:
```php
// Custom Exceptions mit Context
final class ComponentActionNotFoundException extends FrameworkException
{
public static function forAction(string $componentName, string $action): self
{
return self::create(
ErrorCode::LIVECOMPONENT_ACTION_NOT_FOUND,
"Action '{$action}' not found on component '{$componentName}'"
)->withData([
'component' => $componentName,
'action' => $action,
'available_actions' => self::getAvailableActions($componentName),
'suggestion' => self::suggestSimilarAction($componentName, $action)
]);
}
}
```
**Files to Create**:
- `src/Framework/LiveComponents/Exceptions/ComponentActionNotFoundException.php`
- `src/Framework/LiveComponents/Exceptions/InvalidStateException.php`
- `src/Framework/LiveComponents/Exceptions/ComponentNotFoundException.php`
**Framework Pattern**: FrameworkException mit ErrorCode, ExceptionContext
---
## Phase 2: Architektur-Verbesserungen (P1 - SHOULD HAVE)
### 2.1 Lifecycle Hooks
**Impact**: 4 | **Effort**: 3 | **Framework Compliance**: 4 | **Priority**: 3.7
**Problem**:
- Keine standardisierten Lifecycle-Events
- Schwer, Initialization/Cleanup zu machen
**Lösung**:
```php
interface ComponentLifecycle
{
public function beforeMount(ComponentData $initialState): ComponentData;
public function mount(): void;
public function hydrate(ComponentData $state): ComponentData;
public function dehydrate(ComponentData $state): array;
public function beforeUpdate(ComponentData $oldState, ComponentData $newState): ComponentData;
public function updated(ComponentData $state): void;
public function beforeDestroy(): void;
}
// Trait mit Default-Implementations
trait ComponentLifecycleTrait
{
public function beforeMount(ComponentData $initialState): ComponentData
{
return $initialState;
}
// ... all hooks with empty defaults
}
```
**Files to Create**:
- `src/Framework/LiveComponents/Contracts/ComponentLifecycle.php`
- `src/Framework/LiveComponents/Traits/ComponentLifecycleTrait.php`
**Files to Modify**:
- `src/Framework/LiveComponents/LiveComponentHandler.php` (call hooks)
- `src/Framework/LiveComponents/ComponentRegistry.php` (call mount hook)
**Framework Pattern**: Contract + Trait für optionale Hooks
---
### 2.2 Middleware-Pipeline
**Impact**: 4 | **Effort**: 3 | **Framework Compliance**: 5 | **Priority**: 4.0
**Problem**:
- Cross-Cutting Concerns (Logging, Rate-Limiting) fest im Handler
- Nicht erweiterbar
**Lösung**:
```php
interface ComponentMiddleware
{
public function before(
LiveComponentContract $component,
string $method,
ActionParameters $params
): void;
public function after(
LiveComponentContract $component,
ComponentUpdate $update
): ComponentUpdate;
}
// Built-in Middlewares
final readonly class RateLimitMiddleware implements ComponentMiddleware {}
final readonly class LoggingMiddleware implements ComponentMiddleware {}
final readonly class ValidationMiddleware implements ComponentMiddleware {}
final readonly class CsrfMiddleware implements ComponentMiddleware {}
```
**Files to Create**:
- `src/Framework/LiveComponents/Middleware/ComponentMiddleware.php`
- `src/Framework/LiveComponents/Middleware/MiddlewarePipeline.php`
- `src/Framework/LiveComponents/Middleware/RateLimitMiddleware.php`
- `src/Framework/LiveComponents/Middleware/LoggingMiddleware.php`
- `src/Framework/LiveComponents/Middleware/ValidationMiddleware.php`
- `src/Framework/LiveComponents/Middleware/CsrfMiddleware.php`
**Files to Modify**:
- `src/Framework/LiveComponents/LiveComponentHandler.php` (integrate pipeline)
**Framework Pattern**: Middleware-Pattern wie bei HTTP-Requests
---
### 2.3 Partial Rendering & DOM-Diffing
**Impact**: 5 | **Effort**: 4 | **Framework Compliance**: 3 | **Priority**: 3.5
**Problem**:
- Ganzer Component wird re-rendert
- Ineffizient bei großen Components
**Lösung**:
```php
// Server-Side: Fragments
interface SupportsFragments
{
public function getFragments(): array;
public function renderFragment(string $name, ComponentData $data): string;
}
// Client-Side: DOM-Diffing
class DomPatcher {
patch(element, newHtml) {
// morphdom-ähnlicher Algorithmus
this.diffAndPatch(element, newHtml);
}
}
```
**Files to Create**:
- `src/Framework/LiveComponents/Contracts/SupportsFragments.php`
- `src/Framework/LiveComponents/Rendering/FragmentRenderer.php`
- `public/js/modules/livecomponent/dom-patcher.js`
**Files to Modify**:
- `src/Framework/View/LiveComponentRenderer.php` (fragment support)
- `resources/js/modules/livecomponent/index.js` (DOM diffing)
**Framework Pattern**: Contract für Fragment-Support
---
### 2.4 Rate-Limiting auf Action-Level
**Impact**: 4 | **Effort**: 2 | **Framework Compliance**: 4 | **Priority**: 4.0
**Problem**:
- Keine Rate-Limits auf Actions
- Potentielle DOS-Attacken
**Lösung**:
```php
#[Attribute(Attribute::TARGET_METHOD)]
final readonly class RateLimit
{
public function __construct(
public int $maxAttempts = 60,
public int $decayMinutes = 1
) {}
}
// Usage
#[RateLimit(maxAttempts: 5, decayMinutes: 1)]
public function sendMessage(string $message): ComponentData
{
// Max 5 calls per minute
}
```
**Files to Create**:
- `src/Framework/LiveComponents/Attributes/RateLimit.php`
**Files to Modify**:
- `src/Framework/LiveComponents/Middleware/RateLimitMiddleware.php` (check attribute)
**Framework Pattern**: Attribute-basierte Konfiguration wie bei Routes
---
## Phase 3: Developer Experience (P2 - NICE TO HAVE)
### 3.1 CLI-Generator
**Impact**: 3 | **Effort**: 2 | **Framework Compliance**: 3 | **Priority**: 2.7
**Problem**:
- Boilerplate-Code für neue Components
- Inkonsistente Struktur
**Lösung**:
```bash
php console.php make:livecomponent TodoList \
--pollable \
--cacheable \
--with-test \
--with-template
```
**Files to Create**:
- `src/Framework/Console/Commands/MakeLiveComponentCommand.php`
- `resources/stubs/livecomponent.stub`
- `resources/stubs/livecomponent-template.stub`
- `resources/stubs/livecomponent-test.stub`
**Framework Pattern**: ConsoleCommand mit Stub-Templates
---
### 3.2 Test-Harness
**Impact**: 3 | **Effort**: 2 | **Framework Compliance**: 4 | **Priority**: 3.0
**Problem**:
- Boilerplate für Component-Tests
- Inkonsistente Test-Setups
**Lösung**:
```php
it('increments counter', function () {
$component = $this->mountComponent(CounterComponent::class, ['count' => 5]);
$update = $this->callAction($component, 'increment');
$this->assertComponentState($update, ['count' => 6]);
$this->assertEventDispatched($update, 'counter:changed');
});
```
**Files to Create**:
- `tests/Framework/LiveComponentTestCase.php`
- `tests/Framework/Traits/LiveComponentTestHelpers.php`
**Framework Pattern**: Pest Test Helpers
---
### 3.3 Autodiscovery-Optimierung
**Impact**: 3 | **Effort**: 2 | **Framework Compliance**: 5 | **Priority**: 3.3
**Problem**:
- Discovery funktioniert, aber könnte klarer sein
- Keine Kollisionswarnungen
**Lösung**:
```php
// Fallback auf Class-Name → kebab-case wenn kein Attribute
final class TodoListComponent implements LiveComponentContract {}
// → Auto-Name: "todo-list"
// Collision Detection
if (isset($map[$name])) {
throw new ComponentNameCollisionException(
"Component name '{$name}' already registered by {$map[$name]}"
);
}
```
**Files to Modify**:
- `src/Framework/LiveComponents/ComponentRegistry.php` (fallback + collision check)
**Framework Pattern**: Convention over Configuration
---
### 3.4 Template-Konventionen dokumentieren
**Impact**: 2 | **Effort**: 1 | **Framework Compliance**: 3 | **Priority**: 2.0
**Problem**:
- Keine klaren Template-Patterns
- Inkonsistente Component-Markup
**Lösung**:
```html
<!-- Standard Component Template Structure -->
<div data-lc="component-name" data-key="{id}" class="lc-component">
<!-- Component-specific markup -->
<!-- Slot system for extensibility -->
<slot name="header"></slot>
<slot name="content">Default content</slot>
<slot name="footer"></slot>
</div>
```
**Files to Create/Modify**:
- `docs/claude/livecomponent-template-conventions.md`
- Update existing templates to follow conventions
**Framework Pattern**: Template-System Integration
---
## Phase 4: Performance & Polish (P3 - FUTURE)
### 4.1 Request-Cache/Memoization
**Impact**: 3 | **Effort**: 3 | **Framework Compliance**: 4 | **Priority**: 2.7
**Lösung**:
```php
interface Cacheable
{
public function getCacheTtl(): Duration;
public function getCacheKey(): CacheKey;
public function getCacheTags(): array;
public function varyBy(): array; // ['state.userId', 'state.filter']
}
```
**Framework Pattern**: SmartCache mit varyBy-Support
---
### 4.2 Polling-Coalescing
**Impact**: 2 | **Effort**: 3 | **Framework Compliance**: 2 | **Priority**: 1.3
**Lösung**:
```javascript
// Batch multiple poll requests
class PollCoalescer {
batchRequest(components) {
return fetch('/live-component/poll-batch', {
body: JSON.stringify({
components: components.map(c => c.id)
})
});
}
}
```
---
### 4.3 Debounce/Throttle im Protokoll
**Impact**: 3 | **Effort**: 2 | **Framework Compliance**: 2 | **Priority**: 2.3
**Lösung**:
```html
<input
data-live-action="search"
data-debounce="300"
data-throttle="1000"
/>
```
---
### 4.4 SSE Streaming
**Impact**: 3 | **Effort**: 4 | **Framework Compliance**: 3 | **Priority**: 2.0
**Lösung**:
```php
interface StreamableComponent
{
public function streamUpdates(): Generator;
}
```
---
### 4.5 Optimistic UI
**Impact**: 2 | **Effort**: 4 | **Framework Compliance**: 2 | **Priority**: 1.0
**Lösung**:
```javascript
// Client setzt State vorläufig, Server bestätigt oder rollt zurück
await component.callAction('deleteItem', { id: 123 }, { optimistic: true });
```
---
### 4.6 Background Actions
**Impact**: 2 | **Effort**: 3 | **Framework Compliance**: 3 | **Priority**: 1.7
**Lösung**:
```php
#[Attribute(Attribute::TARGET_METHOD)]
final readonly class Background
{
public function __construct(
public bool $queueable = true,
public ?string $queue = null
) {}
}
```
---
### 4.7 Accessibility Hooks
**Impact**: 2 | **Effort**: 2 | **Framework Compliance**: 2 | **Priority**: 1.3
**Lösung**:
```html
<div
data-lc="component"
data-lc-keep-focus
data-lc-announce-updates
role="region"
aria-live="polite"
>
```
---
### 4.8 DevTools Overlay
**Impact**: 2 | **Effort**: 4 | **Framework Compliance**: 1 | **Priority**: 0.7
**Lösung**:
```javascript
// Dev-Modus Overlay mit Component-Inspector
if (ENV === 'development') {
new LiveComponentDevTools().init();
}
```
---
## Quick Wins (Immediate Implementation)
### Top 5 Quick Wins (High Impact / Low Effort)
1. **CSRF-Integration** (Impact: 5, Effort: 1, Priority: 5.7)
- 2-4 Stunden
- Sofortiger Security-Benefit
2. **Starke Fehlermeldungen** (Impact: 4, Effort: 2, Priority: 4.0)
- 1 Tag
- Massiv verbesserte DX
3. **CLI-Generator** (Impact: 3, Effort: 2, Priority: 2.7)
- 1 Tag
- Accelerates development
4. **Test-Harness** (Impact: 3, Effort: 2, Priority: 3.0)
- 1 Tag
- Better test coverage
5. **Template-Konventionen** (Impact: 2, Effort: 1, Priority: 2.0)
- 4 Stunden
- Consistency across components
---
## Implementation Roadmap
### Sprint 1 (Week 1-2): Security Foundation
- ✅ CSRF-Integration
- ✅ Action Guards & Permissions
- ✅ State-Validierung
**Deliverables**: Secure LiveComponents-System
---
### Sprint 2 (Week 3-4): Architecture Improvements
- ✅ Middleware-Pipeline
- ✅ Lifecycle Hooks
- ✅ Starke Fehlermeldungen
**Deliverables**: Extensible, Developer-Friendly System
---
### Sprint 3 (Week 5-6): Performance & DX
- ✅ Partial Rendering
- ✅ Rate-Limiting
- ✅ CLI-Generator
- ✅ Test-Harness
**Deliverables**: Production-Ready System with Developer Tools
---
### Sprint 4+ (Future): Polish & Advanced Features
- ☐ Request-Cache/Memoization
- ☐ Polling-Coalescing
- ☐ SSE Streaming
- ☐ Optimistic UI
- ☐ Background Actions
- ☐ DevTools Overlay
**Deliverables**: Enterprise-Grade LiveComponents
---
## Success Metrics
### Security
- ✅ 100% CSRF-Protection on all Actions
- ✅ Permission-Checks auf allen kritischen Actions
- ✅ State-Validierung für alle Components
### Performance
- ✅ < 50ms Action-Latency (P95)
- ✅ < 100ms Render-Time (P95)
- ✅ 80%+ Cache-Hit-Rate
### Developer Experience
- ✅ < 5 Minuten neue Component erstellen
- ✅ < 10 Minuten Component-Test schreiben
- ✅ Klare Fehlermeldungen mit Lösungsvorschlägen
### Quality
- ✅ 90%+ Test-Coverage
- ✅ 100% Framework-Pattern-Compliance
- ✅ Zero breaking changes to existing components
---
## Risk Assessment
### High Risk
- **Middleware-Pipeline**: Breaking Changes möglich
- **Mitigation**: Optional in v1, Mandatory in v2
- **Lifecycle Hooks**: Performance Impact
- **Mitigation**: Profiling, Optional Hooks
### Medium Risk
- **Partial Rendering**: Client-Side Complexity
- **Mitigation**: Progressive Enhancement, Fallback zu Full-Render
- **State-Validierung**: Performance bei großen States
- **Mitigation**: Lazy Validation, Schema-Caching
### Low Risk
- **CSRF, Permissions, CLI-Generator**: Isolierte Änderungen
---
## Next Steps
1. **Review & Approval**: Stakeholder-Review dieses Plans
2. **Prototype**: CSRF-Integration als Proof-of-Concept
3. **Sprint Planning**: Detaillierte Sprint-1-Tasks
4. **Implementation**: Start mit Quick Wins
---
## Appendix: Framework-Compliance Checklist
Alle Implementations müssen folgen:
- ✅ Readonly Classes wo möglich
- ✅ Value Objects statt Primitives
- ✅ Composition over Inheritance
- ✅ Attribute-basierte Discovery
- ✅ Explicit Dependency Injection
- ✅ FrameworkException für Fehler
- ✅ Contracts statt Abstraction
- ✅ Pest Tests

View File

@@ -1,717 +0,0 @@
# LiveComponents Lazy Loading
**Status**: ✅ Implementiert
**Date**: 2025-10-09
Lazy Loading System für LiveComponents mit IntersectionObserver, Priority Queues und Skeleton Loaders.
---
## Übersicht
Das Lazy Loading System ermöglicht es, LiveComponents erst zu laden wenn sie im Viewport sichtbar werden. Dies verbessert die initiale Ladezeit und reduziert unnötige Server-Requests.
**Key Features**:
- IntersectionObserver API für Viewport Detection
- Priority-basierte Loading Queue (high, normal, low)
- Configurable threshold und root margin
- Professional Skeleton Loaders während des Ladens
- Automatic Component Initialization nach Load
- Error Handling mit Retry Logic
- Statistics Tracking
---
## Architecture
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Template │───▶│ Placeholder │───▶│ LazyComponent │───▶│ LiveComponent │
│ Function │ │ with Skeleton │ │ Loader │ │ Initialization │
└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │ │
lazy_component() data-live-component-lazy IntersectionObserver render + init
Template Syntax Skeleton Loader CSS Priority Queue Full Component
```
### Workflow
1. **Template Rendering**: `{{ lazy_component('id', options) }}` generiert Placeholder
2. **Initial Page Load**: Skeleton Loader wird angezeigt
3. **Viewport Detection**: IntersectionObserver erkennt Sichtbarkeit
4. **Priority Queue**: Component wird basierend auf Priority geladen
5. **Server Request**: Fetch von `/live-component/{id}/lazy-load`
6. **DOM Update**: Placeholder wird durch Component HTML ersetzt
7. **Initialization**: LiveComponent wird als normale Component initialisiert
---
## Template Usage
### Basic Lazy Loading
```php
<!-- Einfaches Lazy Loading -->
{{ lazy_component('user-stats:123') }}
<!-- Mit Priority -->
{{ lazy_component('notification-bell:user-456', {
'priority': 'high'
}) }}
<!-- Mit Custom Placeholder -->
{{ lazy_component('activity-feed:latest', {
'placeholder': 'Loading your activity feed...',
'class': 'skeleton-feed'
}) }}
<!-- Mit allen Optionen -->
{{ lazy_component('analytics-chart:dashboard', {
'priority': 'normal',
'threshold': '0.25',
'rootMargin': '100px',
'placeholder': 'Loading analytics...',
'class': 'skeleton-chart'
}) }}
```
### LazyComponentFunction Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `priority` | `'high'\|'normal'\|'low'` | `'normal'` | Loading priority in queue |
| `threshold` | `string` | `'0.1'` | Visibility threshold (0.0-1.0) |
| `placeholder` | `string\|null` | `null` | Custom loading text |
| `rootMargin` | `string\|null` | `null` | IntersectionObserver root margin |
| `class` | `string` | `''` | CSS class for skeleton loader |
### Priority Levels
**High Priority** (`priority: 'high'`):
- Laden sobald sichtbar (minimal delay)
- Use Cases: Above-the-fold content, kritische UI-Elemente
- Beispiele: Navigation, User Profile, Critical Notifications
**Normal Priority** (`priority: 'normal'`):
- Standard Queue Processing
- Use Cases: Reguläre Content-Bereiche
- Beispiele: Article List, Comment Sections, Product Cards
**Low Priority** (`priority: 'low'`):
- Laden nur wenn Idle Time verfügbar
- Use Cases: Below-the-fold content, optional Features
- Beispiele: Related Articles, Advertisements, Footer Content
---
## Skeleton Loaders
### Available Skeleton Types
Das Framework bietet 8 vorgefertigte Skeleton Loader Varianten:
#### 1. Text Skeleton
```html
<div class="skeleton skeleton-text skeleton-text--full"></div>
<div class="skeleton skeleton-text skeleton-text--80"></div>
<div class="skeleton skeleton-text skeleton-text--60"></div>
<div class="skeleton skeleton-text skeleton-text--lg"></div>
```
**Use Cases**: Text Placeholders, Titles, Paragraphs
#### 2. Card Skeleton
```html
<div class="skeleton-card">
<div class="skeleton-card__header">
<div class="skeleton skeleton-card__avatar"></div>
<div class="skeleton-card__title">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text skeleton-text--60"></div>
</div>
</div>
<div class="skeleton skeleton-card__image"></div>
<div class="skeleton-card__content">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text"></div>
</div>
</div>
```
**Use Cases**: User Cards, Product Cards, Article Cards
#### 3. List Skeleton
```html
<div class="skeleton-list">
<div class="skeleton-list__item">
<div class="skeleton skeleton-list__icon"></div>
<div class="skeleton-list__content">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text skeleton-text--60"></div>
</div>
<div class="skeleton skeleton-list__action"></div>
</div>
</div>
```
**Use Cases**: Navigation Lists, Settings Lists, Item Lists
#### 4. Table Skeleton
```html
<div class="skeleton-table">
<div class="skeleton-table__row skeleton-table__row--header">
<div class="skeleton skeleton-table__cell"></div>
<div class="skeleton skeleton-table__cell"></div>
<div class="skeleton skeleton-table__cell"></div>
</div>
<div class="skeleton-table__row">
<div class="skeleton skeleton-table__cell"></div>
<div class="skeleton skeleton-table__cell"></div>
<div class="skeleton skeleton-table__cell"></div>
</div>
</div>
```
**Use Cases**: Data Tables, Reports, Grids
#### 5. Feed Skeleton
```html
<div class="skeleton-feed">
<div class="skeleton-feed__item">
<div class="skeleton-feed__header">
<div class="skeleton skeleton-feed__avatar"></div>
<div class="skeleton-feed__meta">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text skeleton-text--60"></div>
</div>
</div>
<div class="skeleton-feed__content">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text"></div>
</div>
</div>
</div>
```
**Use Cases**: Social Feeds, Activity Feeds, Comment Threads
#### 6. Stats Skeleton
```html
<div class="skeleton-stats">
<div class="skeleton-stats__card">
<div class="skeleton skeleton-stats__label"></div>
<div class="skeleton skeleton-stats__value"></div>
<div class="skeleton skeleton-stats__trend"></div>
</div>
</div>
```
**Use Cases**: Dashboard Stats, Analytics Cards, Metrics Display
#### 7. Chart Skeleton
```html
<div class="skeleton-chart">
<div class="skeleton skeleton-chart__title"></div>
<div class="skeleton-chart__graph">
<div class="skeleton skeleton-chart__bar"></div>
<div class="skeleton skeleton-chart__bar"></div>
<div class="skeleton skeleton-chart__bar"></div>
</div>
<div class="skeleton-chart__legend">
<div class="skeleton skeleton-chart__legend-item"></div>
<div class="skeleton skeleton-chart__legend-item"></div>
</div>
</div>
```
**Use Cases**: Charts, Graphs, Data Visualizations
#### 8. Container Skeleton
```html
<div class="skeleton-container">
<!-- Any skeleton content -->
</div>
```
**Use Cases**: Generic Container mit Loading Indicator
### Skeleton Loader Features
**Shimmer Animation**:
```css
.skeleton {
background: linear-gradient(
90deg,
var(--skeleton-bg) 0%,
var(--skeleton-shimmer) 50%,
var(--skeleton-bg) 100%
);
animation: skeleton-shimmer 1.5s infinite ease-in-out;
}
```
**Dark Mode Support**:
- Automatic color adjustment via `@media (prefers-color-scheme: dark)`
- Accessible contrast ratios
**Reduced Motion Support**:
```css
@media (prefers-reduced-motion: reduce) {
.skeleton {
animation: none;
opacity: 0.5;
}
}
```
**Responsive Design**:
- Mobile-optimized layouts
- Breakpoints at 768px
---
## Backend Implementation
### LazyComponentFunction
**Location**: `src/Framework/View/Functions/LazyComponentFunction.php`
```php
final readonly class LazyComponentFunction implements TemplateFunction
{
public function __invoke(string $componentId, array $options = []): string
{
// Extract and validate options
$priority = $options['priority'] ?? 'normal';
$threshold = $options['threshold'] ?? '0.1';
$placeholder = $options['placeholder'] ?? null;
// Build HTML attributes
$attributes = [
'data-live-component-lazy' => htmlspecialchars($componentId),
'data-lazy-priority' => htmlspecialchars($priority),
'data-lazy-threshold' => htmlspecialchars($threshold)
];
// Generate placeholder HTML
return sprintf('<div %s></div>', $attributesHtml);
}
}
```
**Registration**: Automatisch in `PlaceholderReplacer` registriert
### Lazy Load Endpoint
**Route**: `GET /live-component/{id}/lazy-load`
**Controller**: `LiveComponentController::handleLazyLoad()`
```php
#[Route('/live-component/{id}/lazy-load', method: Method::GET)]
public function handleLazyLoad(string $id, HttpRequest $request): JsonResult
{
try {
$componentId = ComponentId::fromString($id);
$component = $this->componentRegistry->resolve($componentId, initialData: null);
$html = $this->componentRegistry->renderWithWrapper($component);
return new JsonResult([
'success' => true,
'html' => $html,
'state' => $component->getData()->toArray(),
'csrf_token' => $this->generateCsrfToken($componentId),
'component_id' => $componentId->toString()
]);
} catch (\Exception $e) {
return new JsonResult([
'success' => false,
'error' => $e->getMessage()
], 500);
}
}
```
**Response Format**:
```json
{
"success": true,
"html": "<div data-live-component='counter:demo'>...</div>",
"state": {
"count": 0,
"label": "Counter"
},
"csrf_token": "abc123...",
"component_id": "counter:demo"
}
```
---
## Frontend Implementation
### LazyComponentLoader
**Location**: `resources/js/modules/livecomponent/LazyComponentLoader.js`
**Features**:
- IntersectionObserver für Viewport Detection
- Priority-basierte Loading Queue
- Configurable threshold und root margin
- Error Handling mit Retry Logic
- Statistics Tracking
**Initialization**:
```javascript
// Automatic initialization via LiveComponent module
import { LiveComponent } from './modules/livecomponent/index.js';
// LazyComponentLoader wird automatisch initialisiert
LiveComponent.initLazyLoading();
```
**Manual Usage** (optional):
```javascript
import { LazyComponentLoader } from './modules/livecomponent/LazyComponentLoader.js';
import { LiveComponent } from './modules/livecomponent/index.js';
const lazyLoader = new LazyComponentLoader(LiveComponent);
lazyLoader.init();
```
### Loading Process
1. **Scan DOM** für `[data-live-component-lazy]` Elemente
2. **Register Components** mit IntersectionObserver
3. **Detect Visibility** basierend auf threshold
4. **Queue by Priority**: high → normal → low
5. **Fetch from Server**: `/live-component/{id}/lazy-load`
6. **Replace Placeholder**: Update DOM mit Component HTML
7. **Initialize Component**: `LiveComponent.init(element)`
### Configuration Options
```javascript
class LazyComponentLoader {
constructor(liveComponentManager) {
this.config = {
threshold: 0.1, // Default visibility threshold
rootMargin: '0px', // Default root margin
priorityWeights: { // Priority processing weights
high: 1,
normal: 5,
low: 10
}
};
}
}
```
---
## Performance Characteristics
### Loading Performance
**Metrics** (typical values):
- **Initial Scan**: <10ms for 100 components
- **IntersectionObserver Setup**: <5ms per component
- **Visibility Detection**: <1ms (native browser API)
- **Fetch Request**: 50-200ms (network dependent)
- **DOM Replacement**: 5-20ms per component
- **Component Initialization**: 10-50ms per component
**Total Load Time**: ~100-300ms per component (network + processing)
### Priority Queue Performance
**Processing Strategy**:
```javascript
// High priority: Process immediately
// Normal priority: 5ms delay between loads
// Low priority: 10ms delay between loads
```
**Concurrent Loading**:
- Max 3 concurrent requests (browser limit)
- Queue processes in priority order
- Automatic retry on failure (max 3 attempts)
### Memory Footprint
- **LazyComponentLoader**: ~5KB
- **Per Component**: ~500 bytes (metadata + observer)
- **100 Lazy Components**: ~55KB total overhead
---
## Best Practices
### When to Use Lazy Loading
**✅ Use Lazy Loading For**:
- Below-the-fold content
- Heavy components (charts, tables, complex UI)
- Optional features (comments, related articles)
- User-specific content (notifications, profile widgets)
- Analytics and tracking components
**❌ Don't Use Lazy Loading For**:
- Above-the-fold critical content
- Navigation elements
- Essential UI components
- Small, lightweight components
- Content needed for SEO
### Priority Guidelines
**High Priority**:
```php
{{ lazy_component('user-notifications:current', {'priority': 'high'}) }}
{{ lazy_component('shopping-cart:summary', {'priority': 'high'}) }}
```
**Normal Priority**:
```php
{{ lazy_component('article-list:category-123', {'priority': 'normal'}) }}
{{ lazy_component('comment-section:post-456', {'priority': 'normal'}) }}
```
**Low Priority**:
```php
{{ lazy_component('related-articles:post-789', {'priority': 'low'}) }}
{{ lazy_component('ad-banner:sidebar', {'priority': 'low'}) }}
```
### Skeleton Loader Selection
**Match Skeleton to Component Structure**:
```php
<!-- User Card Component Card Skeleton -->
{{ lazy_component('user-card:123', {'class': 'skeleton-card'}) }}
<!-- Data Table Component Table Skeleton -->
{{ lazy_component('analytics-table:dashboard', {'class': 'skeleton-table'}) }}
<!-- Activity Feed Feed Skeleton -->
{{ lazy_component('activity-feed:user-456', {'class': 'skeleton-feed'}) }}
```
### Threshold Configuration
**Viewport Thresholds**:
- `0.0` - Load as soon as any pixel is visible
- `0.1` - Load when 10% visible (default, recommended)
- `0.5` - Load when 50% visible
- `1.0` - Load only when fully visible
**Root Margin** (preloading):
```php
<!-- Load 200px before entering viewport -->
{{ lazy_component('image-gallery:album-1', {
'rootMargin': '200px'
}) }}
<!-- Load only when fully in viewport -->
{{ lazy_component('video-player:clip-1', {
'threshold': '1.0',
'rootMargin': '0px'
}) }}
```
---
## Error Handling
### Retry Logic
```javascript
// LazyComponentLoader retry configuration
async loadComponent(config) {
const maxRetries = 3;
let attempt = 0;
while (attempt < maxRetries) {
try {
const response = await fetch(`/live-component/${config.id}/lazy-load`);
// ... process response
return;
} catch (error) {
attempt++;
if (attempt >= maxRetries) {
this.showError(config.element, error);
}
await this.delay(1000 * attempt); // Exponential backoff
}
}
}
```
### Error Display
```javascript
showError(element, error) {
element.innerHTML = `
<div class="lazy-load-error">
<p>Failed to load component</p>
<button onclick="window.location.reload()">Retry</button>
</div>
`;
}
```
---
## Debugging
### Enable Debug Logging
```javascript
// In browser console
localStorage.setItem('livecomponent-debug', 'true');
location.reload();
```
**Debug Output**:
```
[LazyComponentLoader] Initialized
[LazyComponentLoader] Found 15 lazy components
[LazyComponentLoader] Registered: counter:lazy-1 (priority: normal)
[LazyComponentLoader] Component visible: counter:lazy-1
[LazyComponentLoader] Loading: counter:lazy-1
[LazyComponentLoader] Loaded successfully: counter:lazy-1 (142ms)
```
### Statistics
```javascript
// Get loading statistics
const stats = LiveComponent.lazyLoader.getStats();
console.log(stats);
// {
// total_components: 15,
// loaded: 8,
// pending: 7,
// failed: 0,
// average_load_time_ms: 125
// }
```
---
## Testing
### Manual Testing
```html
<!-- Test Page -->
<!DOCTYPE html>
<html>
<body>
<h1>Lazy Loading Test</h1>
<!-- Above fold - should NOT lazy load -->
{{{ counter }}}
<div style="height: 2000px;"></div>
<!-- Below fold - should lazy load -->
{{ lazy_component('timer:demo', {
'priority': 'normal',
'class': 'skeleton-card'
}) }}
<script type="module">
import { LiveComponent } from '/assets/js/main.js';
LiveComponent.initLazyLoading();
</script>
</body>
</html>
```
### E2E Testing (Playwright)
```javascript
// tests/e2e/lazy-loading.spec.js
import { test, expect } from '@playwright/test';
test('lazy loads component on scroll', async ({ page }) => {
await page.goto('/test/lazy-loading');
// Component should not be loaded initially
const lazyComponent = page.locator('[data-live-component-lazy="timer:demo"]');
await expect(lazyComponent).toBeVisible();
await expect(lazyComponent).toContainText(''); // Empty placeholder
// Scroll to component
await lazyComponent.scrollIntoViewIfNeeded();
// Wait for loading
await page.waitForSelector('[data-live-component="timer:demo"]', {
timeout: 5000
});
// Component should be loaded
const loadedComponent = page.locator('[data-live-component="timer:demo"]');
await expect(loadedComponent).toBeVisible();
await expect(loadedComponent).not.toBeEmpty();
});
```
---
## Troubleshooting
### Common Issues
**1. Component not loading**
- Check browser console for errors
- Verify component ID format: `name:instance`
- Check network tab for 404 errors
- Ensure component is registered in ComponentRegistry
**2. Skeleton loader not showing**
- Verify CSS is loaded: `component-playground.css`
- Check class name in template matches skeleton variant
- Inspect HTML for correct skeleton structure
**3. Loading too slow**
- Check network tab for request time
- Reduce rootMargin to preload earlier
- Increase priority for important components
- Optimize backend endpoint response time
**4. Multiple loads of same component**
- Ensure unique instance IDs
- Check for duplicate lazy_component() calls
- Verify IntersectionObserver cleanup
---
## Framework Integration
**Template System**: Integrated via `TemplateFunctions`
**View Module**: Uses `LiveComponentRenderer`
**HTTP**: Standard Route + Controller
**JavaScript**: Core Module with auto-initialization
**CSS**: Component Layer with @layer architecture
**Dependencies**:
- PlaceholderReplacer (template processing)
- ComponentRegistry (component resolution)
- LiveComponentController (HTTP endpoint)
- LiveComponent Module (frontend initialization)
---
## Summary
Das Lazy Loading System bietet:
**Performance**: Reduziert initiale Ladezeit um 40-60% für content-heavy Pages
**User Experience**: Professional Skeleton Loaders mit Shimmer Animation
**Developer Experience**: Simple Template Syntax `{{ lazy_component() }}`
**Flexibility**: 8 Skeleton Variants, Priority Levels, Configurable Thresholds
**Accessibility**: Dark Mode, Reduced Motion Support
**Robustness**: Error Handling, Retry Logic, Statistics Tracking
**Framework Compliance**: Value Objects, Readonly Classes, Convention over Configuration

View File

@@ -1,701 +0,0 @@
# LiveComponents Monitoring & Debugging
**Status**: ✅ Implemented
**Date**: 2025-10-09
Comprehensive monitoring and debugging infrastructure for LiveComponents system.
---
## Overview
Production-ready monitoring and development debugging tools for LiveComponents:
- **Production Monitoring**: Metrics, health checks, performance tracking
- **Development Debugging**: Debug panel, component inspector
- **Admin-Only Security**: All endpoints require admin authentication
---
## 1. Production Monitoring
### 1.1 Monitoring Controller
**Location**: `src/Framework/LiveComponents/Controllers/LiveComponentMonitoringController.php`
**Dependencies**:
- `CacheMetricsCollector` - Cache performance metrics
- `ComponentRegistry` - Component statistics
- `ComponentMetadataCache` - Metadata caching info
- `ComponentStateCache` - State caching info
- `ProcessorPerformanceTracker` - Optional template processor profiling
### 1.2 Monitoring Endpoints
#### GET `/api/livecomponents/metrics`
**Auth**: Admin only (`#[Auth(roles: ['admin'])]`)
Comprehensive system metrics including:
```json
{
"cache": {
"overall": {
"hit_rate": "85.50%",
"miss_rate": "14.50%",
"total_requests": 1000,
"average_lookup_time_ms": 0.15
},
"by_type": {
"state": { "hit_rate": "70.00%", ... },
"slot": { "hit_rate": "60.00%", ... },
"template": { "hit_rate": "80.00%", ... }
},
"performance_assessment": {
"state_cache": { "grade": "B", "meets_target": true },
"slot_cache": { "grade": "B", "meets_target": true },
"template_cache": { "grade": "A", "meets_target": true },
"overall_grade": "B+"
}
},
"registry": {
"total_components": 15,
"component_names": ["counter", "timer", "chat", ...],
"memory_estimate": 76800
},
"processors": {
"enabled": true,
"metrics": { ... }
},
"system": {
"memory_usage": 12582912,
"peak_memory": 15728640
},
"timestamp": 1696857600
}
```
**Use Cases**:
- Production monitoring dashboards
- Performance trend analysis
- Capacity planning
- Alerting integration
#### GET `/api/livecomponents/health`
**Auth**: Public (no authentication required)
Quick health check for monitoring systems:
```json
{
"status": "healthy", // or "degraded"
"components": {
"registry": true,
"cache": true
},
"warnings": [],
"timestamp": 1696857600
}
```
**HTTP Status Codes**:
- `200 OK` - System healthy
- `503 Service Unavailable` - System degraded
**Use Cases**:
- Load balancer health checks
- Uptime monitoring (Pingdom, UptimeRobot, etc.)
- Auto-scaling triggers
- Alerting systems
#### GET `/api/livecomponents/metrics/cache`
**Auth**: Admin only
Focused cache metrics:
```json
{
"overall": { ... },
"by_type": { ... },
"performance_assessment": { ... }
}
```
#### GET `/api/livecomponents/metrics/registry`
**Auth**: Admin only
Component registry statistics:
```json
{
"total_components": 15,
"component_names": [...],
"memory_estimate": 76800
}
```
#### POST `/api/livecomponents/metrics/reset`
**Auth**: Admin only
**Environment**: Development only
Reset all collected metrics:
```json
{
"message": "Metrics reset successfully",
"timestamp": 1696857600
}
```
Returns `403 Forbidden` in production.
---
## 2. Component Inspector
### 2.1 Inspector Endpoints
#### GET `/api/livecomponents/inspect/{componentId}`
**Auth**: Admin only
Detailed component inspection for debugging:
```json
{
"component": {
"id": "counter:demo",
"name": "counter",
"instance_id": "demo",
"class": "App\\Application\\LiveComponents\\CounterComponent"
},
"metadata": {
"properties": [
{
"name": "count",
"type": "int",
"nullable": false,
"hasDefault": true
}
],
"actions": [
{
"name": "increment",
"parameters": []
},
{
"name": "decrement",
"parameters": []
}
],
"constructor_params": ["id", "initialData"],
"compiled_at": "2025-10-09 01:45:23"
},
"state": {
"cached": true,
"data": {
"count": 5,
"label": "My Counter"
}
},
"cache_info": {
"metadata_cached": true,
"state_cached": true
},
"timestamp": 1696857600
}
```
**Use Cases**:
- Runtime debugging
- State inspection
- Metadata verification
- Cache status checking
- Development troubleshooting
#### GET `/api/livecomponents/instances`
**Auth**: Admin only
List all available component types:
```json
{
"total": 15,
"instances": [
{
"name": "counter",
"class": "App\\Application\\LiveComponents\\CounterComponent",
"metadata_cached": true
},
{
"name": "timer",
"class": "App\\Application\\LiveComponents\\TimerComponent",
"metadata_cached": true
}
],
"timestamp": 1696857600
}
```
**Use Cases**:
- Component discovery
- System overview
- Debugging aid
- Cache status overview
---
## 3. Development Debug Panel
### 3.1 Debug Panel Renderer
**Location**: `src/Framework/LiveComponents/Debug/DebugPanelRenderer.php`
**Features**:
- Auto-rendered in development environment
- Collapsible panel with component details
- State inspection with JSON preview
- Performance metrics (render time, memory usage)
- Cache hit/miss indicators
- Component metadata display
- Zero overhead in production
### 3.2 Activation
**Environment Variables**:
```bash
# Option 1: Development environment
APP_ENV=development
# Option 2: Explicit debug flag
LIVECOMPONENT_DEBUG=true
```
**Auto-Detection**:
```php
DebugPanelRenderer::shouldRender()
// Returns true if APP_ENV=development OR LIVECOMPONENT_DEBUG=true
```
### 3.3 Debug Panel Display
**Visual Example**:
```
┌─────────────────────────────────────────────────────┐
│ 🔧 counter ▼ │
├─────────────────────────────────────────────────────┤
│ Component: App\Application\LiveComponents\... │
│ Render Time: 2.35ms │
│ Memory: 2.5 MB │
│ Cache: ✅ HIT │
│ │
│ State: │
│ { │
│ "count": 5, │
│ "label": "My Counter" │
│ } │
│ │
│ Actions: increment, decrement, reset │
│ Metadata: 3 properties, 3 actions │
└─────────────────────────────────────────────────────┘
```
**Features**:
- Click header to collapse/expand
- Inline styles (no external CSS needed)
- JSON-formatted state with syntax highlighting
- Performance metrics
- Cache status indicators
### 3.4 Integration
**Automatic Injection**:
- Debug panel automatically appended after component rendering
- Only in development environment
- No code changes required in components
- Fully transparent to production
**Component Registry Integration**:
```php
// In ComponentRegistry::render()
if ($this->debugPanel !== null && DebugPanelRenderer::shouldRender()) {
$renderTime = (microtime(true) - $startTime) * 1000;
$html .= $this->renderDebugPanel($component, $renderTime, $cacheHit);
}
```
---
## 4. Metrics Collection
### 4.1 Cache Metrics Collector
**Location**: `src/Framework/LiveComponents/Cache/CacheMetricsCollector.php`
**Features**:
- Real-time metric collection
- Per-cache-type tracking (State, Slot, Template)
- Aggregate metrics across all caches
- Performance target validation
- Automatic performance grading (A-F)
**Metrics Tracked**:
```php
public function recordHit(CacheType $cacheType, float $lookupTimeMs): void
public function recordMiss(CacheType $cacheType, float $lookupTimeMs): void
public function recordInvalidation(CacheType $cacheType): void
public function updateSize(CacheType $cacheType, int $size): void
```
**Performance Assessment**:
```php
$assessment = $collector->assessPerformance();
// [
// 'state_cache' => [
// 'target' => '70.0%',
// 'actual' => '85.5%',
// 'meets_target' => true,
// 'grade' => 'A'
// ],
// ...
// ]
```
**Performance Targets**:
- State Cache: 70% hit rate (faster initialization)
- Slot Cache: 60% hit rate (faster resolution)
- Template Cache: 80% hit rate (faster rendering)
**Grading Scale**:
- A: ≥90% hit rate
- B: 80-89%
- C: 70-79%
- D: 60-69%
- F: <60%
### 4.2 Performance Warnings
**Automatic Detection**:
```php
if ($collector->hasPerformanceIssues()) {
$warnings = $collector->getPerformanceWarnings();
// [
// "State cache hit rate (65.2%) below target (70.0%)",
// "Template cache hit rate (75.3%) below target (80.0%)"
// ]
}
```
**Integration**:
- Health check endpoint includes warnings
- Monitoring alerts can trigger on warnings
- Debug panel shows performance issues
---
## 5. Processor Performance Tracking
### 5.1 Performance Tracker
**Location**: `src/Framework/View/ProcessorPerformanceTracker.php`
**Features**:
- Optional profiling (disable in production)
- Minimal overhead (<0.1ms when enabled)
- Per-processor metrics
- Performance grading (A-F)
- Bottleneck identification
**Activation**:
```bash
# Enable via environment variable
ENABLE_TEMPLATE_PROFILING=true
```
**Metrics Tracked**:
```php
public function measure(string $processorClass, callable $execution): string
// Tracks:
// - Execution time (ms)
// - Memory usage (bytes)
// - Invocation count
// - Average/min/max times
```
**Performance Report**:
```php
$report = $tracker->generateReport();
// ProcessorPerformanceReport {
// processors: [
// 'PlaceholderReplacer' => [
// 'total_time_ms' => 15.3,
// 'invocation_count' => 100,
// 'average_time_ms' => 0.153,
// 'grade' => 'A'
// ],
// ...
// ],
// bottlenecks: ['ForProcessor'],
// overall_grade: 'B+'
// }
```
---
## 6. Usage Examples
### 6.1 Production Monitoring
**Prometheus/Grafana Integration**:
```bash
# Scrape metrics endpoint
curl -s https://api.example.com/api/livecomponents/metrics \
-H "Authorization: Bearer $ADMIN_TOKEN" \
| jq '.cache.overall.hit_rate'
```
**Health Check Monitoring**:
```bash
# Simple uptime check
curl -f https://api.example.com/api/livecomponents/health || alert_team
# Detailed health with warnings
curl -s https://api.example.com/api/livecomponents/health | jq '.warnings[]'
```
**Alerting Rules**:
```yaml
# Prometheus alert rule
- alert: LiveComponentsCacheDegraded
expr: livecomponents_cache_hit_rate < 0.7
for: 5m
annotations:
summary: "LiveComponents cache performance degraded"
```
### 6.2 Development Debugging
**Component Inspection**:
```bash
# Inspect specific component instance
curl https://localhost/api/livecomponents/inspect/counter:demo \
-H "Authorization: Bearer $ADMIN_TOKEN" \
| jq '.state.data'
# List all available components
curl https://localhost/api/livecomponents/instances \
-H "Authorization: Bearer $ADMIN_TOKEN" \
| jq '.instances[].name'
```
**Debug Panel**:
```bash
# Enable debug panel
export APP_ENV=development
# Or via dedicated flag
export LIVECOMPONENT_DEBUG=true
# Debug panel auto-appears in rendered components
# Click panel header to collapse/expand
```
---
## 7. Security Considerations
### 7.1 Authentication
**Admin-Only Endpoints**:
- All monitoring endpoints require `admin` role
- Health check endpoint is public (by design)
- Component inspector admin-only
- Metrics reset admin-only + development-only
**Authentication Pattern**:
```php
#[Route('/api/livecomponents/metrics', method: Method::GET)]
#[Auth(roles: ['admin'])]
public function metrics(): JsonResult
```
### 7.2 Environment Restrictions
**Production Safety**:
- Debug panel disabled in production (APP_ENV check)
- Metrics reset blocked in production
- Performance tracking optional (minimal overhead)
**Development Features**:
- Debug panel auto-enabled in development
- Metrics reset available
- Component inspector with full details
---
## 8. Performance Impact
### 8.1 Production Overhead
**Metrics Collection**:
- **Memory**: ~5KB per component metadata
- **CPU**: <0.1ms per metric recording
- **Storage**: In-memory metrics (no persistence)
**Health Check Endpoint**:
- **Response Time**: <10ms
- **Memory**: Negligible
- **CPU**: Minimal
**Monitoring Endpoints**:
- **Response Time**: 50-100ms (includes metric aggregation)
- **Memory**: Temporary allocation for JSON serialization
- **CPU**: Metric calculation and formatting
### 8.2 Development Overhead
**Debug Panel**:
- **Render Time**: +1-2ms per component
- **Memory**: +10KB per component (metadata + panel HTML)
- **Zero Overhead**: Completely disabled in production
**Component Inspector**:
- **Query Time**: 10-50ms (metadata + state lookup)
- **Memory**: Temporary allocation
- **No Impact**: On-demand only
---
## 9. Integration with Performance Optimizations
### 9.1 Metrics Integration
**Cache Metrics**:
- ComponentMetadataCache reports to CacheMetricsCollector
- ComponentStateCache reports to CacheMetricsCollector
- SlotContentCache reports to CacheMetricsCollector
- TemplateFragmentCache reports to CacheMetricsCollector
**Performance Tracking**:
- ProcessorPerformanceTracker integrates with TemplateProcessor
- Optional profiling via environment variable
- Minimal overhead when disabled
### 9.2 Debug Integration
**Debug Panel Data Sources**:
- ComponentMetadataCache for metadata
- ComponentStateCache for state
- Render timing from ComponentRegistry
- Memory usage from PHP runtime
**Component Inspector**:
- ComponentMetadataCache for structure
- ComponentStateCache for runtime state
- ComponentRegistry for class mapping
---
## 10. Future Enhancements
### 10.1 Planned Features
**Metrics Persistence**:
- Store metrics in database for historical analysis
- Metric retention policies
- Trend analysis and visualization
**Advanced Alerting**:
- Custom alert rules
- Slack/Email notifications
- Automated incident creation
**Component Profiler**:
- Detailed performance profiling per component
- Flame graphs for render pipeline
- Bottleneck identification
**Interactive Debug UI**:
- Web-based debug panel (alternative to inline)
- State manipulation
- Action testing
- Component playground
### 10.2 Integration Opportunities
**APM Integration**:
- New Relic integration
- Datadog integration
- Elastic APM integration
**Logging Integration**:
- Structured logging for all metrics
- Log aggregation (ELK, Splunk)
- Metric-to-log correlation
---
## 11. Troubleshooting
### 11.1 Common Issues
**Metrics Not Updating**:
```bash
# Check if metrics collector is registered
curl https://localhost/api/livecomponents/metrics/cache | jq '.overall.total_requests'
# Reset metrics (development only)
curl -X POST https://localhost/api/livecomponents/metrics/reset \
-H "Authorization: Bearer $ADMIN_TOKEN"
```
**Debug Panel Not Showing**:
```bash
# Verify environment
echo $APP_ENV # Should be "development"
echo $LIVECOMPONENT_DEBUG # Should be "true"
# Check DebugPanelRenderer registration
# Should be auto-registered via DebugPanelInitializer
```
**Health Check Failing**:
```bash
# Check detailed health status
curl -s https://localhost/api/livecomponents/health | jq '.'
# Check warnings
curl -s https://localhost/api/livecomponents/health | jq '.warnings'
```
### 11.2 Performance Degradation
**Cache Hit Rate Low**:
- Check cache TTL configuration
- Verify cache key generation
- Review cache invalidation patterns
- Analyze workload patterns
**High Memory Usage**:
- Check component count (registry statistics)
- Review metadata cache size
- Analyze state cache retention
- Consider cache eviction policies
---
## Summary
Comprehensive monitoring and debugging infrastructure providing:
**Production**:
- ✅ Metrics endpoint (cache, registry, performance)
- ✅ Health check endpoint (200/503 responses)
- ✅ Cache metrics collection with grading
- ✅ Performance tracking (optional)
- ✅ Admin-only security
**Development**:
- ✅ Debug panel (auto-rendered)
- ✅ Component inspector (detailed runtime info)
- ✅ Component instance listing
- ✅ Metrics reset capability
- ✅ Zero production overhead
**Integration**:
- ✅ Works with all performance optimizations
- ✅ Integrates with cache layers
- ✅ Hooks into component registry
- ✅ Template processor profiling support
- ✅ Framework-compliant patterns

View File

@@ -1,540 +0,0 @@
# LiveComponents Observability System
**Complete observability, metrics, and debugging infrastructure for LiveComponents.**
## Overview
The Observability system provides comprehensive monitoring, debugging, and performance analysis for LiveComponents through:
1. **Backend Metrics Collection** - ComponentMetricsCollector for server-side tracking
2. **Frontend DevTools** - Interactive debugging overlay with real-time insights
3. **Performance Profiling** - Execution timeline, flamegraph, and memory tracking
4. **DOM Badges** - Visual component identification on the page
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ LiveComponent Lifecycle │
└────────────────────┬────────────────────────────────────────────┘
┌───────────┴───────────┐
│ │
┌────▼────┐ ┌──────▼──────┐
│ Backend │ │ Frontend │
│ Metrics │ │ DevTools │
└────┬────┘ └──────┬──────┘
│ │
┌────▼────────────┐ ┌───▼─────────────────┐
│ ComponentMetrics │ │ LiveComponentDevTools│
│ Collector │ │ (Overlay) │
│ │ │ │
│ - Render times │ │ - Component Tree │
│ - Action metrics │ │ - Action Log │
│ - Cache stats │ │ - Event Log │
│ - Event tracking │ │ - Network Log │
│ - Upload metrics │ │ - Performance Tab │
│ - Batch ops │ │ - DOM Badges │
└──────────────────┘ └──────────────────────┘
```
## Backend Metrics Collection
### ComponentMetricsCollector
**Location**: `src/Framework/LiveComponents/Observability/ComponentMetricsCollector.php`
**Purpose**: Server-side metrics collection for component performance and behavior tracking.
**Metrics Categories**:
1. **Render Metrics**
- `livecomponent_renders_total` - Total component renders (cached/non-cached)
- `livecomponent_render_duration_ms` - Render duration histogram
2. **Action Metrics**
- `livecomponent_actions_total` - Total actions executed (success/error)
- `livecomponent_action_duration_ms` - Action execution time histogram
- `livecomponent_action_errors_total` - Failed action count
3. **Cache Metrics**
- `livecomponent_cache_hits_total` - Cache hit count
- `livecomponent_cache_misses_total` - Cache miss count
- Cache hit rate calculated in summary
4. **Event Metrics**
- `livecomponent_events_dispatched_total` - Events dispatched by components
- `livecomponent_events_received_total` - Events received by components
5. **Hydration Metrics**
- `livecomponent_hydration_duration_ms` - Client-side hydration time
6. **Batch Operations**
- `livecomponent_batch_operations_total` - Batch operations executed
- `livecomponent_batch_success_total` - Successful batch items
- `livecomponent_batch_failure_total` - Failed batch items
- `livecomponent_batch_size` - Batch size histogram
- `livecomponent_batch_duration_ms` - Batch execution time
7. **Fragment Updates**
- `livecomponent_fragment_updates_total` - Fragment update count
- `livecomponent_fragment_count` - Fragments per update
- `livecomponent_fragment_duration_ms` - Fragment update duration
8. **Upload Metrics**
- `livecomponent_upload_chunks_total` - Upload chunks processed
- `livecomponent_upload_chunk_duration_ms` - Chunk upload time
- `livecomponent_uploads_completed_total` - Complete uploads
- `livecomponent_upload_total_duration_ms` - Total upload duration
- `livecomponent_upload_chunk_count` - Chunks per upload
### Usage Example
```php
use App\Framework\LiveComponents\Observability\ComponentMetricsCollector;
final readonly class LiveComponentService
{
public function __construct(
private readonly ComponentMetricsCollector $metricsCollector
) {}
public function renderComponent(string $componentId): string
{
$startTime = microtime(true);
// Render logic
$html = $this->renderer->render($componentId);
$duration = (microtime(true) - $startTime) * 1000; // milliseconds
$this->metricsCollector->recordRender($componentId, $duration, $cached = false);
return $html;
}
public function executeAction(string $componentId, string $actionName): array
{
$startTime = microtime(true);
try {
$result = $this->actionExecutor->execute($componentId, $actionName);
$duration = (microtime(true) - $startTime) * 1000;
$this->metricsCollector->recordAction($componentId, $actionName, $duration, true);
return $result;
} catch (\Exception $e) {
$duration = (microtime(true) - $startTime) * 1000;
$this->metricsCollector->recordAction($componentId, $actionName, $duration, false);
throw $e;
}
}
}
```
### Metrics Export
**Summary Statistics**:
```php
$summary = $metricsCollector->getSummary();
// Returns:
// [
// 'total_renders' => 150,
// 'total_actions' => 45,
// 'cache_hits' => 100,
// 'cache_misses' => 50,
// 'total_events' => 30,
// 'action_errors' => 2,
// 'avg_render_time_ms' => 25.5,
// 'avg_action_time_ms' => 15.3,
// 'cache_hit_rate' => 66.67
// ]
```
**Prometheus Export**:
```php
$prometheus = $metricsCollector->exportPrometheus();
// Returns Prometheus-formatted metrics:
// # HELP LiveComponents metrics
// # TYPE livecomponent_* counter/histogram
//
// livecomponent_renders_total{component_id=user-profile,cached=false} 45.00 1704067200
// livecomponent_render_duration_ms{component_id=user-profile,cached=false} 25.50 1704067200
// ...
```
### Performance Integration
The ComponentMetricsCollector integrates with the Framework's PerformanceCollector:
```php
$collector = new ComponentMetricsCollector($performanceCollector);
$collector->recordRender('comp-123', 45.5, false);
// Also records to PerformanceCollector:
// - Metric name: "livecomponent.render.comp-123"
// - Category: RENDERING
// - Duration: 45.5ms
// - Metadata: ['cached' => false]
```
## Frontend DevTools
### LiveComponentDevTools
**Location**: `resources/js/modules/LiveComponentDevTools.js`
**Purpose**: Interactive debugging overlay for real-time component monitoring and analysis.
**Features**:
1. **Component Tree Tab**
- Hierarchical view of all active components
- Component state inspection
- Real-time component count
- Component selection for detailed view
2. **Actions Tab**
- Chronological action execution log
- Action name, componentId, duration, timestamp
- Success/failure status
- Filter by component or action name
- Clear log functionality
- Export as JSON
3. **Events Tab**
- Event dispatch and receive tracking
- Event name, source, timestamp
- Event data inspection
- Filter by event type
4. **Network Tab**
- HTTP request tracking
- Method, URL, status code, duration
- Request/response body inspection
- Filter by status or method
- Performance analysis
5. **Performance Tab** (NEW)
- **Recording Controls**: Start/stop performance profiling
- **Summary Statistics**: Total events, actions, renders, avg times
- **Flamegraph**: Execution breakdown by component/action
- **Timeline**: Chronological execution visualization
- **Memory Chart**: JavaScript heap usage over time
### Performance Profiling
**Recording**:
```javascript
// Toggle recording with button or programmatically
devTools.togglePerformanceRecording();
// Recording captures:
// - Action execution times
// - Component render times
// - Memory snapshots (every 500ms)
// - Execution timeline data
```
**Data Structures**:
```javascript
// Performance recording entry
{
type: 'action' | 'render',
componentId: 'comp-abc-123',
actionName: 'handleClick', // for actions only
duration: 25.5, // milliseconds
startTime: 1000.0, // performance.now()
endTime: 1025.5, // performance.now()
timestamp: 1704067200 // Date.now()
}
// Memory snapshot
{
timestamp: 1704067200,
usedJSHeapSize: 25000000,
totalJSHeapSize: 50000000,
jsHeapSizeLimit: 2000000000
}
```
**Visualizations**:
1. **Flamegraph** - Top 10 most expensive operations
- Horizontal bars showing total execution time
- Execution count (×N)
- Average time per execution
- Color coded: Actions (yellow), Renders (blue)
2. **Timeline** - Chronological execution view
- Horizontal bars showing when and how long
- Stacked vertically for readability
- Time labels (0ms, middle, end)
- Limited to 12 concurrent events for clarity
3. **Memory Chart** - Memory usage over time
- Used Heap, Total Heap, Heap Limit, Delta
- SVG line chart visualization
- Color coded delta (green = reduction, red = increase)
### DOM Badges
**Purpose**: Visual component identification directly on the page.
**Features**:
- Badge shows component name and truncated ID
- Action counter updates in real-time
- Click badge to focus component in DevTools
- Hover badge to highlight component with blue outline
- Toggle visibility with "⚡ Badges" button
**Badge Appearance**:
- Dark semi-transparent background (#1e1e1e, 95% opacity)
- Blue border (#007acc) changing to green (#4ec9b0) on hover
- Backdrop filter (4px blur) for modern glass-morphism
- Positioned at top-left of component element
**Badge Content**:
```
⚡ UserProfile (comp-abc1...)
Actions: 5
```
**Auto-Management**:
- Created when component initializes
- Updated when actions execute
- Removed when component destroyed
- Position updates on DOM changes (MutationObserver)
### Keyboard Shortcuts
- **Ctrl+Shift+D**: Toggle DevTools visibility
### DevTools Initialization
**Automatic** (Development only):
```html
<html data-env="development">
<!-- DevTools automatically initializes -->
</html>
```
**Manual**:
```javascript
import { LiveComponentDevTools } from './modules/LiveComponentDevTools.js';
const devTools = new LiveComponentDevTools();
// Auto-initializes if data-env="development"
```
## Integration with LiveComponent Lifecycle
### Event-Driven Architecture
The Observability system integrates via custom events:
```javascript
// Component Initialization
document.dispatchEvent(new CustomEvent('livecomponent:registered', {
detail: {
componentId: 'comp-abc-123',
componentName: 'UserProfile',
initialState: { userId: 1, name: 'John' }
}
}));
// Action Execution
document.dispatchEvent(new CustomEvent('livecomponent:action', {
detail: {
componentId: 'comp-abc-123',
actionName: 'handleClick',
startTime: 1000.0,
endTime: 1025.5,
duration: 25.5,
success: true
}
}));
// Component Destruction
document.dispatchEvent(new CustomEvent('livecomponent:destroyed', {
detail: {
componentId: 'comp-abc-123'
}
}));
```
### Backend Integration
```php
// In LiveComponent implementation
final class UserProfileComponent
{
public function __construct(
private readonly ComponentMetricsCollector $metrics
) {}
public function render(): string
{
$start = microtime(true);
$html = $this->renderTemplate();
$duration = (microtime(true) - $start) * 1000;
$this->metrics->recordRender($this->componentId, $duration, false);
return $html;
}
public function handleAction(string $actionName): array
{
$start = microtime(true);
try {
$result = $this->executeAction($actionName);
$duration = (microtime(true) - $start) * 1000;
$this->metrics->recordAction($this->componentId, $actionName, $duration, true);
return $result;
} catch (\Exception $e) {
$duration = (microtime(true) - $start) * 1000;
$this->metrics->recordAction($this->componentId, $actionName, $duration, false);
throw $e;
}
}
}
```
## Testing
### Backend Tests
**Location**: `tests/Framework/LiveComponents/Observability/ComponentMetricsCollectorSimpleTest.php`
**Coverage**:
- ✅ Metrics recording (render, action, cache, events, etc.)
- ✅ Summary statistics calculation
- ✅ Prometheus export format
- ✅ Cache hit rate calculation
- ✅ Error tracking
- ✅ Multiple component tracking
- ✅ Reset functionality
- ✅ Edge cases (zero operations, all hits/misses)
**Run Tests**:
```bash
docker exec php ./vendor/bin/pest tests/Framework/LiveComponents/Observability/
```
**Test Results**: ✅ 20 passed (35 assertions)
### Frontend Tests
**Location**: `tests/Feature/LiveComponentDevToolsTest.php`
**Coverage**:
- ✅ DevTools initialization (development/production)
- ✅ Component tracking (initialization, actions, state, destruction)
- ✅ Network logging
- ✅ Action log filtering and export
- ✅ DOM badge management
- ✅ Performance recording
- ✅ Memory snapshots
- ✅ Data aggregation
- ✅ Byte formatting
- ✅ Performance summary calculation
## Performance Characteristics
### Backend Metrics
**Memory Usage**:
- ComponentMetricsCollector: ~50KB base memory
- Per metric: ~1KB (including labels and metadata)
- Typical project: ~500KB for 500 metrics
**Performance Impact**:
- Metric recording: <0.1ms per operation
- Summary calculation: <5ms for 1000 metrics
- Prometheus export: <10ms for 1000 metrics
**Prometheus Integration**: ✅ Full Prometheus format support for external monitoring
### Frontend DevTools
**Memory Usage**:
- DevTools overlay: ~100KB base
- Per component: ~2KB in tree
- Per action log entry: ~500 bytes
- Performance recording (100 entries): ~50KB
**Performance Impact**:
- Event listener overhead: <0.01ms per event
- Badge rendering: <1ms per badge
- DOM mutation observer: Batched, minimal impact
- Performance recording: <0.1ms per recording entry
**Data Limits**:
- Action log: Last 100 entries auto-trimmed
- Performance recording: Last 100 entries
- Memory snapshots: Last 100 snapshots
- Prevents unbounded memory growth
## Best Practices
### Backend Metrics
1. **Selective Recording**: Record only relevant metrics in production
2. **Batch Operations**: Use batch recording methods for multiple operations
3. **Regular Reset**: Reset metrics periodically to prevent memory buildup
4. **Export Strategy**: Export to monitoring systems (Prometheus, Grafana) regularly
### Frontend DevTools
1. **Development Only**: Never enable in production (data-env check)
2. **Performance Recording**: Use recording only when actively debugging
3. **Badge Visibility**: Disable badges when not needed to reduce DOM overhead
4. **Log Management**: Clear logs regularly during long debugging sessions
5. **Export Data**: Export action logs for offline analysis
### Integration
1. **Event Consistency**: Use standard event names for consistent tracking
2. **Error Handling**: Always record failed actions with proper error context
3. **Component Naming**: Use descriptive component names for easier debugging
4. **Action Granularity**: Keep actions focused and well-named
## Color Scheme (VS Code Dark Theme)
All visualizations use consistent VS Code dark theme colors:
- **Primary Blue** (#569cd6): Component names, borders
- **Yellow** (#dcdcaa): Actions, metrics, numbers
- **Green** (#4ec9b0): Success, cache hits, memory reduction
- **Red** (#f48771): Errors, failures, memory increase
- **Gray** (#858585): Subdued text, labels
- **Dark Background** (#1e1e1e): Panels, overlays
- **Border** (#3c3c3c): Separators, containers
## Summary
The LiveComponents Observability system provides:
**Comprehensive Metrics** - Backend tracking of all component operations
**Real-Time Debugging** - Interactive DevTools overlay with 5 tabs
**Performance Profiling** - Flamegraph, timeline, and memory analysis
**Visual Identification** - DOM badges for quick component location
**Production Ready** - Prometheus export and performance optimized
**Developer Experience** - Keyboard shortcuts, filtering, export
**Fully Tested** - 20 backend tests, integration tests
**Framework Integration** - Event-driven, lifecycle-aware
This system enables developers to:
- Monitor component performance in real-time
- Debug issues with comprehensive logging
- Analyze performance bottlenecks with profiling tools
- Track metrics for production monitoring
- Visualize component hierarchy and relationships
- Export data for offline analysis

View File

@@ -1,725 +0,0 @@
# LiveComponents Performance Optimizations
Umfassende Dokumentation aller Performance-Optimierungen für das LiveComponents-System.
## Übersicht
Das LiveComponents-System wurde für **maximale Performance** optimiert mit messbaren Verbesserungen in allen kritischen Bereichen:
**Gesamt-Performance-Steigerung**: ~70-80% schnelleres Component Rendering
**Validiert durch Benchmarks**: Alle Performance-Claims wurden durch automatisierte Benchmarks in `tests/Performance/LiveComponentsPerformanceBenchmark.php` validiert.
## Optimization Layers
```
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: Component Registry (Metadata Cache) │
│ ~90% faster registration, ~85% faster lookup │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Layer 2: Multi-Layer Caching (State/Slot/Template) │
│ ~70% faster init, ~60% faster slots, ~80% faster templates │
└─────────────────────────────────────────────────────────────┐
┌─────────────────────────────────────────────────────────────┐
│ Layer 3: Template Processing Optimization │
│ ~30-40% faster via processor chain optimization │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Layer 4: Smart Invalidation & Memory Management │
│ Minimal cache clearing, efficient memory usage │
└─────────────────────────────────────────────────────────────┘
```
## 1. Component Registry Optimizations
### Compiled Metadata Caching
**Problem**: Reflection overhead bei jedem Component-Zugriff (~5-10ms pro Component)
**Solution**: `ComponentMetadataCache` mit vorcompilierten Metadata
**Performance Gain**: ~99% faster metadata access (5-10ms → ~0.01ms)
**Implementation**:
```php
// ComponentMetadataCache warmup beim Registry-Init
private function buildNameMap(): array
{
// ... build map ...
// Batch warm metadata cache (~85% faster)
if (!empty($classNames)) {
$this->metadataCache->warmCache($classNames);
}
return $map;
}
// Fast metadata access - no reflection
$metadata = $registry->getMetadata('counter');
$hasAction = $metadata->hasAction('increment'); // ~0.01ms
```
**Key Features**:
- Pre-compiled properties, actions, constructor params
- Automatic staleness detection via file modification time
- 24-hour TTL (metadata changes rarely)
- Batch operations for registry initialization
- ~5KB memory per cached component
### Metadata Components
**CompiledComponentMetadata**:
- Component name and class name
- Public properties with types
- Action methods with signatures
- Constructor parameters
- Attributes
- Compiled timestamp for staleness check
**ComponentMetadataCompiler**:
- One-time reflection per component
- Extracts all metadata upfront
- Batch compilation support
**ComponentMetadataCache**:
- Long TTL caching (24h)
- Automatic staleness detection
- Batch warmup for startup performance
### Performance Metrics
| Operation | Before | After | Improvement |
|-----------|--------|-------|-------------|
| Component Registration | 5-10ms | 0.5ms | **90% faster** |
| Metadata Lookup | 2-3ms | 0.01ms | **99% faster** |
| Registry Initialization | 50-100ms | 5-10ms | **85% faster** |
## 2. Multi-Layer Caching System
### Component State Cache
**Performance**: ~70% faster component initialization
**Key Optimizations**:
- State hash-based cache keys
- Auto-TTL per component type (counter: 5min, layout: 24h)
- Lazy invalidation with timestamps
- Batch state operations
```php
// Auto-optimized TTL
$cache->storeWithAutoTTL($componentId, $state, 'counter');
// Fast retrieval
$cachedState = $cache->retrieve($componentId, $currentState);
// ~70% faster than fresh initialization
```
### Slot Content Cache
**Performance**: ~60% faster slot resolution
**Key Optimizations**:
- Content hash-based automatic invalidation
- Batch operations for multiple slots
- Shared slot caching across component instances
```php
// Batch store - single cache operation
$cache->storeBatch($componentId, [
'header' => $headerHtml,
'footer' => $footerHtml,
'sidebar' => $sidebarHtml
]);
// Batch retrieve - ~60% faster
$cachedSlots = $cache->getBatch($componentId, $slotNames);
```
### Template Fragment Cache
**Performance**: ~80% faster template rendering
**Key Optimizations**:
- Remember pattern for elegant caching
- Static template caching (layouts, headers)
- Data hash-based auto-invalidation
- Variant support for template variations
```php
// Remember pattern - cache-aware rendering
$html = $cache->remember(
componentType: 'card',
data: $templateData,
callback: fn() => $this->renderer->render('card.view.php', $templateData),
ttl: Duration::fromHours(2)
);
// ~80% faster on cache hit
```
### Cache Performance Metrics
| Cache Type | Hit Rate Target | Typical Hit Rate | Performance Gain |
|------------|-----------------|------------------|------------------|
| State | 70%+ | 85-90% | ~70% faster |
| Slot | 60%+ | 70-80% | ~60% faster |
| Template | 80%+ | 90-95% | ~80% faster |
## 3. Template Processing Optimization
### Processor Chain Optimization
**Performance**: ~30-40% schnellere Template-Verarbeitung durch optimierte Processor-Reihenfolge
**Key Optimizations**:
- Template-Content-Analyse für Processor-Relevanz
- Dynamische Processor-Reihenfolge basierend auf Template-Features
- Früher Exit für irrelevante Processors
- Cached Processor-Order (24h TTL)
```php
// Automatische Processor-Optimierung im TemplateProcessor
$processors = $this->chainOptimizer !== null
? $this->optimizeProcessorChain($this->stringProcessors, $html)
: $this->stringProcessors;
// Optimierte Reihenfolge: Häufig verwendete Processors zuerst
// 1. PlaceholderReplacer (Score: 100 + placeholder_count)
// 2. IfProcessor (Score: 80 + if_count * 5)
// 3. ForProcessor (Score: 70 + for_count * 10)
// 4. ComponentProcessor (Score: 60 + component_count * 8)
// ...
```
**Processor Scoring Strategy**:
- **Häufigkeit**: Häufig verwendete Processors bekommen höheren Score
- **Performance**: Schnelle Processors werden bevorzugt
- **Relevanz**: Irrelevante Processors (Score 0) werden übersprungen
### Compiled Template Caching
**Performance**: ~50-60% schnelleres Re-Rendering mit unterschiedlichen Daten
**Key Features**:
- Cached vorverarbeitete Templates (AST)
- Staleness-Detection via File-Modification-Time
- Strukturiertes Caching statt nur HTML
```php
// CompiledTemplate Value Object
final readonly class CompiledTemplate
{
public function __construct(
public string $templatePath,
public array $placeholders, // Extracted placeholders
public array $components, // Referenced components
public array $instructions, // Processor instructions
public int $compiledAt, // Compilation timestamp
public Hash $contentHash // Content hash for validation
) {}
}
// Usage via CompiledTemplateCache
$compiled = $this->compiledTemplateCache->remember(
$templatePath,
fn($content) => $this->compiler->compile($content)
);
```
### Processor Performance Tracking
**Performance**: Minimal overhead (< 0.1ms), nur Development/Profiling
**Key Features**:
- Execution Time Tracking pro Processor
- Memory Usage Measurement
- Performance Grade Calculation (A-F)
- Bottleneck Identification (> 10ms threshold)
```php
// Optional Performance Tracking (aktiviert via ENABLE_TEMPLATE_PROFILING=true)
if ($this->performanceTracker !== null) {
$html = $this->performanceTracker->measure(
$processorClass,
fn() => $processor->process($html, $context)
);
}
// Performance Report generieren
$report = $this->performanceTracker->generateReport();
/*
Processor Details:
--------------------------------------------------------------------------------
PlaceholderReplacer | 1250 calls | Avg: 0.45ms | Grade: A
IfProcessor | 850 calls | Avg: 1.20ms | Grade: B
ForProcessor | 420 calls | Avg: 3.80ms | Grade: B
ComponentProcessor | 180 calls | Avg: 8.50ms | Grade: C
*/
```
### Template Processing Performance Metrics
| Optimization | Performance Gain | Memory Impact | Cache Strategy |
|--------------|------------------|---------------|----------------|
| Processor Chain Optimization | ~30-40% faster | Minimal | 24h TTL, structure-based key |
| Compiled Template Cache | ~50-60% faster | ~5KB/template | 24h TTL, file staleness detection |
| Performance Tracking | < 0.1ms overhead | ~2KB/processor | Development only |
**Integration**:
```php
// In TemplateRendererInitializer
$chainOptimizer = new ProcessorChainOptimizer($cache);
$compiledTemplateCache = new CompiledTemplateCache($cache);
// Performance Tracker nur in Development/Profiling
$performanceTracker = null;
if (getenv('ENABLE_TEMPLATE_PROFILING') === 'true') {
$performanceTracker = new ProcessorPerformanceTracker();
$performanceTracker->enable();
}
$templateProcessor = new TemplateProcessor(
domProcessors: $doms,
stringProcessors: $strings,
container: $this->container,
chainOptimizer: $chainOptimizer,
compiledTemplateCache: $compiledTemplateCache,
performanceTracker: $performanceTracker
);
```
## 4. Event System Optimization (SSE)
### Event Batching for SSE
**Performance**: ~40-50% reduzierte HTTP-Overhead durch Event-Batching
**Key Features**:
- Time-based Batching (default: 100ms)
- Size-based Batching (default: 10 events)
- Automatic Flush Management
- Optional per Channel
```php
// Enable batching in SseBroadcaster
$broadcaster = $container->get(SseBroadcaster::class);
$broadcaster->enableBatching(
maxBatchSize: 10,
maxBatchDelayMs: 100
);
// Events werden automatisch gebatched
$broadcaster->broadcastComponentUpdate($componentId, $state, $html);
$broadcaster->broadcastComponentUpdate($componentId2, $state2, $html2);
// ... bis zu 10 Events oder 100ms vergehen
// Manual flush wenn nötig
$broadcaster->flushAll();
// Disable batching
$broadcaster->disableBatching(); // Flushed automatisch vor Deaktivierung
```
**Batching Strategy**:
- **Size-based**: Batch wird gesendet wenn `maxBatchSize` erreicht
- **Time-based**: Batch wird gesendet nach `maxBatchDelayMs` Millisekunden
- **Mixed**: Whichever condition is met first
**Batch Event Format**:
```json
{
"type": "batch",
"count": 5,
"events": [
{"event": "component-update", "data": {...}, "id": "..."},
{"event": "component-update", "data": {...}, "id": "..."},
...
]
}
```
### SSE Performance Metrics
| Optimization | Performance Gain | Bandwidth Reduction | Latency Impact |
|--------------|------------------|---------------------|----------------|
| Event Batching | ~40-50% faster | ~30% less data | +100ms max delay |
| Dead Connection Cleanup | ~10% faster | N/A | Automatic |
| Channel-based Routing | ~20% faster | N/A | Negligible |
**Client-Side Batch Handling**:
```javascript
// In SSE client
eventSource.addEventListener('batch', (e) => {
const batch = JSON.parse(e.data);
// Process all batched events
batch.events.forEach(event => {
// Handle each event based on type
if (event.event === 'component-update') {
updateComponent(JSON.parse(event.data));
}
});
});
```
**Production Usage**:
```php
// In SSE initialization (optional, default: disabled)
if (getenv('SSE_BATCHING_ENABLED') === 'true') {
$broadcaster->enableBatching(
maxBatchSize: (int) getenv('SSE_BATCH_SIZE') ?: 10,
maxBatchDelayMs: (int) getenv('SSE_BATCH_DELAY_MS') ?: 100
);
}
```
## 5. Smart Invalidation Strategy
### Coordinated Multi-Layer Invalidation
**Key Features**:
- Invalidate only affected caches
- State-aware slot invalidation
- Bulk invalidation for related components
- Lazy invalidation with timestamps
```php
// Smart invalidation - only affected caches
$result = $strategy->invalidateOnStateChange(
$componentId,
$oldState,
$newState
);
// Only invalidates slots if state keys affect slots:
// sidebarWidth, sidebarCollapsed, isOpen, padding, theme, variant
```
### Invalidation Performance
| Invalidation Type | Operations | Time | Affected Caches |
|-------------------|------------|------|-----------------|
| Smart State Change | 1-2 | <1ms | 1-2 layers |
| Component | 2 | <1ms | State + Slots |
| Template Type | 1 | <1ms | Templates only |
| Bulk (100 components) | 200 | ~10ms | State + Slots |
## 4. Performance Monitoring
### Real-time Metrics Collection
**Decorator Pattern** für transparente Metrics:
- No performance overhead from metrics (~0.1ms per operation)
- Automatic hit/miss tracking
- Average lookup time measurement
- Performance grade calculation (A-F)
```php
// Automatic metrics via decorator
$cache = new MetricsAwareComponentStateCache($baseCache, $metricsCollector);
// Normal operations - metrics collected automatically
$state = $cache->retrieve($componentId, $currentState);
// Get performance insights
$summary = $metricsCollector->getSummary();
// Hit rates, lookup times, performance grades
```
### Performance Targets & Validation
```php
// Automatic target validation
$assessment = $metricsCollector->assessPerformance();
/*
[
'state_cache' => [
'target' => '70.0%',
'actual' => '85.50%',
'meets_target' => true,
'grade' => 'A'
],
...
]
*/
// Performance warnings
if ($metricsCollector->hasPerformanceIssues()) {
$warnings = $metricsCollector->getPerformanceWarnings();
// "State cache hit rate (65.00%) below target (70.0%)"
}
```
## 5. Memory Management
### Efficient Memory Usage
**Optimizations**:
- Shared metadata across component instances (~75% memory reduction)
- Lazy loading of component classes
- Cache size limits with LRU eviction
- Weak references for temporary objects
**Memory Footprint**:
```
Per Component:
- Compiled Metadata: ~5KB (shared)
- Cached State: ~2KB per instance
- Cached Slot: ~1KB per slot
- Cached Template: ~5KB per variant
Total (10,000 components):
- Metadata Cache: ~50MB (one-time)
- State Cache: ~20MB (active instances)
- Slot Cache: ~10MB (common slots)
- Template Cache: ~50MB (variants)
Total: ~130MB (reasonable for high-traffic app)
```
### Memory Optimization Techniques
```php
// 1. Lazy loading - only load when needed
$metadata = $registry->getMetadata('counter'); // Loads on demand
// 2. Shared metadata - single instance per component class
$counterMeta1 = $registry->getMetadata('counter');
$counterMeta2 = $registry->getMetadata('counter');
// Same CompiledComponentMetadata instance
// 3. Cache limits - prevent unbounded growth
// Configured in ComponentStateCache, SlotContentCache, etc.
// 4. Batch operations - single allocation for multiple operations
$metadata = $metadataCache->getBatch($classNames); // Single array allocation
```
## 6. Production Performance
### Real-World Performance Metrics
**Production Environment** (10,000+ requests/hour):
| Metric | Without Optimizations | With Optimizations | Improvement |
|--------|----------------------|-------------------|-------------|
| Avg Component Render | 21.4ms | 5.1ms | **76% faster** |
| Registry Lookup | 2.3ms | 0.01ms | **99% faster** |
| State Init | 5.2ms | 1.5ms | **71% faster** |
| Slot Resolution | 3.8ms | 1.5ms | **61% faster** |
| Template Render | 12.4ms | 2.1ms | **83% faster** |
| Memory Usage (10K comp) | ~520MB | ~130MB | **75% reduction** |
### Cache Hit Rates (Production)
```
State Cache: 87.3% hit rate (Target: 70%) Grade: A
Slot Cache: 76.8% hit rate (Target: 60%) Grade: C
Template Cache: 93.2% hit rate (Target: 80%) Grade: A
Overall Grade: A
```
### Throughput Improvements
```
Before Optimizations:
- ~500 components/second
- ~150ms p95 latency
- ~400MB memory baseline
After Optimizations:
- ~2000 components/second (+300%)
- ~40ms p95 latency (-73%)
- ~130MB memory baseline (-67%)
```
## 7. Best Practices
### Do's ✅
1. **Use Auto-TTL Methods**
```php
$cache->storeWithAutoTTL($id, $state, 'counter');
```
2. **Batch Operations**
```php
$cache->storeBatch($id, $slots);
$cache->getBatch($id, $slotNames);
```
3. **Remember Pattern for Templates**
```php
$html = $cache->remember($type, $data, fn() => $this->render($data));
```
4. **Smart Invalidation**
```php
$strategy->invalidateOnStateChange($id, $old, $new);
```
5. **Monitor Performance**
```php
if ($metricsCollector->hasPerformanceIssues()) {
// Alert or auto-tune
}
```
### Don'ts ❌
1. **Manual TTL ohne Component-Type Consideration**
```php
// ❌ Bad - too long for counter
$cache->store($id, $state, Duration::fromHours(24));
// ✅ Good - auto-optimized
$cache->storeWithAutoTTL($id, $state, 'counter');
```
2. **Individual Calls statt Batch**
```php
// ❌ Bad - 3 cache operations
foreach ($slots as $name => $content) {
$cache->storeResolvedContent($id, $name, $content);
}
// ✅ Good - 1 cache operation
$cache->storeBatch($id, $slots);
```
3. **Always Invalidate All**
```php
// ❌ Bad - invalidates unnecessary caches
$strategy->invalidateComponent($id);
// ✅ Good - only invalidates affected
$strategy->invalidateOnStateChange($id, $old, $new);
```
## 8. Optimization Checklist
### Application Startup
- ✅ Warmup metadata cache for all components
- ✅ Batch load component metadata
- ✅ Pre-compile frequently used templates
### Component Rendering
- ✅ Check state cache before initialization
- ✅ Use batch slot operations
- ✅ Apply remember pattern for templates
- ✅ Smart invalidation on state changes
### Production Deployment
- ✅ Monitor cache hit rates (targets: 70%, 60%, 80%)
- ✅ Set up performance alerts
- ✅ Configure cache drivers (Redis for best performance)
- ✅ Implement cache warmup on deployment
- ✅ Monitor memory usage
### Continuous Optimization
- ✅ Review performance metrics weekly
- ✅ Adjust TTL strategies based on hit rates
- ✅ Identify and optimize cache misses
- ✅ Profile slow components
- ✅ Update metadata cache on component changes
## 9. Troubleshooting
### Low Hit Rates
**Symptom**: Hit rate below target
**Diagnosis**:
```php
$summary = $metricsCollector->getSummary();
// Check hit_rate_percent for each cache type
```
**Solutions**:
1. Increase TTL if cache expiring too fast
2. Review invalidation strategy (too aggressive?)
3. Check for cache size limits (evictions?)
4. Monitor for high variance in data (prevents caching)
### High Memory Usage
**Symptom**: Memory usage > 200MB for moderate load
**Diagnosis**:
```php
$stats = $registry->getRegistryStats();
// Check total_components and metadata_loaded
```
**Solutions**:
1. Implement cache size limits
2. Review TTL settings (too long?)
3. Clear memory cache periodically
4. Use Redis instead of file cache
### Slow Component Rendering
**Symptom**: Render time > 10ms even with caching
**Diagnosis**:
```php
$metrics = $metricsCollector->getMetrics(CacheType::TEMPLATE);
// Check average_lookup_time_ms
```
**Solutions**:
1. Profile template rendering (bottleneck in template?)
2. Check cache driver performance (Redis vs File)
3. Optimize template complexity
4. Use static templates where possible
## Zusammenfassung
Das LiveComponents Performance-Optimierungssystem bietet:
**~76% schnelleres Component Rendering** (21.4ms → 5.1ms)
**~99% schnellerer Registry Lookup** (2.3ms → 0.01ms)
**~75% weniger Memory Usage** (520MB → 130MB)
**~300% höherer Throughput** (500 → 2000 comp/s)
**~30-40% schnelleres Template Processing** durch Processor Chain Optimization
**~40-50% reduzierte SSE-Overhead** durch Event Batching
**Optimization Stack**:
- **Layer 1**: Component Registry mit Compiled Metadata Cache (~90% faster)
- **Layer 2**: 3-Layer Caching System (State, Slot, Template) (~70-80% faster)
- **Layer 3**: Template Processing Optimization (~30-40% faster)
- **Layer 4**: Event System (SSE) Batching (~40-50% faster)
- **Layer 5**: Smart Invalidation Strategy (minimal cache clearing)
- **Layer 6**: Real-time Performance Monitoring (metrics & grades)
- **Layer 7**: Efficient Memory Management (~75% reduction)
**Validierung**:
- Alle Performance-Claims durch automatisierte Benchmarks validiert
- Benchmark-Suite in `tests/Performance/LiveComponentsPerformanceBenchmark.php`
- Production-Metriken über 10,000+ requests/hour
**Framework-Konform**:
- Value Objects (CacheType, CacheMetrics, Percentage, Hash, Duration)
- Readonly Classes (wo möglich)
- Immutable State (Transformation Methods)
- Decorator Pattern für Metrics
- Type Safety überall
- Composition over Inheritance
- Explicit Dependency Injection
**Neue Performance-Klassen**:
- `ComponentMetadataCache` - Compiled metadata caching
- `ComponentMetadataCompiler` - One-time reflection
- `CompiledComponentMetadata` - Metadata Value Objects
- `ProcessorChainOptimizer` - Template processor optimization
- `CompiledTemplateCache` - Template AST caching
- `ProcessorPerformanceTracker` - Template profiling
- `CacheMetricsCollector` - Real-time metrics
- `MetricsAware*Cache` - Decorator pattern für alle Caches
- `SseBroadcaster` (erweitert) - Event batching für SSE

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -1,504 +0,0 @@
# LiveComponents Test Harness
Comprehensive test harness für LiveComponents mit ComponentTestCase trait und ComponentFactory.
## Übersicht
Der Test-Harness bietet:
- **ComponentTestCase trait**: Umfassende Test-Helper-Methoden
- **ComponentFactory**: Builder-Pattern für Test-Component-Erstellung
- **Automatische Setup**: CSRF, Authorization, State Validation Integration
- **Assertions**: State, Action, Authorization und Event Assertions
## Quick Start
```php
<?php
use Tests\Framework\LiveComponents\ComponentFactory;
use Tests\Framework\LiveComponents\ComponentTestCase;
// Use ComponentTestCase trait
uses(ComponentTestCase::class);
// Setup before each test
beforeEach(function () {
$this->setUpComponentTest();
});
it('executes component action', function () {
$component = ComponentFactory::counter(initialCount: 5);
$result = $this->callAction($component, 'increment');
expect($result->state->data['count'])->toBe(6);
});
```
## ComponentTestCase Trait
### Setup Methode
**`setUpComponentTest()`** - Initialisiert Test-Environment:
- Erstellt Session mit CSRF-Token-Generator
- Initialisiert LiveComponentHandler mit allen Dependencies (CSRF, Auth, Validation)
- Setzt EventDispatcher, AuthorizationChecker, StateValidator, SchemaCache auf
```php
beforeEach(function () {
$this->setUpComponentTest();
});
```
### Authentication Helper
**`actingAs(array $permissions = [], int $userId = 1)`** - Mock authenticated user:
```php
$this->actingAs(['posts.edit', 'posts.delete']);
$result = $this->callAction($component, 'deletePost', ['id' => 123]);
```
### Action Execution
**`callAction(LiveComponentContract $component, string $method, array $params = [])`** - Execute action with automatic CSRF:
```php
$component = ComponentFactory::counter();
// Automatic CSRF token generation
$result = $this->callAction($component, 'increment');
// With parameters
$result = $this->callAction($component, 'addItem', ['item' => 'New Task']);
```
### Action Assertions
**`assertActionExecutes()`** - Assert action executes successfully:
```php
$result = $this->assertActionExecutes($component, 'increment');
expect($result->state->data['count'])->toBe(1);
```
**`assertActionThrows()`** - Assert action throws exception:
```php
$component = ComponentFactory::make()
->withId('error:component')
->withState(['data' => 'test'])
->withAction('fail', function() {
throw new \RuntimeException('Expected error');
})
->create();
$this->assertActionThrows($component, 'fail', \RuntimeException::class);
```
**`assertActionRequiresAuth()`** - Assert action requires authentication:
```php
// Note: Requires real component class with #[RequiresPermission] attribute
// ComponentFactory closures don't support attributes
$this->assertActionRequiresAuth($component, 'protectedAction');
```
**`assertActionRequiresPermission()`** - Assert action requires specific permission:
```php
$this->actingAs(['posts.view']); // Insufficient permission
$this->assertActionRequiresPermission(
$component,
'deletePost',
['posts.view'] // Should fail with only 'view' permission
);
```
### State Assertions
**`assertStateEquals(ComponentUpdate $result, array $expected)`** - Assert state matches expected:
```php
$result = $this->callAction($component, 'increment');
$this->assertStateEquals($result, ['count' => 1]);
```
**`assertStateHas(ComponentUpdate $result, string $key)`** - Assert state has key:
```php
$this->assertStateHas($result, 'items');
```
**`assertStateValidates(ComponentUpdate $result)`** - Assert state passes validation:
```php
$result = $this->callAction($component, 'updateData', ['value' => 'test']);
$this->assertStateValidates($result);
```
**`getStateValue(ComponentUpdate $result, string $key)`** - Get specific state value:
```php
$count = $this->getStateValue($result, 'count');
expect($count)->toBe(5);
```
### Event Assertions
**`assertEventDispatched(ComponentUpdate $result, string $eventName)`** - Assert event was dispatched:
```php
$result = $this->callAction($component, 'submitForm');
$this->assertEventDispatched($result, 'form:submitted');
```
**`assertNoEventsDispatched(ComponentUpdate $result)`** - Assert no events were dispatched:
```php
$result = $this->callAction($component, 'increment');
$this->assertNoEventsDispatched($result);
```
**`assertEventCount(ComponentUpdate $result, int $count)`** - Assert event count:
```php
$result = $this->callAction($component, 'bulkOperation');
$this->assertEventCount($result, 3);
```
## ComponentFactory
### Builder Pattern
**`ComponentFactory::make()`** - Start builder:
```php
$component = ComponentFactory::make()
->withId('posts:manager')
->withState(['posts' => [], 'count' => 0])
->withAction('addPost', function(string $title) {
$this->state['posts'][] = $title;
$this->state['count']++;
return ComponentData::fromArray($this->state);
})
->create();
```
### Builder Methods
- **`withId(string $id)`** - Set component ID
- **`withState(array $state)`** - Set initial state (cannot be empty!)
- **`withAction(string $name, callable $handler)`** - Add custom action
- **`withTemplate(string $template)`** - Set template name
- **`create()`** - Create component instance
### Pre-configured Components
**`ComponentFactory::counter(int $initialCount = 0)`** - Counter component:
```php
$component = ComponentFactory::counter(initialCount: 5);
// Actions: increment, decrement, reset
$result = $this->callAction($component, 'increment');
expect($result->state->data['count'])->toBe(6);
```
**`ComponentFactory::list(array $initialItems = [])`** - List component:
```php
$component = ComponentFactory::list(['item1', 'item2']);
// Actions: addItem, removeItem, clear
$result = $this->callAction($component, 'addItem', ['item' => 'item3']);
expect($result->state->data['items'])->toHaveCount(3);
```
## Integration Features
### Automatic CSRF Protection
```php
// CSRF token automatically generated and validated
$result = $this->callAction($component, 'action');
// CSRF token: 'livecomponent:{componentId}' form ID
```
### Automatic State Validation
```php
// State automatically validated against derived schema
$result = $this->callAction($component, 'updateState');
// Schema derived on first getData() call
// Cached for subsequent validations
```
### Authorization Integration
```php
// Mock authenticated user with permissions
$this->actingAs(['admin.access']);
// Authorization automatically checked for #[RequiresPermission] attributes
$result = $this->callAction($component, 'adminAction');
```
## Best Practices
### State Must Not Be Empty
```php
// ❌ Empty state causes schema derivation error
$component = ComponentFactory::make()
->withState([])
->create();
// ✅ Always provide at least one state field
$component = ComponentFactory::make()
->withState(['initialized' => true])
->create();
```
### Authorization Testing Requires Real Classes
```php
// ❌ Closures don't support attributes for authorization
$component = ComponentFactory::make()
->withAction('protectedAction',
#[RequiresPermission('admin')] // Attribute wird ignoriert
function() { }
)
->create();
// ✅ Use real component class for authorization testing
final readonly class TestComponent implements LiveComponentContract
{
#[RequiresPermission('admin')]
public function protectedAction(): ComponentData
{
// Implementation
}
}
```
### Action Closures Have Access to Component State
```php
$component = ComponentFactory::make()
->withState(['count' => 0])
->withAction('increment', function() {
// $this->state available via closure binding
$this->state['count']++;
return ComponentData::fromArray($this->state);
})
->create();
```
### Multiple Actions in Sequence
```php
it('handles multiple actions', function () {
$component = ComponentFactory::counter();
$result1 = $this->callAction($component, 'increment');
$result2 = $this->callAction($component, 'increment');
$result3 = $this->callAction($component, 'decrement');
// Note: Component state is immutable
// Each call returns new state, doesn't mutate original
expect($result1->state->data['count'])->toBe(1);
expect($result2->state->data['count'])->toBe(2);
expect($result3->state->data['count'])->toBe(1);
});
```
## Test Organization
```
tests/
├── Framework/LiveComponents/
│ ├── ComponentTestCase.php # Trait with helper methods
│ └── ComponentFactory.php # Builder for test components
└── Feature/Framework/LiveComponents/
├── TestHarnessDemo.php # Demo of all features
├── SimpleTestHarnessTest.php # Simple examples
└── ExceptionTestHarnessTest.php # Exception handling examples
```
## Complete Example
```php
<?php
use Tests\Framework\LiveComponents\ComponentFactory;
use Tests\Framework\LiveComponents\ComponentTestCase;
uses(ComponentTestCase::class);
beforeEach(function () {
$this->setUpComponentTest();
});
describe('Shopping Cart Component', function () {
it('adds items to cart', function () {
$component = ComponentFactory::make()
->withId('shopping-cart')
->withState(['items' => [], 'total' => 0])
->withAction('addItem', function(string $product, int $price) {
$this->state['items'][] = ['product' => $product, 'price' => $price];
$this->state['total'] += $price;
return ComponentData::fromArray($this->state);
})
->create();
$result = $this->callAction($component, 'addItem', [
'product' => 'Laptop',
'price' => 999
]);
$this->assertStateHas($result, 'items');
expect($result->state->data['items'])->toHaveCount(1);
expect($result->state->data['total'])->toBe(999);
});
it('requires authentication for checkout', function () {
$component = ComponentFactory::make()
->withId('shopping-cart')
->withState(['items' => [['product' => 'Laptop', 'price' => 999]]])
->withAction('checkout', function() {
// Checkout logic
return ComponentData::fromArray($this->state);
})
->create();
// Without authentication
// Note: For authorization testing, use real component classes
// With authentication
$this->actingAs(['checkout.access']);
$result = $this->assertActionExecutes($component, 'checkout');
});
});
```
## Known Limitations
### 1. Closure Attributes
Attributes on closures passed to `withAction()` are not supported for authorization checks:
```php
// ❌ Doesn't work - attribute ignored
$component = ComponentFactory::make()
->withAction('protectedAction',
#[RequiresPermission('admin')]
function() { }
)
->create();
```
**Workaround**: Create real component class for authorization testing.
### 2. Empty State Not Allowed
Components must have at least one state field for schema derivation:
```php
// ❌ Throws InvalidArgumentException: 'Schema cannot be empty'
$component = ComponentFactory::make()
->withState([])
->create();
// ✅ Provide at least one field
$component = ComponentFactory::make()
->withState(['initialized' => true])
->create();
```
### 3. Magic Method Reflection
ComponentFactory uses `__call()` for actions, which limits reflection-based parameter analysis. The handler falls back to direct parameter passing for magic methods.
## Performance Considerations
- **Schema Caching**: Schema derived once per component class and cached
- **CSRF Generation**: CSRF token generated per test, not reused
- **Session State**: Session state reset in `setUpComponentTest()`
- **Event Dispatcher**: Events collected per action call, not persisted
## Troubleshooting
### "Method not found" Error
```
BadMethodCallException: Method increment not found on component
```
**Fix**: Ensure `method_exists()` check supports `__call()` magic methods:
```php
// LiveComponentHandler checks both real and magic methods
if (!method_exists($component, $method) && !is_callable([$component, $method])) {
throw new \BadMethodCallException(...);
}
```
### "Schema cannot be empty" Error
```
InvalidArgumentException: Schema cannot be empty
```
**Fix**: Provide non-empty state:
```php
// ❌ Empty state
->withState([])
// ✅ Non-empty state
->withState(['data' => 'test'])
```
### Reflection Exception for Actions
```
ReflectionException: Method increment() does not exist
```
**Fix**: Handler catches ReflectionException and falls back to direct call:
```php
try {
$reflection = new \ReflectionMethod($component, $method);
// Parameter analysis
} catch (\ReflectionException $e) {
// Direct call for magic methods
return $component->$method(...$params->toArray());
}
```
## Summary
Der Test-Harness bietet:
-**Einfache Component-Erstellung** via ComponentFactory
-**Umfassende Assertions** für State, Actions, Events
-**Automatische Integration** mit CSRF, Auth, Validation
-**Flexible Test-Components** via Builder Pattern
-**Pre-configured Components** (Counter, List)
- ⚠️ **Known Limitations** mit Closure-Attributes
**Framework-Integration**: Vollständig integriert mit LiveComponentHandler, StateValidator, AuthorizationChecker und EventDispatcher.

View File

@@ -1,202 +0,0 @@
# Migration System Quick Reference
## TL;DR
**Default**: Migrations sind forward-only (nur `up()` Methode).
**Optional**: Implementiere `SafelyReversible` nur wenn Rollback OHNE Datenverlust möglich ist.
## Quick Decision Tree
```
Is rollback safe (no data loss)?
├─ YES (e.g., create table, add nullable column, create index)
│ └─ Implement: Migration, SafelyReversible
└─ NO (e.g., drop column, transform data, delete data)
└─ Implement: Migration only
```
## Code Templates
### Forward-Only Migration (Default)
```php
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationVersion;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Schema\Schema;
final readonly class YourMigration implements Migration
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
// Your migration logic
$schema->execute();
}
public function getVersion(): MigrationVersion
{
return MigrationVersion::fromTimestamp("2024_12_20_100000");
}
public function getDescription(): string
{
return 'Description of what this migration does';
}
public function getDomain(): string
{
return "YourDomain";
}
}
```
### Safe Rollback Migration
```php
use App\Framework\Database\Migration\{Migration, SafelyReversible};
use App\Framework\Database\Migration\MigrationVersion;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Schema\Schema;
/**
* This migration is safely reversible because:
* - [Explain why rollback is safe, e.g., "Creates new empty table"]
*/
final readonly class YourSafeMigration implements Migration, SafelyReversible
{
public function up(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
// Your migration logic
$schema->execute();
}
public function down(ConnectionInterface $connection): void
{
$schema = new Schema($connection);
// Reverse the changes safely
$schema->execute();
}
public function getVersion(): MigrationVersion
{
return MigrationVersion::fromTimestamp("2024_12_20_100000");
}
public function getDescription(): string
{
return 'Description of what this migration does';
}
public function getDomain(): string
{
return "YourDomain";
}
}
```
## Common Operations
### Create Migration
```bash
php console.php make:migration CreateYourTable
```
### Run Migrations
```bash
php console.php db:migrate
```
### Rollback (Only SafelyReversible)
```bash
# Rollback last migration
php console.php db:rollback
# Rollback last 3 migrations
php console.php db:rollback 3
```
### Check Status
```bash
php console.php db:status
```
## Safe vs Unsafe Cheatsheet
| Operation | Safe? | Implements |
|-----------|-------|------------|
| Create table | ✅ | Migration, SafelyReversible |
| Drop table (empty) | ✅ | Migration, SafelyReversible |
| Drop table (with data) | ❌ | Migration only |
| Add nullable column | ✅ | Migration, SafelyReversible |
| Add NOT NULL column (with default) | ⚠️ | Case by case |
| Drop column (empty) | ✅ | Migration, SafelyReversible |
| Drop column (with data) | ❌ | Migration only |
| Rename column | ✅ | Migration, SafelyReversible |
| Change column type | ❌ | Migration only |
| Create index | ✅ | Migration, SafelyReversible |
| Drop index | ✅ | Migration, SafelyReversible |
| Add foreign key | ✅ | Migration, SafelyReversible |
| Drop foreign key | ✅ | Migration, SafelyReversible |
| Transform data | ❌ | Migration only |
| Delete data | ❌ | Migration only |
| Merge tables | ❌ | Migration only |
## Error Handling
### Rollback Failed (Not SafelyReversible)
```bash
❌ Rollback failed: Migration 2024_12_20_100000 does not support safe rollback
💡 Recommendation:
Create a new forward migration to undo the changes instead:
php console.php make:migration FixYourChanges
```
### What to do:
1. Create new forward migration
2. Implement reverse logic in `up()` method
3. Run `php console.php db:migrate`
## Testing Rollback
```bash
# Test cycle in development
php console.php db:migrate
php console.php db:rollback 1
php console.php db:migrate # Should succeed again
```
## Best Practices
1.**Document why safe**: Always comment why a migration is SafelyReversible
2.**Test rollback cycle**: Test `up() → down() → up()` in development
3.**Default to forward-only**: Only add SafelyReversible if certain
4.**Never rollback in production**: Even "safe" rollbacks should be avoided
5.**Use fix-forward**: Prefer new migrations over rollback
6.**Backup before rollback**: Always backup database before rolling back
7.**Check for data**: Verify column is empty before making it SafelyReversible
## Links
- **Full Examples**: `docs/claude/examples/migrations/SafeVsUnsafeMigrations.md`
- **Database Patterns**: `docs/claude/database-patterns.md`
- **Migration Interface**: `src/Framework/Database/Migration/Migration.php`
- **SafelyReversible Interface**: `src/Framework/Database/Migration/SafelyReversible.php`
## Framework Compliance
✅ Readonly classes: `final readonly class`
✅ Composition over inheritance: Interface-based
✅ Explicit contracts: `SafelyReversible` is opt-in
✅ Type safety: Strong typing throughout
✅ No primitive obsession: Uses Value Objects (MigrationVersion)

1316
docs/claude/posix-system.md Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,291 +0,0 @@
# Route Authorization System
Dokumentation des namespace-basierten Route Authorization Systems.
## Übersicht
Das Route Authorization System ermöglicht die Zugangskontrolle für Routes auf Basis von:
1. **Legacy `#[Auth]` Attribute** - Backward compatibility
2. **Namespace-basierte Blockierung** - Blockiere ganze Controller-Namespaces (z.B. `App\Application\Admin\*`)
3. **Namespace-basierte IP-Restrictions** - IP-basierte Zugriffskontrolle per Namespace
4. **Route-spezifische `#[IpAuth]` Attribute** - Feinkörnige IP-Kontrolle per Route
## Architektur
```
RouteAuthorizationService
├── checkLegacyAuthAttribute()
├── checkNamespaceAccessPolicy() [NEU]
├── checkNamespaceIpRestrictions()
└── checkRouteIpAuthAttribute()
```
Der Service wird in der `RoutingMiddleware` **nach dem Routing** aufgerufen, sodass die gematchte Route bekannt ist.
## Konfiguration
### Initializer-basierte Konfiguration
**Location**: `src/Framework/Auth/RouteAuthorizationServiceInitializer.php`
```php
#[Initializer]
public function __invoke(Container $container): RouteAuthorizationService
{
$namespaceConfig = [
// Namespace Pattern => Configuration
'App\Application\Admin\*' => [
'visibility' => 'admin', // IP-based restriction
'access_policy' => NamespaceAccessPolicy::blocked()
],
];
return new RouteAuthorizationService(
config: $this->config,
namespaceConfig: $namespaceConfig
);
}
```
### Namespace Patterns
**Wildcard-basierte Patterns**:
- `App\Application\Admin\*` - Matched alle Admin-Controller
- `App\Application\Api\*` - Matched alle API-Controller
- `App\Application\*` - Matched alle Application-Controller
**Exact Match**:
- `App\Application\Admin\Dashboard` - Nur exakt dieser Namespace
## Use Cases
### 1. Admin-Bereich komplett blockieren
```php
$namespaceConfig = [
'App\Application\Admin\*' => [
'access_policy' => NamespaceAccessPolicy::blocked()
],
];
```
**Ergebnis**: Alle Admin-Controller werfen `RouteNotFound` (404)
### 2. Admin-Bereich mit Allowlist
```php
use App\Application\Admin\LoginController;
use App\Application\Admin\HealthCheckController;
$namespaceConfig = [
'App\Application\Admin\*' => [
'access_policy' => NamespaceAccessPolicy::blockedExcept(
LoginController::class,
HealthCheckController::class
)
],
];
```
**Ergebnis**:
-`LoginController` und `HealthCheckController` öffentlich erreichbar
- ❌ Alle anderen Admin-Controller blockiert
### 3. Kombination: IP-Restriction + Namespace-Blocking
```php
$namespaceConfig = [
'App\Application\Admin\*' => [
'visibility' => 'admin', // Nur Admin-IPs erlaubt
'access_policy' => NamespaceAccessPolicy::blockedExcept(
LoginController::class // Aber Login ist public
)
],
];
```
**Ergebnis**:
-`LoginController` - öffentlich erreichbar (trotz admin visibility)
- 🔒 Alle anderen Admin-Controller - nur von Admin-IPs
### 4. API-Bereich mit IP-Restriction (ohne Blocking)
```php
$namespaceConfig = [
'App\Application\Api\*' => [
'visibility' => 'local', // Nur localhost/private IPs
// Kein access_policy - keine Namespace-Blockierung
],
];
```
**Ergebnis**: API nur von localhost/private IPs erreichbar
### 5. Mehrere Namespace-Policies
```php
$namespaceConfig = [
// Admin komplett gesperrt
'App\Application\Admin\*' => [
'access_policy' => NamespaceAccessPolicy::blocked()
],
// API nur von localhost
'App\Application\Api\*' => [
'visibility' => 'local'
],
// Internal Tools nur admin IPs
'App\Application\Internal\*' => [
'visibility' => 'admin',
'access_policy' => NamespaceAccessPolicy::blocked()
],
];
```
## Value Objects
### NamespaceAccessPolicy
```php
// Alle Controller im Namespace blockieren
NamespaceAccessPolicy::blocked()
// Alle blockieren außer spezifische Controller
NamespaceAccessPolicy::blockedExcept(
LoginController::class,
HealthCheckController::class
)
// Alle erlauben (default)
NamespaceAccessPolicy::allowed()
// Prüfen ob Controller blockiert ist
$policy->isControllerBlocked(Dashboard::class) // true/false
```
## Visibility Modes (IP-Restrictions)
**Predefined Modes**:
- `public` - Keine IP-Restrictions
- `admin` - Nur Admin-IPs (WireGuard, etc.)
- `local` - Nur localhost/127.0.0.1
- `development` - Development-IPs
- `private` - Alias für `local`
- `custom` - Custom IP-Liste via Config
## Execution Order
1. **Legacy Auth Attribute** - Backward compatibility check
2. **Namespace Access Policy** - Block/Allow basierend auf Controller-Klasse
3. **Namespace IP Restrictions** - IP-basierte Zugriffskontrolle
4. **Route IP Auth Attribute** - Feinkörnige Route-Level IP-Kontrolle
Alle Checks werfen `RouteNotFound` bei Failure (versteckt Route-Existenz).
## Testing
### Unit Test Beispiel
```php
use App\Framework\Auth\RouteAuthorizationService;
use App\Framework\Auth\ValueObjects\NamespaceAccessPolicy;
it('blocks admin controllers except allowlist', function () {
$config = ['App\Application\Admin\*' => [
'access_policy' => NamespaceAccessPolicy::blockedExcept(
LoginController::class
)
]];
$service = new RouteAuthorizationService(
config: $this->config,
namespaceConfig: $config
);
// Should throw RouteNotFound for Dashboard
expect(fn() => $service->authorize($request, $dashboardRoute))
->toThrow(RouteNotFound::class);
// Should allow LoginController
expect(fn() => $service->authorize($request, $loginRoute))
->not->toThrow(RouteNotFound::class);
});
```
## Best Practices
### 1. Namespace-Blocking für Production
- Blockiere Admin/Internal-Bereiche in Production
- Nutze Allowlist nur für wirklich öffentliche Endpoints (Login, Health)
### 2. IP-Restrictions für Sensitive Bereiche
- Kombiniere Namespace-Blocking mit IP-Restrictions
- Nutze `visibility: 'admin'` für maximale Sicherheit
### 3. Graceful Error Handling
- Alle Checks werfen `RouteNotFound` (404)
- Versteckt Route-Existenz vor Angreifern
- Keine Information Leakage
### 4. Konfiguration via Initializer
- Zentrale Konfiguration in `RouteAuthorizationServiceInitializer`
- Environment-spezifische Configs möglich (dev vs. production)
- Type-safe via Value Objects
## Migration Guide
### Von altem System (RoutingMiddleware::withNamespaceConfig)
**Alt**:
```php
RoutingMiddleware::withNamespaceConfig(
$router, $dispatcher, $config, $performance, $container,
namespaceConfig: [
'App\Application\Admin\*' => ['visibility' => 'admin']
]
);
```
**Neu**:
```php
// In RouteAuthorizationServiceInitializer
$namespaceConfig = [
'App\Application\Admin\*' => [
'visibility' => 'admin',
'access_policy' => NamespaceAccessPolicy::blocked() // NEU
]
];
```
## Troubleshooting
### Problem: Route wirft 404 obwohl sie erreichbar sein sollte
**Debugging**:
1. Prüfe `RouteAuthorizationServiceInitializer` Config
2. Check ob Controller-Namespace in `namespaceConfig` matched
3. Prüfe `access_policy` - ist Controller in Allowlist?
4. Check IP-Restrictions (`visibility`)
### Problem: Allowlist funktioniert nicht
**Ursache**: Controller-Klasse exakt mit `::class` angeben
```php
// ❌ Falsch
NamespaceAccessPolicy::blockedExcept('LoginController')
// ✅ Korrekt
NamespaceAccessPolicy::blockedExcept(
\App\Application\Admin\LoginController::class
)
```
## Framework Integration
- **Automatic Discovery**: Service wird via `#[Initializer]` automatisch registriert
- **DI Container**: Alle Dependencies werden automatisch injected
- **Type Safety**: Value Objects für alle Policies
- **Readonly Classes**: Unveränderliche Policies für Thread-Safety
- **Framework-konform**: Nutzt bestehende Patterns (IpAuthPolicy, RouteNotFound)

View File

@@ -1,306 +0,0 @@
# Routing Value Objects
Das Framework unterstützt **parallele Routing-Ansätze** für maximale Flexibilität und Typsicherheit.
## Überblick
```php
// ✅ Traditioneller String-Ansatz (schnell & gewohnt)
#[Route(path: '/api/users/{id}')]
// ✅ Value Object-Ansatz (typsicher & framework-konform)
#[Route(path: RoutePath::fromElements('api', 'users', Placeholder::fromString('id')))]
```
Beide Ansätze sind **vollständig kompatibel** und können parallel verwendet werden.
## String-Basierte Routen
**Einfach und gewohnt** für schnelle Entwicklung:
```php
final readonly class UserController
{
#[Route(path: '/api/users')]
public function index(): JsonResult { /* ... */ }
#[Route(path: '/api/users/{id}')]
public function show(int $id): JsonResult { /* ... */ }
#[Route(path: '/api/users/{id}/posts/{postId}')]
public function showPost(int $id, int $postId): JsonResult { /* ... */ }
#[Route(path: '/files/{path*}', method: Method::GET)]
public function downloadFile(string $path): StreamResult { /* ... */ }
}
```
## Value Object-Basierte Routen
**Typsicher und framework-konform** für Production-Code:
### Basis-Syntax
```php
use App\Framework\Router\ValueObjects\RoutePath;
use App\Framework\Router\ValueObjects\Placeholder;
final readonly class ImageController
{
#[Route(path: RoutePath::fromElements('images', Placeholder::fromString('filename')))]
public function show(string $filename): ImageResult
{
return new ImageResult($this->imageService->getImage($filename));
}
}
```
### Typisierte Parameter
```php
#[Route(path: RoutePath::fromElements(
'api',
'users',
Placeholder::typed('userId', 'uuid'),
'posts',
Placeholder::typed('postId', 'int')
))]
public function getUserPost(string $userId, int $postId): JsonResult
{
// userId wird automatisch als UUID validiert
// postId wird automatisch als Integer validiert
}
```
### Verfügbare Parameter-Typen
| Typ | Regex Pattern | Beispiel |
|-----|---------------|----------|
| `int` | `(\d+)` | `/users/{id}` → 123 |
| `uuid` | `([0-9a-f]{8}-...)` | `/users/{id}``550e8400-e29b-...` |
| `slug` | `([a-z0-9\-]+)` | `/posts/{slug}``my-blog-post` |
| `alpha` | `([a-zA-Z]+)` | `/category/{name}``Technology` |
| `alphanumeric` | `([a-zA-Z0-9]+)` | `/code/{id}``ABC123` |
| `filename` | `([a-zA-Z0-9._\-]+)` | `/files/{name}``image.jpg` |
### Wildcard-Parameter
```php
#[Route(path: RoutePath::fromElements('files', Placeholder::wildcard('path')))]
public function serveFile(string $path): StreamResult
{
// Matched: /files/uploads/2024/image.jpg
// $path = "uploads/2024/image.jpg"
}
```
## Fluent Builder API
**Expressiver Builder** für komplexe Routen:
```php
final readonly class ApiController
{
#[Route(path: RoutePath::create()
->segment('api')
->segment('v1')
->segment('users')
->typedParameter('userId', 'uuid')
->segment('posts')
->typedParameter('postId', 'int')
->build()
)]
public function getUserPost(string $userId, int $postId): JsonResult { /* ... */ }
// Quick Helper für häufige Patterns
#[Route(path: RoutePath::create()->segments('api', 'users')->uuid())]
public function showUser(string $id): JsonResult { /* ... */ }
}
```
## Praktische Beispiele
### RESTful API Routes
```php
final readonly class ProductController
{
// String-Ansatz für einfache Routen
#[Route(path: '/api/products', method: Method::GET)]
public function index(): JsonResult { }
#[Route(path: '/api/products', method: Method::POST)]
public function create(CreateProductRequest $request): JsonResult { }
// Value Object-Ansatz für komplexe Routen
#[Route(path: RoutePath::fromElements(
'api',
'products',
Placeholder::typed('productId', 'uuid'),
'reviews',
Placeholder::typed('reviewId', 'int')
), method: Method::GET)]
public function getProductReview(string $productId, int $reviewId): JsonResult { }
}
```
### File Serving
```php
final readonly class FileController
{
// Static files (string)
#[Route(path: '/assets/{type}/{filename}')]
public function staticAsset(string $type, string $filename): StreamResult { }
// Dynamic file paths (Value Object mit Wildcard)
#[Route(path: RoutePath::fromElements(
'uploads',
Placeholder::wildcard('path')
))]
public function uploadedFile(string $path): StreamResult { }
}
```
### Admin Routes
```php
final readonly class AdminController
{
// String für bekannte Admin-Pfade
#[Route(path: '/admin/dashboard')]
#[Auth(strategy: 'ip', allowedIps: ['127.0.0.1'])]
public function dashboard(): ViewResult { }
// Value Objects für dynamische Admin-Actions
#[Route(path: RoutePath::fromElements(
'admin',
'users',
Placeholder::typed('userId', 'uuid'),
'actions',
Placeholder::fromString('action')
))]
#[Auth(strategy: 'session', roles: ['admin'])]
public function userAction(string $userId, string $action): JsonResult { }
}
```
## Migration & Kompatibilität
### Bestehende Routen bleiben unverändert
```php
// ✅ Weiterhin gültig und funktional
#[Route(path: '/api/users/{id}')]
public function show(int $id): JsonResult { }
```
### Schrittweise Migration
```php
final readonly class UserController
{
// Phase 1: Strings für einfache Routen
#[Route(path: '/users')]
public function index(): ViewResult { }
// Phase 2: Value Objects für neue komplexe Routen
#[Route(path: RoutePath::fromElements(
'users',
Placeholder::typed('userId', 'uuid'),
'preferences',
Placeholder::fromString('section')
))]
public function userPreferences(string $userId, string $section): JsonResult { }
}
```
### Konsistenz-Check
```php
// Beide Ansätze sind äquivalent
$stringRoute = new Route(path: '/api/users/{id}');
$objectRoute = new Route(path: RoutePath::fromElements('api', 'users', Placeholder::fromString('id')));
$stringRoute->getPathAsString() === $objectRoute->getPathAsString(); // true
```
## Best Practices
### Wann String verwenden
- **Prototyping**: Schnelle Route-Erstellung
- **Einfache Routen**: Statische oder 1-Parameter-Routen
- **Legacy-Kompatibilität**: Bestehende Routen beibehalten
```php
// ✅ Gut für einfache Fälle
#[Route(path: '/health')]
#[Route(path: '/api/status')]
#[Route(path: '/users/{id}')]
```
### Wann Value Objects verwenden
- **Production-Code**: Maximale Typsicherheit
- **Komplexe Routen**: Mehrere Parameter mit Validierung
- **API-Endpoints**: Starke Typisierung für externe Schnittstellen
- **Framework-Konsistenz**: Vollständige Value Object-Nutzung
```php
// ✅ Gut für komplexe Fälle
#[Route(path: RoutePath::fromElements(
'api',
'v2',
'organizations',
Placeholder::typed('orgId', 'uuid'),
'projects',
Placeholder::typed('projectId', 'slug'),
'files',
Placeholder::wildcard('filePath')
))]
```
### Hybrid-Ansatz (Empfohlen)
```php
final readonly class ProjectController
{
// String für einfache Routen
#[Route(path: '/projects')]
public function index(): ViewResult { }
// Value Objects für komplexe/kritische Routen
#[Route(path: RoutePath::fromElements(
'api',
'projects',
Placeholder::typed('projectId', 'uuid'),
'members',
Placeholder::typed('memberId', 'uuid')
))]
public function getProjectMember(string $projectId, string $memberId): JsonResult { }
}
```
## Router-Integration
Das Framework konvertiert automatisch zwischen beiden Ansätzen:
```php
// Route-Attribute bietet einheitliche Interface
public function getPathAsString(): string // Immer String für Router
public function getRoutePath(): RoutePath // Immer RoutePath-Objekt
// RouteCompiler verwendet automatisch getPathAsString()
$path = $routeAttribute->getPathAsString(); // Funktioniert für beide
```
## Fazit
- **String-Routen**: Schnell, gewohnt, ideal für einfache Fälle
- **Value Object-Routen**: Typsicher, framework-konform, ideal für komplexe Fälle
- **Vollständige Kompatibilität**: Beide Ansätze parallel nutzbar
- **Keine Breaking Changes**: Bestehender Code funktioniert weiterhin
- **Schrittweise Adoption**: Migration nach Bedarf möglich
**Empfehlung**: Hybrid-Ansatz mit Strings für einfache und Value Objects für komplexe Routen.

View File

@@ -1,458 +0,0 @@
# Scheduler-Queue Pipeline
Komplette Dokumentation der Scheduler-Queue Integration Pipeline im Custom PHP Framework.
## Übersicht
Die Scheduler-Queue Pipeline ist eine vollständig integrierte Lösung für zeitbasierte Aufgabenplanung und asynchrone Job-Ausführung. Sie kombiniert das Framework's Scheduler System mit dem erweiterten Queue System für robuste, skalierbare Background-Processing.
## Architektur
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Scheduler │───▶│ Job Dispatch │───▶│ Queue System │───▶│ Background Exec │
│ System │ │ & Validation │ │ (13 Tables) │ │ & Logging │
└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │ │
Cron/Interval JobPayload FileQueue/Redis Named Job Classes
OneTime/Manual Value Objects w/ Metrics w/ handle() Method
Schedule Types Type Safety & Monitoring Result Logging
```
## Pipeline Komponenten
### 1. Scheduler System
**Location**: `src/Framework/Scheduler/`
**Schedule Types**:
- **CronSchedule**: Traditionelle Cron-basierte Zeitplanung
- **IntervalSchedule**: Wiederholende Ausführung in festen Intervallen
- **OneTimeSchedule**: Einmalige Ausführung zu bestimmter Zeit
- **ManualSchedule**: Manuelle Trigger-basierte Ausführung
**Core Services**:
- `SchedulerService`: Hauptservice für Task-Management
- `TaskExecutionResult`: Value Object für Execution-Ergebnisse
- `Timestamp`/`Duration`: Zeit-Value Objects mit Framework-Compliance
### 2. Job Dispatch & Validation
**Integration Layer zwischen Scheduler und Queue**
**Key Features**:
- **Type Safety**: Verwendung von JobPayload Value Objects
- **Named Job Classes**: Vermeidung von Anonymous Classes (ClassName Restrictions)
- **Validation**: Input-Validierung vor Queue-Dispatch
- **Error Handling**: Graceful Fallbacks bei Dispatch-Fehlern
### 3. Queue System
**Location**: `src/Framework/Queue/`
**Database Schema** (13 Specialized Tables):
```sql
jobs -- Haupt-Job-Queue
job_batches -- Batch-Job-Verwaltung
job_history -- Execution-Historie
job_metrics -- Performance-Metriken
dead_letter_jobs -- Fehlgeschlagene Jobs
job_priorities -- Prioritäts-basierte Scheduling
job_dependencies -- Inter-Job Dependencies
delayed_jobs -- Zeit-verzögerte Ausführung
worker_processes -- Worker-Management
job_locks -- Distributed Locking
job_progress -- Progress Tracking
recurring_jobs -- Wiederkehrende Patterns
job_tags -- Kategorisierung & Filtering
```
**Core Features**:
- **Multi-Driver Support**: FileQueue, Redis, Database
- **Priority Scheduling**: High/Medium/Low Priority Queues
- **Metrics & Monitoring**: Real-time Performance Tracking
- **Dead Letter Queue**: Automatic Failed Job Management
- **Distributed Locking**: Multi-Worker Coordination
### 4. Background Execution & Logging
**Job Processing mit umfassender Protokollierung**
**Execution Pattern**:
```php
final class ScheduledBackgroundJob
{
public function handle(): array
{
// Business Logic Execution
$result = $this->performWork();
// Automatic Logging
$this->logExecution($result);
return $result;
}
}
```
## Verwendung
### Basic Scheduler-Queue Integration
```php
use App\Framework\Scheduler\Services\SchedulerService;
use App\Framework\Scheduler\Schedules\IntervalSchedule;
use App\Framework\Queue\Queue;
use App\Framework\Queue\ValueObjects\JobPayload;
use App\Framework\Core\ValueObjects\Duration;
// 1. Schedule Setup
$scheduler = $container->get(SchedulerService::class);
$queue = $container->get(Queue::class);
// 2. Create Interval Schedule (every 10 minutes)
$schedule = IntervalSchedule::every(Duration::fromMinutes(10));
// 3. Register Task with Queue Dispatch
$scheduler->schedule('email-cleanup', $schedule, function() use ($queue) {
$job = new EmailCleanupJob(
olderThan: Duration::fromDays(30),
batchSize: 100
);
$payload = JobPayload::immediate($job);
$queue->push($payload);
return ['status' => 'queued', 'timestamp' => time()];
});
```
### Advanced Pipeline Configuration
```php
// Complex Multi-Stage Pipeline
final class ReportGenerationPipeline
{
public function __construct(
private readonly SchedulerService $scheduler,
private readonly Queue $queue
) {}
public function setupDailyReports(): void
{
// Stage 1: Data Collection (Daily at 2 AM)
$this->scheduler->schedule(
'daily-data-collection',
CronSchedule::fromExpression('0 2 * * *'),
fn() => $this->dispatchDataCollection()
);
// Stage 2: Report Generation (After data collection)
$this->scheduler->schedule(
'daily-report-generation',
CronSchedule::fromExpression('30 2 * * *'),
fn() => $this->dispatchReportGeneration()
);
// Stage 3: Distribution (After generation)
$this->scheduler->schedule(
'daily-report-distribution',
CronSchedule::fromExpression('0 3 * * *'),
fn() => $this->dispatchReportDistribution()
);
}
private function dispatchDataCollection(): array
{
$job = new DataCollectionJob(
sources: ['database', 'analytics', 'external_apis'],
target_date: Timestamp::yesterday()
);
$payload = JobPayload::withPriority($job, Priority::HIGH);
$this->queue->push($payload);
return ['stage' => 'data_collection', 'queued_at' => time()];
}
}
```
### Named Job Classes (Best Practice)
```php
// ✅ Framework-Compliant Job Class
final class EmailCleanupJob
{
public function __construct(
private readonly Duration $olderThan,
private readonly int $batchSize = 100
) {}
public function handle(): array
{
$deleted = $this->emailService->deleteOldEmails(
$this->olderThan,
$this->batchSize
);
$this->logCleanupResults($deleted);
return [
'deleted_count' => count($deleted),
'batch_size' => $this->batchSize,
'cleanup_threshold' => $this->olderThan->toDays() . ' days'
];
}
public function getType(): string
{
return 'email-cleanup';
}
}
```
## Pipeline Monitoring
### Health Checks
```php
// Pipeline Health Verification
final class PipelineHealthChecker
{
public function checkPipelineHealth(): PipelineHealthReport
{
return new PipelineHealthReport([
'scheduler_status' => $this->checkSchedulerHealth(),
'queue_status' => $this->checkQueueHealth(),
'integration_status' => $this->checkIntegrationHealth(),
'performance_metrics' => $this->gatherPerformanceMetrics()
]);
}
private function checkSchedulerHealth(): array
{
$dueTasks = $this->scheduler->getDueTasks();
$nextExecution = $this->scheduler->getNextExecutionTime();
return [
'due_tasks_count' => count($dueTasks),
'next_execution' => $nextExecution?->format('Y-m-d H:i:s'),
'status' => count($dueTasks) < 100 ? 'healthy' : 'overloaded'
];
}
private function checkQueueHealth(): array
{
$stats = $this->queue->getStats();
return [
'queue_size' => $stats['total_size'],
'priority_distribution' => $stats['priority_breakdown'],
'processing_rate' => $this->calculateProcessingRate(),
'status' => $stats['total_size'] < 1000 ? 'healthy' : 'backlog'
];
}
}
```
### Performance Metrics
```php
// Real-time Pipeline Performance
final class PipelineMetricsCollector
{
public function collectMetrics(string $timeframe = '1hour'): array
{
return [
'scheduler_metrics' => [
'tasks_executed' => $this->getExecutedTasksCount($timeframe),
'average_execution_time' => $this->getAverageExecutionTime($timeframe),
'success_rate' => $this->getSchedulerSuccessRate($timeframe)
],
'queue_metrics' => [
'jobs_processed' => $this->getProcessedJobsCount($timeframe),
'average_wait_time' => $this->getAverageWaitTime($timeframe),
'throughput' => $this->calculateThroughput($timeframe)
],
'integration_metrics' => [
'dispatch_success_rate' => $this->getDispatchSuccessRate($timeframe),
'end_to_end_latency' => $this->getEndToEndLatency($timeframe),
'error_rate' => $this->getIntegrationErrorRate($timeframe)
]
];
}
}
```
## Troubleshooting
### Häufige Probleme
**1. Jobs werden nicht ausgeführt**
```bash
# Diagnose Queue Status
docker exec php php console.php queue:status
# Check Scheduler Tasks
docker exec php php console.php scheduler:status
# Verify Integration
docker exec php php tests/debug/test-scheduler-queue-integration-fixed.php
```
**2. Memory Leaks bei großen Jobs**
```php
// Memory-effiziente Job Implementation
final class LargeDataProcessingJob
{
public function handle(): array
{
// Batch Processing to prevent memory exhaustion
$batch = $this->dataSource->getBatch($this->batchSize);
while (!empty($batch)) {
$this->processBatch($batch);
// Force garbage collection
gc_collect_cycles();
$batch = $this->dataSource->getNextBatch($this->batchSize);
}
return ['processed' => $this->totalProcessed];
}
}
```
**3. Queue Backlog Management**
```php
// Automatic Backlog Resolution
final class BacklogManager
{
public function resolveBacklog(): void
{
$stats = $this->queue->getStats();
if ($stats['total_size'] > $this->backlogThreshold) {
// Scale up workers
$this->workerManager->scaleUp($this->calculateRequiredWorkers($stats));
// Prioritize critical jobs
$this->queue->reprioritize(['high_priority_types']);
// Alert operations team
$this->alertManager->sendBacklogAlert($stats);
}
}
}
```
## Testing
### Integration Tests
```php
// Complete Pipeline Testing
describe('Scheduler Queue Pipeline', function () {
it('handles full pipeline flow', function () {
// 1. Setup Scheduler Task
$schedule = IntervalSchedule::every(Duration::fromSeconds(1));
$this->scheduler->schedule('test-task', $schedule, function() {
$job = new TestPipelineJob('pipeline-test');
$payload = JobPayload::immediate($job);
$this->queue->push($payload);
return ['dispatched' => true];
});
// 2. Execute Scheduler
$results = $this->scheduler->executeDueTasks();
expect($results)->toHaveCount(1);
expect($results[0]->success)->toBeTrue();
// 3. Process Queue
$jobPayload = $this->queue->pop();
expect($jobPayload)->not->toBeNull();
$result = $jobPayload->job->handle();
expect($result['status'])->toBe('completed');
// 4. Verify End-to-End
expect($this->queue->size())->toBe(0);
});
});
```
### Performance Tests
```php
// Pipeline Performance Benchmarks
describe('Pipeline Performance', function () {
it('processes 1000 jobs within 30 seconds', function () {
$startTime = microtime(true);
// Dispatch 1000 jobs via scheduler
for ($i = 0; $i < 1000; $i++) {
$job = new BenchmarkJob("job-{$i}");
$payload = JobPayload::immediate($job);
$this->queue->push($payload);
}
// Process all jobs
while ($this->queue->size() > 0) {
$jobPayload = $this->queue->pop();
$jobPayload->job->handle();
}
$executionTime = microtime(true) - $startTime;
expect($executionTime)->toBeLessThan(30.0);
expect($this->queue->size())->toBe(0);
});
});
```
## Best Practices
### 1. Job Design
- **Named Classes**: Immer Named Classes statt Anonymous Functions verwenden
- **Type Safety**: JobPayload Value Objects für alle Queue-Operationen
- **Idempotency**: Jobs sollten mehrfach ausführbar sein ohne Seiteneffekte
- **Error Handling**: Graceful Degradation bei Fehlern implementieren
### 2. Scheduler Configuration
- **Reasonable Intervals**: Nicht zu aggressive Scheduling-Intervalle
- **Resource Awareness**: CPU/Memory-Limits bei Task-Design beachten
- **Monitoring**: Kontinuierliche Überwachung der Execution-Times
- **Failover**: Backup-Strategien für kritische Tasks
### 3. Queue Management
- **Priority Classes**: Sinnvolle Priorisierung für verschiedene Job-Types
- **Batch Processing**: Große Datenmengen in Batches aufteilen
- **Dead Letter Handling**: Automatische Failed-Job-Recovery
- **Metrics Collection**: Performance-Daten für Optimierung sammeln
### 4. Production Deployment
- **Health Checks**: Regelmäßige Pipeline-Health-Verification
- **Alerting**: Automatische Benachrichtigungen bei Problemen
- **Scaling**: Auto-Scaling für Worker-Processes
- **Backup**: Disaster-Recovery-Strategien für Queue-Daten
## Framework Integration
Die Pipeline nutzt konsequent Framework-Patterns:
- **Value Objects**: Timestamp, Duration, JobPayload, Priority
- **Readonly Classes**: Unveränderliche Job-Definitionen
- **Event System**: Integration mit Framework's Event Dispatcher
- **DI Container**: Automatic Service Resolution
- **Attribute Discovery**: Convention-over-Configuration
- **MCP Integration**: AI-gestützte Pipeline-Analyse
## Performance Charakteristiken
**Typische Performance-Werte**:
- **Job Dispatch Latency**: < 50ms
- **Queue Throughput**: 1000+ Jobs/Minute (FileQueue)
- **Memory Usage**: < 50MB für Standard-Jobs
- **Scheduler Precision**: ±1 Second für Cron-basierte Tasks
- **End-to-End Latency**: < 500ms für einfache Jobs
**Skalierungscharakteristiken**:
- **Horizontal Scaling**: Multi-Worker Support
- **Queue Capacity**: 100,000+ Jobs (Database-backed)
- **Scheduler Load**: 10,000+ concurrent scheduled tasks
- **Memory Efficiency**: Linear scaling mit Job-Complexity

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,671 @@
# Sockets Module Documentation
## Übersicht
Das Sockets-Modul (`src/Framework/Sockets/`) bietet eine vollständige, type-safe Abstraktion über PHP's Socket-Erweiterung. Es ermöglicht:
- **Type-safe Socket-Operationen**: Alle Socket-Operationen verwenden Value Objects statt primitiver Typen
- **TCP/UDP Server & Client**: Vollständige Server- und Client-Implementierung
- **Unix Domain Sockets**: Unterstützung für Unix Domain Sockets
- **PCNTL Integration**: Socket-Server in geforkten Worker-Prozessen
- **Async Integration**: Non-blocking Socket I/O mit Fibers
- **Connection Pooling**: Automatisches Management von Socket-Verbindungen
## Architektur
### Modulstruktur
```
src/Framework/Sockets/
├── SocketService.php # Haupt-Service (Facade)
├── SocketFactory.php # Socket-Erstellung
├── SocketServer.php # Server-Operationen
├── SocketClient.php # Client-Operationen
├── SocketConnection.php # Connection Wrapper
├── SocketConnectionPool.php # Connection Pool Management
├── SocketsInitializer.php # DI-Initializer
├── Exceptions/ # Exception-Klassen
├── ValueObjects/ # Type-safe Value Objects
└── Integration/ # PCNTL & Async Integration
```
## Value Objects
### SocketAddress
Repräsentiert eine Socket-Adresse (IPv4, IPv6 oder Unix Domain Socket).
```php
use App\Framework\Sockets\ValueObjects\SocketAddress;
// IPv4 Adresse
$address = SocketAddress::ipv4('127.0.0.1', 8080);
// IPv6 Adresse
$address = SocketAddress::ipv6('::1', 8080);
// Unix Domain Socket
$address = SocketAddress::unix('/tmp/socket.sock');
// Von String parsen
$address = SocketAddress::fromString('127.0.0.1:8080');
$address = SocketAddress::fromString('/tmp/socket.sock');
// Eigenschaften
$host = $address->getHost(); // null für Unix Sockets
$port = $address->getPort(); // null für Unix Sockets
$path = $address->getUnixPath(); // null für Network Sockets
$protocol = $address->getProtocol(); // SocketProtocol Enum
$isUnix = $address->isUnixSocket();
$isNetwork = $address->isNetworkSocket();
$string = $address->toString(); // "127.0.0.1:8080" oder "/tmp/socket.sock"
```
### SocketType
Enum für Socket-Typen.
```php
use App\Framework\Sockets\ValueObjects\SocketType;
SocketType::TCP // SOCK_STREAM
SocketType::UDP // SOCK_DGRAM
SocketType::RAW // SOCK_RAW
SocketType::RDM // SOCK_RDM
SocketType::SEQPACKET // SOCK_SEQPACKET
// Methoden
$type->getName(); // "TCP", "UDP", etc.
$type->getValue(); // SOCK_STREAM, SOCK_DGRAM, etc.
$type->isReliable(); // true für TCP, SEQPACKET
$type->isConnectionless(); // true für UDP
```
### SocketProtocol
Enum für Protokoll-Familien.
```php
use App\Framework\Sockets\ValueObjects\SocketProtocol;
SocketProtocol::IPv4 // AF_INET
SocketProtocol::IPv6 // AF_INET6
SocketProtocol::UNIX // AF_UNIX
// Methoden
$protocol->getName(); // "IPv4", "IPv6", "Unix Domain"
$protocol->getValue(); // AF_INET, AF_INET6, AF_UNIX
$protocol->isNetwork(); // true für IPv4/IPv6
$protocol->isUnix(); // true für Unix Domain
```
### SocketOption
Enum für Socket-Optionen.
```php
use App\Framework\Sockets\ValueObjects\SocketOption;
SocketOption::SO_REUSEADDR
SocketOption::SO_REUSEPORT
SocketOption::SO_KEEPALIVE
SocketOption::SO_LINGER
SocketOption::SO_RCVBUF
SocketOption::SO_SNDBUF
SocketOption::SO_RCVTIMEO
SocketOption::SO_SNDTIMEO
SocketOption::TCP_NODELAY
SocketOption::TCP_KEEPIDLE
SocketOption::TCP_KEEPINTVL
SocketOption::TCP_KEEPCNT
// Methoden
$option->getName(); // "SO_REUSEADDR", etc.
$option->getLevel(); // SOL_SOCKET oder SOL_TCP
$option->getValue(); // SO_REUSEADDR, etc.
```
### SocketResource
Type-safe Wrapper für Socket-Ressourcen mit automatischem Cleanup.
```php
use App\Framework\Sockets\ValueObjects\SocketResource;
$resource = SocketResource::fromResource($socket, $type, $protocol);
// Eigenschaften
$socketResource = $resource->getResource(); // Native Socket-Resource
$type = $resource->getType(); // SocketType
$protocol = $resource->getProtocol(); // SocketProtocol
$isClosed = $resource->isClosed();
// Manuelles Schließen (automatisch im Destructor)
$resource->close();
```
## Core Services
### SocketFactory
Factory für Socket-Erstellung mit automatischem Option-Setup.
```php
use App\Framework\Sockets\SocketFactory;
use App\Framework\Sockets\ValueObjects\SocketAddress;
use App\Framework\Sockets\ValueObjects\SocketType;
use App\Framework\Sockets\ValueObjects\SocketProtocol;
$factory = new SocketFactory();
// TCP Socket erstellen
$address = SocketAddress::ipv4('127.0.0.1', 8080);
$socket = $factory->createTcp($address);
// UDP Socket erstellen
$socket = $factory->createUdp($address);
// Unix Domain Socket erstellen
$unixAddress = SocketAddress::unix('/tmp/socket.sock');
$socket = $factory->createUnix($unixAddress);
// Generische Erstellung
$socket = $factory->createServer(SocketType::TCP, SocketProtocol::IPv4);
$socket = $factory->createClient(SocketType::TCP, SocketProtocol::IPv4);
```
### SocketServer
Server-Operationen für Socket-Verwaltung.
```php
use App\Framework\Sockets\SocketServer;
use App\Framework\Sockets\ValueObjects\SocketAddress;
use App\Framework\Sockets\ValueObjects\SocketResource;
$server = new SocketServer();
$address = SocketAddress::ipv4('0.0.0.0', 8080);
// Socket binden
$server->bind($socket, $address);
// Auf Verbindungen lauschen
$server->listen($socket, 10); // backlog = 10
// Verbindung akzeptieren (non-blocking)
$connection = $server->accept($socket);
if ($connection !== null) {
// Verbindung verarbeiten
$clientSocket = $connection->getSocket();
$clientAddress = $connection->getAddress();
}
// Socket-Auswahl (select)
$read = [$socket];
$write = [];
$except = [];
$numReady = $server->select($read, $write, $except, 1); // 1 Sekunde Timeout
// Non-blocking/Blocking Mode
$server->setNonBlocking($socket);
$server->setBlocking($socket);
```
### SocketClient
Client-Operationen für Socket-Verbindungen.
```php
use App\Framework\Sockets\SocketClient;
use App\Framework\Sockets\ValueObjects\SocketAddress;
use App\Framework\Sockets\ValueObjects\SocketResource;
$client = new SocketClient();
$address = SocketAddress::ipv4('127.0.0.1', 8080);
// Verbindung herstellen
$client->connect($socket, $address);
// Daten lesen
$data = $client->read($socket, 1024); // null wenn keine Daten (non-blocking)
// Daten schreiben
$bytesWritten = $client->write($socket, "Hello, World!");
// Daten senden (mit Flags)
$bytesSent = $client->send($socket, $data, MSG_DONTWAIT);
// Daten empfangen
$data = $client->receive($socket, 1024, MSG_DONTWAIT);
// Verbindung schließen
$client->close($socket);
```
### SocketConnection
Wrapper für Socket-Verbindungen mit Metadaten.
```php
use App\Framework\Sockets\SocketConnection;
$connection = SocketConnection::create($socketResource, $address);
// Eigenschaften
$socket = $connection->getSocket();
$address = $connection->getAddress();
$connectionId = $connection->getConnectionId();
$isClosed = $connection->isClosed();
// Verbindung schließen
$connection->close();
```
### SocketConnectionPool
Verwaltung mehrerer Socket-Verbindungen.
```php
use App\Framework\Sockets\SocketConnectionPool;
$pool = new SocketConnectionPool(
maxConnectionsPerAddress: 5,
connectionTimeoutSeconds: 300
);
// Verbindung hinzufügen
$pool->add($connection);
// Verbindung entfernen
$pool->remove($connection);
// Verbindung abrufen
$connection = $pool->get($connectionId);
$connections = $pool->getConnectionsByAddress($address);
$connection = $pool->getConnectionForAddress($address);
// Dead Connections bereinigen
$removed = $pool->cleanupDeadConnections();
// Statistiken
$count = $pool->getConnectionCount();
$allConnections = $pool->getAllConnections();
```
### SocketService
Haupt-Facade für alle Socket-Operationen.
```php
use App\Framework\Sockets\SocketService;
$socketService = $container->get(SocketService::class);
// Socket-Erstellung
$socket = $socketService->createTcp($address);
$socket = $socketService->createUdp($address);
$socket = $socketService->createUnix($address);
// Server-Operationen
$socketService->bind($socket, $address);
$socketService->listen($socket, 10);
$connection = $socketService->accept($socket);
$socketService->setNonBlocking($socket);
// Client-Operationen
$socketService->connect($socket, $address);
$data = $socketService->read($socket, 1024);
$bytes = $socketService->write($socket, $data);
// Connection Pool
$socketService->addConnection($connection);
$socketService->removeConnection($connection);
$connection = $socketService->getConnection($connectionId);
$removed = $socketService->cleanupDeadConnections();
```
## Integration
### PCNTL Integration
Socket-Server in geforkten Worker-Prozessen für Load Balancing.
```php
use App\Framework\Sockets\Integration\PcntlSocketServer;
use App\Framework\Sockets\SocketService;
use App\Framework\Pcntl\PcntlService;
use App\Framework\Sockets\ValueObjects\SocketAddress;
use App\Framework\Sockets\ValueObjects\SocketResource;
$socketService = $container->get(SocketService::class);
$pcntlService = $container->get(PcntlService::class);
$pcntlServer = new PcntlSocketServer(
socketService: $socketService,
pcntlService: $pcntlService,
maxWorkers: 4
);
// Connection Handler setzen
$pcntlServer->setConnectionHandler(function (SocketConnection $connection) {
// Verbindung verarbeiten
$socket = $connection->getSocket();
$data = $socketService->read($socket->getResource(), 1024);
// ...
});
// Server starten
$address = SocketAddress::ipv4('0.0.0.0', 8080);
$socket = $socketService->createTcp($address);
$pcntlServer->start($socket, $address, workerCount: 4);
// Server stoppen (graceful shutdown)
$pcntlServer->stop();
```
### Async Integration
Non-blocking Socket-Server mit Fibers für parallele Connection-Verarbeitung.
```php
use App\Framework\Sockets\Integration\AsyncSocketServer;
use App\Framework\Sockets\SocketService;
use App\Framework\Async\FiberManager;
use App\Framework\Sockets\ValueObjects\SocketAddress;
$socketService = $container->get(SocketService::class);
$fiberManager = $container->get(FiberManager::class);
$asyncServer = new AsyncSocketServer(
socketService: $socketService,
fiberManager: $fiberManager
);
// Server starten (gibt Fiber zurück)
$address = SocketAddress::ipv4('0.0.0.0', 8080);
$socket = $socketService->createTcp($address);
$serverFiber = $asyncServer->startAsync(
socket: $socket,
address: $address,
connectionHandler: function (SocketConnection $connection) {
// Connection in separatem Fiber verarbeiten
$socket = $connection->getSocket();
// Non-blocking lesen
$readFiber = $asyncServer->readAsync($connection, 1024);
$data = $readFiber->start();
// Non-blocking schreiben
$writeFiber = $asyncServer->writeAsync($connection, "Response");
$bytes = $writeFiber->start();
}
);
// Server-Fiber starten
$serverFiber->start();
```
## Beispiele
### Einfacher TCP Server
```php
use App\Framework\Sockets\SocketService;
use App\Framework\Sockets\ValueObjects\SocketAddress;
$socketService = $container->get(SocketService::class);
$address = SocketAddress::ipv4('0.0.0.0', 8080);
// Socket erstellen
$socket = $socketService->createTcp($address);
// Binden und lauschen
$socketService->bind($socket, $address);
$socketService->listen($socket, 10);
$socketService->setNonBlocking($socket);
// Hauptschleife
while (true) {
// Verbindung akzeptieren
$connection = $socketService->accept($socket);
if ($connection !== null) {
$clientSocket = $connection->getSocket();
// Daten lesen
$data = $socketService->read($clientSocket, 1024);
if ($data !== null) {
// Antwort senden
$socketService->write($clientSocket, "Echo: " . $data);
}
// Verbindung schließen
$connection->close();
}
usleep(10000); // 10ms
}
```
### TCP Client
```php
use App\Framework\Sockets\SocketService;
use App\Framework\Sockets\ValueObjects\SocketAddress;
$socketService = $container->get(SocketService::class);
$address = SocketAddress::ipv4('127.0.0.1', 8080);
// Client-Socket erstellen
$socket = $socketService->createClient(
SocketType::TCP,
SocketProtocol::IPv4
);
// Verbinden
$socketService->connect($socket, $address);
// Daten senden
$socketService->write($socket, "Hello, Server!");
// Antwort lesen
$response = $socketService->read($socket, 1024);
// Verbindung schließen
$socketService->close($socket);
```
### Unix Domain Socket Server
```php
use App\Framework\Sockets\SocketService;
use App\Framework\Sockets\ValueObjects\SocketAddress;
$socketService = $container->get(SocketService::class);
$address = SocketAddress::unix('/tmp/mysocket.sock');
// Socket erstellen
$socket = $socketService->createUnix($address);
// Binden und lauschen
$socketService->bind($socket, $address);
$socketService->listen($socket, 10);
// Verbindungen akzeptieren
while (true) {
$connection = $socketService->accept($socket);
if ($connection !== null) {
// Verarbeitung
$socket = $connection->getSocket();
$data = $socketService->read($socket, 1024);
// ...
}
}
```
## Migration von bestehendem Code
### Von `socket_create()` zu `SocketFactory`
**Vorher:**
```php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
```
**Nachher:**
```php
$address = SocketAddress::ipv4('127.0.0.1', 8080);
$socket = $socketService->createTcp($address);
```
### Von `socket_bind()`/`socket_listen()` zu `SocketServer`
**Vorher:**
```php
socket_bind($socket, '127.0.0.1', 8080);
socket_listen($socket, 10);
```
**Nachher:**
```php
$address = SocketAddress::ipv4('127.0.0.1', 8080);
$socketService->bind($socket, $address);
$socketService->listen($socket, 10);
```
### Von `socket_accept()` zu `SocketServer::accept()`
**Vorher:**
```php
$clientSocket = socket_accept($socket);
```
**Nachher:**
```php
$connection = $socketService->accept($socket);
if ($connection !== null) {
$clientSocket = $connection->getSocket();
}
```
### Von `socket_select()` zu `SocketServer::select()`
**Vorher:**
```php
socket_select($read, $write, $except, 1);
```
**Nachher:**
```php
$numReady = $socketService->select($read, $write, $except, 1);
```
### Von `socket_read()`/`socket_write()` zu `SocketClient`
**Vorher:**
```php
$data = socket_read($socket, 1024);
socket_write($socket, $data, strlen($data));
```
**Nachher:**
```php
$data = $socketService->read($socket, 1024);
$bytes = $socketService->write($socket, $data);
```
### Von `socket_close()` zu `SocketResource` Destructor
**Vorher:**
```php
socket_close($socket);
```
**Nachher:**
```php
// Automatisch im Destructor, oder manuell:
$socket->close();
```
## Fehlerbehandlung
Das Sockets-Modul verwendet spezifische Exceptions für verschiedene Fehlertypen:
```php
use App\Framework\Sockets\Exceptions\SocketException;
use App\Framework\Sockets\Exceptions\SocketBindException;
use App\Framework\Sockets\Exceptions\SocketConnectException;
use App\Framework\Sockets\Exceptions\SocketReadException;
try {
$socketService->bind($socket, $address);
} catch (SocketBindException $e) {
// Bind-Fehler behandeln
error_log("Failed to bind: " . $e->getMessage());
}
try {
$socketService->connect($socket, $address);
} catch (SocketConnectException $e) {
// Connect-Fehler behandeln
error_log("Failed to connect: " . $e->getMessage());
}
try {
$data = $socketService->read($socket, 1024);
} catch (SocketReadException $e) {
// Read-Fehler behandeln
if ($e->getMessage() === 'Socket connection closed') {
// Verbindung geschlossen
}
}
```
## Best Practices
1. **SocketService verwenden**: Nutze immer `SocketService` als Haupt-API statt direkter Aufrufe der einzelnen Services
2. **Value Objects**: Verwende immer Value Objects (`SocketAddress`, `SocketType`, etc.) statt primitiver Typen
3. **Resource Management**: `SocketResource` kümmert sich automatisch um Cleanup, aber manuelles Schließen ist für explizite Kontrolle möglich
4. **Non-blocking I/O**: Verwende `setNonBlocking()` für Server-Sockets, um mehrere Verbindungen parallel zu handhaben
5. **Connection Pooling**: Nutze `SocketConnectionPool` für Client-Verbindungen, die wiederverwendet werden sollen
6. **Error Handling**: Fange spezifische Exceptions (`SocketBindException`, `SocketConnectException`, etc.) für präzise Fehlerbehandlung
7. **PCNTL für Load Balancing**: Verwende `PcntlSocketServer` für Socket-Server, die mehrere Worker-Prozesse benötigen
8. **Async für Parallelität**: Verwende `AsyncSocketServer` für non-blocking I/O mit Fibers
## Dependency Injection
Alle Services werden automatisch über `SocketsInitializer` im DI-Container registriert:
```php
use App\Framework\Sockets\SocketService;
use App\Framework\Sockets\SocketFactory;
use App\Framework\Sockets\SocketServer;
use App\Framework\Sockets\SocketClient;
// Services sind im Container verfügbar
$socketService = $container->get(SocketService::class);
$factory = $container->get(SocketFactory::class);
$server = $container->get(SocketServer::class);
$client = $container->get(SocketClient::class);
```
## Framework-Kompatibilität
Das Sockets-Modul folgt allen Framework-Prinzipien:
-**Final readonly classes** wo möglich
-**Value Objects** statt Primitiven
-**Dependency Injection** überall
-**Composition** statt Inheritance
-**Strict Types** (`declare(strict_types=1)`)
-**PSR-12** Code Style
## Siehe auch
- [PCNTL Module Documentation](./pcntl-module.md) - Für PCNTL-Integration
- [Async Module Documentation](./async-module.md) - Für Async-Integration
- [Framework Guidelines](./guidelines.md) - Allgemeine Framework-Prinzipien

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff