Was ist die beste Methode, um zwei PHP-Objekte zusammenzuführen?

222

Wir haben zwei PHP5-Objekte und möchten den Inhalt von einem in das zweite zusammenführen. Es gibt keine Vorstellung von Unterklassen zwischen ihnen, daher können die im folgenden Thema beschriebenen Lösungen nicht angewendet werden.

Wie kopiert man ein PHP-Objekt in einen anderen Objekttyp?

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

Bemerkungen:

  • Dies sind Objekte, keine Klassen.
  • Die Objekte enthalten ziemlich viele Felder, so dass ein Foreach ziemlich langsam wäre.
  • Bisher ziehen wir in Betracht, die Objekte A und B in Arrays umzuwandeln und sie dann mit array_merge () zusammenzuführen, bevor wir sie erneut in ein Objekt umwandeln. Wir können jedoch nicht sagen, dass wir stolz darauf sind.
Veynom
quelle
30
"Die Objekte enthalten ziemlich viele Felder, so dass ein Foreach ziemlich langsam wäre." - Computer sind ziemlich schnell, "ziemlich langsam" ist oft schnell genug.
Sean McSomething

Antworten:

435

Wenn Ihre Objekte nur Felder enthalten (keine Methoden), funktioniert dies:

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

Dies funktioniert tatsächlich auch, wenn Objekte Methoden haben. (getestet mit PHP 5.3 und 5.6)

