اتخطّى للمحتوى

المرحلة 05 · الأداء والتشغيلدرس 28 من 3421 دقيقة قراءةآخر تحديث:

Lesson 28 / 34

Magento Cacheمن الصفر.

أهم موضوع في أداء Magento. هنفهم طبقات الكاش وترتيبها، أنواع الكاش الداخلية، الـ Full Page Cache و Varnish، الـ tags والإبطال، وإزاي تتحكم في الكاش من كودك وتعمل نوع مخصّص.

● شامل من الصفر كود مشروح أمثلة عملية

الكاش في Magento 2 طبقات: كاش المتصفح، وبعدين الـ Full Page Cache أو Varnish اللي بيرد قبل ما PHP يشتغل، وبعدين الكاش الداخلي زي config و layout و block_html، وأخيرًا قاعدة البيانات والـ indexers. كل صفحة بتتخزّن بـ tags زي cat_p_42 فلما المنتج يتغيّر بيتمسح اللي فيه بس، والمحتوى الشخصي بيتحمّل بالـ private content.

// ليه ده أهم موضوع في الأداء Magento تطبيق تقيل بطبيعته — صفحة منتج واحدة ممكن تحتاج مئات الاستعلامات. الكاش هو اللي بيخلّيه سريع. من غيره المتجر بطيء مهما حسّنت أي حاجة تانية، ومعاه مظبوط بيخدم آلاف الزوار على سيرفر متواضع. الفرق بين الحالتين ممكن يكون 50 ضعف.
◆ Part 1 — الفهم
01

يعني إيه cache أصلاً

الأساس — لو ده واضح، الباقي كله منطقي.

التعريف

الـ cache هو تخزين نتيجة عملية تقيلة عشان المرة الجاية ترجّعها من غير ما تعيدها. بس كده — مفيش سحر في الموضوع.

طلب
في الكاش؟
رجّع فوراً
الدورة الكاملة
  • يجي طلب لحاجة (صفحة، قيمة إعداد، نتيجة حساب).
  • النظام بيشوف الحاجة دي محفوظة في الكاش ولا لأ.
  • لو موجودة (cache hit) — بيرجّعها فوراً وخلاص.
  • لو مش موجودة (cache miss) — بيحسبها، يخزّنها، ويرجّعها.
  • المرة الجاية بتبقى hit.
تشبيه زي حل مسألة رياضية صعبة. أول مرة بتقعد تحسبها وتاخد وقت. بتكتب الناتج على ورقة جنبك. المرة الجاية اللي حد يسألك نفس السؤال، بتبص على الورقة وترد فوراً. بس لو الأرقام اتغيّرت، لازم ترمي الورقة وتحسب من الأول — والجزء الأخير ده هو أصعب حاجة في الكاش.
المشكلة الحقيقية

التخزين سهل. الصعب هو معرفة امتى النتيجة المخزّنة بقت قديمة ولازم تتلغي. ده اسمه cache invalidation (إبطال الكاش)، وهو مصدر معظم المشاكل — عرض سعر قديم، أو منتج اتشال ولسه ظاهر.

i
فيه مقولة مشهورة بين المبرمجين: "فيه حاجتين صعبين في علوم الحاسب — إبطال الكاش وتسمية المتغيرات". الجملة دي بتتقال كنكتة بس هي دقيقة فعلاً، وهتفهم ليه بعد القسم 08.
• • •
02

طبقات الكاش في Magento

فيه أكتر من طبقة، وكل واحدة بتشتغل في مرحلة مختلفة.

1 · Browser Cache الملفات الثابتة عند الزائر
2 · Varnish / FPC صفحة HTML كاملة — قبل ما يوصل Magento
3 · Block / Config Cache أجزاء من الصفحة — جوّه Magento
4 · Database + Indexers المصدر الفعلي
القاعدة الذهبية

كل ما الطلب يتوقف عند طبقة أعلى، كل ما كان أسرع. لو Varnish رد على الطلب، Magento مااشتغلش أصلاً — ولا PHP ولا قاعدة بيانات. ده الفرق بين 5 مللي ثانية و 800.

layer
1 · Browser Cache
المتصفح بيحفظ الـ CSS والـ JS والصور عند الزائر نفسه.
بيوفّرطلبات شبكة بالكامل. بيتحكم فيه بالـ headers.
2 · Varnish / FPC
صفحة HTML جاهزة، بترجع قبل ما PHP يشتغل.
الأهمأكبر مكسب أداء في Magento كله. ده اللي تركّز عليه.
3 · الكاش الداخلي
أجزاء محسوبة — إعدادات، layouts، blocks، ترجمات.
بيوفّرحسابات متكرّرة لما Magento يضطر يشتغل.
4 · Indexers
مش كاش بالمعنى الحرفي، بس نفس المبدأ — بيانات محضّرة مسبقاً.
بيوفّرحسابات قاعدة البيانات المعقّدة.
!
لما تشخّص مشكلة "التعديل مش ظاهر"، امشي على الطبقات من فوق لتحت. معظم الناس بتفتح الكود على طول والمشكلة في طبقة 2 أو 3. جرّب في نافذة خاصة الأول — لو التعديل ظهر، المشكلة في كاش المتصفح.
• • •
03

