Chmura tagów Tagcloud w PHP

Mar 07

Chmura tagów Tagcloud w PHP

Ostatnio byłem zobligowany napisać nową klasę tagów do mojego projektu Sypacz.pl, która de facto zachowała stare API, lecz rozszerzyła swoje funkcjonalności, więc w kodzie projektu nie było wielu zmian. Wena spowodowała to, że zacząłem pisać kod od zera.

Cały problem polega na tym, aby napisać na tyle elastyczną klasę tagów, która przyjmie nam zestaw danych, a następnie zaprezentować ją w formie chmury, czym zaopiekuje się arkusz stylów CSS:

Nazwa tagu => Ilość występowań

Parę osób mnie pytało, jak wyciągnąć takie informacje z bazy danych:

SELECT tag_name, COUNT(tag_name) AS tag_times FROM tags GROUP BY tag_name ORDER BY tag_times LIMIT 1, 50

Wykorzystałem obiekt Vframe_Attribute, aby ustandaryzować komponent względem pozostałych w moim frameworku. Jeżeli ktoś nie chce używać obiektu Attribute, może w prosty sposób przekształcić klasę tagów, otrzymując ten sam efekt, deklarując tylko atrybut chroniony protected $_aAttributes = array();. Temat chmury tagów wydaje mi się na tyle trywialny, że nie ma się co nad nim zbyt wiele rozwodzić, zamieszczę tylko klasę i opiszę krótko w przykładach jej możliwości.

  • Vframe_Tagcloud – klasa tagów,
  • Vframe_Attribute – pomocnicza klasa atrybutów dla stosu $_aAttributes, dziedziczenie można usunąć i zadeklarować atrybut samemu.

Aby stworzyć nowy obiekt tagów, po prostu wywołujemy konstruktor:

$oCloud = new Vframe_Tagcloud();

Konstruktor nie przyjmuje żadnych argumentów, wiec możemy od razu przejść do podawania obiektowi tagów. W tym miejscu warto nadmienić, że każdy znak jest rozróżniany (ze względów elastycznych), więc jeżeli chcesz, aby Nazwatagu oraz nazwatagu były rozpoznawane jako jeden klucz, wypadałoby użyć funkcji strtolower lub mb_strtolower (dla Multibyte Strings):

foreach($aDataTags as $iKey => $aRow)
  $oCloud->add(strtolower($aRow['tag_name']), $aRow['tag_times']);

Najistotniejszą częścią klasy jest sposób renderowania chmury, które może działać w dwóch trybach:

  • Tryb prosty zwraca nam nazwę tagu oraz jego wagę po przeliczeniu w formie liczby.
  • Tryb zaawansowany zwraca nam nazwę tagu oraz tablicę z danymi:
    • level – waga tagu po przeliczeniu,
    • count – ilość występowań, taka jaką podaliśmy,
    • count_percentage – informacja, w jakiej procentowej części ilości występowań znajduje się tag, przyjmując za 100% tag, który występuje najczęściej.

Aby w prosty sposób wyrenderować chmurę tagów, używamy poniższego przykładu:

$aDataTagsRender = $oCloud->render();

Najczęściej używa się trybu prostego. Oba tryby są dalej rozbudowane, bowiem mamy możliwość zdefiniowania zakresu i dokładności wag tagów. Domyślnie wagi tagów zawierają się pomiędzy 1, a 10. Możemy na przykład przyjąć, że najmniejszą wagą jest liczba 3, największą 5, a precyzja wag tagów to 2 miejsca po przecinku:

$aDataTagsRender = $oCloud->render(3, 5, 2);

W celu uniknięcia precyzji po przecinku (chcemy otrzymać liczby całkowite), ustawimy precyzję na 0.

Aby wywołać tryb zaawansowany, musimy podać 4 argument dla metody render() i ustawić go na true.

$aDataTagsRender = $oCloud->render(1, 5, 0, true);

Warto w tym miejscu nadmienić, że tagi mogą nie być posortowane alfabetycznie (co ma miejsce podczas tworzenia chmury tagów). Wystarczy wywołać funkcję ksort (key sort).

Finalny przykład używania klasy tagów, celem wywołania klas CSS level_X, gdzie X to liczba całkowita z zakresu od 1 do 10, resztę robi CSS (kolorowanie, nakładanie rozmiaru):

if(count($aDataTags)) {
 
$oCloud = new Vframe_Tagcloud();
 
foreach($aDataTags as $iKey => $aRow)
  $oCloud->add(strtolower($aRow['tag_name']), $aRow['tag_times']);
 
$aDataTags = $oCloud->render(1, 10, 0);
ksort($aDataTags);
 
foreach($aDataTags as $sTag => $iTag)
  echo '<a href="' . $this->route('tag', $sTag) . '" class="level_' . $iTag . '">' . $sTag . '</a>';
 
}
Read More

