3

Is there any general consensus on the best way to replace M1 style controller rewrites in M2?

That is, in Magento 1, you could rewrite a URL path to point to controllers from your own module

<rewrite>
    <foo>
        <from><![CDATA[#^/address/#]]></from>
        <to>/customer/address/</to>
    </foo>
    <bar>
        <from><![CDATA[#^/account/#]]></from>
        <to>/customer/account/</to>
    </bar>
</rewrite>

This quickly fell out of fashion and wasn't ported to Magento 2.

Is there any general consensus on "the right" way to have URLs already claimed by Module A point to controllers in Modules B?

2 Answers 2

3

A plugin on the original controller's execute method.

<!-- file: etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\ModuleA\Controller\AddressAction">
        <plugin name="fooman_example_address"
                type="Fooman\Example\Plugin\AdressAction" />
        </plugin>
    </type>
</config>

And then in the plugin class

<!-- file: Plugin/AddressAction.php -->
<?php
class AddressAction 
    {
    public function aroundExecute(
        \Magento\ModuleA\Controller\AddressAction $subject,
        \Closure $proceed
    ) {
        //Do whatever you want to do instead
    }
}
4
  • One subtle difference I think might be in how permissions are handled. I believe the M1 approach would only check on the new controller. Whereas with the above the plugin doing the authentication would still execute (ie if you don't have access to ModuleA you also don't have access to ModuleB). Commented May 18, 2017 at 22:13
  • you would also potentially disable plugins on the original controller action if you implement this plugin without calling the original method, which would be especially problematic to 3rd patry vendors Commented May 18, 2017 at 22:24
  • on the other hand, this might be exactly what you want in most cases when overriding controller actions. In a beautiful future maybe, the core controllers only execute functionality of models and you can just recompose them Commented May 18, 2017 at 22:30
  • Will need to consider layout updates when doing this.
    – benmarks
    Commented May 19, 2017 at 16:49
2

I'm not sure there's a cut-and-dry "best" override method for a Controller - just as there are reasons why you might use a preference or plugin for any other class in Magento 2.

The benefit of using a plugin for the execute function is that you can make small changes to the response/layout while reducing the likelihood of conflicts with third-party plugins etc. I would expressly suggest never using an around plugin which completely bypasses the $proceed callable because this will trash any other plugins that were not yet executed. It's better to create a preference in this case.

Ultimately, I would say that using a plugin is the best method to inject your own logic if it is feasible to do so. (It's a public function, you don't need to access protected/private properties on the original class or recreate a ton of protected/private functions, and you are okay with still executing the code of the original function - or modifying the parameters/response is all that's necessary.) Done properly, plugins should be reasonably compatible across multiple modules. When you resort to a preference (or before route rewrite), then you open the door to other modules trying to claim the same class which will require additional technical work for the end-user to make them interoperable. (The plague of Magento 1 modules.)

The other answer details the plugin method, so below I will define the other two.

Creating a Preference

One is to create a direct preference for a given Controller class.

In a module <module_dir>/etc/frontend/di.xml file:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Cms\Controller\Index\Index" type="Example\Module\Rewrite\Controller\Cms\Index\Index"/>
</config>

Thus the class \Example\Module\Rewrite\Controller\Cms\Index\Index at <module_dir>/Rewrite/Controller/Cms/Index/Index.php will be called instead of the original class. This is a 'complete rewrite' in a sense. You will only call the code from the original controller class if you extend it in the new class. I believe Magento 2 is smart enough to see this type of rewrite and transition existing plugins to use the new class in lieu of the old one.

This type of rewrite is best for a surgical change to a single controller class where a plugin won't work. (See above.) You can replicate this by using an around plugin without calling the $proceed function - however this method will skip any plugins that are lower in the sortOrder.

It is theoretically feasible to do whatever you want to do with a plugin on a public function, but keep in mind that any non-public functions the original method used will have to be recreated if necessary and non-public properties without any getters/setters will be totally inaccessible.

Before/After Routers

Finally, there are route before/after rewrites. These are the most useful if you intend to 'claim' or add additional actions to an existing route in bulk.

In the <module_dir>/etc/frontend/routes.xml file:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="uniqueidhere">
            <module name="Example_Module" before="Magento_Cms"/>
        </route>
    </router>
</config>

The above configuration will 'claim' the original frontName defined within the Magento_Cms module. Similar to the old Magento 1 config.xml before/after controller methods, this will cause the module to be inserted before or after the original module when Magento 2 tries to load an action for the request path.

When before is used, you can create your own <module_dir>/Controller/<ControllerRoute>/<ActionRoute>.php class files. If those files match an existing route, they will claim that route and be executed before the original classes. (You can always extend the originals.) Any unused actions will fall back to the original files. Alternatively, if after is used then any new controller/action classes you create will create new available routes which can be executed under the original frontName but the existing action classes will still be used.

I haven't used this method much, so I'm not sure if plugins will respect controller overrides created this way. You can potentially add di.xml preference entries for these classes to instruct Magento to reroute existing plugins to the new class.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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