Categories


Archives


Recent Posts


Categories


Magento 2 VirtualTypes at Runtime

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Updated for Magento 2! No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

Whether you’re using your IDE, a debugging extension, or calling get_class and new ReflectionClass yourself, PHP (or any language’s) ability to examine itself at runtime is a vital tool for debugging a program.

Most bugs come down to “this variable does not have what I thought it had in it”, or “the thing in this variable behaves differently than I thought it did”. Being able to dump out the contents of a variable exactly when the bug happens during program execution is the quickest way to get to the bottom of any problem.

If you’re a seasoned debugger, the virtual types in Magento’2 di.xml system might throw you for a loop. You define a virtual type like this

<virtualType name="pageConfigRenderPool" type="MagentoFrameworkViewLayoutReaderPool">
    <arguments>
        <argument name="readers" xsi:type="array">
            <item name="html" xsi:type="string">MagentoFrameworkViewPageConfigReaderHtml</item>
            <item name="head" xsi:type="string">MagentoFrameworkViewPageConfigReaderHead</item>
            <item name="body" xsi:type="string">MagentoFrameworkViewPageConfigReaderBody</item>
        </argument>
    </arguments>
</virtualType>

This virtual type (named pageConfigRenderPool) is based on the MagentoFrameworkViewLayoutReaderPool class and will behave identical to this class except that Magento will pass different objects to its constructor (based on the values in <arguments/>).

Once defined, the only place you can use this virtual type is in other di.xml configurations. You can see the pageConfigRenderPool virtual type used here

<type name="MagentoFrameworkViewResultPage">
    <arguments>
        <argument name="layoutReaderPool" xsi:type="object">pageConfigRenderPool</argument>
        <argument name="generatorPool" xsi:type="object">MagentoBackendModelViewLayoutGeneratorPool</argument>
        <argument name="template" xsi:type="string">Magento_Theme::root.phtml</argument>
    </arguments>
</type>

When Magento (via the object manager) wants to instantiate a MagentoFrameworkViewResultPage object, it first instantiates the $layoutReaderPool constructor parameter. When this happens, Magento will instantiate the virtual type named pageConfigRenderPool instead of the type indicated by the type hint.

If you’re not familiar with all this, the Object Manager series is a good place to start.

The Debugging Problem

The sticky wicket in all this is PHP has no awareness whether or not a variable came from an regular type, or a virtual type. When you peek at the $layoutReaderPool’s class

var_dump(get_class($layoutReaderPool));

you see it’s a MagentoFrameworkViewLayoutReaderPool. There’s nothing to indicate that this is a different MagentoFrameworkViewLayoutReaderPool object, whose constructor arguments are different. Additionally, every instinct you have as a programmer is to assume two objects of type MagentoFrameworkViewLayoutReaderPool will behave the same, or if they behave differently that differences is because of something code, not configuration, did.

Even if you’re savvy enough to check di.xml for a MagentoFrameworkViewLayoutReaderPool type definition, you need to know which object’s constructor $layoutReaderPool was instantiated for.

For me personally, this has led to a year’s worth of on and off frustration with the following method

#File: vendor/magento/framework/View/Layout/ReaderPool.php
public function interpret(ReaderContext $readerContext, LayoutElement $element)
{            
    $this->prepareReader($this->readers);
    /** @var $node LayoutElement */
    foreach ($element as $node) {
        $nodeName = $node->getName();
        if (!isset($this->nodeReaders[$nodeName])) {
            continue;
        }
        /** @var $reader LayoutReaderInterface */
        $reader = $this->nodeReaders[$nodeName];
        $reader->interpret($readerContext, $node, $element);
    }
    return $this;
}

The individual reader objects have their own ReaderPool objects which can recursively call back to this interpret method. Recursion is tricky enough to keep track of, but when there’s individual object’s whose instantiation is hidden away from view via virtual types the debugging becomes almost impossible.

Maybe this will get easier over time, but most agency developers will likely side-step all this, and never understand how these systems work.

The main takeaway for us – whenever we’re considering a class’s definition file, we need to search through the di.xml files for its <type/> definition(s), as well as any virtual types that use it as an virtual-type-ancestor. Thinking out loud, you’ll also need to do this for any of the class’s parent classes as well.

If I stay on the Magento 2 path, expect to see something in Commerce Bug that reflects this.

Copyright © Alan Storm 1975 – 2019 All Rights Reserved

Originally Posted: 1st April 2017