أنواع الكاش الداخلية

اللي بتشوفها في لوحة الأدمن — كل واحد بيخزّن إيه.

cache type
config
كل ملفات الإعداد المدموجة (di.xml, config.xml, الخ).
امسحه لماتعدّل أي ملف XML في موديول. أكتر واحد هتمسحه.
layout
ملفات الـ layout المدموجة.
امسحه لماتعدّل layout XML.
block_html
مخرجات الـ blocks المرندرة.
امسحه لماتعدّل template أو block ومش شايف التغيير.
full_page
صفحات HTML كاملة (لو مفيش Varnish).
امسحه لماتعدّل أي حاجة في الواجهة.
collections
نتائج استعلامات الـ collections.
امسحه لماتعدّل بيانات مباشرة في قاعدة البيانات.
db_ddl
بنية جداول قاعدة البيانات.
امسحه لماتعدّل schema — أو تحصل أخطاء "عمود غير موجود" غريبة.
eav
تعريفات وبيانات الـ EAV.
امسحه لماتضيف أو تعدّل خاصية منتج.
translate
الترجمات.
امسحه لماتعدّل ملفات CSV الترجمة.
reflection
بيانات الـ API interfaces.
امسحه لماتعدّل data interfaces أو webapi.
compiled_config
إعدادات الـ DI المجمّعة.
امسحه لماتعدّل di.xml بشكل جوهري.
customer_notification
إشعارات العملاء المؤقتة.
نادراًمالوش تأثير على التطوير عادة.
target_rule
قواعد المنتجات المرتبطة (Adobe Commerce).
امسحه لماتعدّل قواعد التوصيات.
i
الأربعة الأوائل (بالكهرماني) هم 90% من شغلك اليومي. لو حفظت إنك تمسحهم بعد أي تعديل، هتوفّر على نفسك وقت كتير من الحيرة.
!
في production، مسح الكاش كله مرة واحدة على متجر مزدحم بيعمل "عاصفة" — كل الطلبات بتوصل لـ PHP في نفس اللحظة والسيرفر ممكن يقع. امسح النوع اللي محتاجه بس، أو اعمل إحماء تدريجي.
• • •
04

الـ Backends — فين بيتخزّن

الكاش لازم يتخزّن في مكان — والاختيار بيفرق كتير.

File (الافتراضي)

ملفات في var/cache. شغّال بدون إعداد، بس بطيء ومش بيتشارك بين سيرفرات.

Redis

في الذاكرة، سريع جداً، ومشترك بين كل السيرفرات. ده اللي تستخدمه في production.

ليه Redis ضروري لما يكون فيه أكتر من سيرفر

لو الكاش ملفات على كل سيرفر، كل واحد عنده نسخته. تمسح الكاش على سيرفر، والتاني لسه بيقدّم القديم. والـ sessions نفس المشكلة — المستخدم بيتنقل بين السيرفرات فيفقد سلته.

app/etc/env.php — إعداد Redisphp
'cache' => [
    'frontend' => [
        // الكاش العام (config, layout, block_html...)
        'default' => [
            'backend' => 'Cm_Cache_Backend_Redis',
            'backend_options' => [
                'server'   => '127.0.0.1',
                'port'     => '6379',
                // رقم قاعدة بيانات مستقل
                'database' => '0',
            ],
        ],
        // كاش الصفحات — قاعدة بيانات منفصلة
        'page_cache' => [
            'backend' => 'Cm_Cache_Backend_Redis',
            'backend_options' => [
                'server'   => '127.0.0.1',
                'port'     => '6379',
                'database' => '1',
            ],
        ],
    ],
],
شرح الإعداد
  • default — الكاش العام: الإعدادات، الـ layouts، الـ blocks.
  • page_cache — كاش الصفحات الكاملة، منفصل عشان مسحه ما يمسحش الباقي.
  • database — رقم قاعدة البيانات في Redis. لازم يكون مختلف لكل غرض.
