Який найкращий метод об'єднати два об'єкти 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;

Зауваження:

  • Це об’єкти, а не класи.
  • Об'єкти містять досить багато полів , тому Еогеасп буде досить повільним.
  • Поки ми розглядаємо перетворення об'єктів A і B в масиви, а потім об'єднання їх за допомогою array_merge () перед повторним перетворенням в об'єкт, але не можемо сказати, що ми пишаємось цим.

30
"Об'єкти містять досить багато полів, тому передбачення буде досить повільним". - Комп'ютери досить швидкі, 'досить повільний' часто досить швидкий.
Шон Мак-Що-небудь,

Відповіді:


435

Якщо ваші об’єкти містять лише поля (немає методів), це працює:

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

Це також працює, коли об'єкти мають методи. (тестовано на PHP 5.3 та 5.6)


1
Ви також можете використовувати array_merge_recursive для глибокої поведінки копіювання. Вас також може зацікавити array_replace_recursive. Різниці
Vincent Pazeller

12
Об'єкт, отриманий в результаті цього, буде екземпляром stdclass. Хоча це "працює" в певному сенсі на об'єктах методами, він ефективно руйнує об'єкт у тому випадку (шляхом видалення методів).
Brilliand

Це корисно для повернення декількох наборів результатів в одній функції (І повернення лише об’єкта з парами ключ-значення.)
Leonel Atencio

1
Це не спрацює, якщо в об’єкті є цілий ключ. Розглянемо наступний приклад: $ arr1 = array ('a' => 9, 'b' => 'asd'); $ arr2 = масив ('a' => 10, 'd' => 'qwert', 0 => 100, 1 => 200, 4 => 400); $ arr3 = array_merge ($ arr1, $ arr2); ехо (print_r ($ arr3, 1)); Фактичний вихід: Array ([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [2] => 400) Бажаний вихід: Array ([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [4] => 400)
Сувік

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.
Корнель

@porneL: що таке Інтерфейсний інтерфейс?
Пім Джагер

2
Я б відредагував його коментар, але ви не можете цього зробити. Я думаю, що він має на увазі Ітератора
Аллайн Лалонде

Мені дуже подобається ваше рішення, Аллайн, але я боюся, що це означає, що нам доведеться переписати всю нашу програму, якщо ми вирішимо її використовувати.
Вейном

3
Гаразд ... тоді виберіть спосіб, який не потребує повного перезапису.
Аллайн Лалонда

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

6
Це швидше, ніж прийнята відповідь у версіях PHP <7 (приблизно 50% швидше). Але в PHP> = 7 прийнята відповідь на кшталт 400% швидша. Дивіться тут: sandbox.onlinephpfunctions.com/code/…
yunzen

Як ми можемо тут використовувати або об'єднати дані?

1
@ramedju У цьому прикладі $objectBзберігаються об'єднані дані.
Корнель

10

Я розумію, що використання загальних об’єктів [stdClass ()] та їх передавання у вигляді масивів відповідає на питання, але я подумав, що Композитор - це чудова відповідь. Але я відчував, що це може використовувати деякі вдосконалення функцій і може бути корисним для когось іншого.

Особливості:

  • Вкажіть посилання або клон
  • Вкажіть перший або останній запис, щоб мати перевагу
  • Кілька (більше двох) об'єктів, що зливаються з подібністю синтаксису до array_merge
  • Спосіб зв’язування: $ 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
щоб узагальнити ваші коментарі видаліть Ampersand (&) з $ об’єкта всередині: foreach (перший коментар) ... array_push, array_unshift (другий коментар)
Кріс,

1
@Chris Я оновив код, щоб вирішити проблеми за коментарями вище.
Райан Шумахер

У своєму коді "Використання" ви неправильно
написали

7

Дуже просте рішення, враховуючи, що у вас є об'єкти A і B:

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

Це все. Тепер у вас є objA з усіма значеннями від objB.


Чому б ти не просто робив: $ objB = $ objA;
Scottymeuk

2

\ArrayObjectКлас має можливість обміну поточний масив , щоб відключити оригінальну посилання . Для цього він пропонує два зручні методи: exchangeArray()і getArrayCopy(). Решта - це просто простий 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;

Це дуже розумно, капелюхи до цього. Я не думаю, що мені було б зручно з методами, які не визначені в результуючому класі об'єктів.
Слізерин

спасибі? .. за капелюх ... Це було просто для розваги, і я згоден з вами щодо зручності в користуванні здебільшого щодо автозаповнення в 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. Сподіваюся, це комусь допоможе!

Після того, як Об'єкт знаходиться у форматі масиву, ви можете використовувати 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;
}

Вся заслуга належить: Джейсону Оклі

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.