tags:

views:

5104

answers:

7

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.
+10  A: 

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
That is very nice.
Pim Jager
Complete implementation would probably need __isset(), __unset() and implement Interator interface.
porneL
@porneL: what is Interator Interface?
Pim Jager
I'd edit his comment, but you can't do that. I think he means Iterator
Allain Lalonde
I like very much your solution, Allain, but I'm afraid it means we have to rewrite our entire application if we decide to use it.
Veynom
Ok... then pick the way that doesn't require a complete rewrite.
Allain Lalonde
oarsome idea +1
thomasrutter
+2  A: 
foreach($objectA as $k => $v) $objectB->$k = $v;
porneL
+7  A: 

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

$obj_merged = (object) array_merge((array) $obj1, (array) $obj2);
Perfect for what I was needing. Simple and easy!
Ben Sinclair
A: 

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;
}
+1  A: 

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 Compositer(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
+8  A: 

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)

floch69
A: 

Hi,

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.

Jônatas Eridani