Artykuł

sxc.hu sxc.hu
lip 01 2012
0

Chmura tagów w PHP i MySQL

Na sukces określonej strony WWW składa się wiele czynników. Z pewnością większość z Was zapytanych z miejsca odpowie, że będzie to dobry design witryny oraz ciekawa i unikatowa treść. Niewątpliwie jest w tym wiele prawdy, ale jak pokazuje życie wskazane elementy to tylko cząstka sukcesu.

Dobry webmaster oprócz tego, że musi trafić w zmysł estetyczny i intelektualny swoich czytelników, powinien mieć w sobie coś z marketingowca i socjologa. Marketing rzecz znana i raczej nie wymaga wielkich wyjaśnień. Ot po prostu każdy z nas powinien umieć sprzedać swój produkt i dopieszczać klientów/użytkowników swojej witryny.

Kwestia socjologiczna, to aspekt który dopiero od niedawna przykuwa szerszą uwagę webmasterów. Socjologia zajmuje się badaniem zachowań ludzkich, bądź też przewidywaniem ich reakcji na pewne zjawiska. Zestawienie tej dziedziny nauki z procesem tworzenia strony WWW może wydawać się z pozoru bardzo abstrakcyjne, ale jeśli spojrzeć na to z szerszej perspektywy... to łatwo zdać sobie sprawę, że pewne zachowania użytkowników można przekuć w sukces naszej witryny, stawiając choćby na dobrą i czytelną nawigację, która cały czas będzie serwować czytelnikowi treści które mogą go zainteresować w obrębie serwisu. W ten oto sposób zmierzamy powoli do sedna tego tekstu, czyli praktycznej rozprawy nad nawigacją w witrynie, którą w wymierny sposób możemy wesprzeć za pomocą tytułowej chmury tagów.

Nawigacja kluczem do sukcesu

Jakiś czas temu, popełniłem wpis, w którym starałem się przekazać Wam moją wizje doboru kategorii i tagów na stronę WWW. Przedstawiłem wtedy swoje stanowisko, które sprowadzało się do tworzenia kilku kategorii tematycznych oraz kilkunastu/kilkudziesięciu tagów określających najpopularniejsze zagadnienia poruszane w obrębie kategorii.

Dla przykładu, na stronie mam kategorie Webmastering i Programowanie, których przeznaczenie jest raczej oczywiste. Mam też różne tagi, które mogą się pojawić w takich kategoriach z mniejszą częstotliwością np. SQL, czy PHP.

Problem jaki się w tym momencie pojawia, to odpowiednia ekspozycja tych treści. W przypadku kategorii sprawa jest dość prosta. Z reguły jest ich powiedzmy do 10 i wyświetla się je w pasku bocznym, lub nawet bezpośrednio pod logo. Tagów bywa znacznie więcej, a ich ekspozycja jest utrudniona. W tym miejscu właśnie przychodzi z pomocą chmura tagów, która służy do wyświetlenia pozycji, które pojawiają się najczęściej.

Warto również wspomnieć o istotnych korzyściach jakie przynosi ten mechanizm, a te zasadniczo są dwie. Zastosowanie chmury tagów w wymierny sposób przyspiesza indeksowanie witryny. Tagi są lepiej eksponowane i łatwiej do nich dotrzeć również mechanizmom wyszukiwarek. Druga istotna zaleta to relatywna szansa na obniżenie wskaźnika odrzuceń w witrynie.

Implementacja

Do samej implementacji przydatna będzie podstawowa znajomość PHP i MySQL. W mniejszym stopniu istotna okaże się również CSS oraz HTML.

Kluczowym elementem będzie niewątpliwie baza danych, przechowująca powiązania między postami i tagami. Bardzo ważny będzie również skrypt PHP, który najpierw pobierze odpowiednie tagi z bazy danych, a później przypisze im stosowne klasy CSS w zależności od częstotliwości ich wystąpienia.

Baza danych

Aby móc zrealizować naszą chmurę tagów, potrzebować będziemy trzech tabel:

  • Post
  • Tag
  • PostTag