MVC – Model

Mar 09

MVC – Model

Powstało masę artykułów na temat MVC, temat staje się naprawdę oklepany. Postanowiłem zebrać wszystkie informacje w jedno miejsce i streścić je w jednym artykule uzupełniając go o informacje, które nabyłem z własnego doświadczenia oraz zwracając uwagę na najistotniejsze informacje.

Czym jest model

Model to jedna z warstw wzorca projektowego MVC, który odpowiada logikę biznesową, czyli pozyskiwanie oraz modelowanie danych pozyskanych ze źródła danych. Na samym wstępie brzmi to bardzo abstrakcyjnie. W myśl architektury MVC, dostęp do modelu powinien mieć tylko kontroler, a w żadnym wypadku widok. Dodatkowo model musi pobrać i modelować dane w taki sposób, aby można było go ewentualnie wymienić bez jakiejkolwiek ingerencji w kontroler, a co za tym idzie – widok. Niezależnie od tego, z jakiego źródła informacji korzysta (pliki tekstowe, bazy danych, pliki XML)  kontroler powinien otrzymać maksymalnie zbliżone dane podczas wymiany źródła informacji.

mvc-model

Model != baza danych

Często spotykam się z definicją modelu jako źródłem połączenia i wykonywania zapytań do serwera bazy danych. Otóż nie jest to prawdą. Według ideologii MVC model powinien być jedynie pośrednikiem między warstwą aplikacji przeznaczoną do połączenia do bazy danych, wykonywania zapytań itp., a kontrolerem. Dodatkowo powinien pomóc kontrolerowi w zbudowaniu zapytania do źródła informacji (pobranie danych na podstawie kryteriów), zmodelować je i zwrócić. Dlaczego model nie jest połączeniem do bazy danych? Jeżeli model potraktujemy jako pośrednika między kontrolerem a źródłem danych, ma on prawo wybrać dowolny sposób uzyskania żądanych informacji. Wcale nie oznacza to, że model musi używać baz danych, ale może użyć plików XML lub API udostępniane przez konkretny serwis (np. YouTube)

Wymienialność modeli i modelowanie danych

Modelowanie informacji jest to dostosowanie ich do użytku przez kontroler. Zazwyczaj jest to przekazywanie informacji w postaci tablic, wartości logicznych, liczb i ciągów znaków. Przykładem może być pobieranie informacji z bazy danych. Kontroler de facto nie wie skąd są pobierane dane, wie to tylko model, otrzymuje suche informacje. Jak rozumieć modelowanie danych przy projektowaniu aplikacji? Wyobraźmy sobie sytuację, że zmieniamy źródło informacji z bazy danych na pliki XML. W tym przypadku kontroler powinien otrzymać rekordy danych jako tablica o tych samych kluczach i tych samych typach danych, jak miało to miejsce przy używaniu bazy danych. Wymiana modelu odbywa się bez ingerowania w kontroler.

Przykłady modeli

Najpopularniejszym sposobem pozyskania informacji jest połączenie do bazy danych i pobieranie (reprezentowanie) ich na różnoraki sposób. Doskonale wyjaśnia to tekst znajdujący się w wikipedii:

Frameworki MVC do operacji na bazach danych używają modeli i mapowania relacyjno-obiektowego, ORM (ang. object-relationship mapping) – w Railsach jest to ActiveRecord, w Catalyscie np. DBIx::Class, a framework Spring w Javie używa Hibernate. Zwykle jest też możliwe użycie baz danych przez bezpośrednie zapytania SQL. Użycie modeli upraszcza typowe operacje – wyświetlanie ze stronicowaniem, edycję danych, a także uniezależnia od konkretnego typu bazy danych.

Posiadam przykład od siebie. Źródłem danych jest API serwisu Last.fm:

Read More

Zintegrowane logowanie cms z forum

Feb 06

Zintegrowane logowanie cms z forum

Projektowałem wiele serwisów, które miały zintegrowane z forum komponenty takie jak:

  • rejestracja,
  • przypomnienie hasła,
  • zmiana hasła, nicku lub adresu email,
  • usunięcie konta.

