Fundamentalsبالتفصيل.
شرح عميق لكل مفهوم أساسي في Magento 2 backend — مش بس "إيه هو"، لكن كمان "ليه موجود" و"إزاي بيشتغل من جوّه"، مع أمثلة كود عملية. كل مفهوم في كارت تفتحه لوحده.
Magento 2 مبني على modules مستقلة بتتكلّم مع بعض بطرق محددة. الـ Dependency Injection بيجيب الـ dependencies للـ class عن طريق الـ constructor وبيتظبط في di.xml، والـ Plugins بتعدّل سلوك public methods، والـ Observers بتتفاعل مع events. بيانات المنتجات بتتخزّن بنظام EAV، والـ Service Contracts بتقدّم interfaces ثابتة بين الـ modules.
إيه هو Magento أصلًا؟
قبل التفاصيل، لازم تفهم طبيعة الـ platform اللي بتشتغل عليها.
Magento مش مجرد تطبيق e-commerce جاهز. هو في الحقيقة framework كامل مبني فوق مكوّنات من Symfony وLaminas (Zend)، متخصّص في الـ commerce. اللي بتشوفه كـ"متجر" هو مجموعة modules شغّالة فوق الـ framework ده.
لأنك كـ backend developer مش بتعدّل "متجر"، أنت بتوسّع framework. كل حاجة قابلة للتخصيص عن طريق نقاط توسعة رسمية (plugins, events, preferences) من غير ما تلمس الـ core. ده اللي بيخلّي الـ upgrades ممكنة.
الكود بيتقسّم لطبقتين: الـ Framework (الأدوات العامة: DI, cache, DB abstraction) والـ Modules (المنطق التجاري: Catalog, Checkout, Customer). أنت بتضيف modules بتاعتك جنبهم.
Magento Open Source مجاني ومفتوح المصدر. Adobe Commerce (كان اسمه Magento Commerce / Enterprise) نسخة مدفوعة فيها مميزات إضافية زي B2B، Page Builder المتقدم، customer segmentation، وRabbitMQ built-in.
عشان يعرفوا خبرتك بأي نسخة. المفاهيم الأساسية (DI, plugins, EAV) واحدة في الاتنين، بس بعض المميزات موجودة في Adobe Commerce بس.
فلسفة الـ Modularity
الفكرة اللي بُني عليها Magento كله. لو فهمتها، كل حاجة بعدها بتبقى منطقية.
كل وظيفة في Magento معزولة في module مستقل. الـ Catalog module، الـ Checkout module، الـ Customer module... كلهم قطع منفصلة تقدر تتفعّل أو تتعطّل. حتى وظايفك الجديدة بتبقى module.
عشان الفصل والاستقلالية: تقدر تعطّل feature مش محتاجها، تستبدل module بآخر، وتوسّع بدون ما تكسر باقي النظام. وكمان بيسمح لفرق مختلفة تشتغل على modules مختلفة في نفس الوقت.
مش بتنادي بعض مباشرة. بتتواصل عن طريق interfaces (service contracts) وevents. ده بيخلّي كل module معتمد على "عقد" مش على تفاصيل داخلية، فتقدر تغيّر الداخل بدون ما تكسر اللي بيستخدمه.
Magento مبني بشكل صارم على مبادئ SOLID. مش مجرد نظرية — دي بتفسّر ليه الكود متقسّم بالشكل ده.
- S — Single Responsibility: الـ Model بيمسك الـ data، الـ ResourceModel بيكلّم الـ DB، الـ Block للـ view. كل واحد مسؤولية واحدة.
- O — Open/Closed: مفتوح للتوسعة (plugins, events)، مقفول للتعديل (متلمسش الـ core).
- L — Liskov Substitution: أي preference بيستبدل class لازم يحترم نفس الـ interface.
- I — Interface Segregation: interfaces صغيرة مركّزة زي ProductRepositoryInterface بدل واحد ضخم.
- D — Dependency Inversion: الكود بيعتمد على interfaces مش على classes، والـ DI بيوصّلهم.
Dependency Injection
أهم مفهوم في الـ backend. خد وقتك فيه — لو فهمته صح، فهمت 60% من Magento.
الـ Dependency هي أي object الـ class بتاعك محتاجه عشان يشتغل (زي logger أو repository). الـ Injection معناها إن حد تاني بيجهّزلك الـ objects دي ويسلّمهالك، بدل ما تعملها بنفسك بـ new.
لو كتبت new Logger() جوّه الكلاس، بتبقى مربوط بالـ Logger ده تحديدًا للأبد. لو عايز تغيّره، أو تعمله mock في الـ testing، أو module تاني عايز يعدّله — مش هتقدر. الـ DI بيفكّ الارتباط ده: أنت بتقول "أنا محتاج logger" والنظام بيقرر أنهي واحد يديك.
بتطلب الـ dependencies في الـ constructor كـ type-hints على interfaces. Magento بيقرا الـ constructor، يجهّز كل حاجة محتاجها، ويبنيلك الـ object جاهز.
class OrderProcessor { public function process() { // Hard-coded dependency — bad $logger = new Logger(); $logger->info('Processing...'); } }
class OrderProcessor { // Ask for the interface — Magento injects it public function __construct( private readonly LoggerInterface $logger ) {} public function process() { $this->logger->info('Processing...'); } }
Object Manager & di.xml
الآلة اللي بتشغّل الـ DI من ورا الكواليس، والملف اللي بتتحكم بيه فيها.
الـ Object Manager هو الـ class المسؤول عن إنشاء كل الـ objects في Magento وحقن الـ dependencies بتاعتهم. لما تطلب object في الـ constructor، هو اللي بيبنيه ويجهّزه.
ممكن تنادي ObjectManager::getInstance()->create(...) يدوي، بس ده ممنوع في الكود العادي لأنه بيخفي الـ dependencies (محدش يعرف الكلاس محتاج إيه بمجرد ما يبص على الـ constructor)، وبيكسّر الـ testing، وبيتخطّى الـ plugins.
في أماكن محدودة بس: داخل الـ factories، الـ tests، وبعض نقاط الـ bootstrap. غير كده، دايمًا استخدم constructor injection.
الـ di.xml هو الملف اللي بتقول فيه للـ Object Manager "إزاي تبني الحاجات": أنهي class يمثّل أنهي interface، أنهي plugins تشتغل، وأنهي arguments تتحقن.
1) preference — يربط interface بـ class. 2) type / plugin — يضيف interceptor. 3) virtualType — يعمل نسخة مخصّصة من class. 4) arguments — يحقن قيم أو objects محددة.
<config> <!-- 1. interface → class --> <preference for="Vendor\Module\Api\NotifierInterface" type="Vendor\Module\Model\Notifier"/> <!-- 2. inject a value into a class --> <type name="Vendor\Module\Model\Notifier"> <arguments> <argument name="maxRetries" xsi:type="number">3</argument> </arguments> </type> </config>
Factories & Proxies
حلّان ذكيان لمشكلتين في الـ DI: إنشاء نُسخ متعددة، وتأجيل التحميل.
الـ constructor injection بيديك نسخة واحدة مشتركة (singleton). بس أحيانًا محتاج تعمل objects جديدة كتير — زي لما تعمل loop وتنشئ product جديد كل مرة. النسخة المشتركة مش هتنفع هنا.
الـ Factory هو class مهمته الوحيدة إنه ينشئ نُسخ جديدة. أنت مش بتكتبه — Magento بيولّده أوتوماتيك في generated/. بس بتحط SomethingFactory في الـ constructor وتنادي ->create().
public function __construct( // This class is auto-generated private readonly ProductFactory $productFactory ) {} public function createProducts() { foreach ($data as $row) { // Fresh instance each time $product = $this->productFactory->create(); $product->setData($row); } }
بعض الـ dependencies تقيلة جدًا في الإنشاء. لو حطيتها في الـ constructor، هتتبنى كل مرة حتى لو الكود مش هيستخدمها في الغالب — ده هدر في الأداء.
الـ Proxy هو غلاف خفيف بياخد مكان الـ dependency التقيلة. بيتبنى بسرعة، ومبيبنيش الـ object الحقيقي إلا أول ما تنادي method منه فعلًا (lazy loading).
<type name="Vendor\Module\Model\LightService"> <arguments> <!-- inject a Proxy instead of the heavy class --> <argument name="heavy" xsi:type="object"> Vendor\Module\Model\HeavyService\Proxy </argument> </arguments> </type>
Plugins (Interceptors)
الطريقة الأساسية لتعديل سلوك Magento بأمان. افهمها بعمق لأنها بتتسأل في كل إنترفيو.
الـ Plugin (أو Interceptor) بيسمحلك تتدخّل قبل، بعد، أو حوالين أي public method في أي class — من غير ما تعدّل الكلاس نفسه.
لما تعرّف plugin، Magento بيولّد class جديد اسمه Interceptor بيورّث من الكلاس الأصلي وبيلفّ الـ methods بتاعته. الـ Object Manager بيديك الـ Interceptor ده بدل الأصلي — فأي نداء بيمرّ على الـ plugins بتاعتك الأول. عشان كده لازم يكون الـ object متبني عن طريق الـ DI.
بيشتغل قبل الـ method الأصلية، وبيقدر يعدّل الـ arguments اللي داخلة ليها. بيرجّع الـ arguments المعدّلة (أو مبيرجّعش حاجة لو مش عايز يغيّرها).
// Original: setName(string $name) public function beforeSetName( Product $subject, string $name ) { // Modify the incoming argument return [trim($name)]; }
بيشتغل بعد الـ method، وبياخد الـ result اللي رجّعته ويقدر يعدّله. ده الأكثر استخدامًا والأأمن لأنه مبيتدخّلش في تنفيذ الـ method نفسها.
// Original: getName(): string public function afterGetName( Product $subject, $result // the returned value ) { return strtoupper($result); }
بيلفّ الـ method بالكامل. بياخد $proceed (دالة بتشغّل الأصلية) وأنت بتقرر تناديها إمتى، أو تعدّل قبلها وبعدها، أو متناديهاش خالص.
لأنه الأتقل أداءً، ولو نسيت تنادي $proceed() بتكسّر باقي الـ plugins بصمت. استخدمه بس لو محتاج تمنع تنفيذ الأصلية بشرط معيّن.
public function aroundSave( Product $subject, callable $proceed, ...$args ) { if ($this->isBlocked()) { return null; // skip original entirely } return $proceed(...$args); // run original }
Events & Observers
نظام آخر للتوسعة، بمنطق مختلف عن الـ plugins. اعرف الفرق بعمق.
Magento بيطلق (dispatch) events في لحظات مهمة (order placed, customer saved). أي module يقدر يسجّل observer يسمع الـ event ده ويتفاعل معاه.
الـ plugin بيتدخّل في method محددة. الـ event بيعلن عن لحظة حصلت بدون ما يهمه مين بيسمع. ده نمط publish/subscribe — الكود اللي بيطلق الـ event مش عارف ولا مهتم مين هيتفاعل.
بتعرّف الـ observer في events.xml، والـ observer class بينفّذ execute() اللي بياخد الـ Observer object وياخد منه البيانات.
<event name="sales_order_place_after"> <observer name="vendor_notify" instance="Vendor\Module\Observer\NotifyOnOrder"/> </event>
- محتاج تغيّر input/output بتاع method؟ → Plugin
- محتاج تتفاعل مع حدث (تبعت إيميل، تحدّث مخزون)؟ → Observer
- محتاج تمنع method من الاشتغال؟ → around Plugin
- عايز شغلك يفضل معزول عن تفاصيل الـ method؟ → Observer
الـ Data Layer
إزاي Magento بيتعامل مع البيانات — الثلاثي المقدّس: Model, ResourceModel, Collection.
Magento بيفصل التعامل مع البيانات لتلات مسؤوليات منفصلة، تطبيقًا لمبدأ Single Responsibility.
- Model — بيمثّل entity واحدة (منتج واحد مثلًا). بيمسك البيانات والمنطق التجاري بس مبيلمسش الـ DB.
- ResourceModel — الطبقة الوحيدة اللي بتكلّم الـ database فعليًا (load, save, delete). بتترجم بين الـ Model والـ SQL.
- Collection — بتتعامل مع مجموعة entities. بتبني query بـ filters و sorting وبترجّع نتائج كتير.
عشان لو غيّرت طريقة التخزين (DB مختلفة مثلًا)، بتعدّل الـ ResourceModel بس. الـ Model والمنطق التجاري يفضلوا زي ما هما. الفصل بيحمي المنطق من تفاصيل التخزين.
// Model delegates DB work to its ResourceModel class Log extends AbstractModel { protected function _construct() { // bind model to its resource $this->_init(ResourceModel\Log::class); } }
الـ Collection مبتنفّذش الـ query وقت ما تبنيها. بتفضل بتضيف شروط لحد ما تحتاج البيانات فعلًا (أول iteration أو getItems()) — ساعتها بتتنفّذ مرة واحدة.
عشان تقدر تبني الـ query على مراحل بدون ما تعمل عدة استعلامات، وعشان متعملش query لو مش هتستخدم النتيجة. بس ده كمان فخ: لو عملت count() و loop، ممكن تعمل استعلامين من غير ما تحس.
نظام الـ EAV
أكتر مفهوم بيربك المبتدئين، وأكتر سؤال متكرر. خد وقتك تفهمه بعمق.
EAV = Entity – Attribute – Value. بدل جدول واحد عريض فيه عمود لكل خاصية، البيانات بتتوزّع: جدول للـ entities، جدول بيعرّف الـ attributes، وجداول بتخزّن الـ values حسب نوعها (varchar, int, decimal, text, datetime).
المنتج في Magento ممكن يكون ليه مئات الخصائص، والـ admin بيقدر يضيف خصائص جديدة من اللوحة في أي وقت. لو كان جدول عادي، كل خاصية جديدة هتحتاج ALTER TABLE — ده مستحيل عمليًا. الـ EAV بيسمح بخصائص لا نهائية بدون تعديل الـ schema.
القراءة بتحتاج JOINs كتير عشان تجمّع بيانات entity واحدة من كل جداول الـ values. ده بيخلّيه أبطأ من الجدول المسطّح — وعشان كده Magento بيعمل flat / indexed tables للـ storefront.
-- entity catalog_product_entity (entity_id, sku) -- values split by type ..._entity_varchar (entity_id, attribute_id, value) ..._entity_int (entity_id, attribute_id, value) ..._entity_decimal (entity_id, attribute_id, value) -- attribute definitions eav_attribute (attribute_id, attribute_code, ...)
Service Contracts
الطبقة اللي بتخلّي Magento قابل للتطوير والصمود مع الـ upgrades. المفهوم اللي بيميّز الـ senior.
الـ Service Contract هو مجموعة interfaces بتعرّف الـ "عقد" العام للتعامل مع entity. بتتكوّن من نوعين: Data interfaces (شكل البيانات: getters/setters) وService interfaces / Repositories (العمليات: save, getById, delete, getList).
لو كل module بيتعامل مع البيانات عن طريق الـ Models مباشرة، أي تغيير داخلي في الـ Model هيكسّر كل اللي بيستخدمه. الـ Service Contract بيوفّر واجهة ثابتة — التفاصيل الداخلية تتغيّر، بس العقد يفضل زي ما هو، فالكود اللي بيعتمد عليه مبيتكسرش مع الـ upgrades.
الـ REST والـ GraphQL APIs بيتبنوا أوتوماتيك فوق الـ service contracts. أول ما تعرّف repository صح، تقدر تعرّضه كـ API بمجرد config. عقد واحد بيخدم الكود الداخلي والـ APIs الخارجية.
interface LogRepositoryInterface { public function save(LogInterface $log): LogInterface; public function getById(int $id): LogInterface; public function delete(LogInterface $log): bool; public function getList( SearchCriteriaInterface $criteria ): LogSearchResultsInterface; }
الأساسيات بالتفصيل ✓
دلوقتي مش بس عارف "إيه" المفاهيم دي، لكن كمان "ليه" موجودة و"إزاي" بتشتغل من جوّه. ده الفرق بين حد بيحفظ وحد بيفهم — والمُقابِل بيحس بالفرق ده بسهولة.
نصيحة: اقرا كل كارت، وبعدين اقفله وحاول تشرح المفهوم بصوت عالي بكلماتك. لو عرفت، هو راسخ. لو لأ، ارجع للكارت.
خلّصت الدرس؟علّمه عشان تتابع تقدّمك في الكورس.
أسئلة شائعة
ما الفرق بين Plugin و Observer في Magento 2؟
الـ Plugin بيتدخّل في public method محددة (before أو after أو around) عشان يغيّر الـ input أو الـ output أو يمنع التنفيذ. الـ Observer بيسمع event زي sales_order_place_after ويعمل حاجة إضافية، بنمط publish/subscribe. القاعدة: لو بتغيّر "إيه اللي بيرجع" استخدم Plugin، ولو بتعمل "حاجة إضافية لما يحصل كذا" استخدم Observer.
ليه مينفعش أستخدم ObjectManager مباشرة في Magento 2؟
لأن نداء ObjectManager::getInstance() جوّه الكود بيخفي الـ dependencies، فمحدش يعرف الكلاس محتاج إيه من الـ constructor، وبيصعّب الـ testing. ده من أشهر العلامات الحمرا في الـ code review والإنترفيو، والمسموح بيه بس أماكن محدودة زي الـ factories والـ tests وبعض نقاط الـ bootstrap.
ما الفرق بين Factory و Proxy في Magento 2؟
الـ Factory بيحل مشكلة إنك محتاج نُسخ جديدة كتير، لأن الـ constructor injection بيديك نسخة مشتركة، فبتحط ProductFactory في الـ constructor وتنادي create()، وMagento بيولّده أوتوماتيك في generated/. الـ Proxy غلاف خفيف بتحقنه عن طريق di.xml مكان dependency تقيلة، ومبيبنيش الـ object الحقيقي إلا أول ما تنادي method منه (lazy loading).
إيه أنواع الـ plugins في Magento 2؟
فيه 3 أنواع: before بيعدّل الـ arguments قبل الـ method، وafter بيعدّل الـ result وهو الأشيع والأأمن، وaround بيلفّ الـ method كلها عن طريق $proceed. الـ around الأتقل أداءً، ولو نسيت تنادي $proceed() بتكسّر باقي الـ plugins بصمت، فاستخدمه بس لو محتاج تمنع التنفيذ بشرط.
ليه الـ plugin مش شغّال على method معيّنة في Magento 2؟
الـ plugins بتشتغل عن طريق class اسمه Interceptor بيتولّد وبيورّث من الكلاس الأصلي، والـ Object Manager بيديهولك بداله. عشان كده مبتشتغلش على objects متعملة بـ new، ولا على methods من نوع private أو final أو static لأن الوراثة مبتقدرش تلفّهم.
يعني إيه EAV في Magento 2؟
EAV اختصار Entity–Attribute–Value: جدول للـ entities، وجدول eav_attribute بيعرّف الخصائص، وجداول للقيم حسب نوعها (varchar وint وdecimal وغيرهم). ده بيسمح للأدمن يضيف attributes جديدة من غير ALTER TABLE، والثمن إن القراءة محتاجة JOINs كتير. الـ Product والـ Category والـ Customer والـ Customer Address شغّالين EAV، والـ Orders والـ Quotes والـ Invoices جداول flat.
ليه أستخدم repository بدل $model->load() في Magento 2؟
الـ load() بقى deprecated للاستخدام العام وبيربطك بالتفاصيل الداخلية للـ Model. الـ repository جزء من الـ Service Contract وبيديك واجهة ثابتة وtyped وسهلة في الـ testing ومفصولة عن الـ ORM، وتقدر تعرّضها كـ REST API عن طريق config، فكودك مبيتكسرش مع الـ upgrades.