Pierwsza z nich, przechowuje informacje o poszczególnych postach. W tym przypadku skróciłem ją do minimum i zawiera tylko id, tytuł oraz treść. W rzeczywistości tabele tego typu niosą ze sobą znacznie więcej informacji.

Druga z tabel przechowuje informacje na temat poszczególnych tagów. Każdy z tagów może w teorii wystąpić wielokrotnie, ale wpis w tej tabeli będzie mieć tylko jeden.

W trzeciej i ostatniej tabeli, przechowujemy powiązania pomiędzy wpisami i tagami. Generalnie idea jest taka, że każdy wpis może mieć dowolną ilość tagów, a każdy tag może być podpięty do dowolnej ilości wpisów. Stąd też musimy utworzyć relację pośredniczą tego typu, w której wiązać będziemy pary post-tag. Z punktu widzenia dalszej implementacji jest to kluczowa tabela naszego systemu.

Poniżej możecie zapoznać się z kodem pozwalającym na utworzenie wszystkich wymienionych wyżej tabel:

CREATE TABLE Post (
  PostId CHAR(36) NOT NULL,
  Title VARCHAR(150) NOT NULL,
  Content TEXT NOT NULL,
  PRIMARY KEY(PostId)
);

CREATE TABLE Tag (
  TagId CHAR(36) NOT NULL,
  TagName VARCHAR(50) NOT NULL,
  TagDisplayName VARCHAR(50) NOT NULL,
  PRIMARY KEY(TagId)
);

CREATE TABLE PostTag (
  PostTagId CHAR(36) NOT NULL,
  PostId CHAR(36) NOT NULL,
  TagId CHAR(36) NOT NULL,
  PRIMARY KEY(PostTagId)
);

Klasa danych w PHP

Kolejny krok, to już pierwszy etap implementacji w PHP. Na początek potrzebować będziemy klasy, która przechowywać będzie dane pojedynczego elementu naszej chmury tagów. W tym celu zdefiniujemy klasę CTagCloudItem, która zawierać będzie następujące elementy:

  • Id tagu
  • Nazwę tagu
  • Nazwę wyświetlaną
  • Liczbę tagów
  • Klasę CSS tagu

Wszystkie elementy poza klasą CSS będziemy w stanie pobrać z bazy danych, dlatego też przypiszemy je już od razu w konstruktorze, a później pobierzemy za pomocą właściwości (funkcja __get). Nazwę klasy CSS dodamy do wybranej instancji klasy w trakcie przetwarzania.

<?php
class CTagCloudItem
{
	private $m_sTagId = '';
	private $m_sTagName = '';
	private $m_sTagDisplayName = '';
	private $m_nTagsCount = 0;
	private $m_sTagsClass = '';

	public function __construct($sTagId, $sTagName, $sTagDisplayName,
		$nTagsCount)
	{
		$this->m_sTagId = $sTagId;
		$this->m_sTagName = $sTagName;
		$this->m_sTagDisplayName = $sTagDisplayName;
		$this->m_nTagsCount = $nTagsCount;
	}

	public function __get($sVariableName)   
	{  
		return($this->$sVariableName);  
	}  

	public function SetTagsClass($sTagsClass)
	{
		$this->m_sTagsClass = $sTagsClass;
	}	
}
?>

Menadżer bazy danych

W kolejnym kroku musimy stworzyć menadżer bazy danych, który pobierać będzie dla nas potrzebne informacje. Pomijam tutaj oczywiście wszelkie metody związane z dodawaniem nowych postów i tagów do bazy danych, bo to jest raczej temat na inny wpis i nie jest to w tej chwili istotne.

Menadżer bazy danych będzie funkcjonować przy wykorzystaniu Mysqli. Na nasze potrzeby konieczne będzie również utworzenie jednej funkcji do pobierania tablicy obiektów klasy CTagCloudItem, którą przed chwilą zdefiniowaliśmy. Spójrzmy zatem na kod:

<?php
class CDbManager
{
	private $m_oMysqli = null;

	public function __construct()
	{
		$this->m_oMysqli = @new mysqli("host", "user", "pass", "database");
		if(mysqli_connect_errno())
		{			
			throw new Exception('Db error');
		}
	}
	