Wówczas nie było żadnego problemu – wystarczyło wszystkie te akcje z forum przekierować na URL’e obsługiwane przez CMS, który zajmował się zmianami  tabelach forum. Dlaczego przekierować? Jeżeli ktoś rejestruje się w serwisie, jest zarejestrowany na forum, natomiast, gdy rejestruje się na forum, nie jest rejestrowany w serwisie. To CMS integrujemy z forum, a nie forum z CMS’em (chyba, że zamierzamy inaczej, wtedy na odwrót).

Ostatnio klient zażyczył sobie, żeby zintegrowane było również logowanie. Nie najlepiej widzi mi się implementacja systemu autoryzacji z forum w CMS’ie, więc poszedłem “na łatwiznę”, bowiem miałem do czynienia z phpBB. Do osiągnięcia celu postanowiłem wykonać dwa kroki:

  1. wysłać żądanie POST do forum na adres logowania z wypełnionymi polami POST z formularza logowania w CMS’ie,
  2. przechwycić wysłane przez forum ciasteczka i przekazać je użytkownikowi.

Do połączenia się z forum via http użyłem HttpRequest. Wyszło z tego parę linijek kodu.

Read More

Wzorzec projektowy Registry w PHP

Jan 17

Wzorzec projektowy Registry w PHP

Registry to wzorzec projektowy, który ma za zadanie przechowywać i udostępniać dane w obrębie aplikacji. Implementacja wzorca zastępuje globalny zasięg wartości zarejestrowanych w przestrzeni klasy. Różnicą globalnego zasięgu zmiennych oraz wartości ujętych w Registry jest to, że można je ściśle kontrolować (dostęp w aplikacji itd.). W tej publikacji przedstawię jedną z najprostszego wykorzystania wzorca Registry.

Głównym założeniem Registry jest globalny zasięg (pomijając już zabezpieczenia dostępowe, którymi się nie zajmujemy). Język PHP od wersji 5 obsługuje statyczne static wywołania metod klas, czyniąc tym samym ich globalny zasięg wraz z połączeniem ze słowem kluczowym public. Dla wygody – nie trzeba tworzyć instancji klasy, więc wywołanie jest bardzo proste i nie zajmuje dużo miejsca w naszym kodzie.

Podstawowymi funkcjonalnościami Registry będzie:

  • dodawanie i usuwanie,
  • pobieranie

…zmiennych z rejestru. Pojęcie zmienna jest bardzo względne. Przechowywać w rejestrze możemy praktycznie wszystkie typy zmiennych dostępnych w PHP, włączając w to typ resource (zasoby). Registry to nic innego, jak przechowywanie zmiennych w przestrzeni jednej klasy, więc nie mamy wobec tego żadnych ograniczeń.

Zajmiemy się teraz bardziej rozbudowanym przykładem wzorca. Wykonamy następujące operacje:

  1. Nowa klasa RegistryAdvenced będzie dziedziczyła z RegistrySimple na potrzeby metody Registry().
  2. Zaimplementujemy interfejsy:
  3. Dodamy prywatny konstruktor, skorzystamy ze wzorca Singleton.

Gotowy kod klasy RegistryAdvenced.

Interpretacja i rozwijanie swojego wzorca Registry jest szeroka, można na przykład zastosowac przestrzenie rejestrów, tj. tworzyć wiele rejestrów na podstawie ich instancji. Ile programistów, tyle pomysłów, ale zasada działania nie zmienia się. Zainteresowanych zapraszam do manuala, jak problem został rozwiązany w Zend_Registry.

Read More

Paginacja (stronicowanie) w PHP

Jan 04

Paginacja (stronicowanie) w PHP

Zaczynając swoją przygodę z PHP nie miałem pojęcia jak wykonać paginację newsów, która załamywałaby łańcuchy liczb w momencie, w których chce. Na dzień dzisiejszy postanowiłem napisać swój nowy pager, gdyż ten, który dotychczas używałem przez ostatnie 2 lata nie odpowiadał mi pod trzema względami:

  • nagromadzenie metod, tj. setLimit(), getLimit(), zamiast pojedynczego limit(),
  • brak możliwości ustawienia załamywania się łańcucha (domyślnie były to 2 liczby):
    1, 2 ... 6, 7, 8 ...  12, 13.
  • brak możliwości definiowania limitu, ilości elementów oraz aktualnie przeglądanej strony w jednej metodzie, najchętniej w samym konstruktorze.

Ostatnio potrzebowałem zwiększyć limit liczb “z przodu” i “tyłu” oraz “w środku”:
1, 2, 3 ... 5, 6, 7, 8, 9 ...  11, 12, 13

Dla tych, którzy ciągle szukają komponentu obsługującego paginację prezentuję Vframe_Pagination.

Implementacja od strony kontrolera (metoda krótka):

