Magento 2 Plugins

Magento 2 plugins are classes that can modify the behaviour of public class functions by running code before, after, or around that function call.

This means that any method’s behaviour can be modified within the application without the need to explicitly dispatch an event and create an observer like in Magento 1.

There is also another problem that plugins solve. In Magento 1, if you wanted to rewrite a class, you would have to create your own class and extend the original one. However, if another third party extension attempted to rewrite the same class, there would be rewrite conflicts and either your class of the third party class’ extended functionality would not be executed.

Since plugins do not replace the target class, any number of plugins can be active on a class at the same time.

So it is advisable to use plugins where possible rather than preferences.

Plugins themselves do have limitations. They cannot be used with non-public and final methods, final classes and a few other scenarios.

As mentioned, code from plugins can be executed before, around or after the target function.

The code responsible can be seen in Magento’s Interceptor trait.


<?php
namespace Magento\Framework\Interception;

use Magento\Framework\App\ObjectManager;

trait Interceptor
    ....
    protected function ___callPlugins($method, array $arguments, array $pluginInfo)
    {
        $capMethod = ucfirst($method);
        $result = null;
        if (isset($pluginInfo[DefinitionInterface::LISTENER_BEFORE])) {
            // Call 'before' listeners
            foreach ($pluginInfo[DefinitionInterface::LISTENER_BEFORE] as $code) {
                $pluginInstance = $this->pluginList->getPlugin($this->subjectType, $code);
                $pluginMethod = 'before' . $capMethod;
                $beforeResult = $pluginInstance->$pluginMethod($this, ...array_values($arguments));
                if ($beforeResult) {
                    $arguments = $beforeResult;
                }
                unset($pluginInstance, $pluginMethod);
            }
        }
        if (isset($pluginInfo[DefinitionInterface::LISTENER_AROUND])) {
            // Call 'around' listener
            $chain = $this->chain;
            $type = $this->subjectType;
            /** @var \Magento\Framework\Interception\InterceptorInterface $subject */
            $subject = $this;
            $code = $pluginInfo[DefinitionInterface::LISTENER_AROUND];
            $next = function () use ($chain, $type, $method, $subject, $code) {
                return $chain->invokeNext($type, $method, $subject, func_get_args(), $code);
            };
            $pluginInstance = $this->pluginList->getPlugin($this->subjectType, $code);
            $pluginMethod = 'around' . $capMethod;
            $result = $pluginInstance->$pluginMethod($this, $next, ...array_values($arguments));
            unset($pluginInstance, $pluginMethod);
        } else {
            // Call original method
            $result = parent::$method(...array_values($arguments));
        }
        if (isset($pluginInfo[DefinitionInterface::LISTENER_AFTER])) {
            // Call 'after' listeners
            foreach ($pluginInfo[DefinitionInterface::LISTENER_AFTER] as $code) {
                $result = $this->pluginList->getPlugin($this->subjectType, $code)
                    ->{'after' . $capMethod}($this, $result);
            }
        }
        return $result;
    }
    ....
}

A very basic example of a plugin usage could include modifying the getName() method in the Magento\Catalog\Model\Product class.

Assuming you have already set up the relevant module structure as seen here, add a di.xml from within the module’s etc directory. Within this file, you can add a plugin configuration within the pair of <type> nodes.


<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Model\Product">
        <plugin name="magento-catalog-product-plugin" type="Siphor\Custom\Plugin\Product" sortOrder="10"/>
    </type>
</config>

Within the module’s Product.php class, the afterGetName() method will be used to modify the behaviour of the original getName() method.

Here we are simply adding pipes before and after the name.


<?php
namespace Siphor\Custom\Plugin;

class Product {
    public function afterGetName(\Magento\Catalog\Model\Product $product, $result) {
        return "|" . $result . "|";
    }
}

When navigating to any product page, you should now see the product name has been modified correctly, letting us know that our example of using Magento 2 plugins has worked.

Magento 2 Plugins

Note: This article is based on Magento Community/Open Source version 2.1.