PHP Menüstruktur umsetzen

CPU

Lieutenant
Registriert
Jan. 2006
Beiträge
704
Guten Morgen!

Ein Freund hat mich um Hilfe gegbeten, und ich habe versucht Ihm zu helfen, doch ich schaffe es einfach nicht ... - doch versprochen ist versprochen!

Hier das Problem:
Es geht darum, eine Menüstruktur, die in einer Datenbank vorliegt in eine Baumstruktur in HTML umzusetzen.
Man hat beispielsweise folgenden Datenbankeintrag:

Code:
id | name   | is_folder   | parent
1  | S1     | 1           | 0
2  | SS1    | 1           | 1
3  | SSS3   | 0           | 2
4  | S2     | 0           | 0

"is_folder" bedeutet, dass es unterhalb dieser Seite noch eine Seite gibt
"parent" gibt die "id" des Elterndokuments an. Ist die "id" null, so liegt das Dokument in der root-Ebene.

Mein Problem ist nun, dass ich es nicht hinbekomme, eine Funktion zu schreiben, die mir das in folgenden HTML-Quellcode umwandelt:
HTML:
<ul>
	<li><a href="">S1</a>
		<ul>
			<li><a href="">SS1</a>
				<ul>
					<li><a href="">SSS1</a></li>
				</ul>
			</li>
		</ul>
	</li>
	<li><a href="">S2</a></li>
</ul>

Könnte mir vielleicht jemand helfen? Ich bin im Moment ziemlich hilflos ... :(
CPU
 
PHP:
lese alle datensaetze aus
funktion darstellen(ordner = 0)
 gehe alle elemente durch
 wenn element ein verzeichnis ist
   darstellen(ordner_id)
 sonst
   element ausgeben
 
Danke! :)

Diesen PHP-Code habe ich daraus erstellt:
PHP:
$all = mysql_query("SELECT * FROM sites;");

function darstellen($ordner = 0) {
	global $all;
	
	while($erg = mysql_fetch_array($all)) {
		if($erg['s_is_folder'] == 1) {
			darstellen($erg['s_id']);
		} else {
			echo $erg['s_menu_name'];
			
		}
	}
}

Die Ausgabe:
HTML:
SeiteSub1SeiteSubSub1Seite2

oder sortiert ...

SeiteSub1
SeiteSubSub1
Seite2

Es ist schon mal ein Anfang, aber irgendwie fehlt die Benutzung der Variable "$ordner" und es muss noch der HTML-Quellcode eingebunden werden.

Ich habe mich vorhin auch selbst an rekursiven Funktionen versucht, doch erfolglos!

CPU
 
Folgendes sollte funktionieren, aber keine Garantie da nicht getestet :P

Code:
funktion darstellen (id = 0)
    print "<ul>"
    Für alle Einträge mit parent == id
        Falls Eintrag ein Ordner ist
            print "<li>" + Name des Eintrags
            darstellen (id des Eintrags)
            print "</li>"
         sonst
              print "<li>" + Name des Eintrags + "</li>
    print "</ul>
 
In PHP:
PHP:
$all = mysql_query("SELECT * FROM sites;");

function darstellen($ordner = 0) {
	global $all;
	echo "<ul>";
	
	while($erg = mysql_fetch_array($all)) {
		if($erg['s_is_folder'] == 1) {
			echo "<li>";
			echo $erg['s_menu_name'];
			darstellen($erg['s_id']);
			echo "</li>";
		} else {
			if($erg['s_parent'] == 0) {
				echo "</ul>";
				
			}
			echo "<li>" . $erg['s_menu_name'] . "</li>";
		}
	}
	echo "</ul>";
}

Ergebnis:
HTML:
<ul>
<li>
Seite1
<ul>
<li>SeiteSub1</li>
<li>SeiteSubSub1</li>
</ul>
</li>
<li>Seite2</li>
</ul>

Das funktioniert schon ganz gut, bis auf das Einrücken des 3. Unterelements! Wie kann ich das außerdem noch auf einer Variable speichern? z.B. funktionsintern auf "$Result".

Könnte mir vielleicht jemand mal einen Pseudocode aufschrieben, wie das ganze laufen würde, wenn man festlegt, dass es nur 3 Unterebenen geben kann und das man das nicht mit Rekursion macht, sondern mit for bzw. while-Schleifen?

Danke, vorab!
CPU
 
Dein PHP code entspricht nicht meinem Pseudocode, daher klappt's auch nicht mit dem 3. Unterelement.
 
Also Rekursion ist und bleibt an dieser Stelle einfach das Beste was man machen kann. Hier wird bei jedem Datensatz gefragt, ob es Child Elemente gibt und dann nochmals der Menüaufbau angefragt mit dem neuen Parent.

Eine andere Variante ist, beim Durchlaufen für jeden Datensatz abzufragen auf welcher Ebene er sich befindet und entsprechend den HTML-Code zu setzen, z.B. bei Ebene 3, 3 Leerzeichen zum einrücken.
Die Ebene kann man sich auch direkt über SQL direkt holen:

select node.name, (count(parent.name)-1) as ebene
from tabelle as node, tabelle as parent
where node.parentid = parent.id
group by node.name
order by node.id;
 
