What do I do when an extension overwrites a class globally and I want to use the original?

38

5

We're using an extension which globally overwrites the Mage_Catalog_Block_Product_List_Toolbar block.

<global>
    <blocks>
        <catalog>
            <rewrite>
                <product_list_toolbar>Amasty_Shopby_Block_Catalog_Product_List_Toolbar</product_list_toolbar>
            </rewrite>
        </catalog>
    </blocks>
</global>

While the extension works in the context of a layered navigation category, the rewritten class doesn't work properly when we insert an arbitrary product list into another (custom) view in our own in-house module. If we take out the extension overwrite just for testing purposes, everything works fine.

How can we undo an extension's rewrite just for our own controller, without editing the extension developer's community code?

Aaron Pollock

Posted 2013-01-22T21:26:29.910

Reputation: 614

2If you change the class you'll probably break the Shopby extension but... Never tried this however you might just want to rewrite that extensions class in your own extension

Your_Extension_Block_Catalog_Product_List_Toolbar extends Amasty_Shopby_Block_Catalog_Product_List_Toolbar – Sander Mangel – 2013-01-22T21:32:38.913

From what I can tell, Magento only allows one &lt;rewrite&gt; per class, so although I could create my own class extending the core class, I'm not sure how I'd get it to work via the getBlock('catalog/product_list_toolbar') factory method. – Aaron Pollock – 2013-01-22T21:45:48.713

If it is a payed extension you should contact Amasty support, this look as bug – Fra – 2013-01-22T21:48:07.287

did you manage to pinpoint the issue? what causes the problem you are facing (which function in the extended class)? – FlorinelChis – 2013-01-22T21:54:12.247

@FlorinelChis not yet, it's fairly dense and makes big changes to the layered navigation. Since this particular instance of the ProductList doesn't use layered navigation, I'd rather not get into trying to fix the extension with no benefit to the site. I'd rather just find the most maintainable way to get back to using the core class if I can. – Aaron Pollock – 2013-01-22T22:57:10.533

Perhaps a lesson here is don't use an extension that overwrites things more broadly than it needs to. – Aaron Pollock – 2013-01-22T23:09:26.287

1@AaronPollock maybe, but this problem could still arise from an extension that overwrites things exactly as broadly as it needs to. Perhaps we'd be better off reexamining the inheritance model itself. Maybe mixins or traits will help. – kojiro – 2013-01-23T12:32:35.490

Answers

24

Caveats: There's no designed way to do what you're asking in the system. The following should work, but I've never tried it out extensively on a production system, and there may be situations where it will cause more trouble that it's worth. Only proceed if you're comfortable debugging problems related to the changing the rewrites of a working system.

Step 1 is undoing the rewrite. The Magento configuration tree can be changed at runtime. So, if you run the following code

$config = Mage::getConfig();        
$config->setNode(
    'global/blocks/catalog/rewrite/product_list_toolbar',
    'Mage_Catalog_Block_Product_List_Toolbar'
);

Then Magento will instantiate the original Mage_Catalog_Block_Product_List_Toolbar block for the remainder of the request.

Step 2 is deciding where to call this in your module. Since this is just for your controller and it's rewriting a block that won't be instantiated until the end of your controller, I'd add a method to your controller class something like this

protected function _undoRewrites()
{
    $config = Mage::getConfig();        
    $config->setNode(
        'global/blocks/catalog/rewrite/product_list_toolbar',
        'Mage_Catalog_Block_Product_List_Toolbar'
    );    
}

and then just call this method at the start of each of your actions

public function indexAction()
{
    $this->_undoRewrites();
    $test = Mage::getSingleton('core/layout')->createBlock('catalog/product_list_toolbar');        
    var_dump($test);
}

This may seem a little clunky, but I think it's a good idea to be clunky (i.e. obvious) when you're being clever with Magento's system objects. Another place for this could be the controller_action_predispatch or controller_action_predispatch_front_controller_action events and/or applied conditionally.

Just remember the rewrite won't be undone until this method is called. That means if you attempt to instantiate a block before calling _undoRewrites, the rewritten class will be used to instantiate the object.

