| 1 |
<?php |
| 2 |
|
| 3 |
declare(strict_types=1); |
| 4 |
|
| 5 |
namespace Yatra\Services; |
| 6 |
|
| 7 |
use Yatra\Utils\Cache; |
| 8 |
use Yatra\Utils\Logger; |
| 9 |
|
| 10 |
/** |
| 11 |
* Cache Service |
| 12 |
* |
| 13 |
* High-level caching operations for business entities |
| 14 |
*/ |
| 15 |
class CacheService |
| 16 |
{ |
| 17 |
/** |
| 18 |
* Cache trip with relationships |
| 19 |
*/ |
| 20 |
public static function cacheTrip(int $tripId, \stdClass $tripData): void |
| 21 |
{ |
| 22 |
Cache::set( |
| 23 |
Cache::PREFIX_TRIP_DATA . $tripId, |
| 24 |
$tripData, |
| 25 |
Cache::DURATION_TRIP_DATA |
| 26 |
); |
| 27 |
|
| 28 |
Logger::debug("Trip cached", ['trip_id' => $tripId]); |
| 29 |
} |
| 30 |
|
| 31 |
/** |
| 32 |
* Get cached trip |
| 33 |
*/ |
| 34 |
public static function getCachedTrip(int $tripId): ?\stdClass |
| 35 |
{ |
| 36 |
return Cache::get(Cache::PREFIX_TRIP_DATA . $tripId); |
| 37 |
} |
| 38 |
|
| 39 |
/** |
| 40 |
* Cache booking data |
| 41 |
*/ |
| 42 |
public static function cacheBooking(int $bookingId, \stdClass $bookingData): void |
| 43 |
{ |
| 44 |
Cache::set( |
| 45 |
Cache::PREFIX_BOOKING_DATA . $bookingId, |
| 46 |
$bookingData, |
| 47 |
Cache::DURATION_BOOKING_DATA |
| 48 |
); |
| 49 |
|
| 50 |
Logger::debug("Booking cached", ['booking_id' => $bookingId]); |
| 51 |
} |
| 52 |
|
| 53 |
/** |
| 54 |
* Get cached booking |
| 55 |
*/ |
| 56 |
public static function getCachedBooking(int $bookingId): ?\stdClass |
| 57 |
{ |
| 58 |
return Cache::get(Cache::PREFIX_BOOKING_DATA . $bookingId); |
| 59 |
} |
| 60 |
|
| 61 |
/** |
| 62 |
* Cache customer data |
| 63 |
*/ |
| 64 |
public static function cacheCustomer(int $customerId, \stdClass $customerData): void |
| 65 |
{ |
| 66 |
Cache::set( |
| 67 |
Cache::PREFIX_CUSTOMER_DATA . $customerId, |
| 68 |
$customerData, |
| 69 |
Cache::DURATION_CUSTOMER_DATA |
| 70 |
); |
| 71 |
|
| 72 |
Logger::debug("Customer cached", ['customer_id' => $customerId]); |
| 73 |
} |
| 74 |
|
| 75 |
/** |
| 76 |
* Get cached customer |
| 77 |
*/ |
| 78 |
public static function getCachedCustomer(int $customerId): ?\stdClass |
| 79 |
{ |
| 80 |
return Cache::get(Cache::PREFIX_CUSTOMER_DATA . $customerId); |
| 81 |
} |
| 82 |
|
| 83 |
/** |
| 84 |
* Cache activity data |
| 85 |
*/ |
| 86 |
public static function cacheActivity(int $activityId, \stdClass $activityData): void |
| 87 |
{ |
| 88 |
Cache::set( |
| 89 |
Cache::PREFIX_ACTIVITY_DATA . $activityId, |
| 90 |
$activityData, |
| 91 |
Cache::DURATION_ACTIVITY_DATA |
| 92 |
); |
| 93 |
|
| 94 |
Logger::debug("Activity cached", ['activity_id' => $activityId]); |
| 95 |
} |
| 96 |
|
| 97 |
/** |
| 98 |
* Get cached activity |
| 99 |
*/ |
| 100 |
public static function getCachedActivity(int $activityId): ?\stdClass |
| 101 |
{ |
| 102 |
return Cache::get(Cache::PREFIX_ACTIVITY_DATA . $activityId); |
| 103 |
} |
| 104 |
|
| 105 |
/** |
| 106 |
* Cache destination data |
| 107 |
*/ |
| 108 |
public static function cacheDestination(int $destinationId, \stdClass $destinationData): void |
| 109 |
{ |
| 110 |
Cache::set( |
| 111 |
Cache::PREFIX_DESTINATION_DATA . $destinationId, |
| 112 |
$destinationData, |
| 113 |
Cache::DURATION_DESTINATION_DATA |
| 114 |
); |
| 115 |
|
| 116 |
Logger::debug("Destination cached", ['destination_id' => $destinationId]); |
| 117 |
} |
| 118 |
|
| 119 |
/** |
| 120 |
* Get cached destination |
| 121 |
*/ |
| 122 |
public static function getCachedDestination(int $destinationId): ?\stdClass |
| 123 |
{ |
| 124 |
return Cache::get(Cache::PREFIX_DESTINATION_DATA . $destinationId); |
| 125 |
} |
| 126 |
|
| 127 |
/** |
| 128 |
* Clear a single trip and listing/query caches (e.g. after attribute changes via {@see TripService}). |
| 129 |
*/ |
| 130 |
public static function clearTripCache(int $tripId): void |
| 131 |
{ |
| 132 |
if ($tripId <= 0) { |
| 133 |
return; |
| 134 |
} |
| 135 |
Cache::invalidateAfterTripWrite('update', $tripId); |
| 136 |
} |
| 137 |
|
| 138 |
/** |
| 139 |
* Invalidate entity cache when data changes |
| 140 |
*/ |
| 141 |
public static function invalidateEntity(string $entityType, int $entityId): void |
| 142 |
{ |
| 143 |
// Handle both entity types and service class names |
| 144 |
$normalizedType = self::normalizeEntityType($entityType); |
| 145 |
|
| 146 |
switch ($normalizedType) { |
| 147 |
case 'trip': |
| 148 |
$prefix = Cache::PREFIX_TRIP_DATA; |
| 149 |
break; |
| 150 |
case 'booking': |
| 151 |
$prefix = Cache::PREFIX_BOOKING_DATA; |
| 152 |
break; |
| 153 |
case 'customer': |
| 154 |
$prefix = Cache::PREFIX_CUSTOMER_DATA; |
| 155 |
break; |
| 156 |
case 'activity': |
| 157 |
$prefix = Cache::PREFIX_ACTIVITY_DATA; |
| 158 |
break; |
| 159 |
case 'destination': |
| 160 |
$prefix = Cache::PREFIX_DESTINATION_DATA; |
| 161 |
break; |
| 162 |
case 'difficulty': |
| 163 |
$prefix = Cache::PREFIX_QUERY_RESULT; // For difficulty levels, use query cache |
| 164 |
break; |
| 165 |
default: |
| 166 |
$prefix = null; |
| 167 |
break; |
| 168 |
} |
| 169 |
|
| 170 |
if ($prefix) { |
| 171 |
if ($normalizedType === 'difficulty') { |
| 172 |
// For difficulty and other non-entity types, clear by prefix |
| 173 |
Cache::clearByPrefix($prefix); |
| 174 |
} else { |
| 175 |
// For entities, clear specific entity cache |
| 176 |
Cache::delete($prefix . $entityId); |
| 177 |
} |
| 178 |
|
| 179 |
// Also clear related query caches |
| 180 |
Cache::clearByPrefix(Cache::PREFIX_QUERY_RESULT); |
| 181 |
Cache::clearByPrefix(Cache::PREFIX_STATS); |
| 182 |
|
| 183 |
Logger::info("Entity cache invalidated", [ |
| 184 |
'entity_type' => $entityType, |
| 185 |
'normalized_type' => $normalizedType, |
| 186 |
'entity_id' => $entityId |
| 187 |
]); |
| 188 |
} else { |
| 189 |
// If no specific prefix found, just clear query caches |
| 190 |
Cache::clearByPrefix(Cache::PREFIX_QUERY_RESULT); |
| 191 |
Cache::clearByPrefix(Cache::PREFIX_STATS); |
| 192 |
|
| 193 |
Logger::debug("Generic cache invalidation", [ |
| 194 |
'entity_type' => $entityType, |
| 195 |
'entity_id' => $entityId |
| 196 |
]); |
| 197 |
} |
| 198 |
} |
| 199 |
|
| 200 |
/** |
| 201 |
* Normalize entity type from service class names |
| 202 |
*/ |
| 203 |
private static function normalizeEntityType(string $entityType): string |
| 204 |
{ |
| 205 |
// Convert service class names to entity types |
| 206 |
$entityType = strtolower($entityType); |
| 207 |
|
| 208 |
// Handle service class patterns |
| 209 |
if (str_contains($entityType, 'trip')) { |
| 210 |
return 'trip'; |
| 211 |
} |
| 212 |
if (str_contains($entityType, 'booking')) { |
| 213 |
return 'booking'; |
| 214 |
} |
| 215 |
if (str_contains($entityType, 'customer')) { |
| 216 |
return 'customer'; |
| 217 |
} |
| 218 |
if (str_contains($entityType, 'activity')) { |
| 219 |
return 'activity'; |
| 220 |
} |
| 221 |
if (str_contains($entityType, 'destination')) { |
| 222 |
return 'destination'; |
| 223 |
} |
| 224 |
if (str_contains($entityType, 'difficulty')) { |
| 225 |
return 'difficulty'; |
| 226 |
} |
| 227 |
|
| 228 |
// Return as-is if no pattern matches |
| 229 |
return $entityType; |
| 230 |
} |
| 231 |
|
| 232 |
/** |
| 233 |
* Warm up cache for frequently accessed entities |
| 234 |
*/ |
| 235 |
public static function warmUpCache(): void |
| 236 |
{ |
| 237 |
Logger::info("Starting cache warm-up"); |
| 238 |
|
| 239 |
// Warm up popular trips |
| 240 |
$tripRepository = new \Yatra\Repositories\TripRepository(); |
| 241 |
$popularTrips = $tripRepository->getPopularTrips(20); |
| 242 |
|
| 243 |
foreach ($popularTrips as $trip) { |
| 244 |
// This would trigger cache population when the trip is next accessed |
| 245 |
Logger::debug("Marked trip for cache warm-up", ['trip_id' => $trip->id]); |
| 246 |
} |
| 247 |
|
| 248 |
// Warm up recent bookings |
| 249 |
$bookingRepository = new \Yatra\Repositories\BookingRepository(); |
| 250 |
$recentBookings = $bookingRepository->getRecentBookings(7, 50); |
| 251 |
|
| 252 |
foreach ($recentBookings as $booking) { |
| 253 |
Logger::debug("Marked booking for cache warm-up", ['booking_id' => $booking->id]); |
| 254 |
} |
| 255 |
|
| 256 |
Logger::info("Cache warm-up completed", [ |
| 257 |
'trips_marked' => count($popularTrips), |
| 258 |
'bookings_marked' => count($recentBookings) |
| 259 |
]); |
| 260 |
} |
| 261 |
|
| 262 |
/** |
| 263 |
* Get cache performance metrics |
| 264 |
*/ |
| 265 |
public static function getPerformanceMetrics(): array |
| 266 |
{ |
| 267 |
return [ |
| 268 |
'cache_stats' => Cache::getCacheStats(), |
| 269 |
'memory_usage' => [ |
| 270 |
'current' => memory_get_usage(true), |
| 271 |
'peak' => memory_get_peak_usage(true), |
| 272 |
'formatted_current' => self::formatBytes(memory_get_usage(true)), |
| 273 |
'formatted_peak' => self::formatBytes(memory_get_peak_usage(true)), |
| 274 |
], |
| 275 |
'cache_backends' => Cache::getAvailableBackends(), |
| 276 |
]; |
| 277 |
} |
| 278 |
|
| 279 |
/** |
| 280 |
* Format bytes for human reading |
| 281 |
*/ |
| 282 |
private static function formatBytes(int $bytes): string |
| 283 |
{ |
| 284 |
$units = ['B', 'KB', 'MB', 'GB']; |
| 285 |
$bytes = max($bytes, 0); |
| 286 |
$pow = floor(($bytes ? log($bytes) : 0) / log(1024)); |
| 287 |
$pow = min($pow, count($units) - 1); |
| 288 |
|
| 289 |
$bytes /= (1 << (10 * $pow)); |
| 290 |
|
| 291 |
return round($bytes, 2) . ' ' . $units[$pow]; |
| 292 |
} |
| 293 |
|
| 294 |
/** |
| 295 |
* Remember pattern - get from cache or execute callback and cache result |
| 296 |
* |
| 297 |
* @return mixed |
| 298 |
*/ |
| 299 |
public static function remember(string $key, callable $callback, ?int $duration = null) |
| 300 |
{ |
| 301 |
// Try to get from cache first |
| 302 |
$cached = Cache::get($key); |
| 303 |
if ($cached !== null) { |
| 304 |
Logger::debug("Cache hit", ['key' => $key]); |
| 305 |
return $cached; |
| 306 |
} |
| 307 |
|
| 308 |
// Execute callback to get fresh data |
| 309 |
$startTime = microtime(true); |
| 310 |
$result = $callback(); |
| 311 |
$executionTime = microtime(true) - $startTime; |
| 312 |
|
| 313 |
// Cache the result if it's not null |
| 314 |
if ($result !== null) { |
| 315 |
$cacheDuration = $duration ?? Cache::DURATION_QUERY_RESULT; |
| 316 |
Cache::set($key, $result, $cacheDuration); |
| 317 |
|
| 318 |
Logger::debug("Cache miss - data cached", [ |
| 319 |
'key' => $key, |
| 320 |
'execution_time' => round($executionTime * 1000, 2) . 'ms', |
| 321 |
'duration' => $cacheDuration |
| 322 |
]); |
| 323 |
} else { |
| 324 |
Logger::debug("Cache miss - null result not cached", [ |
| 325 |
'key' => $key, |
| 326 |
'execution_time' => round($executionTime * 1000, 2) . 'ms' |
| 327 |
]); |
| 328 |
} |
| 329 |
|
| 330 |
return $result; |
| 331 |
} |
| 332 |
|
| 333 |
/** |
| 334 |
* Clear cache by prefix - delegates to Cache utility |
| 335 |
*/ |
| 336 |
public static function clearByPrefix(string $prefix): void |
| 337 |
{ |
| 338 |
Cache::clearByPrefix($prefix); |
| 339 |
Logger::debug("Cache cleared by prefix", ['prefix' => $prefix]); |
| 340 |
} |
| 341 |
|
| 342 |
/** |
| 343 |
* Clear all entity caches |
| 344 |
*/ |
| 345 |
public static function clearAllEntityCaches(): void |
| 346 |
{ |
| 347 |
$prefixes = [ |
| 348 |
Cache::PREFIX_TRIP_DATA, |
| 349 |
Cache::PREFIX_BOOKING_DATA, |
| 350 |
Cache::PREFIX_CUSTOMER_DATA, |
| 351 |
Cache::PREFIX_ACTIVITY_DATA, |
| 352 |
Cache::PREFIX_DESTINATION_DATA, |
| 353 |
]; |
| 354 |
|
| 355 |
foreach ($prefixes as $prefix) { |
| 356 |
Cache::clearByPrefix($prefix); |
| 357 |
} |
| 358 |
|
| 359 |
Logger::info("All entity caches cleared"); |
| 360 |
} |
| 361 |
} |
| 362 |
|