Magento has a neat publish-subscribe pattern implementation that we call events and observers. By dispatching events when certain actions are triggered, we can run our custom code in response to the triggered event. The events are dispatched using the Magento\Framework\Event\Manager class, which implements Magento\Framework\Event\ManagerInterface.
To dispatch an event, we simply call the dispatch method of the event manager instance, providing it with the name of the event we are dispatching with an optional array of data we wish to pass on to the observers, as per the following example taken from the <MAGENTO_DIR>/module-customer/Controller/Account/CreatePost.php file:
$this->_eventManager->dispatch(
'customer_register_success',
['account_controller' => $this, 'customer' => $customer]
);
Observers are registered via an events.xml file, as per the following example from the <MAGENTO_DIR>/module-persistent/etc/frontend/events.xml file:
<event name="customer_register_success">
<observer name="persistent" instance="Magento\Persistent\Observer\RemovePersistentCookieOnRegisterObserver" />
</event>
By doing a lookup for the eventManager->dispatch string across the entire <MAGENTO_DIR> directory's *.php files, we can see hundreds of events examples, spread across the majority of Magento's modules. While all of these events are of the same technical importance, we might say that some are likely to be used more on a day to day basis than others.
This makes it worth taking some time to study the following classes and the events they dispatch:
- The Magento\Framework\App\Action\Action class, with the following events:
- controller_action_predispatch
- 'controller_action_predispatch_' . $request->getRouteName()
- 'controller_action_predispatch_' . $request->getFullActionName()
- 'controller_action_postdispatch_' . $request->getFullActionName()
- 'controller_action_postdispatch_' . $request->getRouteName()
- controller_action_postdispatch
- The Magento\Framework\Model\AbstractModel class, with the following events:
- model_load_before
- $this->_eventPrefix . '_load_before'
- model_load_after
- $this->_eventPrefix . '_load_after'
- model_save_commit_after
- $this->_eventPrefix . '_save_commit_after'
- model_save_before
- $this->_eventPrefix . '_save_before'
- model_save_after
- clean_cache_by_tags
- $this->_eventPrefix . '_save_after'
- model_delete_before
- $this->_eventPrefix . '_delete_before'
- model_delete_after
- clean_cache_by_tags
- $this->_eventPrefix . '_delete_after'
- model_delete_commit_after
- $this->_eventPrefix . '_delete_commit_after'
- $this->_eventPrefix . '_clear'
- The Magento\Framework\Model\ResourceModel\Db\Collection class, with the following events:
- core_collection_abstract_load_before
- $this->_eventPrefix . '_load_before'
- core_collection_abstract_load_after
- $this->_eventPrefix . '_load_after'
Some more important events can be found in a few of the types defined under the <MAGENTO_DIR>/framework/View directory:
- view_block_abstract_to_html_before
- view_block_abstract_to_html_after
- view_message_block_render_grouped_html_after
- layout_render_before
- 'layout_render_before_' . $this->request->getFullActionName()
- core_layout_block_create_after
- layout_load_before
- layout_generate_blocks_before
- layout_generate_blocks_after
- core_layout_render_element
Let's take a closer look at one of these events, the one found in the <MAGENTO_DIR>/framework/Model/AbstractModel.php file:
public function afterCommitCallback() {
$this->_eventManager->dispatch('model_save_commit_after', ['object' => $this]);
$this->_eventManager->dispatch($this->_eventPrefix . '_save_commit_after', $this->_getEventData());
return $this;
}
protected function _getEventData() {
return [
'data_object' => $this,
$this->_eventObject => $this,
];
}
The $_eventPrefix and $_eventObject type properties are particularly important here. If we glimpse over types such as Magento\Catalog\Model\Product, Magento\Catalog\Model\Category, Magento\Customer\Model\Customer, Magento\Quote\Model\Quote, Magento\Sales\Model\Order, and others, we can see that a great deal of these entity types are essentially extending from Magento\Framework\Model\AbstractModel and provide their own values to replace $_eventPrefix = 'core_abstract' and $_eventObject = 'object'. What this means is that we can use events such as $this->_eventPrefix . '_save_commit_after' to specify observers via events.xml.
Let's take a look at the following example, taken from the <MAGENTO_DIR>/module-downloadable/etc/events.xml file:
<config>
<event name="sales_order_save_commit_after">
<observer name="downloadable_observer" instance="Magento\Downloadable\Observer\SetLinkStatusObserver" />
</event>
</config>
Observers are placed inside the <ModuleDir>/Observer directory. Every observer implements a single execute method on the Magento\Framework\Event\ObserverInterface class:
class SetLinkStatusObserver implements \Magento\Framework\Event\ObserverInterface {
public function execute(\Magento\Framework\Event\Observer $observer) {
$order = $observer->getEvent()->getOrder();
}
}
Much like plugins, badly implemented observers can easily cause bugs or even break the entire application. This is why we need to keep our observer small and computationally efficient—to avoid performance bottlenecks.
The cyclical event loop is a trap that's easy to fall into. This happens when an observer, at some point, is dispatching the same event that it listens to. For example, if an observer listens to the model_save_before event, and then tries to save the same entity again within the observer, this would trigger a cyclical event loop.
To make our observers as specific as possible, we need to declare them in an appropriate scope:
- For observing frontend only events, you can declare observers in <ModuleDir>/etc/frontend/events.xml
- For observing backend only events, you can declare observers in <ModuleDir>/etc/adminhtml/events.xml
- For observing global events, you can declare observers in <ModuleDir>/etc/events.xml
Unlike plugins, observers are used for triggering the follow-up functionality, rather than changing the behavior of functions or data which is part of the event they are observing.