OVH Cloud OVH Cloud

Objet : Un peu perdu...

9 réponses
Avatar
Nicolas Ecarnot
Bonjour,

Je me replonge dans un mini code en objet, et j'avoue avoir un peu de mal.
Merci aux spécialistes de m'éclairer et de me conseiller.

J'ai un objet 'node' qui décrit un noeud dans un arbre.
Cet objet contient parmi ses attributs un tableau de ses noeuds fils.
Cet objet 'node' dispose d'une méthode 'ajouterFils', qui effectue un
array_push($this->mesFils, $unNoeud);

Je remplis ainsi un arbre, et ça marche.
J'accède ensuite à un noeud en partant du sommet, et j'accède ainsi à tous
les noeuds.

Maintenant, si je modifie l'attribut d'un noeud, je ne vois pas la modif vu
du top.

Cela voudrait-il dire qu'en 'pushant' (voir ligne array_push ci-dessus)
dans le tableau, je viens d'en faire une copie par valeur ?
Dans ce cas, les modifs sur l'objet lui-même ne seront pas répercutées ?

Alors mes questions :
- Ai-je bien compris la problémtaique ? (-> push par valeur...)
- Si oui, est-il alors possible de pusher par référence ? (avec & ?)
- Si non, comment font les meilleurs codeurs objet en php ?

--
Nicolas Ecarnot

9 réponses

Avatar
Bruno Desthuilliers
Bruno Desthuilliers wrote:
(snip)
La bonne solution consiste à passer le noeud fils par référence :
class Node {
function ajouterFils(&unNoeud) {
array_push($this->mesFils, $unNoeud);
}
}
Note le & avant le nom du paramètre dans le prototype de ajouterFils().
Là, tu n'a pas à t'embêter avec les appels, le problème est réglé avant.



Petite précision quand même : ça règle le problème entre l'appel à
Node::AjouterFils() et l'appel à array_push().

Si le Node est trimballé par plusieurs fonctions ou méthodes avant
d'arriver à Node::AjouterFils(), il faut vérifier que toute la chaine
passe bien une référence...

// Exemple 1 : là c'est raté.
function machin() {
$node = new Node();
(...)
truc($node);
(...)
$node->changeThis($value); // raté
}

fonction truc($node) {
/* le $node recu est déjà une copie :( */
(...)
global $pere;
$pere->ajouterFils($node);
}

// Exemple 2 : là c'est ok.
function machin() {
$node = new Node();
(...)
truc($node);
(...)
$node->changeThis($value); // ça marche
}

fonction truc(&$node) {
/* le $node recu est une référence :) */
(...)
global $pere;
$pere->ajouterFils($node);
}

Bruno

Avatar
John GALLET
'alut Nico,

Cela voudrait-il dire qu'en 'pushant' (voir ligne array_push ci-dessus)
dans le tableau, je viens d'en faire une copie par valeur ?
On a déjà eu un thread complet là dessus et sur la copie implicite. Il y a

deux à trois mois on va dire.

- Ai-je bien compris la problémtaique ? (-> push par valeur...)
Pour moi oui.

- Si oui, est-il alors possible de pusher par référence ? (avec & ?)
De mémoire ... Et bien je sais plus. Je crois bien que oui, pas de raisons.


- Si non, comment font les meilleurs codeurs objet en php ?
Sauf si j'ai compris ton soucis de travers (parfaitement possible) je dirais

que ce n'est pas un problème de POO mais de portée de variable.

John

