Scenariosأسئلة وإجابات.
سيناريوهات أسئلة حقيقية زي اللي بتتسأل في الإنترفيو — بس مش مجرد إجابة. كل سؤال مشروح بـ: إزاي تفكّر فيه، الإجابة النموذجية، ليه كده، والفخاخ اللي تتجنّبها.
الدرس فيه 14 سيناريو إنترفيو Magento 2 من السهل للصعب، زي تعديل سعر منتج، والاختيار بين Plugin و Observer، والموقع البطيء، وتعارض الـ modules، ومشكلة N+1. لكل سيناريو طريقة التفكير خطوة بخطوة، والإجابة النموذجية، وليه هي صح، والفخ اللي المفروض تتجنّبه وإنت بتجاوب.
السؤال المفتاحي: هل أنا بغيّر قيمة راجعة من method؟ آه — سعر المنتج بيرجع من method زي getPrice. يبقى الأداة الصح هي الـ plugin، مش observer.
أعمل after plugin على method السعر (زي getFinalPrice)، أشيك جوّه الـ plugin هل المنتج في الـ category دي، ولو آه أعدّل الـ result وأضيف 10%.
لأني محتاج أغيّر القيمة اللي بترجع من method محددة. الـ observer بيتفاعل مع حدث بس، مبيقدرش يعدّل قيمة راجعة. ده الفرق الجوهري.
لو قلت "أعدّل السعر في الـ DB" — غلط. ده بيغيّر السعر الأصلي نهائيًا. المطلوب تعديل وقت العرض بس، عشان كده الـ plugin على method العرض هو الصح.
السؤال ده بيقيس فهمك للفرق الجوهري، مش الحفظ. المفتاح: هل أنا بتدخّل في method محددة، ولا بتفاعل مع لحظة حصلت؟
- Plugin — لما أحتاج أغيّر الـ input أو الـ output بتاع public method محددة.
- Observer — لما أحتاج أتفاعل مع حدث (order placed, customer saved) وأعمل حاجة إضافية.
Observer = "لما X يحصل، اعمل كمان Y". Plugin = "غيّر إزاي الـ method دي بالظبط بتشتغل". الجملة دي بتوضّح إنك فاهم مش حافظ.
متقولش "الاتنين نفس الحاجة" أو "بستخدم اللي أعرفه". لو المُقابِل حسّ إنك مش فاهم الفرق، دي علامة سلبية.
سؤال تشخيص — المُقابِل عايز يشوف منهجيتك، مش إجابة واحدة. ابدأ من الأعم (config) للأخص (كود). اتكلم بترتيب منطقي.
- أتأكد الـ deploy mode على production والـ caches شغّالة.
- أشوف لو فيه indexers على "update on save" بدل schedule.
- أستخدم profiler (Blackfire / New Relic / Xdebug) عشان ألاقي الـ bottleneck.
- أراجع الـ custom modules الأخيرة — around plugins كتير؟ collections تقيلة؟
- أشوف الـ slow query log في الـ DB لو المشكلة في الاستعلامات.
لأنك بتبدأ بأرخص وأسرع الحلول (config) قبل ما تغوص في الكود. ده بيوفّر وقت وبيوضّح إنك منظّم.
متقفزش على "المشكلة في الكود" على طول. لو الـ cache مقفول أو الـ mode مش production، دي أغلب الأسباب — والحل أبسط بكتير.
السؤال بيختبر فهمك لخطورة الـ preference. المفتاح: الـ preference بيستبدل الـ class بالكامل، فمينفعش اتنين يكسبوا.
الـ preference اللي بيتحمّل في module بترتيب لاحق (حسب الـ sequence) هو اللي بيكسب — والتاني بيتجاهل تمامًا. الحل الصح: متستخدمش preference أصلًا للحالة دي، استخدم plugins — لأن كذا plugin يقدروا يشتغلوا مع بعض من غير تعارض.
ده بيوضّح إنك فاهم ليه الـ plugin أأمن من الـ preference. الـ preference حصري (واحد بس)، الـ plugin تشاركي (كتير مع بعض).
لو قلت "أعمل preference للـ preference" — ده بيعمّق المشكلة. الحل مش تراكم preferences، الحل تتجنّبها من الأساس.
القيد المهم: "من غير ما نبطّئ الـ checkout". يعني ممنوع أعمل الـ API call المتزامن وقت الـ checkout. لازم حاجة async.
أعمل Observer على event checkout_submit_all_after، بس بدل ما يبعت للـ ERP مباشرة، يحط message في Message Queue (RabbitMQ). وconsumer بياخد الرسالة ويبعتها للـ ERP في الخلفية.
لأن الـ API call ممكن يكون بطيء أو يفشل. لو عملته وقت الـ checkout، العميل هيستنى — أو أسوأ، لو الـ ERP وقع، الـ checkout هيفشل. الـ Queue بيفصل العمليتين.
لو قلت "أعمل الـ API call في الـ observer مباشرة" — ده بيبطّئ الـ checkout ويربطه بتوفّر الـ ERP. الفكرة كلها إنك تفصل العملية.
الـ plugins ليها قيود واضحة. راجعهم واحد واحد. غالبًا السبب واحد من ليستة معروفة.
- الـ method اللي بتعملها plugin مش public (private/protected مبتشتغلش).
- الـ method أو الـ class final.
- الـ object متعمل بـ new مباشرة مش عن طريق الـ DI.
- الـ method بتتنادى من جوّه نفس الـ class ($this->method()).
- نسيت setup:di:compile في production.
- خطأ في اسم الـ plugin method (لازم before/after/around + MethodName).
ابدأ بالأكثر شيوعًا (public/compile) قبل الأندر. ده بيوضّح إنك بتشخّص بكفاءة.
لو نسيت قيد الـ $this->method() — ده أكتر سبب بيتنسى. الـ plugin مبيشتغلش على النداءات الداخلية جوّه نفس الـ class.
المنتج entity من نوع EAV. يعني مش هضيف عمود في جدول — هضيف attribute عن طريق الـ EAV setup.
أعمل Data Patch يستخدم EavSetup عشان يضيف الـ attribute بـ addAttribute()، محدّد فيه النوع، الـ input، والـ group.
$eavSetup->addAttribute( Product::ENTITY, 'custom_field', [ 'type' => 'varchar', 'label' => 'Custom Field', 'input' => 'text', 'required' => false, ] );
لأنه بيتنفّذ مرة واحدة وبيتسجّل، فمش هيضيف الـ attribute مرتين. وده الأسلوب الحديث بدل الـ InstallData القديم.
لو قلت "أضيف عمود في db_schema.xml" — غلط للمنتج، لأنه EAV. الـ attributes بتتضاف عن طريق EavSetup مش schema مباشر.
الـ checkout مبني على Knockout.js + REST APIs. البطء ممكن يكون frontend (JS bundles) أو backend (الـ APIs اللي بتتنادى).
- أفعّل JS bundling / minification وأستخدم production mode.
- أراجع الـ custom plugins على quote/cart methods — دي بتتنادى كتير في الـ checkout.
- أشوف الـ API calls اللي الـ checkout بيعملها (network tab) وألاقي البطيء منها.
- أتأكد إن مفيش collections تقيلة بتتحمّل مع كل estimate/totals.
- أستخدم Varnish للصفحات، وأتأكد الـ FPC شغّال.
لأن methods الـ quote/totals بتتنادى عشرات المرات في الـ checkout الواحد. أي plugin تقيل عليها بيتضاعف تأثيره.
لو ركّزت على الـ frontend بس ونسيت الـ backend APIs — أو العكس. الـ checkout مشكلته بتكون في الاتنين، لازم تفحصهم مع بعض.
على عكس الـ preference، الـ plugins بتشتغل كلها — بس الترتيب بيفرق. المفتاح: الـ sortOrder.
أتحكم في الترتيب عن طريق sortOrder في تعريف الـ plugin في di.xml. الأقل بيشتغل الأول في الـ before/around، والعكس في الـ after. وأتأكد إن الـ module بتاعي فيه sequence صح لو محتاج يتحمّل بترتيب معيّن.
<plugin name="my_plugin" type="..." sortOrder="20"/>
لأن الحل بيخلّي الاتنين يشتغلوا، مش واحد يلغي التاني. ده جوهر إن الـ plugins تشاركية.
افتكر إن الـ sortOrder بيشتغل عكسي في الـ after plugins. لو خلطت، الترتيب هيطلع مقلوب.
العدد كبير (100 ألف). المفتاح: الأداء والذاكرة. مينفعش أحمّل الكل في الميموري مرة واحدة أو أعمل save عادي في loop.
- أعطّل الـ indexers مؤقتًا وأعملهم reindex في الآخر مرة واحدة.
- أستخدم batch processing — أنقل على دفعات (1000 مثلًا) مش الكل مرة واحدة.
- للحجم الكبير جدًا، أفكّر في direct SQL / bulk insert بدل الـ ORM اللي بطيء.
- أشغّل العملية عن طريق CLI command مش HTTP (عشان الـ timeout والذاكرة).
- أعمل logging للتقدّم عشان أقدر أكمّل لو وقفت.
لأن كل save بيشغّل reindex. مع 100 ألف منتج، ده بيبطّئ العملية بشكل كارثي. الأفضل reindex واحد في الآخر.
لو قلت "loop عادي مع $product->save()" — ده هياخد ساعات وممكن يفصل من الذاكرة. الحجم الكبير محتاج تفكير مختلف.
حماية الـ API مسؤولية على كذا طبقة — مش Magento بس. فكّر في الـ infrastructure والـ application مع بعض.
- Rate limiting على مستوى الـ web server / API gateway (Nginx, Cloudflare).
- استخدام Varnish للـ cache على الـ GET endpoints.
- الـ tokens ليها صلاحية محدودة، وأراقب استخدامها.
- للعمليات التقيلة، أحوّلها لـ async APIs (bulk endpoints) اللي بتشتغل عن طريق queue.
- أراجع الـ ACL عشان كل endpoint متاح للمصرّح له بس.
لأن الـ rate limiting الأكفأ بيكون قبل ما الـ request يوصل لـ Magento أصلًا (على الـ gateway). ده بيوفّر موارد الـ application.
لو ركّزت على حل داخل Magento بس، بتفوّت الطبقة الأهم (الـ infrastructure). المُقابِل بيحب يشوف تفكير معماري.
الفرق بين local و production في Magento معروف: أشهرهم الـ mode والـ compiled DI. فكّر في اللي بيختلف بين البيئتين.
- الـ production فيه di:compile — لو الكود فيه مشكلة DI، بتظهر هنا بس.
- الـ generated code قديم — محتاج setup:di:compile تاني.
- الـ static content مش deployed.
- الـ caches شغّالة في production فبتخفي تغييرات (لو مش عملت clean).
- اختلاف في الـ env.php أو الـ config بين البيئتين.
- الـ file permissions أو مسارات مختلفة.
لأن الـ developer mode بيولّد الكود وقت الطلب، لكن production بيعتمد على المولّد مسبقًا. أخطاء الـ DI بتتكشف في الـ compile.
لو قلت "الكود صح، المشكلة في السيرفر" من غير تفصيل — إجابة ضعيفة. اذكر الأسباب المحددة اللي بتفرق بين البيئتين.
الـ checkout مبني على Knockout.js + UI Components + LayoutProcessor. تعديله مختلف عن باقي الصفحات — محتاج JS مش بس PHP.
- أستخدم LayoutProcessor plugin عشان أضيف الـ field/step في الـ checkout config.
- أعمل Knockout component (JS) للـ UI بتاع الخطوة الجديدة.
- أحفظ البيانات على الـ quote عن طريق extension attribute + API.
- عند الـ order، أنقل البيانات من الـ quote للـ order.
لأن الـ checkout SPA (single page app) بالـ Knockout، مش صفحة PHP عادية. أي إضافة محتاجة تشتغل على الـ frontend JS والـ backend مع بعض.
لو قلت "أضيف الحقل في template عادي" — ده مبيشتغلش في الـ checkout. لازم تتعامل مع نظام الـ UI Components والـ Knockout.
استعلامات كتير في loop = غالبًا N+1 query problem. المشكلة الكلاسيكية دي بتحصل لما تجيب list وبعدين تعمل query لكل عنصر.
ده على الأرجح N+1 problem: بتجيب N عنصر، وبعدين بتعمل query إضافي لكل عنصر (N+1 استعلام). الحل: حمّل البيانات المرتبطة دفعة واحدة عن طريق:
- استخدام addAttributeToSelect عشان تجيب الحقول المطلوبة مع الـ collection.
- عمل join بدل استعلامات منفصلة.
- تحميل الـ related entities بـ getList مع filter واحد بدل loop.
لأن الـ lazy loading سهل يخفي المشكلة. كل $item->getSomething() جوّه loop ممكن يعمل query لوحده من غير ما تحس.
لو حللتها بـ cache بس من غير ما تصلّح الـ N+1 — ده بيخبّي المشكلة مش بيحلها. الحل الجذري تقليل عدد الاستعلامات نفسها.
14 سيناريو ✓
دلوقتي عندك مكتبة من السيناريوهات الحقيقية بطريقة التفكير الكاملة. الأهم اللي تطلع بيه: المُقابِل مش بيقيّم إجابتك بس، بيقيّم إزاي وصلت لها.
نصيحة أخيرة: في الإنترفيو، فكّر بصوت عالي. حتى لو مش متأكد، وضّح خطوات تفكيرك — ده بيبيّن إنك بتحلّل صح، وده أهم من الإجابة النهائية.
خلّصت الدرس؟علّمه عشان تتابع تقدّمك في الكورس.
أسئلة شائعة
إمتى أستخدم Plugin وإمتى Observer في Magento 2؟
استخدم الـ Plugin لما تحتاج تغيّر الـ input أو الـ output بتاع public method محددة، واستخدم الـ Observer لما تحتاج تتفاعل مع حدث حصل زي order placed أو customer saved. القاعدة المختصرة: Observer معناه «لما X يحصل اعمل كمان Y»، وPlugin معناه «غيّر إزاي الـ method دي بالظبط بتشتغل».
ليه الـ plugin مش شغّال في Magento 2؟
غالبًا السبب إن الـ method مش public، أو إن الـ method أو الـ class final، أو إن الـ object اتعمل بـ new مش عن طريق الـ DI. كمان الـ plugin مبيشتغلش لو الـ method بتتنادى من جوّه نفس الـ class بـ $this->method()، أو لو نسيت setup:di:compile في production أو كتبت اسم الـ plugin method غلط.
إيه اللي بيحصل لو اتنين modules عملوا preference لنفس الـ class؟
واحد بس بيكسب، وهو الـ preference اللي في الـ module اللي بيتحمّل بترتيب لاحق حسب الـ sequence، والتاني بيتجاهل تمامًا. الحل الصح إنك تستخدم plugins بدل preference، لأن كذا plugin يقدروا يشتغلوا مع بعض وتتحكّم في ترتيبهم بـ sortOrder في di.xml.
إزاي أبعت بيانات الـ order لـ ERP خارجي من غير ما أبطّئ الـ checkout؟
اعمل Observer على event checkout_submit_all_after، وبدل ما يبعت للـ ERP مباشرة يحط message في Message Queue زي RabbitMQ. الـ consumer بياخد الرسالة ويبعتها في الخلفية، فالـ checkout مايستناش API call بطيء ومايفشلش لو الـ ERP وقع.
إزاي أضيف custom attribute للمنتج في Magento 2؟
المنتج EAV entity، فمش هتضيف عمود في db_schema.xml، هتعمل Data Patch يستخدم EavSetup::addAttribute() وتحدد فيه النوع والـ input والـ label. الـ Data Patch بيتنفّذ مرة واحدة وبيتسجّل، وده الأسلوب الحديث بدل الـ InstallData القديم.
إزاي أنقل 100 ألف منتج لـ Magento 2 بكفاءة؟
شغّل العملية من CLI command مش HTTP، وانقل على دفعات (1000 مثلًا) بدل $product->save() في loop عادي، وللحجم الكبير جدًا فكّر في direct SQL أو bulk insert. عطّل الـ indexers وقت النقل واعمل reindex مرة واحدة في الآخر، وسجّل التقدّم عشان تقدر تكمّل لو العملية وقفت.
إيه هي مشكلة N+1 query في Magento 2 وإزاي أحلها؟
بتحصل لما تجيب list من N عنصر وبعدين تعمل query إضافي لكل عنصر جوّه loop، والـ lazy loading غالبًا بيخبّيها. الحل إنك تحمّل البيانات المرتبطة دفعة واحدة بـ addAttributeToSelect أو join أو getList بفلتر واحد، مش إنك تغطّيها بالـ cache.