'post', 'context_id' => 123]); * $settings = Query_Cache::get_or_execute($cache_key, function() use ($wpdb) { * return $wpdb->get_results("SELECT * FROM wp_thinkrank_seo_settings WHERE context_type = 'post'"); * }, 600); // Cache for 10 minutes * ``` * * @package ThinkRank * @subpackage Core * @since 1.0.0 */ declare(strict_types=1); namespace ThinkRank\Core; /** * Query Cache Class * * @since 1.0.0 */ class Query_Cache { /** * Memory cache for current request * * @since 1.0.0 * @var array */ private static array $memory_cache = []; /** * Cache group for WordPress object cache * * @since 1.0.0 * @var string */ private const CACHE_GROUP = 'thinkrank_queries'; /** * Default cache duration in seconds * * @since 1.0.0 * @var int */ private const DEFAULT_DURATION = 300; // 5 minutes /** * Option name for cache group version * * @since 1.0.0 * @var string */ private const VERSION_OPTION = 'thinkrank_query_cache_version'; /** * Get current cache group version * * @since 1.0.0 * @return int Version number >= 1 */ private static function get_group_version(): int { $version = (int) get_option(self::VERSION_OPTION, 1); return $version > 0 ? $version : 1; } /** * Bump cache group version to invalidate all keys * * @since 1.0.0 * @return bool Always true */ private static function bump_group_version(): bool { $new = self::get_group_version() + 1; // Ensure option exists and does not autoload $existing = get_option(self::VERSION_OPTION, null); if ($existing === null) { add_option(self::VERSION_OPTION, $new, '', 'no'); } else { update_option(self::VERSION_OPTION, $new, false); } return true; } /** * Apply current group version to a cache key * * @since 1.0.0 * @param string $key Base key * @return string Versioned key */ private static function apply_version(string $key): string { $v = self::get_group_version(); return 'v' . $v . ':' . $key; } /** * Get cached result or execute callback * * @since 1.0.0 * * @param string $key Cache key * @param callable $callback Function to execute if cache miss * @param int $duration Cache duration in seconds * @return mixed Query result */ public static function get_or_execute(string $key, callable $callback, int $duration = self::DEFAULT_DURATION) { $versioned_key = self::apply_version($key); // Check memory cache first (fastest) if (isset(self::$memory_cache[$versioned_key])) { return self::$memory_cache[$versioned_key]; } // Check persistent cache $cached = wp_cache_get($versioned_key, self::CACHE_GROUP); if ($cached !== false) { // Store in memory cache for this request self::$memory_cache[$versioned_key] = $cached; return $cached; } // Execute callback and cache result $result = call_user_func($callback); // Store in both caches self::$memory_cache[$versioned_key] = $result; wp_cache_set($versioned_key, $result, self::CACHE_GROUP, $duration); return $result; } /** * Generate cache key from query and parameters * * @since 1.0.0 * * @param string $query_type Query type identifier * @param array $params Query parameters * @return string Cache key */ public static function generate_key(string $query_type, array $params = []): string { // Use JSON encoding for stable cross-runtime key generation return 'query_' . $query_type . '_' . md5(wp_json_encode($params)); } /** * Clear cache for specific key or pattern * * @since 1.0.0 * * @param string $key_or_pattern Cache key or pattern * @return bool Success */ public static function clear(string $key_or_pattern): bool { $versioned_key = self::apply_version($key_or_pattern); // Clear from memory cache if (isset(self::$memory_cache[$versioned_key])) { unset(self::$memory_cache[$versioned_key]); } // Clear from persistent cache return wp_cache_delete($versioned_key, self::CACHE_GROUP); } /** * Clear all query cache * * @since 1.0.0 * * @return bool Success */ public static function clear_all(): bool { // Clear memory cache self::$memory_cache = []; // Bump version to invalidate all keys logically return self::bump_group_version(); } /** * Get cache statistics * * @since 1.0.0 * * @return array Cache statistics */ public static function get_stats(): array { return [ 'memory_cache_entries' => count(self::$memory_cache), 'memory_usage_bytes' => strlen(serialize(self::$memory_cache)), 'cache_group' => self::CACHE_GROUP, 'group_version' => self::get_group_version(), 'default_duration' => self::DEFAULT_DURATION ]; } }