Avatar billede o-zone Nybegynder
09. februar 2008 - 10:40 Der er 16 kommentarer og
1 løsning

Problemer med variabel scope i et objekt?

Hej med jer.

Jeg har et php objekt der fungerer som node i et hierakisk træ. Hver node objekt har en firstChild, og en sibling
Hvis en node har flere children får jeg fat i dem gennem firstChild og dennes sibling (og igen dennes siblings sibling osv.)
Altså en kædet liste.
Mit objekt har en metode addChild(nyNode) som checker om noden allerede har unger. Har den ikke bliver nyNode sat som firstChild. Har den allerede en firstChild, så løber jeg firstChilds siblings igennem, og indsætter den nye child et sted i den kæde.

Mit problem er at jeg tilsyneladende godt kan indsætte firstChilds, men så snart jeg prøver at indsætte objekter i firstChilds siblings liste, så fungerer det indenfor metoden addChild (en testudskrift i slutningen af metoden tyder på at alt er korrekt) - men det "forsvinder" så snart jeg er ude af metoden.

Jeg går ud fra at det er et scope problem der gør at det objekt jeg henter som firstChild kommer til at være en lokal kopi der bliver smidt ud så snart metoden er overstået, uden at det får indflydelse på mit "globale" træ. Jeg kan bare ikke lige greje hvordan jeg får metoden til at arbejde på det rigtige træ istedet for en kopi (hvis det da er det der sker?).

Det virker list sært at skulle skrive global $firstChild, da det hele sker inde i kroppen på objektet (og derfor ifølge min regnebog burde være de rigtige objekter jeg har fat i?)

Er der nogen derude der kan hjælpe mig?
/o-zone

Her er lidt kode:
---8<----------
class Node{
    var $sortOrder = 0;
    var $name = '';
    var $firstChild = null;
    var $sibling = null;

    function addChild($nyNode){
        if ($this->firstChild == null){
            // this is a virgin
            $this->firstChild = $nyNode;
        } else {
            // node has children already
            $oldSib = $this->firstChild;
            $newSib = $oldSib->sibling;

            while ((!is_null($newSib))&&($newSib->sortOrder < $nyNode->sortOrder)){
                $oldSib = $newSib;
                $newSib = $newSib->sibling;
            }

            $oldSib->sibling = $nyNode;
            $nyNode->sibling = $newSib;

echo('Efter indsættelse bør det være: "'.$oldSib->name.'"-->"'.$nyNode->name.'"-->"'.$newSib.'"<br />');           
echo('Efter indsættelse er det: "'.$oldSib->name.'"-->"'.$oldSib->sibling->name.'"-->"'.$oldSib->sibling->sibling.'"<br />');           
}
---8<----------
Avatar billede erikjacobsen Ekspert
09. februar 2008 - 11:24 #1
Teknisk set er man vil ikke en "virgin" bare fordi man ikke har fået børn ;)
Prøv:
    $oldSib =& $newSib;
og se forklaringen på
    http://php.net/
http://www.php.net/manual/en/language.references.whatdo.php
Avatar billede erikjacobsen Ekspert
09. februar 2008 - 11:25 #2
Og du skal selvfølgelig gøre det i mere end lige den viste tilordning.
Avatar billede olebole Juniormester
09. februar 2008 - 16:42 #3
<ole>

- men når det en sjælden gang sker, at en jomfru får barn, er det noget, man taler om i mange, mange år ... og sågar skriver tykke bøger om  ;o)

