ugrás a tartalomhoz

Hierarchikus adatkezelés SQL-lel PHP-ben II.

Poetro · 2005. Jan. 18. (K), 23.00
Hierarchikus adatkezelés SQL-lel PHP-ben II.
Az előző részt folytatva a hirearchikus adatkezelést boncolgatjuk tovább, azaz hogyan lehet hierarchikus adatainkat hatékonyan tárolni adatbázisban. Előző cikkemben egy jól használható, ugyanakkor nem eléggé hatékony megoldást mutattam be az adatok tárolásával kapcsolatban. Most újra megnézzük a problémát, de egy jóval hatékonyabb és kezelhetőbb megoldást láthatunk, elkerülve a processzor és memóriaigényes rekurziót, nem is szólva arról, hogy az adatbázis lekérdezések számát is radikálisan csökkentjük.

A sorozatban megjelent

Egy kis elmélet

Mielőtt az új módszerhez tartozó kódot bemutatnám, tekintsük át a hozzá tartozó elméletet. Vegyük az előző példabeli faszerkezetet, de most a jobb áttekinthetőség végett vízszintesen ábrázolva.

Programozás
+------------------------------------------+
PHP                                        HTML
+-----------------+                        +---------+
Stringkezelés     PEAR                     CSS       XHTML
                  +---------+              |
                  Mail      HttpClient     kiválasztók

Minden elemhez társítani fogunk egy jobb illetve bal értéket a következő algoritmusnak megfelelően.
  1. Számot növeljük 1-gyel.
  2. Az aktuális elem bal értékét beállítjuk a számra.
  3. Ha létezik az aktuális elemnek gyereke, akkor az lesz az aktuális elem, és folytatás az 1. lépéssel.
  4. Számot növeljük 1-gyel.
  5. Az aktuális elem jobb értékét beállítjuk a számra.
  6. Ha nincs az aktuális elemnek testvére, akkor vesszük a szülőt mint aktuális elemet, majd folytatjuk a 4. lépéssel.
  7. Vesszük az aktuális elem testvérét, mint az aktuális elemet, majd folytatjuk az 1. lépéssel.
Ha ezt végigjátszuk a fenti példán, akkor a következő értékeket kapjuk:
1 Programozás 20
  +-----------------------------------------------+
2 PHP 11                                       12 HTML 19
  +------------------+                            +---------+
3 Stringkezelés 4  5 PEAR 10                   13 CSS 16  17 XHTML 18
                     +---------+                  |
                   6 Mail 7  8 HttpClient 9    14 kiválasztók 15

A szemléletesség érdekében íme az ábra grafikusan:

1. ábra


Jobban áttekintve az ábrát pár érdekes matematikai összefüggést vehetünk észre.
  • Azon elemek tartoznak egy meghatározott elem alá, melyek bal és jobb értékei az elem bal és jobb értéke közé esnek.
  • A bal és jobb értékek közül az egyik páros, a másik páratlan.
  • Az elem gyerekeinek száma (jobb-bal-1)/2.
  • Egy elem levél, ha bal értéke egyel kisebb, mint a jobb (jobb-bal=1).

Az adatbázis

Az előzőek mintájára újfajta struktúrájú adatbázist kell létrehoznunk az adatok tárolására.

CREATE TABLE my_table (
    id int(10) unsigned NOT NULL auto_increment,
    lft int(10) unsigned NOT NULL default '0',
    rgt int(10) unsigned NOT NULL default '0',
    PRIMARY KEY (id),
    KEY lft (lft),
    KEY rgt (rgt) 
)
Mint látjuk jelen esetben 3 értéket tárolunk az adatbázisunkban:
  • id: az elem azonosítója, ami egy auto_increment mező, hogy új elemek hozzáadása esetén automatikusan új értéket kapjon
  • lft: az elem bal értéke (azért nem használom a left értéket, mert az az SQL-ben foglalt szó akár csak a right)
  • rgt: az elem jobb értéke
Mivel ezen elemekre gyakran van szükség az id pedig egyedi, ezért mindegyiket kulcsnak kell definiálni, az id pedig elsődleges kulcs.

A Tree osztály

Az egész adatbázis kezelésére létrehoztam egy Tree nevű osztályt, melynek konstruktora csak az adatbázis nevét kéri.
Ez az osztály a következő függvényeket ismeri:
retrieve($id, $indent='   ', $newLine="\n")
retrieve2array($id)
_addToArray(&$array, $depth, $item)
pathTo($id)
descendants($id)
getDepths()
getDepth($depth=1)
getDirectDescendants($id)
addNode($id)
leaf($id)
delNode($id)
moveNode($id, $pid)

Lekérdezések

A retrieve az előző cikkben tárgyalt displayChildren függvényt testesíti meg, csak új formában.
<?php
// Visszaadja a fa formázott stringjét az $id-ben megadott gyökér alapján
function retrieve($id, $indent='   ', $newLine="\n") {
  $output = '';
  // lekérdezzük a bal és jobb értékét a gyökér elemnek
  $result = mysql_query('SELECT lft, rgt FROM  '.$this->table.
                        ' WHERE id='.$id); 
  if($row = mysql_fetch_assoc($result)) {
    // a $right tömb egy verem lesz
    $right = array(); 

    // most lekérdezzük az elem összes leszármazottját
    $query = 'SELECT id, lft, rgt FROM '.$this->table.
             ' WHERE lft BETWEEN '.$row['lft'].' AND '. 
             $row['rgt'].' ORDER BY lft ASC';
    $result = mysql_query($query); 

    // megjelenítjük az összes sort
    while ($row = mysql_fetch_array($result)) { 
      // csak akkor ellenőrízzük a vermet, ha nem üres
      if (count($right)>0) { 
        // ellenőrizzük, hogy ki kell-e venni az elemet a veremből
        while ($right[count($right)-1]<$row['rgt']) { 
          array_pop($right);
        } 
      }
      // megjelenítjük az elemet a megfelelő indent-tel
      $output.=str_repeat($indent,count($right)).$row['id'].$newLine;

      // hozzárakjuk ezt az elemet a veremhez
      $right[] = $row['rgt'];
    }
  }
  return $output;
}
?>

Fa tömbben ábrázolása

A függvény működése elég trükkös. Először is vesszük a gyökér elem jobb és bal értékét, hogy tudjuk mely elemek tartoznak ezen elem alá, majd lekérdezzük ezen elemeket.

Azért hatékony ez a megoldás, mert összesen két lekérdezést kell tennünk, nem úgy mint a rekurzív megoldásban, ahol minden egyes elemnél kellett egy lekérdezés.

A $right verem arra szolgál, hogy lépést tartsunk abban, hogy éppen hol is tartunk a fastruktúrában. Azaz a verem a gyökértől kezdve mindig tartalmazza az aktuális ágat, amit éppen vizsgálunk, a gyökértől az aktuális levélig tartalmazva az elemeket.

