*/ protected array $items; /** * @var array */ protected array $changeItems = []; /** * @param array $options */ public function __construct(array $nonces = [], array $options = []) { $this->options = [ 'size' => max(1, (int)($options['size'] ?? self::DEFAULT_SIZE)), 'expiration' => max(0, (int)($options['expiration'] ?? self::DEFAULT_EXPIRATION)), ]; foreach ($nonces as $name => $value) { if ($value !== null && !$value instanceof Nonce) { throw new \LogicException(sprintf('Invalid valid for nonce "%s"', $name), 1664195013); } } // filter valid items $this->items = array_filter( $nonces, fn(?Nonce $item, string $name) => $item !== null && $this->isValidNonceName($item, $name) && $this->isNonceUpToDate($item), ARRAY_FILTER_USE_BOTH ); // items that were not valid -> to be revoked $invalidItems = array_diff_key($nonces, $this->items); $this->changeItems = array_fill_keys(array_keys($invalidItems), null); } public function findSigningSecret(string $name): ?Nonce { return $this->items[$name] ?? null; } public function provideSigningSecret(): Nonce { $items = array_filter($this->changeItems); $nonce = reset($items); if (!$nonce instanceof Nonce) { $nonce = Nonce::create(); $this->emit($nonce); } return $nonce; } public function merge(self $other): self { $this->items = array_merge($this->items, $other->items); $this->changeItems = array_merge($this->changeItems, $other->changeItems); return $this; } public function purge(): self { $size = $this->options['size']; $items = array_filter($this->items); if (count($items) <= $size) { return $this; } uasort($items, static fn(Nonce $a, Nonce $b) => $b->time <=> $a->time); $exceedingItems = array_splice($items, $size, null, []); foreach ($exceedingItems as $name => $_) { $this->changeItems[$name] = null; } return $this; } public function emit(Nonce $nonce): self { $this->changeItems[$nonce->getSigningIdentifier()->name] = $nonce; return $this; } public function revoke(Nonce $nonce): self { $this->revokeSigningSecret($nonce->getSigningIdentifier()->name); return $this; } public function revokeSigningSecret(string $name): void { if (isset($this->items[$name])) { $this->changeItems[$name] = null; } } /** * @return array */ public function getEmittableNonces(): array { return array_filter($this->changeItems); } /** * @return list */ public function getRevocableNames(): array { return array_keys( array_diff_key($this->changeItems, $this->getEmittableNonces()) ); } protected function isValidNonceName(Nonce $nonce, $name): bool { return $nonce->getSigningIdentifier()->name === $name; } protected function isNonceUpToDate(Nonce $nonce): bool { if ($this->options['expiration'] <= 0) { return true; } $now = new \DateTimeImmutable(); $interval = new \DateInterval(sprintf('PT%dS', $this->options['expiration'])); return $nonce->time->add($interval) > $now; } }