合并两个PHP对象的最佳方法是什么?


222

我们有两个PHP5对象,并希望将其中一个的内容合并到第二个中。它们之间没有子类的概念,因此以下主题中描述的解决方案不适用。

如何将PHP对象复制到其他对象类型

//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转换为数组,然后在重新转换为对象之前使用array_merge()合并它们,但是我们不能为此感到骄傲。

30
“对象包含很多字段,因此foreach会很慢。” -计算机非常快,“相当慢”通常足够快。
肖恩·麦克索明

Answers:


435

如果您的对象仅包含字段(没有方法),则可以这样做:

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

当对象具有方法时,这实际上也适用。(经过PHP 5.3和5.6测试)


1
您还可以使用array_merge_recursive来进行深层复制行为。您可能也对array_replace_recursive感兴趣。此处详细说明了这些差异:brian.serveblog.net/2011/07/31/php-array_replace-vs-array_merge
Vincent Pazeller 2013年

12
由此产生的对象将是的实例stdclass。尽管从某种意义上说,它确实对使用方法的对象“起作用”,但在这种情况下,它有效地破坏了对象(通过删除方法)。
Brilliand 2015年

这对于在单个函数中返回多个结果集很有用(并且仅返回具有键值对的对象。)
Leonel Atencio 2016年

1
如果对象中有整数键,则此操作将无效。请考虑以下示例:$ 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)); 实际输出:数组([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [2] => 400)所需的输出:Array([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [4] => 400)
Souvik,

2
只是我还是这个答案是已经发布了几个月的答案的逐字记录副本?stackoverflow.com/a/794356/151509
maryisdead,

28

您可以创建另一个对象,该对象将对魔术方法的调用分派给基础对象。这是您的处理方式__get,但是要使其完全发挥作用,您必须重写所有相关的魔术方法。您可能会发现语法错误,因为我只是从头顶输入语法错误。

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

祝好运。


完整的实现可能需要__isset()和__unset()并实现Interator接口。
Kornel

@porneL:什么是Interator接口?
Pim Jager

2
我会编辑他的评论,但您不能这样做。我认为他是指迭代器
Allain Lalonde

我非常喜欢您的解决方案,Allain,但恐怕这意味着如果我们决定使用它,我们就必须重写整个应用程序。
Veynom

3
好的...然后选择不需要完全重写的方式。
拉隆德

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

6
这比PHP版本<7中可接受的答案要快(估计快了50%)。但是在PHP> = 7中,可接受的答案快了400%。看到这里:sandbox.onlinephpfunctions.com/code/…–
yunzen

我们如何在这里使用或获取合并的数据?

1
@ramedju在此示例中$objectB保存合并的数据。
Kornel

10

我知道使用通用对象[stdClass()]并将其转换为数组可以解决此问题,但是我认为Compositor是一个很好的答案。但是我觉得它可以使用一些功能增强功能,并且可能对其他人有用。

特征:

  • 指定参考或克隆
  • 指定第一个或最后一个条目优先
  • 与array_merge语法相似的多个(两个以上)对象合并
  • 方法链接:$ obj-> f1()-> f2()-> f3()...
  • 动态复合材料:$ obj-> merge(...)/ *在这里工作* / $ obj-> merge(...)

码:

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 Compositor(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';

2
需要指出的是:调用时传递引用在PHP 5.3.0中被标记为已弃用,而在PHP 5.4.0中已被删除(导致致命错误)。要解决此问题,请执行以下操作:替换foreach($objects as &$object) $this->with(&$object);foreach($objects as &$object) $this->with($object);可解决问题。资料来源:[ php.net/manual/en/language.references.pass.php]
wes.hysell 2013年

2
此外:if($this->first_precedence) array_push($this->composite, &$object); else array_unshift($this->composite, &$object);应该用if($this->first_precedence) array_push($this->composite, $object); else array_unshift($this->composite, $object);
wes.hysell

1
因此,总结一下您的评论,请从$ object里面删除“&”号:foreach(第一条评论)... array_push,array_unshift(第二条评论)
Chris

1
@Chris我更新了代码,以根据上面的评论解决问题。
瑞安·舒马赫

在您的“用法”代码中,您拼写错误的Compositor为Compositer
Xesau 2015年


2

所述\ArrayObject类具有这样的可能性交换当前的阵列,以断开原始参考。为此,它提供了两种便捷的方法:exchangeArray()getArrayCopy()。其余部分array_merge()对具有ArrayObjects公共属性的对象很简单:

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

用法很简单:

 $base = new MergeBase();

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

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

 $base->merge( $toMerge );

这实际上应该是公认的答案。唯一一件好事是,如果merge($array)实际上也会请求一个\ArrayObject
kaiser

2

解决方案要保留合并的onject的方法和属性,就是要创建一个可以

  • 在__construct上获取任意数量的对象
  • 使用__call访问任何方法
  • 使用__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';
    } 
}

使用简单

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;

那很聪明,对此表示怀疑。我认为我不会对未在结果对象类上定义的方法感到满意。
斯莱特林

谢谢?..戴上帽子...只是为了好玩,我同意您的意见,主要是关于在netbeans或其他编辑器中使用自动完成功能方面的
便利

1

我将把第二个对象链接到第一个对象的属性中。如果第二个对象是函数或方法的结果,请使用引用。例如:

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

1

合并任意数量的原始对象

function merge_obj(){
    foreach(func_get_args() as $a){
        $objects[]=(array)$a;
    }
    return (object)call_user_func_array('array_merge', $objects);
}

0

这是一个将对象或数组展平的函数。仅在您确定密钥唯一时才使用此功能。如果您具有相同名称的密钥,它们将被覆盖。您需要将其放置在一个类中,并用您的类名替换“ Functions”。请享用...

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

0

让我们保持简单!

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

如果那不能回答您的问题,那肯定会帮助您找到答案。上面代码的功劳归我所有:)


0

此代码段将递归地将数据转换为单一类型(数组或对象),而无需嵌套的foreach循环。希望它能对某人有所帮助!

对象为数组格式后,可以使用array_merge并根据需要转换回对象。

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

程序方式

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

所有功劳归功于:Jason Oakley

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.