Qual é o melhor método para mesclar dois objetos PHP?

222

Temos dois objetos PHP5 e gostaríamos de mesclar o conteúdo de um no segundo. Não há noção de subclasses entre elas, portanto, as soluções descritas no tópico a seguir não podem ser aplicadas.

Como você copia um objeto PHP para um tipo de objeto diferente

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

Observações:

  • Estes são objetos, não classes.
  • Os objetos contêm muitos campos, então um foreach seria bem lento.
  • Até agora, consideramos transformar os objetos A e B em matrizes e depois mesclá-los usando array_merge () antes de voltar a se transformar em um objeto, mas não podemos dizer que estamos orgulhosos disso.
Veynom
fonte
30
"Os objetos contêm muitos campos, então um foreach seria bem lento". - Os computadores são bem rápidos, "bastante lentos" costuma ser rápido o suficiente.
Sean McSomething

Respostas:

435

Se seus objetos contiverem apenas campos (sem métodos), isso funcionará:

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

Na verdade, isso também funciona quando os objetos têm métodos. (testado com PHP 5.3 e 5.6)

flochtililoch
fonte
1
Você também pode usar array_merge_recursive para ter um comportamento de cópia profunda. Você também pode estar interessado em array_replace_recursive. As diferenças são explicadas em detalhes aqui: brian.serveblog.net/2011/07/31/php-array_replace-vs-array_merge
Vincent Pazeller
12
O objeto resultante disso será uma instância de stdclass. Embora "funcione" de certo modo em objetos com métodos, ele efetivamente arruina o objeto nesse caso (removendo os métodos).
Brilliand
Isso é útil para retornar vários conjuntos de resultados em uma única função (e retornar apenas um objeto com pares chave-valor.)
Leonel Atencio
1
Isso não funcionará se houver uma chave inteira no objeto. Considere o seguinte exemplo: $ 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)); Saída real: matriz ([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [2] => 400) Saída desejada: matriz ([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [4] => 400)
Souvik
2
Sou eu ou essa resposta é uma cópia literal de uma resposta que já foi postada há meses? stackoverflow.com/a/794356/151509
maryisdead
28

Você pode criar outro objeto que despacha chamadas para métodos mágicos para os objetos subjacentes. Aqui está como você lidaria__get , mas para fazê-lo funcionar completamente, você teria que substituir todos os métodos mágicos relevantes. Você provavelmente encontrará erros de sintaxe desde que eu o inseri de cabeça para baixo.

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

Boa sorte.

Allain Lalonde
fonte
A implementação completa provavelmente precisaria de __isset (), __unset () e implementaria a interface do Interator.
Kornel
@porneL: o que é Interator Interface?
Pim Jager
2
Eu editaria o comentário dele, mas você não pode fazer isso. Eu acho que ele significa Iterator
Allain Lalonde
Gosto muito da sua solução, Allain, mas acho que isso significa que precisamos reescrever todo o aplicativo se decidirmos usá-lo.
Veynom
3
Ok ... então escolha o caminho que não requer uma reescrita completa.
Allain Lalonde
25
foreach($objectA as $k => $v) $objectB->$k = $v;
Kornel
fonte
6
Isso é mais rápido que a resposta aceita nas versões PHP <7 (estimada em 50% mais rápido). Mas em PHP> = 7 a resposta aceita é algo como 400% mais rápido. Veja aqui: sandbox.onlinephpfunctions.com/code/…
yunzen
Como podemos usar ou obter os dados mesclados aqui?
1
@ramedju Neste exemplo, $objectBos dados mesclados são mantidos .
Kornel
10

Entendo que o uso dos objetos genéricos [stdClass ()] e a projeção deles como matrizes respondem à pergunta, mas achei que o compositor era uma ótima resposta. No entanto, achei que poderia usar alguns aprimoramentos de recursos e pode ser útil para outra pessoa.

Recursos:

  • Especificar referência ou clone
  • Especifique a primeira ou a última entrada para ter precedência
  • Objeto múltiplo (mais de dois) mesclado com similaridade de sintaxe com array_merge
  • Método de vinculação: $ obj-> f1 () -> f2 () -> f3 () ...
  • Compostos dinâmicos : $ obj-> mesclar (...) / * trabalhar aqui * / $ obj-> mesclar (...)

Código:

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

Uso:

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

Exemplo:

$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
fonte
2
Apenas para salientar: a passagem por referência do tempo de chamada foi marcada como obsoleta no PHP 5.3.0 e removida no PHP 5.4.0 (resultando em um erro fatal aumentado). Para corrigir o problema: Substituir foreach($objects as &$object) $this->with(&$object);por foreach($objects as &$object) $this->with($object);corrige o problema. Fonte: [ php.net/manual/en/language.references.pass.php]
wes.hysell
2
Além disso: if($this->first_precedence) array_push($this->composite, &$object); else array_unshift($this->composite, &$object);deve ser substituído porif($this->first_precedence) array_push($this->composite, $object); else array_unshift($this->composite, $object);
wes.hysell
1
Então, para resumir seus comentários remover Ampersand (&) de US $ objeto dentro: foreach (primeiro comentário) ... array_push, array_unshift (segundo comentário)
Chris
1
@ Chris Atualizei o código para corrigir os problemas pelos comentários acima.
Ryan Schumacher
No código 'Uso', você
digitou
7

Uma solução muito simples, considerando que você tem o objeto A e B:

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

Isso é tudo. Agora você tem objA com todos os valores de objB.

Jônatas Eridani
fonte
Por que você não faria apenas: $ objB = $ objA;
Scottymeuk
2

A \ArrayObjectclasse tem a possibilidade de trocar a matriz atual para desconectar a referência original . Para fazer isso, ele vem com dois métodos úteis: exchangeArray()e getArrayCopy(). O resto é simples array_merge()do objeto fornecido com as ArrayObjectpropriedades públicas s:

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

O uso é tão fácil quanto isto:

 $base = new MergeBase();

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

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

 $base->merge( $toMerge );
Corelmax
fonte
Essa realmente deve ser a resposta aceita . A única coisa legal seria se merge($array)realmente solicitasse um \ArrayObject.
Kaiser #
2

uma solução Para preservar, métodos e propriedades de objetos mesclados é criar uma classe combinadora que possa

  • pegue qualquer número de objetos em __construct
  • acessar qualquer método usando __call
  • acessar qualquer propriedade usando __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';
    } 
}