Avatar
nicolas.pas.de.spam
Le 25 Aug 2003 17:18:17 GMT,
Bruno Desthuilliers disait :
Tu ne donne pas assez de contexte pour qu'on puisse l'affirmer de façon
formelle (on ne sait pas, à ce stade, si $unNoeud est lui-même l'objet
ou un référence,


$unNoeud est lui-même l'objet

il faudrait tracer tout l'appel), mais il semblerait
que ce soit bien le cas.

Dans ce cas, les modifs sur l'objet lui-même ne seront pas répercutées ?
Non, puisque dans ce cas c'est une copie



Dans ce cas, en effet.

La bonne solution consiste à passer le noeud fils par référence :
class Node {
function ajouterFils(&unNoeud) {
array_push($this->mesFils, $unNoeud);
}
}
Note le & avant le nom du paramètre dans le prototype de ajouterFils().
Là, tu n'a pas à t'embêter avec les appels, le problème est réglé avant.


Hélas non, le fait de rajouter un '&' n'a rien changé.
Il semblerait que ce soit toujours une copie de mes objets qui sont
stockés dans ce tableau.

Je ne sais pas trop comment debugguer un truc pareil. Des idées ?

--
Nicolas Ecarnot

Love is never asking why?


Avatar
Bruno Desthuilliers
Nicolas Ecarnot wrote:
Le 25 Aug 2003 17:18:17 GMT,
Bruno Desthuilliers disait :

(snip)

La bonne solution consiste à passer le noeud fils par référence :
class Node {
function ajouterFils(&unNoeud) {
array_push($this->mesFils, $unNoeud);
}
}
Note le & avant le nom du paramètre dans le prototype de ajouterFils().
Là, tu n'a pas à t'embêter avec les appels, le problème est réglé avant.



Hélas non, le fait de rajouter un '&' n'a rien changé.
Il semblerait que ce soit toujours une copie de mes objets qui sont
stockés dans ce tableau.


??? Clé de 12 !-(
Tu a vérifié tout la pile des appels ? il suffit d'un seul passage par
valeur entre l'original et l'appel de méthode pour qu'il y ait copie (cf
mon autre post).

Je ne sais pas trop comment debugguer un truc pareil. Des idées ?



D'abord faire un test avec une pile des appels réduite au minimum pour
s'assurer de bonnes conditions

Si là ça marche, vérifier toute la pile des appels dans le code
d'origine, et modifier les prototypes des fonctions intermédiaires pour
avoir un passage par référence.

Sinon :
remplacer
array_push($this->mesFils, $unNoeud);
par
$this->mesFils[] =& $unNoeud;

Si ça ne marche toujours pas, sacrifier un poulet à Legba ou à ougoun
ferail !-)

(et vérifier dans la doc et sur clphp s'il tu trouve quelque chose là
dessus)

Pas mieux, désolé
Bruno


Avatar
Marc
Nicolas Ecarnot wrote:
Bonjour,

Je me replonge dans un mini code en objet, et j'avoue avoir un peu de mal.
Merci aux spécialistes de m'éclairer et de me conseiller.

J'ai un objet 'node' qui décrit un noeud dans un arbre.
Cet objet contient parmi ses attributs un tableau de ses noeuds fils.
Cet objet 'node' dispose d'une méthode 'ajouterFils', qui effectue un
array_push($this->mesFils, $unNoeud);

Je remplis ainsi un arbre, et ça marche.
J'accède ensuite à un noeud en partant du sommet, et j'accède ainsi à tous
les noeuds.

Maintenant, si je modifie l'attribut d'un noeud, je ne vois pas la modif vu
du top.

Cela voudrait-il dire qu'en 'pushant' (voir ligne array_push ci-dessus)
dans le tableau, je viens d'en faire une copie par valeur ?
Dans ce cas, les modifs sur l'objet lui-même ne seront pas répercutées ?

Alors mes questions :
- Ai-je bien compris la problémtaique ? (-> push par valeur...)
- Si oui, est-il alors possible de pusher par référence ? (avec & ?)
- Si non, comment font les meilleurs codeurs objet en php ?



voici une classe "composite" qui fonctionne. Voir methode add().
sinon, il faut faire attention :

