Question

In Magento 2, when you create an "around" plugin

public function aroundRenderResult(
    \Magento\Framework\Controller\ResultInterface $subject,
    \Closure $proceed,
    ResponseHttp $response
) {
    //...
    $proceed($response);
    //...      
}    

you can proceed to the next around plugin, culminating with call the actual original method, by calling/invoking the passed in $proceed method. This is a common design pattern, often seen in PHP Frameworks middleware implementations.

However -- it does present some confusion w/r/t to implementation details. Specifically

If, in addition to an aroundPlugin, an object/class has a before or after plugin defined, when do they fire in relation to the chain of around plugins?

i.e. will all the before methods fire before any around plugin methods fire? Or will before plugins only fire before the final, actual real method fires?

The specific problem I'm trying to track down is, I can't seem to get a plugin attached to the dispatch method the a Magento 2 front controller when Magento's in full page caching mode. The full page cache operates by an around plugin that does not call $proceed($response). I've tried digging into some of the code around these plugins and have found the system difficult to reason about without knowing how its intended that plugins work.

i.e. -- the description on the dev docs page appears, in this one specific instance, to be inaccurate. It's unclear if the documentation is wrong, or if this is a recently introduced bug, if it's an edge case, or if my plugin configuration is wrong.

Does anyone know, by direct observation, or by cultural knowledge, how this prioritization is supposed to work?

Was it helpful?

Solution

Plugins are sorted by sort order first, and then by method prefix.

Example: for method with 3 plugins (PluginA, PluginB, PluginC) with following methods and sortOrder:

  • PluginA (sortOrder = 10)
    • beforeDispatch()
    • afterDispatch()
  • PluginB (sortOrder = 20)
    • beforeDispatch()
    • aroundDispatch()
    • afterDispatch()
  • PluginC (sortOrder = 30):
    • beforeDispatch()
    • aroundDispatch()
    • afterDispatch()

Execution flow should be following:

  • PluginA::beforeDispatch()
  • PluginB::beforeDispatch()
  • PluginB::aroundDispatch()
    • PluginC::beforeDispatch()
    • PluginC::aroundDispatch()
      • Action::dispatch()
    • PluginC::afterDispatch()
  • PluginB::afterDispatch()
  • PluginA::afterDispatch()

OTHER TIPS

From the Magento 2 cookbook:

If there are multiple plugins that extend the same original function, they are executed in the following sequence:

  • the before plugin with the lowest sortOrder
  • the around plugin with the lowest sortOrder
  • other before plugins (from the lowest to highest sortOrder)
  • other around plugins (from the lowest to highest sortOrder)
  • the after plugin with the highest sortOrder
  • other after plugins (from the highest to the lowest sortOrder)

For me it should work as:

  • if sort order is not defined its equivalent of zero (and this mean that real order is undefined)
  • plugins should be sort by order

If you review code of \Magento\Framework\Interception\Interceptor::___callPlugins() you can see that plugins called in order of stored in $pluginInfo variable. This information passed form auto generated method in interceptors like

public function {method}()
{
    $pluginInfo = $this->pluginList->getNext($this->subjectType, '{method}');
    if (!$pluginInfo) {
        return parent::{method}();
    } else {
        return $this->___callPlugins('{method}', func_get_args(), $pluginInfo);
    }
}

As you see \Magento\Framework\Interception\PluginListInterface interface and \Magento\Framework\Interception\PluginList\PluginList default implementation responsible for plugin sorting. See _inheritPlugins:152 method

/**
 * Sort items
 *
 * @param array $itemA
 * @param array $itemB
 * @return int
 */
protected function _sort($itemA, $itemB)
{
    if (isset($itemA['sortOrder'])) {
        if (isset($itemB['sortOrder'])) {
            return $itemA['sortOrder'] - $itemB['sortOrder'];
        }
        return $itemA['sortOrder'];
    } elseif (isset($itemB['sortOrder'])) {
        return $itemB['sortOrder'];
    } else {
        return 1;
    }
} 

For me this function have two logical errors:

  • return $itemB['sortOrder']; should be return - $itemB['sortOrder'];
  • return 1; should be return 0;

Hope it will help you.

Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top