Pbcopie de tableau après utilisation de références -COMPLEXE

Mammouth du PHP | 505 Messages

29 juin 2007, 11:48

Plutot qu'un long discours, voila un bout de code qui reproduit mon pb
// un tableau de démo
$tree['one']['two']['three'] = '__LEAF__';
// On affiche le contenu
print_r($tree);

// crée un pointer sur le tableau
$pointer = &$tree;

// parcour le tableau jusqu'a la premiere feuille 
// Ca n'alter rien ca parcours, a la fin du while, echo $pointer retournerai __LEAF__
while(is_array($pointer)) {
	$key = key($pointer);
	$pointer = &$pointer[$key];
}

// copy le tableau
$copy = $tree;

// Modifie la feuille
$tree['one']['two']['three'] = 'ALTER';
// Affiche le tableau d'origine et la copy
print_r($tree);
print_r($copy);
Et le résultat

Code : Tout sélectionner

Array ( [one] => Array ( [two] => Array ( [three] => __LEAF__ ) ) ) Array ( [one] => Array ( [two] => Array ( [three] => ALTER ) ) ) Array ( [one] => Array ( [two] => Array ( [three] => ALTER ) ) )
La copie est altérée aussi ... :(

Pourquoi le fait de créer un pointeur sur un tableau rend induplicable ce tableau ?
Suis-je tombé sur un bug ? Comment faire pour obtenir une copie

ViPHP
ViPHP | 2287 Messages

29 juin 2007, 12:30

Bonjour,

Petite lecture du manuel qu'on ne lit jamais assez :) : http://www.php.net/manual/fr/language.o ... gnment.php

Code : Tout sélectionner

Depuis PHP 5, les objets sont assignés par référence, sans une demande spécifique du contraire en utilisant le nouveau mot clé clone.
Ce qui n'est pas écrit, dans la version française, c'est que ça concerne aussi les tableaux.

Déjà, demande toi sérieusement si tu as vraiment besoin d'une copie de ton tableau. Si tu la veux vraiment, je pense que
$copie=unserialize(serialize($letableau));

fera l'affaire.
if(!@work()){ Nespresso(); } else { what(); }
______________________________

Mammouth du PHP | 505 Messages

29 juin 2007, 13:21

Oui, on est bien d'accord, les objets sont passé par référence en php5.

Ceci étant dit, en php, un tableau n'est pas un objet mais un type basique. donc $tab1 = $tab2 est censé effectuer une copie si $tab2 est un tableau ou n'importe qu elle type basique

ViPHP
ViPHP | 2287 Messages

29 juin 2007, 13:35

Oui, on est bien d'accord, les objets sont passé par référence en php5.
Et les tableaux. C'est une lacune de la documentation mais c'est pourtant vrai, tu peux me croire.
Ceci étant dit, en php, un tableau n'est pas un objet mais un type basique. donc $tab1 = $tab2 est censé effectuer une copie si $tab2 est un tableau ou n'importe qu elle type basique
http://www.php.net/manual/fr/language.types.php

Code : Tout sélectionner

PHP supporte quatre types scalaires : * booléen * entier * nombre à virgule flottante * chaîne de caractères PHP supporte deux types composés : * tableau * objet PHP supporte deux types spéciaux : * ressource * NULL
if(!@work()){ Nespresso(); } else { what(); }
______________________________

Mammouth du PHP | 505 Messages

29 juin 2007, 13:56

Non, je ne suis pas d'accord.
Et je le prouve :)
$tree['one']['two']['three'] = '__LEAF__';

$copy = $tree;
$tree['one']['two']['three'] = 'ALTER';
print_r($copy);echo '<br>';
print_r($tree);echo '<br>';
$copy = &$tree;
$tree['one']['two']['three'] = 'REALTER';
print_r($copy);echo '<br>';
print_r($tree);echo '<br>';