!
أخطر خطأ في الإعداد ده: استخدام نفس رقم الـ database في بيئتين (staging و production مثلاً) على نفس سيرفر Redis. النتيجة إن البيئتين بيدهسوا كاش بعض — وبتظهر كأخطاء عشوائية مستحيل تفهمها. رقم مختلف لكل بيئة ولكل غرض.
i
فيه استخدام تالت لـ Redis وهو الـ sessions، وبيتعرّف في قسم منفصل في env.php اسمه session. برضه محتاج رقم database خاص بيه.
• • •
◆ Part 2 — كاش الصفحات
05

الـ Full Page Cache

أكبر مكسب أداء — وأكتر واحد بيتعطّل بالغلط.

الفكرة

بدل ما تخزّن أجزاء، تخزّن الصفحة كاملة كـ HTML. أول زائر بيشوف الصفحة بيحسّبها، وكل اللي بعده بياخدوا النسخة الجاهزة.

من غير FPC

كل طلب: PHP يشتغل، مئات الاستعلامات، بناء الصفحة. مئات المللي ثانية.

مع FPC

الصفحة الجاهزة بترجع مباشرة. ميلي ثواني معدودة.

الصفحات اللي بتتكاش

الصفحات العامة اللي كل الزوار بيشوفوها زي بعض: الرئيسية، الأقسام، المنتجات، صفحات CMS.

الصفحات اللي مابتتكاشش

الصفحات الشخصية: السلة، الـ checkout، حساب العميل. لأن محتواها بيختلف من زائر للتاني — ومينفعش تعرض سلة واحد لواحد تاني.

تشبيه زي مخبز بيجهّز عيش مقدّماً. الأصناف اللي الكل بيطلبها بتتخبز مرة وتتحط في الرف — أي زبون بياخد فوراً. لكن لو حد طلب طلب خاص، لازم يتعمل من الأول ليه هو بس.
نوعان من FPC
option
Built-in Cache
Magento بيخزّن الصفحات بنفسه (في Redis أو ملفات).
امتىالتطوير، والمشاريع الصغيرة. أبطأ لأن PHP لازم يشتغل عشان يرد.
Varnish
سيرفر مستقل قدّام Magento بيرد قبل ما PHP يشتغل.
امتىproduction دايماً. أسرع بمراحل.
!
الفرق الجوهري: مع الـ built-in، PHP لازم يشتغل ويحمّل Magento عشان يشوف الصفحة في الكاش. مع Varnish، الرد بيحصل قبل ما PHP يتنده أصلاً. ده السبب إن Varnish أسرع بكتير مش شوية.
• • •
06

Varnish

الطبقة اللي قدّام كل حاجة.

الزائر
Varnish
Magento
الدورة
  • الطلب بيوصل Varnish الأول (مش Magento).
  • Varnish بيشوف الصفحة عنده ولا لأ.
  • لو عنده — بيرد فوراً وMagento مااشتغلش خالص.
  • لو مش عنده — بيمرّر الطلب لـ Magento.
  • Magento بيبني الصفحة ويرجّعها مع headers بتقول "دي تتكاش وde مدتها".
  • Varnish بيخزّنها ويرجّعها للزائر. الطلب الجاي هيتوقف عند خطوة 3.
الإعداد

من الأدمن: Stores → Configuration → Advanced → System → Full Page Cache، اختار Varnish Caching. وبعدها تصدّر ملف الإعداد (VCL) وتحطه على سيرفر Varnish.

تصدير إعداد Varnishbash
# بيولّد ملف VCL مناسب لنسختك
bin/magento varnish:vcl:generate \
  --export-version=6 \
  --backend-host=localhost \
  --backend-port=8080 \
  --output-file=/etc/varnish/default.vcl
إزاي تتأكد إنه شغّال
فحص الـ headersbash
curl -I https://your-store.com/some-category

# دوّر على السطور دي في الرد:
#   X-Magento-Cache-Debug: HIT   ← الصفحة من الكاش ✓
#   X-Magento-Cache-Debug: MISS  ← اتبنت دلوقتي
#   Age: 320                     ← قاعدة في الكاش 320 ثانية
i
الـ X-Magento-Cache-Debug بيظهر لما تفعّل وضع التشخيص وتضيف الـ IP بتاعك في قائمة الـ developer IPs. من غير كده مش هيبان — وده مقصود لأسباب أمنية.
!
لو كل الصفحات بترجع MISS دايماً، الأسباب الشائعة: كوكي بيمنع الكاش، أو block معلّم إنه غير قابل للكاش (القسم 09)، أو الـ VCL مش مظبوط. صفحة واحدة فيها block غير قابل للكاش بتخلّي الصفحة كلها MISS.
• • •
07

المحتوى الخاص بالزائر

المعضلة: الصفحة متكاشة، فإزاي أعرض "أهلاً أحمد"؟

المشكلة

