在 Magento 2 中,插件被用來擴展原生類的行爲。這些原生的行爲能夠經過建立插件來進行修改,但插件改變的是這些類的方法的行爲,而不是類自己。就是說,當某個類被其餘類所繼承,那麼做用在該類的插件並不會影響繼承這個類的其餘類的行爲。php
p.s. 在 di.xml 中經過插件(plugin)能夠改變類的某個方法,而 preference 則能夠改變整個類。html
插件經過 di.xml 定義,如下是插件在該配置文件中的格式及參數說明:數組
<config> <type name="[ObservedType]"> <plugin name="[pluginName]" type="[PluginClassName]" sortOrder="1" disabled="true" /> </type> </config>
[ObservedType] - 插件的注入目標類名。包含空間名,如 Magento\Customer\Model\Resource\Visitor
ide
[pluginName] - 插件名。全站同一個域內惟一,如 catalogLog函數
[PluginClassName] - 插件的實體類名。包含空間名,如 Magento\Catalog\Model\Plugin\Log
fetch
sortOrder
- 執行順序。當同一個類有多個插件時,根據這個參數決定插件的執行順序ui
disabled
- 該參數爲 true 時不執行該插件this
...插件
當改變一個原生方法的參數,或者在執行這個原生方法以前添加一些行爲的時候,就須要在插件中用到 Before-Listener 方法。code
Before-Listener 方法的命名規則是,將原生方法的首字母改爲大寫,並在原生方法名前添加前綴 before。
Before-Listener 方法的第一個參數是原生方法所屬類的實例。若是原生方法有返回值,則第二個參數爲原生方法的返回值。參數類型能夠跟原生方法不一致,至關於在 di.xml 中用 argument 節點指定了對應參數的類。
若是須要修改原生方法的參數內容,能夠在 Before-Listener 方法中返回一個數組,數組第一到 n 個元素將依次爲原生方法的入參。
以 MagentoPersistent 模塊爲例,其 di.xml 定義了:
<type name="Magento\Quote\Model\AddressAdditionalDataProcessor"> <plugin name="persistent_remember_me_checkbox_processor" type="Magento\Persistent\Model\Checkout\AddressDataProcessorPlugin" /> </type>
修改的是原生 MagentoQuoteModelAddressAdditionalDataProcessor 類的 process 方法:
public function process(AddressAdditionalDataInterface $additionalData) { return; }
在 MagentoPersistentModelCheckoutAddressDataProcessorPlugin 中指定了該方法的參數類型,並添加了在其執行前的行爲:
public function beforeProcess(AddressAdditionalDataProcessor $subject, AddressAdditionalData $additionalData) { if (!$this->persistentHelper->isEnabled() || !$this->persistentHelper->isRememberMeEnabled()) { return; } $checkboxStatus = $additionalData->getExtensionAttributes()->getPersistentRememberMe(); $isRememberMeChecked = empty($checkboxStatus) ? false : true; $this->persistentSession->setRememberMeChecked($isRememberMeChecked); $this->checkoutSession->setRememberMeChecked($isRememberMeChecked); }
當改變一個原生方法的返回值,或者在執行這個原生方法以後添加一些行爲的時候,就須要在插件中用到 After-Listener 方法。
After-Listener 方法的命名規則是,將原生方法的首字母改爲大寫,並在原生方法名前添加前綴 after。
After-Listener 方法的第一個參數是原生方法所屬類的實例。若是原生方法有返回值,則第二個參數爲原生方法的返回值。
After-Listener 方法的返回值類型與原生方法的返回值一致。
以 MagentoCatalog 模塊爲例,其 di.xml 定義了:
<type name="Magento\Customer\Model\Resource\Visitor"> <plugin name="catalogLog" type="Magento\Catalog\Model\Plugin\Log" /> </type>
修改的是原生 MagentoCustomerModelResourceVisitor 類的 clean 方法:
public function clean(\Magento\Customer\Model\Visitor $object) { $cleanTime = $object->getCleanTime(); $connection = $this->getConnection(); $timeLimit = $this->dateTime->formatDate($this->date->gmtTimestamp() - $cleanTime); while (true) { $select = $connection->select()->from( ['visitor_table' => $this->getTable('customer_visitor')], ['visitor_id' => 'visitor_table.visitor_id'] )->where( 'visitor_table.last_visit_at < ?', $timeLimit )->limit( 100 ); $visitorIds = $connection->fetchCol($select); if (!$visitorIds) { break; } $condition = ['visitor_id IN (?)' => $visitorIds]; $connection->delete($this->getTable('customer_visitor'), $condition); } return $this; }
在 MagentoCatalogModelPluginLog 中添加了在其執行後的行爲:
public function afterClean(\Magento\Customer\Model\Resource\Visitor $subject, $logResourceModel) { $this->_productCompareItem->clean(); return $logResourceModel; }
須要同時改變一個原生方法的參數及返回值,或者在執行這個原生方法先後都添加一些行爲的時候,就用到 Around-Listener 方法。
Around-Listener 方法的命名規則是,將原生方法的首字母改爲大寫,並在原生方法名前添加前綴 around。
Around-Listener 方法的第一個參數是原生方法所屬類的實例;第二個參數是下一個插件的類名或方法名。若是原生方法有參數,則以同一順序依次跟在第二個參數以後。
Around-Listener 方法的返回值類型與原生方法的返回值一致。
以 MagentoCatalog 模塊爲例,其 di.xml 定義了:
<type name="Magento\Eav\Model\Entity\Attribute\Set"> <plugin name="invalidateEavIndexerOnAttributeSetSave" type="Magento\Catalog\Model\Indexer\Product\Eav\Plugin\AttributeSet" /> </type>
修改的是原生 MagentoEavModelEntityAttributeSet 類的 save 方法(繼承自 MagentoFrameworkModelAbstractModel):
public function save() { $this->_getResource()->save($this); return $this; }
在 MagentoCatalogModelIndexerProductEavPluginAttributeSet 中添加了在其執行先後的行爲:
public function aroundSave(\Magento\Eav\Model\Entity\Attribute\Set $subject, \Closure $proceed) { $requiresReindex = false; if ($subject->getId()) { /** @var \Magento\Eav\Model\Entity\Attribute\Set $originalSet */ $originalSet = clone $subject; $originalSet->initFromSkeleton($subject->getId()); $originalAttributeCodes = array_flip($this->_attributeFilter->filter($originalSet)); $subjectAttributeCodes = array_flip($this->_attributeFilter->filter($subject)); $requiresReindex = (bool)count(array_merge( array_diff_key($subjectAttributeCodes, $originalAttributeCodes), array_diff_key($originalAttributeCodes, $subjectAttributeCodes) )); } $result = $proceed(); if ($requiresReindex) { $this->_indexerEavProcessor->markIndexerAsInvalid(); } return $result; }
當多個插件同時指向同一個原生方法時,將按如下順序執行:
執行 sortOrder
最小的插件的 Before-Listener 方法
執行 sortOrder
最小的插件的 Around-Listener 方法
按 sortOrder
值從小到大順序執行插件的其餘 Before-Listener 方法
按 sortOrder
值從小到大順序執行插件的其餘 Around-Listener 方法
按 sortOrder
值從大到小順序執行插件的 After-Listener 方法