Resultat
La premiere fois, on a bien 2 instances distinctes, et quand je fais une copy par référence, j'ai bien une référence, mais il faut que je la demande. Dans le cas présent, c'est le comportement normal et attendu. Donc $var = $tableau effectue une copie du tableau, pas une référence.

Code : Tout sélectionner

Array ( [one] => Array ( [two] => Array ( [three] => __LEAF__ ) ) ) Array ( [one] => Array ( [two] => Array ( [three] => ALTER ) ) ) Array ( [one] => Array ( [two] => Array ( [three] => REALTER ) ) ) Array ( [one] => Array ( [two] => Array ( [three] => REALTER ) ) )

ViPHP
ViPHP | 2287 Messages

29 juin 2007, 14:18

Non, je ne suis pas d'accord.
Et je le prouve :)
$tree['one']['two']['three'] = '__LEAF__';

$copy = $tree;
$tree['one']['two']['three'] = 'ALTER';
print_r($copy);echo '<br>';
print_r($tree);echo '<br>';
$copy = &$tree;
$tree['one']['two']['three'] = 'REALTER';
print_r($copy);echo '<br>';
print_r($tree);echo '<br>';



Resultat
La premiere fois, on a bien 2 instances distinctes, et quand je fais une copy par référence, j'ai bien une référence, mais il faut que je la demande. Dans le cas présent, c'est le comportement normal et attendu. Donc $var = $tableau effectue une copie du tableau, pas une référence.

Code : Tout sélectionner

Array ( [one] => Array ( [two] => Array ( [three] => __LEAF__ ) ) ) Array ( [one] => Array ( [two] => Array ( [three] => ALTER ) ) ) Array ( [one] => Array ( [two] => Array ( [three] => REALTER ) ) ) Array ( [one] => Array ( [two] => Array ( [three] => REALTER ) ) )
En effet, tu as raison. Et encore plus étonnant :
// simple copie, on modifie l'original juste après : la copie ne change pas
$tree['one']['two']['three'] = '__LEAF__';
$copy = $tree;
$tree['one']['two']['three'] = 'ALTER'; 

echo 'copy : '.print_r($copy,true).'<br>';
echo 'tree : '.print_r($tree,true).'<br>';

// référence puis modification de l'original : la référence change
$copy = &$tree;
$tree['one']['two']['three'] = 'REALTER';

echo 'copy : '.print_r($copy,true).'<br>';
echo 'tree : '.print_r($tree,true).'<br>';

// simple copie et modification : la copie change !!!!
$copy = $tree;
$tree['one']['two']['three'] = 'REREALTER';

echo 'copy : '.print_r($copy,true).'<br>';
echo 'tree : '.print_r($tree,true).'<br>';

// Pour forcer la copie : forcer la destruction de la variable
// pour revenir à 1 seule référence
unset($copy);
$copy = $tree;
$tree['one']['two']['three'] = 'REREREALTER';

echo 'copy : '.print_r($copy,true).'<br>';
echo 'tree : '.print_r($tree,true).'<br>';

Code : Tout sélectionner

copy : Array ( [one] => Array ( [two] => Array ( [three] => __LEAF__ ) ) ) tree : Array ( [one] => Array ( [two] => Array ( [three] => ALTER ) ) ) copy : Array ( [one] => Array ( [two] => Array ( [three] => REALTER ) ) ) tree : Array ( [one] => Array ( [two] => Array ( [three] => REALTER ) ) ) copy : Array ( [one] => Array ( [two] => Array ( [three] => REREALTER ) ) ) tree : Array ( [one] => Array ( [two] => Array ( [three] => REREALTER ) ) ) copy : Array ( [one] => Array ( [two] => Array ( [three] => REREALTER ) ) ) tree : Array ( [one] => Array ( [two] => Array ( [three] => REREREALTER ) ) )
J'en déduis que le compteur de références de php a un comportement différent vis-à-vis des affectations de tableaux, s'il existe une seule référence vers le tableau : copie (comme en php4) ou s'il en existe plusieurs : création d'une nouvelle référence vers le même tableau.