	public function GetTagsForCloud()
	{
		$aTags = array();
		$oResult = $this->m_oMysqli->query('SELECT * 
			FROM (
			    SELECT TagId, TagName, TagDisplayName, COUNT(TagId) AS TagsCount 
			    FROM PostTag
			    NATURAL JOIN Tag
			    GROUP BY TagId
			) AS TagsCountQuery
			ORDER BY TagsCount DESC
			LIMIT 25'
		);
		while($oTagItem = $oResult->fetch_object())
		{
			$aTags[] = new CTagCloudItem($oTagItem->TagId, $oTagItem->TagName, 
				$oTagItem->TagDisplayName, $oTagItem->TagsCount);
		}	
		return($aTags);			
	}	
}
?>

Kluczowa w tym przypadku jest funkcja GetTagsForCloud oraz zapytanie w niej zawarte. W naszym zapytaniu skorzystamy z podzapytania, dzięki któremu uzyskamy zgrupowane rezultaty z wyliczoną ilością powtórzeń wskazanego tagu. Ten rezultat będziemy mogli później posortować malejąco w zapytaniu głównym i ewentualnie ograniczyć liczebność pobieranych tagów.

Zwrócone elementy umieścimy w tablicy obiektów CTagCloudItem.

Połączmy wszystko razem

Udało się nam zasadniczo zbudować wszystkie elementy pośredniczące, które były niezbędne w głównym procesie pobierania danych. Teraz możemy przystąpić do właściwiej implementacji w PHP.

<?php
class CIndex
{
	private $m_oDb = null; 	

	public function __construct()
	{
		$this->m_oDb = new CDbManager();
	}	
	
	public function __destruct()
	{
		$this->m_oDb = null;
	}
	
	public function GetTagsCloudsArray()
	{
		$oTagsCloud = $this->m_oDb->GetTagsForCloud();
		if(sizeof($oTagsCloud) == 0)
		{
			return;
		}
		$nMaxValue = $oTagsCloud[0]->m_nTagsCount;
		$nMinValue = $oTagsCloud[sizeof($oTagsCloud) - 1]->m_nTagsCount;
		$nMaxDiffrenceValue = $nMaxValue - $nMinValue;
		foreach ($oTagsCloud as &$oItem) 
		{
			$nItemRange = (($oItem->m_nTagsCount - $nMinValue) * 100) 
				/ $nMaxDiffrenceValue ;
			if($nItemRange > 90)
			{
				$oItem->SetTagsClass('tag9');
			}
			else if($nItemRange > 80)
			{
				$oItem->SetTagsClass('tag8');
			}
			else if($nItemRange > 70)
			{
				$oItem->SetTagsClass('tag7');
			}
			else if($nItemRange > 60)
			{
				$oItem->SetTagsClass('tag6');
			}
			else if($nItemRange > 50)
			{
				$oItem->SetTagsClass('tag5');
			}
			else if($nItemRange > 40)
			{
				$oItem->SetTagsClass('tag4');
			}
			else if($nItemRange > 30)
			{
				$oItem->SetTagsClass('tag3');
			}
			else if($nItemRange > 20)
			{
				$oItem->SetTagsClass('tag2');
			}
			else if($nItemRange > 10)
			{
				$oItem->SetTagsClass('tag1');
			}
			else 
			{
				$oItem->SetTagsClass('tag0');
			}
		}
		usort($oTagsCloud, 'CompareTagCloudItem');
		return $oTagsCloud;
	}
	
	private function CompareTagCloudItem($oItemA, $oItemB)
	{
	    return strcasecmp($oItemA->m_sTagDisplayName, 
			$oItemB->m_sTagDisplayName);
	}
}
$oIndex = new CIndex();
$oIndex->GetTagsCloudsArray();
?>

