Creating a Dynamic Magento Plugin with virtualType and Dependency Injection (DI) and Tests

In Magento 2, leveraging Dependency Injection (DI) and virtualType allows developers to create dynamic and flexible customizations without altering core functionality. This guide demonstrates how to create a Magento plugin that modifies product behavior based on a custom flag, enablePlugin, using virtualType and includes unit tests to validate the functionality.

Note: This approach is primarily theoretical, designed to showcase the flexibility of Magento’s plugin and virtualType systems. In practical applications, simpler implementations may suffice.


Step 1: Extend the Product Class

Create a custom product class that extends Magento\Catalog\Model\Product and includes a boolean flag enablePlugin.

File: app/code/YourVendor/YourModule/Model/CustomProduct.php

namespace YourVendor\YourModule\Model;

use Magento\Catalog\Model\Product;

class CustomProduct extends Product
{
    private $enablePlugin = false;

    /**
     * Set the enablePlugin value.
     *
     * @param bool $enablePlugin
     * @return void
     */
    public function setEnablePlugin(bool $enablePlugin): void
    {
        $this->enablePlugin = $enablePlugin;
    }

    /**
     * Get the enablePlugin value.
     *
     * @return bool
     */
    public function getEnablePlugin(): bool
    {
        return $this->enablePlugin;
    }
}

Step 2: Configure virtualType in di.xml

Define virtualTypes in the di.xml configuration to create variations of the CustomProduct class with different enablePlugin values.

File: app/code/YourVendor/YourModule/etc/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<virtualType name="CustomProductWithPlugin" type="YourVendor\YourModule\Model\CustomProduct">
<arguments>
<argument name="enablePlugin" xsi:type="boolean">true</argument>
</arguments>
</virtualType>
<virtualType name="CustomProductWithoutPlugin" type="YourVendor\YourModule\Model\CustomProduct">
<arguments>
<argument name="enablePlugin" xsi:type="boolean">false</argument>
</arguments>
</virtualType>
</config>
<type name="YourVendor\YourModule\Model\ProductRepository">
<arguments>
<argument name="product" xsi:type="object">CustomProductWithPlugin</argument>
</arguments>
</type>

Step 3: Create the Plugin

Develop a plugin that modifies the product’s behavior based on the enablePlugin flag. For demonstration, we’ll append a suffix to the product name if enablePlugin is true.

File: app/code/YourVendor/YourModule/Plugin/ProductPlugin.php

namespace YourVendor\YourModule\Plugin;

use Magento\Catalog\Api\Data\ProductInterface;

class ProductPlugin
{
    /**
     * Modify the product name after it is retrieved if enablePlugin is true.
     *
     * @param ProductInterface $subject
     * @param string $result
     * @return string
     */
    public function afterGetName(ProductInterface $subject, string $result): string
    {
        if (!$subject->getEnablePlugin()) {
            return $result;
        }

        // Append a custom suffix to the product name
        return $result . ' - Custom Suffix';
    }
}

Step 4: Write Unit Tests

Implement unit tests to verify that the plugin behaves as expected when enablePlugin is true or false.

File: app/code/YourVendor/YourModule/Test/Unit/Plugin/ProductPluginTest.php

namespace YourVendor\YourModule\Test\Unit\Plugin;

use PHPUnit\Framework\TestCase;
use YourVendor\YourModule\Plugin\ProductPlugin;
use YourVendor\YourModule\Model\CustomProduct;

class ProductPluginTest extends TestCase
{
    public function testAfterGetNameWithPluginEnabled()
    {
        $product = $this->createMock(CustomProduct::class);
        $product->method('getEnablePlugin')->willReturn(true);
        $product->method('getName')->willReturn('Original Name');

        $plugin = new ProductPlugin();
        $result = $plugin->afterGetName($product, $product->getName());

        $this->assertEquals('Original Name - Custom Suffix', $result);
    }

    public function testAfterGetNameWithPluginDisabled()
    {
        $product = $this->createMock(CustomProduct::class);
        $product->method('getEnablePlugin')->willReturn(false);
        $product->method('getName')->willReturn('Original Name');

        $plugin = new ProductPlugin();
        $result = $plugin->afterGetName($product, $product->getName());

        $this->assertEquals('Original Name', $result);
    }
}

Practical Considerations

While this approach demonstrates Magento’s flexibility, it introduces complexity that may not be necessary for all scenarios. In many cases, a single plugin with conditional logic based on product attributes can achieve similar results with less overhead. It’s essential to assess the specific needs of your project before implementing such a design.


By following this guide, you’ve explored how to create a dynamic Magento plugin using virtualType and DI, with unit tests to ensure functionality. This theoretical approach showcases the potential of Magento’s architecture for creating modular and adaptable solutions.


Join the Conversation

Have thoughts about this approach? Share your experience or feedback!

  • What do you think about using virtualType in this way? Have you implemented a similar solution, or do you prefer alternative approaches?
  • Are there scenarios where this approach would be particularly useful for your projects?
  • What challenges have you faced when combining plugins and virtualType in Magento?

Feel free to leave a comment below or start a discussion. Your insights can help others in the Magento development community!