Qual è il metodo migliore per unire due oggetti PHP?


222

Abbiamo due oggetti PHP5 e vorremmo unire il contenuto di uno nel secondo. Non esiste alcuna nozione di sottoclasse tra loro, quindi le soluzioni descritte nel seguente argomento non possono essere applicate.

Come si copia un oggetto PHP in un diverso tipo di oggetto

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

Osservazioni:

  • Questi sono oggetti, non classi.
  • Gli oggetti contengono molti campi, quindi una foreach sarebbe piuttosto lenta.
  • Finora pensiamo di trasformare gli oggetti A e B in array e di fonderli usando array_merge () prima di ri-trasformarli in un oggetto, ma non possiamo dire di essere orgogliosi se questo.

30
"Gli oggetti contengono molti campi, quindi una foreach sarebbe piuttosto lenta." - I computer sono abbastanza veloci, 'abbastanza lento' è spesso abbastanza veloce.
Sean McSomething il

Risposte:


435

Se i tuoi oggetti contengono solo campi (nessun metodo), questo funziona:

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

Questo funziona anche quando gli oggetti hanno dei metodi. (testato con PHP 5.3 e 5.6)


1
È inoltre possibile utilizzare array_merge_recursive per avere un comportamento di copia approfondita. Potresti anche essere interessato a array_replace_recursive. Le differenze sono spiegate in dettaglio qui: brian.serveblog.net/2011/07/31/php-array_replace-vs-array_merge
Vincent Pazeller

12
L'oggetto risultante da questo sarà un'istanza di stdclass. Mentre "funziona" in un certo senso su oggetti con metodi, in tal caso rovina effettivamente l'oggetto (rimuovendo i metodi).
Brilliand,

Ciò è utile per restituire più set di risultati in un'unica funzione (e restituire solo un oggetto con coppie chiave-valore.)
Leonel Atencio,

1
Questo non funzionerà se nell'oggetto è presente una chiave intera. Considera il seguente esempio: $ 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)); Uscita effettiva: matrice ([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [2] => 400) Uscita desiderata: matrice ([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [4] => 400)
Souvik,

2
Sono solo io o questa risposta è una copia integrale di una risposta che era già stata pubblicata per mesi? stackoverflow.com/a/794356/151509
maryisdead

28

È possibile creare un altro oggetto che invia chiamate a metodi magici agli oggetti sottostanti. Ecco come gestiresti __get, ma per farlo funzionare a dovere dovresti scavalcare tutti i metodi magici rilevanti. Probabilmente troverai errori di sintassi da quando l'ho appena inserito nella parte superiore della mia testa.

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

In bocca al lupo.


L'implementazione completa avrebbe probabilmente bisogno di __isset (), __unset () e implementare l'interfaccia di Interator.
Kornel,

@porneL: cos'è Interator Interface?
Pim Jager,

2
Modificherei il suo commento, ma non puoi farlo. Penso che intenda Iterator
Allain Lalonde il

Mi piace moltissimo la tua soluzione, Allain, ma temo che significhi che dobbiamo riscrivere la nostra intera applicazione se decidiamo di usarla.
Veynom,

3
Ok ... quindi scegli il modo in cui non è richiesta una riscrittura completa.
Allain Lalonde,

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

6
Questo è più veloce della risposta accettata nelle versioni PHP <7 (stimato più veloce del 50%). Ma in PHP> = 7 la risposta accettata è più veloce del 400%. Vedi qui: sandbox.onlinephpfunctions.com/code/…
yunzen

Come possiamo usare o ottenere i dati uniti qui?

1
@ramedju In questo esempio $objectBcontiene i dati uniti.
Kornel,

10

Capisco che usare gli oggetti generici [stdClass ()] e lanciarli come array risponde alla domanda, ma ho pensato che Compositor fosse un'ottima risposta. Tuttavia, ho pensato che potesse utilizzare alcuni miglioramenti delle funzionalità e che potrebbe essere utile per qualcun altro.

