DEV Community

Joshua Johnson
Joshua Johnson

Posted on • Originally published at ua1.us on

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"
 *     }
 *   }
 * }
 */
Enter fullscreen mode Exit fullscreen mode

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) "/"
 *      }
 *    }
 *  }
 * /
Enter fullscreen mode Exit fullscreen mode

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!

The post Tutorial: How to Recursively Merge Two Objects In PHP appeared first on UA1 Labs.

Latest comments (0)