$oPager = new Vframe_Pagination($iItemsCount, $iLimit, $iCurrentPage);

Metoda długa:

$oPager = new Vframe_Pagination();
$oPager->limit($iLimit);
$oPager->items($iItemsCount);
$oPager->page($iPage);

Metody limit, items oraz page zwracają liczby odpowiadające ich nazwą niezależnie od tego, czy została podana nowa wartość w argumencie, czy nie, co jest absolutnie wygodnym (dla mnie) rozwiązaniem.

Aby wyświetlić oczekiwane rekordy, wykorzystujemy pagera:

$aData = $oModel->GetList($iUser, $oPager->start(), $oPager->limit());

Lub bezpośrednio w zapytaniu do bazy dancyh:

$sSql = "SELECT news_id FROM news LIMIT " . $oPager->start() . ", " . $oPager->limit();

Od strony widoku, prezentacja pagincaji prezentuje się w bardzo prosty sposób:

<?php echo $oPager->Render(true); ?>

Metoda render przyjmuje kolejno:

  1. $mMode (mixed: bool, null, string), jeżeli true, paginacja jest zwracana w formacie HTML. Jeżeli null, numerki stron podane w formie array’a zamiast linków HTML. Jeżeli string, zostaje zwracana wartość po renderowaniu pagera, np. element startujący przedział mnożony po limicie: $oPager->Render(‘start’);. Ten sam efekt powoduje metoda $oPager->start();
  2. $iDrawCutLimit (int) ilość numerków na początku i końcu łańcucha paginacji.
  3. $iDrawCutLimitCenter (int) ilość numerków “w środku” obok aktualnie przeglądanej strony.
  4. $bNavigation (bool) linki “poprzednia” i “następna” strona znajdujące się na początku i końcu łańcucha.

Możemy sami ostylować linki generowane przez pager. Wystarczy że w widoku dodamy swój apperance:

$this->oPager->PatternPage('<a href="?[$]">[$]</a>');
$this->oPager->PatternPageCurrent('<strong>[$]</strong>');
$this->oPager->PatternPageNavigation('<a href="?[$]" rel="nofollow">[$$]</a>', array('&laquo; poprzednia', 'następna &raquo;'));
$this->oPager->PatternSeparator('<span>...</span>');

Końcowy efekt, możemy nie wyświetlać pagera, gdy jest tylko jedna strona elementów:

<?php if($this->oPager->Render('pages') > 1) {
// wyswietl pager...
} ?>

Read More

Logowanie po nickname i email (usability)

Dec 28

Logowanie po nickname i email (usability)

Projektując serwisy zaczynam ostrożnie podchodzić do usability. Temat jest bardzo wrażliwy, każdy webmaster to inne zdanie. Dziś chciałbym przedstawić problem logowania do serwisu. Oprócz hasła, przy logowaniu używamy:

  • standardowo nazwy użytkownika,
  • w niektórych serwisach adresu email, rzadziej spotykane zjawisko.

W trosce o użytkowników w kilku moich serwisach zastosowałem możliwość logowania się na adres email lub nazwę użytkownika. Wszystko po to, aby ułatwić dostęp do ukrytej części witryny, aby nikt nie “zwątpił” bo nazwy użytkownika, lub adresu email, który podał przy zakładaniu profilu. Sam używam w sieci kilku adresów email oraz kilku prefixów i suffixów do nicka Athlan. Mam problem z zalogowaniem, gdy danie nie pasują, a potrzebuję dostęp tylko na chwilę.

Przygotujmy zatem tok myślenia programu, który pobierze dane, w zależności od tego, jakie dane podał użytkownik. Pomijam walidację hasła etc:

  1. Wykrycie, czy nick jest adresem email.
  2. Jeżeli tak, pobierz dane użytkownika po polu user_mail i zapisz je do zmiennej $aUser.
  3. Jeżeli nie, pobierz dane identyfikując rekord po kluczu user_name i zapisz pobrane dane do zmiennej $aUser.

Jak możemy zauważyć, w obu przypadkach dane zapisujemy do tej samej zmiennej $aUser, więc możemy je dalej tak samo wykorzystywać. Różni się tylko pobieranie, dlateg nie trzeba w żadnym wypadku powielać kodu.

Jak słusznie zauważył devnull, należy wykluczyć możliwość użycia znaku małpy w loginie przy rejestracji użytkownika. Wyjaśnienie znajdziecie w komentarzach.

Dla programisty nie jest wiele pracy, a warto ułatwić dostęp użytkownikowi do serwisu. Usability na pierwszym miejscu ;-)

Read More