Et j'ai testé l'astuce que je t'ai donnée plus haut, passer par serialize() et unserialize()... Elle ne fonctionne pas :roll: Il faut vraiment faire retomber le compteur de références à 1 pour avoir une copie indépendante.
if(!@work()){ Nespresso(); } else { what(); }
______________________________

Mammouth du PHP | 505 Messages

29 juin 2007, 14:28

Ah si, le coup du unserialize/serialize fonctionne. Je vois pas comment il pourrait en etre autrement, le serialize ne gere pas de notion de référence.

C'est dailleurs comme ca que je contourne mon bug pour l'instant. Je suis en 5.2.0 et toi tu fais tes tests en quelle version, je pense que je vais ouvrir un bug.

$tree['one']['two']['three'] = 'LEAF';
$pointer = &$tree;
while(is_array($pointer)) {
	$key = key($pointer);
	$pointer = &$pointer[$key];
}
$copy = unserialize(serialize($tree));
print_r($tree);echo '<br>';
print_r($copy);echo '<br>';
$tree['one']['two']['three'] = 'ALTER';
print_r($tree);echo '<br>';
print_r($copy);echo '<br>';

Code : Tout sélectionner

Array ( [one] => Array ( [two] => Array ( [three] => LEAF ) ) ) Array ( [one] => Array ( [two] => Array ( [three] => LEAF ) ) ) Array ( [one] => Array ( [two] => Array ( [three] => ALTER ) ) ) Array ( [one] => Array ( [two] => Array ( [three] => LEAF ) ) )
Si quelqu'un a une 5.2.3 sous la main et peux me donner le résultat du test, merci d'avance.

ViPHP
ViPHP | 2287 Messages

29 juin 2007, 14:50

J'utilise PHP 5.2.1 (cli) (built: May 22 2007 19:05:44) sous Linux.

Essaye ça (regarde bien le test du serialize en 4°) :
// simple copie, on modifie l'original juste après : la copie ne change pas [OK]
$tree['one']['two']['three'] = 0;
$copy = $tree;
$tree['one']['two']['three'] = 1; 

echo 'copy : '.print_r($copy,true).'<br>';
echo 'tree : '.print_r($tree,true).'<br>';

// référence puis modification de l'original : la référence change [OK]
$copy = &$tree;
$tree['one']['two']['three'] = 2;

echo 'copy : '.print_r($copy,true).'<br>';
echo 'tree : '.print_r($tree,true).'<br>';

// simple copie et modification : la copie change [??]
$copy = $tree;
$tree['one']['two']['three'] = 3;

echo 'copy : '.print_r($copy,true).'<br>';
echo 'tree : '.print_r($tree,true).'<br>';

// copie par sérialisation : la copie change [??]
$copy = unserialize(serialize($tree));
$tree['one']['two']['three'] = 4;

echo 'copy : '.print_r($copy,true).'<br>';
echo 'tree : '.print_r($tree,true).'<br>';

// Pour forcer la copie : revenir à 1 seule référence [OK]
unset($copy);
$copy = $tree;
$tree['one']['two']['three'] = 5;

echo 'copy : '.print_r($copy,true).'<br>';
echo 'tree : '.print_r($tree,true).'<br>';
Chez moi ça donne :

Code : Tout sélectionner

copy : Array ( [one] => Array ( [two] => Array ( [three] => 0 ) ) ) tree : Array ( [one] => Array ( [two] => Array ( [three] => 1 ) ) ) copy : Array ( [one] => Array ( [two] => Array ( [three] => 2 ) ) ) tree : Array ( [one] => Array ( [two] => Array ( [three] => 2 ) ) ) copy : Array ( [one] => Array ( [two] => Array ( [three] => 3 ) ) ) tree : Array ( [one] => Array ( [two] => Array ( [three] => 3 ) ) ) copy : Array ( [one] => Array ( [two] => Array ( [three] => 4 ) ) ) tree : Array ( [one] => Array ( [two] => Array ( [three] => 4 ) ) ) copy : Array ( [one] => Array ( [two] => Array ( [three] => 4 ) ) ) tree : Array ( [one] => Array ( [two] => Array ( [three] => 5 ) ) )
Le bug serait donc bien dû à une référence non-détruite, vu que même serialize/unserialize n'y change rien.