Alan Storm

Posted 2013-01-22T21:26:29.910

Reputation: 26 515

19

Solution 1:
You can try to instantiate the class directly (php way) in your controller

instead of

$this->getLayout()->createBlock('catalog/product_list_toolbar');

something like:

$block = New Magento_Catalog_Product_List_Toolbar;
$this->getLayout()->addBlock(....);

Solution 2:
Another approach would be create a new class, in your module, that extends the original class and use that one.

Solution 3:
Otherwise if the extension is not crypted ( we all love open source :) you can try to find out why it breaks your stuff

Fra

Posted 2013-01-22T21:26:29.910

Reputation: 4 163

1@Aaron Pollock, you CAN do second rewrite on the same base class. Just name the module namespace as Z (any letter after A) and magento will use it instead of Amasty one. – Amasty – 2014-07-30T22:06:35.293

Solution 2 does work (pragmatic solution) but it isn't great in that I can't do a second rewrite on the same base class. Therefore the factory method won't work (you've realised this I think already). Maybe there isn't a Magento way to do this, but let's hold out a bit to see if there's a better way. – Aaron Pollock – 2013-01-22T23:00:23.060

Solution 2 is what I would go with...I was getting ready to suggest that until I saw Francesco's answer. ;) – davidalger – 2013-01-23T03:01:14.320

1Even though I like solution 2 the best, a note to solution 1: you can also provide a complete class name to createBlock (as in $this-&gt;getLayout()-&gt;createBlock("Mage_Catalog_Block_Product_List_Toolbar") when your are in a block class context). If there is no / in the parameter Magento will just use the string as is to search for the class. – Matthias Zeis – 2013-01-23T05:48:19.310

5

If multiple rewrites exist for the same class alias, then the last one the Magento config loader parses from config.xml "wins". I'd attack this problem by:

  1. Create a new extension of your own.
  2. Rewrite the catalog/product_list_toolbar in your extension
  3. Have your block extend Mage_Catalog_Block_Product_List_Toolbar instead of the Amasty class.
  4. Liberally comment your class explaining that this rewrite conflict is intentional. You don't want another developer who runs MageRun to try and "fix" the rewrite conflict you've just created.
  5. Add a dependency in your extension's app/etc/modules/blah.xml file to ensure your extension is loaded after the Amasty one.

Jim OHalloran

Posted 2013-01-22T21:26:29.910

Reputation: 485

1

You do need to do a slight change in the extension code I am afraid. Do not rewrite the class in your own config.xml anymore , just change Amasty_Shopby_Block_Catalog_Product_List_Toolbar to extend your class that in turn extends Mage_Catalog_Block_Product_List_Toolbar.

Paul Grigoruta

Posted 2013-01-22T21:26:29.910

Reputation: 1 112

I see extension code like core code - somebody else's business (to maintain the ability to upgrade cleanly). There must be a way that avoids touching it. Also, the problem is the Amasty class breaks the core functionality in the context of an arbitrary product list. I don't to inject my own functionality; I need to revive the core functionality. My own class, if I were to follow your solution, would be empty and any attempted fix I put in there would be overwritten by the higher precedent Amasty class. – Aaron Pollock – 2013-01-22T21:41:09.027

You'd better create a new block and extend it FROM Amasty toolbar, not vice versa. – Amasty – 2014-07-30T22:08:10.447

This is a bad habit. External modules should always be be untouched. If you need to update your module, you'd need to redo all of your changes within the new version. This might become a nightmare in terms of maintainability. – Michael Türk – 2013-01-23T06:48:47.220

1

Similar to what Francesco suggested above, but I believe you can actually pass the full class name in to getModel. This way, you are somewhat still doing the same thing, but using core methods to do it. I'm not entirely sure of the pros/cons to this method, but thought I would throw this out there as an idea.

Mage::getModel('Mage_Catalog_Block_Product_List_Toolbar');

On a side note, I believe this will be the standard way to load classes in Magento2.

jmspldnl

Posted 2013-01-22T21:26:29.910

Reputation: 191