Caratteristiche:

  • Specifica riferimento o clone
  • Specificare la prima o l'ultima voce per avere la precedenza
  • Più oggetti (più di due) uniti alla sintassi simile a array_merge
  • Collegamento metodo: $ obj-> f1 () -> f2 () -> f3 () ...
  • Compositi dinamici : $ obj-> merge (...) / * lavora qui * / $ obj-> merge (...)

Codice:

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

Esempio:

$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
Solo per sottolineare: il riferimento pass-by-time delle chiamate è stato contrassegnato come obsoleto in PHP 5.3.0 e rimosso in PHP 5.4.0 (con conseguente errore irreversibile aumentato). Per correggere il problema: la sostituzione foreach($objects as &$object) $this->with(&$object);con foreach($objects as &$object) $this->with($object);risolve il problema. Fonte: [
php.net/manual/en/language.references.pass.php

2
Inoltre: if($this->first_precedence) array_push($this->composite, &$object); else array_unshift($this->composite, &$object);dovrebbe essere sostituito conif($this->first_precedence) array_push($this->composite, $object); else array_unshift($this->composite, $object);
wes.hysell il

1
quindi per riassumere i tuoi commenti rimuovi Ampersand (&) da $ object dentro: foreach (primo commento) ... array_push, array_unshift (secondo commento)
Chris

1
@ Chris Ho aggiornato il codice per risolvere i problemi in base ai commenti sopra.
Ryan Schumacher,

Nel tuo codice "Uso" hai scritto erroneamente Compositor come Compositore
Xesau il

7

Una soluzione molto semplice considerando che hai gli oggetti A e B:

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

È tutto. Ora hai objA con tutti i valori di objB.


Perché non dovresti semplicemente fare: $ objB = $ objA;
Scottymeuk,

2

La \ArrayObjectclasse ha la possibilità di scambiare l'array corrente per disconnettere il riferimento originale . Per fare ciò, viene fornito con due metodi utili: exchangeArray()e getArrayCopy(). Il resto è semplice array_merge()dell'oggetto fornito con le ArrayObjectproprietà pubbliche s:

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

L'utilizzo è semplice come questo:

 $base = new MergeBase();

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

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

 $base->merge( $toMerge );

Questa in realtà dovrebbe essere la risposta accettata . L'unica cosa carina sarebbe se merge($array)davvero richiedesse anche un \ArrayObject.
Kaiser

2

una soluzione Per preservare, sia i metodi che le proprietà degli onject uniti sono la creazione di una classe combinatrice che può

  • accetta qualsiasi numero di oggetti su __construct
  • accedere a qualsiasi metodo usando __call
  • accedere a qualsiasi proprietà utilizzando __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 semplice

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;

Questo è molto intelligente, va bene così. Non penso che mi sentirei a mio agio con i metodi che non vengono definiti nella classe di oggetti risultante.
Serpeverde,

grazie? .. per il cappello ... Era solo per divertimento e sono d'accordo con te sul confort in uso principalmente per quanto riguarda il completamento automatico in netbeans o altri editor
bortunac,

1

Vorrei andare con il collegamento del secondo oggetto in una proprietà del primo oggetto. Se il secondo oggetto è il risultato di una funzione o metodo, utilizzare i riferimenti. 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');

1

Per unire qualsiasi numero di oggetti grezzi

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

0

Ecco una funzione che appiattirà un oggetto o un array. Usa questo solo se sei sicuro che le tue chiavi siano uniche. Se si dispone di chiavi con lo stesso nome, verranno sovrascritte. Dovrai inserirlo in una classe e sostituire "Funzioni" con il nome della tua classe. Godere...

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

Rendiamolo semplice!

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 questo non risponde alla tua domanda, sicuramente aiuterà nella risposta. Il merito del codice sopra va a me stesso :)


0

Questo frammento di codice convertirà ricorsivamente quei dati in un singolo tipo (array o oggetto) senza i cicli foreach nidificati. Spero che aiuti qualcuno!

Una volta che un oggetto è in formato array, è possibile utilizzare array_merge e riconvertirlo in oggetto, se necessario.

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

Modo procedurale

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

Tutto il merito va a: Jason Oakley

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.