Was haltet Ihr davon:
PHP:
$all = mysql_query("SELECT * FROM sites;");

function darstellen($id = 0) {
		global $all;
    echo "<ul>";
    
    while($erg = mysql_fetch_array($all)) {
        if($erg['s_parent'] == $id) {
					if($erg['s_is_folder']) {
							echo "<li>" . erg['s_menu_name'];
							darstellen(erg['s_id']);
							echo "</li>";
					} else {
						  echo "<li>" . erg['s_menu_name'] . "</li>";
					}
        }
    }
    echo "</ul>";
}

Entspricht das dem Pseudocode?
CPU :(
 
Das sieht schon besser aus, und du hast es ja bestimmt auch schon getestet um zu sehen ob es wie gewünscht funktioniert. Allerdings würde ich statt "if($erg['s_parent'] == $id) { " eher "WHERE parent = $id" in der SQL Abfrage verwenden.
 
Hier nochmals eine verbesserte Variante:
PHP:
function darstellen($id = 0) {
	echo "<ul>";

	$all = mysql_query("SELECT * FROM sites WHERE s_parent = "  .  $id . ";");
	while($erg = mysql_fetch_array($all)) {

		if($erg['s_is_folder'] == 1) {
			echo "<li>" . $erg['s_menu_name'];
			darstellen($erg['s_id']);
			echo "</li>";
		} else {
			echo "<li>" . $erg['s_menu_name'] . "</li>";
		}

	}
	echo "</ul>";
}

doch irgendwie funktioniert diese Funktion nicht ganz, denn der HTML-Quellcode sieht so aus:
HTML:
<ul>
	<li>Seite1
	<ul>
		<li>SeiteSub1</li>
	</ul>
	</li>
	<li>Seite2</li>
</ul>

CPU :(
 
Zuletzt bearbeitet: (RF)
Hmm, die Funktion sieht zumindest auf den ersten Blick richtig aus. Stimmt der Inhalt der Datenbank mit deinem Beispiel aus dem ersten Post überein?
 
Tja, so schnell schleicht sich ein Fehler ein ... Ich hatte bei der Seite mit der id "2" vergessen den Wert "is_folder" auf 1 zu setzen.

Es klappt - prima! :):)

Wie bekomme ich das Ganze nun noch so umgebaut, dass alles über return als String zurückgegeben wird?
 
Um alles in einem String zu haben hängst du eben alles was du jetzt per echo ausgibst an einen String an und gibst den dann per return zurück. Das Ergebnis der rekursiven Aufrufe solltest du natürlich auch jeweils anhängen.

Als kleine Übung und zur Verschönerung könntest du auch noch einen Zähler für dir Rekursionstiefe einbauen und die Ausgabe entsprechend einrücken.
 
Dankeschön! :)
Es klappt alles wunderbar.

CPU
 
Leider muss ich die ganze Geschichte nochmals aufrollen ... ich hab da noch ne Frage :D

Der Datenbankeintrag (ganz oben) würde ja ungefähr folgendermaßen dargestellt:
Code:
+S1
  +- S1
  +-- SS1
  +--- SSS1
+S2

Wie kann ich nun das Ganze umdrehen (sodass S2 vor S1 angezeigt wird)? z.B. Es hat sich ein Nutzer vertan und erst S1 mit allen Unterpunkten angelegt. Diesem fällt nun aber ein, dass S2 vor S1 kommen muss?

Ich habe schon ein Paar Varianten ausprobiert, doch die sind immer fehlgeschlagen ...

CPU
 
id von S2 und S1 in der Datenbank vertauschen (und natürlich in allen Abfragen nach id sortieren) ;)

Edit: Dabei musst du offensichtlich auch alle parent Einträge der direkten Unterpunkte anpassen und is_folder entsprechend setzen.
 
Zuletzt bearbeitet:
Code:
id | name   | is_folder   | parent
1  | S1     | 1           | 0
2  | SS1    | 1           | 1
3  | SSS3   | 0           | 2
4  | S2     | 0           | 0

Also, d.h. ich muss die id's tauschen: 4=>1 1=>4? Dabei habe ich schon mein erstes Problem, denn die Spalte "id" ist Primärschlüssel und sie wird automatisch gesetzt. Wenn ich nun "4=>1 1=>4" versuche durchzuführen, gibt es einen Fehler! Man müsste es als folgendermaßen machen: 4=> z.B. 100 1=>4 100=>1 - Doch dann wird bei der nächsten eingefügten Zeile die id auf 101, 102, ... gesetzt ...
Außerdem müsste ich ja dann die Seiten der Id's 2 und 3 anpassen, oder müsste ich alle id's ändern - also praktisch neu sortieren??

Kann mir jemand vielleicht ein Paar Zeilen Pseudocode geben?
CPU :(
 
Ich würde da vorschlagen einen Sortierungswert zu erstellen.

Code:
id | name    | is_folder   | parent | sort
1  | S1      | 1           | 0      | 1
2  | SS1     | 1           | 1      | 1
3  | SSS3    | 0           | 2      | 1
4  | S2      | 0           | 0      | 2

So kannst du jedes Element auf der gleichen Ebene wunderbar frei sortieren.
 
Hat jemand vielleicht ein Stück angepassten Pseudocode? :(
CPU
 
Zurück
Oben