uso simples

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
fonte
Isso é muito inteligente, tiramos o chapéu. Eu não acho que me sentiria confortável com métodos não definidos na classe de objetos resultantes.
Sonserina
? thanks .. para o chapéu ... Foi só por diversão e eu concordo com você sobre o conforto em uso principalmente sobre autocomplete no netbeans ou outro editor
bortunac
1

Eu iria vincular o segundo objeto a uma propriedade do primeiro objeto. Se o segundo objeto for o resultado de uma função ou método, use referências. 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
fonte
1

Para mesclar qualquer número de objetos brutos

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

Aqui está uma função que achatará um objeto ou matriz. Use isso apenas se tiver certeza de que suas chaves são únicas. Se você tiver chaves com o mesmo nome, elas serão substituídas. Você precisará colocar isso em uma classe e substituir "Funções" pelo nome da sua classe. Aproveitar...

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

fonte
0

Vamos simplificar!

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

Se isso não responder à sua pergunta, certamente ajudará na resposta. Crédito para o código acima vai para mim :)

Rolf
fonte
0

Esse trecho de código converterá recursivamente esses dados em um único tipo (matriz ou objeto) sem os loops foreach aninhados. Espero que ajude alguém!

Quando um Objeto estiver no formato de matriz, você poderá usar array_merge e converter novamente em Objeto, se necessário.

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

Maneira processual

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

Todo o crédito é para: Jason Oakley

Drmzindec
fonte