Magento Eventsالمرجع الكامل.
الأحداث المهمة في Magento مقسّمة بالمجال — كل حدث بيتطلق امتى وامتى تستخدمه. وقبلها الأنماط اللي بتخلّيك تخمّن اسم أي حدث، وإزاي تكتشف الأحداث بنفسك في مشروعك. وفي الآخر، إزاي تعمل حدث خاص بيك.
الـ Events في Magento 2 نظام إطلاق واستماع: كود بينادي dispatch باسم الحدث وبيانات، والـ Event Manager بينده كل observer مسجّل في events.xml. نص الأحداث بتتولّد من _eventPrefix بنمط زي catalog_product_save_after، فبتقدر تخمّن الاسم، أو تكتشفه في نسختك بـ grep على dispatch. الـ observer للتفاعل مع حدث، والـ plugin لتغيير نتيجة method.
إزاي بيشتغل نظام الأحداث
الفكرة بسيطة، والفهم ده بيخلّي الباقي منطقي.
كود في Magento بينادي "حصل كذا" ومعاه بيانات. الـ Event Manager بيدوّر على كل اللي مسجّل إنه مهتم بالحدث ده، ويناديهم واحد واحد. الكود اللي أطلق الحدث مايعرفش مين بيسمع ولا بيستنى نتيجة.
- الإطلاق — كود بينده dispatch('event_name', $data).
- الاستماع — إنت بتسجّل observer في events.xml ويشتغل تلقائياً.
الأنماط — تخمين أي اسم
نص أحداث Magento بتتولّد تلقائياً بنمط ثابت. افهم النمط تستغنى عن نص القائمة.
أي model بيورّث من AbstractModel بيطلق أحداث تلقائياً عند الحفظ والحذف والتحميل. كل model عنده خاصية اسمها _eventPrefix، والأحداث بتتركّب منها.
# الصيغة العامة {_eventPrefix}_{action}_{timing} # مثال: Product عنده _eventPrefix = catalog_product catalog_product_save_before catalog_product_save_after catalog_product_delete_after catalog_product_load_after # مثال: Order عنده _eventPrefix = sales_order sales_order_save_before sales_order_save_after
افتح الـ model ودوّر على _eventPrefix. مثال: Magento\Catalog\Model\Product فيه protected $_eventPrefix = 'catalog_product';
في الـ observer بتقرا الكيان بـ $observer->getEvent()->getSomething() — بس إيه الـ something؟ ده محدّد بخاصية تانية اسمها _eventObject.
// جوّه Magento\Catalog\Model\Product protected $_eventPrefix = 'catalog_product'; protected $_eventObject = 'product'; // يعني في الـ observer: $product = $observer->getEvent()->getProduct(); // وكمان دايماً متاح الاسم العام: $product = $observer->getEvent()->getDataObject();
اكتشف الأحداث بنفسك
أهم قسم في الدرس — بيغنيك عن أي قائمة محفوظة.
كل إطلاق بيعدي على dispatch()، فالبحث عنه بيديك القائمة الحقيقية في نسختك بالظبط.
# كل الأسماء، مرتّبة وبدون تكرار grep -rhoP "dispatch\(\s*['\"]\K[a-z0-9_]+" \ vendor/magento/ app/code/ | sort -u # أحداث مجال معيّن grep -rhoP "dispatch\(\s*['\"]\K[a-z0-9_]+" \ vendor/magento/module-sales/ | sort -u # الإطلاق مع مكانه — عشان تقرا السياق grep -rn "dispatch('sales_order_place_after'" vendor/magento/
لما تلاقي مكان الإطلاق، اقرا السطر — هو بيقولك اسم كل متغيّر تقدر تقراه في الـ observer.
$this->_eventManager->dispatch( 'checkout_cart_product_add_after', ['quote_item' => $result, 'product' => $product] ); // يعني في الـ observer متاح: // $observer->getEvent()->getQuoteItem() // $observer->getEvent()->getProduct()
لو مش عارف أنهي حدث بيتطلق في سيناريو معيّن، اعمل observer مؤقت على كل الأحداث وسجّلها.
class EventLogger implements ObserverInterface { public function __construct( private readonly LoggerInterface $logger ) {} public function execute(Observer $observer): void { $this->logger->debug( 'EVENT: ' . $observer->getEvent()->getName() ); } }
الكتالوج والمنتجات
أحداث المنتجات والأقسام.
العملاء
التسجيل والدخول والعناوين.
السلة والـ Quote
من الإضافة للسلة لحد قبل التأكيد.
الـ Checkout والأوردرات
أكتر المجالات استخداماً في التكاملات.
الفواتير والشحنات
ما بعد الأوردر.
الـ Controllers والأدمن
أحداث على مستوى الطلب كله.
العرض والـ Layout
أحداث بناء الصفحة.
حدث مخصّص من الصفر
إزاي تطلق حدث بتاعك عشان غيرك يقدر يمدّد كودك.
عشان موديولك يبقى قابل للتمديد. لو موديولك بيعمل عملية مهمة، إطلاق حدث بيخلّي أي حد (إنت أو موديول تاني) يضيف سلوك من غير ما يعدّل كودك — نفس المبدأ اللي Magento نفسه مبني عليه.
namespace Vendor\Module\Model; use Magento\Framework\Event\ManagerInterface; class TicketProcessor { public function __construct( private readonly ManagerInterface $eventManager ) {} public function process(Ticket $ticket): void { // حدث قبل — يسمح بالتعديل أو المنع $this->eventManager->dispatch( 'vendor_module_ticket_process_before', ['ticket' => $ticket] ); $this->doWork($ticket); // حدث بعد — مع النتيجة $this->eventManager->dispatch( 'vendor_module_ticket_process_after', [ 'ticket' => $ticket, 'result' => $ticket->getStatus(), ] ); } }
- حروف صغيرة و underscore بس — مفيش حروف كبيرة أو شرطات.
- ابدأ باسم الـ vendor والموديول — يمنع التصادم مع أحداث تانية.
- اختم بـ _before أو _after — يوضّح التوقيت فوراً.
- اسم واضح للعملية — ticket_process أحسن من handle.
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <event name="vendor_module_ticket_process_after"> <observer name="vendor_module_notify_on_ticket" instance="Vendor\Module\Observer\NotifyOnTicket"/> </event> </config>
namespace Vendor\Module\Observer; use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; class NotifyOnTicket implements ObserverInterface { public function execute(Observer $observer): void { // الأسماء دي من مصفوفة الـ dispatch $ticket = $observer->getEvent()->getTicket(); $result = $observer->getEvent()->getResult(); if (!$ticket) { return; } // المنطق بتاعك هنا } }
Event ولا Plugin؟
قرار بيتاخد غلط كتير.
Event / Observer
بتتفاعل مع حاجة حصلت. مابترجّعش قيمة، ومابتأثرش على سير العملية.
Plugin
بتتدخّل في نداء method. تقدر تعدّل المدخلات والمخرجات وتمنع التنفيذ.
- عايز تتفاعل مع حاجة حصلت (إشعار، تسجيل، مزامنة).
- الحدث موجود فعلاً وبيديك البيانات اللي محتاجها.
- مش محتاج تغيّر سلوك العملية نفسها.
- عايز كذا موديول يتفاعل مع نفس الحاجة من غير تعارض.
- عايز تغيّر القيمة الراجعة من method.
- عايز تعدّل المدخلات قبل التنفيذ.
- عايز تمنع التنفيذ تحت شرط.
- مفيش حدث مناسب أصلاً.
الفخاخ الشائعة
اقرا دي حتى لو مش هتقرا حاجة تانية.
- منطق تقيل في حدث بيتنده كتير — load_after وcollect_totals وpredispatch بيتندهوا عشرات أو مئات المرات. نداء API واحد هناك بيقتل الصفحة.
- أثر خارجي في _save_after بدل _commit_after — إيميل بيتبعت عن حاجة مااتحفظتش.
- استثناء غير محسوب في observer — بيوقّف العملية الأصلية. لفّ المنطق الثانوي في try/catch وسجّل الخطأ.
- الاعتماد على ترتيب الـ observers — الترتيب مش مضمون. لو محتاج ترتيب، المنطق ده مكانه مش هنا.
- تعديل الكيان في _save_after وحفظه تاني — بيعمل حلقة لا نهائية. عدّل في _save_before.
- observer عام والمطلوب محدد — controller_action_predispatch_checkout_index_index أرخص بمراحل من العام.
- الحدث في المكان الغلط — etc/events.xml بيحمّل في كل السياقات. حدده لو تقدر.
- الاعتماد على اسم حدث من مقال قديم — تحقق من نسختك بـ grep الأول (القسم 03).
public function execute(Observer $observer): void { $entity = $observer->getEvent()->getEntity(); // 1. اخرج بدري لو البيانات مش موجودة if (!$entity || !$entity->getId()) { return; } try { // 2. المنطق $this->doWork($entity); } catch (\Exception $e) { // 3. متوقّفش العملية الأصلية $this->logger->error( 'Observer failed: ' . $e->getMessage() ); } }
Magento Events ✓
دلوقتي عندك الأنماط اللي بتخلّيك تخمّن أي اسم، طريقة اكتشاف الأحداث في نسختك، قوائم بأهم الأحداث في 7 مجالات، وإزاي تعمل حدث خاص بيك.
الخلاصة: افهم النمط بدل ما تحفظ القائمة، تحقق بالـ grep قبل ما تعتمد على أي اسم، وخلّي الـ observers خفيفة وآمنة الفشل.
خلّصت الدرس؟علّمه عشان تتابع تقدّمك في الكورس.
أسئلة شائعة
إزاي أعرف اسم الحدث اللي بيتطلق في Magento 2؟
كل إطلاق بيعدّي على dispatch()، فدوّر عليه في الكود: grep -rhoP "dispatch\(\s*['\"]\K[a-z0-9_]+" vendor/magento/ app/code/ | sort -u بيطلّعلك كل الأحداث الموجودة فعلاً في نسختك. ولما تلاقي مكان الإطلاق اقرا المصفوفة اللي بتتبعت معاه، لأن مفاتيحها هي أسماء الـ getters اللي هتستخدمها في الـ observer.
إيه الفرق بين save_after و save_commit_after في Magento؟
_save_after بيتطلق بعد الحفظ بس جوّه الـ transaction، فلو الـ transaction فشلت بعده الحفظ بيترجع. _save_commit_after بيتطلق بعد ما الـ transaction تتأكّد فعلاً. أي أثر خارجي زي إيميل أو نداء API أو رسالة queue مكانه _save_commit_after، وإلا ممكن تبعت إشعار عن حاجة مااتحفظتش.
امتى أستخدم Observer وامتى أستخدم Plugin في Magento 2؟
الـ Observer بيتفاعل مع حاجة حصلت: إشعار أو تسجيل أو مزامنة، ومابيرجّعش قيمة ولا بيغيّر سير العملية. الـ Plugin بيتدخّل في نداء method معيّنة، فيقدر يعدّل المدخلات والمخرجات أو يمنع التنفيذ. لو عايز تغيّر نتيجة أو تمنع عملية استخدم plugin، ولو عايز تتفاعل مع حدث موجود استخدم observer.
إزاي أعمل حدث مخصّص في Magento 2؟
احقن Magento\Framework\Event\ManagerInterface في الكلاس بتاعك ونادي dispatch('vendor_module_ticket_process_after', ['ticket' => $ticket]) بعد العملية. الاسم بحروف صغيرة و underscore، يبدأ باسم الـ vendor والموديول عشان مايتصادمش مع أحداث تانية، وينتهي بـ _before أو _after. اللي عايز يستمع بيسجّل observer في etc/events.xml ويقرا البيانات بـ $observer->getEvent()->getTicket()، فمفاتيح المصفوفة جزء من العقد العام لموديولك.
إيه الفرق بين etc/events.xml و etc/frontend/events.xml؟
etc/events.xml بيتحمّل في كل السياقات: الواجهة والأدمن والـ cron والـ API. etc/frontend/events.xml للواجهة بس، وetc/adminhtml/events.xml للأدمن بس. لو الحدث بيخص العملاء فقط حطه في frontend، لأن الـ observer ساعتها مش بيتحمّل أصلاً في الأدمن ولا في الـ cron، وده تحسين أداء مجاني.
ليه الـ observer بتاعي بيبطّئ الموقع؟
غالبًا لأنه مسجّل على حدث بيتطلق كتير جدًا زي catalog_product_load_after أو sales_quote_collect_totals_after أو controller_action_predispatch أو view_block_abstract_to_html_before. الأحداث دي بتتنده عشرات أو مئات المرات في الصفحة الواحدة، فنداء API واحد جوّاها بيقتل الأداء. استخدم الحدث الأكتر تحديدًا، أو plugin على الكلاس المطلوب.
هل الـ observer ممكن يوقّف العملية الأصلية لو رمى exception؟
أيوه، أي exception غير محسوب في الـ observer بيوقّف العملية اللي أطلقت الحدث. عشان كده لو المنطق ثانوي، لفّه في try/catch وسجّل الخطأ. لو قاصد تمنع الحفظ فعلًا، ارمي LocalizedException في حدث _save_before، أو الأفضل استخدم before plugin على الـ method نفسها لأن ده دوره الأساسي.