Ennek a függvénynek létezik a jobban használahtó változata is, mégpedig a retrieve2array:
<?php
/* A fát adaja vissza a $id gyökértől kezdve egy tömbben.
   Minden mélység 0. eleme maga a szinten lévő elem és a többi tömb
   az elem mellett pedig a gyerekei.
*/
function retrieve2array($id) {
  $arr = array();
  // lekérdezzük a bal és jobb értékét a gyökér elemnek
  $result = mysql_query('SELECT lft, rgt FROM  '.$this->table.
                        ' WHERE id='.$id); 
  if($row = mysql_fetch_assoc($result)) {
    // a $right tömb egy verem lesz
    $right = array(); 

    // most lekérdezzük az elem összes leszármazottját
    $query = 'SELECT * FROM '.$this->table.
             ' WHERE lft BETWEEN '.$row['lft'].' AND '. 
             $row['rgt'].' ORDER BY lft ASC';
    $result = mysql_query($query); 

    // minden sort belepakolunk a $arr tömbbe
    while ($row = mysql_fetch_assoc($result)) { 
      // csak akkor ellenőrízzük a vermet, ha nem üres
      if (count($right)>0) { 
        // ellenőrízzük, hogy ki kell-e venni az elemet a veremből
        while ($right[count($right)-1]<$row['rgt']) { 
          array_pop($right);
        } 
      }
      // hozzáadjuk az új elemet a $arr tömbhöz
      $this->_addToArray($arr, count($right), $row);
      // hozzáadjuk az elemet a veremhez
      $right[] = $row['rgt'];
    }
    return $arr[0];
  }
  return $arr;
}
?>
Maga a működés az előzőhöz hasonló, azonban az elemek nem egy stringbe, hanem egy tömbbe kerülnek. Ezt a működést szolgálja a _addToArray függvény.

Az _addToArray függvény három paramétert vár: a tömböt, amihez pakolni kell, a mélységet, ahova az elemet rakni kell és magát az elemet.
<?php
/*
   Segédfüggvény
   Létrehoz egy új elemet a tömbben a $depth mélységben és
   azt az $item-re állítja
*/
function _addToArray(&$array, $depth, $item) {
  // előkészítünk egy ideiglenes referenciát a tömbre a bejárásához
  $arr=&$array;
  // bejárjuk a tömböt, és a referenciát a $depth mélységben
  // levő utolsó elemre állítja, ha az nem üres
  $i=0;
  while($i<$depth && $c=count($arr)) {
    $arr=&$arr[$c-1];
    $i++;
  }
  // készítünk egy új tömböt a referencia tömb végén
  $arr[]=array();
  // hozzáadjuk az új elemet a tömbhöz
  $arr[count($arr)-1][]=$item;
}
?>
Álljon itt egy megjelenítő függvény, ami megmutatja, hogy hogyan is lehet az ilyen típusú tömböt megjeleníteni rendezetlen listában. Ehhez feltételezzük, hogy egy name nevű mezőt is tartalmaz az előbbi adatbázis.
ALTER TABLE my_table ADD name VARCHAR(50)
<?php
// $arr a tömb, amit ábrázolni akarunk, pid az elem szülője,
// $first pedig, hogy kell-e a gyökér elem
function makeUL(&$arr, $pid, $first=true) {
  $output='';
  // ha nem ez az első elem, akkor kirakjuk az LI-t és az elem nevét
  if(!$first) {
    $output='<li>'.$arr[0]['name'];	
  }
  $c=count(&$arr);
  // ha léteznek az elemnek gyerekei, azokat is ábrázolni kell
  if($c>1) {
    $output.="\n<ul>\n";
    // ábrázoljuk az egyes gyerekeket, úgy hogy rekurzívan
    // végigmegyünk rajtuk
    for($i=1; $i<$c; $i++) {
      $output.=makeUL(&$arr[$i], $arr[0]['id'], false);
    }
    $output.="</ul>\n";
  }
  if(!$first) {
    $output.="</li>\n";
  }
  return $output;
}
include_once('classes/tree.class.php');
$mytable=new Tree('my_table');
$myarray=$mytable->retrieve2array(1);
// ha meg akarjuk mutatni a gyökér elemet is, akkor ki kell rakni
// az UL elemeket
echo("<ul>\n");
echo(makeUL($myarray, 1, false));
echo("</ul>\n");
// ha nem kell a gyökér elem, akkor
echo(makeUL($myarray, 1));
?>
Noha ez a függvény rekurzív, viszonylag gyorsan lefut, mivel csak referenciákat ad át, és vissza is csak az ábrázolandó stringet adja. A rekurzió azért szükséges, mert maga a tömb amivel dolgozik már tartalmazza a fastuktúrát, és a teljes bejárás tudtommal csak rekurzívan kivitelezhető.

Közvetlen leszármazottak

A közvetlen leszármazottak lekérdezéséhez már bonyolultabb SQL műveletek szükségesek.

Mivel tudjuk hogy a leszármazottak bal és jobb értékei az elem bal és jobb értéke közé esnek, ezért csak ezen elemek lekérdezésére van szükségünk, ugyanakkor szükségünk van az elemünk mélységére is a fában, és a leszármazottak mélységére is. A getDirectDescendants függvény pont ezen műveleteket végzi el:
<?php
// Visszaadja az $id azonosítójú elem közvetlen leszármazottait
// egy asszociatív tömbben (id => resultset)
function getDirectDescendants($id) {
  $results=array();
  // lekérdezzük a mélységét és a jobb ill. bal értékét az elemnek
  $query = 'SELECT T1.id, T1.lft, T1.rgt, COUNT(T1.id) AS depth FROM '.
           $this->table.' AS T1, '.$this->table.
           ' AS T2 WHERE T1.lft BETWEEN T2.lft AND T2.rgt AND T1.id='.
           $id.' GROUP BY T1.id ORDER BY T1.lft';
  $result = mysql_query($query);
  if($row = mysql_fetch_assoc($result)) {
    // lekérdezzünk az elemünk alá eső elemeket, melyek
    // mélysége egyel nagyobb, mint az elem mélysége
    $query = 'SELECT T1.*, COUNT(T1.id) AS depth FROM '.
             $this->table.' AS T1, '.$this->table.
            ' AS T2 WHERE T1.lft BETWEEN T2.lft AND T2.rgt AND T1.lft BETWEEN '.
            $row['lft'].' AND '.$row['rgt'].
            ' GROUP BY T1.id HAVING depth='.($row['depth']+1).
            ' ORDER BY T1.lft';
    $result = mysql_query($query);
    while($row = mysql_fetch_assoc($result)) {
      $results[$row['id']] = $row;
    }
  }
  return $results;
}
?>

Egyéb lekérdezések

A különböző megjelenítések során szükségünk lehet más fajta lekérdezésekre is.

Egy $id-jű elem leszármazottainak számát a descendants függvényel tudhatjuk meg, amely a már korábban említett matematikai összefüggést használja, mely szerint leszármazottak_száma=(jobb-bal-1)/2.

Elemünk elérhetőségi útját a pathTo függvény szolgáltatja. A függvény kihasználja a jobb és bal értékek alakulását, miszerint a szülő bal értéke kisebb, mint az elem bal értéke, a jobb értéke pedig nagyobb.

A getDepth függvény az egy adott mélyégben levő elemeket szolgáltatja, függetlenül a szülőjüktől, a getDirectDescendants mélység lekérdezését használva.

A getDepths függvény az $id elemtől, mint gyökértől kezdve adja vissza az egyes elemek mélységét a fastruktúrában.

A fa módosítása

Elsőként kezdjük egy, a módosítások szempontjából létfontosságú fügvénnyel, mégpedig amelyik megmondja, hogy egy adott elem levél-e. Ez az függvény a leaf. Igaz értéket ad vissza, ha egy elem levél - azaz a jobb-bal==1, hamisat, ha az elem nem létezik, illetve nem levél.

Elem hozzáadása a fához

