教程

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

个人理解

类似在方法这个层次的中间件,允许在被代理的方法前、后做一些其他事情。主要有 before、after 和 around 方法。可以对比 laravel 或者其他框架中的 middleware 来理解,或者类比设计模式中的装饰器模式,或者 python 中的 decorator,只不过是在 method 这个层次。

适用场景

plugin 不能被用在以下类型中

  • Final method
  • Final class
  • 非 public 方法
  • 类方法(比如静态方法)
  • __construct()
  • Virtual Types
  • 在 Framework\Interception 启动之前初始化的对象

Plugin 可以被用在下面几个情况中

  • class
  • interface
  • 抽象类
  • 父类

通过在 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 返回的参数必须是一个数组,返回值类型和个数必须和被监听的方法一致。

module-payment/etc/frontend/di.xml 我们能看到类似下面的写法

  1. <type name="Magento\Checkout\Block\Checkout\LayoutProcessor">
  2. <plugin name="ProcessPaymentConfiguration"
  3. type="Magento\Payment\Plugin\PaymentConfigurationProcess"/>
  4. </type>

上面代码中 plugin 的 beforeProcess 方法监听的 Magento\Checkout\Block\Checkout\LayoutProcessorMagento\Checkout\Block\Checkout\LayoutProcessor 中的 Magento\Checkout\Block\Checkout\LayoutProcessor process 方法。

  1. public function process($jsLayout)
  2. {
  3. //代码块
  4. return $jsLayout;
  5. }

before plugin 的实现是通过 Magento\Payment\Plugin\PaymentConfigurationProcess 类中的 beforeProcess 方法来完成的。

  1. public function beforeProcess(
  2. \Magento\Checkout\Block\Checkout\LayoutProcessor $processor,
  3. $jsLayout) {
  4. // 代码块...
  5. return [$jsLayout];
  6. }

around plugin

around plugin 功能允许我们在被监听的方法前、后运行一部分我们自己的代码。这个功能使我们能够在改变输入参数的同时改变返回结果值。

关于 around plugin, 要记住的几个要点有

  • plugin 中的第一个参数是监听的类的实例
  • plugin 中的第二个参数是一个 callable/Closure 类型。通常写作 callable $proceed ,调用 $proceed 时的入参需要和被监听方法参一致。
  • 其余的参数需要和被监听方法一致。
  • plugin 的返回值必须和原函数保持一致。通常是直接返回 return $proceed(…) 或者先调用 $returnValue = $proceed(); 后直接返回 $returnValue; 有时候我们也需要修改 $returnValue;

下面来看一个 around plugin 的例子。
module-grouped-product/etc/di.xml 文件中

  1. <type name="Magento\Catalog\Model\ResourceModel\Product\Link">
  2. <plugin name="groupedProductLinkProcessor" type="Magento\GroupedProduct\Model\ResourceModel\Product\Link\RelationPersister" />
  3. </type>

plugin 中的方法监听的是 Magento\GroupedProduct\Model\ResourceModel\Product\Link\RelationPersister 类中的 aroundDeleteProductLink 方法

  1. public function aroundDeleteProductLink(
  2. \Magento\GroupedProduct\Model\ResourceModel\Product\Link $subject,
  3. \Closure $proceed, $linkId) {
  4. // The rest of the code...
  5. $result = $proceed($linkId);
  6. // The rest of the code...
  7. return $result;
  8. }

after plugin

after plugin 主要是在被监听的方法之后执行一部分代码。

在写 after plugin 的时候,要记住以下几点:

  • plugin 的第一个参数是被监听类型的实例
  • plugin 的第二个参数是被监听方法的执行结果,通常叫做 $result ,也可以在被监听方法返回值之后被调用。例如下面例子中的 $data
  • 剩下的其他参数和被监听方法一致
  • plugin 必须返回和 $result|$data 同类型的返回值

在 module-catalog/etc/di.xml 中的 after plugin 的例子如下:

  1. <type name="Magento\Indexer\Model\Config\Data">
  2. <plugin name="indexerProductFlatConfigGet"
  3. type="Magento\Catalog\Model\Indexer\Product\Flat\Plugin\IndexerConfigData" />
  4. </type>

plugin 中监听的方法是 Magento\Indexer\Model\Config\Data 类中的 get 方法

  1. public function get($path = null, $default = null) {
  2. // The rest of the code...
  3. return $data;
  4. }

Magento\Catalog\Model\Indexer\Product\Flat\Plugin\IndexerConfigData 类中的 afterGet 就是这里的 after plugin 的实现,具体如下:

  1. public function afterGet(Magento\Indexer\Model\Config\Data, $data, $path = null, $default = null) {
  2. // The rest of the code...
  3. return $data;
  4. }

使用 plugin 时需要特别注意, 它很灵活,但是也很容易产生 Bug 和性能瓶颈,尤其是在多个 plugin 监听同一个方法的时候。