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

المرحلة 04 · الاحتراف والإنترفيودرس 20 من 2111 دقيقة قراءةآخر تحديث:

Lesson 20 / 21

Scenariosأسئلة وإجابات.

سيناريوهات أسئلة حقيقية زي اللي بتتسأل في الإنترفيو — بس مش مجرد إجابة. كل سؤال مشروح بـ: إزاي تفكّر فيه، الإجابة النموذجية، ليه كده، والفخاخ اللي تتجنّبها.

● 14 سيناريو تفكير + حل فخاخ شائعة

الدرس فيه 14 سيناريو إنترفيو Magento 2 من السهل للصعب، زي تعديل سعر منتج، والاختيار بين Plugin و Observer، والموقع البطيء، وتعارض الـ modules، ومشكلة N+1. لكل سيناريو طريقة التفكير خطوة بخطوة، والإجابة النموذجية، وليه هي صح، والفخ اللي المفروض تتجنّبه وإنت بتجاوب.

// إزاي تستخدمه اقرا السؤال الأول واقفل عينك وحاول تجاوب بنفسك. بعدين افتح الإجابة وقارن. الأهم مش الإجابة الصح — الأهم طريقة التفكير اللي بتوصلك لها، لأن دي اللي المُقابِل بيقيّمك عليها. الأسئلة مرتّبة من الأسهل للأصعب.
Easy
إزاي تفكّر

السؤال المفتاحي: هل أنا بغيّر قيمة راجعة من method؟ آه — سعر المنتج بيرجع من method زي getPrice. يبقى الأداة الصح هي الـ plugin، مش observer.

الإجابة

أعمل after plugin على method السعر (زي getFinalPrice)، أشيك جوّه الـ plugin هل المنتج في الـ category دي، ولو آه أعدّل الـ result وأضيف 10%.

ليه plugin مش observer

لأني محتاج أغيّر القيمة اللي بترجع من method محددة. الـ observer بيتفاعل مع حدث بس، مبيقدرش يعدّل قيمة راجعة. ده الفرق الجوهري.

الفخ

لو قلت "أعدّل السعر في الـ DB" — غلط. ده بيغيّر السعر الأصلي نهائيًا. المطلوب تعديل وقت العرض بس، عشان كده الـ plugin على method العرض هو الصح.

إزاي تفكّر

السؤال ده بيقيس فهمك للفرق الجوهري، مش الحفظ. المفتاح: هل أنا بتدخّل في method محددة، ولا بتفاعل مع لحظة حصلت؟

الإجابة
  • Plugin — لما أحتاج أغيّر الـ input أو الـ output بتاع public method محددة.
  • Observer — لما أحتاج أتفاعل مع حدث (order placed, customer saved) وأعمل حاجة إضافية.
القاعدة اللي تقولها

Observer = "لما X يحصل، اعمل كمان Y". Plugin = "غيّر إزاي الـ method دي بالظبط بتشتغل". الجملة دي بتوضّح إنك فاهم مش حافظ.

الفخ

متقولش "الاتنين نفس الحاجة" أو "بستخدم اللي أعرفه". لو المُقابِل حسّ إنك مش فاهم الفرق، دي علامة سلبية.

Medium
إزاي تفكّر

سؤال تشخيص — المُقابِل عايز يشوف منهجيتك، مش إجابة واحدة. ابدأ من الأعم (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 في الخلفية.

ليه Queue مش call مباشر

لأن الـ 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.

php
$eavSetup->addAttribute(
    Product::ENTITY,
    'custom_field',
    [
        'type'  => 'varchar',
        'label' => 'Custom Field',
        'input' => 'text',
        'required' => false,
    ]
);
ليه Data Patch

لأنه بيتنفّذ مرة واحدة وبيتسجّل، فمش هيضيف الـ attribute مرتين. وده الأسلوب الحديث بدل الـ InstallData القديم.

الفخ

لو قلت "أضيف عمود في db_schema.xml" — غلط للمنتج، لأنه EAV. الـ attributes بتتضاف عن طريق EavSetup مش schema مباشر.

Hard
إزاي تفكّر

الـ 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 شغّال.
ليه الـ plugins على quote خطيرة

لأن methods الـ quote/totals بتتنادى عشرات المرات في الـ checkout الواحد. أي plugin تقيل عليها بيتضاعف تأثيره.

الفخ

لو ركّزت على الـ frontend بس ونسيت الـ backend APIs — أو العكس. الـ checkout مشكلته بتكون في الاتنين، لازم تفحصهم مع بعض.

إزاي تفكّر

على عكس الـ preference، الـ plugins بتشتغل كلها — بس الترتيب بيفرق. المفتاح: الـ sortOrder.

الإجابة

أتحكم في الترتيب عن طريق sortOrder في تعريف الـ plugin في di.xml. الأقل بيشتغل الأول في الـ before/around، والعكس في الـ after. وأتأكد إن الـ module بتاعي فيه sequence صح لو محتاج يتحمّل بترتيب معيّن.

xml
<plugin name="my_plugin"
  type="..." sortOrder="20"/>
ليه ده أحسن من preference

لأن الحل بيخلّي الاتنين يشتغلوا، مش واحد يلغي التاني. ده جوهر إن الـ plugins تشاركية.

الفخ

افتكر إن الـ sortOrder بيشتغل عكسي في الـ after plugins. لو خلطت، الترتيب هيطلع مقلوب.

إزاي تفكّر

العدد كبير (100 ألف). المفتاح: الأداء والذاكرة. مينفعش أحمّل الكل في الميموري مرة واحدة أو أعمل save عادي في loop.

الإجابة
  • أعطّل الـ indexers مؤقتًا وأعملهم reindex في الآخر مرة واحدة.
  • أستخدم batch processing — أنقل على دفعات (1000 مثلًا) مش الكل مرة واحدة.
  • للحجم الكبير جدًا، أفكّر في direct SQL / bulk insert بدل الـ ORM اللي بطيء.
  • أشغّل العملية عن طريق CLI command مش HTTP (عشان الـ timeout والذاكرة).
  • أعمل logging للتقدّم عشان أقدر أكمّل لو وقفت.
ليه نعطّل الـ indexers

لأن كل 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 متاح للمصرّح له بس.
ليه مش Magento بس

لأن الـ 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 أو مسارات مختلفة.
ليه الـ compile بالذات

لأن الـ 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 سيناريو ✓

دلوقتي عندك مكتبة من السيناريوهات الحقيقية بطريقة التفكير الكاملة. الأهم اللي تطلع بيه: المُقابِل مش بيقيّم إجابتك بس، بيقيّم إزاي وصلت لها.

نصيحة أخيرة: في الإنترفيو، فكّر بصوت عالي. حتى لو مش متأكد، وضّح خطوات تفكيرك — ده بيبيّن إنك بتحلّل صح، وده أهم من الإجابة النهائية.

// magento 2 backend · interview scenarios · 14 questions

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

كاتب الدرس

Abdulrahman Masoud

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

أسئلة شائعة

إمتى أستخدم 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.