Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.98% covered (success)
92.98%
53 / 57
93.75% covered (success)
93.75%
15 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
CacheInvalidationService
92.98% covered (success)
92.98%
53 / 57
93.75% covered (success)
93.75%
15 / 16
20.14
0.00% covered (danger)
0.00%
0 / 1
 invalidatePlanCaches
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 invalidateAllPlanCaches
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 invalidateFeatureCaches
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 invalidateUserSubscriptionCachesByPlan
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 invalidateUserSubscriptionCachesByFeature
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 invalidateUserSubscriptionCache
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 invalidateParameterCaches
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 invalidateUserCache
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 invalidateUserSettingsCaches
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 invalidateGlobalSettingsCaches
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 invalidateRemoteConfigCache
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getRemoteConfigCacheKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUserSubscriptionCachePrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPlansCachePrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getParametersCachePrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getGlobalSettingsCacheKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace App\Http\Services;
4
5use App\Http\Models\Plans;
6use App\Http\Models\Subscription;
7use App\Jobs\InvalidateUserSubscriptionCachesJob;
8use Illuminate\Support\Facades\Cache;
9use Illuminate\Support\Facades\Log;
10
11/**
12 * Centralized service for coordinating cache invalidation.
13 *
14 * This service provides methods to invalidate various caches when
15 * admin changes are made to plans, features, parameters, and settings.
16 * It ensures users receive fresh data immediately after admin updates.
17 */
18class CacheInvalidationService
19{
20    /**
21     * Cache key prefixes.
22     */
23    private const PLANS_PREFIX = 'plans:';
24
25    private const PARAMETERS_PREFIX = 'parameters:';
26
27    private const FEATURES_PREFIX = 'features:';
28
29    private const GLOBAL_SETTINGS_KEY = 'global_settings';
30
31    private const REMOTE_CONFIG_KEY = 'remote_config:current';
32
33    private const USER_DETAILS_PREFIX = 'user_details_';
34
35    private const USER_SETTING_PREFIX = 'setting_';
36
37    private const USER_SETTING_DETAILS_PREFIX = 'setting_details_';
38
39    private const USER_SUBSCRIPTION_PREFIX = 'current_subscription_';
40
41    /**
42     * Invalidate all plan-related caches for a specific plan.
43     *
44     * @param  string  $planId  The plan ID
45     * @param  string|null  $identifier  The plan identifier (optional)
46     */
47    public function invalidatePlanCaches(string $planId, ?string $identifier = null): void
48    {
49        Cache::forget(self::PLANS_PREFIX.$planId);
50        Cache::forget(self::PLANS_PREFIX.$planId.':features');
51        Cache::forget(self::PLANS_PREFIX.'all_active');
52
53        if ($identifier) {
54            Cache::forget("plan_quota_{$identifier}");
55            Plans::clearIdentifierCache($identifier);
56        }
57
58        Log::info('Plan caches invalidated', [
59            'plan_id' => $planId,
60            'identifier' => $identifier,
61        ]);
62    }
63
64    /**
65     * Invalidate all plan caches (global invalidation).
66     */
67    public function invalidateAllPlanCaches(): void
68    {
69        Cache::forget(self::PLANS_PREFIX.'all_active');
70
71        Log::info('All plan caches invalidated');
72    }
73
74    /**
75     * Invalidate all feature caches.
76     */
77    public function invalidateFeatureCaches(): void
78    {
79        Cache::forget(self::FEATURES_PREFIX.'all');
80
81        Log::info('Feature caches invalidated');
82    }
83
84    /**
85     * Invalidate user subscription caches for all users subscribed to a specific plan.
86     *
87     * This method dispatches an async job to handle bulk invalidation without
88     * blocking the admin API response.
89     *
90     * @param  string  $stripeId  The Stripe plan ID (price ID)
91     */
92    public function invalidateUserSubscriptionCachesByPlan(string $stripeId): void
93    {
94        InvalidateUserSubscriptionCachesJob::dispatch($stripeId);
95
96        Log::info('User subscription cache invalidation job dispatched', [
97            'stripe_id' => $stripeId,
98        ]);
99    }
100
101    /**
102     * Invalidate user subscription caches for all users subscribed to plans
103     * that use a specific feature.
104     *
105     * @param  string  $featureId  The feature ID
106     */
107    public function invalidateUserSubscriptionCachesByFeature(string $featureId): void
108    {
109        // Get all plans that use this feature via plan_features
110        $planStripeIds = Plans::whereHas('planFeatures', function ($query) use ($featureId) {
111            $query->where('feature_id', $featureId);
112        })->whereNotNull('stripe_id')->pluck('stripe_id');
113
114        foreach ($planStripeIds as $stripeId) {
115            $this->invalidateUserSubscriptionCachesByPlan($stripeId);
116        }
117
118        Log::info('User subscription cache invalidation dispatched for feature', [
119            'feature_id' => $featureId,
120            'affected_plans' => $planStripeIds->count(),
121        ]);
122    }
123
124    /**
125     * Invalidate a single user's subscription cache.
126     *
127     * @param  string  $userId  The user ID
128     * @return bool True if cache was cleared
129     */
130    public function invalidateUserSubscriptionCache(string $userId): bool
131    {
132        $cacheKey = self::USER_SUBSCRIPTION_PREFIX.$userId;
133
134        return Cache::forget($cacheKey);
135    }
136
137    /**
138     * Invalidate parameter caches.
139     *
140     * @param  string|null  $name  Optional specific parameter name
141     * @param  string|null  $id  Optional specific parameter ID
142     */
143    public function invalidateParameterCaches(?string $name = null, ?string $id = null): void
144    {
145        // Always invalidate the all parameters cache
146        Cache::forget(self::PARAMETERS_PREFIX.'all');
147
148        // Always invalidate the metadata parameters cache
149        Cache::forget(self::PARAMETERS_PREFIX.'metadata');
150
151        // Invalidate specific parameter caches if provided
152        if ($name) {
153            Cache::forget(self::PARAMETERS_PREFIX.'name:'.$name);
154        }
155
156        if ($id) {
157            Cache::forget(self::PARAMETERS_PREFIX.'id:'.$id);
158        }
159
160        Log::info('Parameter caches invalidated', [
161            'name' => $name,
162            'id' => $id,
163        ]);
164    }
165
166    /**
167     * Invalidate the user details cache.
168     *
169     * Should be called whenever the User model is modified so that
170     * the /user/details endpoint returns fresh data.
171     *
172     * @param  string  $userId  The user ID
173     */
174    public function invalidateUserCache(string $userId): void
175    {
176        Cache::forget(self::USER_DETAILS_PREFIX.$userId);
177
178        Log::info('User details cache invalidated', [
179            'user_id' => $userId,
180        ]);
181    }
182
183    /**
184     * Invalidate user settings caches.
185     *
186     * Clears both the raw setting cache and the computed setting details cache
187     * for a specific user.
188     *
189     * @param  string  $userId  The user ID
190     */
191    public function invalidateUserSettingsCaches(string $userId): void
192    {
193        Cache::forget(self::USER_SETTING_PREFIX.$userId);
194        Cache::forget(self::USER_SETTING_DETAILS_PREFIX.$userId);
195
196        Log::info('User settings caches invalidated', [
197            'user_id' => $userId,
198        ]);
199    }
200
201    /**
202     * Invalidate global settings cache.
203     */
204    public function invalidateGlobalSettingsCaches(): void
205    {
206        Cache::forget(self::GLOBAL_SETTINGS_KEY);
207
208        Log::info('Global settings cache invalidated');
209    }
210
211    /**
212     * Invalidate the remote config cache.
213     *
214     * Called when admin makes changes to the remote config so that
215     * the consumer endpoint (meta-data) returns fresh data.
216     */
217    public function invalidateRemoteConfigCache(): void
218    {
219        Cache::forget(self::REMOTE_CONFIG_KEY);
220
221        Log::info('Remote config cache invalidated');
222    }
223
224    /**
225     * Get the remote config cache key.
226     */
227    public function getRemoteConfigCacheKey(): string
228    {
229        return self::REMOTE_CONFIG_KEY;
230    }
231
232    /**
233     * Get the cache key prefix for user subscriptions.
234     *
235     * Exposed for use by other services that need to build cache keys.
236     */
237    public function getUserSubscriptionCachePrefix(): string
238    {
239        return self::USER_SUBSCRIPTION_PREFIX;
240    }
241
242    /**
243     * Get the cache key prefix for plans.
244     */
245    public function getPlansCachePrefix(): string
246    {
247        return self::PLANS_PREFIX;
248    }
249
250    /**
251     * Get the cache key prefix for parameters.
252     */
253    public function getParametersCachePrefix(): string
254    {
255        return self::PARAMETERS_PREFIX;
256    }
257
258    /**
259     * Get the global settings cache key.
260     */
261    public function getGlobalSettingsCacheKey(): string
262    {
263        return self::GLOBAL_SETTINGS_KEY;
264    }
265}