How to Apply Magento 2 Core Patches
In the ideal world, we wouldn't need patches for Magento 2 - the idea of applying semantic versioning rules in Magento 2 was to avoid having patches floating around, and instead of that, to fix all bugs and vulnerabilities by upgrading the whole system to latest "patch" version. In the real world, there are situations in which we need to fix core code before the new version is released:
- Magento 2 Commerce support can provide patches for bugs reported through the portal
- we may need to port a change from a higher version eg. 2.2 into a site running on lower eg. 2.1 if we don't have time for a full upgrade
- we may need to apply a patch for a bug which is already reported in Magento 2 GitHub, but is not fixed yet in any released version
Having these situations in mind, we needed to find a solution which allows for maintenance of such patches. The basic requirements were:
- have the ability to patch any Magento 2 core file in vendor/magento directory
- avoid breaking composer workflow in any of the environments
- additionally, have the ability to patch any file in vendor/ directory, not only Magento 2 core code
Let's review the solutions we considered at Rocket Web.
Composer patches by Cameron Eagans
This is a simple composer based solution - https://github.com/cweagans/composer-patches. It's easy to set up - it requires installing cweagans/composer-patches dependency and then each patch can be configured in composer.json file in the following way:
"extra": {
"patches": {
"magento/module-reports": {
"Apply my first fix": "magento-patch/XDMV-123445-module1.patch"
}
}
Each configured patch is automatically applied when "composer install" runs. It's smart enough to not try applying the same patch multiple times.
This works for simple patches, but it has a limitation - each patch can contain code which patches only one module in the vendor directory. Often patches provided by Magento contain code which modifies multiple different modules. In order to use this solution to apply such patches, we would need to modify each patch file to make sure it contains only code for one module. This would require too much unnecessary work.
Composer patches by Inviqua
It is also a simple composer based solution - https://github.com/jamescowie/composer-patcher. Set up is similar to the previous solution - after installing jamescowie/composer-patcher dependency "extra" node can be used to configure patches:
"patches": {
"patch-group-1": {
"patch-name-1": {
"type": "patch",
"title": "Apply my patch",
"url": "magento-patches/XDMV-123445.patch"
}
}
}
We were not able to get this working, files were not being patched, no error was given. The repository doesn't have any activity in last 3 years, so it may be it worked for the older version of Magento 2 and stopped at some point.
Our first low-tech solution
As composer based solutions had their limitations or simply didn't work, we went with a low-tech way. We placed patched files into magento-patch/ directory, preserving full file paths, including vendor/ directory. Then we created small shell script apply-patch.sh which was executed after each deployment:
#/bin/sh
cp -r magento-patch/* ./
This is a really simple, low-tech solution, but works. It can run multiple times, so it can be included in deployment automation.
A better low-tech solution
The previous solution still was not ideal as it required significant work to prepare patched files which could be put in magento-patch/ directory. We improved it a bit to make it work with patch files, instead of using already patched files. We created a new apply-patch.sh file as follows:
#/bin/sh
rm -rf vendor/magento/module-new-relic-reporting
rm -rf vendor/magento/module-reports
composer install
patch -p1 < magento-patch/XDMV-123445-module1.patch
patch -p1 < magento-patch/XDMV-123445-module2.patch
apply-patch.sh is executed after each deployment. It can be executed multiple times - it removes each module affected by any patch from the vendor directory and then runs "composer install" to install it from the cache. This ensures that "patch" commands executed later will always work.
This is a really low-tech solution, but it is fast, repeatable and allows us to patch any Magento or non-Magento file from the vendor directory. If any patches require another -p parameter value, it can be easily adjusted in the shell file.