I needed to make some changes to the product list in magento 2.
So I went with this approach. Create a plugin on the Magento\Catalog\Block\Product\ListProduct::toHtml method and do my changes in there.
So far so good. It works nicely on the category view page, but it does not work on the search results list.
After some debuging I found out that on the category view page the class Magento\Catalog\Block\Product\ListProduct\Interceptor is used and on the search results list the class Magento\Catalog\Block\Product\ListProduct is used. That's why I don't get my plugin executed.

My question is, why is the class Magento\Catalog\Block\Product\ListProduct used directly and not the generated interceptor for the search results page?

  • Do you mean catalogsearch_result_index? – Daniel Dec 15 '20 at 11:03

I ended up declaring a plugin on a virtual type and it worked.
Yes, I know, this should not work. As Daniel explains this didn't work for me because the block declared in the catalogsearch_result_index.xml layout file is a virtual type of Magento\Catalog\Block\Product\ListProduct.

But to my surprise it works if I add a plugin to the virtual type.

<type name="Magento\Catalog\Block\Product\ListProduct">
    <plugin name="name-here" type="[Vendor]\[Module]\Plugin\Block\Product\ListProductPlugin" />

<virtualType name="Magento\CatalogSearch\Block\SearchResult\ListProduct">
    <plugin name="other-name-here" type="[Vendor]\[Module]\Plugin\Block\Product\ListProductPlugin" />

I'm still puzzled to why this works.

I think I got it.
This works because I have plugins on both regular class and virtual type. It will not work if I remove the plugin declaration on the actual class.
Here are some more details.

The plugin will work as long as the interceptor class is used instead of the original class.
So here is the piece of code that decides to use the interceptor or not: Magento\Framework\Interception\ObjectManager\Config\Developer::getInstanceType

public function getInstanceType($instanceName)
    $type = parent::getInstanceType($instanceName);
    if ($this->interceptionConfig && $this->interceptionConfig->hasPlugins($instanceName)
        && $this->interceptableValidator->validate($instanceName)
    ) {
        return $type . '\\Interceptor';
    return $type;

In this particular case, $instanceName is the virtual type name and $type is the actual class name. And the code above checks if there are any plugins declared on the virtual type name. If there is one it uses the interceptor for the real class.
SO basically declaring an interceptor on a virtual type does not necessarily means it works, it means the interceptor class will be used where the virtual type is declared. But the plugins declared on the virtual types will not be called...ever. Only the plugins on the actual class will be called.

  • I wonder if it's a misinterpretation and in the dev docs they mean the plugin won't work if the class is instantiated (called?) by a virtual type and plugins on virtual types directly are not meant. That would mean the docs should be more clear. – Daniel Dec 15 '20 at 15:47
  • 1
    @Daniel. I think I got it. You can see my edit in case you were puzzled too. – Marius Dec 16 '20 at 9:32
  • I wonder if it's similar in an production environment since Magento\Framework\Interception\ObjectManager\Config\Developer::getInstanceType is used in developer mode if I am not mistaken. – Daniel Dec 16 '20 at 11:49
  • 1
    I thought you were going to say that. Will check on production also, but I have a feeling that it will work because for production mode it should behave the same the only difference being that the class names used are pre-calculated – Marius Dec 16 '20 at 12:42

In vendor/magento/module-catalog-search/view/frontend/layout/catalogsearch_result_index.xml the block search_result_list is set to use the class Magento\CatalogSearch\Block\SearchResult\ListProduct which is a VirtualType defined in vendor/magento/module-catalog-search/etc/frontend/di.xml:

<virtualType name="Magento\CatalogSearch\Block\SearchResult\ListProduct" type="Magento\Catalog\Block\Product\ListProduct">
        <argument name="catalogLayer" xsi:type="object">Magento\Catalog\Model\Layer\Search</argument>

VirtualTypes use the classes directly not their Interceptors, that's why you cannot use plugins on it. See the DevDocs on more information about plugin limitations.

  • 1
    I saw the devdocks page, but the part about not being able to intercept virtual types is not explained very well.....specially since I found a way to make it work, and I did just this, add a plugin to a virtual type. Will post an answer soon – Marius Dec 15 '20 at 13:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.