类似在方法这个层次的中间件,允许在被代理的方法前、后做一些其他事情。主要有 before、after 和 around 方法。可以对比 laravel 或者其他框架中的 middleware 来理解,或者类比设计模式中的装饰器模式,或者 python 中的 decorator,只不过是在 method 这个层次。
plugin 不能被用在以下类型中
- Final method
- Final class
- 非 public 方法
- 类方法(比如静态方法)
- __construct()
- Virtual Types
- 在 Framework\Interception 启动之前初始化的对象
Plugin 可以被用在下面几个情况中
- class
- interface
- 抽象类
- 父类
<br />通过在 Magento 源码中搜索 <plugin 就能在 di.xml 文件中搜索到很多 plugin 的例子。
before plugin 会在被监听的方法之前运行。before plugin 有以下几条规则
- before 关键词会被添加到被监听的方法前面,比如如果监听的是 getSomeValue 方法,那么在 plugin 中对应的方法名称就是 beforeGetSomeValue (下称为 before plugin method)
- before plugin method 中的第一个参数是被监听的对象实例,通常缩写为 $subject 或者直接使用对应的类名,在例子中是 $processor
- before plugin method 中的剩余所有参数都必须和被监听的方法中的参数一致。
- before plugin method 返回的参数必须是一个数组,返回值类型和个数必须和被监听的方法一致。
在<MAGENTO_DIR>module-payment/etc/frontend/di.xml 我们能看到类似下面的写法
<type name="Magento\Checkout\Block\Checkout\LayoutProcessor">
<plugin name="ProcessPaymentConfiguration"
type="Magento\Payment\Plugin\PaymentConfigurationProcess"/>
</type>
上面代码中 plugin 的 beforeProcess 方法监听的 Magento\Checkout\Block\Checkout\LayoutProcessorMagento\Checkout\Block\Checkout\LayoutProcessor 中的 Magento\Checkout\Block\Checkout\LayoutProcessor process 方法。
public function process($jsLayout)
{
//代码块
return $jsLayout;
}
before plugin 的实现是通过 Magento\Payment\Plugin\PaymentConfigurationProcess 类中的 beforeProcess 方法来完成的。
public function beforeProcess(
\Magento\Checkout\Block\Checkout\LayoutProcessor $processor,
$jsLayout) {
// 代码块...
return [$jsLayout];
}
around plugin 功能允许我们在被监听的方法前、后运行一部分我们自己的代码。这个功能使我们能够在改变输入参数的同时改变返回结果值。
关于 around plugin, 要记住的几个要点有
- plugin 中的第一个参数是监听的类的实例
- plugin 中的第二个参数是一个 callable/Closure 类型。通常写作 callable $proceed ,调用 $proceed 时的入参需要和被监听方法参一致。
- 其余的参数需要和被监听方法一致。
- plugin 的返回值必须和原函数保持一致。通常是直接返回 return $proceed(…) 或者先调用 $returnValue = $proceed(); 后直接返回 $returnValue; 有时候我们也需要修改 $returnValue;
下面来看一个 around plugin 的例子。<br /><MAGENTO_DIR>module-grouped-product/etc/di.xml 文件中
<type name="Magento\Catalog\Model\ResourceModel\Product\Link">
<plugin name="groupedProductLinkProcessor" type="Magento\GroupedProduct\Model\ResourceModel\Product\Link\RelationPersister" />
</type>
plugin 中的方法监听的是 Magento\GroupedProduct\Model\ResourceModel\Product\Link\RelationPersister 类中的 aroundDeleteProductLink 方法
public function aroundDeleteProductLink(
\Magento\GroupedProduct\Model\ResourceModel\Product\Link $subject,
\Closure $proceed, $linkId) {
// The rest of the code...
$result = $proceed($linkId);
// The rest of the code...
return $result;
}
after plugin 主要是在被监听的方法之后执行一部分代码。
在写 after plugin 的时候,要记住以下几点:
- plugin 的第一个参数是被监听类型的实例
- plugin 的第二个参数是被监听方法的执行结果,通常叫做 $result ,也可以在被监听方法返回值之后被调用。例如下面例子中的 $data
- 剩下的其他参数和被监听方法一致
- plugin 必须返回和 $result|$data 同类型的返回值
在 module-catalog/etc/di.xml 中的 after plugin 的例子如下:
<type name="Magento\Indexer\Model\Config\Data">
<plugin name="indexerProductFlatConfigGet"
type="Magento\Catalog\Model\Indexer\Product\Flat\Plugin\IndexerConfigData" />
</type>
plugin 中监听的方法是 Magento\Indexer\Model\Config\Data 类中的 get 方法
public function get($path = null, $default = null) {
// The rest of the code...
return $data;
}
Magento\Catalog\Model\Indexer\Product\Flat\Plugin\IndexerConfigData 类中的 afterGet 就是这里的 after plugin 的实现,具体如下:
public function afterGet(Magento\Indexer\Model\Config\Data, $data, $path = null, $default = null) {
// The rest of the code...
return $data;
}
使用 plugin 时需要特别注意, 它很灵活,但是也很容易产生 Bug 和性能瓶颈,尤其是在多个 plugin 监听同一个方法的时候。