/mvh
</bole>
Avatar billede o-zone Nybegynder
14. februar 2008 - 23:37 #4
Jeg har nu prøvet at indsætte en masse =&'er, og det har også hjulpet en del - jeg har dog stadig et problem som jeg ikke helt fatter:
Jeg har et array $menupunkt der indeholder en masse objekter af typen menuItem, som er en trænode der indeholder nogle stamdata (som f.eks. navn og id) og en firstChild og en sibling der indeholder referencer til hhv. det første childNode og til næste siblingNode
Mit problem er at det tilsyneladende kun root noden har noget i firstChild. Sibling virker tilsyneladende efter hensigten. Det mærkelige er at mine testudskrifter tyder på at alt fungerer korrekt inden i foreach loopet, men alle firstChild undtagen rootnodens er væk så snart jeg er ude af loopet. :-(

Her er lidt udvalgt kode:
---[kode]---
  //root element
  $menupunkt[0] = new menuItem();
  $menupunkt[0]->setName("root");
 
  //indlæs alle noderne i arrayet $menupunkt med deres id som id og et menuItemObjekt som indhold
  [...cut...]
 
  foreach($menupunkt as $id=>$item){
    if ($id!=0){ // skip hvis det er rodnoden (for den har ingen parentNode)
      $menupunkt[$item->parentId]->addChild($item); // hægt den aktuelle node på dens parent som en childNode
      //Herinde findes $menupunkt[$item->pid]->firstChild korrekt uanset om det er en rod-, gren- eller blad-node
    }
  }
 
  //Herude er det (ukorrekt) KUN root elementet der har en firstChild?
  return $menupunkt[0];


//...og her er funktionen addChild fra menuItem objektet:
  [...cut...]
  function addChild($menuItem){
    if ($this->firstChild == null){
      // this is a virgin
      $this->firstChild =& $menuItem;
    } else {
      // node has children already
      $oldSib =& $this->firstChild;
      $newSib =& $oldSib->sibling;

      while ((!is_null($newSib))&&($newSib->sortOrder < $menuItem->sortOrder)){
        $oldSib =& $newSib;
        $newSib =& $newSib->sibling;
      }
      $oldSib->sibling =& $menuItem;
      $menuItem->sibling =& $newSib;
    }
  }
  [...cut...]
---[/kode]---
Avatar billede erikjacobsen Ekspert
14. februar 2008 - 23:42 #5
Jeg tror din sidste whileløkke ikke virker efter hensigten i forbindelse med din sortOrder. Så vidt jeg kan se, kan du ikke indsætte noget før din firstChild.
Avatar billede o-zone Nybegynder
15. februar 2008 - 09:03 #6
Det er meget muligt at der er en logisk fejl i den sidste whileløkke, og det vil jeg naturligvis se på ved førstkommende lejlighed. Men sådan som jeg får indlæst mine noder, så bør det ikke give noget problem (endnu - men det skal selvfølgelig rettes på sigt).

Mit store problem lige nu er at jeg slet ikke får firstChilds på andre noder end root noden.
Det ser ud til at det går fint i den indre foreachløkke. Ved en testudskrift ved denne linie:
//Herinde findes $menupunkt[$item->pid]->firstChild korrekt uanset ...
ser alt korrekt ud - alle noder der skal have firstChilds har det. Men når jeg så kommer ud af løkken og tester ved linien:
//Herude er det (ukorrekt) KUN root elementet der har en firstChild?
så er alle firstChilds på alt andet end root forsvundet igen. Jeg forestiller mig at det stadig handler om noget med scope. Jeg tror i hvert fald ikke at det kan skyldes en fejl i addChild metoden, da den jo fungerer fint i maven på foreachløkken?

(men jeg skal selvfølgelig også have rettet den fejl du påpeger, så metoden ikke kræver at få firstChilds først!)

/o-zone
Avatar billede erikjacobsen Ekspert
15. februar 2008 - 09:11 #7
Kunne du overtales til at fremstille et minimalt eksempel med problemet?
Avatar billede o-zone Nybegynder
16. februar 2008 - 01:17 #8
Jo da - hvis du orker at se på det, skulle jeg da være et skarn ikke at orke lave et eksempel til dig. Det tog lige lidt tid at lave et nogenlunde (håber jeg?) overskueligt eksempel. Det er en lille smule langt, men de spændende parter er ved de to linier "//Her ER den der!" og "//Nu er den her ikke???":

---8<----------
<?php
class menuItem{
    var $id;
    var $pid = 0;
    var $sortOrder = 0;
    var $name = '';
    var $firstChild = null;
    var $sibling = null;

    function menuItem($id,$pid,$sortOrder,$name){
        $this->id = $id;
        $this->pid = $pid;
        $this->sortOrder = $sortOrder;
        $this->name = $name;
    }
   
    function addChild($menuItem){
        if ($this->firstChild == null){
            // this is a virgin
            $this->firstChild =& $menuItem;
        } else {
            // node has children already
            $oldSib =& $this->firstChild;
            $newSib =& $oldSib->sibling;
           
            while ((!is_null($newSib))&&($newSib->sortOrder < $menuItem->sortOrder)){
                $oldSib =& $newSib;
                $newSib =& $newSib->sibling;
            }
           
            $oldSib->sibling =& $menuItem;
            $menuItem->sibling =& $newSib;
        }
    }
}

function getMenuTreeRoot(){
    //root element
    $menupunkt[0] = new menuItem();
    $menupunkt[0]->name = "root";
    $menupunkt[0]->id = 0;

    $menupunkt[1] = new menuItem(1,0,10,'ting');
    $menupunkt[2] = new menuItem(2,0,20,'dyr');
    $menupunkt[3] = new menuItem(3,0,30,'sport');
    $menupunkt[4] = new menuItem(4,2,10,'fugle');
    $menupunkt[5] = new menuItem(5,2,20,'pattedyr');
    $menupunkt[6] = new menuItem(6,3,10,'kampsport');
    $menupunkt[7] = new menuItem(7,3,20,'boldspil');
    $menupunkt[8] = new menuItem(8,4,10,'rovfugle');
    $menupunkt[9] = new menuItem(9,4,20,'andefugle');

    //Ovenstående bør ende med et træ i som beskrevet nedenfor:
    //root
    // +-ting
    // +-dyr
    // |     +-fugle
    // |     |    +-rovfugle
    // |     |    +-andefugle
    // |     +-pattedyr
    // +-sport
    //      +-kampsport
    //      +-boldspil

echo("<hr />nu går vi ind i foreach løkken<hr />");

    foreach($menupunkt as $id=>$item){
        if ($id!=0){ // skip if it is the (empty) root element
            $menupunkt[$item->pid]->addChild($item);
//Her ER den der!
echo('Inde i foreach løkken er noden '.$menupunkt[$item->pid]->name.' (id='.$item->pid.')\'s firstChild = '.$menupunkt[$item->pid]->firstChild->name.'<br />');
        }
    }
   
echo("<hr />nu er vi ude af foreach løkken<hr />");
//Nu er den her ikke???
    return $menupunkt[0];
} // end function getMenuTreeRoot()


function printNode($prefix,$indent,$node,$postfix){
    $indentStrType = '-';
    $indentStr = '';
    for($i=0;$i < $indent;$i++){ $indentStr = $indentStrType.$indentStr; }
   
    if ($node->id!=0) { echo($prefix.$indentStr.$node->name.$postfix); } // only print name, if it is not the root element
echo('udenfor foreach løkken er noden '.$node->name.' (id='.$node->id.')\'s firstChild = "'.$node->firstChild.'"<br />');
    if (!is_null($node->firstChild)){ printNode($prefix,$indent+1,$node->firstChild,$postfix); } else { echo('TEST: noden '.$node->name.' (id='.$node->id.') har ikke nogen børn<br />'); }
    if (!is_null($node->sibling)) { printNode($prefix,$indent,$node->sibling,$postfix); } else { echo('TEST: noden '.$node->name.' har ikke nogen søskende<br />'); }
}


$root = getMenuTreeRoot();

printNode('<span style="color:red;">',0,$root,'</span><br />');

?>---8<----------

Hvis du skal have noget forklaret, så skriv her!
Mit problem er som tidligere nævnt at jeg kun får firstChild på root elementet. :-(
Avatar billede o-zone Nybegynder
16. februar 2008 - 01:18 #9
hmmm... der mangler selvfølgelig en "?>" i bunden af filen - men ellers burde du kunne gemme det hele som en php fil, og så eksekvere det hele.
Avatar billede erikjacobsen Ekspert
16. februar 2008 - 08:58 #10
http://n0p.com/818705.php

Koden er kun lidt ændret - men bruger du PHP4??:

<?php
class menuItem{
    var $id;
    var $pid = 0;
    var $sortOrder = 0;
    var $name = '';
    var $firstChild = null;
    var $sibling = null;

    function menuItem($id,$pid,$sortOrder,$name){
        print "Constructor menuItem<br>\n";
        $this->id = $id;
        $this->pid = $pid;
        $this->sortOrder = $sortOrder;
        $this->name = $name;
    }
 
    function addChild($menuItem){
        if ($this->firstChild == null){
            // this is a virgin
            $this->firstChild =& $menuItem;
        } else {
            // node has children already
            $oldSib =& $this->firstChild;
            $newSib =& $oldSib->sibling;
         
            while ((!is_null($newSib))&&($newSib->sortOrder < $menuItem->sortOrder)){
                $oldSib =& $newSib;
                $newSib =& $newSib->sibling;
            }
         
            $oldSib->sibling =& $menuItem;
            $menuItem->sibling =& $newSib;
        }
    }
}

function getMenuTreeRoot(){
    //root element
    $menupunkt[0] = new menuItem(0,0,0,"root");
  //  $menupunkt[0]->name = "root";
  //  $menupunkt[0]->id = 0;

    $menupunkt[1] = new menuItem(1,0,10,'ting');
    $menupunkt[2] = new menuItem(2,0,20,'dyr');
    $menupunkt[3] = new menuItem(3,0,30,'sport');
    $menupunkt[4] = new menuItem(4,2,10,'fugle');
    $menupunkt[5] = new menuItem(5,2,20,'pattedyr');
    $menupunkt[6] = new menuItem(6,3,10,'kampsport');
    $menupunkt[7] = new menuItem(7,3,20,'boldspil');
    $menupunkt[8] = new menuItem(8,4,10,'rovfugle');
    $menupunkt[9] = new menuItem(9,4,20,'andefugle');

    //Ovenstående bør ende med et træ i som beskrevet nedenfor:
    //root
    // +-ting
    // +-dyr
    // |    +-fugle
    // |    |    +-rovfugle
    // |    |    +-andefugle
    // |    +-pattedyr
    // +-sport
    //      +-kampsport
    //      +-boldspil

echo("<hr />nu går vi ind i foreach løkken<hr />");

    foreach($menupunkt as $id=>$item){
        if ($id!=0){ // skip if it is the (empty) root element
            $menupunkt[$item->pid]->addChild($item);
//Her ER den der!
echo('Inde i foreach løkken er noden '.$menupunkt[$item->pid]->name.' (id='.$item->pid.')\'s firstChild = '.$menupunkt[$item->pid]->firstChild->name.'<br />');
        }
    }
 
echo("<hr />nu er vi ude af foreach løkken<hr />");
//Nu er den her ikke???
    return $menupunkt[0];
} // end function getMenuTreeRoot()


function printNode($prefix,$indent,$node,$postfix){
    $indentStrType = '-';
    $indentStr = '';
    for($i=0;$i < $indent;$i++){ $indentStr = $indentStrType.$indentStr; }
 
    if ($node->id!=0) { echo($prefix.$indentStr.$node->name.$postfix); } // only print name, if it is not the root element
echo('udenfor foreach løkken er noden '.$node->name.' (id='.$node->id.')\'s firstChild = "'.$node->firstChild->name.'"<br />');
    if (!is_null($node->firstChild)){ printNode($prefix,$indent+1,$node->firstChild,$postfix); } else { echo('TEST: noden '.$node->name.' (id='.$node->id.') har ikke nogen børn<br />'); }
    if (!is_null($node->sibling)) { printNode($prefix,$indent,$node->sibling,$postfix); } else { echo('TEST: noden '.$node->name.' har ikke nogen søskende<br />'); }
}


$root = getMenuTreeRoot();

printNode('<span style="color:red;">',0,$root,'</span><br />');

?>
Avatar billede o-zone Nybegynder
16. februar 2008 - 09:16 #11
Jeg brugte PHP4, men nu har jeg også prøvet med PHP5 ... og ingen af delene gav nogen ændringer. :-(

Virker det da når du prøver at køre den? Det er meningen at det fulde træ skal printes ud i rød skrift? i bunden af testen (med testbeskeder imellem. Bliver det det hos dig?

Jeg får stadig kun udskrevet roots children, og ikke andet (fordi de andre noder tilsyneladende ikke har nogen children længere så snart jeg er udenfor foreach løkken. :-(
Avatar billede o-zone Nybegynder
16. februar 2008 - 09:22 #12
OK - jeg så ikke lige dit link :)

Jeg går ud fra at det må have været et php4 problem, for nu virker det pludseligt også hos mig :-O

Det er muligt at jeg var lidt for hurtig til at teste med php5, og det så i virkeligheden stadig har været php4 jeg testede med :-O
Avatar billede erikjacobsen Ekspert
16. februar 2008 - 09:24 #13
Jah, måske. Der er i hvert fald ingen grund til at spilde sit liv med PHP4 og OO-ting.
Avatar billede o-zone Nybegynder
16. februar 2008 - 09:25 #14
Øv - jeg fatter stadig ikke rigtigt hvad problemet var - men fedt at det virker nu! :)
Smid et svar, så kaster jeg en håndfuld velfortjente points efter dig! :D
Avatar billede erikjacobsen Ekspert
16. februar 2008 - 09:28 #15
Problemet var nok bare PHP4 og OO. Du'r ikke.

Du skal nok ændre lidt mere, fx ændre dine "var"-erklæringer til "private" eller "public".

Jeg samler slet ikke på point, tak. Jeg gætter på at olebole ikke ligefrem står i kø for at få point for sin kommentar. Så pointene er dine: svar selv, accepter eget svar.
Avatar billede o-zone Nybegynder
16. februar 2008 - 10:28 #16
Hmmm... helt pricipielt, så synes jeg at det er en skam at folk ikke bruger pointssystemet som det er beregnet til :-/

Hvis jeg støder på et besvaret spørgsmål med en hulens masse indlæg, så er det meget nemmere, hvis jeg hurtigt kan se hvilket indlæg der indeholder guldet, ved at se hvilket indlæg der er blevet accepteret. På den måde slipper jeg for at læse alle de ikke accepterede svar igennem.

Men eftersom du alligevel kun har svaret med kommentarer hjælper det selvfølgelig ikke meget at hvine over det nu.

Hvor prisværdigt jeg end synes at det er at I hjælper folk uden at kræve points for det, så burde eksperten måske overveje et tag-system der sætter spørgeren istand til at udpege det svar der rent faktisk hjalp på problemet. Hmmm... det her er vist noget jeg skal tage op i et andet forum :)

Anyway - 1.000 tak for hjælpen - jeg var faktisk kørt fuldstændig fast i det. Men det hjalp altså at skifte til PHP5, og at lade være med at prøve at testudskrive objekter, men at testudskrive deres attributter istedet!

altså det rigtige:
echo("TEST: ".$etObjekt->child->name);
istedet for det forkerte:
echo("TEST: ".$etObjekt->child);

...grunden til at jeg nøjedes med at prøve at udskrive objektet, var at jeg troede at jeg ville få en fejl ved at prøve at tilgå en attribut, hvis objektet var null ... men det virker åbenbart fint i php5 (men ikke i PHP4? :-O )

Tak for hjælpen Erik! :)
/o-zone
Avatar billede erikjacobsen Ekspert
16. februar 2008 - 11:13 #17
Det er såmænd ikke for at underminere pointsystemet, men for at holde mig udenfor alt det ævl og kævl, der er med points. Dem der så bruger pointsystemet gør det jo heller ikke som beregnet, og fx 15-30-60 for lette-svære spørgsmål overholdes jo heller ikke.
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Vi tilbyder markedets bedste kurser inden for webudvikling

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester