*/ private array $entries = []; /** * @var list */ private array $invalidations = []; private int $resolutionSequence = 0; public function __construct(?string $requestScopeId = null) { $this->requestScopeId = $requestScopeId ?? (string) Str::uuid(); } public function requestScopeId(): string { return $this->requestScopeId; } public function resolve( DerivedStateKey $key, callable $resolver, ?string $freshnessPolicy = null, ?bool $allowNegativeResultCache = null, ): mixed { $freshnessPolicy ??= $key->family->defaultFreshnessPolicy(); if ($freshnessPolicy === self::FRESHNESS_NO_REUSE) { return $resolver(); } $fingerprint = $key->fingerprint(); if (array_key_exists($fingerprint, $this->entries)) { return $this->entries[$fingerprint]['value']; } $value = $resolver(); $negativeResult = $this->isNegativeResult($value); $allowNegativeResultCache ??= $key->family->allowsNegativeResultCache(); if ($negativeResult && ! $allowNegativeResultCache) { return $value; } $this->entries[$fingerprint] = [ 'key' => $key, 'value' => $value, 'negative_result' => $negativeResult, 'freshness_policy' => $freshnessPolicy, 'resolved_at' => ++$this->resolutionSequence, ]; return $value; } public function resolveFresh( DerivedStateKey $key, callable $resolver, ?string $freshnessPolicy = null, ?bool $allowNegativeResultCache = null, ): mixed { $this->invalidateKey($key); return $this->resolve($key, $resolver, $freshnessPolicy, $allowNegativeResultCache); } public function invalidateKey(DerivedStateKey $key): int { $fingerprint = $key->fingerprint(); if (! array_key_exists($fingerprint, $this->entries)) { return 0; } unset($this->entries[$fingerprint]); $this->invalidations[] = $fingerprint; return 1; } public function invalidateFamily( DerivedStateFamily $family, ?string $recordClass = null, string|int|null $recordKey = null, ?string $variant = null, ?int $workspaceId = null, ?int $tenantId = null, ): int { $invalidated = 0; foreach ($this->entries as $fingerprint => $record) { if (! $record['key']->matches($family, $recordClass, $recordKey, $variant, $workspaceId, $tenantId)) { continue; } unset($this->entries[$fingerprint]); $this->invalidations[] = $fingerprint; $invalidated++; } return $invalidated; } public function invalidateModel(DerivedStateFamily $family, Model $record, ?string $variant = null): int { return $this->invalidateFamily( family: $family, recordClass: $record::class, recordKey: $record->getKey(), variant: $variant, ); } public function entryCount(): int { return count($this->entries); } public function countStored( DerivedStateFamily $family, ?string $recordClass = null, string|int|null $recordKey = null, ?string $variant = null, ): int { return count(array_filter( $this->entries, static fn (array $record): bool => $record['key']->matches($family, $recordClass, $recordKey, $variant), )); } /** * @return array{ * key: DerivedStateKey, * value: mixed, * negative_result: bool, * freshness_policy: string, * resolved_at: int * }|null */ public function resolutionRecord(DerivedStateKey $key): ?array { return $this->entries[$key->fingerprint()] ?? null; } /** * @return list */ public function invalidations(): array { return $this->invalidations; } private function isNegativeResult(mixed $value): bool { return $value === null || $value === []; } }