Interception

A plugin changes a public method from outside the class, and Magento generates an interceptor subclass to make that happen.

Syntax<plugin name="unique_name" type="Vendor\Module\Plugin\ClassName" sortOrder="10" disabled="false"/>

A plugin is a class that runs code before, after or around a public method of another class, without editing that class. Magento calls the mechanism interception. It exists so your module can change core behaviour and still survive an upgrade: the core file is untouched, so a patch to it cannot conflict with your change.

How it works

You declare the plugin in di.xml, the file that configures object creation for a module (a folder under app/code/Vendor/Module with its own etc directory). Magento then writes an interceptor: a generated subclass of the target, saved under generated/code/. Every public method of the parent is overridden with the same shape. Ask the plugin list whether anything is registered for this method, call the plugins if so, otherwise call parent.

The object manager, the service that builds objects for you, hands out that subclass wherever the original type is requested. Code that injects the interface receives the wrapped object and never knows.

What trips people up

Merged DI configuration is cached. A new plugin does nothing until you run bin/magento cache:clean config. In production mode the interceptors are written once by bin/magento setup:di:compile rather than on demand, so a plugin added without recompiling stays invisible.

One shorthand to recognise: in PHP, Vendor\Module\Plugin\Name::class and a string holding that same fully qualified name are the same value. In di.xml you always write the plain name with single backslashes.

Example

Example · xml
<!-- app/code/Vendor/Greeting/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\Message\ManagerInterface">
        <plugin name="vendor_greeting_prefix_success_message"
                type="Vendor\Greeting\Plugin\PrefixSuccessMessage"
                sortOrder="10"/>
    </type>
</config>
<!-- Apply with: bin/magento cache:clean config -->

When to use it

  • Append a note to a core storefront message without copying the core class.
  • Log the arguments a core service receives while tracking down a bug.
  • Change what a core method returns from inside your own module, so an upgrade does not undo it.

More examples

The plugin class the declaration points at

The method name is the target method with before prefixed and the first letter capitalised. The first argument is always the object being wrapped, by convention named $subject. After cache:clean config, a storefront success message such as the one shown after adding a product to the cart reads "Thanks. You added ... to your shopping cart."

Example · php
<?php
// app/code/Vendor/Greeting/Plugin/PrefixSuccessMessage.php
declare(strict_types=1);

namespace Vendor\Greeting\Plugin;

use Magento\Framework\Message\ManagerInterface;

class PrefixSuccessMessage
{
    /**
     * @param ManagerInterface $subject
     * @param string|\Magento\Framework\Phrase $message
     * @param string|null $group
     * @return array
     */
    public function beforeAddSuccessMessage(
        ManagerInterface $subject,
        $message,
        $group = null
    ): array {
        return ['Thanks. ' . $message, $group];
    }
}

What Magento generates for you

Read this once and the rest of the subject follows from it. The interceptor is a subclass, it keeps the parent constructor signature, and it asks the plugin list per method call. Delete the generated folder and it is written again; in production mode setup:di:compile writes it ahead of time.

Example · php
<?php
// generated/code/Magento/Framework/Message/Manager/Interceptor.php
// Written by Magento, abridged to one method.

namespace Magento\Framework\Message\Manager;

class Interceptor extends \Magento\Framework\Message\Manager implements
    \Magento\Framework\Interception\InterceptorInterface
{
    use \Magento\Framework\Interception\Interceptor;

    public function __construct(...)
    {
        $this->___init();
        parent::__construct(...);
    }

    public function addSuccessMessage($message, $group = null)
    {
        $pluginInfo = $this->pluginList->getNext($this->subjectType, 'addSuccessMessage');

        return $pluginInfo
            ? $this->___callPlugins('addSuccessMessage', func_get_args(), $pluginInfo)
            : parent::addSuccessMessage($message, $group);
    }
}

Limiting a plugin to the storefront

The same file in etc/ applies everywhere; in etc/frontend/ it applies only in the frontend area, and in etc/adminhtml/ only in the admin. Narrow is better: admin grids then pay nothing for a storefront change. Apply it with bin/magento cache:clean config.

Example · xml
<!-- app/code/Vendor/Greeting/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\Framework\Message\ManagerInterface">
        <plugin name="vendor_greeting_prefix_success_message"
                type="Vendor\Greeting\Plugin\PrefixSuccessMessage"/>
    </type>
</config>

Discussion

  • Be the first to comment on this lesson.