*/ private array $entries = []; public function __construct( private readonly Duration $windowSize, private readonly string $identifier, private readonly SlidingWindowAggregator $aggregator, ) { } public function record(mixed $value, Timestamp $timestamp): void { $this->entries[] = new WindowEntry($value, $timestamp); $this->cleanup(); } public function getValues(?Timestamp $now = null): array { $now ??= Timestamp::now(); $this->cleanup($now); return array_map( fn (WindowEntry $entry) => $entry->value, $this->getValidEntries($now) ); } public function getStats(?Timestamp $now = null): SlidingWindowStats { $now ??= Timestamp::now(); $this->cleanup($now); $validEntries = $this->getValidEntries($now); $windowStart = $now->diff($this->windowSize) > Duration::zero() ? Timestamp::fromFloat($now->toFloat() - $this->windowSize->toSeconds()) : $now; $aggregatedData = []; if (! empty($validEntries)) { $result = $this->aggregator->aggregate(...$validEntries); $aggregatedData = method_exists($result, 'toArray') ? $result->toArray() : [$this->aggregator->getIdentifier() => $result]; } return new SlidingWindowStats( totalCount: count($validEntries), windowSize: $this->windowSize, windowStart: $windowStart, windowEnd: $now, aggregatedData: $aggregatedData ); } public function clear(): void { $this->entries = []; } public function getWindowSize(): Duration { return $this->windowSize; } public function getIdentifier(): string { return $this->identifier; } /** * Get all valid entries within the window * * @return array */ private function getValidEntries(Timestamp $now): array { $cutoffTime = Timestamp::fromFloat($now->toFloat() - $this->windowSize->toSeconds()); return array_filter( $this->entries, fn (WindowEntry $entry) => $entry->isWithinWindow($cutoffTime) ); } /** * Remove entries outside the window */ private function cleanup(?Timestamp $now = null): void { $now ??= Timestamp::now(); $this->entries = $this->getValidEntries($now); } }