Plugin, also called an interceptor, is a class that modifies the behavior of public class functions by intercepting a function call and running code before, after, or around that function call. This allows you to substitute or extend the behavior of original, public methods for any class or interface.

As a very powerful feature of Magento 2, plugins:

  • Are used to change the behavior of public class functions.
  • Can not be used for final classes, final methods, non-public classes, static methods, construct functions, virtual types, and objects that are instantiated before Magento\Framework\Interception is bootstrapped.

Plugins allow you to customize a single method which it can be called before, after, and around the method. To avoid conflicts, plugins for the same class are executed in sequence based on their sort order.

A plugin can be declared in the di.xml file (global, or area-based) of your module with the following structure:

<config>
    <type name="{ObservedType}">
      <plugin name="{pluginName}" type="{PluginClassName}" sortOrder="1" disabled="false" />
    </type>
</config>

The following elements must be specified when declaring a plugin:

  • Type name: a class, interface that the plugin observes.
  • Plugin name: An arbitrary plugin name that identifies a plugin. Also used to merge the configurations for the plugin.
  • Plugin type: The name of a plugin’s class or its virtual type. Use the following naming convention when you specify this element: \Vendor\Module\Plugin\<ClassName>

Additional attributes:

  • sortOrder: Plugins that call the same method will be run based on sort order.
  • disabled: To disable a plugin, set this element to true. The default value is false.

Here is an example with a simple syntax:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Checkout\Model\ShippingInformationManagement">
   <plugin name="save-in-quote" type="Magenest\GiftRegistry\Plugin\Checkout\ShippingInformationManagement" sortOrder=”1” disabled=”false”/>
</type>
</config>

This article will use code snippets from our Magento 2 Gift Registry extension, which helps customers create a gift registry list and share it with their acquaintances.

Types of plugins

As mentioned, there are three types of Magento 2 plugins: before, after, and around.

1. Before plugin

Before method is used to change arguments of a method or add some behaviors before method is called. Magento run all before methods ahead of the call to the observed method. Before plugin has access to parameters passed to the methods. These methods must have the same name as observed method with ‘before’ prefix.

Below is an example of before method checking whether the buyer of the item in a gift registry sends greetings to the owner of the gift registry.

public function beforeSaveAddressInformation(
\Magento\Checkout\Model\ShippingInformationManagement $subject,
$cartId,
\Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
){
$return = [$cartId, $addressInformation];
$address = $addressInformation->getShippingAddress();
if($address instanceof \Magento\Quote\Api\Data\AddressInterface ){
	$extAttributes = $address->getExtensionAttributes();
	$giftregistryMessage = $extAttributes->getGiftregistryMessage();
	if(!isset($giftregistryMessage) || $giftregistryMessage == ""){
    	$extAttributes->setGiftregistryMessage(__("I wish you a wonderful Birthday!"));
	}
}
return $return;
}

2. After plugin

After method changes the values returned by the origin method or add behaviors after the original method is called. Magento runs all after methods following the completion of the original method. After plugins have access to the result of the original method. Since Magento 2.2 after plugins also have access to input parameters.

The ‘after’ prefix must be added to the name of this method. For example:

public function afterSaveAddressInformation(
\Magento\Checkout\Model\ShippingInformationManagement $subject,
$result,
$cartId,
\Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
){
$address = $addressInformation->getShippingAddress();
if($address instanceof \Magento\Quote\Api\Data\AddressInterface ){
	$extAttributes = $address->getExtensionAttributes();
	$giftregistryMessage = "From Magenest store: ".$extAttributes->getGiftregistryMessage();
	if(!isset($giftregistryMessage) || $giftregistryMessage == ""){
    	$extAttributes->setGiftregistryMessage($giftregistryMessage);
	}
}
return $result;
}

3. Around plugin

Around methods can change both arguments and return values of an original method or add behaviors before/after the method is called. The ‘around’ prefix is added to the name of this method.

Example:

public function aroundSaveAddressInformation(
\Magento\Checkout\Model\ShippingInformationManagement $subject,
\Closure $proceed,
$cartId,
\Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
) {
$address = $addressInformation->getShippingAddress();
if($address instanceof \Magento\Quote\Api\Data\AddressInterface ){
	try{
    	$extAttributes = $address->getExtensionAttributes();
    	$giftregistryMessage = $extAttributes->getGiftregistryMessage();
    	$giftregistryId = $this->_helperCart->getRegistryId();
    	/** @var \Magento\Quote\Model\Quote $quote */
    	$quote = $this->_quoteRepository->getActive($cartId);
    	$quoteId = $quote->getId();
    	$tranModel = $this->_tranFactory->create();
    	$this->_tranResource->load($tranModel,$quoteId,'quote_id');
    	$tranModel->addData([
        	'giftregistry_id' => $giftregistryId,
        	'message' => $giftregistryMessage,
        	'quote_id' => $quoteId
    	]);
    	$this->_tranResource->save($tranModel);
	}catch (\Exception $exception){
    	$this->_logger->critical($exception->getMessage());
	}
}
return $proceed($cartId, $addressInformation);
}

When you wrap a method that accepts arguments, your plugin must also accept those arguments and you must forward them when you invoke the proceed callable. You must be careful to match the default parameters and type hints of the original signature of the method.

Plugin execution order

A plugin has execution order as before method, around method, origin method, and after method.

If plugins apply to the same method, Magento executes before method from lowest to highest sortOrder. If a plugin includes multiple methods, after Magento completes the execution of the before method(s) in the plugin, it will call the around method in the same plugin until callable() before searching for the next before method in the execution stack.

 Plugin APlugin BPlugin CAction
sortOrder102030 
beforebeforeDispatch()1beforeDispatch()   2 beforeDispatch()   4  
around (first half) aroundDispatch() [first half]   3 aroundDispatch() [first half]   5  
original   dispatch()   6
around (second half) aroundDispatch() [second half]   9 aroundDispatch() [second half]   7  
afterafterDispatch()   11 afterDispatch()   10 afterDispatch()   8  

Here we have 3 plugins observing the same method, with each plugin having different types.

The execution flow will be as follows:

  • Plugin A: will run beforeDispatch() method first
  • Plugin B: beforeDispatch() method run after method of PluginA finish
  • Plugin B: aroundDispatch() (Magento calls the first half until callable)
  • Plugin C: beforeDispatch()
  • Plugin C: aroundDispatch() (Magento calls the first half until callable)

                        . Action: dispatch()

  • Plugin C: aroundDispatch() (Magento calls the second half after callable)
  • Plugin C: afterDispatch()
  • Plugin B: aroundDispatch() (Magento calls the second half after callable)
  • Plugin B: afterDispatch()
  • Plugin A: afterDispatch()

( execution order is written in bold in the table )

Summary

In this article, we’ve learned about the purpose of Magento 2 plugins, how to declare, define and use them. Plugins are commonly used in Magento 2 development, and we hope they can assist you in designing the features you need.