Dans le code que tu donnais juste au dessus, tu stockes la référence dans $pointer et ensuite la copies dans $copy, ce qui peut expliquer pourquoi ça marche.
if(!@work()){ Nespresso(); } else { what(); }
______________________________

Mammouth du PHP | 505 Messages

29 juin 2007, 15:35

Bah dans le code que je donne, si on ne fais pas le serialize/unserialize, le bug est la.

Pour ton cas, y a encore plus etrange...

// simple copie, on modifie l'original juste après : la copie ne change pas [OK]
$tree['one']['two']['three'] = 0;
$copy = $tree;
$tree['one']['two']['three'] = 1;

echo 'copy : '.print_r($copy,true).'<br>';
echo 'tree : '.print_r($tree,true).'<br><hr>';

// référence puis modification de l'original : la référence change [OK]
$copy = &$tree;
$tree['one']['two']['three'] = 2;

echo 'copy : '.print_r($copy,true).'<br>';
echo 'tree : '.print_r($tree,true).'<br><hr>';

// simple copie et modification : la copie change [??] mais pas copy2 !!!
$copy2 = $tree;
$copy = $tree;
$tree['one']['two']['three'] = 3;

echo 'copy : '.print_r($copy,true).'<br>';
echo 'tree : '.print_r($tree,true).'<br>';
echo 'copy2 : '.print_r($copy2,true).'<br><hr>';

// copie par sérialisation : la copie change [??] mais pas copy3
$copy3 = unserialize(serialize($tree));
$copy = unserialize(serialize($tree));
$tree['one']['two']['three'] = 4;

echo 'copy : '.print_r($copy3,true).'<br>';
echo 'tree : '.print_r($tree,true).'<br>';
echo 'copy3 : '.print_r($copy3,true).'<br><hr>';

Code : Tout sélectionner

copy : Array ( [one] => Array ( [two] => Array ( [three] => 0 ) ) ) tree : Array ( [one] => Array ( [two] => Array ( [three] => 1 ) ) ) copy : Array ( [one] => Array ( [two] => Array ( [three] => 2 ) ) ) tree : Array ( [one] => Array ( [two] => Array ( [three] => 2 ) ) ) copy : Array ( [one] => Array ( [two] => Array ( [three] => 3 ) ) ) tree : Array ( [one] => Array ( [two] => Array ( [three] => 3 ) ) ) copy2 : Array ( [one] => Array ( [two] => Array ( [three] => 2 ) ) ) copy : Array ( [one] => Array ( [two] => Array ( [three] => 3 ) ) ) tree : Array ( [one] => Array ( [two] => Array ( [three] => 4 ) ) ) copy3 : Array ( [one] => Array ( [two] => Array ( [three] => 3 ) ) )
On peut donc en déduire que lorsque une variable est utilisé en tant que référence, elle est transformée pointeur et se comporte après coup systématiquement en pointeur, meme si tu lui affecte autre chose qu'une référence.

Personne pour tester ce truc en 5.2.3 ?

Eléphanteau du PHP | 11 Messages

29 juin 2007, 15:37

Je vois pas de bug, moi.
Le problème, c'est que lorsque tu modifies la feuille après la copie, $pointer est encore une référence du dernier champ du tableau, donc en modifiant $tree['one']['two']['three'], tu modifies aussi $pointer, donc la copie aussi (à cause de la référence toujours présente).

Un
unset($pointer);
juste avant ta copie et tu n'as plus de problème (ou alors j'ai rien compris :D).