Mint láthattuk elég sok matematikai összefüggés van a fa bal és jobb értékét figyelembe véve. Ezt fogjuk kihasználni új elem hozzáadásakor is. Mielőtt egy új elemet szúrnánk be a fába, módosítani kell a szülő elem bal érték minusz 1-nél nagyobb jobb és bal értékű elemeket a fában, méghozzá 2-vel kell megnövelni ezen elemek bal és jobb értékét.

Miért pont 2?

Ha ránézünk a fastruktúránkra, akkor látjuk, hogy minden elem pontosan 2-vel növeli meg a jobb és bal értékek összegét. Azaz ha egy új elemet kell berakni, akkor szintén ennyivel kell megnövelni az elemek jobb illetve bal értékét.

Az új elem beszúrásakor, a szülő elemtől jobbra levő elemek jobb és bal értéke tehát kettővel nő, és mivel a szülő elem jobb értéke nagyobb mint a bal, ezért az szintén megnő, ahogy szerettük is volna, hogy a fa szerkezetét egyensúlyban tartsuk.

Már csak be kell szúrni az új elemet, aminek bal értéke a szülő eredeti bal értéke lesz, jobb értéke, pedig a bal+1. Ezen műveleteket foglalja magába a addNode függvény, mely az újonnan beszúrt elem id-jét adja vissza, vagy -1-et, ha nincs meg a szülő.
<?php
function addNode($id) {
  if($id) {
    $result = mysql_query('SELECT lft, rgt FROM '.$this->table.
                          ' WHERE id='.$id); 
    if($row = mysql_fetch_assoc($result)) {
      $result = mysql_query('UPDATE '.$this->table.
                            ' SET rgt=rgt+2 WHERE rgt>'.
                            ($row['rgt']-1));
      $result = mysql_query('UPDATE '.$this->table.
                            ' SET lft=lft+2 WHERE lft>'.
                            ($row['rgt']-1));
      $result = mysql_query('INSERT INTO '.$this->table.
                            ' (lft, rgt) VALUES('.$row['rgt'].
                            ', '.($row['rgt']+1).')');
      return mysql_insert_id();
    }
    else {
      return -1;
    }
  }
  else {
    return -1;
  }
}
?>

Elem törlése a fából

Az elem törlése hasonlóképpen működik, mint az elem hozzáadása csak éppen kivonni kell.
<?php
function delNode($id) {
  if($id) {
    $result = mysql_query('SELECT lft, rgt FROM '.$this->table.
                          ' WHERE id='.$id);
    if($row = mysql_fetch_assoc($result)) {
      // a sor megvan, megnézzük, hogy levél-e, majd törlünk
      if($row['lft']+1==$row['rgt']) {
        $result = mysql_query('UPDATE '.$this->table.
                              ' SET rgt=rgt-2 WHERE rgt>'.
                              ($row['rgt']-1));
        $result = mysql_query('UPDATE '.$this->table.
                              ' SET lft=lft-2 WHERE lft>'.
                              ($row['rgt']-1));
        $result = mysql_query('DELETE FROM '.$this->table.
                              ' WHERE id='.$id);
        return 1;
      }
      else {
        return -1;
      }
    }
  }
  // nem találtuk meg az elemet, vagy nem levél
  return 0;
}
?>
A törlés előtt leeléenőrizzük, hogy az elem létezik-e, és hogy levél-e, majd töröljük és 1-et adunk vissza. Ha nem levél, akkor -1 el térünk vissza, míg ha az elem nem is létezik, akkor 0-val.

Elem mozgatása

Az elem mozgatása a fenti két művelet kombinálása. Megvizsgáljuk, hogy létezik-e az új szülő ($pid), és hogy az elemünk levél-e. Ha ezen feltételek teljesülnek, akkor előbb az elemtől eredetileg jobbra levő elemek bal és jobb értékét 2-vel csökkentjük, majd a szülőtől jobbra eső elemek bal értékét pedig 2-vel növeljük, végül beállítjuk az elem jobb és bal értékét.
<?php
// az $id elemet mozgatja az $pid elem alá
function moveNode($id, $pid) {
  if($id && $pid) {
    // vesszük az elemet
    $result = mysql_query('SELECT lft, rgt FROM '.$this->table.
                          ' WHERE id='.$id);
    if($row = mysql_fetch_assoc($result)) {
      // megnézzük levél-e
      if($row['lft']+1==$row['rgt']) {
        // tároljuk az eredeti bal értéket
        $orgt=$row['rgt'];
        // vesszük a szülőt
        $result = mysql_query('SELECT lft, rgt FROM '.$this->table.
                              ' WHERE id='.$pid);
        if($row = mysql_fetch_assoc($result)) {
          // megvan a szülő, frissítjük az elemeket
          // eredetitől jobbra levők

          $result = mysql_query('UPDATE '.$this->table.
                                ' SET rgt=rgt-2 WHERE rgt>'.
                                ($orgt-1));
          $result = mysql_query('UPDATE '.$this->table.
                                ' SET lft=lft-2 WHERE lft>'.
                                ($orgt-1));
          // szülőtől jobbra levők
          $result = mysql_query('UPDATE '.$this->table.
                                ' SET rgt=rgt+2 WHERE rgt>'.
                                ($row['rgt']-1));
          $result = mysql_query('UPDATE '.$this->table.
                                ' SET lft=lft+2 WHERE lft>'.
                                ($row['rgt']-1));
          // a kívánt elemünk
          $result = mysql_query('UPDATE '.$this->table.' SET lft='.
                                $row['rgt'].', rgt='.($row['rgt']+1).
                                ' WHERE id='.$id);
          return 1;
        }
      }
    }
  }
  return 0;
}
?>

Összefoglalás

Áttekintettük a fastruktúra újabb kezelési módját, és megismerhettük ennek matematikai hátterét. Megmutattam, hogy lehet ezen ismereteket felhasználni a fastruktúra különböző féle lekérdezéseihez, és magának a struktúrának a módosításához.
 
Poetro arcképe
Poetro
1998 óta foglalkozik webfejlesztéssel, amikor is a HTML és a JavaScript világa elvarázsolta. Azóta jópár évet dolgozott reklámügynökségeknél, és nemzetközi hírportálok fejlesztésével. Legfőképpen Drupal fejlesztéssel, site buildinggel és JavaScripttel, azon belül is jQuery-vel és Node.js-sel foglalkozik.
1

children vs direct desc

Jano · 2005. Jan. 19. (Sze), 00.08
Hello!

Nem teljesen sikerult meg megerteni a dolgot:
1.A children es a direct descendent miben kulonbozik?
2.Hogy adja vissza a count(T1.id) a melyseget?
3

Re: children vs direct desc

Poetro · 2005. Jan. 19. (Sze), 10.37
  1. Teljesen igazad van, a gyermek megegyezik a közvetlen leszármazottal. Anno fejlesztés közben így egyértleműbb volt, de a gyermek (children) ténylegesen csak a közvetlen leszármazott
  2. Ha megfigyeled a lekérdezést, akkor észreveszed a hozzá kapcsolt T2-t ami a direkt szorzat lenne önmagával. De a feltétel szerint csak azokkal történik meg, melyik bal és jobb értéke ezen értékek közé esik, és ekkor csak a felmenők, és a megjelölt elem maradnak meg. A COUNT pedig már a felmenők számát adja vissza, plusz az aktuálisan megjelölt elemet.

Más megoldások