1/ au passages par reference aux niveau des methodes et fonctiosn
(ca se passe a la declaration pas l'appel, en peu comme en c++)

2/ les affectations :
$tab1 = &$ref_sur_objet; // OK
$tab2 = $copie_objet; // pas OK dans ton cas (arbre).

il n'y a pas d'autre subtilité en php pour ton petit probleme.


<?php

require_once ('CoreObject.php');

class Container extends CoreObject {

var $items;

function &Container() {
$this->_constructor();
}

function _constructor(){
parent::_constructor();
$this->items = array();
}

function &add(&$obj) {
$this->items[] = &$obj;

// set parent object
$obj->parent = &$this;
return ($obj);
}

function destroy($index = '') {
if($index != '') {
$this->items->_destroy();
// mark removed
$this->items[$index] = -1;
} else {
foreach ($this->items as $key => $item){
if($item != -1){
$item->_destroy();
$this->items[$key] = -1;
}
}
}
}

function deep_apply($func, $arg1 = '', $arg2 = '', $arg3 = ''){

if(!$this->is_a('Container'))
return '';

$val = '';

foreach ($this->items as $key => $item_copy){
$item = &$this->items[$key];
if($item->is_a('Container')){
$val .= $item->$func($arg1, $arg2, $arg3);
$val .= $item->deep_apply($func, $arg1 = '', $arg2 = '', $arg3 = '');
} else {
$val .= $item->$func($arg1, $arg2, $arg3);
}
}
return $val;
}


function apply($func, $arg1 = '', $arg2 = '', $arg3 = ''){

if(!$this->is_a('Container'))
return '';

$val = '';
foreach ($this->items as $key => $item_copy) {
$item = &$this->items[$key];
$val .= $item->$func($arg1, $arg2, $arg3);
}
return $val;
}

}// class Container
?>

Avatar
Nicolas Ecarnot
Marc wrote in news::

[...]
function &add(&$obj) {
$this->items[] = &$obj;

// set parent object
$obj->parent = &$this;
return ($obj);
}


Ici, il y a deux choses que je ne comprends pas.

1) Pourquoi un '&' devant le nom de la fonction ?
Je pensais que ce n'était nécessaire que lorsqu'on souhaite obtenir une
référence vers une fonction, et pas dans le cas où on retourne une
référence vers un objet.
Dans ce cas, c'est bien un objet qu'on retourne.
Pourquoi toucher à la fonction ?
(Ok pour le '&' devant l'argument, là je comprends)

2) (Aucun rapport, mais) Je ne comprends pas non plus le $this->items[] =
&obj; dans une fonction add.
Je pensais que tu aurais ajouté &$obj à la fin du tableau items[] ... ?

--
Nicolas Ecarnot

Avatar
Bruno Desthuilliers
Nicolas Ecarnot wrote:
Marc wrote in news::

[...]

function &add(&$obj) {
$this->items[] = &$obj;

// set parent object
$obj->parent = &$this;
return ($obj);
}



Ici, il y a deux choses que je ne comprends pas.

(snip)



2) (Aucun rapport, mais) Je ne comprends pas non plus le $this->items[] =
&obj; dans une fonction add.
Je pensais que tu aurais ajouté &$obj à la fin du tableau items[] ... ?


Lis la doc : c'est exactement ce qu'il fait. Une affectation à un
tableau sans précision de [indice|clé] ajoute l'élément à la fin du
tableau.

Bruno


Avatar
marc.quinton-PAS-DE-
1) Pourquoi un '&' devant le nom de la fonction ?
Je pensais que ce n'était nécessaire que lorsqu'on souhaite obtenir une
référence vers une fonction, et pas dans le cas où on retourne une
référence vers un objet.


le &add(), est déclaratif. Je ne sais pas si ca change qq chose.
En c++, la syntaxe est plus ou moins la meme. J'ai peut-etre pas
raison, mais mon arbre, non, mon objet composite marche parfaitement.

faudrait faire des test pour verifer cette subtilité. C'est une syntaxte
qu'on trouve assez souvent dans PEAR.

Dans ce cas, c'est bien un objet qu'on retourne.
Pourquoi toucher à la fonction ?
(Ok pour le '&' devant l'argument, là je comprends)



ouaip,

2) (Aucun rapport, mais) Je ne comprends pas non plus le $this->items[] =
&obj; dans une fonction add.
Je pensais que tu aurais ajouté &$obj à la fin du tableau items[] ... ?


c'est une syntaxe abrégée de array_push(). Ca doit faire la meme chose.
attention, si l'une des fonctions array_* ne prend pas une reference, ca
peut empecher a l'arbre de fonctionner.

Avatar
Marc
Et peut-on voir la classe dont dérive cette classe ?