flochtililoch
quelle
1
Sie können auch array_merge_recursive verwenden, um ein tiefes Kopierverhalten zu erzielen. Sie könnten auch an array_replace_recursive interessiert sein. Die Unterschiede werden hier ausführlich erklärt: brian.serveblog.net/2011/07/31/php-array_replace-vs-array_merge
Vincent Pazeller
12
Das daraus resultierende Objekt ist eine Instanz von stdclass. Während es in gewissem Sinne bei Objekten mit Methoden "funktioniert", ruiniert es in diesem Fall das Objekt effektiv (durch Entfernen der Methoden).
Brilliand
Dies ist nützlich, um mehrere Ergebnismengen in einer einzigen Funktion zurückzugeben (und nur ein Objekt mit Schlüssel-Wert-Paaren zurückzugeben.)
Leonel Atencio
1
Dies funktioniert nicht, wenn das Objekt einen Ganzzahlschlüssel enthält. Betrachten Sie das folgende Beispiel: $ 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)); Tatsächliche Ausgabe: Array ([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [2] => 400) Gewünschte Ausgabe: Array ([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [4] => 400)
Souvik
2
Ist es nur ich oder ist diese Antwort eine wörtliche Kopie einer Antwort, die bereits seit Monaten veröffentlicht wurde? stackoverflow.com/a/794356/151509
Maryisdead
28

Sie können ein anderes Objekt erstellen, das Aufrufe von magischen Methoden an die zugrunde liegenden Objekte sendet. Hier ist, wie Sie damit umgehen würden __get, aber um es vollständig zum Laufen zu bringen, müssten Sie alle relevanten magischen Methoden überschreiben. Sie werden wahrscheinlich Syntaxfehler finden, da ich sie gerade über den Kopf eingegeben habe.

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

Viel Glück.

Allain Lalonde
quelle
Eine vollständige Implementierung würde wahrscheinlich __isset (), __unset () und die Implementierung der Interator-Schnittstelle erfordern.
Kornel
@porneL: Was ist Interator Interface?
Pim Jäger
2
Ich würde seinen Kommentar bearbeiten, aber das kannst du nicht. Ich denke, er meint Iterator
Allain Lalonde
Ihre Lösung gefällt mir sehr gut, Allain, aber ich fürchte, wir müssen unsere gesamte Anwendung neu schreiben, wenn wir sie verwenden möchten.
Veynom
3
Ok ... dann wählen Sie den Weg, der kein vollständiges Umschreiben erfordert.
Allain Lalonde
25
foreach($objectA as $k => $v) $objectB->$k = $v;
Kornel
quelle
6
Dies ist schneller als die akzeptierte Antwort in PHP-Versionen <7 (geschätzte 50% schneller). Aber in PHP> = 7 ist die akzeptierte Antwort ungefähr 400% schneller. Siehe hier: sandbox.onlinephpfunctions.com/code/…
yunzen
Wie können wir die zusammengeführten Daten hier verwenden oder erhalten?
1
@ramedju In diesem Beispiel $objectBenthält die zusammengeführten Daten.
Kornel
10

Ich verstehe, dass die Verwendung der generischen Objekte [stdClass ()] und deren Umwandlung als Arrays die Frage beantwortet, aber ich fand, dass der Compositor eine großartige Antwort war. Ich hatte jedoch das Gefühl, dass es einige Funktionsverbesserungen gebrauchen und für andere nützlich sein könnte.

Eigenschaften:

  • Geben Sie eine Referenz oder einen Klon an
  • Geben Sie den ersten oder letzten Eintrag an, der Vorrang haben soll
  • Mehrere (mehr als zwei) Objekte werden mit Syntaxähnlichkeit zu array_merge zusammengeführt
  • Methodenverknüpfung: $ obj-> f1 () -> f2 () -> f3 () ...
  • Dynamische Verbundwerkstoffe: $ obj-> merge (...) / * hier arbeiten * / $ obj-> merge (...)

Code:

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

Verwendung:

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

Beispiel:

$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
quelle
2
Nur um darauf hinzuweisen: Die Referenzzeit für die Anrufzeit wurde in PHP 5.3.0 als veraltet markiert und in PHP 5.4.0 entfernt (was zu einem schwerwiegenden schwerwiegenden Fehler führte). So beheben Sie das Problem: Durch Ersetzen foreach($objects as &$object) $this->with(&$object);durch wird foreach($objects as &$object) $this->with($object);das Problem behoben. Quelle: [ php.net/manual/en/language.references.pass.php]
wes.hysell
2
Zusätzlich: if($this->first_precedence) array_push($this->composite, &$object); else array_unshift($this->composite, &$object);sollte durchif($this->first_precedence) array_push($this->composite, $object); else array_unshift($this->composite, $object);
wes.hysell
1
Um Ihre Kommentare zusammenzufassen, entfernen Sie kaufmännisches Und (&) aus $ object inside: foreach (erster Kommentar) ... array_push, array_unshift (zweiter Kommentar)
Chris
1
@ Chris Ich habe den Code aktualisiert, um die Probleme gemäß den obigen Kommentaren zu beheben.
Ryan Schumacher
In Ihrem 'Verwendungscode' haben Sie Compositor als Compositer
Xesau
7

Eine sehr einfache Lösung, wenn Sie Objekt A und B haben:

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

Das ist alles. Sie haben jetzt objA mit allen Werten von objB.

Jônatas Eridani
quelle
Warum sollten Sie nicht einfach Folgendes tun: $ objB = $ objA;
Scottymeuk
2

Die \ArrayObjectKlasse hat die Möglichkeit, das aktuelle Array auszutauschen , um die ursprüngliche Referenz zu trennen . Dazu gibt es zwei praktische Methoden: exchangeArray()und getArrayCopy(). Der Rest ist ganz einfach array_merge()für das bereitgestellte Objekt mit den ArrayObjectöffentlichen Eigenschaften:

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

Die Bedienung ist so einfach:

 $base = new MergeBase();

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

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

 $base->merge( $toMerge );
Corelmax
quelle
Dies sollte eigentlich die akzeptierte Antwort sein . Das einzig schöne wäre, wenn merge($array)man tatsächlich auch eine anfordern würde \ArrayObject.
Kaiser
2

Eine Lösung Um sowohl Methoden als auch Eigenschaften von zusammengeführten Objekten zu erhalten, müssen Sie eine Kombinatorklasse erstellen, die dies kann

  • Nimm eine beliebige Anzahl von Objekten auf __construct
  • Greifen Sie mit __call auf eine beliebige Methode zu
  • Mit __get auf jede Eigenschaft zugreifen

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

einfache Verwendung

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
quelle
Das ist sehr klug, Hut ab. Ich glaube nicht, dass ich mit Methoden zufrieden sein würde, die für die resultierende Objektklasse nicht definiert sind.
Slytherin
danke? .. für den Hut ... Es war nur zum Spaß und ich stimme Ihnen in Bezug auf die Verwendung in Bezug auf die automatische Vervollständigung in Netbeans oder einem anderen Editor zu
Bortunac
1

Ich würde das zweite Objekt mit einer Eigenschaft des ersten Objekts verknüpfen. Wenn das zweite Objekt das Ergebnis einer Funktion oder Methode ist, verwenden Sie Referenzen. 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
quelle
1

Zum Zusammenführen einer beliebigen Anzahl von Rohobjekten

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

Hier ist eine Funktion, die ein Objekt oder Array reduziert. Verwenden Sie diese Option nur, wenn Sie sicher sind, dass Ihre Schlüssel eindeutig sind. Wenn Sie gleichnamige Schlüssel haben, werden diese überschrieben. Sie müssen dies in eine Klasse einfügen und "Funktionen" durch den Namen Ihrer Klasse ersetzen. Genießen...

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

quelle
0

Lass es uns einfach halten!

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

Wenn das Ihre Frage nicht beantwortet, hilft es sicherlich bei der Beantwortung. Gutschrift für den obigen Code geht an mich :)

Rolf
quelle
0

Dieser Codeausschnitt konvertiert diese Daten rekursiv in einen einzelnen Typ (Array oder Objekt) ohne die verschachtelten foreach-Schleifen. Hoffe es hilft jemandem!

Sobald ein Objekt im Array-Format vorliegt, können Sie array_merge verwenden und bei Bedarf wieder in Object konvertieren.

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

Verfahrensweise

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

Alle Kredite gehen an: Jason Oakley

Drmzindec
quelle