A retrieve és retrieve2array metódusban használt módszer helyett próbálkoztam direkt SQL-es megoldással is, de sokszori tesztelés után is lassabbnak bizonyult.
A közvetlen SQL-es megoldás az indent számításra, ha már a $lft és a $rgt értékek megvannak

SELECT T1.*, COUNT(T2.id) AS indent FROM my_table AS T1, my_table AS T2 
WHERE T1.lft BETWEEN T2.lft AND T2.rgt AND T1.lft BETWEEN $lft AND $rgt 
GROUP BY T1.id ORDER BY T1.lft ASC

Teszt

Összehasonlítottam a két metódus időigényét. Mindkét esetben elvégeztem lekérdezést végeztem véletlenszerű mélységből:
  1. magában a PHPben számítom ki az indent-et ahogy az előbb említett függvényekben
  2. az előbb említett lekérdezést használom az indent számítására

Időigények ezer lekérdezés esetén 10enként átlagolva:
  1. 1.29451007843 másodperc
  2. 3.26239590645 másodperc

Egyes kiugró esetekben a különbség akár 10-szeres is lehet a függvényekben használt metódus előnyére.
--------
Poetro
4

mélységi szám

Jano · 2005. Jan. 19. (Sze), 11.22
Arra gondoltam, hogy a bal értékek ugyis a mélységi bejárás szerinti sorrendet adják, akkor meg lehetne jegyezni a mélységi számot is (hanyadik szinten van az elem). Így sokkal egyszerübb lenne a gyerekek lekérdezése pl.

Masrészt akkor ha jól okoskodom akkor egy nem rekurzív algoritmussal is lehetne pl egy UL listát generálni.

Az UL listában a forrásban pont a balszámok szerinti sorrendben vannak az elemek, csak azt kell tudnunk a kiiráskor, hogy a következő elem az gyereke, testvére vagy valamelyik felmenőhőz kapcsolodó gyereke. Ezt a mélységi szám alapján megtudjuk. Ha mélységi szám +1 akkor gyereke, ha = akkor testvér, ha kisebb akkor ős gyereke.

Vegyük az adott fát és írjuk ki a left értékek sorrendjében egy tömbbe, de jegyezzük fel minden elemhez a mélységi számot is.

pl:
programozas 1
php 2
string 3
pear 3
mail 4
httpclient 4
html 1
...

- nyit UL
ismetel:
{
- nyit LI
- kiir item
- ha van kovetkezo es melysegiszáma(m2) {
ha m2 < m1 akkor
(m2-m1) -szer zaro LI /* be kell zarni felmenoket m2-ig */
ha m2 = m1 akkor zaro LI
}
}
- utolso m-szer zar LI.
- zar UL

Meg nem probaltam ki, ugyhogy lehet nem jo, vagy hias.
26

Rekurzió nélküli megjelenítés

Poetro · 2005. Jún. 30. (Cs), 15.05
Most jutott rá időm, és megvalósítottam a rekurzió nélkül megjelenítést is. Ehhez a retrieve2array függvényt alakítottam át a következőképpen:

<?php
	function retrieve2array_ext($id) {
		// retrieve the left and right value of the $root node 
		$result = mysql_query('SELECT lft, rgt FROM  '.$this->table.' WHERE id='.$id);
		$arr = array();
		if($row = mysql_fetch_assoc($result)) {
			// start with an empty $right stack 
			$right = array(); 
			// now, retrieve all descendants of the $root node
			$result = mysql_query('SELECT * FROM '.$this->table.' WHERE lft BETWEEN '.$row['lft'].' AND '. 
						$row['rgt'].' ORDER BY lft ASC'); 
			// add each row to the array
			while ($row = mysql_fetch_assoc($result)) { 
				// only check stack if there is one 
				if (count($right)>0) { 
					// check if we should remove a node from the stack 
					while ($right[count($right)-1] < $row['rgt']) { 
						array_pop($right);
					}
				}
				// add the new element to the array
				$row['_depth']=count($right);
				$arr[]=$row;
				// add this node to the stack 
				$right[] = $row['rgt'];
			}
		}
		return $arr;
	}
?>
Így egy tömböt adok vissza, minden elemnél megadva a mélységet (_depth), az elemek továbbra is megfelelő sorrendben lesznek.
A megjelenítéshez pedig fejlesztettem egy aranyos kis függvényt. Kettő paramétert vár. Az első az imént előállított tömbb, a második, pedig, hogy mi az elem neve a tömbben, amit ki kell írni.

<?php
function makeUL2(&$tree, $title) {
	$output = '';
	if (count($tree)) {
		$depth = -1;
		foreach ($tree as $term) {
			// az előző al-eleme, új lista jön
			if($depth<$term['_depth']) {
				$output .= "\n<ul>\n";
			}
			// az előző elem mellett van, az előzőt bezárjuk
			else if($depth == $term['_depth']) {
				$output .= "</li>\n";
			}
			// az előző elem alatt van, bezárjuk az aktuálisat, és bezárjuk az előzőeket, amik felette vannak
			else {
				$output .= '</li>'.str_repeat("\n</ul>\n</li>\n", $depth-$term['_depth']);
			}
			$output .= '<li class ="leaf'.$term['_depth'].'">'.$term[$title];
			$depth = $term['_depth'];
		}
		// bezárjuk a maradékot
		$output .= str_repeat("</li>\n</ul>\n", $depth+1);		
	}
	return $output;
}
?>
Remélhetőleg minden változatban valid HTML listát kapunk. Leteszteltem, de azért lehetnek extrém változatok.
Itt van hozzá egy minta, amin teszteltem:
"id","lft","rgt","title"
"1","0","55","MENU"
"2","1","2","címoldal"
"3","3","4","hírek"
"4","5","14","tanszék"
"5","15","30","info"
"6","31","40","szakirány"
"7","41","42","klub"
"8","43","54","állás"
"9","6","7","munkatársak"
"10","8","9","eredmények"
"11","10","11","támogatók"
"12","12","13","kontakt"
"13","16","17","tantárgyak"
"14","18","19","tematika"
"15","20","21","tételsor"
"16","22","23","diploma"
"17","24","25","letöltés"
"18","26","27","jegybeiratás"
"19","28","29","eredmények"
"20","32","33","ismeret"
"21","34","35","nappali"
"22","36","39","UD"
"23","44","45","keres"
"24","46","53","kínál"
"25","47","52","Untitled"
"26","48","49","Untitled"
"27","50","51","Untitled"
"28","37","38","Untitled"

<?php
$mytable=new Tree('menu');
$myarray=$mytable->retrieve2array_ext(1);
echo(makeUL2($myarray, 'title'));
?>
--------
Poetro
2