Na potrzeby tego wpis, stworzyłem klasę CIndex, która pobierze tablicę tagów i odpowiednio ją przetworzy. Polem składowym tej klasy będzie tylko menadżer bazy danych (4), którego inicjujemy w konstruktorze (6-9). Kluczowym elementem klasy jest funkcja GetTagsCloudsArray, która pobiera z bazy danych tablicę odpowiednio posortowanych tagów (18), a następnie każdemu z tagów przydziela odpowiednią wagę, przy czym tag o największej liczebności otrzymuję wagę 100, a ten najmniej popularny 0. Aby łatwiej było przeprowadzić obliczenia, sprowadzamy najmniejszą wartość do zera. Różnicę powstałą w wyniku tej operacji odejmiemy później od każdego tagu w tablicy, dzięki czemu uzyskujemy nowy punkt odniesienia (23-25).

Kluczowym fragmentem kodu jest pętla foreach (26-70), w której każdemu z tagów nadajemy odpowiednią klasę. Istotne jest w tym celu wyliczenie wagi danego tagu, które realizujemy w liniach 28-29. Następnie za pomocą dłuuugiej pętli if przypisujemy odpowiednią klasę.

Chodź mechanizm z pozoru wygląda odrobinę skomplikowanie, w gruncie rzeczy działa całkiem prosto.

Spójrzmy na przykładowy zestaw tagów o wystąpieniach 5,11 i 16. Wartości zmiennych wewnątrz funkcji prezentować będą się następująco:

$nMaxValue = 16
$nMinValue = 5;
$nMaxDiffrenceValue = 11;

dla x = 5  -> $nItemRange = ((5-5) * 100) / 11 = 0;
dla x = 11 -> $nItemRange = ((11-5) * 100) / 11 = 54,55;
dla x = 16 -> $nItemRange = ((16-5) * 100) / 11 = 100;

Nietrudno zatem dokonać odpowiedniego przypisania klas. Oczywiście w tym przypadku zastosowałem skok co 10 punktów, nic nie stoi na przeszkodzie by zwiększyć, bądź też zmniejszyć liczbę zastosowanych klas, lub zmienić wartości poszczególnych przedziałów na bardziej nieregularne - może to być przydatne w sytuacji gdy większość wartości koncentruje się w pobliżu określonego przedziału. Manipulacji można dokonywać również za pomocą klas CSS, ale o tym kilka słów więcej dosłownie za momencik.

Po przetworzeniu elementów z tablicy, możemy ją uporządkować alfabetycznie według nazwy wyświetlanej tagu, co też czynimy przy pomocy funkcji usort, która korzysta ze specjalnie napisanej w tym celu funkcji CompareTagCloudItem.

Oprócz kodu PHP, powinniśmy również napisać odpowiednie reguły CSS dla każdej z wymienionych w konstrukcji if klas. W moim przypadku każda kolejna klasa zawiera rozmiar czcionki o jeden większy od poprzednika. Przy czym klasa tag0 stosowana jest do najmniejszego przedziału, a klasa tag9 do największego. Również w tym przypadku ilość klas i wielkości czcionek w nich użytych możecie dowolnie modyfikować (oczywiście w ten sposób by pasowały one do kodu PHP).

.tag0{
	font-size: 10px;	
}

.tag1{
	font-size: 11px;	
}

.tag2{
	font-size: 12px;	
}

.tag3{
	font-size: 13px;	
}

.tag4{
	font-size: 14px;	
}

.tag5{
	font-size: 15px;	
}

.tag6{
	font-size: 16px;	
}

.tag7{
	font-size: 17px;	
}

.tag8{
	font-size: 18px;	
}

.tag9{
	font-size: 19px;	
}

Wyświetlanie

Wyświetlanie pozostawię już Waszej indywidualnej kwestii, ponieważ w dużej mierze zależy od wykorzystywanego środowiska. Wszystkie dane zwracane są w uporządkowanej alfabetycznie tablicy powstałej w wyniku działania funkcji GetTagsCloudsArray, dlatego też wystarczy zastosować na niej foreach, by wyświetlić wszystkie tagi. Przykład wyświetlania podobnej konstrukcji możecie znaleźć w stopce Alt Control Delete.

Chciałbym zaznaczyć, że jest to tylko przykładowa implementacja chmury tagów, mająca swoje wady i zalety. W razie pytań zapraszam do komentarzy, bądź też do bezpośredniego kontaktu.

Data ostatniej modyfikacji: 09.06.2013, 19:29.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus