Jaka jest najlepsza metoda scalenia dwóch obiektów PHP?

222

Mamy dwa obiekty PHP5 i chcielibyśmy połączyć zawartość jednego w drugi. Pomiędzy nimi nie ma podklas, dlatego nie można zastosować rozwiązań opisanych w poniższym temacie.

Jak skopiować obiekt PHP na inny typ obiektu

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

Uwagi:

  • To są obiekty, a nie klasy.
  • Obiekty zawierają dość dużo pól, więc foreach byłby dość wolny.
  • Do tej pory rozważamy przekształcenie obiektów A i B w tablice, a następnie scalenie ich za pomocą array_merge () przed ponownym przekształceniem w obiekt, ale nie możemy powiedzieć, że jesteśmy z tego dumni.
Veynom
źródło
30
„Obiekty zawierają dość dużo pól, więc foreach byłby dość wolny”. - Komputery są dość szybkie, „dość wolne” często jest wystarczająco szybkie.
Sean McSomething

Odpowiedzi:

435

Jeśli Twoje obiekty zawierają tylko pola (bez metod), działa to:

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

Działa to również wtedy, gdy obiekty mają metody. (testowany z PHP 5.3 i 5.6)

flochtililoch
źródło
1
Możesz także użyć array_merge_recursive, aby zachować zachowanie głębokiej kopii. Możesz być także zainteresowany array_replace_recursive. Różnice wyjaśniono szczegółowo tutaj: brian.serveblog.net/2011/07/31/php-array_replace-vs-array_merge
Vincent
12
Wynikającym z tego obiektem będzie instancja stdclass. Chociaż „działa” w pewnym sensie na obiekty metodami, w tym przypadku skutecznie niszczy obiekt (usuwając metody).
Brilliand
Jest to przydatne do zwracania wielu zestawów wyników w jednej funkcji (i zwracania tylko obiektu z parami klucz-wartość.)
Leonel Atencio
1
To nie zadziała, jeśli w obiekcie znajduje się klucz liczby całkowitej. Rozważ następujący przykład: $ arr1 = array ('a' => 9, 'b' => 'asd'); $ arr2 = tablica („a” => 10, „d” => „qwert”, 0 => 100, 1 => 200, 4 => 400); $ arr3 = array_merge ($ arr1, $ arr2); echo (print_r ($ arr3, 1)); Rzeczywiste wyjście: Array ([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [2] => 400) Pożądane wyjście: Array ([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [4] => 400)
Souvik 23.01.2018
2
Czy to tylko ja, czy ta odpowiedź jest dosłowną kopią odpowiedzi, która została już opublikowana od miesięcy? stackoverflow.com/a/794356/151509
maryisdead
28

Możesz stworzyć inny obiekt, który będzie wywoływał wywołania metod magicznych do obiektów znajdujących się pod spodem. Oto jak sobie z tym poradzisz __get, ale aby w pełni funkcjonować, musisz zastąpić wszystkie odpowiednie magiczne metody. Prawdopodobnie znajdziesz błędy składniowe, ponieważ właśnie wprowadziłem go z góry głowy.

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

Powodzenia.

Allain Lalonde
źródło
Pełna implementacja prawdopodobnie wymagałaby __isset (), __unset () i zaimplementowała interfejs Interatora.
Kornel
@porneL: co to jest interfejs Interatora?
Pim Jager,
2
Zredagowałbym jego komentarz, ale nie możesz tego zrobić. Myślę, że ma na myśli Iterator
Allain Lalonde
Bardzo podoba mi się twoje rozwiązanie, Allain, ale obawiam się, że oznacza to, że musimy przepisać całą naszą aplikację, jeśli zdecydujemy się z niej skorzystać.
Veynom
3
Ok ... następnie wybierz sposób, który nie wymaga całkowitego przepisania.
Allain Lalonde
25
foreach($objectA as $k => $v) $objectB->$k = $v;
Kornel
źródło
6
Jest to szybsze niż zaakceptowana odpowiedź w wersjach PHP <7 (szacunkowo 50% szybciej). Ale w PHP> = 7 zaakceptowana odpowiedź jest o około 400% szybsza. Zobacz tutaj: sandbox.onlinephpfunctions.com/code/…
yunzen
Jak możemy tutaj wykorzystać lub uzyskać scalone dane?
1
@ramedju W tym przykładzie $objectBprzechowywane są scalone dane.
Kornel
10

Rozumiem, że użycie ogólnych obiektów [stdClass ()] i rzutowanie ich jako tablice odpowiada na pytanie, ale myślałem, że Kompozytor był świetną odpowiedzią. Jednak czułem, że przydałoby się ulepszenie niektórych funkcji i może być przydatne dla kogoś innego.

Cechy:

  • Podaj odniesienie lub klon
  • Określ pierwszy lub ostatni wpis, który ma mieć pierwszeństwo
  • Wiele (więcej niż dwa) obiekty łączą się z podobieństwem składniowym do array_merge
  • Łączenie metod: $ obj-> f1 () -> f2 () -> f3 () ...
  • Kompozyty dynamiczne : $ obj-> merge (...) / * działa tutaj * / $ obj-> merge (...)

Kod:

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

Stosowanie:

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

Przykład:

$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
źródło
2
Dla przypomnienia: Przekazanie przez referencję czasu zadziałania zostało oznaczone jako przestarzałe w PHP 5.3.0 i usunięte w PHP 5.4.0 (powodując podwyższony błąd krytyczny). Aby rozwiązać problem: zastąpienie foreach($objects as &$object) $this->with(&$object);go foreach($objects as &$object) $this->with($object);rozwiązuje problem. Źródło: [ php.net/manual/en/language.references.pass.php]
wes.hysell
2
Dodatkowo: if($this->first_precedence) array_push($this->composite, &$object); else array_unshift($this->composite, &$object);należy zastąpić przezif($this->first_precedence) array_push($this->composite, $object); else array_unshift($this->composite, $object);
wes.hysell 16.12.2013
1
więc podsumowując swoje komentarze usuń Ampersand (&) z $ obiektu wewnątrz: foreach (pierwszy komentarz) ... array_push, array_unshift (drugi komentarz)
Chris
1
@Chris Zaktualizowałem kod, aby rozwiązać problemy zgodnie z powyższymi komentarzami.
Ryan Schumacher
W kodzie „Wykorzystanie” źle
napisałeś
7

Bardzo proste rozwiązanie, biorąc pod uwagę, że masz obiekt A i B:

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

To wszystko. Teraz masz objA ze wszystkimi wartościami z objB.

Jônatas Eridani
źródło
Dlaczego nie zrobiłbyś tego po prostu: $ objB = $ objA;
Scottymeuk,
2

\ArrayObjectKlasa ma możliwość wymiany aktualną tablicę odłączyć oryginalną odniesienia . Aby to zrobić, oferuje dwie przydatne metody: exchangeArray()i getArrayCopy(). Reszta jest po prostu prosta array_merge()z podanego obiektu o ArrayObjectpublicznych właściwościach:

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

Użycie jest tak proste:

 $base = new MergeBase();

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

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

 $base->merge( $toMerge );
Corelmax
źródło
To powinna być zaakceptowana odpowiedź . Jedyną przyjemną rzeczą byłoby, gdyby merge($array)faktycznie poprosił o \ArrayObjectrównież.
kaiser
2

rozwiązanie Aby zachować zarówno metody, jak i właściwości ze scalonych obiektów, należy utworzyć klasę kombinatora, która może

  • weź dowolną liczbę obiektów na __construct
  • uzyskać dostęp do dowolnej metody za pomocą __call
  • uzyskać dostęp do dowolnej właściwości za pomocą __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';
    } 
}

