教程

本文完整教程地址:https://www.kancloud.cn/yuesir/magento , 希望对 Magento 新手和老手都能有所帮助或者启发。

定义

Magento 中的 Events 和 observers 是对发布-订阅模式的简洁实现。它可以使我们在特定行为触发时抛出一个事件,在响应触发事件过程中调用我们的自定义代码。事件通过 Magento\Framework\Event\Manager 类来分发。它实现了 Magento\Framework\Event\ManagerInterface 接口。

调用调用事件管理对象(eventManager) 的实例的 dispatch 方法来触发事件。第一个参数是触发的事件名称,第二个参数是可选参数,类型是数组,里面可以包含需要传递给监听者的数据。/module-customer/Controller/Account/CreatePost.php 中的例子如下:

  1. $this->_eventManager->dispatch(
  2. 'customer_register_success',
  3. ['account_controller' => $this, 'customer' => $customer]
  4. );

通过 events.xml 来注册观察者,/module-persistent/etc/frontend/events.xml 中的例子如下:

  1. <event name="customer_register_success">
  2. <observer name="persistent" instance="Magento\Persistent\Observer\RemovePersistentCookieOnRegisterObserver" />
  3. </event>

Magento 中比较重要的事件

在<MAGENTO_DIR> 目录下的 PHP 文件中搜索 eventManager->dispatch 能看到很多事件的例子。我们会重点研究以下的类以及相关事件。

Magento\Framework\App\Action\Action 类中包含以下事件

  • controller_action_predispatch
  • ‘controlleraction_predispatch‘ . $request->getRouteName()
  • ‘controlleraction_predispatch‘ . $request->getFullActionName()
  • ‘controlleraction_postdispatch‘ . $request->getFullActionName()
  • ‘controlleraction_postdispatch‘ . $request->getRouteName()
  • controller_action_postdispatch

Magento\Framework\Model\AbstractModel 中包含以下事件

  • 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’

Magento\Framework\Model\ResourceModel\Db\Collection 类中包含以下事件

  • core_collection_abstract_load_before
  • $this->_eventPrefix . ‘_load_before’
  • core_collection_abstract_load_after
  • $this->_eventPrefix . ‘_load_after’

另外在 /framework/View 中也有一些比较重要的事件

  • view_block_abstract_to_html_before
  • view_block_abstract_to_html_after
  • view_message_block_render_grouped_html_after
  • layout_render_before

  • ‘layoutrender_before‘ . $this->request->getFullActionName()

  • core_layout_block_create_after

  • layout_load_before

  • layout_generate_blocks_before

  • layout_generate_blocks_after

  • core_layout_render_element

具体事件相关实现

下面看看 /framework/Model/AbstractModel.php 类中的一个事件的代码

  1. public function afterCommitCallback() {
  2. $this->_eventManager->dispatch('model_save_commit_after', ['object' => $this]);
  3. $this->_eventManager->dispatch($this->_eventPrefix . '_save_commit_after', $this->_getEventData());
  4. return $this;
  5. }
  6. protected function _getEventData() {
  7. return [
  8. 'data_object' => $this,
  9. $this->_eventObject => $this,
  10. ];
  11. }

这里的 $_eventPrefix 和 $_eventObject 属性在这里十分重要,如果我们看看类似 Magento\Catalog\Model\Product 、Magento\Catalog\Model\Category 、 Magento\Customer\Model\Customer、Magento\Quote\Model\Quote 、Magento\Sales\Model\Order 和其他继承自 Magento\Framework\Model\AbstractModel 的类,都提供了自己的值来覆盖父类的 $_eventPrefix = ‘core_abstract’ 和 $_eventObject = ‘object’, 因此我们可以通过 $this->_eventPrefix . ‘_save_commit_after’ 来指定不同的观察者。

看一个 /module-downloadable/etc/events.xml 中的例子

  1. <config>
  2. <event name="sales_order_save_commit_after">
  3. <observer name="downloadable_observer" instance="Magento\Downloadable\Observer\SetLinkStatusObserver" />
  4. </event>
  5. </config>

观察者放置在 <ModuleDir>/Observer 目录,每个观察者都实现了 Magento\Framework\Event\ObserverInterface 接口的 execute 方法。

  1. class SetLinkStatusObserver implements \Magento\Framework\Event\ObserverInterface {
  2. public function execute(\Magento\Framework\Event\Observer $observer) {
  3. $order = $observer->getEvent()->getOrder();
  4. }
  5. }

编写事件时要注意的点

和 Plugin 一样,实现糟糕的观察者很容易引起 bug, 甚至造成应用崩溃。因此我们要保证我们的监听者足够精简和高效,以免造成性能问题。

事件环路陷阱发生在事件监听者触发了它自己所监听的事件的时候。举个栗子,如果一个观察者监听了 model_save_before 事件,同时监听者内部也尝试保存同一个实体 (entity) 的话,就会造成事件监听环路。

为了使我们的监听者足够明确,我们需要在合适的 scope 下声明它。

仅仅监听前台相关事件,可以在 /etc/frontend/events.xml 中声明观察者 仅仅监听后台相关事件,可以在 /etc/adminhtml/events.xml 中声明观察者 监听全局事件,可以在 /etc/events.xml 中声明观察者