الصفحة الرئيسية متكاشة ونسخة واحدة للكل. بس فيها اسم العميل وعدد منتجات السلة — وديهما مختلفين لكل زائر. لو كاشتهم، كل واحد هيشوف بيانات غيره.

الحل

Magento بيفصل الصفحة لجزئين: الهيكل العام (متكاش ومشترك) والأجزاء الشخصية (بتتحمّل بعدين بـ JavaScript من الـ customer-data section).

صفحة متكاشة
+
بيانات شخصية (AJAX)
=
الصفحة النهائية
تشبيه زي خطاب مطبوع مسبقاً بفراغات. النص العام مطبوع على آلاف النسخ (متكاش)، والاسم بيتكتب بالقلم في الفراغ لكل شخص (شخصي). كده بتستفيد من الطباعة الجماعية من غير ما تضحّي بالتخصيص.
قراءة البيانات الشخصية في JSjavascript
define(['Magento_Customer/js/customer-data'], function (customerData) {
    'use strict';

    // بيرجّع كائن قابل للمراقبة
    var cart = customerData.get('cart');
    var customer = customerData.get('customer');

    // بيتحدّث تلقائياً لما البيانات تتغيّر
    console.log(cart().summary_count);
    console.log(customer().firstname);
});
إضافة قسم بيانات خاص بيك
etc/frontend/sections.xmlxml
<?xml version="1.0"?>
<config>
  <!-- لما الـ action دي تتنفّذ، حدّث القسم -->
  <action name="vendor/loyalty/redeem">
    <section name="loyalty-points"/>
  </action>
</config>
CustomerData/LoyaltyPoints.phpphp
namespace Vendor\Module\CustomerData;

use Magento\Customer\CustomerData\SectionSourceInterface;

class LoyaltyPoints implements SectionSourceInterface
{
    public function __construct(
        private readonly Session $customerSession,
        private readonly PointsRepository $points
    ) {}

    // بيرجّع البيانات اللي هتوصل للـ JS
    public function getSectionData(): array
    {
        if (!$this->customerSession->isLoggedIn()) {
            return ['points' => 0];
        }

        $customerId = $this->customerSession->getCustomerId();

        return [
            'points' => $this->points->getBalance($customerId),
        ];
    }
}
etc/frontend/di.xml — التسجيلxml
<type name="Magento\Customer\CustomerData\SectionPoolInterface">
  <arguments>
    <argument name="sectionSourceMap" xsi:type="array">
      <item name="loyalty-points" xsi:type="string">
        Vendor\Module\CustomerData\LoyaltyPoints
      </item>
    </argument>
  </arguments>
</type>
!
متعرضش بيانات حسّاسة في الـ sections. البيانات دي بتتخزّن في localStorage في متصفح الزائر — فأي حاجة تحطها فيها متاحة لأي سكريبت في الصفحة. الرصيد والاسم تمام، أرقام كاملة أو بيانات دفع لأ.
• • •
◆ Part 3 — التحكّم
08

الـ Tags والإبطال

الجزء الذكي — إزاي Magento بيعرف يمسح إيه بالظبط.

المشكلة اللي بتحلّها

منتج واحد اتغيّر سعره. الصفحات اللي فيها المنتج ده لازم تتمسح — بس مش كل الصفحات. لو مسحت كله، المتجر هيبطّئ بدون داعي.

الحل

كل صفحة متكاشة بتتخزّن مع لافتات (tags) بتقول "الصفحة دي فيها المنتجات كذا وكذا والأقسام كذا". لما منتج يتغيّر، Magento بيمسح الصفحات اللي عليها اللافتة دي بس.

شكل الـ tagstext
# صفحة منتج رقم 42 ممكن يكون عليها:
cat_p_42          # المنتج نفسه
cat_c_15          # القسم اللي فيه
cat_p             # كل المنتجات (عام)