Tu peux reproduire le comportement avec un script plus simple encore :
<?php
	header('Content-type: text/plain');
	
	$tab = array('truc');
	
	// On crée une référence vers la cellule du tableau
	$ref =& $tab[0];
	
	// On fait une copie du contenu, la référence est "dupliquée" aussi
	$copie = $tab;
	
	// On modifie la cellule
	$tab[0] = 'autre chose';
	
	// Les deux tableaux contiennent bien exactement la même chose
	echo "Tableau original :\n";
	print_r($tab);
	echo "\nCopie :\n";
	print_r($copie);
	
	/* Affiche :
Tableau original :
Array
(
    [0] => autre chose
)

Copie :
Array
(
    [0] => autre chose
)
	*/
?>

Mammouth du PHP | 505 Messages

29 juin 2007, 15:58

Ca reproduit bien le pb, mais je suis dubitatif qd meme

Lorsqu'une une référence vers kk $tab[0] est créée, on devrait avoir un pointer sur la le contenu de $tab[0] et point barre. $tab n'est pas modifée par ca. En fait, tant qu'une référence existe, il n'est plus possible de dupliquer la variable, tout ce qu'on obtient c'est des référence. C'est space qd meme .

Eléphanteau du PHP | 11 Messages

29 juin 2007, 16:14

Un problème semblable et l'explication : http://bugs.php.net/bug.php?id=15025

Mammouth du PHP | 505 Messages

29 juin 2007, 16:36

Ah ok, la je pige. C'est donc un pb de conception au niveau php.
Tin, ca date de 2002, a mon avis, c'est pas près de changer.

thx.

ViPHP
ViPHP | 5924 Messages

29 juin 2007, 20:24

En fait, c'est pas super compliqué, et très logique, quand on a une référence entre $machin1 et $machin2, si on fait :

Code : Tout sélectionner

$machin2 = 'truc';
On ne supprime pas la référence entre $machin1 et $machin2, mais on affecte à $machin 1 et $machin2 la même valeur, soit 'truc', hé bien quand on fait :

Code : Tout sélectionner

$machin2 = $machin1;
Il n'y a aucune raison que cela supprime la référence, car cela ne fait qu'affecter à $machin2 et $machin1 la valeur de $machin1. C'est logique, '=' ne supprime pas une référence lors d'une affectation banale, il n'y a aucune raison que cela la supprime pour un cas particulier comme celui là...

Administrateur PHPfrance
Administrateur PHPfrance | 3088 Messages

29 juin 2007, 20:57

Je n'ai pas encore lu le topic en profondeur (j'essaie encore de comprendre le code du premier message) mais il y a deux points que j'aimerais qu'on éclaircisse pour éviter que quelqu'un tombe dessus au hasard d'une recherche et retienne une fausse information.
Ce qui n'est pas écrit, dans la version française, c'est que ça concerne aussi les tableaux.
Comme ça a été vérifié dans la suite du topic, sauf erreur les tableaux ne sont pas passés par références, mais lorsqu'on copie un tableau les références qu'il contient restent intactes.
le serialize ne gere pas de notion de référence
Je ne sais pas si on parle de la même chose, mais serialize() préserve les références au sein d'un objet ou d'un tableau. Par exemple
$a = array(5);
$a[1] =& $a[0];

$a = unserialize(serialize($a));
$a[1] = 3;

print_r($a);
La référence $a[1] <=> $a[0] est préservée et lorsqu'on en modifie un l'autre se retrouve également affecté.

Au passage, j'en profite pour partager une habitude indispensable concernant les références. N'oubliez pas de détruire les références inutilisées à la seconde où vous ne vous en servez plus. Dans l'exemple du premier message, il faudrait donc ajouter
unset($pointer);
...juste à la sortie du while(). Il m'est arrivé de perdre beaucoup de temps sur des bugs extrêment étranges (absurdes ou qui semblent se produire de manière aléatoire) parce que j'avais oublié qu'une variable était une référence et que j'avais par la suite réutilisé cette variable. (un nom commun comme $row ou $tmp par exemple)