ca n'apporte rien au probleme, mais ce n'est pas du code source
secret. Ce sont des classes de mon propre cru, sans doute pas
completement blindées.

ci desous : la class CorObject suivi d'un exemple d'utilisation
de container. Je veux bien mettre ces classes dans mon depot
habituel mais ca me prend un peu de temps.


<?php

class CoreObject {

var $name;
var $parent;

function &CoreObject($name = '') {
$this->_constructor();
}

function _constructor($name = ''){
$this->name = $name;
$this->parent = '';

if($this->get_class() == "base")
$this->error("this is an abstract object ; can't be instatiated");
}

// called when an object is removed (destroyed)
function _destroy(){

}

// return name of object
function name(){
return $this->name;
}

// ----------------------------------------------------------------------------------------------------
// class functions
function is_a($class){
return(is_a($this, $class));
}

function class_name(){
return($this->get_class());
}

function get_class(){
return(get_class($this));
}

function parent_class(){
return(get_parent_class());
}

function &parent(){
return($this->parent);
}

// ----------------------------------------------------------------------------------------------------
// errors functions
function error($msg, $line = '', $file = '') {
$this->_trigger_error ($msg, E_USER_ERROR, $line, $file);
}

function warning($msg, $line = '', $file = '') {
$this->_trigger_error ($msg, E_USER_NOTICE, $line, $file);
}

function warn($msg, $line = '', $file = '') {
$this->warning($msg, E_USER_NOTICE, $line, $file);
}

function notify($msg, $line = '', $file = '') {
$this->warning($msg, $line, $file);
}

function _trigger_error($msg, $flag, $line, $file){
$class = get_class($this);
trigger_error("$class : " . $msg, $flag);

# echo "<pre>n";
# debug_backtrace();
# echo "</pre>n";

}


// ----------------------------------------------------------------------------------------------------
// trace and debug functions.
//
function _debug(&$var, $str ='') {
$class = get_class($this);
echo "<pre>nclass : $class : $str ";
print_r($var);
echo "</pre>n";
}

// ----------------------------------------------------------------------------------------------------
function _trace($msg) {
if($this->trace){
$class = get_class($this);
echo "$class::$msg<br>n";
}
}


} // class CoreObject



<?php

error_reporting(E_ALL);

ini_set("include_path", ".:..:".ini_get("include_path"));

require_once ('CoreObject.php');
require_once ('Container.php');

class BaseObject extends CoreObject {
var $name;

function &BaseObject($name){
$this->_constructor($name);
}

function _constructor($name){
# parent::_constructor();
$this->name = $name;
printf("%s::constructor(%s)n", get_class($this), $name);
}

function tree(&$level){
$level++;
return ($level . get_class($this) . '::' . $this->name . "n");
$level--;
}
}

class Person extends CoreObject {
var $age;
var $name;

function &Person($name, $age){
$this->name = $name;
$this->age = $age;
}

function tree(&$level){
$level++;
return ($level . ' ' . get_class($this) . '::' . $this->name . ' ' . $this->age . "n");
$level--;
}

}

class MyContainer extends Container {

var $name;

function &MyContainer($name) {
$this->_constructor($name);
}

function _constructor($name){
parent::_constructor();
$this->name = $name;

printf("%s::constructor(%s)n", $this->get_class(), $name);
}

function tree(&$level){
$level++;
return ($level . " " . $this->get_class($this) . '::' . $this->name . "n");
}
}

echo "<pre>n";

# main | sub1 | sub2 | BaseObj1
$main = &new MyContainer('Main');
$sub1 = &new MyContainer('Sub1');
$sub2 = &new MyContainer('Sub2');
$sub3 = &new MyContainer('Sub3');

$p1 = &new Person('Marc', 39);
$p2 = &new Person('Steph', 30);
$p3 = &new Person('Tine', 39);


$main->add($sub1);
$sub1->add($sub2);
$sub2->add($sub3);
$sub3->add($p2);
$sub3->add($p1);

$main->add($p3);

print_r($main);

echo "apply() : n" . $main->apply('tree', 0);
echo "deep_apply() : n" . $main->deep_apply('tree', 0);

?>