$optOn, 'debug_mode' => $wpDebug, 'yatra_debug_mode' => $pluginDebug, 'effective_status' => $effective, 'wp_debug' => defined('WP_DEBUG') ? WP_DEBUG : false, 'option_value' => $optOn, 'reason_disabled' => $reason, ]; } /** * Enable cache for testing (temporary) */ public static function enableForTesting(): bool { update_option('yatra_cache_enabled', true); Logger::info('Cache enabled for testing', ['status' => true]); return true; } /** * Disable cache for testing (temporary) */ public static function disableForTesting(): bool { update_option('yatra_cache_enabled', false); Logger::info('Cache disabled for testing', ['status' => false]); return true; } /** * Get cached value * * @return mixed */ public static function get(string $key) { // Check if caching is enabled if (!self::isEnabled()) { Logger::debug("Cache disabled, skipping cache lookup", ['key' => $key]); return null; } // Try memory cache first (fastest) if (isset(self::$memoryCache[$key])) { Logger::debug("Cache hit (memory)", ['key' => $key]); return self::$memoryCache[$key]; } // Try external cache backends foreach (self::getAvailableBackends() as $backend) { $value = self::getFromBackend($backend, $key); if ($value !== null) { // Store in memory cache for subsequent requests self::$memoryCache[$key] = $value; Logger::debug("Cache hit ({$backend})", ['key' => $key]); return $value; } } Logger::debug("Cache miss", ['key' => $key]); return null; } /** * Set cached value * * @param mixed $value */ public static function set(string $key, $value, int $duration = 3600): bool { // Check if caching is enabled if (!self::isEnabled()) { Logger::debug("Cache disabled, skipping cache set", ['key' => $key]); return false; } // Always store in memory cache self::$memoryCache[$key] = $value; // Store in external cache backends $success = false; foreach (self::getAvailableBackends() as $backend) { if (self::setToBackend($backend, $key, $value, $duration)) { $success = true; Logger::debug("Cache set ({$backend})", ['key' => $key, 'duration' => $duration]); } } return $success; } /** * Delete cached value */ public static function delete(string $key): bool { // Check if caching is enabled if (!self::isEnabled()) { Logger::debug("Cache disabled, skipping cache delete", ['key' => $key]); return false; } // Remove from memory cache unset(self::$memoryCache[$key]); // Remove from external cache backends $success = false; foreach (self::getAvailableBackends() as $backend) { if (self::deleteFromBackend($backend, $key)) { $success = true; Logger::debug("Cache delete ({$backend})", ['key' => $key]); } } return $success; } /** * Clear cache by prefix */ public static function clearByPrefix(string $prefix): bool { // Check if caching is enabled (but allow clearing even when disabled) if (!self::isEnabled()) { Logger::debug("Cache disabled, but clearing cache entries", ['prefix' => $prefix]); } // Clear memory cache foreach (self::$memoryCache as $key => $value) { if (strpos($key, $prefix) === 0) { unset(self::$memoryCache[$key]); } } // Clear external cache backends $success = false; foreach (self::getAvailableBackends() as $backend) { if (self::clearPrefixFromBackend($backend, $prefix)) { $success = true; Logger::debug("Cache clear prefix ({$backend})", ['prefix' => $prefix]); } } return $success; } /** * Get or set cached value (cache-aside pattern) * * @return mixed */ public static function remember(string $key, callable $callback, int $duration = 3600) { // Check if caching is enabled if (!self::isEnabled()) { Logger::debug("Cache disabled, executing callback directly", ['key' => $key]); return $callback(); } $value = self::get($key); if ($value !== null) { return $value; } // Generate value and cache it $value = $callback(); self::set($key, $value, $duration); Logger::debug("Cache remember", ['key' => $key, 'duration' => $duration]); return $value; } /** * Cache table existence check */ public static function tableExists(string $tableName, callable $checkCallback): bool { $key = self::PREFIX_TABLE_EXISTS . $tableName; return (bool) self::remember($key, $checkCallback, self::DURATION_TABLE_EXISTS); } /** * Cache trip data * * @return mixed */ public static function getTripData(int $tripId, callable $fetchCallback) { $key = self::PREFIX_TRIP_DATA . $tripId; return self::remember($key, $fetchCallback, self::DURATION_TRIP_DATA); } /** * Cache booking data * * @return mixed */ public static function getBookingData(int $bookingId, callable $fetchCallback) { $key = self::PREFIX_BOOKING_DATA . $bookingId; return self::remember($key, $fetchCallback, self::DURATION_BOOKING_DATA); } /** * Cache query results * * @return mixed */ public static function getQueryResult(string $queryHash, callable $queryCallback) { $key = self::PREFIX_QUERY_RESULT . $queryHash; return self::remember($key, $queryCallback, self::DURATION_QUERY_RESULT); } /** * Cache statistics * * @return mixed */ public static function getStats(string $statsKey, callable $calculateCallback) { $key = self::PREFIX_STATS . $statsKey; return self::remember($key, $calculateCallback, self::DURATION_STATS); } /** * Invalidate related caches when data changes */ public static function invalidateTrip(int $tripId): void { self::delete(self::PREFIX_TRIP_DATA . $tripId); self::clearByPrefix(self::PREFIX_QUERY_RESULT); self::clearByPrefix(self::PREFIX_STATS); Logger::info("Cache invalidated for trip", ['trip_id' => $tripId]); } /** * Invalidate booking-related caches */ public static function invalidateBooking(int $bookingId): void { self::delete(self::PREFIX_BOOKING_DATA . $bookingId); self::clearByPrefix(self::PREFIX_STATS); Logger::info("Cache invalidated for booking", ['booking_id' => $bookingId]); } /** * Trip / activity / destination listing caches (admin + frontend grids). * Kept in sync with {@see \Yatra\Hooks\CacheHooks} expectations. */ public static function invalidateListingCaches(): void { self::clearByPrefix(self::PREFIX_QUERY_RESULT); self::clearByPrefix('trip_listing_'); self::clearByPrefix('activity_listing_'); self::clearByPrefix('destination_listing_'); } /** * Dashboard and report aggregate keys (beyond {@see PREFIX_STATS}). */ public static function invalidateDashboardReportCaches(): void { self::clearByPrefix('dashboard_stats_'); self::clearByPrefix('report_stats_'); } /** * After a trip row write from {@see \Yatra\Repositories\TripRepository} (create / update / delete). * Matches {@see CacheHooks} trip handlers so hooks and repository stay aligned. */ public static function invalidateAfterTripWrite(string $operation, int $tripId): void { $operation = strtolower($operation); if ($operation === 'create') { // Listings + stats (matches legacy TripService::create follow-up clears). self::invalidateListingCaches(); self::clearByPrefix(self::PREFIX_STATS); return; } if ($tripId <= 0) { return; } self::invalidateTrip($tripId); self::invalidateListingCaches(); } /** * After bulk UPDATEs on the trips table (cron: scheduled publish/archive, seasonal) that bypass * per-row {@see \Yatra\Repositories\TripRepository::afterWrite}. */ public static function invalidateAfterBulkTripTableWrites(): void { self::invalidateListingCaches(); self::clearByPrefix(self::PREFIX_STATS); self::invalidateDashboardReportCaches(); self::clearByPrefix(self::PREFIX_TRIP_DATA); } /** * After booking mutations from {@see \Yatra\Repositories\BookingRepository}. * Aligns with {@see CacheHooks::onBookingUpdated} and related handlers. */ public static function invalidateAfterBookingWrite(int $bookingId): void { self::invalidateBooking($bookingId); self::invalidateDashboardReportCaches(); } /** * Pro Dynamic Pricing rule list/detail caches (see {@see KEY_PRO_PRICING_RULES} / {@see KEY_PRO_PRICING_RULE}). */ public static function invalidateProDynamicPricingCaches(?int $ruleId = null): void { self::clearByPrefix(self::KEY_PRO_PRICING_RULES); self::clearByPrefix(self::KEY_PRO_ACTIVE_RULES); if ($ruleId !== null && $ruleId > 0) { self::delete(self::KEY_PRO_PRICING_RULE . '_' . $ruleId); } else { self::clearByPrefix(self::KEY_PRO_PRICING_RULE); } } /** * Get available cache backends */ public static function getAvailableBackends(): array { if (self::$availableBackends === null) { self::$availableBackends = []; foreach (self::$backends as $backend) { if (self::isBackendAvailable($backend)) { self::$availableBackends[] = $backend; } } // Always have memory as fallback if (!in_array('memory', self::$availableBackends)) { self::$availableBackends[] = 'memory'; } } return self::$availableBackends; } /** * Check if cache backend is available */ private static function isBackendAvailable(string $backend): bool { switch ($backend) { case 'transient': return function_exists('get_transient'); case 'memory': return true; default: return false; } } /** * Get value from specific backend * * @return mixed */ private static function getFromBackend(string $backend, string $key) { switch ($backend) { case 'transient': return self::getFromTransient($key); case 'memory': return self::$memoryCache[$key] ?? null; default: return null; } } /** * Set value to specific backend * * @param mixed $value */ private static function setToBackend(string $backend, string $key, $value, int $duration): bool { switch ($backend) { case 'transient': return self::setToTransient($key, $value, $duration); case 'memory': return true; // Already handled above default: return false; } } /** * Delete from specific backend */ private static function deleteFromBackend(string $backend, string $key): bool { switch ($backend) { case 'transient': return self::deleteFromTransient($key); case 'memory': return true; // Already handled above default: return false; } } /** * Clear prefix from specific backend */ private static function clearPrefixFromBackend(string $backend, string $prefix): bool { switch ($backend) { case 'transient': return self::clearPrefixFromTransient($prefix); case 'memory': return true; // Already handled above default: return false; } } /** * WordPress Transient backend methods * * @return mixed */ private static function getFromTransient(string $key) { if (!function_exists('get_transient')) return null; $value = get_transient($key); return $value !== false ? $value : null; } /** * @param mixed $value */ private static function setToTransient(string $key, $value, int $duration): bool { if (!function_exists('set_transient')) return false; return set_transient($key, $value, $duration); } private static function deleteFromTransient(string $key): bool { if (!function_exists('delete_transient')) return false; return delete_transient($key); } private static function clearPrefixFromTransient(string $prefix): bool { global $wpdb; if (!$wpdb) return false; // Delete transient entries $wpdb->query($wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_' . $prefix . '%' )); // Delete timeout entries $wpdb->query($wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", '_transient_timeout_' . $prefix . '%' )); return true; } /** * Get cache statistics */ public static function getCacheStats(): array { $stats = [ 'memory_cache_size' => count(self::$memoryCache), 'memory_usage' => 0, 'available_backends' => self::getAvailableBackends() ]; // Calculate memory usage safely if (function_exists('memory_get_usage')) { $stats['memory_usage'] = memory_get_usage(true); } return $stats; } /** * Clear all caches */ public static function flush(): void { self::$memoryCache = []; foreach (self::getAvailableBackends() as $backend) { self::clearPrefixFromBackend($backend, 'yatra_'); } Logger::info("All caches flushed"); } }