PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.7
Yatra – Travel Booking & Tour Operator Software v3.0.2.7
3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 All 83 releases
yatra / app / Services / BaseService.php

BaseService.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.7, at app/Services/BaseService.php

703 lines 21.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Services;
6
7 use Yatra\Utils\Logger;
8 use Yatra\Services\CacheService;
9 use Yatra\Contracts\ServiceInterface;
10 use Yatra\Utils\Cache;
11
12 /**
13 * Enhanced Base Service Class
14 *
15 * Provides enterprise-grade service layer with:
16 * - Integrated caching
17 * - Comprehensive logging
18 * - Transaction support
19 * - Event hooks
20 * - Performance monitoring
21 */
22 abstract class BaseService implements ServiceInterface
23 {
24 /**
25 * Get repository instance
26 */
27 abstract protected function getRepository();
28
29 /**
30 * Get all items with caching
31 */
32 public function getAll(array $filters = []): array
33 {
34 $startTime = microtime(true);
35
36 // Check if caching is enabled
37 if ($this->isCacheEnabled()) {
38 $cacheKey = $this->getCacheKey('all', $filters);
39
40 $result = $this->getCachedResult($cacheKey, function() use ($filters) {
41 return $this->getRepository()->all($filters);
42 });
43 } else {
44 // Bypass cache and get fresh data
45 Logger::debug("Cache disabled, getting fresh data", ['service' => static::class, 'filters' => $filters]);
46 $result = $this->getRepository()->all($filters);
47 }
48
49 $executionTime = microtime(true) - $startTime;
50 Logger::debug("Service getAll executed", [
51 'service' => static::class,
52 'filters' => $filters,
53 'execution_time' => $executionTime,
54 'result_count' => count($result)
55 ]);
56
57 return $result;
58 }
59
60 /**
61 * Get item by ID with caching
62 */
63 public function getById(int $id): ?\stdClass
64 {
65 if ($id <= 0) {
66 Logger::warning("Invalid ID provided to getById", ['service' => static::class, 'id' => $id]);
67 return null;
68 }
69
70 $startTime = microtime(true);
71
72 // Check if caching is enabled
73 if ($this->isCacheEnabled()) {
74 $cacheKey = $this->getCacheKey('entity', ['id' => $id]);
75
76 $result = $this->getCachedResult($cacheKey, function() use ($id) {
77 return $this->getRepository()->find($id);
78 });
79 } else {
80 // Bypass cache and get fresh data
81 Logger::debug("Cache disabled, getting fresh data", ['service' => static::class, 'id' => $id]);
82 $result = $this->getRepository()->find($id);
83 }
84
85 $executionTime = microtime(true) - $startTime;
86 Logger::debug("Service getById executed", [
87 'service' => static::class,
88 'id' => $id,
89 'execution_time' => $executionTime,
90 'found' => $result !== null,
91 'cache_enabled' => $this->isCacheEnabled()
92 ]);
93
94 return $result;
95 }
96
97 /**
98 * Create new item with enhanced error handling and logging
99 */
100 public function create(array $data): int
101 {
102 $startTime = microtime(true);
103
104 try {
105 Logger::info("Service create started", [
106 'service' => static::class,
107 'data_keys' => array_keys($data)
108 ]);
109
110 // Validate data
111 $this->validateCreate($data);
112
113 // Process data before creation
114 $data = $this->processBeforeCreate($data);
115
116 // Fire before create hook
117 $this->fireHook('before_create', $data);
118
119 // Create record
120 $id = $this->getRepository()->create($data);
121
122 // Process after creation
123 $this->processAfterCreate($id, $data);
124
125 // Fire after create hook
126 $this->fireHook('after_create', $id, $data);
127
128 // Clear related caches
129 $this->invalidateRelatedCaches($id, 'create');
130
131 $executionTime = microtime(true) - $startTime;
132 Logger::info("Service create completed", [
133 'service' => static::class,
134 'id' => $id,
135 'execution_time' => $executionTime
136 ]);
137
138 return $id;
139
140 } catch (\Exception $e) {
141 $executionTime = microtime(true) - $startTime;
142 Logger::error("Service create failed", [
143 'service' => static::class,
144 'data_keys' => array_keys($data),
145 'execution_time' => $executionTime,
146 'error' => $e->getMessage()
147 ]);
148 throw $e;
149 }
150 }
151
152 /**
153 * Update item with enhanced error handling and logging
154 */
155 public function update(int $id, array $data): bool
156 {
157 $startTime = microtime(true);
158
159 try {
160 if ($id <= 0) {
161 throw new \InvalidArgumentException('Invalid ID provided for update');
162 }
163
164 Logger::info("Service update started", [
165 'service' => static::class,
166 'id' => $id,
167 'data_keys' => array_keys($data)
168 ]);
169
170 // Validate data
171 $this->validateUpdate($id, $data);
172
173 // Process data before update
174 $data = $this->processBeforeUpdate($id, $data);
175
176 // Fire before update hook
177 $this->fireHook('before_update', $id, $data);
178
179 // Update record
180 $result = $this->getRepository()->update($id, $data);
181
182 if ($result) {
183 // Process after update
184 $this->processAfterUpdate($id, $data);
185
186 // Fire after update hook
187 $this->fireHook('after_update', $id, $data);
188
189 // Clear related caches
190 $this->invalidateRelatedCaches($id, 'update');
191 }
192
193 $executionTime = microtime(true) - $startTime;
194 Logger::info("Service update completed", [
195 'service' => static::class,
196 'id' => $id,
197 'success' => $result,
198 'execution_time' => $executionTime
199 ]);
200
201 return $result;
202
203 } catch (\Exception $e) {
204 $executionTime = microtime(true) - $startTime;
205 Logger::error("Service update failed", [
206 'service' => static::class,
207 'id' => $id,
208 'data_keys' => array_keys($data),
209 'execution_time' => $executionTime,
210 'error' => $e->getMessage()
211 ]);
212 throw $e;
213 }
214 }
215
216 /**
217 * Delete item with enhanced error handling and logging
218 */
219 public function delete(int $id): bool
220 {
221 $startTime = microtime(true);
222
223 try {
224 if ($id <= 0) {
225 throw new \InvalidArgumentException('Invalid ID provided for delete');
226 }
227
228 Logger::info("Service delete started", [
229 'service' => static::class,
230 'id' => $id
231 ]);
232
233 // Process before delete
234 $this->processBeforeDelete($id);
235
236 // Fire before delete hook
237 $this->fireHook('before_delete', $id);
238
239 // Delete record
240 $result = $this->getRepository()->delete($id);
241
242 if ($result) {
243 // Process after delete
244 $this->processAfterDelete($id);
245
246 // Fire after delete hook
247 $this->fireHook('after_delete', $id);
248
249 // Clear related caches
250 $this->invalidateRelatedCaches($id, 'delete');
251 }
252
253 $executionTime = microtime(true) - $startTime;
254 Logger::info("Service delete completed", [
255 'service' => static::class,
256 'id' => $id,
257 'success' => $result,
258 'execution_time' => $executionTime
259 ]);
260
261 return $result;
262
263 } catch (\Exception $e) {
264 $executionTime = microtime(true) - $startTime;
265 Logger::error("Service delete failed", [
266 'service' => static::class,
267 'id' => $id,
268 'execution_time' => $executionTime,
269 'error' => $e->getMessage()
270 ]);
271 throw $e;
272 }
273 }
274
275 /**
276 * Validate data for creation (implement ServiceInterface)
277 */
278 public function validateCreate(array $data): void
279 {
280 // Default implementation - override in child classes
281 $this->validate($data);
282 }
283
284 /**
285 * Validate data for update (implement ServiceInterface)
286 */
287 public function validateUpdate(int $id, array $data): void
288 {
289 // Default implementation - override in child classes
290 $this->validate($data, $id);
291 }
292
293 /**
294 * Legacy validate method (override in child classes)
295 */
296 protected function validate(array $data, ?int $id = null): void
297 {
298 // Override in child classes
299 }
300
301 /**
302 * Count items with caching
303 */
304 public function count(array $filters = []): int
305 {
306 if ($this->isCacheEnabled()) {
307 $cacheKey = $this->getCacheKey('count', $filters);
308
309 return (int) $this->getCachedResult($cacheKey, function() use ($filters) {
310 return $this->getRepository()->count($filters);
311 }, 300); // 5 minute cache for counts
312 } else {
313 // Bypass cache and get fresh data
314 Logger::debug("Cache disabled, getting fresh count", ['service' => static::class, 'filters' => $filters]);
315 return $this->getRepository()->count($filters);
316 }
317 }
318
319 /**
320 * Check if item exists
321 */
322 public function exists(int $id): bool
323 {
324 if ($id <= 0) {
325 return false;
326 }
327
328 if ($this->isCacheEnabled()) {
329 $cacheKey = $this->getCacheKey('exists', ['id' => $id]);
330
331 return (bool) $this->getCachedResult($cacheKey, function() use ($id) {
332 return $this->getRepository()->exists($id);
333 }, 600); // 10 minute cache for existence checks
334 } else {
335 // Bypass cache and get fresh data
336 Logger::debug("Cache disabled, checking existence directly", ['service' => static::class, 'id' => $id]);
337 return $this->getRepository()->exists($id);
338 }
339 }
340
341 /**
342 * Get paginated results with caching
343 */
344 public function paginate(int $page = 1, int $perPage = 10, array $filters = []): array
345 {
346 if ($this->isCacheEnabled()) {
347 $cacheKey = $this->getCacheKey('paginate', [
348 'page' => $page,
349 'per_page' => $perPage,
350 'filters' => $filters
351 ]);
352
353 return $this->getCachedResult($cacheKey, function() use ($page, $perPage, $filters) {
354 return $this->executePaginateQuery($page, $perPage, $filters);
355 });
356 } else {
357 // Bypass cache and get fresh data
358 Logger::debug("Cache disabled, getting fresh pagination", ['service' => static::class, 'page' => $page, 'per_page' => $perPage]);
359 return $this->executePaginateQuery($page, $perPage, $filters);
360 }
361 }
362
363 /**
364 * Execute pagination query
365 */
366 private function executePaginateQuery(int $page, int $perPage, array $filters): array
367 {
368 $repository = $this->getRepository();
369
370 // Handle different paginate method signatures
371 if (method_exists($repository, 'paginate')) {
372 // Try the new signature first: paginate(int $page, int $perPage, array $filters)
373 try {
374 return $repository->paginate($page, $perPage, $filters);
375 } catch (ArgumentCountError $e) {
376 // Fall back to old signature: paginate(array $filters)
377 $filters['page'] = $page;
378 $filters['per_page'] = $perPage;
379 return $repository->paginate($filters);
380 }
381 } else {
382 // Fallback to all method with pagination parameters
383 $filters['limit'] = $perPage;
384 $filters['offset'] = ($page - 1) * $perPage;
385 return $repository->all($filters);
386 }
387 }
388
389 /**
390 * Check if caching is enabled (delegates to {@see Cache::isEnabled()} then backend probe).
391 */
392 protected function isCacheEnabled(): bool
393 {
394 try {
395 if (!\Yatra\Utils\Cache::isEnabled()) {
396 return false;
397 }
398
399 if (!$this->isCacheBackendAvailable()) {
400 Logger::warning("Cache enabled but backend not available, disabling cache");
401 return false;
402 }
403
404 return true;
405 } catch (\Exception $e) {
406 Logger::error("Failed to check cache enabled status", [
407 'error' => $e->getMessage(),
408 ]);
409 return false;
410 }
411 }
412
413 /**
414 * Check if cache backend is available
415 */
416 private function isCacheBackendAvailable(): bool
417 {
418 try {
419 // Test basic cache operation
420 $testKey = 'yatra_cache_test_' . time();
421 $testValue = 'test_value';
422
423 Cache::set($testKey, $testValue, 1);
424 $retrieved = Cache::get($testKey);
425
426 // Clean up test key
427 Cache::delete($testKey);
428
429 return $retrieved === $testValue;
430
431 } catch (\Exception $e) {
432 Logger::warning("Cache backend availability check failed", [
433 'error' => $e->getMessage()
434 ]);
435 return false;
436 }
437 }
438
439 /**
440 * Clear cache for entity
441 */
442 protected function clearEntityCache(int $id): void
443 {
444 if (!$this->isCacheEnabled()) {
445 return; // No cache to clear
446 }
447
448 $entityType = strtolower(str_replace(['\\', 'Service'], ['_', ''], static::class));
449
450 // Clear specific entity cache
451 $this->clearCacheByPattern("{$entityType}_entity_{$id}");
452
453 // Clear related list caches
454 $this->clearCacheByPattern("{$entityType}_all");
455 $this->clearCacheByPattern("{$entityType}_count");
456 $this->clearCacheByPattern("{$entityType}_exists_{$id}");
457 $this->clearCacheByPattern("{$entityType}_paginate");
458
459 // Clear query result caches
460 $this->clearCacheByPattern(Cache::PREFIX_QUERY_RESULT);
461
462 Logger::info("Entity cache cleared", [
463 'service' => static::class,
464 'entity_id' => $id,
465 'entity_type' => $entityType
466 ]);
467 }
468
469 /**
470 * Clear all cache for this service
471 */
472 protected function clearAllServiceCache(): void
473 {
474 if (!$this->isCacheEnabled()) {
475 return; // No cache to clear
476 }
477
478 $entityType = strtolower(str_replace(['\\', 'Service'], ['_', ''], static::class));
479
480 // Clear all caches related to this service
481 $this->clearCacheByPattern($entityType);
482
483 Logger::info("All service cache cleared", [
484 'service' => static::class,
485 'entity_type' => $entityType
486 ]);
487 }
488
489 /**
490 * Clear cache by pattern
491 */
492 private function clearCacheByPattern(string $pattern): void
493 {
494 try {
495 Cache::clearByPrefix($pattern);
496 } catch (\Exception $e) {
497 Logger::warning("Failed to clear cache pattern", [
498 'pattern' => $pattern,
499 'error' => $e->getMessage()
500 ]);
501 }
502 }
503
504 /**
505 * Get cached result or execute callback with performance monitoring
506 *
507 * @return mixed
508 */
509 protected function getCachedResult(string $cacheKey, callable $callback, int $duration = 1800)
510 {
511 $startTime = microtime(true);
512
513 try {
514 if ($this->isCacheEnabled()) {
515 $result = Cache::remember($cacheKey, $callback, $duration);
516
517 // Record cache performance metrics
518 $this->recordCacheMetrics($cacheKey, $startTime, true);
519
520 return $result;
521 } else {
522 // Bypass cache and execute callback directly
523 $result = $callback();
524
525 // Record non-cache performance metrics
526 $this->recordCacheMetrics($cacheKey, $startTime, false);
527
528 return $result;
529 }
530
531 } catch (\Exception $e) {
532 // Fallback to direct execution on cache failure
533 Logger::warning("Cache operation failed, falling back to direct execution", [
534 'cache_key' => $cacheKey,
535 'error' => $e->getMessage()
536 ]);
537
538 $result = $callback();
539 $this->recordCacheMetrics($cacheKey, $startTime, false, $e);
540
541 return $result;
542 }
543 }
544
545 /**
546 * Record cache performance metrics
547 */
548 private function recordCacheMetrics(string $cacheKey, float $startTime, bool $cacheUsed, ?\Exception $error = null): void
549 {
550 $executionTime = microtime(true) - $startTime;
551
552 $metrics = [
553 'cache_key' => substr($cacheKey, 0, 50) . '...',
554 'cache_used' => $cacheUsed,
555 'execution_time' => round($executionTime * 1000, 2), // in milliseconds
556 'service' => static::class,
557 'timestamp' => time()
558 ];
559
560 if ($error) {
561 $metrics['error'] = $error->getMessage();
562 Logger::warning("Cache performance recorded with error", $metrics);
563 } else {
564 Logger::debug("Cache performance recorded", $metrics);
565 }
566
567 // Store metrics for analytics (optional - can be extended)
568 $this->storeCacheMetrics($metrics);
569 }
570
571 /**
572 * Store cache metrics for analytics
573 */
574 private function storeCacheMetrics(array $metrics): void
575 {
576 try {
577 // Store metrics in WordPress options for analytics
578 $existingMetrics = get_option('yatra_cache_metrics', []);
579 $existingMetrics[] = $metrics;
580
581 // Keep only last 1000 entries to prevent bloat
582 if (count($existingMetrics) > 1000) {
583 $existingMetrics = array_slice($existingMetrics, -1000);
584 }
585
586 update_option('yatra_cache_metrics', $existingMetrics);
587
588 } catch (\Exception $e) {
589 // Don't let metrics storage failures break the main functionality
590 Logger::debug("Failed to store cache metrics", [
591 'error' => $e->getMessage()
592 ]);
593 }
594 }
595
596 /**
597 * Generate cache key for service operations
598 */
599 protected function getCacheKey(string $operation, array $params = []): string
600 {
601 $serviceClass = strtolower(str_replace(['\\', 'Service'], ['_', ''], static::class));
602 $paramsHash = md5(serialize($params));
603
604 return "service_{$serviceClass}_{$operation}_{$paramsHash}";
605 }
606
607 /**
608 * Fire WordPress hooks for service events
609 */
610 protected function fireHook(string $action, ...$args): void
611 {
612 $serviceClass = strtolower(str_replace(['\\', 'Service'], ['_', ''], static::class));
613 $hookName = "yatra_{$serviceClass}_{$action}";
614
615 do_action($hookName, ...$args);
616
617 Logger::debug("Service hook fired", [
618 'hook' => $hookName,
619 'args_count' => count($args)
620 ]);
621 }
622
623 /**
624 * Invalidate related caches
625 */
626 protected function invalidateRelatedCaches(int $id, string $operation): void
627 {
628 // Clear entity cache
629 $this->clearEntityCache($id);
630
631 // Clear all service cache for list operations
632 $this->clearAllServiceCache();
633
634 // Clear related entity caches based on operation type
635 $this->clearRelatedEntityCaches($id, $operation);
636
637 Logger::debug("Related caches invalidated", [
638 'service' => static::class,
639 'entity_id' => $id,
640 'operation' => $operation
641 ]);
642 }
643
644 /**
645 * Clear related entity caches
646 */
647 protected function clearRelatedEntityCaches(int $id, string $operation): void
648 {
649 // Override in child classes to clear specific related caches
650 // For example: TripService would clear destination, activity caches
651 // AttributeService would clear trip-related caches, etc.
652 }
653
654 /**
655 * Process data before create (override in child classes)
656 */
657 protected function processBeforeCreate(array $data): array
658 {
659 return $data;
660 }
661
662 /**
663 * Process after create (override in child classes)
664 */
665 protected function processAfterCreate(int $id, array $data): void
666 {
667 // Override in child classes
668 }
669
670 /**
671 * Process data before update (override in child classes)
672 */
673 protected function processBeforeUpdate(int $id, array $data): array
674 {
675 return $data;
676 }
677
678 /**
679 * Process after update (override in child classes)
680 */
681 protected function processAfterUpdate(int $id, array $data): void
682 {
683 // Override in child classes
684 }
685
686 /**
687 * Process before delete (override in child classes)
688 */
689 protected function processBeforeDelete(int $id): void
690 {
691 // Override in child classes
692 }
693
694 /**
695 * Process after delete (override in child classes)
696 */
697 protected function processAfterDelete(int $id): void
698 {
699 // Override in child classes
700 }
701 }
702
703