# لما المنتج 42 يتغيّر، Magento بيمسح
# كل حاجة عليها cat_p_42 — ودي الصفحات
# اللي المنتج ده ظاهر فيها بس
تشبيه زي ملصقات ملوّنة على ملفات في أرشيف. كل ملف عليه ملصقات بتقول بيخص مين. لما معلومة عن عميل معيّن تتغيّر، بتسحب الملفات اللي عليها ملصق العميل ده بس — مش بتفضّي الأرشيف كله.
الـ tags الشائعة
tag
cat_p_{id}
منتج محدد.
بيتمسح لماالمنتج ده يتعدّل.
cat_c_{id}
قسم محدد.
بيتمسح لماالقسم يتعدّل أو منتجاته تتغيّر.
cms_p_{id}
صفحة CMS.
بيتمسح لماالصفحة تتعدّل.
cms_b_{id}
بلوك CMS.
بيتمسح لماالبلوك يتعدّل — وده بيمسح كل صفحة فيها البلوك.
استخدام الـ tags في كودك
block بيعلن اعتمادياتهphp
class FeaturedProducts extends Template
    implements IdentityInterface
{
    // Magento بينده دي عشان يعرف الصفحة دي
    // تتمسح امتى
    public function getIdentities(): array
    {
        $identities = [];

        foreach ($this->getProducts() as $product) {
            // كل منتج بيضيف لافتته
            $identities = array_merge(
                $identities,
                $product->getIdentities()
            );
        }

        return $identities;
    }
}
!
لو block بيعرض منتجات ومطبّقش IdentityInterface، الصفحة مش هتتمسح لما المنتجات تتغيّر — وهتفضل تعرض أسعار قديمة لحد ما الكاش يخلص مدته. ده أخطر خطأ في التعامل مع الكاش، لأنه صامت تماماً وبيظهر كشكوى من العملاء مش كخطأ في الـ log.
• • •
09

التحكم في كاش الـ Block

إزاي تخلّي block يتكاش أو لأ.

التحكم من الـ layoutxml
<!-- block عادي — بيتكاش -->
<block class="Vendor\Module\Block\Info"
  name="my.info"
  template="Vendor_Module::info.phtml"/>

<!-- block مش بيتكاش -->
<block class="Vendor\Module\Block\Live"
  name="my.live"
  cacheable="false"/>
!
أخطر إعداد في Magento كله. الـ cacheable="false" مابيلغيش كاش الـ block ده بس — بيلغي كاش الصفحة كلها. block صغير في الـ footer معلّم كده بيخلّي كل صفحات الموقع غير قابلة للكاش. ودي حالة معروفة بتقتل أداء متاجر كاملة، والناس بتدوّر شهور على السبب.
البديل الصح

لو محتاج محتوى متغيّر في صفحة متكاشة، استخدم الـ private content (القسم 07) — الصفحة تفضل متكاشة والجزء المتغيّر بيتحمّل بالـ JavaScript.

التحكم من الـ block نفسهphp
class MyBlock extends Template
{
    protected function _construct()
    {
        // مدة الكاش بالثواني (ساعة)
        $this->addData([
            'cache_lifetime' => 3600,
        ]);
    }

    // المفاتيح اللي بتميّز نسخ الـ block
    public function getCacheKeyInfo(): array
    {
        return [
            'MY_BLOCK',
            $this->_storeManager->getStore()->getId(),
            $this->getData('category_id'),
        ];
    }
}
شرح الـ cache key

الـ getCacheKeyInfo() بيحدد إيه اللي بيخلّي نسخة تختلف عن نسخة. في المثال ده، الـ block هيتخزّن بنسخة مختلفة لكل متجر ولكل قسم. لو نسيت تضيف متغيّر مؤثّر، هتعرض نسخة غلط.

i
مثال على الخطأ: block بيعرض سعر ونسيت تضيف مجموعة العميل في المفتاح. النتيجة إن عميل الجملة هيشوف سعر التجزئة أو العكس — حسب مين فتح الصفحة الأول.
• • •
10

استخدام الكاش في كودك

لما تحتاج تخزّن نتيجة عملية تقيلة بنفسك.

Model/ExpensiveData.phpphp
namespace Vendor\Module\Model;

use Magento\Framework\App\CacheInterface;
use Magento\Framework\Serialize\SerializerInterface;

class ExpensiveData
{
    // مفتاح مميّز للبيانات دي
    private const CACHE_KEY = 'vendor_expensive_data';

    // لافتة عشان نقدر نمسحها انتقائياً
    private const CACHE_TAG = 'vendor_expensive';

    // المدة بالثواني — ساعة
    private const LIFETIME = 3600;

    public function __construct(
        private readonly CacheInterface $cache,
        private readonly SerializerInterface $serializer,
        private readonly ApiClient $api
    ) {}

    public function getData(): array
    {
        // 1. جرّب الكاش الأول
        $cached = $this->cache->load(self::CACHE_KEY);

        if ($cached) {
            // لقيناها — رجّعها بعد فك التحويل
            return $this->serializer->unserialize($cached);
        }

        // 2. مش موجودة — احسبها
        $data = $this->api->fetchSomethingSlow();

        // 3. خزّنها للمرة الجاية
        $this->cache->save(
            $this->serializer->serialize($data),
            self::CACHE_KEY,
            [self::CACHE_TAG],   // اللافتات
            self::LIFETIME        // المدة
        );

        return $data;
    }