a baja a modszernek:(

zedorg · 2005. Jan. 19. (Sze), 09.51
Tenyleg az 1ik leghatekonyabb modszer fa kezelesre, csak 1 roppant nagy hatranya van:(. Nem lehet orderelni :(. Az adott szinteken levoket csak a bentlevo sorrendben lehet lekerdezni, ha nev szerint, vagy bar mias szerint akarnank, nem tudjuk. De ha tudjuk, hogy tutira mindig csak nev szerint akarunk sorrendet, es mindig csak 1 iranyut, akkor update es insertnel kicsit kiegeszitve a dologot, a fat ujra alkotjuk mindig a jo sorrendben.
5

bajok baja?

dtaylor · 2005. Jan. 19. (Sze), 12.07
Szia!

A 2-t kell kombinalni:

Van ez a nestedset megoldas, es van a hagyomanyos.
A nestedset-es be be kell tenni egy parent_id mezot.

Azzal mar sokkal tobb mindent el tudsz intezni...

Sot, meg egy tree_id mezot bele, es akkor tobb fa is lehet egy db-ben.
(mondjuk szalakba szedett forum hozzaszolasok). Ekkor csak konkret fa-ra kell a rebuild-et ertelmezni egy uj elem beszurasakor.


--
[ Dönci ]
9

mondjuk 1 latogatott forumnal

zedorg · 2005. Jan. 20. (Cs), 02.34
mondjuk 1 latogatott forumnal ez nem az igazi ;) van mondjuk 3 rendezesi lehetoseg, az 3 tree, amit ujra kell buildelni insert/update/delete -nel:)
10

Miert is van 3 tree?

dtaylor · 2005. Jan. 22. (Szo), 16.25
Szia!

Egy tree van csak egy forum topichoz! csak annyi a kulonbseg, hogy:

Main tree

topic1 tree
topic2 tree
stb.

Akkor ha a topic2 tree-be van beszuras, akkor csak azt az egyet kell updatelni, a tree2-t nem.

Mig ha ugy szervezed, hogy:
Main tree
   Forum
      Main topic cat
           Sub topic cat
               Topic 1
               Topic 2
           Sub topic cat
      Main topic cat
        :
        :


Akkor mindent kell update-lni.
Ezert celszeru tree_id-t is bevezetni, ami azonositja azt a fat.
Illetve, en betettem meg egy level azonositot is. Akkor lehet tudni, hogy eppen milyen melyen jarok a faban. Jo is az! :)

--
[ Dönci ]
19

A level-t amugy is tudod leke

zedorg · 2005. Feb. 8. (K), 10.39
A level-t amugy is tudod lekerdezesnel ;) kiveve persze ha nem a csucsrol inditod. Amugy tree_id jaja, de 1 tree alatt is valoszinuleg rengeteg tree van :D) Mondjuk nem altalanosak a 3-4 szintu forumok ez igaz.
6

kérdés

Anonymous · 2005. Jan. 19. (Sze), 14.57
bocsánat ha buta kérdés, de a getDirectDescendants függvény lekérdező parancsaiba miért kell az ORDER BY T1.lft alparancs ?
7

Ezért, hogy a fában szerepl

Poetro · 2005. Jan. 19. (Sze), 16.02
Ezért, hogy a fában szereplő sorrendben kapjuk vissza az adatokat, azaz úgy, ahogyan a fát bejárnánk a bal és jobb értékeknek megfelelően (lásd 1. ábra).
--------
Poetro
8

kérdés

Anonymous · 2005. Jan. 19. (Sze), 16.27
és/de az elsőbe miért :) ?
11

innodb + tranzakció

Anonymous · 2005. Jan. 25. (K), 15.47
Hali!

Van valakinek tapasztalata, hogy mennyire megterhelő ez a mysql + innodb + tranzakció hármasnak több ezres rekordszámnál?

Illetve mennyivel hatékonyabb az első változatnál?

És mi van, ha nincs tranzakciókezelés, és mondjuk valakik egyidőben szúrnak be? Én inkább itt látom a gyenge pontot...
12

meg se forduljon a fejekben!

gerzson · 2005. Jan. 25. (K), 17.10
És mi van, ha nincs tranzakciókezelés, és ...?

A tranzakció(kezelés) alapfogalom és követelmény az adatbázisok világában, ezért ezt a felvetés jobb mielőbb elfelejteni.
13

bocs

sotetbarna · 2005. Jan. 25. (K), 21.22
bocs, de a példában hol van nyoma a tranzakciókezelésnek?

nem kötözködni akarok, de a mysql-ben csak innodb meg bdb tábla formátumban van tranzakciókezelés, ami szerintem ennél a példánál elengedhetetlen

úgyhogy szvsz egyről beszélünk

sotetbarna
15

Igen?

sajt · 2005. Jan. 27. (Cs), 14.42
Szerintem azok kozul az emberek kozul, akik ezt az oldalt olvassak 100-bol, ha egy hasznal tranzakciokezelest, es talan 1000-bol 1 mysql-ben.

Mondjuk szerintem az ilyen adatkezelesek jellemyo felhasznalasi terulete a menukezeles, amit altalaban egy ember szokott vegezni, igy nem nagyon lesznek ilyen osszeakadasos problemak.

Es meg valami. En mar eleg sok helyen dolgoztam, kulonbozo fejlesztesi temakban, de meg sehol nem lattam, hogy valahol tranzakciokat hasznaltak volna. Foleg php-s fejleszteseknel. Ez nem azt jelenti, hogy ez igy jo, de - sajnos - igy van.

Persze az sem veletlen, hogy a php5 az sqllite-ot tamogatja, mert a felhasznalok 99%-a nem hasznalta masra a mysql-t mint egyszeru adatok beirasara es kiolvasasara, amire az sqllite tokeletesen megfelel, es meg csak agy mysql-t sem kell hozza elinditani.
16

nem? nemtudom!

kmm · 2005. Jan. 27. (Cs), 14.49
Szerintem azok kozul akik nezik a weblabort en benne vagyok abban a százban, ami szerintem 10...

Mondjuk szerintem ha nagyobbacska forgalom van akkor celszeru a menut nem az sqlben tárolni, hanem statikusan, vagy kesselve, és csak akkor kérni a dbtől mikor változik, és akkor megint kesselni...

Es meg valami. En nem sok helyen dolgoztam meg de azert par helyen igen, de mindenhol volt tranzakciokezeles. Foleg phps fejlesztesekben dolgoztam.

Persze sz sem veletlen, hogy nem hasznalok mysqlt, hanem pgt, ha csak tehetem, mert azt szeretem... sqllite-ot meg meg sosem hasznaltam.



--
üdv: kmm...
17

Mázlista

sajt · 2005. Jan. 27. (Cs), 15.06
Akkor te egy mazlista vagy :) En is orulnek,a olyan helyen dolgozhatnek. Dehat a vagyak es a valosag...
14

szvsz a gyenge pont maga az i

zedorg · 2005. Jan. 27. (Cs), 01.10
szvsz a gyenge pont maga az innodb, a mysql-en kivul.

Hatekonyabbnak mindenkepp hatekonyabb, legyen szo barmilyen adatbazisrol is, amennyiben a fa szerkezet valtozas joval kevesebb mint a fa struktura lekerdezese. Marpedig az esetek 99.99999%-ban ez a helyzet :)
18

MLM rendszer felhasználóinak tárolása

Anonymous · 2005. Feb. 5. (Szo), 15.54
Pont a legjobbkor jött ez a cikk.
Egy MLM (Multi Level Marketing ) rendszer felhasználóit és kapcsolati rendszerét szeretném PHP + valamilyen adatbázis segítségével tárolni és kezelni.

A programom által megvalósítandó funkciókat nagyjából, kis kiegészítésekkel fedik a fenti függvények.

De most nem egy menü struktúráról, vagy kategória fáról, hanem nagyságrendre akár egy 10-100, 1000? szint mélységű kapcsolati rendszerekből álló, 10000-100000, 1000000? rekordszámú adatbázisról lenne szó.

Ez egy gyakorlatilag nulláról induló, folyamatosan növekvő adatbázis lenne. A kezdeti költségeket minimalizálni kell, így jó lenne, ha működhetne PHP+MySQL-el (olcsó szolgáltató) valamint egy amatőr programozóval (én).

