# Poll System Usage Examples Das Poll-System ermöglicht es, beliebige Methoden als pollable zu markieren und automatisch in konfigurierbaren Intervallen auszuführen. ## Zwei Ansätze: Attribute vs. Closures Das Framework bietet **zwei komplementäre Ansätze** für Polling: 1. **Attribute-basiert (#[Poll])**: Für langlebige, wiederverwendbare Poll-Methoden in Klassen 2. **Closure-basiert (PollableClosure)**: Für Template-spezifische, dynamische Polls --- ## Attribute-Based Polling ### 1. Methode mit #[Poll] Attribut markieren ```php use App\Framework\LiveComponents\Attributes\Poll; final readonly class NotificationComponent { public function __construct( private NotificationService $notificationService ) {} /** * Poll for new notifications every second */ #[Poll(interval: 1000)] public function checkNotifications(): array { return [ 'count' => $this->notificationService->getUnreadCount(), 'latest' => $this->notificationService->getLatest(5) ]; } } ``` ### 2. Poll manuell ausführen ```php use App\Framework\LiveComponents\Polling\PollExecutor; $executor = $container->get(PollExecutor::class); // Execute specific poll $result = $executor->executePoll( NotificationComponent::class, 'checkNotifications' ); if ($result->isSuccess()) { $data = $result->data; echo "Unread notifications: {$data['count']}\n"; } ``` ### Poll mit Event Dispatch ```php use App\Framework\LiveComponents\Attributes\Poll; final readonly class ServerHealthComponent { #[Poll( interval: 5000, // Every 5 seconds event: 'server.health.checked' // Dispatch event )] public function checkHealth(): array { return [ 'cpu' => $this->metrics->getCpuUsage(), 'memory' => $this->metrics->getMemoryUsage(), 'disk' => $this->metrics->getDiskUsage() ]; } } ``` **Event Handler**: ```php use App\Framework\Core\Events\OnEvent; use App\Framework\LiveComponents\Polling\PollExecutedEvent; final readonly class HealthMonitor { #[OnEvent(PollExecutedEvent::class)] public function onHealthChecked(PollExecutedEvent $event): void { if ($event->poll->event === 'server.health.checked') { $health = $event->result; if ($health['cpu'] > 80) { $this->alerting->sendAlert('High CPU usage detected'); } } } } ``` ### Poll mit stopOnError ```php final readonly class CriticalMonitor { #[Poll( interval: 10000, stopOnError: true // Stop polling if error occurs )] public function checkCriticalService(): array { // Will stop polling if this throws return $this->criticalService->healthCheck(); } } ``` --- ## Closure-Based Polling **Ideal für Template-spezifische, dynamische Polls ohne dedizierte Klassen.** ### Basic Usage in Templates ```php use App\Framework\LiveComponents\Polling\PollableClosure; // In Controller final readonly class DashboardController { #[Route('/dashboard', Method::GET)] public function dashboard(): ViewResult { return new ViewResult('dashboard', [ // ✅ PREFERRED: First-class callable syntax (...) 'notifications' => new PollableClosure( closure: $this->notificationService->getUnread(...), interval: 1000 ), 'serverStatus' => new PollableClosure( closure: $this->healthService->getStatus(...), interval: 5000 ) ]); } } ``` **Warum `...` bevorzugen?** - ✅ Kürzer und lesbarer - ✅ Bessere Performance (keine zusätzliche Closure) - ✅ IDE kann Methodensignatur besser analysieren - ✅ PHP 8.1+ Standard für first-class callables ### Advanced Closure Examples **Mit Parametern (closure wrapper notwendig)**: ```php // ✅ First-class callable mit Parametern 'userActivity' => new PollableClosure( closure: fn() => $this->activityService->getRecent($userId, 10), interval: 2000 ) // Wenn Methode Parameter braucht, MUSS closure wrapper verwendet werden ``` **Mit Event Dispatch**: ```php 'systemHealth' => new PollableClosure( closure: $this->monitoring->getHealth(...), interval: 10000, event: 'system.health.polled' // Dispatches event ) ``` **Mit stopOnError**: ```php 'criticalData' => new PollableClosure( closure: $this->dataService->getCritical(...), interval: 1000, stopOnError: true // Stops on first error ) ``` **Disabled by Default**: ```php 'debugInfo' => new PollableClosure( closure: $this->debug->getInfo(...), interval: 500, enabled: false // Disabled until explicitly enabled ) ``` ### Automatic Template Integration **Der `PollableClosureTransformer` macht Closures automatisch pollbar:** ```php // Template: dashboard.view.php

Notifications ({{ $notifications['count'] }})

