29 Lipiec 2008

menu z podkategoriami…

Jakiś czas temu (blisko rok wstecz) zapragnąłem posiadać komentarze w formie drzewka (umieszczanie odpowiedzi na konkretny wpis hierarchicznie) czyli coś na zasadzie:

  • pierwszy wpis
    • odpowiedź na pierwszy wpis
      • odpowiedź do odpowiedzi z pierwszego wpisu
        • odpowiedź na odpowiedź odpowiedzi z pierwszego wpisu
    • inna odpowiedź na pierwszy wpis
  • drugi wpis
    • odpowiedź na drugi wpis

Zwie się to fachowo tablicą rekurencyjną, gdzie istnieją zależności na linii rodzic->dzieci. Na ówczesne potrzeby znalazłem jakieś gotowe rozwiązanie wykorzystujące dodatkowe pola (poziomy: lewa-prawa), i korzystam z tego do dziś. Wczoraj jednak zapragnąłem pozbyć się ręcznego definiowania podkategorii wiadomości i… rozpocząłem zabawę z wynalezieniem sensownego rozwiązania.

W tabeli utworzyłem dodatkowe pole zawierającą id kategorii, do której należy podkategoria. Jeśli kategoria nie należy do żadnej – przypisuję jej wartość 0 (zero). Teraz trzeba było odpowiednio zapętlić funkcję aby wywoływała samą siebie jeśli natrafi na podkategorię należącą do podkategorii, która jest podkategorią jeszcze innej podkategorii. Jak widać przesuwanie się na linii rodzic->dziecko może sięgać bardzo głęboko. Pierwszy z brzegu przykład zaczerpnięty z życia:

  • sport
    • piłka nożna
      • liga polska
        • ekstraklasa
          • puchar ekstraklasy
      • reprezentacja
      • puchar polski
    • siatkówka

Jak widać podkategorii może być sporo i drzewko odpowiednio się rozrośnie. Na swoje potrzeby napisałem cztery funkcje, ale tylko dwie z nich odgrywają kluczową rolę, dwie pozostałe odnoszą się jedynie do pobierania danych z bazy. Najważniejszą z tych dwóch – kluczowych – funkcji jest ta, która zapętla się wywołując samą siebie tak długo, dopóki istnieją w bazie podkategorie przypisane do kategorii, która jest wywoływana jako parametr tej funkcji. Prawda, że proste? Co robi ta druga funkcja? Przypisuje strukturę drzewka do zmiennej na podstawie danych, które otrzymuje z pozostałych funkcji (w tym od tej kluczowej – zapętlającej się). W efekcie zwracany jest wynik, który można przypisać do jakiejś zmiennej, którą można z powodzeniem wykorzystać w szablonie (np. Smarty).

Dlaczego na to rozwiązanie wpadłem dopiero teraz, a nie rok temu, kiedy zacząłem zabawę nad tablicami rekurencyjnymi?

Z bardzo prostego powodu. Zakładałem bowiem, że potrzebuję X zagłębień w strukturę drzewka, gdzie X było liczbą rzędu 4-6 powtórzeń funkcji. I kiedy tak bawiąc się dochodziłem do wyników powyżej określonego z początku (X>6), uznawałem, że moje zabawy z tym są beznadziejne, a efekty dalekie od zadowalających. Stąd wykorzystanie czyjegoś pomysłu z dwoma dodatkowymi polami (lewa-prawa) i zaprzestanie dalszych prac nad poszukiwaniem rozwiązania prostszego acz skuteczniejszego.

Po co to wszystko?

Pomijając fakt, że aktualnie może to być wykorzystane przy budowie rozwijanego menu, popełniłem to z nieco innego powodu. Chodziło mi o to, aby wchodząc np. na wiadomości sportowe wyświetlały się nie tylko te, które są przypisane wyłącznie do tej kategorii, ale także te, które znajdują się w hierarchicznie ułożonym drzewku podkategorii i obejmują piłkę nożną, siatkówkę, ligę polską, ekstraklasę, reprezentację itp. Uzyskane w ten sposób wiadomości można przypisać do zmiennej i wstawić w szablon, tam gdzie się tego zapragnie. To zadanie załatwia mi jedna funkcja, która wyciąga podkategorie danej kategorii, i osiągnięte w ten sposób wyniki przekazuję do funkcji, która wydobywa z bazy konkretne wiadomości.

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
function subcat( $cid ) {
	if ( $cid ){
		for($j=0;$j<count($cid);$j++){
			$q = 'SELECT * FROM tabela WHERE id_podkategorii = ''.(int)$cid.'';';
			$r = mysql_query($q) or die(mysql_error());
			if ( mysql_num_rows( $r ) > 0 ){
				while( $row = mysql_fetch_assoc( $r )) {
					$tabela[] = $row;
				}
			}
		}
		if ($tabela) {
			for($i=0;$i<count($tabela);$i++){
				$wyniki[] = $tabela[$i];
				$cos = subcat($tabela[$i]['id_podkategorii']);
				if ($cos) {
					for ($a=0;$a<count($cos);$a++){
						$wyniki[] = $cos[$a];
					}
				}
			}
		}
		return $wyniki;
	}
	return null;
}

Żeby jednak tak słodko nie było, wykorzystując krótkie adresy urli, gdzie nie pojawia się id kategorii, a jedynie nazwa, natknąłem się na inny problem dotyczący zawartości pliku .htaccess. Otóż aby działało wszystko prawidłowo link powinien wyglądać mniej więcej tak:

news/kategoria/podkategoria/podkategoria/podkategoria/podkategoria/tytul,id_artykulu

Oczywiście podkategorii może być znacznie więcej, ale może być i mniej. Pytanie tylko czy adres ów jest jeszcze faktycznie krótki, czy już zaliczany powinien być do tych z rodziny dłuższych? A co jeśli dojdą jeszcze kolejne strony artykułu? Pojawi się kolejna zmienna. Stąd pomysł na drastyczne skrócenie do postaci:

news/kategoria/ostatnia_podkategoria/tytul,id_artykulu

Teraz czas na przebudowanie skryptu wyciągającego wiadomości z bazy… Tak jak wcześniej pisałem, mimo iż zbliżam się do końca, co jakiś czas wyskakuje coś nowego. Tak jak w tym przypadku…