*/ private array $registrations = []; /** * Register a pollable closure. * * @param string $templateKey Template variable key (e.g., 'notifications') * @param PollableClosure $closure The pollable closure * @param ComponentId|null $componentId Optional component ID for scoping * @return PollableClosureRegistration */ public function register( string $templateKey, PollableClosure $closure, ?ComponentId $componentId = null ): PollableClosureRegistration { $pollId = $this->generatePollId($templateKey, $componentId); $registration = new PollableClosureRegistration( pollId: $pollId, closure: $closure, templateKey: $templateKey, componentId: $componentId ); $this->registrations[$pollId->value] = $registration; return $registration; } /** * Get a registration by poll ID. */ public function get(PollId $pollId): ?PollableClosureRegistration { return $this->registrations[$pollId->value] ?? null; } /** * Check if a poll ID exists. */ public function has(PollId $pollId): bool { return isset($this->registrations[$pollId->value]); } /** * Get all registered closures. * * @return array */ public function getAll(): array { return array_values($this->registrations); } /** * Get all enabled registrations. * * @return array */ public function getEnabled(): array { return array_values(array_filter( $this->registrations, fn(PollableClosureRegistration $reg) => $reg->shouldPoll() )); } /** * Get registrations for specific component. * * @return array */ public function getForComponent(ComponentId $componentId): array { return array_values(array_filter( $this->registrations, fn(PollableClosureRegistration $reg) => $reg->isForComponent($componentId) )); } /** * Execute a closure by poll ID. * * @throws \RuntimeException if poll ID not found or closure throws */ public function execute(PollId $pollId): mixed { $registration = $this->get($pollId); if ($registration === null) { throw new \RuntimeException("Poll ID '{$pollId}' not found"); } try { return $registration->execute(); } catch (\Throwable $e) { throw new \RuntimeException( "Failed to execute pollable closure '{$pollId}': {$e->getMessage()}", previous: $e ); } } /** * Unregister a closure. */ public function unregister(PollId $pollId): void { unset($this->registrations[$pollId->value]); } /** * Clear all registered closures. */ public function clear(): void { $this->registrations = []; } /** * Get count of registered closures. */ public function count(): int { return count($this->registrations); } /** * Generate unique poll ID. */ private function generatePollId(string $templateKey, ?ComponentId $componentId): PollId { if ($componentId !== null) { return PollId::forClosure($templateKey, $componentId->toString()); } return PollId::forClosure($templateKey); } /** * Get polling metadata for JavaScript generation. * * @return array */ public function getPollingMetadata(): array { $metadata = []; foreach ($this->registrations as $pollIdString => $registration) { if (!$registration->shouldPoll()) { continue; } $metadata[$pollIdString] = $registration->getMetadata(); } return $metadata; } }