Magento 2 plugin Interceptors system. A class which will intercept and modify the behaviour of a public method is referred to as plugins.

Magento 2 plugin Interceptors system:

A class that intercepts and modifies the behavior of a public method is referred to as a plugin.

  • A plugin(interceptor), is a class that modifies the behaviour of public class method by intercepting a method call
  • Intercepting a method call can be achieved by executing code before, after, or around that method call.
  • This allows you to substitute or extend the behaviour of original, public methods for any class or interface.

In other way plugin does:

  • Modify the return value of any function call
  • Modify the arguments of any function call
  • Modifying a method while other modules are doing the same thing to the same function

The concept of rewriting in Magento 1 still exists in Magento 2 through class preferences and argument replacement. However, Magento 2 introduces the Interceptors system to address rewrite issues that were not present in Magento 1. Most common issue of magento1 rewrite was, each class is only “rewritable” by a single module. Once one module claims a method, that means other modules are not able do anything. Conflicts arise between modules, requiring manual resolution by someone with extensive knowledge of both Magento and the resolution of conflicting rewrites; otherwise, it can lead to challenges.

But plugins of magento2 has some limitation:

Plugins(interceptor) cannot be used with any of the following case:

  1. __construct
  2. Static methods
  3. Final methods
  4. Non-public methods
  5. Final classes
  6. Virtual types
  7. Objects that are instantiated before the Magento\Framework\Interception is bootstrapped
  8. Any class which contains at least one final public method

Plugin declaration:

di.xml is responsible for declaring plugin class object of your module.

<config>
    <type name="observed_type">
        <plugin name="plugin_name" type="plugin_class_name" sortOrder="sort_order_number" disable="true|false"></plugin>
    </type>
</config>

Attributes of above declaration:

Mandatory Attributes:

  • type name: The class or interface to which your module wants interfere
  • plugin name: An unique name that identifies your plugin name. It also used to merge the configurations for the plugin
  • plugin type: Name of plugin’s class or its virtual type. e.g: \Vendor\Module\Plugin\<ModelName>\Plugin

Optional Attributes:

  • plugin sortOrder: on the order that call same method will run
  • plugin disable: for disabling a plugin either your or third party plugin or core

Define your plugin:

Defining your plugin can be done in different situation( before, after and around). First argument of all these is an object that provides access to all public methods that your plugin observing.

Before:

This methods runs before the observed method. This methods modifies the arguments of observed method. Return all arguments in an array if there are multiple arguments, and use “null” to indicate that the argument should not be changed. The method’s name will be the same as the observed method with the prefix “before,” for example, beforeObservedMethod.

Through below example I am going to modify parameter value.

<?php
namespace \Vendor\Module\Plugin\ModelName\;

class ProductPlugin
{
	public function beforeSetPrice(\Magento\Catalog\Model\Product $subject, $price)
	{
	    return [$price + 1];
	}
}

After:

The method executes after the observed method, modifying its result and requiring a return value. It bears the same name as the observed method, prefixed with “after,” for example, afterObservedMethod.

Through below example I am going to modify result value.

<?php
namespace \Vendor\Module\Plugin\ModelName\;

class ProductPlugin
{
	public function afterGetPrice(\Magento\Catalog\Model\Product $subject, $result)
	{
	    return $result + 1;
	}
}

Around:

This method operates both before and after the observed method, providing the capability to completely override it. Like the others, its name mirrors the observed method with the prefix “around,” such as aroundObservedMethod.

Before the list of original method call ,around method call callable to allow a call to next method in chain and if not call to callable then it will prevent execution of next method in chain and original method call.

<?php
namespace \Vendor\Module\Plugin\ModelName;

class ProductPlugin
{
	public function aroundSave(\Magento\Catalog\Model\Product $subject, callable $proceed)
	{
		doSomethingBeforeGettingSave
		$returnValue = $proceed();
		if ($returnValue){
			$this->doSomeOtherWork()
		}
		
		return $returnValue;
	}
}