Server Status: {{ $serverStatus['status'] }}

``` **Generated JavaScript (automatisch)**: ```javascript // Automatisch generiert vom PollableClosureTransformer (function() { function poll_notifications() { fetch('/poll/{pollId}', { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' } }) .then(response => response.json()) .then(data => { // Update DOM elements with data-poll="notifications" const elements = document.querySelectorAll('[data-poll="notifications"]'); elements.forEach(el => el.textContent = data); }); } // Start polling every 1000ms setInterval(poll_notifications, 1000); poll_notifications(); // Initial call })(); ``` ### Manual Closure Registration ```php use App\Framework\LiveComponents\Polling\PollableClosureRegistry; use App\Framework\LiveComponents\ValueObjects\ComponentId; $registry = $container->get(PollableClosureRegistry::class); // Register closure for specific component $registration = $registry->register( templateKey: 'notifications', closure: new PollableClosure( closure: $this->notificationService->getUnread(...), interval: 1000 ), componentId: ComponentId::fromString('dashboard-component') ); // Get endpoint URL $endpoint = $registration->getEndpoint(); // "/poll/{pollId}" // Execute closure $result = $registry->execute($registration->pollId); ``` --- ## Unified Poll Controller **Beide Poll-Typen nutzen denselben Endpoint:** ``` GET /poll/{pollId} ``` **PollId Format unterscheidet Typen:** - `closure.*` → Closure-based Poll - `attribute.*` → Attribute-based Poll **Beispiel Request**: ```bash # Closure-based poll curl https://localhost/poll/closure.notifications.abc123 # Attribute-based poll curl https://localhost/poll/attribute.App\\NotificationComponent::checkNotifications ``` **Response Format**: ```json { "count": 5, "latest": [ {"id": 1, "message": "New order"}, {"id": 2, "message": "Payment received"} ] } ``` --- ## Poll Discovery ### List all registered polls ```php use App\Framework\LiveComponents\Polling\PollService; $pollService = $container->get(PollService::class); // Get all attribute-based polls $allPolls = $pollService->getAllPolls(); foreach ($allPolls as $item) { $poll = $item['poll']; $discovered = $item['discovered']; echo sprintf( "Poll: %s::%s - Interval: %dms - Enabled: %s\n", $discovered->className->getFullyQualified(), $discovered->methodName->toString(), $poll->interval, $poll->enabled ? 'Yes' : 'No' ); } ``` ### Get closure-based polls ```php use App\Framework\LiveComponents\Polling\PollableClosureRegistry; $registry = $container->get(PollableClosureRegistry::class); // Get all registered closures $registrations = $registry->getAll(); foreach ($registrations as $registration) { echo sprintf( "Closure Poll: %s - Interval: %dms\n", $registration->templateKey, $registration->closure->interval ); } // Get enabled closures only $enabled = $registry->getEnabled(); ``` --- ## Execution Patterns ### Execute all enabled attribute polls ```php $executor = $container->get(PollExecutor::class); // Execute all enabled polls $results = $executor->executeAllEnabledPolls(); foreach ($results as $result) { if ($result->isSuccess()) { echo "{$result->getPollId()} succeeded in {$result->executionTime->toMilliseconds()}ms\n"; } else { echo "{$result->getPollId()} failed: {$result->error?->getMessage()}\n"; } } ``` ### Execute closure polls ```php $registry = $container->get(PollableClosureRegistry::class); // Execute specific closure by PollId $pollId = PollId::fromString('closure.notifications.abc123'); $result = $registry->execute($pollId); // Process result echo json_encode($result); ``` --- ## Integration with Scheduler ### Schedule attribute polls ```php use App\Framework\Scheduler\Services\SchedulerService; use App\Framework\Scheduler\Schedules\IntervalSchedule; use App\Framework\Core\ValueObjects\Duration; $scheduler = $container->get(SchedulerService::class); $executor = $container->get(PollExecutor::class); // Schedule poll execution $scheduler->schedule( 'execute-all-polls', IntervalSchedule::every(Duration::fromSeconds(1)), fn() => ['executed' => count($executor->executeAllEnabledPolls())] ); ``` ### Schedule closure polls ```php $scheduler->schedule( 'execute-closure-polls', IntervalSchedule::every(Duration::fromSeconds(1)), function() use ($registry) { $enabled = $registry->getEnabled(); $results = []; foreach ($enabled as $registration) { try { $results[] = $registry->execute($registration->pollId); } catch (\Throwable $e) { // Handle error } } return ['executed' => count($results)]; } ); ``` --- ## Performance Monitoring ### Get execution statistics ```php $stats = $executor->getExecutionStats(); echo "Total polls: {$stats['total_polls']}\n"; echo "Enabled polls: {$stats['enabled_polls']}\n"; echo "\nPolls by interval:\n"; foreach ($stats['polls_by_interval'] as $interval => $count) { echo " {$interval}ms: {$count} polls\n"; } ``` ### Monitor poll performance ```php use App\Framework\Core\Events\OnEvent; final readonly class PollPerformanceMonitor { #[OnEvent(PollExecutedEvent::class)] public function onPollExecuted(PollExecutedEvent $event): void { $duration = $event->executionTime; // Log slow polls if ($duration->toMilliseconds() > 100) { $this->logger->warning('Slow poll detected', [ 'poll' => $event->getPollId(), 'duration_ms' => $duration->toMilliseconds() ]); } // Track metrics $this->metrics->timing( "poll.{$event->methodName->toString()}.duration", $duration->toMilliseconds() ); } } ``` --- ## Best Practices ### Wann Attribute, wann Closures? **Attribute (#[Poll]) verwenden für:** - ✅ Wiederverwendbare Business-Logic-Polls - ✅ Polls, die von mehreren Templates genutzt werden - ✅ Komplexe Polls mit Event-Dispatch - ✅ Service-Layer Polls (Health Checks, Monitoring) **Closures (PollableClosure) verwenden für:** - ✅ Template-spezifische Polls - ✅ Einmalige, nicht wiederverwendbare Polls - ✅ Dynamische Polls mit Template-Kontext - ✅ Quick Prototyping ohne dedizierte Klassen ### Interval Guidelines - **Fast (100-1000ms)**: Critical real-time data, notifications - **Medium (1000-10000ms)**: Health checks, status updates - **Slow (10000-60000ms)**: Background sync, cache refresh - **Very Slow (>60000ms)**: Analytics, cleanup tasks ### Method Requirements - Must be `public` - Return type should be serializable (`array`, `SerializableState`, scalars) - Should be idempotent (safe to call multiple times) - Should not have side effects that break on repeated execution ### Error Handling - Use `stopOnError: true` for critical polls - Implement proper logging in poll methods - Return meaningful data structures - Handle exceptions gracefully ### Performance - Keep poll methods fast (<100ms ideal) - Avoid heavy database queries - Use caching where appropriate - Monitor execution times via events ### First-Class Callable Syntax (...) **✅ IMMER bevorzugen wenn möglich:** ```php // ✅ BEST: First-class callable new PollableClosure( closure: $this->service->getData(...), interval: 1000 ) // ⚠️ OK: Wenn Parameter nötig new PollableClosure( closure: fn() => $this->service->getData($userId), interval: 1000 ) // ❌ AVOID: Unnötiger fn() wrapper new PollableClosure( closure: fn() => $this->service->getData(), // AVOID! interval: 1000 ) ``` **Vorteile von `...` Syntax:** - Kürzer und lesbarer - Keine zusätzliche Closure-Allokation - Bessere IDE-Unterstützung - Performance-Vorteil (minimal aber vorhanden) ### Testing ```php it('executes poll and returns expected data', function () { $component = new NotificationComponent($this->notificationService); $result = $component->checkNotifications(); expect($result)->toHaveKey('count'); expect($result)->toHaveKey('latest'); }); it('poll attribute is discoverable', function () { $pollService = $this->container->get(PollService::class); expect($pollService->isPollable( NotificationComponent::class, 'checkNotifications' ))->toBeTrue(); }); it('closure poll executes correctly', function () { $closure = new PollableClosure( closure: fn() => ['status' => 'ok'], interval: 1000 ); $result = $closure->execute(); expect($result)->toHaveKey('status'); expect($result['status'])->toBe('ok'); }); it('closure poll is registered correctly', function () { $registry = $this->container->get(PollableClosureRegistry::class); $registration = $registry->register( 'test', new PollableClosure(fn() => [], 1000) ); expect($registry->has($registration->pollId))->toBeTrue(); }); ``` --- ## Framework Compliance ✅ **Readonly Classes**: Poll, PollableClosure, PollResult, Events are all readonly ✅ **Value Objects**: Uses ClassName, MethodName, Duration, ComponentId, PollId VOs ✅ **Immutability**: All poll-related objects are immutable ✅ **Discovery Integration**: Automatic via DiscoveryRegistry (attribute-based) ✅ **Event System**: Full integration with EventDispatcher ✅ **Type Safety**: Strong typing throughout ✅ **First-Class Callables**: Supports PHP 8.1+ `...` syntax ✅ **Unified API**: Single endpoint for both poll types (`/poll/{pollId}`)