La fonction array_merge_recursive() n'agit pas vraiment comme on pourrait s'y attendre

La fonction array_merge_recursive() n'agit pas vraiment comme on pourrait s'y attendre. Au lieu de simplement combiner des tableaux associatifs, elle en crée de nouveaux pour toutes les valeurs aux clés identiques, même s'il s'agit de simples booléens ou chaines de caractères.

Prenons le cas tout simple de la combinaison de deux tableaux pour configurer un framework génial :

<?php

array_merge_recursive
(
    array('cache modules' => false),
    array('cache modules' => true)
)

array_merge_recursive renverra le tableau suivant :

array
  'cache modules' => 
    array
      0 => boolean false
      1 => boolean true

Alors que j'aurais préféré le résultat suivant :

array
  'cache modules' => boolean true

Pas vraiment ce à quoi on pourrait s'attendre

Alors oui, vu le résultat autant utiliser la fonction array_merge. Pourtant, j'ai vraiment besoin de la récursion, sinon comment combiner les tableaux suivants :

<?php

$ar1 = array
(
    'use cache' => false,

    'modules' => array
    (
        'WdCore' => 'wdcore.php'
    )
);

$ar2 = array
(
    'use cache' => true,

    'modules' => array
    (
        'WdPatron' => 'wdpatron.php'
    )
);

Pour obtenir le résultat suivant :

array
  'cache modules' => boolean true
  'modules' => 
    array
      'WdCore' => string 'wdcore.php' (length=10)
      'WdPatron' => string 'wdpatron.php' (length=12)

Ce que dit la documentation

Si les tableaux passés en arguments ont les mêmes clés (chaînes de caractères), les valeurs sont alors rassemblées dans un tableau, de manière récursive, de façon à ce que, si l'une de ces valeurs est un tableau elle-même, la fonction la rassemblera avec les valeurs de l'entrée courante.

J'ai mis en évidence « si l'une de ces valeurs est un tableau elle-même », parce que c'est là, pour moi, que le bât blesse. En effet, cache modules n'est pas un tableau. Pourquoi se trouve-t-il convertit de force ?

array_merge_recursive corrigée

La fonction wd_array_merge_recursive est ce que j'attendais en terme de combinaison de tableaux. La combinaison de deux clés identiques se fait en deux temps :

  1. Si, et seulement si, le type de la valeur du second tableau est un tableau associatif, alors la valeur du premier tableau est transformée en tableau et les deux tableaux sont combinés.
  2. ensuite, tout dépend de la clé :

    • Si la clé est numérique alors la valeur est ajoutée au tableau
    • dans le cas contraire, la valeur de la clé est remplacée.
<?php

function wd_array_merge_recursive(array $array1, array $array2=array())
{
    $arrays = func_get_args();

    $merge = array_shift($arrays);

    foreach ($arrays as $array)
    {
        foreach ($array as $key => $val)
        {
            #
            # if the value is an array and the key already exists
            # we have to make a recursion
            #

            if (is_array($val) && array_key_exists($key$merge))
            {
                $val = wd_array_merge_recursive((array) $merge[$key]$val);
            }

            #
            # if the key is numeric, the value is pushed. Otherwise, it replaces
            # the value of the _merge_ array.
            #

            if (is_numeric($key))
            {
                $merge[] = $val;
            }
            else
            {
                $merge[$key] = $val;
            }
        }
    }

    return $merge;
}

Trois méthodes de combinaison

Voyons ce que cela donne si l'on combine les tableaux suivants avec les fonctions array_merge, array_merge_recursive et wd_array_merge_recursive.

<?php

$ar1 = array
(
    'color' => array
    (
        'favorite' => 'red'
    ),

    5,

    'use cache' => false,

    'modules' => array
    (
        'WdCore' => 'wdcore.php'
    ),

    'var1' => array(12),
    'var2' => 1
);

$ar2 = array
(
    10,

    'color' => array
    (
        'favorite' => 'green',
        'blue'
    ),

    'use cache' => true,

    'modules' => array
    (
        'WdPatron' => 'wdpatron.php'
    ),

    'var1' => 1,
    'var2' => array(23)
);

Résultat pour array_merge

array
  'color' => 
    array
      'favorite' => string 'green' (length=5)
      0 => string 'blue' (length=4)
  0 => int 5
  'use cache' => boolean true
  'modules' => 
    array
      'WdPatron' => string 'wdpatron.php' (length=12)
  'var1' => int 1
  'var2' => 
    array
      0 => int 2
      1 => int 3
  1 => int 10

Résultat pour array_merge_recursive

array
  'color' => 
    array
      'favorite' => 
        array
          0 => string 'red' (length=3)
          1 => string 'green' (length=5)
      0 => string 'blue' (length=4)
  0 => int 5
  'use cache' => 
    array
      0 => boolean false
      1 => boolean true
  'modules' => 
    array
      'WdCore' => string 'wdcore.php' (length=10)
      'WdPatron' => string 'wdpatron.php' (length=12)
  'var1' => 
    array
      0 => int 1
      1 => int 2
      2 => int 1
  'var2' => 
    array
      0 => int 1
      1 => int 2
      2 => int 3
  1 => int 10

Résultat pour wd_array_merge_recursive

array
  'color' => 
    array
      'favorite' => string 'green' (length=5)
      0 => string 'blue' (length=4)
  0 => int 5
  'use cache' => boolean true
  'modules' => 
    array
      'WdCore' => string 'wdcore.php' (length=10)
      'WdPatron' => string 'wdpatron.php' (length=12)
  'var1' => int 1
  'var2' => 
    array
      0 => int 1
      1 => int 2
      2 => int 3
  1 => int 10

Par rapport à array_merge_recursive, on peut constater que les valeurs de modules ont été combinées avec merveille et que var2 comporte bien une nouvelle entrée. Par contre il y a des différences :

  • favorite a été changé de red à green
  • use cache a été changé de false à true
  • var1 est maintenant un entier puisque c'est comme cela qu'il est défini dans le second tableau

Conlusion

Si le comportement de array_merge_recursive ne vous a jamais gêné, c'est tant mieux. En tout cas, vous aurez maintenant l'embarras du choix quand à la méthode à utiliser pour combiner vos tableaux associatifs.

Laisser un commentaire

5 commentaires

Praud
Praud

Bonjour, je suis tombé sur votre site, grace à notre excellent amis google. Et votre fonction recursive est très réussi. J'avoue cependant avoir du mal à concevoir la récursivité, j'ai un peu de mal.

David J.
David J.

A noter que depuis PHP 5.3 il y a array_replace_recursive() qui fait la même chose…

http://php.net/manual/fr/function.array-replace-recursive.php

Olivier
Olivier

Sérieux !? Je regarde ça dès que je rentre chez moi ! Merci David.

Olivier
Olivier

Bon, en fait ça ne marche pas de la même façon, cette fonction préserve les clés numériques, alors que la mienne fonctionne comme array_merge(). En prenant l'exemple donné dans la doc j'obtiens le résultat suivant  :

array (size=2)
  'citrus' => 
    array (size=2)
      0 => string 'orange' (length=6)
      1 => string 'pineapple' (length=9)
  'berries' => 
    array (size=3)
      0 => string 'blackberry' (length=10)
      1 => string 'raspberry' (length=9)
      2 => string 'blueberry' (length=9)

Pas du tout la même chose.

Badji
Badji

Merci infiniment …