spoiler:
vaak helpt het uitschrijven van een probleem zoals hieronder erg goed om nou te snappen wat er precies gebeurt. alles hieronder wat ik dus op dat moment nog niet kon verklaren, snap ik nu dus wel...
mag wel dicht dit topic, maar misschien is het aardig om te laten staan, oplossing onderaan
mag wel dicht dit topic, maar misschien is het aardig om te laten staan, oplossing onderaan
Ik ben bezig een tree-view te bouwen. Geen nifty zaakjes met DHTML in en uitklappende DIVjes, gewoon refreshen en de boom openklappen op alle nodes die onderdeel vormen van het pad. Zie hier de method die de data binnentrekt:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
| function addChildren($aCurrentPath) { $sQuery = "SELECT sub_node.id, sub_node.name, sub_node.lid "; $sQuery .= "FROM sub_node, sub_relation "; $sQuery .= "WHERE sub_node.id = sub_relation.ikid AND sub_relation.ipar = ".end($this->m_aPath)." "; $sQuery .= "ORDER BY sub_node.lid ASC, sub_node.name ASC"; $aAttributes = $this->m_pcDB->query($sQuery); if(!isset($aAttributes['error'])) { // $aAttributes is een $n aantal MySQL associative result-sets: for($n = 0; $n < count($aAttributes); $n++) { // haal het id er vast uit want die hebben we 2 keer nodig hier: $id = $aAttributes[$n]['id']; // push ff tijdelijk het id bij het pad van de huidge node op: array_push($this->m_aPath,$id); // zodat we een nieuwe node kunnen bouwen met het juiste pad, // geef verder een reference naar de DB class mee, en de attributen: $cChild = new SUB_Tree($this->m_pcDB, $this->m_aPath, &$aAttributes[$n]); // pop dat id er maar weer af: array_pop($this->m_aPath); // als de huidige node onderdeel vormt van het pad, 'klappen' we node open: if(in_array($id, $aCurrentPath)) { // door deze functie recursief aan te roepen: $aResult = $cChild->addChildren(&$aCurrentPath); if(isset($aResult['error'])) { return array("error" => $aResult['error']); } } /* HIER GAAT HET OM */ $this->addChild(&$cChild); //array_push($this->m_aChildren, &$cChild); /* END HIER GAAT HET OM */ } return array("ok" => "tree building completed successfully"); } else { return array("error" => $aChildren['error']); } } |
Nou is mijn doel om zo min mogelijk kopieen te maken van instances, dus ben ik aan het prutsen met references. Als ik de NIET uitgecommente call doe in het blok /* HIER GAAT HET OM */ , wekt het goed. Die method is:
PHP:
1
2
3
4
| function addChild($cNode) { array_push($this->m_aChildren, $cNode); } |
(((
het werkt overigens ook 'andersom', wanneer ik de & verplaats van de call naar de argumenten in de method declaratie:
PHP:
1
2
3
4
5
6
7
| $this->addChild($cChild); .. function addChild(&$cNode) { array_push($this->m_aChildren, $cNode); } |
)))
Maar wat NIET werkt, is de addChild functie helemaal niet gebruiken en de uitgecommente regel gebruiken:
PHP:
1
2
3
4
5
6
| .. /* HIER GAAT HET OM */ //$this->addChild(&$cChild); array_push($this->m_aChildren, &$cChild); /* END HIER GAAT HET OM */ .. |
Terwijl je toch zou verwachten dat dit hetzelfde effect heeft, zijn ineens alle 'children' een referentie naar het LAATSTE child dat aangemaakt wordt. Is dit 'normaal' of zie ik een bug over het hoofd?
Da's vraag 1, maar the saga continues:
Als het child by-reference aan de lijst met children wordt toegevoegd, zou je verwachten dat je hem ook eerst aan de lijst met children kunt toevoegen, en daarna pas de functie recursief aanroept, het is een reference, dus de lijst met kinderen van z'n parent zou mee moeten veranderen indien er nieuwe children bij komen. Op deze manier dus:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| .. // zodat we een nieuwe node kunnen bouwen met het juiste pad, // geef verder een reference naar de DB class mee, en de attributen: $cChild = new SUB_Tree($this->m_pcDB, $this->m_aPath, &$aAttributes[$n]); // pop dat id er maar weer af: array_pop($this->m_aPath); /* HIER GAAT HET OM */ $this->addChild(&$cChild); //array_push($this->m_aChildren, &$cChild); /* END HIER GAAT HET OM */ // als de huidige node onderdeel vormt van het pad, 'klappen' we node open: if(in_array($id, $aCurrentPath)) { // door deze functie recursief aan te roepen: $aResult = $cChild->addChildren(&$aCurrentPath); if(isset($aResult['error'])) { return array("error" => $aResult['error']); } } .. |
Werkt dus mooi niet! de functie wordt wel recursief doorlopen, maar ergens wordt toch stiekem een copy gemaakt, want bij het renderen van de tree van de root gaat ie maximaal 1 niveau diep.
en nu komt het bizarre:
doe ik nu weer de manier van 'adden' die er voor zorgt dat alle children een referentie zijn naar de laatste, dan werkt de recursie ineens wel goed, zij het dan dattie steeds voor elke node de laatste pakt.
ik heb dus zo'n vermoeden dat er in de 'addChild' method toch stiekem copieen worden gemaakt, want die werkt alleen als je de recursie aanroept voordat je hem aan de parent toevoegd. Van de array_push methode is bewezen dattie netjes geen kloontjes bouwt, omdat na het toevoegen aan de lijst met children, de 'kinderlijst' van elk kind gewijzigd kan worden.
Ik snap alleen niet waarom de lijst nou alleen maar de laatste child op elke plek heeft zitten, als ik $cChild vlak voor het adden dump, is het de goeie, print ik na de loop de lijst uit, staat er alleen maar $n keer de laatste in?
RAAR!? Of toch niet?
spoiler:
Nee helemaal niet RAAR! Het verschil tussen het wel en niet gebruiken van de method addChild, komt doordat bij de method een nieuwe reference wordt aangemaakt, in het andere blijft er steeds maar 1 reference naar $cChild. Verklaart ook gelijk waarom de recursie in de foute methode waarbij het adden voor de recursie kwam wel werkte en de goed niet, immers, de reference naar de children wordt steeds om zeep geholpen door de volgende in de lus
Aangezien $cChild een local variable is ie verdwenen na het doorlopen van de functie Door dus eerst de recursie te doen en vervolgens de addChild methode te gebruiken wordt het gewenste effect bereikt...
Aangezien $cChild een local variable is ie verdwenen na het doorlopen van de functie Door dus eerst de recursie te doen en vervolgens de addChild methode te gebruiken wordt het gewenste effect bereikt...