2 minute read

Magento 2 plugins are fundamental parts of the system. Magento plugins are the name for interceptors and plugin code is autogenerated and stored in the /generation folder. It was discussed many times in conferences that plugins are now the preferred way to extend the functionality of the core rather than overrides (preferences).

Even the core itself is using plugins. One example is Magento\Bundle\Controller\Adminhtml\Product\Initialization\Helper\Plugin\Bundle

This plugin (interceptor) is intercepting Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper to initialise bundle item data on the product in the admin. However, what happens when we have to change the way an interceptor works? Plugins are not instantiated through Objectmanager. Interceptors are generated code, rather than individual classes, so there’s no traditional way to set a preference for them and override functionality.

One way to change functionality of a plugin is to disable it entirely and redefine a plugin for the same type. To disable a plugin, one can easily do it using the disable attribute. Place this code in app/code/{Vendor}/{Module}/etc/adminhtml/di.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper">
        <plugin name="Bundle" disabled="true" />
    </type>
</config>

This code works, because the original xml is defining the plugin name “Bundle” for the same type, which can be disabled:

?xml version="1.0"?>
<!--
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper">
        <plugin name="Bundle" type="Magento\Bundle\Controller\Adminhtml\Product\Initialization\Helper\Plugin\Bundle" sortOrder="60" />
    </type>
    ...
</config>

Depending on what are the requirements, it’s also possible to set the sortOrder attribute for our plugin to a smaller number and return earlier from our “before” interceptor preventing the next plugin in the queue to run. However, just for example, let’s stay with the original idea of overriding the plugin by disabling the original. There’s a known bug in Magento 2 in “processBundleOptionsData” preventing to save the product when there are more than 20 bundle items in a single product. To fix this issue, we are going to override this method in our class:

<?php
namespace Vendor\Module\Plugin;

class AdminHelperBundle extends \Magento\Bundle\Controller\Adminhtml\Product\Initialization\Helper\Plugin\Bundle
{

    protected function processBundleOptionsData(\Magento\Catalog\Model\Product $product)
    {
        $bundleOptionsData = $product->getBundleOptionsData();
        if (!$bundleOptionsData) {
            return;
        }
        $options = [];
        foreach ($bundleOptionsData as $key => $optionData) {
            // Mods
            if (isset($optionData['delete']) && (bool)$optionData['delete']) {
                continue;
            }

            $option = $this->optionFactory->create(['data' => $optionData]);
            $option->setSku($product->getSku());
            $option->setOptionId(null);

            $links = [];
            $bundleLinks = $product->getBundleSelectionsData();
            if (empty($bundleLinks[$key])) {
                continue;
            }

            foreach ($bundleLinks[$key] as $linkData) {
                // Mods
                if (isset($linkData['delete']) && (bool)$linkData['delete']) {
                    continue;
                }
                $link = $this->linkFactory->create(['data' => $linkData]);

                if ((int)$product->getPriceType() !== \Magento\Bundle\Model\Product\Price::PRICE_TYPE_DYNAMIC) {
                    if (array_key_exists('selection_price_value', $linkData)) {
                        $link->setPrice($linkData['selection_price_value']);
                    }
                    if (array_key_exists('selection_price_type', $linkData)) {
                        $link->setPriceType($linkData['selection_price_type']);
                    }
                }

                $linkProduct = $this->productRepository->getById($linkData['product_id']);
                $link->setSku($linkProduct->getSku());
                $link->setQty($linkData['selection_qty']);

                if (array_key_exists('selection_can_change_qty', $linkData)) {
                    $link->setCanChangeQuantity($linkData['selection_can_change_qty']);
                }
                $links[] = $link;
            }
            $option->setProductLinks($links);
            $options[] = $option;
        }

        $extension = $product->getExtensionAttributes();
        $extension->setBundleProductOptions($options);
        $product->setExtensionAttributes($extension);
        return;
    }
}

Then the full etc/adminhtml/di.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper">
        <plugin name="Bundle" disabled="true" />
        <plugin name="Homeo_Mods_Bundle" type="Vendor\Module\Plugin\AdminHelperBundle" sortOrder="60" disabled="false"/>
    </type>
</config>

This cause the original plugin to be replaced by the custom one, which is extending the original class and overriding the “processBundleOptionsData” method.

As you can see, plugins can be overridden as well, but it’s better to think about this as we are reordering or disabling them rather than overriding. It’s also impossible to define a plugin (interceptor) around another plugin. The ObjectManager does not instantiate plugins, therefore the usual rules do not apply to them. I hope this can save someone time and help to understand basic concepts of the Magento framework.

Updated:

Comments