    // لمسح الكاش ده تحديداً
    public function invalidate(): void
    {
        $this->cache->clean([self::CACHE_TAG]);
    }
}
شرح كل جزء
  • load() — بيجيب القيمة أو false لو مش موجودة.
  • serializer — الكاش بيخزّن نصوص بس، فلازم تحوّل المصفوفات. متستخدمش serialize() العادية — الـ interface ده أأمن.
  • الـ tags — بتخليك تمسح البيانات دي بس بدل الكاش كله.
  • الـ lifetime — بالثواني. حط null لمدة غير محدودة (بحذر).
!
لاحظ الشيك if ($cached). لو القيمة المخزّنة ممكن تكون '0' أو نص فاضي، الشيك ده هيفشل ويعيد الحساب كل مرة. في الحالة دي استخدم !== false بدلها — وده فخ بيخلّي الكاش "شغّال" بس مالوش أي فايدة.
• • •
11

عمل نوع كاش مخصّص

عشان يظهر في لوحة الأدمن مع باقي الأنواع.

ليه تعمل نوع مخصّص

عشان فريق التشغيل يقدر يمسحه من الأدمن من غير ما يحتاج سطر أوامر، وعشان يتفصل عن باقي الكاش فمسح نوع ما يأثرش على التاني.

etc/cache.xmlxml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <type name="vendor_catalog_feed"
    translate="label,description"
    instance="Vendor\Module\Model\Cache\Type\Feed">
    <label>Catalog Feed</label>
    <description>Cached product feed data</description>
  </type>
</config>
Model/Cache/Type/Feed.phpphp
namespace Vendor\Module\Model\Cache\Type;

use Magento\Framework\Cache\Frontend\Decorator\TagScope;
use Magento\Framework\App\Cache\Type\FrontendPool;

class Feed extends TagScope
{
    // لازم يطابق الـ name في cache.xml
    public const TYPE_IDENTIFIER = 'vendor_catalog_feed';

    // اللافتة اللي كل إدخالات النوع ده هتاخدها
    public const CACHE_TAG = 'VENDOR_CATALOG_FEED';

    public function __construct(FrontendPool $cacheFrontendPool)
    {
        parent::__construct(
            $cacheFrontendPool->get(self::TYPE_IDENTIFIER),
            self::CACHE_TAG
        );
    }
}
الاستخدام
حقن النوع المخصّصphp
use Vendor\Module\Model\Cache\Type\Feed;

class FeedGenerator
{
    public function __construct(
        // بتحقن النوع بتاعك مباشرة
        private readonly Feed $cache
    ) {}

    public function get(string $key): ?string
    {
        $value = $this->cache->load($key);
        return $value !== false ? $value : null;
    }

    public function set(string $key, string $data): void
    {
        // اللافتة بتتضاف تلقائياً
        $this->cache->save($data, $key, [], 3600);
    }
}
التفعيل والاستخدامbash
bin/magento setup:upgrade
bin/magento cache:enable vendor_catalog_feed

# دلوقتي بيظهر في قائمة الأنواع
bin/magento cache:status

# ومسحه لوحده ممكن
bin/magento cache:clean vendor_catalog_feed
i
بعد التفعيل، النوع هيظهر في System → Cache Management في الأدمن بالاسم والوصف اللي كتبتهم. فريق التشغيل يقدر يمسحه بضغطة زرار.
• • •
◆ Part 4 — العملي
12

الأوامر والتشخيص

اللي هتستخدمه كل يوم.

الأوامر الأساسيةbash
# شوف حالة كل الأنواع
bin/magento cache:status

# امسح الإدخالات القديمة (الأخف)
bin/magento cache:clean

# امسح نوع محدد — الأفضل في production
bin/magento cache:clean config layout block_html

# امسح كل حاجة بما فيها إدخالات تطبيقات تانية
bin/magento cache:flush

# عطّل نوع (للتطوير)
bin/magento cache:disable block_html

# فعّل تاني
bin/magento cache:enable block_html
الفرق بين clean و flush

clean

بيمسح إدخالات Magento المعلّمة بلافتاته بس. أخف وأأمن.

flush

بيفضّي مخزن الكاش كله — حتى بيانات تطبيقات تانية بتشارك نفس Redis.

!
لو بتشارك سيرفر Redis بين أكتر من موقع، الـ flush هيمسح كاش المواقع التانية كمان. استخدم clean افتراضياً، وflush بس لما تكون متأكد.
التشخيص السريع
فحص الوضعbash
# هل الصفحة بتتكاش؟
curl -I https://store.com/page | grep -i "x-magento-cache"

# حجم الكاش في Redis
redis-cli -n 0 INFO keyspace
redis-cli -n 1 INFO keyspace