Alkalmas-e a feladatra a fenti algoritmus és a MySQL?
Gyakori műveletek lennének:
- felhasználó regisztráció (levél beszúrás egy ágba)
- felhasználó belépés (felhasználó alá tartozó emberek számának lekérdezése, közvetlenül alatta lévő felhasználók listázása és az azokhoz tartozó felhasználók számának lekérdezése)
Ritkábban:
- ág lecsatolása és áthelyezése,

Én arra gondoltam, hogy bizonyos adatokat cachelve kezelnék. Pl. a felhasználó alatti felhasználók számát felírom egy mezőbe. Viszont ezeket felfelé rekurzívan frissíteni kell pl. egy ág lecsatolásnál.

Rettentő nagy favágásnak számítana a tranzakciókezelős problémát valamiféle lock fájl alkalmazásával áthidalni?
Lényegében erre csak regisztrációkor lenne szükség, és ág áthelyezésekor. Előbbi napi 1-10-100 alkalom, utóbbi talán napi 1-10 eset, de az is lehet, hogy ez manuális lenne.

Milyen nehézségek adódhatnak még?
PHP-ban sokmindent megvalósítottam már, de nem vagyok egy tapasztalt programozó.
Belevághatok-e, vagy vegyem tudomásul, hogy egy ilyen rendszer kidolgozása kemény dió, és nem kisfiúknak való?
20

Alkalmas

Poetro · 2005. Feb. 16. (Sze), 17.09
Maga a módszer alkalmas rá, csak ki kell egészíteni pár funkcióval, azaz teljesen testre kell szabni.
Az egyik dolog, ami fontos lehet, hogy mivel ha jól vettem ki, különböző típusú adatokat akarsz tárolni akkor magában a fában az értékként érdemes lenne a típust és a hivatkozandó elem azonosítóját tárolni, majd típus szerint hozzácsatolni a megfelelő elemet.
Persze ha azonos típusú elemekből áll a hierarchia akkor ilyen probléma nincsen, és akkor a kívánt metódusok már meg is vannak írva az osztályban. Már csak az ábrázolást kell neked hozzáfejlesztened.
--------
Poetro
21

nem kevél elem mozgatása

Anonymous · 2005. Ápr. 11. (H), 09.40
Ha jól értem a cikk végén az elem mozgatás kód, csak azokat az elemeket mozgatja amik levél elemek ?
Ha így van akkor hogy lehet megoldani, hogy bármilyen elemet, akár egész ágakat áthelyezni ?
22

nem levél mozgatása

Poetro · 2005. Ápr. 11. (H), 16.24
Pillanatnyilag csak a levél mozgatására képes a kód, de kisebb minkával megoldható egész ágak átmozgatása is.
Ez akár a jelenlegi kód felhasználásával is történhet, hogy rekurzívan végigmegyünk az elemeken, és mozgatjuk őket.
Kisebb felkészüléssel persze megírható az ágak támozgatása, de ezt a feladatot a fejlesztőkre bízom. Ha előállok egy kész megoldással, akkor azt publikálni fogom.
--------
Poetro
23

Akkor megírom

Anonymous · 2005. Ápr. 11. (H), 18.08
Akkor megírom, csak nemvoltam biztos a dogomban, köszi!
41

ág mozgatása

rrd · 2007. Okt. 4. (Cs), 11.24
Valakinek van erre valami kódja?
42

Nekem van.

janoszen · 2007. Okt. 4. (Cs), 11.57
Nem túl bonyolult kb egy fél éve egyszer megcsináltam, csak arra kell figyelni, hogy ha jobbra mozgatod az ágat akkor a mozgatandó jobb értékénél nagyobb és bal értékeket kell csökkenteni, ha balra mozgatod akkor meg a mozgatandó bal értékénél kisebbeket kell növelni ha jól emlékszem.
43

:)

rrd · 2007. Okt. 4. (Cs), 12.25
előbányásznád nekem?
44

Backup lemezen

janoszen · 2007. Okt. 4. (Cs), 13.17
Ha meggyógyultam, elő fogom bányászni, mert nekem is kell, de így betegen nincs kedvem elővadászni a polcon porosodó backup vinyók közül. :) Szólj rám mailben egy hét múlva légyszi. :)
45

Doctrine

Hodicska Gergely · 2007. Okt. 4. (Cs), 15.10
Bár lehet, hogy ágyúval verébre, de a Doctrine-ban pont a nested set le van programozva, kb. nulla programozással tudod használni, igaz, hogy meg kell ismerned a Doctrine-t, amihez kell egy kis idő, de megérheti, nekem épp nagyon tetszik.


Üdv,
Felhő
46

Működés...

janoszen · 2007. Okt. 4. (Cs), 16.44
Engem mondjuk ha nem éppen éles használatról van szó pont az érdekelne hogy hogy tudom ezt megcsinálni... hogy működik. :)
47

re: ?

Hodicska Gergely · 2007. Okt. 5. (P), 07.15
De hát azt írtad, hogy már megcsináltad. Ez csak egy példa megvalósítás ami jól jöhet a kérdezőnek, ha épp nem akarja beleásni magát ebbe. Mellesleg ez szerintem nem bonyolult, inkább az a kategória, amivel kicsit el kell szöszölni funkciónként.


Üdv,
Felhő
48

Nyilván..

janoszen · 2007. Okt. 5. (P), 09.32
Nyilván _valamire_ kell a kedves tagnak. Lehet hogy valamibe bele akarja gyógyítani. Na mindegy, majd előkeresem amint lesz erőm rá.

(Személy szerint én nem szeretek vakon használni valamit, amit nem ismerek...)
49

begyógyítani

rrd · 2007. Okt. 5. (P), 13.15
Talált. Egy ajax alap cakephp-s személyi feladatütemezőt gyártok és abba akarom beleépíteni.
50

ott a forrás ;)

Hodicska Gergely · 2007. Okt. 6. (Szo), 21.31
Ha nem akarja valaki használni "vakon", akkor még mindig ott a forráskód, amiben meg lehet nézni, hogy hogyan is kell ezt csinálni.


Üdv,
Felhő
51

Fa mozgatása

janoszen · 2007. Okt. 7. (V), 07.49
Na, kivadásztam a régebbi projektemből a fa mozgatását szolgáló kódot. Helyességi garancia nulla, mert nem használom de azt hiszem a tesztek szerint annó helyesen működött.

Ez nem egészen az amit Te szeretnél valszeg, mivel ez URL kezelésre született, de sztem egy kis kisérletezéssel át tudod alakítani.

