Magento 2: Create a custom composer patch

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 🙂

Rajeev K Tomy

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top