ttlSeconds = max(60, $ttlSeconds); } public function createCacheKey(SearchRequest $request): string { $preferredProviders = implode(',', $request->getPreferredProviders()); $diagnosticVersion = $request->getMediaType() === 'medicine' ? 'aifa_diag_0161|' : ''; $identityPolicyVersion = $request->getMediaType() === 'medicine' ? '' : 'identity_media_conflict_v1|'; $completionVersion = $request->isIdentifierSearch() && $request->getMediaType() !== 'medicine' ? 'identifier_source_recovery_v8|' : ''; return sha1($completionVersion . $identityPolicyVersion . $diagnosticVersion . sprintf( '%s|%s|%s|%s|%s|%s|%s|%d|%d', $this->normalizeQuery($request), $request->getSearchType() ?? '', $request->getSearchIntent() ?? '', $request->getMediaType(), $this->normalizeBrand($request->getBrand()), $preferredProviders, $request->getExecutionMode(), $request->getLimit(), $request->getOffset() )); } public function has(string $key): bool { if (isset($this->storage[$key])) { return true; } if (! function_exists('get_transient')) { return false; } $cached = get_transient(self::TRANSIENT_PREFIX . $key); if (! $cached instanceof SearchResult) { return false; } $this->storage[$key] = $cached; return true; } public function get(string $key): SearchResult { $result = $this->storage[$key]; $trace = $result->getProviderTrace(); foreach ($trace as &$attempt) { if (! is_array($attempt)) { continue; } $attempt['cached_snapshot'] = true; $attempt['reason'] = '[historical provider snapshot] ' . (string) ($attempt['reason'] ?? ''); $attempt['historical_response_time_ms'] = (float) ($attempt['response_time_ms'] ?? 0.0); $attempt['response_time_ms'] = 0.0; } unset($attempt); $trace[] = [ 'provider' => 'SearchCache', 'status' => ProviderStatus::CACHED_POSITIVE, 'result_count' => count($result->getItems()), 'ttl_seconds' => $this->ttlSeconds, 'cached_provider_attempts' => $result->getProviderAttempts(), 'cached_provider_response_time_ms' => $result->getProviderResponseTimeMs(), 'response_time_ms' => 0.0, ]; return new SearchResult( $result->getItems(), $result->getTotal(), $result->getProviderUsed(), $result->getProviderFallbackFrom(), 0, 0.0, $trace, $result->getAppliedOffset(), $result->getRemoteTotal(), $result->getKnownQualifiedTotal(), $result->hasRemoteContinuation(), $result->getProviderContinuations() ); } public function store(string $key, SearchResult $result): void { $this->storage[$key] = $result; if (function_exists('set_transient')) { set_transient(self::TRANSIENT_PREFIX . $key, $result, $this->ttlSeconds); } } private function normalizeQuery(SearchRequest $request): string { $query = trim($request->getQuery()); if ($request->isIdentifierSearch()) { return strtoupper((string) preg_replace('/[^A-Z0-9]/i', '', $query)); } $query = function_exists('mb_strtolower') ? mb_strtolower($query, 'UTF-8') : strtolower($query); return trim((string) preg_replace('/\s+/u', ' ', $query)); } private function normalizeBrand(string $brand): string { $brand = function_exists('mb_strtolower') ? mb_strtolower(trim($brand), 'UTF-8') : strtolower(trim($brand)); return trim((string) preg_replace('/\s+/u', ' ', $brand)); } }