# دوّر على blocks معطّلة للكاش (السبب الشائع)
grep -rn 'cacheable="false"' app/code/ vendor/ \
  --include="*.xml"
i
الأمر الأخير هو أول حاجة تعملها لو الـ FPC مش شغّال. في الغالب هتلاقي cacheable="false" في موديول خارجي، وهو ده السبب.
التطوير — وفّر على نفسك
إعداد بيئة التطويرbash
# عطّل الأنواع اللي بتعطّلك أثناء التطوير
bin/magento cache:disable block_html full_page layout

# سيبهم مفعّلين — مسحهم مكلّف وبطيء
#   config, db_ddl, compiled_config
!
متنساش تفعّلهم تاني قبل ما تختبر الأداء أو تنشر. تطوير بكاش معطّل شيء، وقياس أداء بكاش معطّل نتيجته بلا معنى.
• • •
13

الفخاخ الشائعة

اقرا دي حتى لو مش هتقرا حاجة تانية.

  • cacheable="false" على block صغير — بيلغي كاش الصفحة كلها. أخطر فخ على الإطلاق.
  • block بيعرض منتجات من غير IdentityInterface — أسعار قديمة بتفضل ظاهرة، بصمت.
  • نسيان متغيّر في getCacheKeyInfo — عميل بيشوف نسخة عميل تاني.
  • نفس رقم Redis database لبيئتين — البيئات بتدهس كاش بعض، والأعراض عشوائية.
  • flush بدل clean على production — بيمسح كاش مواقع تانية بتشارك Redis.
  • مسح الكاش كله وقت الذروة — كل الطلبات تضرب PHP مرة واحدة والسيرفر ممكن يقع.
  • if ($cached) مع قيم ممكن تكون صفر — الكاش شغّال ومالوش فايدة.
  • بيانات حسّاسة في private content sections — بتتخزّن في متصفح الزائر.
  • قياس الأداء والكاش معطّل — نتيجة بلا معنى.
  • الاعتماد على مدة انتهاء طويلة بدل الـ tags — بيانات قديمة لساعات بدل ما تتمسح فوراً.
i
لو واجهت "التعديل مش ظاهر"، اسأل بالترتيب: نافذة خاصة؟ (كاش متصفح) ← مسحت الكاش؟Varnish اتمسح؟الـ static content اتعمله deploy؟ ← وبعدين بس افتح الكود.
• • •
14

أسئلة الإنترفيو

من أكتر المواضيع اللي بتتسأل فيها.

Q1إيه الفرق بين Varnish والـ built-in FPC؟
الإجابة: الـ built-in بيخزّن الصفحات جوّه Magento، فـ PHP لازم يشتغل عشان يرد. Varnish سيرفر مستقل قدّام Magento، فبيرد قبل ما PHP يتنده أصلاً — أسرع بمراحل. Varnish هو المعيار في production.
Q2إزاي Magento بيعرف يمسح أنهي صفحات لما منتج يتغيّر؟
الإجابة: بنظام الـ cache tags. كل صفحة بتتخزّن بلافتات بتقول إيه اللي فيها (cat_p_42 مثلاً). لما المنتج يتغيّر، بيتمسح كل اللي عليه اللافتة دي بس. والـ blocks بتعلن اعتمادياتها بـ IdentityInterface.
Q3إزاي تعرض بيانات خاصة بالعميل في صفحة متكاشة؟
الإجابة: بالـ private content. الصفحة تفضل متكاشة والأجزاء الشخصية بتتحمّل بـ JavaScript من الـ customer-data sections. الحل الغلط هو cacheable="false" لأنه بيلغي كاش الصفحة كلها.
Q4الموقع بطيء والكاش مفعّل — تبدأ منين؟
الإجابة: أتأكد إن الصفحات فعلاً بترجع HIT بفحص الـ headers. لو كلها MISS، أدوّر على cacheable="false" في الموديولات — ده أشهر سبب. وبعدها أشوف الـ Redis مظبوط والـ indexers على schedule.
Q5الفرق بين cache:clean و cache:flush؟
الإجابة: clean بيمسح إدخالات Magento المعلّمة بلافتاته بس. flush بيفضّي المخزن كله حتى بيانات تطبيقات تانية بتشارك نفس Redis. الافتراضي يكون clean.
Q6إزاي تعمل نوع كاش مخصّص؟
الإجابة: تعرّفه في etc/cache.xml باسم ولابل، وتعمل كلاس بيورّث من TagScope بمعرّف ولافتة. بعدها بيظهر في لوحة الأدمن ويتمسح مستقلاً. الفايدة إن فريق التشغيل يقدر يمسحه من غير سطر أوامر.

Magento Cache ✓

