web-dev-qa-db-fra.com

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

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

Comment copier un objet PHP dans un type d'objet différent

//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'ici, nous envisageons de transformer les objets A et B en tableaux, puis de les fusionner en utilisant array_merge () avant de les transformer à nouveau en objet, mais nous ne pouvons pas dire que nous en sommes fiers.
201
Veynom

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 aussi lorsque les objets ont des méthodes. (testé avec PHP 5.3 et 5.6)

389
flochtililoch

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

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

Vous pouvez créer un autre objet qui distribue des appels de méthodes magiques aux objets sous-jacents. Voici comment vous manipuleriez __get, mais pour le faire fonctionner correctement, vous devrez remplacer toutes les méthodes magiques pertinentes. Vous trouverez probablement des erreurs de syntaxe puisque je viens de les saisir par cœur. 

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.

27
Allain Lalonde
foreach($objectA as $k => $v) $objectB->$k = $v;
22
Kornel

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

Caractéristiques: 

  • Spécifiez la référence ou le clone
  • Spécifiez la première ou la dernière entrée pour avoir priorité
  • Plusieurs objets (plus de deux) fusionnant avec une similarité de syntaxe avec array_merge
  • Méthode de liaison: $ obj-> f1 () -> f2 () -> f3 () ...
  • Dynamique composites: $ obj-> fusionner (...)/* travailler ici */$ obj-> fusionner (...)

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';
10
Ryan Schumacher

Une solution très simple considérant que vous avez 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.

5
Jônatas Eridani

une solution Pour préserver les méthodes et les propriétés des objets fusionnés, vous devez créer une classe de combinateur 

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

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

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

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 );
1
Corelmax

J'irais avec lier le 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');
1
Adrian

Voici une fonction qui va aplatir 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 placer ceci 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;
}
0
jonathaneyoung

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 nous aidera sûrement à trouver la réponse… .. Le crédit pour le code ci-dessus va à moi :)

0
Rolf

Si vous avez besoin de fusionner plusieurs niveaux parce que certains objets sont imbriqués dans les propriétés requises, voici une solution: 

  • passe récursivement à tous les niveaux, 
  • il reconnaît la clé de chaîne en tant que propriétés uniques qui doivent être remplacées par la deuxième valeur, 
  • et supprime la valeur en double.

Solution

$arr1 = [
    "head"=>[
        "title"=>"Tie1",
        3,
        5
    ],
    2,
    3
];
$arr2 = [
    3,
    4,
    5,
    "head"=>[
        "title"=>"Tie2",
        2,
        4,
        5
    ]
];

function object_merge($obj1, $obj2){
    $newVal = $obj1;
    foreach($obj2 as $key => $val){
        if(is_array($val)) $newVal[$key] = object_merge($obj1[$key], $val);
        elseif(is_string($key)) {
            $newVal[$key] = $val;
        }
        else $newVal[] = $val;
    }
    return array_unique($newVal, SORT_REGULAR);
}

$newArr = object_merge($arr1, $arr2);
print_r($newArr);

Sortie

Array
(
    [head] => Array
        (
            [title] => Tie2
            [0] => 3
            [1] => 5
            [2] => 2
            [3] => 4
        )

    [0] => 2
    [1] => 3
    [3] => 4
    [4] => 5
)
0
Jonathan Gagne

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 ça aide quelqu'un!

Une fois qu'un objet est au format tableau, vous pouvez utiliser array_merge et le reconvertir en objet si nécessaire.

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

Manière 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 crédit va à: Jason Oakley

0
Johan Pretorius