두 개의 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가 상당히 느릴 것입니다." -컴퓨터는 꽤 빠르며 'quite slow'는 종종 빠릅니다.
Sean McSomething

답변:


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
빈센트 Pazeller에게

12
이로 인한 객체는의 인스턴스가 stdclass됩니다. 메서드가있는 개체에 대해 "작동"하는 반면,이 경우 메서드를 제거하여 개체를 효과적으로 망치게됩니다.
Brilliand

이것은 하나의 함수에서 여러 결과 집합을 반환하는 데 유용합니다 (그리고 키 - 값 쌍으로 단지 오브젝트를 돌려줍니다.)
레오 넬 Atencio

1
객체에 정수 키가 있으면 작동하지 않습니다. 다음 예제를 고려하십시오. $ arr1 = array ( 'a'=> 9, 'b'=> 'asd'); $ arr2 = 배열 ​​( '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) 원하는 출력 : 배열 ([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 Interface 란 무엇입니까?
Pim Jager

2
그의 의견을 편집 할 수는 있지만 그렇게 할 수는 없습니다. 나는 그가 반복자 의미한다고 생각
Allain이 Lalonde

Allain의 솔루션이 매우 마음에 들지만 사용하기로 결정한 경우 전체 응용 프로그램을 다시 작성해야한다는 것을 두려워합니다.
Veynom

3
좋아 ... 그런 다음 완전히 다시 쓰지 않아도되는 방법을 선택하십시오.
Allain Lalonde

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 ()]를 사용하고 배열로 캐스팅하면 질문에 대한 답변을 얻을 수 있지만 컴포 지터가 큰 답변이라고 생각합니다. 그러나 일부 기능 향상을 사용할 수 있으며 다른 사람에게 유용 할 수 있다고 생각했습니다.

풍모:

  • 참조 또는 복제본 지정
  • 우선 순위를 지정할 첫 번째 또는 마지막 항목을 지정하십시오.
  • array_merge와 구문이 유사한 다중 (2 개 이상) 객체 병합
  • 메소드 연결 : $ 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

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 inside에서 앰퍼샌드 (&)를 제거하십시오. foreach (첫 번째 코멘트) ... array_push, array_unshift (두 번째 코멘트)
Chris

1
@Chris 위의 의견에 따라 문제를 해결하기 위해 코드를 업데이트했습니다.
Ryan Schumacher

'Usage'코드에서
컴포 지터

7

객체 A와 B가 있다고 생각하는 매우 간단한 솔루션 :

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

그게 다야. 이제 objB의 모든 값을 가진 objA가 있습니다.


왜 그렇게하지 않겠습니까 : $ objB = $ objA;
Scottymeuk 2016 년

2

\ArrayObject클래스 가능성 갖는 교환 원래 분리 현재 배열을 참조 . 이렇게하려면 두 개의 편리한 방법을 함께 제공 : exchangeArray()getArrayCopy(). 나머지는 s 공개 속성으로 array_merge()제공된 객체의 단순 ArrayObject합니다.

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것입니다.
카이저

2

솔루션 병합 된 주입에서 메서드와 속성을 모두 유지하려면 다음을 수행 할 수있는 결합기 클래스를 만드는 것입니다.

  • __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;

그것은 매우 영리합니다. 그래도 결과 객체 클래스에 정의되지 않은 메소드에 익숙하지 않을 것이라고 생각합니다.
Slytherin

고마워? .. 모자 ... ... 단지 재미를 위해 netbeans 또는 다른 편집기에서 자동 완성과 관련하여 사용 편의에 대해 당신과 동의합니다
bortunac

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

다음은 객체 또는 배열을 평평하게하는 함수입니다. 키가 고유 한 경우에만 사용하십시오. 이름이 같은 키가 있으면 덮어 씁니다. 이것을 클래스에 배치하고 "함수"를 클래스 이름으로 바꿔야합니다. 즐겨...

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 루프없이 해당 데이터를 단일 유형 (배열 또는 객체)으로 재귀 적으로 변환합니다. 그것이 누군가를 돕기를 바랍니다!

Object가 배열 형식이되면 array_merge를 사용하고 필요한 경우 Object로 다시 변환 할 수 있습니다.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.