# 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
<div class="notifications">
    <h3>Notifications ({{ $notifications['count'] }})</h3>
    <!-- Data wird initial gerendert -->
    <!-- JavaScript polled automatisch /poll/{pollId} -->
</div>

<div class="status">
    <p>Server Status: {{ $serverStatus['status'] }}</p>
    <!-- Wird automatisch alle 5s aktualisiert -->
</div>
```

**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}`)
