Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Plugins

A plugin is a class that intercepts a call to a public method of another class. It can change the arguments before the call, the result after it, or wrap the call completely. This is the main way to change core behaviour without touching core files.

Where a plugin lives

Plugin classes go into the module's Plugin folder and are attached in di.xml. The files are created by hand.

app/code/Acme/Hello/
├── Plugin/
│   └── ProductNamePlugin.php   new file
└── etc/
    ├── module.xml
    └── frontend/
        ├── di.xml              new file
        └── routes.xml

An after plugin

Let's append the word "Acme" to the name of every product on the storefront. The name is returned by the getName() method of the Magento\Catalog\Model\Product class.

// app/code/Acme/Hello/Plugin/ProductNamePlugin.php
namespace Acme\Hello\Plugin;
 
use Magento\Catalog\Model\Product;
 
class ProductNamePlugin
{
    public function afterGetName(Product $subject, $result)
    {
        return $result . ' (Acme)';
    }
}

The method name is built from the after prefix and the name of the intercepted method with a capital letter. The first parameter is the object whose method was called, the second is the result of the original method. Whatever the plugin returns goes to the calling code.

Registration

<?xml version="1.0"?>
<!-- app/code/Acme/Hello/etc/frontend/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Model\Product">
        <plugin name="acme_hello_product_name" type="Acme\Hello\Plugin\ProductNamePlugin" sortOrder="10"/>
    </type>
</config>

Run bin/magento cache:clean and open any product page: "(Acme)" is appended to the name. The file is in etc/frontend, so names in the admin panel stay as they were. The sortOrder attribute sets the order when there are several plugins.

A before plugin

A method with the before prefix receives the arguments of the original method and can change them:

// app/code/Acme/Hello/Plugin/ProductNamePlugin.php
public function beforeSetName(Product $subject, $name): array
{
    return [trim((string) $name)];
}

Before every setName() call, spaces are trimmed from both ends of the name. The plugin returns an array of new arguments, or null if they need no change.

An around plugin

An around method wraps the original call completely and decides whether to run it:

// app/code/Acme/Hello/Plugin/ProductSavePlugin.php
namespace Acme\Hello\Plugin;
 
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Framework\Exception\LocalizedException;
 
class ProductSavePlugin
{
    public function aroundSave(
        ProductRepositoryInterface $subject,
        callable $proceed,
        ProductInterface $product,
        $saveOptions = false,
    ) {
        if ($product->getPrice() < 0) {
            throw new LocalizedException(__('The price cannot be negative.'));
        }
 
        return $proceed($product, $saveOptions);
    }
}

The $proceed parameter is the original method together with the remaining plugins. A product with a negative price is not saved: an error message appears instead. The plugin is registered the same way, but for the Magento\Catalog\Api\ProductRepositoryInterface type and in the global etc/di.xml.

Which kind to choose

KindWhen to use it
beforeTo change the arguments
afterTo change the result
aroundTo cancel the call or run code before and after it

An around plugin is the heaviest: it complicates debugging and gets in the way of other plugins. Use it only when the other two are not enough.

Limitations

A plugin cannot be attached to private and protected methods, to static methods, to a constructor, or to classes and methods marked final.

Common mistake

An around plugin forgot to call $proceed or to return its result. The original method and all following plugins silently stop working, and the calling code receives null. Unless the call must be cancelled, an around plugin always ends with return $proceed(...) and the same arguments.

Thanks for reading!

Donate to the site — we work hard for you.