Quelle est la meilleure méthode pour fusionner deux objets PHP?

222

Nous avons deux objets PHP5 et aimerions fusionner le contenu de l'un dans le second. Il n'y a aucune notion de sous-classes entre elles, donc les solutions décrites dans la rubrique suivante ne peuvent pas s'appliquer.

Comment copiez-vous un objet PHP dans un autre type d'objet

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

Remarques:

  • Ce sont des objets, pas des classes.
  • Les objets contiennent beaucoup de champs, donc un foreach serait assez lent.
  • Jusqu'à présent, nous envisageons de transformer les objets A et B en tableaux, puis de les fusionner à l'aide de array_merge () avant de les transformer à nouveau en un objet, mais nous ne pouvons pas dire que nous en sommes fiers.
Veynom
la source
30
"Les objets contiennent pas mal de champs, donc un foreach serait assez lent." - Les ordinateurs sont assez rapides, «assez lents» est souvent assez rapide.
Sean McSomething le

Réponses:

435

Si vos objets ne contiennent que des champs (pas de méthodes), cela fonctionne:

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

Cela fonctionne également lorsque les objets ont des méthodes. (testé avec PHP 5.3 et 5.6)

flochtililoch
la source
1
Vous pouvez également utiliser array_merge_recursive pour avoir un comportement de copie en profondeur. Vous pouvez également être intéressé par array_replace_recursive. Les différences sont expliquées en détail ici: brian.serveblog.net/2011/07/31/php-array_replace-vs-array_merge
Vincent Pazeller
12
L'objet qui en résulte sera une instance de stdclass. Bien qu'il "fonctionne" dans un sens sur les objets avec des méthodes, il ruine effectivement l'objet dans ce cas (en supprimant les méthodes).
Brilliand
Ceci est utile pour renvoyer plusieurs jeux de résultats dans une seule fonction (et ne renvoyer qu'un objet avec des paires clé-valeur.)
Leonel Atencio
1
Cela ne fonctionnera pas s'il y a une clé entière dans l'objet. Prenons l'exemple suivant: $ arr1 = array ('a' => 9, 'b' => 'asd'); $ arr2 = tableau ('a' => 10, 'd' => 'qwert', 0 => 100, 1 => 200, 4 => 400); $ arr3 = array_merge ($ arr1, $ arr2); echo (print_r ($ arr3, 1)); Sortie réelle: tableau ([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [2] => 400) Sortie souhaitée: tableau ([a] => 10 [b] => asd [d] => qwert [0] => 100 [1] => 200 [4] => 400)
Souvik
2
Est-ce juste moi ou cette réponse est-elle une copie textuelle d'une réponse qui avait déjà été publiée pendant des mois? stackoverflow.com/a/794356/151509
maryisdead
28

Vous pouvez créer un autre objet qui envoie des appels aux méthodes magiques aux objets sous-jacents. Voici comment vous le feriez __get, mais pour le faire fonctionner pleinement, vous devez remplacer toutes les méthodes magiques pertinentes. Vous trouverez probablement des erreurs de syntaxe puisque je viens de le saisir du haut de ma tête.

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

Bonne chance.

Allain Lalonde
la source
L'implémentation complète aurait probablement besoin de __isset (), __unset () et implémenterait l'interface Interator.
Kornel
@porneL: qu'est-ce que l'Interator Interface?
Pim Jager
2
Je modifierais son commentaire, mais vous ne pouvez pas faire ça. Je pense qu'il veut dire Itérateur
Allain Lalonde
J'aime beaucoup votre solution, Allain, mais je crains que cela signifie que nous devons réécrire toute notre application si nous décidons de l'utiliser.
Veynom
3
Ok ... puis choisissez la voie qui ne nécessite pas de réécriture complète.
Allain Lalonde le
25
foreach($objectA as $k => $v) $objectB->$k = $v;
Kornel
la source
6
C'est plus rapide que la réponse acceptée dans les versions PHP <7 (estimée 50% plus rapide). Mais en PHP> = 7, la réponse acceptée est quelque chose comme 400% plus rapide. Voir ici: sandbox.onlinephpfunctions.com/code/…
yunzen
Comment pouvons-nous utiliser ou obtenir les données fusionnées ici?
1
@ramedju Dans cet exemple $objectBcontient les données fusionnées.
Kornel
10

Je comprends que l'utilisation des objets génériques [stdClass ()] et leur conversion en tableaux répondent à la question, mais je pensais que le Compositeur était une excellente réponse. Pourtant, je pensais qu'il pourrait utiliser certaines améliorations de fonctionnalités et pourrait être utile pour quelqu'un d'autre.

Fonctionnalités:

  • Spécifiez une référence ou un clone
  • Spécifiez la première ou la dernière entrée pour avoir la priorité
  • Fusion de plusieurs objets (plus de deux) avec une similitude de syntaxe avec array_merge
  • Liaison de méthode: $ obj-> f1 () -> f2 () -> f3 () ...
  • Composites dynamiques : $ obj-> merge (...) / * work here * / $ 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;
    }
}

