others - 合并两个PHP对象最好的方法?

有两个PHP5对象,希望将一个合并到第二个对象中,


//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;

备注:

  • 这些是对象,不是类,
  • 对象包含很多字段,所以foreach非常慢,
  • 到目前为止,我们考虑先将对象A和B转换成数组,然后合并,再转换成对象。

时间:

如果对象仅包含字段(无方法),则这个有效:

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

当对象有方法时,这实际上也能工作。(用php 5.3测试)

你可以创建另一个对象,将对魔术方法的调用分派给底层对象,


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;
 }
 }
}

如果对象仅包含字段(无方法),则这个有效:


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


foreach($objectA as $k => $v) $objectB->$k = $v;

  • 方法链接:$obj- > f1 ()- > f2 ()- > f3 (),
  • 动态复合:$obj- > merge(...)/*工作在*/ $obj- >合并()

代码:


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;
 }
}

使用方法:


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

例如:


$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';

考虑到对象A和B:的非常简单的解决方案


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

我将把第二个对象链接到第一个对象的属性中,比如:


//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');

以下是将对象或数组展平的函数,


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;
}


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

使用方法:


 $obj = new SomeClass();

 $obj[] = 1;
 $obj[] = 2;

 $array = array(3,4,5);

 $obj->merge($array);


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);
}

...