CREATE FUNCTION `fs_movetree`(node INT, destination INT)
BEGIN
 DECLARE lft INT;
 DECLARE rgt INT;
 DECLARE dlft INT;
 DECLARE drgt INT;
 DECLARE dif INT;
 DECLARE max INT;
 DECLARE url VARCHAR(255);
 
 /* Megkapjuk a cél adatait. */
 SELECT fs_nodes.lft, fs_nodes.rgt INTO lft,rgt FROM fs_nodes WHERE id=node;
 SELECT fs_nodes.lft, fs_nodes.rgt INTO dlft,drgt FROM fs_nodes WHERE id=destination;
 
 /* Alapvető helyességi ellenőrzések hogy nem csinálunk-e kijavíthatatlan ökörséget. */
 IF ((drgt < lft) OR (dlft > rgt) OR (dlft<lft AND drgt>rgt)) AND (node<>destination) THEN
  /* Megnézzük mekkora méretben a mozgatandó fa. */
  SET dif=rgt-lft+1;
  
  /* Node cache törlése, ha használsz ilyet. */
  SELECT CONCAT(fs_buildurl(id), "%") INTO url FROM fs_nodes WHERE id=node;
  DELETE FROM fs_nodecache WHERE fs_nodecache.url LIKE url;
  
  /* A mozgatandó részfát áttesszük a negatív tartományba. */
  UPDATE fs_nodes SET fs_nodes.lft=(-1)*fs_nodes.lft,fs_nodes.rgt=(-1)*fs_nodes.rgt WHERE fs_nodes.lft>=lft AND fs_nodes.rgt<=rgt;
  
  /* Jobbra illetve balra mozgatás különböző tartományokat számoz át. */
  IF drgt>lft THEN
   UPDATE fs_nodes SET fs_nodes.lft=fs_nodes.lft-dif WHERE fs_nodes.lft>=lft AND fs_nodes.lft<drgt;
   UPDATE fs_nodes SET fs_nodes.rgt=fs_nodes.rgt-dif WHERE fs_nodes.rgt>=lft AND fs_nodes.rgt<drgt;
  ELSE
   UPDATE fs_nodes SET fs_nodes.lft=fs_nodes.lft+dif WHERE fs_nodes.lft>=drgt AND fs_nodes.lft<lft;
   UPDATE fs_nodes SET fs_nodes.rgt=fs_nodes.rgt+dif WHERE fs_nodes.rgt>=drgt AND fs_nodes.rgt<lft;
    
   SET drgt=drgt+dif;
  END IF;
  SELECT fs_nodes.rgt INTO drgt FROM fs_nodes WHERE id=destination;
  
  /* A negatív tartományból visszapakoljuk a célhelyre a részfát. */
  UPDATE fs_nodes SET fs_nodes.lft=(drgt-1)-(rgt+fs_nodes.lft) WHERE fs_nodes.lft<=(-1)*lft AND (-1)*rgt<=fs_nodes.lft;
  UPDATE fs_nodes SET fs_nodes.rgt=(drgt-1)-(rgt+fs_nodes.rgt) WHERE fs_nodes.rgt<=(-1)*lft AND (-1)*rgt<=fs_nodes.rgt;
  
  /* Módosítjuk a szülő bejegyzést (1-es és 2-es módszer kombinációját használom.) */
  UPDATE fs_nodes SET fs_nodes.parentid=destination WHERE fs_nodes.id=node;
  
  RETURN 1;
 ELSE
  RETURN 0;
 END IF;
END
Paraméterezése egyszerű, meg kell adni a mozgatandó részfa gyökerét (node) és hogy melyik pont alá akarod betenni. Azonos node-on belül mindig a végére kerül az új fa, mint a fájlrendszereknél.

Ha van rá igény, az egész URL kezelő mechanizmusomat nyilvánossá teszem.
52

reméltem, hogy sql

rrd · 2007. Okt. 8. (H), 11.04
Köszi. Reméltem, hogy sql megoldást kapok. A magam részéről inkább az sql-re rakom az ilyen jellegű terhelést, nem a php-ra :)

Nemsokára kipróbálom.
53

CakePHP

foxmulder · 2007. Okt. 28. (V), 11.57
Szia rrd!

A CakePHP1.2-ben van egy TreeBehavior, amely az ebben a cikkben is vázolt "modified pre-order tree traversal" módszert alkalmazza. Ez nem felel meg a céljaidnak?

Foxmulder
24

Hibás a megfogalmazás a

Anonymous · 2005. Jún. 15. (Sze), 16.51
Hibás a megfogalmazás a "Elem hozzáadása a fához" címü résznél.

"módosítani kell a szülő elem bal érték minusz 1-nél nagyobb jobb és bal értékű elemeket a fában" -- szülő elem jobb érték minusz 1 helyesen.

"Már csak be kell szúrni az új elemet, aminek bal értéke a szülő eredeti bal értéke lesz, jobb értéke, pedig a bal+1." -- szintén szülő eredeti bal értéke helyesen.
25

errr még én is elírtam :)

Anonymous · 2005. Jún. 15. (Sze), 16.52
errr még én is elírtam :) Tehát mindkét esetben bal helyett jobb.
27

Hiba

Anonymous · 2005. Aug. 25. (Cs), 18.45
Csak nekem van tele hibával az egész?

Warning: Call-time pass-by-reference has been deprecated - argument passed by value; If you would like to pass it by reference, modify the declaration of count(). If you would like to enable call-time pass-by-reference, you can set allow_call_time_pass_reference to true in your INI file. However, future versions may not support this any longer. in c:\appserv\www\tree\index.php on line 10

Warning: Call-time pass-by-reference has been deprecated - argument passed by value; If you would like to pass it by reference, modify the declaration of [runtime function name](). If you would like to enable call-time pass-by-reference, you can set allow_call_time_pass_reference to true in your INI file. However, future versions may not support this any longer. in c:\appserv\www\tree\index.php on line 17

Parse error: parse error in c:\appserv\www\tree\tree.class.php on line 299

Fatal error: Cannot instantiate non-existent class: table in c:\appserv\www\tree\index.php on line 27

Warning: mysql_fetch_assoc(): supplied argument is not a valid MySQL result resource in c:\appserv\www\tree\tree.class.php on line 284
28

Hibaüzenetek

Bártházi András · 2005. Aug. 28. (V), 07.32
Első lépésnek nagyon jó, hogy bemásoltad a hibaüzenetet, de a PHP forráskód adott részét is közölhetnéd, mert nehéz távolról kitalálni, hogy vajon mi lehet nálad a 10., 17., 299., 27., 284. sorokban...

Megkérnélek arra is, hogy írjad alá - bármilyen aláírással -, mert az Anonymousok beszélgetésének olvasgatása közben igen nehéz elkülöníteni, hogy ki kit kérdez.

-boogie-
29

forráskód

Anonymous · 2005. Aug. 28. (V), 09.39
a tree.class.php az amit inne le lehet tölteni.
Az index.php-ben pedig a makeUL-van+a listázás
30

Használat

Bártházi András · 2005. Aug. 28. (V), 15.16
A kód használatához nem árt érteni a PHP-hez, enélkül még ha minden klappolna is, sem hiszem, hogy fel tudnád használni. Az, hogy leírtad nekem, hogy az index.php-ben mi van, még nem jelenti azt, hogy ki tudom találni, hogy mi van az adott sorában, mennyi entert tettél elé, mögé, stb.

A letölthető állományban valóban van egy hiba. A 299. sorának végére egy záró zárójelet be kell tenni a pontosvessző elé.

Az index.php-ben ne feledkezz el az adatbázishoz csatlakozás mellett a használandó adattábla kijelöléséről.

Nekem a hibajavítás után működik a kód. Valószínűleg nem ártana tudnunk, hogy milyen PHP-vel próbálod a dolgot. Én PHP 4.x-szel.

-boogie-
31

Itt az index.php

Anonymous · 2005. Aug. 28. (V), 15.34

<?php
mysql_connect('localhost', 'root', '6enfk63Z');
mysql_select_db('tree');

include_once('tree.class.php');