دلوقتي فاهم الكاش من الصفر: الطبقات وترتيبها، الأنواع الداخلية، الـ FPC و Varnish، المحتوى الشخصي، الـ tags والإبطال، والتحكم من كودك.

الخلاصة: كل ما الطلب وقف عند طبقة أعلى كان أسرع، والـ tags هي اللي بتخلّي المسح انتقائي بدل شامل، وcacheable="false" بيلغي الصفحة كلها مش الـ block.

// magento 2 · cache · fpc · varnish · tags

خلّصت الدرس؟علّمه عشان تتابع تقدّمك في الكورس.

كاتب الدرس

Abdulrahman Masoud

عندك سؤال على الدرس أو محتاج مساعدة في مشروع Magento؟ كلّمني.

أسئلة شائعة

إيه الفرق بين Varnish والـ Built-in Full Page Cache في Magento 2؟

الـ Built-in بيخزّن الصفحات جوّه Magento في Redis أو ملفات، فلازم PHP يشتغل ويحمّل Magento عشان يرد حتى لو الصفحة موجودة. Varnish سيرفر مستقل قدّام Magento بيرد قبل ما PHP يتنده أصلًا، فهو أسرع بمراحل. الـ Built-in للتطوير والمشاريع الصغيرة، و Varnish هو المعيار في production ومتاح من Stores › Configuration › Advanced › System › Full Page Cache.

ليه cacheable="false" بيبطّئ Magento؟

لأنه مابيلغيش كاش الـ block ده بس، بيلغي كاش الصفحة كلها. block صغير في الـ footer معلّم cacheable="false" بيخلّي كل صفحات الموقع MISS في الـ Full Page Cache. لو الصفحات كلها MISS، ابدأ بـ grep -rn 'cacheable="false"' app/code/ vendor/، وغالبًا هتلاقيه في موديول خارجي. البديل الصح للمحتوى المتغيّر هو الـ private content.

إزاي Magento 2 بيعرف يمسح أنهي صفحات لما منتج يتغيّر؟

بنظام الـ cache tags. كل صفحة بتتخزّن مع لافتات بتقول فيها إيه، زي cat_p_42 للمنتج و cat_c_15 للقسم. لما المنتج يتحفظ، Magento بيمسح كل اللي عليه اللافتة دي بس. الـ block بتاعك لازم يطبّق IdentityInterface ويرجّع identities المنتجات اللي بيعرضها من getIdentities()، وإلا الصفحة هتفضل تعرض أسعار قديمة.

إزاي أعرض اسم العميل أو السلة في صفحة متكاشة؟

بالـ private content. الصفحة بتفضل متكاشة ونسخة واحدة للكل، والأجزاء الشخصية بتتحمّل بـ JavaScript من الـ customer-data sections. بتعمل كلاس بيطبّق SectionSourceInterface وتسجّله في etc/frontend/di.xml في sectionSourceMap، وتربط الـ actions اللي بتغيّره في etc/frontend/sections.xml. البيانات دي بتتخزّن في localStorage، فمتحطش فيها حاجة حسّاسة.

إيه الفرق بين cache:clean و cache:flush في Magento 2؟

cache:clean بيمسح إدخالات Magento المعلّمة بالـ tags بتاعته بس، وتقدر تحدّد أنواع معيّنة زي config و layout. cache:flush بيفضّي مخزن الكاش كله، حتى بيانات تطبيقات تانية بتشارك نفس Redis. الافتراضي في production يكون clean لأنواع محدّدة، لأن مسح كل حاجة وقت الذروة بيخلّي كل الطلبات تضرب PHP مرة واحدة.

إزاي أخزّن نتيجة عملية تقيلة في كاش Magento 2 من كودي؟

احقن Magento\Framework\App\CacheInterface و SerializerInterface. جرّب load($key) الأول، ولو رجّع false احسب البيانات واحفظها بـ save($serialized, $key, [$tag], $lifetime). الـ tag بيخلّيك تمسح البيانات دي بس بـ clean([$tag]). خلّي بالك إن load بيرجّع false لما مفيش قيمة، فقارن بـ !== false لو القيمة ممكن تكون صفر أو نص فاضي.

إزاي أعمل نوع كاش مخصّص يظهر في Cache Management؟

عرّفه في etc/cache.xml بـ name و label و description و instance، واعمل كلاس بيورّث من Magento\Framework\Cache\Frontend\Decorator\TagScope فيه TYPE_IDENTIFIER مطابق للاسم و CACHE_TAG، وبيمرّر $cacheFrontendPool->get(self::TYPE_IDENTIFIER) للـ parent. بعد setup:upgrade و cache:enable vendor_catalog_feed بيظهر في الأدمن وفريق التشغيل يمسحه لوحده من غير سطر أوامر.