$providers */ public function __construct( #[AutowireIterator('app.provider')] private readonly iterable $providers, private readonly ContributionStore $store, private readonly LoggerInterface $logger, ) {} /** * Incrementally fetches fresh contributions from each configured provider, merges them into * the store, and returns the rolling render window summed across providers. * * For each provider, only data since its last stored date (minus a trailing overlap) is * fetched; a provider with no stored history yet fetches its full default range. Fetches are * split into start/resolve phases so all providers' requests are in flight concurrently. * Failures are logged and the affected provider is skipped, not fatal to the others. * * @return array date (Y-m-d) => contribution count, summed across all providers */ public function aggregate(): array { $configured = []; /** @var ProviderInterface $provider */ foreach ($this->providers as $provider) { if ($provider->isConfigured()) { $configured[] = $provider; } } $pending = []; foreach ($configured as $provider) { $latest = $this->store->latestDate($provider->getName()); $since = $latest !== null ? (new \DateTimeImmutable('@' . $latest))->modify('-' . self::OVERLAP_DAYS . ' days') : null; try { $pending[] = [$provider, $provider->startFetch($since)]; } catch (\Throwable $e) { $this->logger->warning(sprintf('%s fetch failed: %s', $provider::class, $e->getMessage()), ['exception' => $e]); } } foreach ($pending as [$provider, $handle]) { try { $fresh = $provider->resolveFetch($handle); $dateCounts = []; foreach ($fresh as $date => $count) { $dateCounts[(new \DateTimeImmutable($date))->getTimestamp()] = $count; } $this->store->merge($provider->getName(), $dateCounts); } catch (\Throwable $e) { $this->logger->warning(sprintf('%s fetch failed: %s', $provider::class, $e->getMessage()), ['exception' => $e]); } } $contributions = []; foreach ($configured as $provider) { foreach ($this->store->all($provider->getName(), sinceDays: self::RENDER_WINDOW_DAYS) as $contribution) { $date = (new \DateTimeImmutable('@' . $contribution->date))->format('Y-m-d'); $contributions[$date] = ($contributions[$date] ?? 0) + $contribution->count; } } $this->store->prune(); return $contributions; } }