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
Uwaga! Zaprezentowane wyżej zapytanie jest przykładowe, nieoptymalne, a jedynie służące do testowania chmur tagów na małych, testowych bazach danych. Optymalna implementacja struktur tagów w bazie danych dla większych projektów została opisana we wpisie MySQL tags.
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.
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:
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 class="level_' . $iTag . '" href="' . $this->route('tag', $sTag) . '">' . $sTag . '</a>'; }
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.
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:
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:
…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:
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.
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:
1, 2 ... 6, 7, 8 ... 12, 13.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:
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('« poprzednia', 'następna »'));
$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...
} ?>
Dla niektórych wygasanie sesji jest zabezpieczeniem (banki, etc.). Realizując jeden z projektów oczekiwałem od systemu tego, aby użytkownik nigdy nie gubił sesji, gdy ma otwarte okno w przeglądarce. Dlaczego? Może dodaje posta, być może uzupełnia dość obszerny tekst na stronie. Gdy klika zapisz, przerzuca go do strony logowania, a cały tekst zniknął za sprawą tego, że jego przeglądarka nie zapisuje wartości pól formularza. Skąd to znamy.
Jak użytkownik gubi sesję?
Rozwiązania:
Rozmyślając nad podtrzymaniem sesji, próbowałem znaleźć wszystkie metody oraz wybrać najlepszą. Wszystkie sprowadzają się do “odświeżenia” strony lub jej fragmentu tak, aby nasz silnik wykonał tylko potrzebne session_start(); czyli podtrzymanie aktywności sesji. Jest kilka mniej lub bardziej zadowalających sposobów:
Sposób 3 wydaje mi się najlepszy. Można go ulepszyć w ten sposób, aby ramka nie wysyłała żądania zaraz po załadowaniu strony. Powodowałoby to podwójne requesty do serwera.
ping.php wygląda wówczas następująco: aplikacje nie używające frameworków ingerujących w standardowe działanie sesjisession_start(); header('Refresh: 60');Zapewne znajdą się osoby, które powiedzą: a co z użytkownikami, którzy mają wyłączone ramki, lub ich przeglądarki w ogóle ich nie obsługują. Zapytam wówczas: a co z użytkownikami, którzy nie akceptują cisteczek (wówczas sesje nie są dla nich użyteczne, chyba, że użyjemy przesłyki jej identyfikatora w adresie url). Dopytam również: a co z użytkownikami, którzy mają wyłączony Javascript? Patologiczne przepadki się po prostu pomija ;)
Przeglądając forum.php.pl często widziałem, jak użytkownicy przechowują hasła w swoich bazach danych. Najczęściej używają funkcji hashujących md5, sha1 i sha2. Wszystko wygląda bardzo dobrze, hasła są przechowywanie bezpiecznie. No właśnie… na ile bezpiecznie.
Nie będę tutaj rozwodził się nad zabezpieczeniem baz danych, w których owa baza haseł się znajduje, ale nad samym zahashowanym ciągu. Wszyscy doskonale wiemy, że istnieją bazy md5 (sha1, sha2 również).
Przezorny zawsze ubezpieczony. Wiadomo, że nigdy nic nie wiadomo.
Pokażę, jak dodatkowo zabezpieczyć nasze hasła. Będą przechowywane w tej samej bazie danych, używając tych samych metod hashowania, a jednak szansa na “złamanie” hasła (wyszukania w bazie) będzie niemożliwa. Posłużymy się ciągiem znaków zwanym przez programistów solą (salt). Przykład implementacji możemy znaleźć w forum IPB, natomiast phpBB pozbawione jest tego fjuczuru ficzera. Cała sprawa sprowadza się do wygenerowania dowolnego kawałka ciągu znaków i doklejenia go do hasła. Sól potrzebna nam będzie również przy porównaniu hasła, więc trzeba ją zapisać w bazie danych obok hasła.
Poniżej zamieszczam przykładową klasę, która obsługuje solenie haseł. Doklejanie soli może być napisane w dowolny sposób, zależy to od Waszej wyobraźni. Ja dodatkowo dodałem element “losowy” w postaci doklejenia do soli wyniku działania funkcji microtime().