Magento 2 事件處理 (上)

Magento 內建有許多的事件,其實使用起來非常方便,設定上也不會太過於繁瑣,又可以取得想要的參數,今天我們就來看一下事件的呼叫是如何使用的。
1. 什麼是事件 ( Event )
事件 ( Event ) 是一種在 Magento 內實踐設計模式(Design Pattern)中觀察者模式(Observer Pattern)的一種實作方法。
而根據維基百科對於 觀察者模式 (Observer Pattern) 的定義:
一個目標物件管理所有相依於它的觀察者物件,並且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用來實時事件處理系統。
對於設計模式( Design Pattern ) 有興趣的人,可以參閱 深入淺出設計模式 ( 傳送門 ) 這本書。
而 Magento 內因為已經幫我們實作完底層的結構,我們僅需要管理其用法,今天我們會在文章內說明如何使用,讓大家在撰寫 Magento 程式的時候更加順暢。
2. 三種事件處理
事件處理有分為三種,須依照不同的需求,放置在不同的位置,若前後端皆需使用的話,則放在通用的事件,而檔案名稱統一皆為 event.xml。
| 事件區分 | 路徑 | 檔名 |
| 前端 | vendor/extension/etc/frontend/ | event.xml |
| 後端 | vendor/extension/etc/adminhtml/ | event.xml |
| 通用 | vendor/extension/etc/ | event.xml |
3. xml 格式
在模組內註冊需要使用的事件,並指定 instance 到指定的 class ,下面的範例註冊了 controller_action_catalog_product_save_entity_after 這個事件,這是屬於一個原生的事件,而我們便可以從指定的 class 抓到相對應的內容。
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> <event name="controller_action_catalog_product_save_entity_after"> <observer name="custom_after_save_product_event_inventory" instance="Vendor\Extension\Observer\AfterSaveProductEvent" /> </event> </config>
其中裡面幾個參數:
* name – 每一個事件必須要有獨立且唯一的命名
* instance – 必須指定一個 Class 的 namespace,並且繼承 magento 的 interface
* disabled – 開啟或關閉此事件
* shared – 此 instance 是否要與其他事件共用,預設為 false
4. PHP 類別
由上面指定的 instance 對應到 Vendor\Extension\Observer\AfterSaveProductEvent 這個 class,而這邊要注意的是,務必要 implements 原生的 interface (Magento\Framework\Event\ObserverInterface)。
<?php
namespace Vendor\Extension\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
/**
* Class AfterSaveProductEvent
* @package Vendor\Extension\Observer\Adminhtml
*/
class AfterSaveProductEvent implements ObserverInterface
{
/**
* @param Observer $observer
* @return AfterSaveProductEvent
* @throws LocalizedException
* @throws \Exception
*/
public function execute(Observer $observer)
{
$product = $observer->getData('product');
$controller = $observer->getData('controller');
return $this;
}
}
我們可以看到在第 26 行的地方,可以使用 getData 的方法,但是卻不知道有什麼參數可以取得。這時候,我們回到原生的 Magento 程式碼中第 138 行可以看到,在儲存完 product entity 之後會呼叫這個事件 ( Event ) ,並且會同時帶入兩個參數 controller 及 product。因此我們才可以在事件中直接取用。
原生程式:
Magento\Catalog\Controller\Adminhtml\Product\Save
<?php
/**
*
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Backend\App\Action;
use Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\App\Request\DataPersistorInterface;
/**
* Class Save
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Save extends \Magento\Catalog\Controller\Adminhtml\Product
{
/**
* @var Initialization\Helper
*/
protected $initializationHelper;
/**
* @var \Magento\Catalog\Model\Product\Copier
*/
protected $productCopier;
/**
* @var \Magento\Catalog\Model\Product\TypeTransitionManager
*/
protected $productTypeManager;
/**
* @var \Magento\Catalog\Api\CategoryLinkManagementInterface
*/
protected $categoryLinkManagement;
/**
* @var \Magento\Catalog\Api\ProductRepositoryInterface
*/
protected $productRepository;
/**
* @var DataPersistorInterface
*/
protected $dataPersistor;
/**
* @var StoreManagerInterface
*/
private $storeManager;
/**
* Save constructor.
*
* @param Action\Context $context
* @param Builder $productBuilder
* @param Initialization\Helper $initializationHelper
* @param \Magento\Catalog\Model\Product\Copier $productCopier
* @param \Magento\Catalog\Model\Product\TypeTransitionManager $productTypeManager
* @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
Product\Builder $productBuilder,
Initialization\Helper $initializationHelper,
\Magento\Catalog\Model\Product\Copier $productCopier,
\Magento\Catalog\Model\Product\TypeTransitionManager $productTypeManager,
\Magento\Catalog\Api\ProductRepositoryInterface $productRepository
) {
$this->initializationHelper = $initializationHelper;
$this->productCopier = $productCopier;
$this->productTypeManager = $productTypeManager;
$this->productRepository = $productRepository;
parent::__construct($context, $productBuilder);
}
/**
* Save product action
*
* @return \Magento\Backend\Model\View\Result\Redirect
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function execute()
{
$storeId = $this->getRequest()->getParam('store', 0);
$store = $this->getStoreManager()->getStore($storeId);
$this->getStoreManager()->setCurrentStore($store->getCode());
$redirectBack = $this->getRequest()->getParam('back', false);
$productId = $this->getRequest()->getParam('id');
$resultRedirect = $this->resultRedirectFactory->create();
$data = $this->getRequest()->getPostValue();
$productAttributeSetId = $this->getRequest()->getParam('set');
$productTypeId = $this->getRequest()->getParam('type');
if ($data) {
try {
$product = $this->initializationHelper->initialize(
$this->productBuilder->build($this->getRequest())
);
$this->productTypeManager->processProduct($product);
if (isset($data['product'][$product->getIdFieldName()])) {
throw new \Magento\Framework\Exception\LocalizedException(__('Unable to save product'));
}
$originalSku = $product->getSku();
$product->save();
$this->handleImageRemoveError($data, $product->getId());
$this->getCategoryLinkManagement()->assignProductToCategories(
$product->getSku(),
$product->getCategoryIds()
);
$productId = $product->getEntityId();
$productAttributeSetId = $product->getAttributeSetId();
$productTypeId = $product->getTypeId();
$this->copyToStores($data, $productId);
$this->messageManager->addSuccessMessage(__('You saved the product.'));
$this->getDataPersistor()->clear('catalog_product');
if ($product->getSku() != $originalSku) {
$this->messageManager->addNoticeMessage(
__(
'SKU for product %1 has been changed to %2.',
$this->_objectManager->get(
\Magento\Framework\Escaper::class
)->escapeHtml($product->getName()),
$this->_objectManager->get(
\Magento\Framework\Escaper::class
)->escapeHtml($product->getSku())
)
);
}
$this->_eventManager->dispatch(
'controller_action_catalog_product_save_entity_after',
['controller' => $this, 'product' => $product]
);
//... 略
5. 完成
如此一來,即可完成事件的註冊,及使用事件來取得資料。若是依照上面步驟沒有辦法成功的話,請記得試試執行 compile 的指令。
bin/magento setup:di:compile
下一個篇幅,我們將會介紹進階的事件處理給大家,請大家要定期收看喔!
更多的相關文章,此參閱我們Magento教學導覽!
相關文章:
我要留言