linusshops/iddqd

This package is abandoned and no longer maintained. No replacement package was suggested.

Take extraordinary programmatic control of Magento's XML configs using event observers.

Installs: 21

Dependents: 0

Suggesters: 0

Security: 0

Stars: 3

Watchers: 6

Forks: 0

Open Issues: 0

Type:magento-module

dev-develop 2015-07-30 20:57 UTC

This package is not auto-updated.

Last update: 2022-04-30 05:21:07 UTC


README

The purpose of this module is to programmatically intercept Magento's merged and parsed XML object representation of all the configuration *.xml files in operation, and modify it on the fly, JIT-like. This has unbelievably powerful consequences.

Why?

A common challenge to Magento development is the introduction of third-party modules. Most of them are terrible. I digress. Often these third-party modules compete for the right to extend core Magento classes, but unfortunately Magento does not make that easy to control. In scenarios like this, where multiple modules rewrite the same classes, only one can survive, and its name is always Zoidberg.

The consensus among community help forums is to hack away at these modules until they are a symbiotic Loch Ness of code--hopefully never to be seen by the eye of scrutiny--but otherwise most commonly known as a steaming pile of 💩. There is a lot of that in the Magento world.

The other, more clean solution is to create a kind of middleware or adapter module that serves as common ground for both modules to interact between. Ultimately, Magento will fail to please, though. While this approach ensures that both modules can be untouched, thus easing their ability to update, there is still a lot of redundant adapter code that needs to be written.

iddqd attempts to make this a bit more easy. Consider, for example, two modules that modify the layered navigation. Both modules rewrite very similar or identical classes, but each of them have very specific, contexual requirements. One module should only modify the layered navigation on standard category listings, while the other should only modify the layered navigation on search results listings. iddqd makes this possible.

iddqd makes it possible to contextually instantiate classes within Magento.

Impetus

The need for this solution arose when searching to see whether it would be possible to add a custom XML attribute like ifcontroller="someController" to the <rewrite> nodes in config.xml files, in an attempt at a clean integration between two distinct modules that rewrite the same core classes. That did not seem like a fruitful endeavour, but the possibility of throwing an event right before the classes are named, seemed like a good opportunity for this kind of magic.

How?

This is done by a simple rewrite of the getGroupedClassName method in the Mage_Core_Model_Config class. That class is responsible for deciding whether a class has been rewritten by a module. If there is a <rewrite> provided in a module, the method determines that for the given component, use the rewritten class if it exists, otherwise fall back to the Magento default. iddqd rewrites that method to insert an event dispatcher immediately before the logic to determine whether a class rewrite is valid; the entire XML representation is passed by reference as event data, which can then be observed in any other module, at which point the XML representation can be manipulated without restriction before it is finally passed back to the original flow.

A rewrite is just a simple example, though. iddqd lifts any and every restriction when it comes to what classes get instantiated by Magento.

Installation

This should be installed using Composer. A magento build should also include the Magento Composer Installer. This module follows the module structure guidelines provided by Firegento, which will also make it very easy to submit to the Firegento Composer Repository.

Warning

Magento does not allow directly rewriting the Mage_Core_Model_Config class. This is because it is integral to the proper functioning of Magento, and should it be rewritten badly, Magento will catastrophically fail. However, Magento did create a way to modify it, in a very explicit I-know-what-I'm-doing-because-I'm-a-pro type of way. If it is not already clear, this is an experimental module. If the technique applied in this module appears like black magic, installing this is not advised. This is for advanced Magento development.

Having typed that all out, the last installation step is to modify Magento's index.php file to replace the last line so it reads like this instead:

Mage::run($mageRunCode, $mageRunType, array('config_model' => 'Linus_Iddqd_Model_Config'));

Usage (i.e., the fun bits)

In a new module, say, in an adapter module that serves as a way of orchestrating multiple modules, create the boilerplate necessary to observe the following custom event:

before_configxml_rewrite

This event will pass by reference a new Varien_Object with the event payload data, that can then be manipulated before passing control flow back to the original method. Read the source to see what data is passed.

etc/config.xml

<global>
    ...
    <events>
        <before_configxml_rewrite>
            <observers>
                <linus_example>
                    <type>singleton</type>
                    <class>Linus_Example_Model_Observer</class>
                    <method>onBeforeConfigxmlRewrite</method>
                </linus_example>
            </observers>
        </before_configxml_rewrite>
    </events>
    ...
