Files
michaelschiemer/src/Framework/LiveComponents/Polling/PollableClosureRegistry.php

178 lines
4.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\LiveComponents\Polling;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
/**
* Registry for tracking PollableClosure instances in templates.
*
* Maintains a mapping of PollIds to their pollable closure registrations
* for automatic JavaScript generation and endpoint registration.
*/
final class PollableClosureRegistry
{
/** @var array<string, PollableClosureRegistration> */
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<PollableClosureRegistration>
*/
public function getAll(): array
{
return array_values($this->registrations);
}
/**
* Get all enabled registrations.
*
* @return array<PollableClosureRegistration>
*/
public function getEnabled(): array
{
return array_values(array_filter(
$this->registrations,
fn(PollableClosureRegistration $reg) => $reg->shouldPoll()
));
}
/**
* Get registrations for specific component.
*
* @return array<PollableClosureRegistration>
*/
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<string, array{template_key: string, interval: int, event: ?string, endpoint: string, component_id: ?string}>
*/
public function getPollingMetadata(): array
{
$metadata = [];
foreach ($this->registrations as $pollIdString => $registration) {
if (!$registration->shouldPoll()) {
continue;
}
$metadata[$pollIdString] = $registration->getMetadata();
}
return $metadata;
}
}