### 个人理解 类似在方法这个层次的中间件,允许在被代理的方法前、后做一些其他事情。主要有 before、after 和 around 方法。可以对比 laravel 或者其他框架中的 middleware 来理解,或者类比设计模式中的装饰器模式,或者 python 中的 decorator,只不过是在 method 这个层次。 ### 适用场景 plugin 不能被用在以下类型中 > - Final method > - Final class > - 非 public 方法 > - 类方法(比如静态方法) > - __construct() > - Virtual Types > - 在 Framework\Interception 启动之前初始化的对象 Plugin 可以被用在下面几个情况中 > - class > - interface > - 抽象类 > - 父类 通过在 Magento 源码中搜索 &lt;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 方法。 ```php public function process($jsLayout) { //代码块 return $jsLayout; } ``` before plugin 的实现是通过 Magento\Payment\Plugin\PaymentConfigurationProcess 类中的 beforeProcess 方法来完成的。 ```php 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 的例子。 <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 方法 ```php 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 方法 ```php 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 的实现,具体如下: ```php public function afterGet(Magento\Indexer\Model\Config\Data, $data, $path = null, $default = null) { // The rest of the code... return $data; } ``` 使用 plugin 时需要特别注意, 它十分灵活,但是也很容易产生 Bug 和造成性能瓶颈,尤其是在多个 plugin 监听同一个方法的时候。