A composer patch is inevitable when you desperately need a core modification in order to fix a troublesome issue in your end but, the same fix is not available for Magento 2 or the release in which the fix appears prolongs for indefinite time. The same can happen with a third party extension as well (especially it is an open source module and you are not sure the fix will be available soon).
A sample problem for demonstration
To demonstrate the way the patch is prepared, lets assume I am going to fix below problem which I encountered recently.
1 exception(s): Exception #0 (Exception): Deprecated Functionality: trim(): Passing null to parameter #1 ($string) of type string is deprecated in src/vendor/magento/module-customer/Block/Widget/Name.php on line 110 Exception #0 (Exception): Deprecated Functionality: trim(): Passing null to parameter #1 ($string) of type string is deprecated in src/vendor/magento/module-customer/Block/Widget/Name.php on line 110 <pre>#1 trim() called at [vendor/magento/module-customer/Block/Widget/Name.php:110] #2 Magento\Customer\Block\Widget\Name->getPrefixOptions() called at [vendor/hyva-themes/magento2-default-theme/Magento_Customer/templates/widget/name.phtml:41]
This issue was breaking customer account create page.
Here is the \Magento\Customer\Block\Widget\Name::getPrefixOptions()
which breaks in this context.
public function getPrefixOptions()
{
$prefixOptions = $this->options->getNamePrefixOptions();
if ($this->getObject() && !empty($prefixOptions)) {
$prefixOption = $this->getObject()->getPrefix();
$oldPrefix = $this->escapeHtml(trim($prefixOption))
if ($prefixOption !== null && !isset($prefixOptions[$oldPrefix]) && !isset($prefixOptions[$prefixOption])) {
$prefixOptions[$oldPrefix] = $oldPrefix;
}
}
return $prefixOptions;
}
Here $prefixOption
is null
and this will break the expression trim($prefixOption)
as trim
expects a string always. We can fix this problem by adding an early check before the line $oldPrefix = $this->escapeHtml(trim($prefixOption))
. So the fix look like this:
public function getPrefixOptions()
{
$prefixOptions = $this->options->getNamePrefixOptions();
if ($this->getObject() && !empty($prefixOptions)) {
$prefixOption = $this->getObject()->getPrefix();
if (!$prefixOption) {
return $prefixOptions;
}
$oldPrefix = $this->escapeHtml(trim($prefixOption));
if ($prefixOption !== null && !isset($prefixOptions[$oldPrefix]) && !isset($prefixOptions[$prefixOption])) {
$prefixOptions[$oldPrefix] = $oldPrefix;
}
}
return $prefixOptions;
}
Steps to prepare patch
This is how you prepare the patch in this case.
- Open your terminal and cd into the vendor package directory in which you have the problem. So in this case, it would be:
cd vendor/magento/module-customer
- Initialize git there:
git init
- Add everything:
git add -A
- Do the initial commit:
git commit -m "initial commit"
- Now make your changes to the required files
- After that, commit those changes:
git add -A
- git commit -m “fixed the problem: some description”
- Perform below command to create the patch:
git format-patch -1 HEAD
- Above command will create a patch inside the package directory. Now move that file to your patch directory if any (in my case, i keep them inside
patch/composer
directory):cp 0001-Fixing-the-page-break-due-to-the-prefixOption-value-.patch ../../../patches/composer
- Add the above patch in the composer manually like this:
"extra": {
"patches": {
"magento/module-customer": {
"Fixing-customer-account-create-page-break": "patches/composer/0001-Fixing-the-page-break-due-to-the-prefixOption-value-.patch
.patch"
}
}
}
Note that, the package name should be specified correctly at extra.patches
. In my case, it would be magento/module-customer
.
- Now you should require a composer package
cweagans/composer-patches
in order to apply the above patch. So install that package if you don’t have it yet.composer require cweagans/composer-patches
- Finally perform
composer install
command.
With these, the package magento/module-customer
first will be uninstalled and then it will be freshly installed by applying your patch.
Create a patch from a GitHub commit
Some times, we need to convert a github commit into a patch. This is also very easy to perform. Navigate to the commit and just add .diff
in the URL. This will turn the commit into a patch. All you need to do is copy it and paste that in a patch file and then update your composer.json
file as mentioned above.
An example is given below. I have following commit which I want to convert to a patch: https://github.com/magento/magento2/commit/e4d10436897c1e11e46cc871a44135a92a063a5f
I add .diff
in the URL and I get below content: `https://github.com/magento/magento2/commit/e4d10436897c1e11e46cc871a44135a92a063a5f.diff
iff --git a/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php b/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php
index 35a0bff2508c4..0e440909d86c4 100644
--- a/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php
+++ b/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php
@@ -10,6 +10,8 @@
/**
* Abstract entity
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
abstract class EntityAbstract
{
@@ -18,7 +20,7 @@ abstract class EntityAbstract
/**
* Entity type abstract
*/
- const ENTITY_TYPE = 'abstract';
+ public const ENTITY_TYPE = 'abstract';
/**
* @var string[]
@@ -332,14 +334,22 @@ private function extractParameterType(
/** @var string|null $typeName */
$typeName = null;
$parameterType = $parameter->getType();
- if ($parameterType->getName() === 'array') {
+
+ if ($parameterType instanceof \ReflectionUnionType) {
+ $parameterType = $parameterType->getTypes();
+ $parameterType = implode('|', $parameterType);
+ } else {
+ $parameterType = $parameterType->getName();
+ }
+
+ if ($parameterType === 'array') {
$typeName = 'array';
} elseif ($parameterClass = $this->getParameterClass($parameter)) {
$typeName = $this->_getFullyQualifiedClassName($parameterClass->getName());
- } elseif ($parameterType->getName() === 'callable') {
+ } elseif ($parameterType === 'callable') {
$typeName = 'callable';
} else {
- $typeName = $parameterType->getName();
+ $typeName = $parameterType;
}
....
I hope you get the idea. We will meet you with some useful tips for the next time. Until then, bye 🙂