Currently Browsing: Publikacje

jQuery form post ajax request

Już kiedyś interesowałem się jQuery, ale w związku z podjęciem się nowego, dużego projektu postanowiłem pogłębić swoją wiedzę i pobawić się tą biblioteką.

Jako, że klient ma bzika na punkcie przeładowań ajax postanowiłem zaimplementować automatyczne przesyłanie formularzy w tle i wklejanie w <body> response żądania. Od strony PHP sprawa wygląda bardzo prosto – jeżeli request został wysłany przez jQuery, wyświetlamy tylko to, co jest pomiędzy <body> i </body>. Po wysłaniu requesta, jQuery powinno wkleić wynik do body na naszej stronie.

Przykładu nie będę tłumaczył, bo wydaje mi się trywialny.

Dodatkowe ficzery:

  • disable submita do momentu, aż ajax nie otrzyma odpowiedzi (aby użytkownik nie mógł kliknąć drugi raz),
  • automatyczne wykrywanie adresu URL, do którego odnosi się formularz (jeżeli nie sprecyzowano atrybutu action, wówczas wysyłamy żądanie do window.location).

Aby nasz formularz zaczął przesyłać dane w tle wystarczy dołączyć plik jquery.autoload, a w kodzie napisać:

$(document).ready(function() {  InitAjaxPost(' ... SUBMIT_ID ... '); });

Ten sposób nie rozwiązuje przesyłania pól typu file.

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.

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...
} ?>

Podtrzymanie sesji

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ę?

  1. Jego ciastko wygasa, więc serwer nie może go zidentyfikować z sesją.
  2. Po jakimś czasie, choćby odtworzył ciastko, plik sesji znika z naszego serwera (garbage collection).

Rozwiązania:

  1. Wydłużenie czasu wygasania ciastka i sesji.
  2. Odświeżenie strony w interwale mniejszym, niż wynosi czas wygasania sesji i ciastka.

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:

  1. Odświeżenie całej strony.
    To może spowodować, że dane wprowadzane przez użytkownika w formularzu zostaną utracone. Ponad to, jeżeli użytkownik czyta newsy, denerwującym może być fakt, że lista nagle zostanie przescrollowana do góry (prócz opery).
  2. Wysłanie requestu ajax w tle.
    Minusem jest to, że trzeba używać biblioteki ajax lub pisać dodatkowy kod javascriptu. Jeżeli ktoś na stronie używa jakiegoś ajaxa – co za różnica. Poza tym same plusy.
  3. Odświeżanie ukrytej ramki iframe lub elementu frameset.
    Minusów usablity prawie brak. Brak potrzeby instalacji javascriptów i ajaxa. Odświeżacz powinien wysłać nagłówek Refresh lub odpowiedni metatag.

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.

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 ;)

Hashowanie haseł z solą

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().

Next Entries »