Fun with Dependency Injection

Magento 2 uses constructor based dependency injection. I also comes with it's own implementation of dependency injection container. In etc/di.xml file we can not only specify preferences for specific interfaces:

  • we can define plugin methods that can intercept public method executions (altering parameters, return values or even completely skip method execution)
  • alter parameters injected to given types, more over define new types that can be injected using existing types with custom attribute configuration

Plugin Example

In plugin/interceptor example let assume that we need to capitalize all product names without changing data in our database. We can use after interceptor to change result of \Magento\Catalog\Api\Data\ProductInterface::getName in order to capitalize product names. This will affect all implementation of \Magento\Catalog\Api\Data\ProductInterface - assuming all modules installed in our store follow guidelines this should cover all cases of accessing product name throughout the store.

In etc/di.xml we will add new node called type to indicate class we want to intercept. Using plugin node we will define out interceptor class.

    <type name="Magento\Catalog\Api\Data\ProductInterface">
        <plugin name="capitalizeName" type="MMAcademy\MicroReview\Plugin\Product" />
    </type>

Body of our class is very simple: we define one method that accepts instance of type we are intercepting - all types of plugin take instance of currently intercepted object as first parameter - and result of intercepted method as second parameter. All we have to do is return modified result.

<?php

namespace MMAcademy\MicroReview\Plugin;

class Product
{
    /**
     * @param \Magento\Catalog\Api\Data\ProductInterface $product
     * @param                                            $name
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     * @return array
     */
    public function afterGetName(\Magento\Catalog\Api\Data\ProductInterface $product, $name)
    {
        return mb_strtoupper($name, 'UTF-8');
    }
}

This was example of after plugin that changes method result. We can also see before plugin that changes method parameters (all parameters are passed as parameters to our method, and we must return modified parameters as array). And around plugin that is called instead of executing method, we can still execute this method as closure is passed as second parameter and original parameters follows. With around plugins we can replicate before and after plugins but also completely change execution chain.

See official documentation

Configurable object via dependency injection

Let us consider how to design highly optimized product data fetcher. Based on what we have seen in EAV section - we would need an option to provide list of attributes we would like to be fetched. More over we would like to bulk fetch some information like product URL redirects or stock status that are not part of EAV data storage.

Let me suggest following class structure

<?php

class ProductDataFetcher {

    public function __construct(\Magento\Framework\App\ResourceConnection $resourceConnection, $attributes = [], $extra = []) {}

    public function fetch($productIds) {}
}

What we have here is class with method expecting array of product IDs in order to fetch them from database. But take closer look on constructor parameters:

  • we have \Magento\Framework\App\ResourceConnection connection manager object that will allow us to obtain database connection object for direct database access
  • we have attributes array of attribute names that we want to fetch
  • we have extra array of object that will be responsible for fetching extra data

Having such class definition allows us to define what attributes will be processed by given product data fetcher instance and what enhancers we need to use (not all cases require us to fetch stock status, product URL or product prices)

Now we can use virtualType in dependency injection configuration to define cases we want to use product data fetcher for.

    <virtualType name="reviewProductFetcher" type="MMAcademy\MicroReview\Fetcher\Product">
        <arguments>
            <argument name="attributes" xsi:type="array">
                <item name="name" xsi:type="string">name</item>
                <item name="description" xsi:type="string">description</item>
                <item name="name" xsi:type="string">name</item>
            </argument>
            <argument name="extra" xsi:type="array">
                <item name="price" xsi:type="object">MMAcademy\MicroReview\Fetcher\Product\PriceEnhancer</item>
                <item name="url" xsi:type="object">MMAcademy\MicroReview\Fetcher\Product\UrlEnhancer</item>
            </argument>
        </arguments>
    </virtualType>

Here we did declare virtual instance of our class. Now we have a names instance with specific constructor parameters. Note that using xsi:type attribute we can control what type is being injected - especially with argument extra and type object we can specify class name and object of that class will be injected in this place.

Now we can specify that block that is expecting instance of our product data fetcher should be given specific pre configured instance.

    <type name="MMAcademy\MicroReview\Block\Recent">
        <arguments>
            <argument name="fetcher" xsi:type="object">reviewProductFetcher</argument>
        </arguments>
    </type>

See official documentation

results matching ""

    No results matching ""