253

We have two PHP5 objects and would like to merge the content of one into the second. There are no notion of subclasses between them so the solutions described in the following topic cannot apply.

How do you copy a PHP object into a different object type

//We have this:
$objectA->a;
$objectA->b;
$objectB->c;
$objectB->d;

//We want the easiest way to get:
$objectC->a;
$objectC->b;
$objectC->c;
$objectC->d;

Remarks:

  • These are objects, not classes.
  • The objects contain quite a lot of fields so a foreach would be quite slow.
  • So far we consider transforming objects A and B into arrays then merging them using array_merge() before re-transforming into an object but we can't say we are proud if this.
Community
  • 1
  • 1
Veynom
  • 4,079
  • 2
  • 19
  • 24
  • 32
    "The objects contain quite a lot of fields so a foreach would be quite slow." - Computers are pretty fast, 'quite slow' is often fast enough. – Sean McSomething Apr 27 '09 at 23:57

12 Answers12

506

If your objects only contain fields (no methods), this works:

$obj_merged = (object) array_merge((array) $obj1, (array) $obj2);

This actually also works when objects have methods. (tested with PHP 5.3 and 5.6)

Jnanaranjan
  • 1,304
  • 15
  • 31
flochtililoch
  • 6,057
  • 2
  • 21
  • 20
  • 4
    You may also use array_merge_recursive to have a deep copy behavior. You may also be interested in array_replace_recursive. The differences are explained in detail here: http://brian.serveblog.net/2011/07/31/php-array_replace-vs-array_merge/ – Vincent Pazeller Nov 27 '13 at 09:43
  • 12
    The object resulting from this will be an instance of `stdclass`. While it does "work" in a sense on objects with methods, it effectively ruins the object in that case (by removing the methods). – Brilliand Feb 05 '15 at 00:34
  • This is useful for returning multiple resultsets in a single function (And return just an object with key-value pairs.) – Leonel Atencio Feb 26 '16 at 20:07
  • 1
    To confirm @Brilliand 's comment, this does not work with Laravel objects in a Laravel collection – brianlmerritt Aug 22 '17 at 09:59
  • Using this to merge stdClass objects returned from a call to json_decode(). Simple and works. Thanks heaps! – Jamie Carl Dec 05 '17 at 05:06
  • 1
    This will not work if there is an integer key in the object. Consider the following example : $arr1 = array('a' => 9, 'b' => 'asd'); $arr2 = array('a' => 10, 'd' => 'qwert', 0 => 100, 1 => 200, 4 => 400); $arr3 = array_merge($arr1, $arr2); echo(print_r($arr3, 1)); Actual Output : Array ( [a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [2] => 400 ) Desired Output : Array ( [a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [4] => 400 ) – Souvik Jan 23 '18 at 08:29
  • 2
    Is it just me or is this answer a verbatim copy of an answer that had already been posted for months? https://stackoverflow.com/a/794356/151509 – maryisdead May 09 '19 at 15:44
36
foreach($objectA as $k => $v) $objectB->$k = $v;
Kornel
  • 97,764
  • 37
  • 219
  • 309
  • 8
    This is faster than the accepted answer in PHP versions < 7 (estimated 50% faster). But in PHP >= 7 the accepted answer is something like 400% faster. See here: http://sandbox.onlinephpfunctions.com/code/55a8d53dda932368fd00faca2dd142148ce79d2e – yunzen Jan 25 '17 at 11:32
  • How can we use or get the merged data here? –  Jul 25 '18 at 08:17
  • 1
    @ramedju In this example `$objectB` holds the merged data. – Kornel Jul 25 '18 at 21:11
28

You could create another object that dispatches calls to magic methods to the underlying objects. Here's how you'd handle __get, but to get it working fully you'd have to override all the relevant magic methods. You'll probably find syntax errors since I just entered it off the top of my head.

class Compositor {
  private $obj_a;
  private $obj_b;

  public function __construct($obj_a, $obj_b) {
    $this->obj_a = $obj_a;
    $this->obj_b = $obj_b;
  }

  public function __get($attrib_name) {
    if ($this->obj_a->$attrib_name) {
       return $this->obj_a->$attrib_name;
    } else {
       return $this->obj_b->$attrib_name;
    }
  }
}

Good luck.

Allain Lalonde
  • 91,574
  • 70
  • 187
  • 238
11

I understand that using the generic objects [stdClass()] and casting them as arrays answers the question, but I thought the Compositor was a great answer. Yet I felt it could use some feature enhancements and might be useful for someone else.

Features:

  • Specify reference or clone
  • Specify first or last entry to take precedence
  • Multiple (more than two) object merging with syntax similarity to array_merge
  • Method linking: $obj->f1()->f2()->f3()...
  • Dynamic composites: $obj->merge(...) /* work here */ $obj->merge(...)

Code:

class Compositor {

    protected $composite = array();
    protected $use_reference;
    protected $first_precedence;

    /**
     * __construct, Constructor
     *
     * Used to set options.
     *
     * @param bool $use_reference whether to use a reference (TRUE) or to copy the object (FALSE) [default]
     * @param bool $first_precedence whether the first entry takes precedence (TRUE) or last entry takes precedence (FALSE) [default]
     */
    public function __construct($use_reference = FALSE, $first_precedence = FALSE) {
        // Use a reference
        $this->use_reference = $use_reference === TRUE ? TRUE : FALSE;
        $this->first_precedence = $first_precedence === TRUE ? TRUE : FALSE;

    }

    /**
     * Merge, used to merge multiple objects stored in an array
     *
     * This is used to *start* the merge or to merge an array of objects.
     * It is not needed to start the merge, but visually is nice.
     *
     * @param object[]|object $objects array of objects to merge or a single object
     * @return object the instance to enable linking
     */

    public function & merge() {
        $objects = func_get_args();
        // Each object
        foreach($objects as &$object) $this->with($object);
        // Garbage collection
        unset($object);

        // Return $this instance
        return $this;
    }

    /**
     * With, used to merge a singluar object
     *
     * Used to add an object to the composition
     *
     * @param object $object an object to merge
     * @return object the instance to enable linking
     */
    public function & with(&$object) {
        // An object
        if(is_object($object)) {
            // Reference
            if($this->use_reference) {
                if($this->first_precedence) array_push($this->composite, $object);
                else array_unshift($this->composite, $object);
            }
            // Clone
            else {
                if($this->first_precedence) array_push($this->composite, clone $object);
                else array_unshift($this->composite, clone $object);
            }
        }

        // Return $this instance
        return $this;
    }

    /**
     * __get, retrieves the psudo merged object
     *
     * @param string $name name of the variable in the object
     * @return mixed returns a reference to the requested variable
     *
     */
    public function & __get($name) {
        $return = NULL;
        foreach($this->composite as &$object) {
            if(isset($object->$name)) {
                $return =& $object->$name;
                break;
            }
        }
        // Garbage collection
        unset($object);

        return $return;
    }
}

Usage:

$obj = new Compositor(use_reference, first_precedence);
$obj->merge([object $object [, object $object [, object $...]]]);
$obj->with([object $object]);

Example:

$obj1 = new stdClass();
$obj1->a = 'obj1:a';
$obj1->b = 'obj1:b';
$obj1->c = 'obj1:c';

$obj2 = new stdClass();
$obj2->a = 'obj2:a';
$obj2->b = 'obj2:b';
$obj2->d = 'obj2:d';

$obj3 = new Compositor();
$obj3->merge($obj1, $obj2);
$obj1->c = '#obj1:c';
var_dump($obj3->a, $obj3->b, $obj3->c, $obj3->d);
// obj2:a, obj2:b, obj1:c, obj2:d
$obj1->c;

$obj3 = new Compositor(TRUE);
$obj3->merge($obj1)->with($obj2);
$obj1->c = '#obj1:c';
var_dump($obj3->a, $obj3->b, $obj3->c, $obj3->d);
// obj1:a, obj1:b, obj1:c, obj2:d
$obj1->c = 'obj1:c';

$obj3 = new Compositor(FALSE, TRUE);
$obj3->with($obj1)->with($obj2);
$obj1->c = '#obj1:c';
var_dump($obj3->a, $obj3->b, $obj3->c, $obj3->d);
// obj1:a, obj1:b, #obj1:c, obj2:d
$obj1->c = 'obj1:c';
Ryan Schumacher
  • 1,816
  • 2
  • 21
  • 33
  • 2
    Just to point out: Call-time pass-by-reference was marked as deprecated in PHP 5.3.0 and removed in PHP 5.4.0 (resulting in a raised Fatal Error). To correct the issue: Replacing `foreach($objects as &$object) $this->with(&$object);` with `foreach($objects as &$object) $this->with($object);` corrects the issue. Source: [http://php.net/manual/en/language.references.pass.php] – wes.hysell Dec 16 '13 at 22:16
  • 2
    Additionally: `if($this->first_precedence) array_push($this->composite, &$object); else array_unshift($this->composite, &$object);` should be replaced with `if($this->first_precedence) array_push($this->composite, $object); else array_unshift($this->composite, $object);` – wes.hysell Dec 16 '13 at 22:20
  • 1
    so to summarize your comments remove Ampersand (&) from $object inside: foreach (first comment)... array_push, array_unshift (second comment) – CodingInTheUK Apr 05 '15 at 00:48
  • 1
    @Chris I updated the code to fix the issues per the comments above. – Ryan Schumacher Apr 08 '15 at 17:41
  • In your 'Usage' code, you misspelled Compositor as Compositer – Xesau Sep 26 '15 at 16:00
  • If `$first_precedence` is a `boolean`, then you should just assign it instead of assigning the result or `TRUE === TRUE`, which always equals again: A boolean. – kaiser Sep 23 '16 at 07:30
8

A very simple solution considering you have object A and B:

foreach($objB AS $var=>$value){
    $objA->$var = $value;
}

That's all. You now have objA with all values from objB.

2

The \ArrayObject class has the possibility to exchange the current array to disconnect the original reference. To do so, it comes with two handy methods: exchangeArray() and getArrayCopy(). The rest is plain simple array_merge() of the provided object with the ArrayObjects public properties:

class MergeBase extends ArrayObject
{
     public final function merge( Array $toMerge )
     {
          $this->exchangeArray( array_merge( $this->getArrayCopy(), $toMerge ) );
     }
 }

The usage is as easy as this:

 $base = new MergeBase();

 $base[] = 1;
 $base[] = 2;

 $toMerge = [ 3,4,5, ];

 $base->merge( $toMerge );
kaiser
  • 21,817
  • 17
  • 90
  • 110
Corelmax
  • 21
  • 1
  • This actually should be the **accepted answer**. The only thing nice would be if `merge($array)` would actually request an `\ArrayObject` as well. – kaiser Sep 23 '16 at 07:34
2

a solution To preserve,both methods and properties from merged onjects is to create a combinator class that can

  • take any number of objects on __construct
  • access any method using __call
  • accsess any property using __get

class combinator{
function __construct(){       
    $this->melt =  array_reverse(func_get_args());
      // array_reverse is to replicate natural overide
}
public function __call($method,$args){
    forEach($this->melt as $o){
        if(method_exists($o, $method)){
            return call_user_func_array([$o,$method], $args);
            //return $o->$method($args);
            }
        }
    }
public function __get($prop){
        foreach($this->melt as $o){
          if(isset($o->$prop))return $o->$prop;
        }
        return 'undefined';
    } 
}

simple use

class c1{
    public $pc1='pc1';
    function mc1($a,$b){echo __METHOD__." ".($a+$b);}
}
class c2{
    public $pc2='pc2';
    function mc2(){echo __CLASS__." ".__METHOD__;}
}

$comb=new combinator(new c1, new c2);

$comb->mc1(1,2);
$comb->non_existing_method();  //  silent
echo $comb->pc2;
bortunac
  • 4,642
  • 1
  • 32
  • 21
  • Thats very clever, hats off to that. I don't think i would be comfortable with methods not being defined on the resulting objects class though. – Slytherin Dec 09 '16 at 14:35
  • thanks ?.. for the hat ... It was just for fun and I agree with you about confort in use mostly regarding autocomplete in netbeans or other editor – bortunac Dec 10 '16 at 17:32
1

To merge any number of raw objects

function merge_obj(){
    foreach(func_get_args() as $a){
        $objects[]=(array)$a;
    }
    return (object)call_user_func_array('array_merge', $objects);
}
bortunac
  • 4,642
  • 1
  • 32
  • 21
1

I would go with linking the second object into a property of the first object. If the second object is the result of a function or method, use references. Ex:

//Not the result of a method
$obj1->extra = new Class2();

//The result of a method, for instance a factory class
$obj1->extra =& Factory::getInstance('Class2');
Adrian
  • 11
  • 1
1

This snippet of code will recursively convert that data to a single type (array or object) without the nested foreach loops. Hope it helps someone!

Once an Object is in array format you can use array_merge and convert back to Object if you need to.

abstract class Util {
    public static function object_to_array($d) {
        if (is_object($d))
            $d = get_object_vars($d);

        return is_array($d) ? array_map(__METHOD__, $d) : $d;
    }

    public static function array_to_object($d) {
        return is_array($d) ? (object) array_map(__METHOD__, $d) : $d;
    }
}

Procedural way

function object_to_array($d) {
    if (is_object($d))
        $d = get_object_vars($d);

    return is_array($d) ? array_map(__FUNCTION__, $d) : $d;
}

function array_to_object($d) {
    return is_array($d) ? (object) array_map(__FUNCTION__, $d) : $d;
}

All credit goes to: Jason Oakley

Drmzindec
  • 804
  • 2
  • 15
  • 28
0

Let's keep it simple!

function copy_properties($from, $to, $fields = null) {
    // copies properties/elements (overwrites duplicates)
    // can take arrays or objects 
    // if fields is set (an array), will only copy keys listed in that array
    // returns $to with the added/replaced properties/keys
    $from_array = is_array($from) ? $from : get_object_vars($from);
    foreach($from_array as $key => $val) {
        if(!is_array($fields) or in_array($key, $fields)) {
            if(is_object($to)) {
                $to->$key = $val;
            } else {
                $to[$key] = $val;
            }
        }
    }
    return($to);
}

If that doesn't answer your question, it will surely help towards the answer. Credit for the code above goes to myself :)

Rolf
  • 5,550
  • 5
  • 41
  • 61
0

Here is a function that will flatten an object or array. Use this only if you are sure your keys are unique. If you have keys with the same name they will be overwritten. You will need to place this in a class and replace "Functions" with the name of your class. Enjoy...

function flatten($array, $preserve_keys=1, &$out = array(), $isobject=0) {
        # Flatten a multidimensional array to one dimension, optionally preserving keys.
        #
        # $array - the array to flatten
        # $preserve_keys - 0 (default) to not preserve keys, 1 to preserve string keys only, 2 to preserve all keys
        # $out - internal use argument for recursion
        # $isobject - is internally set in order to remember if we're using an object or array
        if(is_array($array) || $isobject==1)
        foreach($array as $key => $child)
            if(is_array($child))
                $out = Functions::flatten($child, $preserve_keys, $out, 1); // replace "Functions" with the name of your class
            elseif($preserve_keys + is_string($key) > 1)
                $out[$key] = $child;
            else
                $out[] = $child;

        if(is_object($array) || $isobject==2)
        if(!is_object($out)){$out = new stdClass();}
        foreach($array as $key => $child)
            if(is_object($child))
                $out = Functions::flatten($child, $preserve_keys, $out, 2); // replace "Functions" with the name of your class
            elseif($preserve_keys + is_string($key) > 1)
                $out->$key = $child;
            else
                $out = $child;

        return $out;
}