【分享】Magento 中的 Plugin 是什么?

个人理解

类似在方法这个层次的中间件,允许在被代理的方法前、后做一些其他事情。主要有 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 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 功能允许我们在被监听的方法前、后运行一部分我们自己的代码。这个功能使我们能够在改变输入参数的同时改变返回结果值。

关于 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 主要是在被监听的方法之后执行一部分代码。

在写 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 监听同一个方法的时候。