</global>

Model/Observer.php

public function onBeforeConfigxmlRewrite(Varien_Event_Observer $observer)
{
    // Get event.
    $event = $observer->getEvent();

    // Get Varien_Object event data.
    $config = $event->getConfig();
    
    /** @var Linus_Iddqd_Model_Config $instance */
    $instance = $config->getInstance();
    $class = $config->getClass();
    $group = $config->getGroup();

    /** @var Mage_Core_Model_Config_Element $configXml */
    $configXml = $instance->getXml(); // Custom Linus_Iddqd method.

    // Retrieve ANY path IN ENTIRE UNIVERSE.
    $catalogModel = $configXml->descend('global/models/catalog');

    // Because you disagree with the chosen class path, SET YOUR OWN.
    $configXml->setNode('global/models/catalog/class', 'Linus_CoolerModule_Something_Something');
    // Or this for short.
    $catalogModel->setNode('class', 'Linus_CoolerModule_Something_Something');
    
    // Get all rewrites for provided path, and pass whatever other classes
    // should be instantiated instead.
    $catalogRewrites = $configXml->descend('global/models/catalog/rewrite');
    $catalogRewrites->setNode('layer_filter_attribute' => 'A_Better_Class');
    
    // ...or just obliterate them, causing Magento to use built-in core classes.
    unset($catalogRewrites->layer_filter_attribute);
    unset($catalogRewrites->layer_filter_category);
    unset($catalogRewrites->layer_filter_item);
    
    // Maybe you just want to modify classes and rewrites on a given page.
    if (stripos(Mage::app()->getRequest()->getRequestUri(), 'helmets') !== false) {
        // https://i.imgur.com/Re3Ti2c.jpg
    }
    
    // Custom Linus_Iddqd methods:
    
    // Merge in new config.xml.
    $instance->mergeConfig('path/to/custom/config.xml');
    
    // Rewrite a class, or multiple.
    $instance
        ->rewriteClass('global/models/catalog/rewrite/layer_filter_attribute', 'Linus_Better_Class')
        ->rewriteClass('global/models/catalog/class', 'Linus_CoolerModule_Something_Something');

    // Delete class. It does not need to be a rewrite. It can be any of them.
    $instance->deleteClass('global/models/catalog/rewrite', 'layer_filter_attribute');
}

Take a moment. That is your mind being blown.

##Layout Injector There may also be call for injecting layout XML based on the request context. As discussed above, Magento is lacking when it comes to getting modules to work together, even in layouts. In the case of two modules trying to modify the layouts in different contexts, this event allows you to inject contextual layout xml to get modules to work together (again, useful in the context of an adapter module).

IDDQD throws an event in the class that builds the layout update xml from all of your modules. Observing this event will allow you to append additional layout update xml files right before the merged XML is built. These additional files can be added based on programmatic rules, allowing you full control over the layouts in use.

###Usage As above, create the observer boilerplate, observing the before_layoutxml_compile event.

etc/config.xml

<global>
    ...
    <events>
        <before_layoutxml_compile>
            <observers>
                <linus_example>
                    <type>singleton</type>
                    <class>Linus_Example_Model_Observer</class>
                    <method>onBeforeLayoutXmlCompile</method>
                </linus_example>
            </observers>
        </before_layoutxml_compile>
    </events>
    ...
</global>

Model/Observer.php

public function onBeforeLayoutXmlCompile(Varien_Event_Observer $observer)
{
	/** @var Linus_Iddqd_Model_Layout_Update $godMode */
	$godMode = $observer->getGodMode();
	//Append the specified layout xml to your layout stack. Since it
	//is processed last, its rules will update everything else.
	if ($someCondition) {
		$godMode->addLayoutUpdate($observer, 'my_contextual_layout.xml');
	} else {
		//Or maybe it's some other context, so we'll use this instead.
		$godMode->addLayoutUpdate($observer, 'different_contextual_layout.xml');
	}
}

TODO

  • Add helpers to make setting and unsetting classes and rewrites much easier.

Author

Dane MacMillan

Contributors

Samuel Schmidt

Origin of the name

iddqd will be obvious to anyone who grew up playing PC games in the 90s. What does it mean? It means you operate on God Mode and nothing can stop you. That is also what this module does.

License

This module was created by Linus Shops and enthusiastically licensed to the Magento community under the MIT License.