proste użycie

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
źródło
To bardzo sprytne. Czapki z głów. Nie sądzę jednak, że czułbym się komfortowo, gdyby metody nie były zdefiniowane w klasie obiektów wynikowych.
Slytherin,
dzięki? .. za kapelusz ... To była dla zabawy i zgadzam się z tobą co do wygody użytkowania głównie w odniesieniu do autouzupełniania w netbeans lub innym edytorze
bortunac
1

Chciałbym połączyć drugi obiekt z właściwością pierwszego obiektu. Jeśli drugi obiekt jest wynikiem funkcji lub metody, użyj odwołań. Dawny:

//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
źródło
1

Aby scalić dowolną liczbę surowych obiektów

function merge_obj(){
    foreach(func_get_args() as $a){
        $objects[]=(array)$a;
    }
    return (object)call_user_func_array('array_merge', $objects);
}
bortunac
źródło
0

Oto funkcja, która spłaszczy obiekt lub tablicę. Użyj tego tylko, jeśli masz pewność, że twoje klucze są unikalne. Jeśli masz klucze o tej samej nazwie, zostaną one zastąpione. Musisz umieścić to w klasie i zastąpić „Funkcje” nazwą swojej klasy. Cieszyć się...

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

źródło
0

Uprośćmy to!

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

Jeśli to nie odpowie na twoje pytanie, z pewnością pomoże w uzyskaniu odpowiedzi. Podziękowania dla powyższego kodu należą do mnie :)

Rolf
źródło
0

Ten fragment kodu będzie rekurencyjnie przekształcał te dane w pojedynczy typ (tablicę lub obiekt) bez zagnieżdżonych pętli foreach. Mam nadzieję, że to komuś pomaga!

Gdy obiekt ma format tablicowy, możesz użyć array_merge i w razie potrzeby przekonwertować go z powrotem na obiekt.

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

Sposób proceduralny

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

Wszystkie podziękowania należą się: Jason Oakley

Drmzindec
źródło