function makeUL(&$arr, $pid, $first=true) {
  $output='';
  // ha nem ez az első elem, akkor kirakjuk az LI-t és az elem nevét
  if(!$first) {
    $output='<li>'.$arr[0]['name'];    
  }
  $c=count(&$arr);
  // ha léteznek az elemnek gyerekei, azokat is ábrázolni kell
  if($c>1) {
    $output.="\n<ul>\n";
    // ábrázoljuk az egyes gyerekeket, úgy hogy rekurzívan
    // végigmegyünk rajtuk
    for($i=1; $i<$c; $i++) {
      $output.=makeUL(&$arr[$i], $arr[0]['id'], false);
    }
    $output.="</ul>\n";
  }
  if(!$first) {
    $output.="</li>\n";
  }
  return $output;
}
$mytable=new Table('my_table');
$myarray=$mytable->retrieve2array(1);
// ha meg akarjuk mutatni a gyökér elemet is, akkor ki kell rakni
// az UL elemeket
echo("<ul>\n");
echo(makeUL($myarray, 1, false));
echo("</ul>\n");
?>
33

Elírás

Bártházi András · 2005. Aug. 28. (V), 16.38
Még egy elírás. A helyes kód:
$mytable=new Tree('my_table');
Azaz nem Table, hanem Tree.

-boogie-
37

Elírás

Anonymous · 2005. Aug. 28. (V), 18.10
Elirás, de a szerző részéről. Nézd meg, így van az oldalon is!!
39

Igen

Bártházi András · 2005. Aug. 29. (H), 06.51
Valóban ez egy elírás a szerző részéről (nem írtam, hogy kié, illetve rövidesen javítjuk is majd mindet, ami fel lett fedezve), de ezek olyan hibák, amiket nem nehéz felfedezni.

-boogie-
32

Még 2 hiba

Anonymous · 2005. Aug. 28. (V), 16.33
Most már csak ez a két hiba van:

Warning: Call-time pass-by-reference has been deprecated - argument passed by value; If you would like to pass it by reference, modify the declaration of count(). If you would like to enable call-time pass-by-reference, you can set allow_call_time_pass_reference to true in your INI file. However, future versions may not support this any longer. in c:\appserv\www\tree\index.php on line 13

Warning: Call-time pass-by-reference has been deprecated - argument passed by value; If you would like to pass it by reference, modify the declaration of [runtime function name](). If you would like to enable call-time pass-by-reference, you can set allow_call_time_pass_reference to true in your INI file. However, future versions may not support this any longer. in c:\appserv\www\tree\index.php on line 20

A 13. sor: $c=count(&$arr);
A 20. sor: $output.=makeUL(&$arr[$i], $arr[0]['id'], false);
A 20. sorban nyilván azért jelez, mert ott ismét meghívja azt a függvényt, amiben a 13. sor van.
34

<Nincs cím>

Anonymous · 2005. Aug. 28. (V), 16.39
Most már ez a kettő is "elmúlt".
Kipróbáltam a másik listázást.
Csak az a baj, hogy a szerző néhol máshogy hivatkozik a dolgokra.
Pl:
-Tree helyett Table
-my_table helyett menu
-name helyett title
Nagyjából ennyi
35

PHP verzió?

Bártházi András · 2005. Aug. 28. (V), 16.39
Nem válaszoltál rá, hogy hányas verziójú PHP-t használsz.

-boogie-
36

<Nincs cím>

Anonymous · 2005. Aug. 28. (V), 18.02
4.3.11
38

allow_call_time_pass_reference = Off

Hodicska Gergely · 2005. Aug. 28. (V), 18.26
Szia!


A Te rendszeredben a fenti beállítás van, míg Poetro-nál nem. Szóval nem konkrétan hibáról van szó, bár az alkalmazott technika már régóta depricated.

A megoldás, hogy a függvények definíciójában kell jelezni, hogy az adott változót referenciaként várja, nem pedig híváskor jelezzük ezt.


Felhő
40

kiegészítés

heal · 2007. Feb. 22. (Cs), 11.13
Üdv,

Először is megköszönném a cikk írójának, hogy időt és energiát fordított arra, hogy ezt az egészet összeállítsa nekünk, nekem nagyon sokat segít a munkámban.

Másodszor pedig kérnék valamit a tőlem okosabbaktól :)
Van egy getDepths() funkció, ami a fő ágtól számítva kéri le az összes ág mélységét. Ezt szerettem volna módosítani úgy, hogy a megadott ágtól kérje csak le a mélységeket, mert fölöslegesen sok adatot ad vissza, ha a root-tól kéri le. Nekem gyakran csak az egyik al-ág mélységeire van szükségem.
Leírnátok, hogy hogyan módosíthatom a cél érdekében ezt a funkciót?

Köszi!
Üdv
54

Áthelyezés Bug

Habib · 2009. Dec. 18. (P), 23.14
Arra figyeljetek, hogy az áthelyezés eredetileg közölt kódja kicsit bugos, azaz nem tud olyan áthelyezést megvalósítani, amikor az elemet egy őséhez helyezzük át. Az eredeti algoritmus a következő:
1) Elem adatainak lekérése.
2) Elem leendő szülőjének a megtalálása, adatainak mentése.
3) Elem törlése a fából, amikor is a jobb és bal számok változnak, az ősöknél mindenképpen.
4) Elem beszúrása az eredetileg eltárolt szülő adatokhoz, amik azóta megváltozhattak a harmadik lépésben.

A rossz sorrend: Új szülő adatok mentése, törlés, beszúrás:

// leendő szülő számítása
$result = $this->_db->query('SELECT lft, rgt FROM '.$this->_name.' WHERE id='.$pid);
$row = $result->fetch(Zend_Db::FETCH_ASSOC);
                        
// törlés, ami megváltoztathatja a leendő szülő adatait
$result = $this->_db->query('UPDATE '.$this->_name.' SET rgt=rgt-2 WHERE rgt>'.($orgt-1));
$result = $this->_db->query('UPDATE '.$this->_name.' SET lft=lft-2 WHERE lft>'.($orgt-1));
                        
// beszúrás az eredetileg számított értékekkel
$result = $this->_db->query('UPDATE '.$this->_name.' SET rgt=rgt+2 WHERE rgt>'.($row['rgt']-1));
$result = $this->_db->query('UPDATE '.$this->_name.' SET lft=lft+2 WHERE lft>'.($row['rgt']-1));
$result = $this->_db->query('UPDATE '.$this->_name.' SET lft='.$row['rgt'].', rgt='.($row['rgt']+1).' WHERE id='.$id);
Javított sorrend: (Törlés, új szülő adatok számítása, beszúrás)

// törlés
$result = $this->_db->query('UPDATE '.$this->_name.' SET rgt=rgt-2 WHERE rgt>'.($orgt-1));
$result = $this->_db->query('UPDATE '.$this->_name.' SET lft=lft-2 WHERE lft>'.($orgt-1));
         
//új szülő lekérése               
$result = $this->_db->query('SELECT lft, rgt FROM '.$this->_name.' WHERE id='.$pid);
$row = $result->fetch(Zend_Db::FETCH_ASSOC);
                   
//beszúrás     
$result = $this->_db->query('UPDATE '.$this->_name.' SET rgt=rgt+2 WHERE rgt>'.($row['rgt']-1));
$result = $this->_db->query('UPDATE '.$this->_name.' SET lft=lft+2 WHERE lft>'.($row['rgt']-1));
$result = $this->_db->query('UPDATE '.$this->_name.' SET lft='.$row['rgt'].', rgt='.($row['rgt']+1).' WHERE id='.$id);