الخطوات
-
1
تحديد ما يستحق التخزين المؤقت
ليس كل استعلام يجب تخزينه مؤقتاً. المكان المثالي هو البيانات التي تُقرأ كثيراً ونادراً ما تتغير. مثل إعدادات الموقع، فئات التنقل، محتوى الصفحة الرئيسية المميز، أو الإحصاءات المجمّعة. تجنب تخزين أي شيء يجب أن يكون فورياً — حالات الطلبات، مستويات المخزون، أو بيانات الجلسة لكل مستخدم.
اعتبر أيضاً تكلفة الاستعلام. بحث بسيط في
usersعبر المفتاح الأساسي سريع أصلاً. أما تقرير بـ multi-join مع GROUP BY على ملايين الصفوف فهو مرشح ممتاز. -
2
تخزين استعلام باستخدام Cache::remember
Cache::remember()هو المحرك الأساسي. يتحقق من التخزين المؤقت أولاً؛ إن وُجد المفتاح يعيد القيمة المخزنة، وإلا ينفّذ الـ closure ويخزن النتيجة ثم يعيدها. مدة الصلاحية TTL بالثواني.phpuse Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; $categories = Cache::remember('categories.all', 3600, function () { return DB::table('categories') ->where('active', true) ->orderBy('name') ->get(); }); // Eloquent version $settings = Cache::remember('settings.all', 86400, fn () => Setting::all()->keyBy('key') ); -
3
اختيار مفاتيح تخزين واضحة
مفتاح التخزين هو الشيء الوحيد الذي يفصل بين قيمتين مخزنتين. التصادمات صامتة وصعبة التصحيح. اتبع نمط تسمية ثابتاً:
اسم.مؤهلأواسم:id:مؤهل. أدرج دائماً العوامل التي تؤثر على النتيجة — اللغة، دور المستخدم، الفلاتر المطبقة.php// Bad — collides across locales Cache::remember('posts', 3600, fn () => Post::published()->get()); // Good — locale-scoped $locale = app()->getLocale(); Cache::remember("posts.featured.{$locale}", 3600, fn () => Post::published()->featured()->with('author')->get() ); // Good — user-scoped Cache::remember("user.{$user->id}.dashboard", 600, fn () => Dashboard::buildFor($user) ); -
4
استخدام الـ cache() helper للاختصار
الـ helper العام
cache()هو اختصار للعمليات الشائعة. هو مطابق للـ facade داخلياً — استخدم أيهما تفضل، لكن كن ثابتاً داخل المشروع.php// Remember cache()->remember('key', 3600, fn () => expensiveQuery()); // Store forever cache()->rememberForever('key', fn () => expensiveQuery()); // Get with default $value = cache('key', 'default'); // Put cache(['key' => $value], now()->addHour()); // Forget cache()->forget('key'); -
5
إبطال التخزين عبر Model Observers
التخزين المؤقت القديم أسوأ من عدمه. عند حفظ نموذج أو حذفه، يجب تنظيف إدخالات التخزين المرتبطة به. الـ model observers هي أنظف مكان لذلك — المنطق يعيش بجانب النموذج وينطلق تلقائياً في كل مسار كتابة.
php<?php namespace App\Observers; use App\Models\Category; use Illuminate\Support\Facades\Cache; class CategoryObserver { public function saved(Category $category): void { Cache::forget('categories.all'); Cache::forget("categories.all.en"); Cache::forget("categories.all.ar"); } public function deleted(Category $category): void { $this->saved($category); } } // Register in AppServiceProvider or via #[ObservedBy] attribute (Laravel 10+) // Category::observe(CategoryObserver::class); -
6
اختيار برنامج تشغيل التخزين المناسب
Laravel يدعم عدة برامج تشغيل. File مناسب للتطبيقات الصغيرة والتطوير المحلي — لا خدمات إضافية. Database مفيد حين لا تستطيع تثبيت Redis لكنك تحتاج التخزين يبقى بعد إعادة التشغيل. Redis هو المعيار في الإنتاج: قراءات دون ميلي ثانية، عمليات ذرية، وإدارة TTL مدمجة. اضبط برنامج التشغيل في
.env.bash# .env CACHE_STORE=redis # redis | database | file | array REDIS_HOST=127.0.0.1 REDIS_PORT=6379 # For database driver, create the table first: php artisan cache:table php artisan migrate -
7
مسح التخزين المؤقت عند النشر
نشر كود جديد قد يغير ما يجب أن يعيده الاستعلام المخزن — أعمدة جديدة، علاقات مختلفة، منطق أعمال محدّث. امسح تخزين التطبيق دائماً كجزء من سكريبت النشر حتى يعيد الطلب الأول بعد النشر بناء قيم جديدة.
bash# In your deploy script (after git pull) php artisan cache:clear php artisan optimize php artisan view:cache -
8
قياس الأداء قبل التخزين وبعده
لا تخزّن بشكل أعمى. استخدم
microtime()للتأكد أنك فعلاً توفر وقتاً، أو فعّل لوحة استعلامات Telescope لاكتشاف مشاكل N+1 والاستعلامات البطيئة قبل اللجوء للتخزين. التخزين يخفي المشاكل — التحليل يجدها.php// Quick benchmark in a controller or tinker $start = microtime(true); $result = Cache::remember('expensive.query', 600, fn () => HeavyReport::generate() ); $ms = round((microtime(true) - $start) * 1000, 2); logger("Cache query took {$ms}ms"); // Or use DB::listen() to log all queries during a request DB::listen(fn($q) => logger($q->sql, ['time' => $q->time . 'ms']));
نصائح ومحاذير
- استخدم <code>Cache::tags()</code> (مع Redis/Memcached فقط) لتجميع المفاتيح المتعلقة ببعضها وإفراغها دفعة واحدة — أنظف بكثير من إدراج كل مفتاح يدوياً في الـ observer.
- اضبط مدة صلاحية TTL واقعية. "للأبد" نادراً ما يكون صحيحاً للبيانات المرتبطة بقاعدة بيانات. حتى ساعة من القِدم مقبولة لمعظم بيانات واجهة المستخدم.
- إذا رمى الـ closure استثناءً، لن يخزن <code>Cache::remember()</code> النتيجة — ينتشر الخطأ. هذا السلوك الصحيح؛ لا تبتلع الاستثناءات داخل closures التخزين أبداً.
- للمفاتيح شديدة الطلب (آلاف الزيارات في الثانية)، فكر في نهج طبقتين: متغير ثابت داخل الطلب يُفحص قبل الوصول إلى مخزن التخزين.
- <code>php artisan tinker</code> مع <code>Cache::get('key')</code> هي أسرع طريقة لفحص ما هو مخزن فعلاً والتأكد من نجاح الإبطال.
خاتمة
التخزين المؤقت الفعّال يقوم على اختيار البيانات الصحيحة، وتسميتها بدقة، وإبقائها حديثة. اضبط هذه الأمور الثلاثة وستتمكن من خفض الحمل على قاعدة البيانات بشكل كبير دون إدخال أخطاء خفية.