Usage:

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

Exemple:

$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
la source
2
Juste pour souligner: la référence de passage par appel a été marquée comme obsolète en PHP 5.3.0 et supprimée en PHP 5.4.0 (entraînant une erreur fatale élevée). Pour corriger le problème: le remplacement foreach($objects as &$object) $this->with(&$object);par foreach($objects as &$object) $this->with($object);corrige le problème. Source: [ php.net/manual/en/language.references.pass.php]
wes.hysell
2
De plus: if($this->first_precedence) array_push($this->composite, &$object); else array_unshift($this->composite, &$object);doit être remplacé parif($this->first_precedence) array_push($this->composite, $object); else array_unshift($this->composite, $object);
wes.hysell
1
pour résumer vos commentaires, supprimez Ampersand (&) de $ object inside: foreach (premier commentaire) ... array_push, array_unshift (deuxième commentaire)
Chris
1
@Chris J'ai mis à jour le code pour corriger les problèmes selon les commentaires ci-dessus.
Ryan Schumacher
Dans votre code "Usage", vous avez mal orthographié Compositor en tant que Compositeur
Xesau
7

Une solution très simple si l'on considère les objets A et B:

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

C'est tout. Vous avez maintenant objA avec toutes les valeurs de objB.

Jônatas Eridani
la source
Pourquoi ne feriez-vous pas simplement: $ objB = $ objA;
Scottymeuk
2

La \ArrayObjectclasse a la possibilité d' échanger le tableau actuel pour déconnecter la référence d' origine . Pour ce faire, il est livré avec deux méthodes pratiques: exchangeArray()et getArrayCopy(). Le reste est tout simplement simple array_merge()de l'objet fourni avec les ArrayObjectpropriétés publiques s:

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

L'utilisation est aussi simple que cela:

 $base = new MergeBase();

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

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

 $base->merge( $toMerge );
Corelmax
la source
Cela devrait en fait être la réponse acceptée . La seule chose intéressante serait de pouvoir merge($array)en demander un \ArrayObjectégalement.
kaiser
2

une solution Pour préserver, les méthodes et les propriétés des objets fusionnés consistent à créer une classe combinatoire

  • prendre n'importe quel nombre d'objets sur __construct
  • accéder à n'importe quelle méthode en utilisant __call
  • accéder à toute propriété en utilisant __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';
    } 
}

utilisation simple

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
la source
C'est très intelligent, chapeau à ça. Je ne pense pas que je serais à l'aise avec des méthodes non définies sur la classe d'objets résultante.
Serpentard
merci? .. pour le chapeau ... C'était juste pour le plaisir et je suis d'accord avec vous sur le confort d'utilisation principalement en ce qui concerne la saisie semi-automatique dans netbeans ou autre éditeur
bortunac
1

J'irais avec la liaison du deuxième objet dans une propriété du premier objet. Si le deuxième objet est le résultat d'une fonction ou d'une méthode, utilisez des références. 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
la source
1

Pour fusionner un nombre quelconque d'objets bruts

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

Voici une fonction qui aplatira un objet ou un tableau. Utilisez-le uniquement si vous êtes sûr que vos clés sont uniques. Si vous avez des clés du même nom, elles seront écrasées. Vous devrez le placer dans une classe et remplacer "Fonctions" par le nom de votre classe. Prendre plaisir...

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

la source
0

Restons simples!

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

Si cela ne répond pas à votre question, cela aidera sûrement à la réponse. Le crédit pour le code ci-dessus me revient :)

Rolf
la source
0

Cet extrait de code convertira récursivement ces données en un seul type (tableau ou objet) sans les boucles foreach imbriquées. J'espère que cela aide quelqu'un!

Une fois qu'un objet est au format tableau, vous pouvez utiliser array_merge et reconvertir en objet si vous en avez besoin.

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

Voie procédurale

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

Tout le mérite revient à: Jason Oakley

Drmzindec
la source