As you can see both “before , after” have same format to modify a observed method but “around” has a little different like “callable” which does the work of prevent and allow to execute the next method in chain. You can use the same argument in your plugin class as the observed class’s argument. If the observed method assigns a null value to the argument, use the argument in your plugin class, as plugins do not accept null.

For example a method “save” like below and you want rewrite by plugin then:

<?php
namespace \Magento\Catalog\Model;

class Product
{
	public function save(anyType $obj = null)
	{
		//method defination
	}
}

but in your plugin class you can use like:

<?php
namespace \Vendor\Module\Plugin\ModelName;

class ProductPlugin
{
	public function aroundSave(\Magento\Catalog\Model\Product $subject, callable $proceed, anyType $obj)
	{
		doSomethingBeforeGettingSave
		$returnValue = $proceed();
		if ($returnValue){
			$this->doSomeOtherWork()
		}
		
		return $returnValue;
	}
}

Not like “public function aroundSave(\Magento\Catalog\Model\Product $subject, callable $proceed, anyType $obj = null)“. If you will use like this then PHP will through fatal error.

Prioritizing your plugins:

“Before” to “around”:

The plugin class you created depends upon the “sortOrder” attribute/property. All plugins will be executed depending upon lowest to highest “sortOrder” prior to execution of observed method. The “before” method will execute first, and the “around” method will be executed depending on the “sortOrder.”

“Around” to “Before”:

The plugin class you created depends upon the “sortOrder” attribute/property. All plugins will be executed depending upon highest to lowest “sortOrder” prior to execution of observed method. The “around” method will execute first, and then the “after” method will be executed depending on the “sortOrder.”

Flow example:

Magento follows these rules for executing plugins during each plugin execution in two primary flows:

Prior to executing the observed method, Magento follows a sequence from the lowest to the highest sortOrder.
    Magento executes the current plugin’s before method.
    Subsequently, the current plugin's around method is invoked.
        The first part of the plugin’s around method is executed.
        The around method executes the callable.
            If another plugin is present in the chain, all subsequent plugins are encapsulated in an independent sequence loop, initiating another execution flow.
            If the current plugin is the last in the chain, the observed method is executed.
        The second part of the around method is executed.
    Magento proceeds to the next plugin.
Following the execution flow, beginning from the lowest to the highest sortOrder in the current sequence plugins loop.
    The current plugin after method is executed.
    Magento proceeds to the next plugin.

Scenario A

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

Scenario B (with a callable around)

PluginB::aroundDispatch() defines the $next argument with a callable type.

For example:

PluginA::beforeDispatch()
PluginB::beforeDispatch()
PluginB::aroundDispatch() (Magento calls the first half before callable)
	PluginC::beforeDispatch()
		Action::dispatch()
	PluginC::afterDispatch()
PluginB::aroundDispatch() (Magento executes the second half after calling the callable.)
PluginA::afterDispatch()
PluginB::afterDispatch()

Scenario B (without a callable around)

PluginB::aroundDispatch() does not define the ($next)

PluginA::beforeDispatch()
PluginB::beforeDispatch()
	PluginB::aroundDispatch()
PluginA::afterDispatch()
PluginB::afterDispatch()

Because the callable type for the $next argument is absent, Action::dispatch() will not be called and Plugin C will be not triggered.

Scenario C

Assuming these methods:

PluginA::beforeDispatch()
PluginA::aroundDispatch() (Magento executes the first part until the callable is invoked.)
	PluginB::beforeDispatch()
	PluginC::beforeDispatch()
	PluginC::aroundDispatch() (Magento executes the first part until the callable is invoked.)
		Action::dispatch()
	PluginC::aroundDispatch() (Magento calls the second half after callable)
	PluginB::afterDispatch()
	PluginC::afterDispatch()
PluginA::aroundDispatch() (Magento calls the second half after callable)
PluginA::afterDispatch()

Hope you may get clear on “Magento 2 plugin Interceptors system“. Thanks for visiting and see you again on another article.

Get your full code from here Source Code

Magento 2 plugin Interceptors system
Tagged on:                     

Leave a Reply

Your email address will not be published. Required fields are marked *