Question

Is there any chance to add a plugin to all public methods of a type without writing a separate method for all of them?

I would like to use that to log method calls. The original method is always called with unmodified parameters.

As it is intended for development/debugging, any dirty hack is appreciated.

Was it helpful?

Solution 2

Here's a dirty hack I came up with: The generated interceptor classes contain all methods of the original class, if there are plugins or not. In every method, PluginList::getNext() is called to get the first plugin.

Unfortunately I cannot create a plugin for the plugin list itself because it will run into infinite recursion during initialization.

But with a one line edit in the generated interceptor file, I could replace the PluginList. This is added at the end of the constructor:

$this->pluginList = new PluginListDecorator($this->pluginList, $this->_logger);

Note: If you change generated files, be prepared to loose the changes any time! This is just a temporary addition!

And the decorator class looks as follows:

class PluginListDecorator implements PluginListInterface
{
    /**
     * @var PluginListInterface
     */
    private $pluginList;
    /**
     * @var LoggerInterface
     */
    private $logger;

    public function __construct(PluginListInterface $pluginList, LoggerInterface $logger)
    {
        $this->pluginList = $pluginList;
        $this->logger = $logger;
    }
    public function getNext($type, $method, $code = null)
    {
        $callee = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
        if ($callee['class'] == Interceptor::class) {
            $this->logger->info('METHOD CALLED: ' . $method);
        }
        return $this->pluginList->getNext($type, $method, $code);
    }

    public function getPlugin($type, $code)
    {
        return $this->pluginList->getPlugin($type, $code);
    }
}

If getNext() is called from the interceptor itself, I do my logging, then pass the call along to the original PluginList.

OTHER TIPS

I don't know if this works, but it's an idea.
The Interceptor class is generated containing all the original class public methods and all interceptor methods look like this:

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

where $this->subjectType is the name of the original class and $this->pluginList is an implementation of Magento\Framework\Interception\PluginListInterface (most probably Magento\Framework\Interception\PluginList\PluginList).
Now you can try something sneaky.
(Here is where my idea may fail.)
Since $this->pluginList->getNext() is a public method, it can be pluginized.
So create a (before or around) plugin for getNext and if the value of the first parameter of getNext (second parameter in your plugin) is your intended class name, then do your logging.

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