Extension Conflicts in Magento 1 vs Magento 2
In the Magento 1 world, extension conflicts are the norm. Once you have a certain number of extensions installed, it's just a matter of time until you need to make code modifications (usually minor) to ensure the two extensions can work simultaneously.
The Magento 2 architecture provides a few improvements that minimise the potential for extensions to clash with each other:
- Class architecture is being overhauled (this is still work in progress) - the result is lots of smaller, more targeted classes. This means that if an extension changes the behaviour of a smaller class, the potential for an overlap with another extension becomes smaller
- More XML - we see a lot more XML based configuration in Magento 2 with areas specifally allowing for new content to be added. Since XML is merged extensions can easily add to existing implementations like adding columns to a grid (however if the same node name is used you would overwrite core behaviour and this can only be done by one extension)
- New Plugin System - this allows an extension developer to change the behaviour of any public method. Either changing the input with a before plugin, changing the output of the method with an after plugin, or changing the complete implementation of the method with an around plugin. As this happens on the method level the potential for overlap between extensions is again minimised. Additionally plugins are designed so that multiple plugins on the same method can be used. You would still have a conflict in the case where logically only one choice can prevail - for example Plugin Blue forces all colour choices to be blue vs Plugin Red forces all colour choices to be red
- New Dependency Injection System - the Magento flavoured DI system allows an extension developer to replace parameters on specific classes only so you don't need to make changes system wide if you only need a change in one place
Discovering my first Magento 2 extension conflict
Taken together, the above changes significantly reduce the potential for extension conflicts in Magento 2. It took a good full year until I encountered my first extension conflict.
The conflict happened between the ShipperHQ extension and our Fooman Order Manager extension, being used on a project by our mutual client Limesharp. Both extensions work on their own perfectly fine, however the conflict arose because both extensions add new columns to the Admin Sales Order Grid.
Why did the conflict happen?
ShipperHQ is an extension to manage eCommerce shipping rates and options from one place and displays shipping relevant data like delivery date in the Admin Sales Order Grid. The Order Manager extension allows one-click bulk order processing, so adds new columns to quickly enter tracking data in the Admin Sales Order Grid.
Adding the columns to the Admin Sales Order Grid via an extension is straightforward - they're added to the sales_order_grid.xml ui_component as new columns. But that only gets you half way there - you still need to fill these columns with data. The two extensions took different approaches for this part. For Fooman Order Manager, I created a virtualType which swapped out the SearchResult to our implementation like this:
<virtualType name="Magento\Sales\Model\ResourceModel\Order\Grid\Collection" type="Fooman\OrderManager\UiComponent\SearchResult">
<arguments>
<argument name="mainTable" xsi:type="string">sales_order_grid</argument>
<argument name="resourceModel" xsi:type="string">Magento\Sales\Model\ResourceModel\Order</argument>
</arguments>
</virtualType>
The ShipperHQ extension took a different route and added a plugin on the \Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory
class and then filtered to adjust the correct collection class:
if ($result instanceof \Magento\Sales\Model\ResourceModel\Order\Grid\Collection) {
$select = $result->getSelect();
$select->joinLeft(
['shipper_order_join' => $this->_resource->getTableName('shipperhq_order_detail_grid')],
'entity_id' . '=shipper_order_join.' . 'order_id' ,
[]
);
}
Both appropaches work fine on their own and power the individual extensions perfectly. But together these two approaches didn't mix well and threw an error that the ShipperHQ columns weren't found when doing any searches.
Solving the conflict
As part of our commitment to the Magento Extension Developers Network, we strive wherever possible to get our extensions playing nicely with other members' extensions.
To get the extensions working together, I changed the approach we took for Fooman Order Manager to also use a plugin to join our data to the grid collection:
public function afterLoadWithFilter(
\Magento\Sales\Model\ResourceModel\Order\Grid\Collection $subject,
$result
) {
This new approach is available on Order Manager M2 v5.0.0+.
The Power of Plugins in Magento 2
Plugins are a really powerful new feature that allow us to customise Magento 2 while minimising extension conflicts.
After making the above change to get Fooman Order Manager compatible with ShipperHQ, I decided to use the plugin approach on two more Fooman extensions.
We implemented small standalone extensions which plug into the core of Fooman Pdf Customiser and Pdf Picking List, so that Limesharp could also use these two extensions together with ShipperHQ for the same client. By using the three extensions and plugin customisation together, we've been able to implement the following custom functionality for the client:
- Pdf Customiser + ShipperHQ = Display your pick up date and time slot on the packing slip
- Pdf PickingList + ShipperHQ = Display your delivery date and time slot on your order picking list
When implementing new functionality for Magento 2 I'd strongly recommend looking at plugins to achieve the desired outcome. While only some public methods are blessed by Magento with an @api
annotation (meaning they include this as part of their public API in regards to semantic versioning) the plugin architecture still allows you to surgically replace functionality while creating code that is easier to maintain.