Tutorial: How to Recursively Merge Two Objects In PHP

I was recently working on a project and found that I had to merge two very complex objects using PHP. I went to search for a solution and of course found array_merge() and array_merge_recursively(). So naturally, I thought I would start out by converting my objects into arrays and using array_merge_recursively.

For which I achieved the following results:

class Utils {
    /**
     * Recursively merges two objects and returns a resulting object.
     * @param object $obj1 The base object
     * @param object $obj2 The merge object
     * @return object The merged object
     */
    public function mergeObjectsRecursively($obj1, $obj2)
    {
        $baseObject = (array) $obj1;
        $mergeObject = (array) $obj2;
        $merged = array_merge_recursive($baseObject, $mergeObject);
        return (object) $merged;
    }
}

$utils = new Utils();

$obj1 = (object) [
    'debug' => true,
    'routes' => (object) [
        'test' => '/my-tests'
    ]
];

$obj2 = (object) [
    'routes' => (object) [
        'test' => '/my-tests-replaced'
    ]
];

$obj3 = $utils->mergeObjectsRecursively($obj1, $obj2);

var_dump($obj3);

/**
 * Resulting Object:
 * object(stdClass)#6 (2) {
 *   ["debug"]=>
 *   bool(true)
 *   ["routes"]=>
 *   array(1) {
 *     ["test"]=>
 *     array(2) {
 *       [0]=>
 *       string(9) "/my-tests"
 *       [1]=>
 *       string(18) "/my-tests-replaced"
 *     }
 *   }
 * }
 */

Notice that in the approach above, the resulting object is an object with a “routes” property that was converted from an object into an array. And not only that, but the obj->routes->test values have been combined into an array of values.

Not Acceptable!

THE ANSWER: Recursively Merge Two PHP Objects

The approach we need to take is to create our own merging logic so that it will override values instead of combining them. Here is the new approach:

class Utils {

    /**
     * Recursively merges two objects and returns a resulting object.
     * @param object $obj1 The base object
     * @param object $obj2 The merge object
     * @return object The merged object
     */
    public function mergeObjectsRecursively($obj1, $obj2)
    {
        $merged = $this->_mergeRecursively($obj1, $obj2);
        return $merged;
    }

    /**
     * Recursively merges two objects and returns a resulting object.
     * @param object $obj1 The base object
     * @param object $obj2 The merge object
     * @return object The merged object
     */
    private function _mergeRecursively($obj1, $obj2) {
        if (is_object($obj2)) {
            $keys = array_keys(get_object_vars($obj2));
            foreach ($keys as $key) {
                if (
                    isset($obj1->{$key})
                    && is_object($obj1->{$key})
                    && is_object($obj2->{$key})
                ) {
                    $obj1->{$key} = $this->_mergeRecursively($obj1->{$key}, $obj2->{$key});
                } elseif (isset($obj1->{$key})
                && is_array($obj1->{$key})
                && is_array($obj2->{$key})) {
                    $obj1->{$key} = $this->_mergeRecursively($obj1->{$key}, $obj2->{$key});
                } else {
                    $obj1->{$key} = $obj2->{$key};
                }
            }
        } elseif (is_array($obj2)) {
            if (
                is_array($obj1)
                && is_array($obj2)
            ) {
                $obj1 = array_merge_recursive($obj1, $obj2);
            } else {
                $obj1 = $obj2;
            }
        }

        return $obj1;
    }
}

$utils = new Utils();

$obj1 = (object) [
    'debug' => true,
    'modules' => [
        'Module1'
    ],
    'routes' => (object) [
        'test' => '/my-tests',
        'override' => 'false',
        'test2' => (object) [
            'override' => 'false',
            'shouldStay' => 'true'
        ]
    ]
];

$obj2 = (object) [
    'debug' => false,
    'modules' => [
        'Module2',
        'Module3'
    ],
    'routes' => (object) [
        'route1' => (object) [
            'path' => '/'
        ],
        'override' => 'true',
        'test2' => (object) [
            'override' => 'true'
        ]
    ]
];

$obj3 = $utils->mergeObjectsRecursively($obj1, $obj2);

var_dump($obj3);

/**
 * Resulting Object:
 *  object(stdClass)#4 (3) {
 *    ["debug"]=>
 *    bool(false)
 *    ["modules"]=>
 *    array(3) {
 *      [0]=>
 *      string(7) "Module1"
 *      [1]=>
 *      string(7) "Module2"
 *      [2]=>
 *      string(7) "Module3"
 *    }
 *    ["routes"]=>
 *    object(stdClass)#3 (4) {
 *      ["test"]=>
 *      string(9) "/my-tests"
 *      ["override"]=>
 *      string(4) "true"
 *      ["test2"]=>
 *      object(stdClass)#2 (2) {
 *        ["override"]=>
 *        string(4) "true"
 *        ["shouldStay"]=>
 *        string(4) "true"
 *      }
 *      ["route1"]=>
 *      object(stdClass)#5 (1) {
 *        ["path"]=>
 *        string(1) "/"
 *      }
 *    }
 *  }
 * /

Notice that the resulting object remains in tact. $obj->routes->test contains the replaced value and both $obj->routes and $obj->routes->test remain an objects.

Enjoy!

Author: Joshua Johnson

My name is Joshua Johnson and I am the founder of UA1 Labs. I am passionate about helping other developers to become successful in their paths. When I started developing software, I didn't have many resources to help me out. UA1 Labs is my way of giving back to all those who helped me.

Leave a Reply

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