The Event Manager
Magento announces work by dispatching named events; observers bound in events.xml listen, and they can never change a return value.
Magento\Framework\Event\ManagerInterface::dispatch($eventName, array $data = [])An event is a named announcement with an array of data attached. Code that wants to tell the rest of the application that something happened dispatches the event. Code that wants to hear about it registers an observer: a class bound to that event name in a file called events.xml. Neither side knows the other exists.
What dispatch does
The event manager is Magento\Framework\Event\ManagerInterface and it has one method. On dispatch it lowercases the name, looks up every observer declared for that name, wraps the data array in a Magento\Framework\Event\Observer object, and calls each observer in turn. This is synchronous. The observers run in the same request, inside the same transaction, before dispatch() returns.
One file per area
An area is one running context of the application: frontend, adminhtml, crontab, graphql, webapi_rest, webapi_soap. A binding in etc/events.xml applies to all of them. A binding in etc/frontend/events.xml applies only to storefront requests. Put it in the narrowest area that needs it.
A notification, not a hook
dispatch() returns nothing, and an observer's own return value is discarded. An observer cannot see the arguments a method received or change the value it returns. The only way an event influences its dispatcher is if the dispatcher deliberately puts a mutable object into the payload for observers to write to.
Event configuration is read through the config cache. After editing events.xml, run bin/magento cache:clean config, or bin/magento cache:flush.
Example
<!-- app/code/Vendor/OrderFeed/etc/events.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="sales_order_place_after">
<observer name="vendor_orderfeed_log_placed_order"
instance="Vendor\OrderFeed\Observer\LogPlacedOrder"/>
</event>
</config>When to use it
- Send an order to a warehouse system the moment it is placed.
- Write an audit line whenever an admin saves a customer record.
- React to a product being added to the cart on the storefront only.
- Switch off a third-party module's observer without editing its code.
More examples
Binding in one area only
This event is dispatched by Magento\Checkout\Model\Cart::addProduct() with the keys quote_item and product. Because the file sits in etc/frontend, the observer fires for storefront requests only; an admin building a cart on the Create New Order screen will not trigger it. Real Magento XML files also begin with the <?xml version="1.0"?> declaration, which has to be the very first line of the file, above any comment. Apply the change with bin/magento cache:flush.
<!-- app/code/Vendor/CartLog/etc/frontend/events.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="checkout_cart_product_add_after">
<observer name="vendor_cartlog_product_added"
instance="Vendor\CartLog\Observer\ProductAdded"/>
</event>
</config>What a dispatch call looks like
Core code dispatching a before and after pair around the work it does. The array keys are the names observers read, and there is no schema for them anywhere: 'order' is a convention documented only by this call site. That is why finding the right event usually means searching vendor/magento for dispatch( near the code you want to react to.
<?php
// vendor/magento/module-sales/Model/Order.php (excerpt)
public function place()
{
$this->_eventManager->dispatch('sales_order_place_before', ['order' => $this]);
$this->_placePayment();
$this->_eventManager->dispatch('sales_order_place_after', ['order' => $this]);
return $this;
}Turning an observer off instead of editing it
events.xml files from every enabled module are merged, and observers inside one event are keyed by their name attribute. Redeclaring the same observer name with disabled="true" from a module that loads later switches it off without touching the original file. The name must match exactly, so copy it from the other module's events.xml, and add that module to <sequence> in your module.xml so yours is merged afterwards. Then run bin/magento cache:flush.
<!-- app/code/Vendor/Override/etc/frontend/events.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="checkout_cart_product_add_after">
<observer name="vendor_cartlog_product_added" disabled="true"/>
</event>
</config>
Discussion