BlocklistCache.php 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Infrastructure\Reputation;
  4. use App\Domain\Policy\Policy;
  5. use App\Domain\Reputation\Blocklist;
  6. use App\Domain\Reputation\BlocklistBuilder;
  7. use App\Domain\Time\Clock;
  8. /**
  9. * Per-policy in-process cache of `Blocklist` results.
  10. *
  11. * SPEC §M07.3: 30-second TTL by default, keyed on `policy_id`. Cache stores
  12. * the domain `Blocklist` object — text and JSON renderings happen
  13. * downstream and share the same cached build (the build is the expensive
  14. * part).
  15. *
  16. * Invalidation: mutation paths call `invalidate($policyId)` for policy-
  17. * scoped changes and `invalidateAll()` for global changes (manual blocks,
  18. * allowlist, "rebuild scores"). Tests can pass `ttlSeconds=0` to disable
  19. * caching entirely.
  20. *
  21. * Multi-replica caveat: the cache is per-process. The cidr-evaluator
  22. * factory has the same caveat (see PROGRESS.md M06).
  23. */
  24. class BlocklistCache
  25. {
  26. /**
  27. * @var array<int, array{blocklist: Blocklist, expires_at: float}>
  28. */
  29. private array $cache = [];
  30. public function __construct(
  31. private readonly BlocklistBuilder $builder,
  32. private readonly Clock $clock,
  33. private readonly int $ttlSeconds = 30,
  34. ) {
  35. }
  36. public function getOrBuild(Policy $policy): Blocklist
  37. {
  38. $now = (float) $this->clock->now()->getTimestamp();
  39. $entry = $this->cache[$policy->id] ?? null;
  40. if ($entry !== null && $now < $entry['expires_at']) {
  41. return $entry['blocklist'];
  42. }
  43. $blocklist = $this->builder->build($policy);
  44. if ($this->ttlSeconds > 0) {
  45. $this->cache[$policy->id] = [
  46. 'blocklist' => $blocklist,
  47. 'expires_at' => $now + $this->ttlSeconds,
  48. ];
  49. }
  50. return $blocklist;
  51. }
  52. public function invalidate(int $policyId): void
  53. {
  54. unset($this->cache[$policyId]);
  55. }
  56. public function invalidateAll(): void
  57. {
  58. $this->cache = [];
  59. }
  60. }