enabled || !$this->isCacheable($context)) { return $next($context); } $cacheKey = $this->generateCacheKey($context); // Versuche aus Cache zu lesen $cachedResult = $this->cacheStrategy->get($cacheKey); if ($cachedResult !== null) { // Cache Hit - speichere Statistik $context = $context->withMetadata('cache_hit', true); $context = $context->withMetadata('cache_key', $cacheKey->toString()); return $this->deserializeResult($cachedResult, $context->operation); } // Cache Miss - führe Query aus $result = $next($context); // Speichere Ergebnis im Cache $ttl = $this->determineTtl($context); $serializedResult = $this->serializeResult($result, $context->operation); $this->cacheStrategy->set($cacheKey, $serializedResult, $ttl); // Speichere Cache-Metadaten $context = $context->withMetadata('cache_hit', false); $context = $context->withMetadata('cache_key', $cacheKey->toString()); $context = $context->withMetadata('cache_ttl', $ttl); return $result; } public function getPriority(): int { return 30; // Niedrige Priorität - nach HealthCheck und Retry } private function isCacheable(QueryContext $context): bool { // Nur bestimmte Operationen sind cacheable if (!in_array($context->operation, $this->cacheableOperations, true)) { return false; } // Transaktions-Queries nicht cachen if ($context->connection->inTransaction()) { return false; } // Prüfe auf non-cacheable SQL-Patterns $sql = strtoupper(trim($context->sql)); // SELECT-Statements sind normalerweise cacheable if (!str_starts_with($sql, 'SELECT')) { return false; } // Bestimmte SELECT-Patterns nicht cachen $nonCacheablePatterns = [ 'NOW()', 'CURRENT_TIMESTAMP', 'RAND()', 'RANDOM()', 'UUID()', 'CURRENT_USER', 'CONNECTION_ID()', ]; foreach ($nonCacheablePatterns as $pattern) { if (str_contains($sql, $pattern)) { return false; } } return true; } private function generateCacheKey(QueryContext $context): QueryCacheKey { return new QueryCacheKey( $context->sql, $context->parameters, $context->connection ); } private function determineTtl(QueryContext $context): int { // Prüfe auf custom TTL in Metadaten if ($context->hasMetadata('cache_ttl')) { return (int) $context->getMetadata('cache_ttl'); } // Intelligente TTL basierend auf Query-Pattern $sql = strtoupper(trim($context->sql)); // Lange TTL für statische/referenz Daten if (str_contains($sql, 'INFORMATION_SCHEMA') || str_contains($sql, 'SHOW TABLES') || str_contains($sql, 'DESCRIBE ')) { return 3600; // 1 Stunde } // Mittlere TTL für Aggregationen if (str_contains($sql, 'COUNT(') || str_contains($sql, 'SUM(') || str_contains($sql, 'AVG(') || str_contains($sql, 'GROUP BY')) { return 900; // 15 Minuten } // Standard TTL return $this->defaultTtlSeconds; } private function serializeResult(mixed $result, string $operation): array { return [ 'operation' => $operation, 'data' => $result, 'timestamp' => time(), 'serialized_at' => microtime(true), ]; } private function deserializeResult(array $cachedData, string $operation): mixed { // Validiere dass Operation übereinstimmt if ($cachedData['operation'] !== $operation) { throw new DatabaseException( "Cache operation mismatch: expected {$operation}, got {$cachedData['operation']}" ); } return $cachedData['data']; } public function invalidatePattern(string $pattern): int { return $this->cacheStrategy->invalidatePattern($pattern); } public function invalidateAll(): void { $this->cacheStrategy->clear(); } public function getCacheStats(): array { return $this->cacheStrategy->getStats(); } }