PHP Solutions 05 2007 PL

84

description

* Relacja z polskiej premiery C++Buildera 2007* MySQL* WordPress* Symfony Framework* Własny system statystyk* Operacje na tekście* Formularz internetowy* CakePHP* SciTE* jQuery* WordPress i punBB* Statystyki zewnętrzne* Witryny internetowe – Tylko dla zarobku?

Transcript of PHP Solutions 05 2007 PL

Page 1: PHP Solutions 05 2007 PL
Page 2: PHP Solutions 05 2007 PL
Page 3: PHP Solutions 05 2007 PL
Page 4: PHP Solutions 05 2007 PL

4 05/2007 5www.phpsolmag.org

06 AKTUALNOŚCIDaniel Ancuta

08 OPIS CD

10 TAM BYLIŚMYRelacja z polskiej premiery C++Buildera 2007

DLA POCZĄTKUJĄCYCH12 MySQLKrzysztof TrynkiewiczKrzysztof opisuje bazę danych – MySQL. Dzięki autorowi poznasz spo-soby wykonywania większości najpotrzebniejszych operacji na bazie danych MySQL. Po przeczytaniu tego artykułu dowiesz się wszystkie-go o instalacji, tworzeniu warstw i tabel, aktualizacji oraz optymaliza-cji danych MySQL.

22 WordPressKonrad GołuchowskiKonrad pokaże w jaki sposób zbudować własną skórkę do systemu WordPress. W przystępny sposób przedstawia jak zbudowane są sza-blony oraz jakie możliwości daje nam stworzenie własnej skórki. Za-prezentuje najczęściej używane funkcje i nauczy Cię wykorzystywać komentarze.

30 Symfony FrameworkŁukasz KlejnbergŁukasz pokaże Ci w przystępny sposób, jak zainstalować i skonfiguro-wać Symfony, a na przykładzie prostej strony WWW zapoznasz się z możliwościami tego frameworka. Między innymi dowiesz się jak wyko-nać rejestrację nowego użytkownika oraz w jaki sposób można spraw-dzić dane przesłane z formularza.

PHP Solutions jest wydawany przez Software-Wydawnictwo Sp. z o.o.

Dyrektor wydawniczy: Sylwia Pogroszewska

Redaktor naczelny: Patrycja Wądołowska [email protected]

Redaktorzy: Magdalena Sobiś [email protected] Anna Kozioł [email protected]

Korekta: Przemka Skudniewska [email protected]

Kierownik produkcji: Marta Kurpiewska [email protected]

Projekt okładki: Agnieszka Marchocka

Skład i łamanie: Robert Zadrożny [email protected]

Dział reklamy: [email protected]

Prenumerata: Marzena Dmowska [email protected]

Nakład: 6 000 egz.

SPIS TREŚCI

Page 5: PHP Solutions 05 2007 PL

4 05/2007 5www.phpsolmag.org

38 Własny system statystykŁukasz BorchmannŁukasz przedstawi Ci sposób na szybkie pobieranie informacji doty-czących gościa, jego oprogramowania oraz ustawień. Pokaże Ci, jak sprawdzić źródła ruchu, dokonać segregacji ze względu na typ (odno-śnik,wyszukiwarka, bezpośredni). Dowiesz się również, jak wyciągać słowa kluczowe oraz zbadać pozycję w wynikach organicznych wy-szukiwarek.

TECHNIKA42 Operacje na tekścieMichał GackiMichał opisuje podstawy wyświetlania tekstu w PHP oraz funkcje, któ-re umożliwiają zamianę pewnych fragmentów tekstu lub samych kon-kretnych znaków. Pokaże Ci, jak sprawdzić czy w tekście istnieje dany ciąg znaków według podanych kryteriów, zmienić kodowanie tekstu itp. Ponadto znajdziesz tu kilka ciekawostek związanych z tematem.

46 Formularz internetowyTomasz RoszkoTomek pomoże Ci w przystępny sposób stworzyć interfejs. Poznasz technologię Ajax w jednym z jej praktycznych zastosowań. Będziesz wiedział jak wysłać i sprawdzić formularz bez przeładowania strony.

50 CakePHPPiotr GapińskiPiotr opisuje różne techniki buforowania stron WWW. Jeśli tylko znasz Framework CakePHP, język SQL oraz potrafisz administrować bazę MySQL. Po przeczytaniu tekstu poznasz możliwości ich praktycznego zastosowania w CakePHP.

NARZĘDZIA

56 SciTERobert ZajdaRobert pokaże Ci, jak wykorzystać zaawansowane możliwości edy-tora SciTE oraz jak skonfigurować go do pracy z językiem PHP. Mu-sisz posiadać podstawową znajomość PHP oraz narzędzi do tworze-nia kodu.

60 jQuery Dariusz DuszyńskiDariusz opisuje bibliotekę javascript, która m.in. tworzy dynamiczne efekty graficzne na stronie internetowej oraz daje możliwość wykony-wania zapytań asynchronicznych.

66 WordPress i punBBPiotr MalińskiPiotr w pierwszym artykule z serii „Warsztat Programisty” przedsta-wi Ci sposoby integrowania ze sobą dwóch skryptów na przykładzie punBB i CMSa WordPress. Zaprezentuje Ci „klasyczny” sposób inte-gracji oraz integracje za pomocą wtyczki WordPress.

TESTY KONSUMENCKIE70 Statystyki zewnętrzne

75 RecenzjeŁukasz Skowroński, Sławomir Zadrożny

78 FelietonWojciech SapiechowskiWitryny internetowe – Tylko dla zarobku?

Adres korespondencyjny:Software-Wydawnictwo Sp. z o.o., ul. Bokserska 1, 02-682 Warszawa, Polskatel. +48 22 887 13 45, fax +48 22 887 10 11www.phpsolmag.org [email protected]

Dołączoną do magazynu płytę CD przetestowano programem AntiVirenKit firmy G DATA Software Sp. z o.o.

Redakcja dokłada wszelkich starań, by publikowane w piśmie i na towarzyszących mu nośnikach informacje i programy były poprawne, jednakże nie bierze odpowiedzialności za efekty wykorzystania ich; nie gwarantuje także poprawnego działania programów shareware, freeware i public domain.

Uszkodzone podczas wysyłki płyty wymienia redakcja.

Wszystkie znaki firmowe zawarte w piśmie są własności odpowiednich firm zostały użyte wyłącznie w celach informacyjnych.

Redakcja używa systemu automatycznego składu

Do tworzenia wykresów i diagramów wykorzystano program firmy

Osoby zainteresowane współpracą prosimy o kontakt:[email protected]

Druk: ArtDruk

Wysokość nakładu obejmuje również dodruki. Redakcja nie udziela pomocy technicznej w instalowaniu i użytkowaniu programów zamieszczonych na płycie CD-ROM dostarczonej razem z pismem.

Sprzedaż aktualnych lub archiwalnych numerów pisma po innej cenie niż wydrukowana na okładce – bez zgody wydawcy – jest działaniem na jego szkodę i skutkuje odpowiedzialnością sądową.

Pismo ukazuje się w następujących wersjach językowych:

polskiej , francuskiej

PRAKTYKA

Page 6: PHP Solutions 05 2007 PL

Aktualności

6 05/2007

Aktualności

7www.phpsolmag.org

Nowa wersja PHP-GTK: 2 BetaDeveloperzy PHP-GTK poinformowali na blogu o wydaniu nowej wersji beta tego rozszerzenia do PHP. Oto kilka z nowości:

• Wynik pokrycia kodu API przez testy to teraz 90 procent, z ponad 95-procen-towym pokryciem tylko dla Gtk+ APIl;

• GtkTreeView widget został zauważalnie rozbudowany dzięki własnemu dostoso-walnemu modelowi i wsparciu dla znane-go nam systemu „przeciągnij i upuść”;

• Bardzo ciekawe nowe cross-platformo-we widgety już dostępne poprzez Gtk-Spell, GtkExtra, GtkHTML3, libsexy i roz-szerzenia Scintilla;

• Wersje 2.8 i 2.10 Gtk+ są teraz wspiera-ne, dzięki czemu dostajemy wiele nowych użytecznych funkcji takich, jak: rozbudo-wane wsparcie dla drukowania, odmło-dzone GtkIconView i GtkNotebook.

Otrzymujemy także oczekiwane przez nie-których wsparcie GtkPlug/Socket dla sys-temów Win32. Wśród ciekawszych nowo-ści znajdziemy także wiele najróżniejszych widgetów, jak GtkStatusIcon i GtkAssistant. Dodatkowo poprawiono wiele znanych błędów oraz dodano kilka pomniejszych usprawnień. Rozszerzenie możecie ściągnąć z strony http://www.gtk.php.net.Źródło: http:/www.gtk.php.net

Symfony 1.0.5Wydana została nowa wersja jednego z naj-popularniejszych frameworków – Symfony, w wersji 1.0.5 zawiera ona tylko poprawki znalezionych błędów.Źródło: http://www.symfony-project.com/weblog/2007/06/25/symfony-1-0-5-rele-ased-security-fix.html

Poprawki bezpieczeństwaWordPress 2.2.1Została wydana nowa wersja WordPress, która to zawiera kilka poprawek błędów – także tych krytycznych, jak SQL Injection czy Shell Injection. Wobec tego zalecamy aktualizację systemu.Źródło: http://wordpress.org/development/2007/06/wordpress-221/

eZ Componnents 2007.1

eZ Componnents to zbiór open sourco-wych bibliotek które zna za pewne każ-dy programista PHP. Komponenty zosta-

ły rozszerzone o dwie nowe biblioteki Authenti-cation który pozwala nam na uwierzytalnianie użytkowników na kilka sposobów (poprzez ba-zę danych, LDAP czy też Open ID) oraz Work-

flow. Warto zapoznać się z dokumentacją http://ez.no/doc/components/overview/latest która poka-że wam możliwości nowych bibliotek.

Źródło: http://ez.no/ezcomponents/news/ez_com-ponents_2007_1

HTML Purifier v 2.0.0

Jak wiemy, największym wrogiem programi-sty jest użytkownik. Jego nieświadome błę-dy lub też świadome próby ataku mogą nara-

zić nas na duże kłopoty. Głównym zagrożeniem są formularze. Tutaj przychodzi nam z pomocą HTML Purifier, który jest biblioteką wartą zainte-resowania każdego programisty PHP. Umożliwia ona programiście kontrolowanie wprowadzanego przez użytkownika kodu HTML. Funkcje biblio-teki, o których warto wspomnieć, to m.in.:

• usuwanie kodu XSS;• uzupełnianie niezamkniętych tagów;• zamienienie przestarzałych elementów na

zgodne z XHTML;• uporządkowanie elementów – nie do-

puszczenia do nieprawidłowego zagnież-dżania.

Jak widać, biblioteka ta nie tylko zapewnia nam bezpieczeństwo aplikacji, pozwala także na zachowanie czystości kodu, dzięki czemu nie musimy się już bać, że użytkownik znisz-czy nasz kod XHTML czy też wstrzyknie nam XSS. Aplikacja posiada rozwinięty sys-tem konfiguracji, co umożliwiadostosowa-

nie jej do swoich potrzeb. Jjeśli mimo to za-braknie nam jakiejś funkcjonalności, zawszemożemy ją zaimplementować, gdyż HTML Purifier jest projektem OpenSource (na li-cencji LGPL). Na stronie WWW projektu znajdziemy sporą dokumentację, która po-zwoli nam w konfortowy sposób korzystać z biblioteki. Jeśli jesteś użytkownikiem CMS-aDrupal czy też MODx, możesz ściągnąć wtyczkę do obsługi tej biblioteki.

Demo: http://htmlpurifier.org/demo.phpDownload: http://htmlpurifier.org/download.htmlŹródło: http://htmlpurifier.org/

Rysunek 2. Strona główna, biblioteki

Rysunek 1. Demo przedstawiające podstawową funkcjonalność

Page 7: PHP Solutions 05 2007 PL

Aktualności

6 05/2007

Aktualności

7www.phpsolmag.org

Zostań członkiem zarządu PHP.plKtoś kiedyś powiedział: „nie wstąpię do klubu, który nazywa mnie członkiem”, dzisiaj jednak każdy z nas ma okazję zostać członkiem zarzą-du wortalu PHP.pl. Wortal ten znany jest w spo-łeczności developerów, a istnieje już od wielu lat. Aktualnie serwis poszukuje kandydatów do nowo tworzonego zarządu. Do głównych zadań zarządu należeć będzie między innymi:

• reprezentacja PHP.pl – podejmowanie współpracy z serwisami, firmami;

• nadzorowanie projektów rozwijanych przez PHP.pl;

• kontrolowanie administratorów;• organizacja konkursów;• pomoc w realizacjach podejmowanych

przez członków grupy PHP.pl.

Z niecierpliwością czekamy na roztrzygnię-cie konkursu, a wszystkim biorącym udział życzymy powodzenia. Więcej informacji na temat zasad konkursu znajdziesz na forum.PHP.pl.Źródło: http://forum.php.pl/Zmiany-na-gorze-t70961.html

Konferencja PHP w HolandiiAmsterdam stał się ostatnio stolicą PHP. Odbyła się tam konferencja poświęcona roz-wojowi języka PHP na świecie. Kilka z tema-tów poruszanych na konferencji to m.in.:

• Zend Framework – przedstawienie funk-cjonalności oraz pokazanie przykłado-wych aplikacji;

• PHP i Oracle;• PDO – metody bardziej efektywnej,

przenośnej i bezpiecznej implementacji;• CodeGear Delphi dla PHP;• Symfony Framework.

Więcej informacji na temat konferencji można znależć na stronie: http://devzone.zend.com/article/2217-Dutch-PHP-Conference-2007-Wrap-UpŹródło: http://devzone.zend.com/article/2217-Dutch-PHP-Conference-2007-Wrap-Up, http://www.phpconference.nl/

Zend Framework 1.0.0

Zend już od ponad roku rozwija Swój framework. Spore grono użytkowni-ków tej aplikacji doczekało się w koń-

cu wersji stabilnej oznaczonej numerem 1.0.0. Wersja ta zawiera sporo poprawek już istnie-jących modułów. Sami autorzy uwarzają Swój

produkt za najlepszy tego typu na rynku, cóż oby im ich pycha nie zaślepiła oczu.

Źródło: http://devzone.zend.com/article/2262-Zend-Framework-1.0.0-production-release

Agavi RC 5

Wydana została kolejna wersja Rele-ase Candidate o numerze 5, jed-nego z najlepszych frameworków

PHP, jakim jest Agavi. Jest to ostatnia wersja przed wydaniem finalnym, które ukaże się w lip-cu. Każda z wersji Agavi utwierdza nas w prze-konaniu, że w PHP także powstają aplikacje w pełnym tego słowa znaczeniu. Wraz z kolejny-mi wersjami dostajemy dawkę kodu, który zde-cydowanie ułatwia nam pracę z tym framewor-kiem lub też dodaje nowe funkcjonalności. Tak samo jest tym razem, developerzy przygotowa-li nam kilka nowych narzędzi oraz usprawnień.Pierwszą rzeczą, o której na pewno warto wspo-mnieć, jest dodanie obsługi SOAP. Stworzenie akcji dostępnej przez SOAP jest naprawdę pro-ste, a opiera się na kilku czynnościach:

• Dodajemy nowy router zawierający elemen-ty <wsdl:input> (dla wejścia) oraz <wsdl:out-put> (dla wyjścia) – z definicją parametrów;

• Jeśli zdefiniujemy nagłówki w WSDL,Agavi doda je automatycznie;

• Z danych ustawionych w routerze Agavi automatycznie wygeneruje WSDL, który może być wykorzystywany przez innych klientów obsługujących protokół.

Jak widzimy, teraz obsługa SOAP staje się dzie-cinnie prosta. Wersja ta została wzbogacona:

• wsparcie dla przestrzeni nazw w XML;• wsparcie dla UTF-8 w StringValidator;• wsparcie dla XSL stylesheets w plikach kon-

figuracyjnych;• parser konfiguracji został przepisany.

Jest to tylko kilka z nowości, po więcej infor-macji odsyłam do changeloga.

Agavi w Ohloh: http://www.ohloh.net/projects/5907Changelog: http://trac.agavi.org/browser/branches/0.11/CHANGELOGŹródło: http://agavi.org/

Rysunek 2. Strona www Agavi

Rysunek 1. Agavi w serwisie ohloh.net

Page 8: PHP Solutions 05 2007 PL

8 05/2007

Opis CD

Płyta zawiera dwa pliki flash z prezentacjami. Pierwsza z nich poka-zuje proces instalacji statystyk 7point, natomiast druga zawiera de-monstrację działania samego systemu statystyk. Pokaz można oglą-dać w dwóch rozdzielczościach 800x600 lub 1024x768.

Prezentacja procesu instalacji statystyk 7pointPrzewodnik, pozwalający prześledzić krok po kroku czynności ko-nieczne do zainstalowania systemu statystyk 7point. Otwierające się kolejno okna prezentacji pozwolą łatwo i szybko prześledzić proces in-stalacji v statystyk od momentu logowania do sytemu, przez konieczne ustawienia, po proces pobierania i wklejania kodu. Kolejne kroki to po-branie i wklejenie kodu.Należy pobrać kod danej strefy i wkleić go na odpowiednich pod stronach serwisu. Ostatnie okno prezentacji zawie-ra informacje końcowe – użyteczne wskazówki dla każdego Użytkow-nika systemu statystyk 7point.

Prezentacja systemu statystyk 7pointPodobnie jak pierwsza, stanowi przewodnik dla Użytkowników systemu 7point – demonstruje szereg jego możliwości. Pokaz zo-stał podzielony na 7 rozdziałów,

• Użytkownicy strony w liczbach;• Funkcjonowanie strony w sieci;• Jakość i atrakcyjność strony;• Strona w wyszukiwarkach;• Schematy zachowań w serwisie;• Serwis na mapie świata;• Tygodniowy raport.

W każdym z 7 rozdziałów znajduje się szczegółowa prezentacja funkcji i zastosowań systemu:

• Pierwszy rozdział to informacje na temat użytkowników;• Podsumowanie ogólne zawierające zestawienie wybranych

danych z ostatnich 7 dni;• Szczegółowe informacje na temat użytkowników, wizyt i odsłon.

Ponadto widoczna jest również opcja wybierania zakresu czasu;• Podział użytkowników wg ilości powrotów;• Podział użytkowników wg czasu ostatniego powrotu.

Prezentacja kolejnych rozdziałów przebiega analogicznie. Użyt-kownik może więc zobaczyć kolejne możliwości i zastosowania systemu statystyk 7point, np.:

• Serwisy linkujące;• Ścieżki;• Szczegóły odsłon dla danej strony;• Słowa kluczowe.

Dodatkowo wszystkie prezentacje wizualne dotyczące statystyk zo-stały opatrzone komentarzem lektora, co znacznie ułatwia odbiór pokazu.

Rysunek 2. Po zalogowaniu do systemu należy przejść do ustawień

Rysunek 1. Pierwszy krok – logowanie ze strony internetowej www.7point.pl

Rysunek 3. Użytkownik może utworzyć własne strefy i pogrupować strony swojego serwisu

System Statystyk 7point

Page 9: PHP Solutions 05 2007 PL

Redakcja nie udziela pomocy technicznej w instalowaniu

i użytkowaniu programów zamieszczonych na płytach

CD-ROM dostarczonych razem z pismem

Jeśli nie możesz odczytać zawartości płyty CD, a nie jest

ona uszkodzona mechanicznie, sprawdź ją na co najmniej

dwóch napędach CD. W razie problemów z płytą, prosimy

pisać pod adres: [email protected]

Page 10: PHP Solutions 05 2007 PL

05/2007

Tam byliśmy

10

Oprócz premierowych pokazów nowego narzędzia IDE dla programistów C++ zaprezentowano także dostępne na

rynku od kilku miesięcy środowiska Delphi 2007 i Delphi for PHP 2007 oraz serwer bazodano-wy InterBase 2007. W pierwszej części konferen-cji konsultant techniczny BSC Polska – BogdanPolak – przedstawił ogólne perspektywy rozwo-ju produktów firmy CodeGear oraz zaprezento-wał nowe możliwości środowiska C++Builder® 2007. Pod koniec każdego bloku tematycznego przewidziano czas na sesje pytań i odpowiedzi.C++Builderc 2007 jest jedynym natywnym śro-dowiskiem typu RAD do programowania w ję-zyku C++ dla systemu Windows. Oferuje pełne wsparcie dla Windows Vista® oraz pozwala pro-gramistom na łatwe dostosowanie istniejących już aplikacji C++ do wymogów tego systemu. Umoż-liwia tworzenie nowych aplikacji Windows wykorzystujących możliwości interfejsu Vista Aero. Nowa edycja komponentów bazodano-wych dbExpress (DBX4) umożliwia łączenie się z najnowszymi wersjami serwerów SQL.

C++Builder® 2007 pozwala tworzyć aplikacje Web 2.0 wykorzystujące technologię AJAX (ang. Asynchronous JavaScript and XML). Nowyzestaw komponentów INDY 10 Internet Proto-col dostarcza nowe możliwości programistom aplikacji internetowych. W kolejnej części kon-ferencji przedstawiono nowe możliwości środo-wiska Delphi 2007 for Win32, a w szczególno-ści wsparcie dla platformy Microsoft Windows Vista®, nową architekturę dostępu do baz da-nych DBX4, możliwości zarządzania buildami w oparciu o MSBuild oraz ulepszoną technolo-gię instalacyjną. Delphi 2007 for Win32 wspie-ra wykorzystanie technologii AJAX, pozwalając na szybkie i wizualne tworzenie interaktywnych aplikacji internetowych. Po raz kolejny duże za-interesowanie uczestników wzbudziło narzę-dzie Delphi dla PHP – pierwsze narzędzie RAD do programowania aplikacji w języku PHP. Pod-czas wykładu słuchacze mogli zapoznać się mię-dzy innymi z możliwościami nowego edytora, debuggera oraz zintegrowanej biblioteki VCL dla PHP. Nowy produkt wzbudził duże zainte-

resowanie wśród programistów Delphi. Code-Gear nie zamierza poprzestać na pierwszej edy-cji oprogramowania. Delphi dla PHP wpisze się na stałe do gamy oferowanych produktów. Pod-czas ostatniej części konferencji Bogdan Polak za-prezentował możliwości serwera bazodanowego InterBase 2007. Nowy InterBase zapewnia wie-le korzyści związanych z architekturą wielowąt-kową. Zastosowanie systemu rejestracji zdarzeńi procedur przywracania do pracy po awa-rii gwarantuje wysoki stopień bezpieczeństwa.InterBase jest jednocześnie jedną z najszybszych baz danych, która dzięki obsłudze SMP może pracować z wieloma procesorami, w tym wie-lordzeniowymi. InterBase uzyskał certyfikację do pracy na systemach Windows, Linux i Solaris.Pożegnanie z uczestnikami uwieńczyło loso-wanie nagród. Po konferencji każdy z uczestni-ków mógł skorzystać z unikalnej oferty ceno-wej przygotowanej specjalnie na tą okazję. Wiele osób skorzystało także z zaproszenia na bezpłat-ne seminaria on-line dotyczące programowaniaw Delphi dla PHP.

W pierwszej połowie czerwca w trzech miastach Polski – Poznaniu, Warszawie i Krakowie – odbył się cykl bezpłatnych konferencji poświęconych najnowszym rozwiązaniom firmy CodeGear, w tym środowiska C++Builder® 2007.

Polska premiera C++Buildera 2007i nie tylko!

Page 11: PHP Solutions 05 2007 PL
Page 12: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

12

MySQL od podstaw

www.phpsolmag.org 13

Pierwszym nasuwającym się pytaniem jest: dlaczego właśnie MySQL? Ta baza danych oferuje wysoką wydajność, po-

twierdzoną licznymi testami – jest to jeden z najszybciej działających produktów na rynku. Jego licencję (GPL) można streścić następują-co: jeśli pobieramy opłaty za używanie pro-duktu korzystającego z MySQL, musimy wy-kupić płatną licencję. Oznacza to, że do zasto-sowań niekomercyjnych można stosować My-SQL bez ograniczeń. Już same te dwa argu-menty przemawiają zdecydowanie za używa-niem MySQL. Oczywiście, jest to baza stabil-na i łatwa w użyciu (obsługuje składnię poda-ną w standardzie SQL 92, dzięki czemu ucząc się jej obsługi, uczymy się także obsługi więk-szości innych baz danych), posiadająca wie-le funkcjonalności, która jest cały czas w roz-woju. Konkurencja nie pozostaje daleko w ty-le, jednak praktycznie każdy produkt ma ja-kąś wadę:

• PostgreSQL – szybkość;• SQLite – brak dostępu przez protokół

zdalny (operuje na plikach lokalnych); • Access – cenę i niską ekonomię.

Niemniej nie należy się martwić – systemy baz danych podlegają w większości standar-dom, dzięki czemu ucząc się składni MySQL,będziemy znać podstawy składni innych

baz danych. Jest to niewątpliwą zaletą w stosunku do języków programistycznych, które mają osobne style, składnie i nazwy funkcji.

InstalacjaPodobnie jak w przypadku PHP, MySQL zo-stał zaprojektowany do działania zdalnego, na serwerze. Do działania nie potrzebuje jednak serwera HTTP, bowiem sam sobie jest serwe-rem. Oznacza to, że instalując MySQL, insta-lujemy aplikację, która działa w tle i oczeku-je na połączenia przez jej protokół. Łączyć z serwerem MySQL możemy się na wiele spo-sobów (przez PHP, phpMyAdmin, czy konso-lę kliencką dostarczaną z pakietem instalacyj-nym), niezależnie, czy serwer działa na kom-puterze lokalnym, czy zdalnym. Mamy więc ponownie do wyboru dwie możliwości: ko-rzystać z usług hosting providera (w Interne-cie można znaleźć darmowe i płatne oferty) lub zainstalować MySQL u siebie. W pierw-szym przypadku, będziemy tworzyć użyt-kownika (dane do logowania) przy pomo-cy interfejsu dostarczanego przez provide-ra (najczęściej przez panel WWW). Otrzy-mamy także zapewne zainstalowany system phpMyAdmin (dostęp do bazy danych przez stronę WWW), co znacznie ułatwi sprawdza-nie wyników wykonywania zapytań. Jeśli ma-my taką możliwość, najprościej będzie z niej skorzystać – możemy przejść do kolejnej czę-ści artykułu.

W przypadku instalacji MySQL na wła-snym komputerze, niezbędnych będzie kil-ka działań. Najpierw musimy zainstalować serwer MySQL (tzw. daemon). Dla syste-

mu Linux, najprościej skorzystać z plików RPM (najlepiej pobrać je z witryny http://www.mysql.com, bowiem te dostarczane z dys-trybucjami często są nieaktualne). Mniej wię-cej w tym samym czasie, gdy PHP wkroczyło w wersję 5, MySQL także opublikowano pod tym numerem. Zmian w stosunku do wersji 4 jest wiele, dlatego zalecam instalację najnow-szej wersji. Pobieramy pliki o nazwach zbli-żonych do MySQL-server-WERSJA.i386.rpm, MySQL-Max-WERSJA.i386.rpm, MySQL-client-WERSJA.i386.rpm. Instalujemy ser-wer wraz z aplikacją kliencką przez wpisa-nie w powłoce:

rpm -i MySQL-server-WERSJA.i386.rpm

MySQL-client-WERSJA.i386.rpm

Nie zapomnijmy zastąpić słowa WERSJA od-powiednim numerem. MySQL zostanie za-instalowane, uruchomiony zostanie ser-wer „mysqld” oraz dodany zostanie wpis w /etc/init.d/ uruchamiający serwer przy każ-dym rozruchu komputera. Procedura instala-cji MySQL bez udziału plików RPM jest bar-dziej skomplikowana, jednak dobrze opisana w manualu.

W przypadku instalacji MySQL na Win-dowsie, logujemy się jako administrator i po-bieramy pakiet. Do wyboru mamy samoin-stalujący się plik *.exe lub spakowane archi-wum. Oczywiście, najłatwiej wybrać pierw-szą możliwość – przez proces instalacji zo-staniemy przeprowadzeni szybko i sprawnie, wszystkie ustawienia zaaplikuje nam instala-tor. W drugim przypadku, możemy wypako-wać archiwum i własnoręcznie umieścić my-sqld-max.exe w autostarcie (to nasz serwer). Możemy także zainstalować MySQL jako usługę przechodząc przy pomocy wiersza po-lecenia do katalogu instalacji, a potem katalo-gu bin i wydając polecenie mysql-max –stan-dalone a następnie mysql-max –install. Pro-cedura ta mogła jednak zmienić się od czasu

MySQL

Stworzenie funkcjonalnej i szybkiej aplikacji w dowolnym języku wymaga sprawnego systemu przechowywania niezbędnych dla niej danych. PHP obsługuje wiele systemów bazodanowych (Oracle, PostgreSQL, SQLite, Access itd.), z których niewątpliwie najpopularniejszym jest MySQL.

Dowiesz się ...• Dowiesz się, jak wykonywać większość naj-

potrzebniejszych operacji na bazie danychMySQL

Powinieneś wiedzieć...• Przydatna będzie znajomość podstaw PHP

Poziom trudności

Bazy danych od podstaw

Page 13: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

12

MySQL od podstaw

www.phpsolmag.org 13

napisania artykułu, dlatego zalecam zajrze-nie do manuala w przypadku instalacji z ar-chiwum.

Manual MySQL zawiera dokładne instruk-cje instalacji dla wielu systemów (Solaris,MacOS itp.) i jest bardzo przystępnie na-pisany, dlatego warto się do niego odwoły-wać. Manuale stanowią ogromny atut PHPi MySQL – są to niewątpliwie jedne z najle-piej opisanych produktów służących do two-rzenia oprogramowania. W przeciwieństwie do manuali innych języków, czytając je, nie czujemy, że czytamy encyklopedię lub słow-nik, lecz normalną książkę.

Po instalacji MySQL sprawdzamy jego działa-nie komendą wpisaną do konsoli (w Windowsie Start -> Uruchom):

mysql -u rootW przypadku systemu Windows podamy także ścieżke do katalogu z MySQL, np.

c:\mysql\bin mysql -u root

Powinniśmy zobaczyć powitanie w Monitorze MySQL, w którym możemy wydawać komen-dy. Opuścić go można przy pomocy komendy \q. Jeśli napotkamy error can't connect to MySQL server, oznacza to, że serwer nie został uruchomiony. Możemy ponownie uruchomić system lub uruchomić serwer wpisując:

mysqld –standalonePrzy logowaniu do mysql użyliśmy nazwy użyt-kownika root. Ze względów bezpieczeństwa, powinniśmy ustanowić hasło w Monitorze ko-mendą:

set password for root@localhost=password(

'hasło');

Jak widać, komendy w MySQL są dosyć in-tuicyjne i opierają się na języku angielskim.W powyższej komendzie słowo hasło zamie-niamy na nasze hasło. Działanie hasła spraw-dzamy wylogowując się, a następnie logując komendą:

mysql -u root -p

Powinniśmy zostać zapytani o hasło.

Używanie MySQLJak wspomniałem, komendy MySQL można wydawać na kilka sposobów. Możemy w tym celu użyć aplikacji phpMyAdmin (jeśli mamy ją zainstalowaną), jak widać na Rysunku 1. Może-my także wydać komendę bezpośrednio w Mo-nitorze MySQL (plik mysql.exe w Windowsie i mysql w Linuxie) – przestawia to Rysunek 2.W końcu, mamy dostęp do MySQL z pozio-mu PHP. Teoretycznym minusem ostatniego rozwiązania, w przypadku nauki, jest fakt, że wyniki otrzymujemy w różnych formach (ta-

blic wielowymiarowych, zmiennych itd.), trze-ba więc wiedzieć, jakich komend użyć, by je wy-świetlić. Dwie pierwsze możliwości zapewnia-ją interfejs, który automatycznie formatuje i wyświetla wyniki zapytań (komend). Narazie więc polecam korzystanie z aplikacji klienckiej (konsoli), ewentualnie panelu phpMyAdmin. Po zapoznaniu się z podstawowymi zapytania-mi, przejdziemy do opisu ich implementacji w PHP. Warto jednak już teraz wspomnieć, że do niektórych zapytań PHP się po prostu nie nada-je (właśnie z racji konieczności własnoręcznego formatowania wyników). Takie operacje to na przykład: tworzenie bazy danych wraz z tabe-lami (czynność jednokrotna – nie warto dla niej budować skryptu PHP, o ile nie tworzymy in-stalatora do naszego skryptu), wyświetlanie do-stępnych tabel, zmiana ustawień bazy lub ta-beli. Do debuggowania (w tym wypadku np. sprawdzania, czy w tabelach znajdują się po-prawne wartości) lepiej skorzystać z konsoli lub phpMyAdmin. Jeśli używamy porządnego edy-tora PHP (np. polecany w artykule „Wstęp do PHP” Zend Developer Environment), mamy do dyspozycji wbudowanego klienta SQL, który także formatuje wyniki zapytań.

Tworzenie baz i tabelZapytania w MySQL kończymy znakiem śred-nika. Jest to ważne, ponieważ możemy dzię-ki temu formatować zapytania. W systemie MySQL możemy stworzyć wiele baz danych, a w nich – wiele tabel. To w tabelach zawarte są dane, natomiast bazy danych służą do kata-logowania tabel. Dobrze jest przyjąć konwen-cję tworzenia jednej bazy danych dla każdego projektu, a także dwóch kont użytkowników o ograniczonych prawach – za pomocą jed-nego konta możliwy byłby jedynie odczyt da-nych, za pomocą drugiego – zapis. Ogranicza-

my wtedy możliwości modyfikacji danych w przypadku przejęcia przez intruza danych do cześciej używanego konta, służącego do odczy-tu. Ustanawianie przywilejów jest jednak kwe-stią „dmuchania na zimne” i ma sens jedynie w przypadku ważnych danych, nie jest więc w gestii tego artykułu – odsyłam do świetnego opisu polecenia GRANT na http://dev.mysql.com/doc/refman/5.0/en/grant.html. Dodajmy jesz-cze, że w MySQL nie są rozróżniane wielkości liter w poleceniach, jednak w niektórych sys-temach operacyjnych rozróżniane są wielkości liter w nazwach baz danych i tabelach. War-to więc przyjąć stałą konwencję w przypadku nazw. Często stosowanym pomysłem jest wy-dawanie poleceń pisanych wielkimi literami z nazwami własnymi pisanymi małymi. Czas już przejść do praktyki. Bazę danych tworzy-my poleceniem:

CREATE DATABASE test;

Gdy stworzymy bazę danych (nasz „katalog” dla tabel), przed stworzeniem tabel, musimy zdeklarować, że właśnie jej chcemy użyć po-leceniem:

USE test;

Jeśli chcemy usunąć bazę danych (wraz z jej ta-belami i danymi w nich zawartymi!), używa-my komendy:

DROP DATABASE test;

W MySQL można używać przydatnych opcjo-nalnych if exists i if not exists. Oznacza to, że np. zapytanie:

CREATE DATABASE IF NOT EXISTS test;

Rysunek 1. PhpMyAdmin w akcji

Page 14: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

14

MySQL od podstaw

www.phpsolmag.org 15

Listing 1. Struktura bazy danych http://eldoras.com

# Struktura tabeli dla `accounts`

CREATE TABLE `accounts` (

`account_id` int(11) NOT NULL auto_increment,

`nick` varchar(15) NOT NULL default '',

`pass` varchar(32) NOT NULL default '',

`email` varchar(128) NOT NULL default '',

PRIMARY KEY (`account_id`)

) ENGINE=InnoDB;

# --------------------------------------------------------

# Struktura tabeli dla `added`

CREATE TABLE `added` (

`video_id` int(11) NOT NULL auto_increment,

`stamp` timestamp NOT NULL default CURRENT_TIMESTAMP on

update CURRENT_TIMESTAMP,

PRIMARY KEY (`video_id`)

) ENGINE=InnoDB;

# --------------------------------------------------------

# Struktura tabeli dla `comments_en_0`

CREATE TABLE `comments_en_0` (

`comment_id` int(11) NOT NULL auto_increment,

`stamp` timestamp NOT NULL default CURRENT_TIMESTAMP on

update CURRENT_TIMESTAMP,

`poster_id` int(11) NOT NULL default '0',

`content` text NOT NULL,

PRIMARY KEY (`comment_id`)

) ENGINE=InnoDB;

# --------------------------------------------------------

# Struktura tabeli dla `comments_pl_0`

CREATE TABLE `comments_pl_0` (

`comment_id` int(11) NOT NULL auto_increment,

`stamp` timestamp NOT NULL default CURRENT_TIMESTAMP on

update CURRENT_TIMESTAMP,

`poster_id` int(11) NOT NULL default '0',

`content` text NOT NULL,

PRIMARY KEY (`comment_id`)

) ENGINE=InnoDB;

# --------------------------------------------------------

# Struktura tabeli dla `links`

CREATE TABLE `links` (

`video_id` int(11) NOT NULL auto_increment,

`video_link` text NOT NULL,

`image_link` varchar(255) NOT NULL default '',

PRIMARY KEY (`video_id`)

) ENGINE=InnoDB;

# --------------------------------------------------------

# Struktura tabeli dla `performers`

CREATE TABLE `performers` (

`performer_id` int(11) NOT NULL auto_increment,

`performer_name` varchar(25) NOT NULL default '',

PRIMARY KEY (`performer_id`)

) ENGINE=InnoDB;

# --------------------------------------------------------

# Struktura tabeli dla `songs`

CREATE TABLE `songs` (

`song_id` int(11) NOT NULL auto_increment,

`song_title` varchar(30) NOT NULL default '',

`song_artist` varchar(20) NOT NULL default '',

PRIMARY KEY (`song_id`)

) ENGINE=InnoDB;

# --------------------------------------------------------

# Struktura tabeli dla `stats`

CREATE TABLE `stats` (

`video_id` int(11) NOT NULL auto_increment,

`views_count` int(11) NOT NULL default '0',

`rate` float(4,2) NOT NULL default '0.00',

`votes_count` int(11) NOT NULL default '0',

`rating_sum` int(11) NOT NULL default '0',

PRIMARY KEY (`video_id`)

) ENGINE=InnoDB;

# --------------------------------------------------------

# Struktura tabeli dla `videos`

CREATE TABLE `videos` (

`video_id` int(11) NOT NULL auto_increment,

`song_id` int(11) NOT NULL default '0',

`performer_id` int(11) NOT NULL default '0',

`proposed_by_id` int(11) NOT NULL default '0',

PRIMARY KEY (`video_id`)

) ENGINE=InnoDB;

nie będzie próbowało utworzyć bazy o nazwie test, jeśli już taka istnieje. Szczególnie przydat-ne jest if not exists w przypadku tworze-nia tabel. Tworzymy tabelę „tabela” (jeśli jesz-cze nie istnieje) z polami:

• id_produktu: wartości tej kolumny bę-dą liczbami całkowitymi (int), wartość tej kolumny nie może być pusta (not null) – inaczej próba dodania nie po-wiedzie się; auto _ increment gwaran-

tuje, że każdy kolejny wiersz będzie miał w tej kolumnie wartość o jeden większą niż poprzedni (to pole będzie więc nu-merem wiersza, który w przypadku baz danych nazywamy rekordem); primarykey oznacza, że będzie to klucz główny naszej tabeli (klucz główny oznacza, że wartości w tej kolumnie nigdy się nie powtarzają, są więc unikalne; jednocze-śnie, klucz główny implikuje indeksowa-nie, czyli tworzenie tabeli pomocniczej,

która przyspiesza wyszukiwanie po tym polu). Znakomita większość tabel po-siada takie pole jako pierwsze – w celu identyfikacji wierszy;

• nazwa: wartości tej kolumny będą składać się z maksymalnie 30 znaków;

• opis: pole podobne do powyższego, jed-nak może zawierać do 65 535 znaków (są też większe i mniejsze typy łańcuchowe pól, jednak te są najczęściej wykorzysty-wane);

Page 15: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

14

MySQL od podstaw

www.phpsolmag.org 15

• data_dodania: kolumna ta posiada for-mat datetime, będzie domyślnie wypeł-niana aktualnym czasem, a zapytania bę-dą zwracać datę w formacie RRRR-MM-DD GG:MM:SS. Istnieją także inne typy dat (np. datetime, date, time, year).

W końcu, po zdefiniowaniu pól (kolumn), jakie mają znaleźć się w tablicy, określamy (nieobowiązkowo) typ tworzonej tabeli.MySQL obsługuje kilka typów tabel, z któ-rych zdecydowanie najpopularniejszymi są MyISAM i InnoDB. Nie wdając się w szcze-góły, różnica między nimi polega na struk-turze przechowywania danych i funkcjo-nalności. MyISAM stosuje się, jeśli tabe-la zawiera dane, do których potrzebny jest szybki dostęp i nie są często modyfikowane (jest to domyślne ustawienie – jeśli usunie-my TYPE=MyISAM z powyższego zapytania, efekt będzie taki sam). Z kolei wolniejszeInnoDB zawiera niewątpliwą zaletę w posta-ci blokowania na poziomie wierszy (a nie ta-beli – jak MyISAM), obsługuje także trans-akcje. Wyjaśnię to na przykładzie: mamy tabelę z tysiącami rekordów. Jeśli tabela jest typu MyISAM, przy dodawaniu nowe-go wiersza, zablokowane na ten czas zosta-nie jakiekolwiek inne działanie na tej tabeli(a więc także odczyt).

Przyjmijmy, że nasza witryna odczytuje z tabeli rekordy przy każdorazowym przeła-dowaniu, zaś użytkowników mamy co naj-mniej setki. Jako administratorzy, dodajemy wiersz do tabeli (np. z nowym towarem). W czasie wykonania takiego zapytania (ułam-ki sekundy, choć niekiedy nawet sekundy – zależnie od wprowadzanych danych) po-bieranie danych przez użytkowników zosta-nie wstrzymane – załadowanie naszej witry-ny będzie przedłużone o ten czas oczekiwa-nia. Jeśli rzadko dodajemy do tabeli nowe da-ne, problem będzie niezauważony (dodatko-wo MyISAM oferuje kompresje i działa szyb-ciej niż InnoDB).

Jeśli jednak dodawanie wiersza będzie od-bywało się automatycznie i często, różnica może być zauważalna. W tym celu stosuje się tabele InnoDB – choć wolniejsze, bloku-ją przed odczytem nie całą tabelę, lecz jedy-nie aktualnie modyfikowany wiersz. W tym czasie inne rekordy z tabeli mogą być pobra-ne przez użytkowników. InnoDB obsługuje także transakcje.

Transakcje można najogólniej objaśnić jako zbiory kilku dowolnych poleceń, które albo wykonają się wszystkie poprawnie, albo ba-za danych wróci do stanu sprzed wykonania pierwszego polecenia.

Jest to szczególnie przydatne przy do-dawaniu danych do wielu tabel – jeśli np.w jednej przechowujemy wartości głosów, a w drugiej ich sumę, nie możemy pozwolić, by suma głosów zwiększyła się bez dodania

konkretnej wartości głosu w drugiej tabeli. Podobnie, nie możemy dodać wartości głosu nie zwiększając sumy – prowadziłoby to do zamieszania i sfałszowania wyników. Trans-akcja gwarantuje, że zajdą obydwie możliwo-ści, lub żadna. Wspomnieć należy, że typ ta-beli można zmienić – jeśli więc zauważymy, że jeden typ okazuje się niewydajny, może-my zastosować inny.

Możemy zobaczyć dostępne bazy danych i tabele (po wybraniu bazy danych przy pomocy USE) wykonując odpowiednio komendy:

SHOW DATABASES;

SHOW TABLES;

Jeśli chcemy obejrzeć zapytanie, które utwo-rzy tabelę już istniejącą, wydajemy polecenie:

SHOW CREATE TABLE tabela;

Rezultat powinien dać polecenie zbliżone do tego, którym stworzyliśmy tabelę. Moż-na spodziewać się kilku detalicznych zmian, które są automatycznie dodawane, więc za-zwyczaj pomijane, np. rozmiar pola int bę-dzie wynosił 11, kodowanie znaków zosta-nie dodane po definicji typu tabeli (w za-leżności od ustawień domyślnych systemubazodanowego) oraz nazwy tabel i kolumn zostaną ujęte w znaki .̀ Jeśli chcemy usunąć tabelę, stosujemy:

DROP TABLE tabela;

Natomiast do modyfikacji tabeli służy polece-nie ALTER. W przypadku graficznych interfej-sów, jak np. phpMyAdmin, nie musimy znać tego polecenia, jednak sposób tworzenia ta-beli będzie miał wpływ na to, jak zbudujemy zapytanie dodające, czy wyszukujące rekordy– warto więc znać tekstowe wersje poleceń. Dla przykładu, aby dodać kolumnę na końcu tabeli, używamy:

ALTER TABLE tabela ADD COLUMN kolumna

varchar(20);

Uwaga: polecenie ALTER modyfikuje jedy-nie definicję tabeli, a nie wartości w niej zapisane. Powyższe polecenie nie zmieni wartości wierszy – do tego służy polece-nie UPDATE, które omówimy w dalszej czę-ści artykułu.

MySQL oferuje wiele typów pól. Warto po-znać te mniej popularne, by nie „strzelać z ar-maty do muchy”, co często dzieje się, gdy pro-jektant użyje TEXT zamiast VARCHAR dla samej nazwy produktu. Oczywiście, jedyną różni-cą będzie czas wykonania zapytania, a więc kluczowa sprawa, jeśli idzie o bazy danych. Wspomniane polecenia mają wiele dodatko-wych opcji, którym warto się przyjrzeć, my jednak na pewno chcemy już operować na da-nych.

Wstawianie, usuwanie i aktualizacja danychOperacje określone powyżej należą raczej do wolnych. Bazy danych nastawione są na szyb-kość wyszukiwania i zwracania informacji, dlatego musimy oszczędnie i rozmyślnie pro-jektować struktury baz danych, by ograniczyć ilość zapytań odpowiednio: INSERT, DELETEi UPDATE.

Polecenie INSERT występuje w różnych od-mianach: możemy dodawać jeden lub kilka wierszy, dodawać pewne warunki lub okre-ślać jedynie wartości niektórych pól tabe-li (ma to sens np. w przypadku, gdy więk-szość pól ma ustawione opcje not null oraz default – wtedy zostaną automatycznie uzu-pełnione). Dodajmy pierwsze wiersze do na-szej tabeli:

W tej formie zapytania podajemy wartości wszystkich pól oddzielone przecinkiem (tekst musi być zawarty w apostrofach), zaś kolejne wiersze oddzielone są przecinkami i ujęte w na-wiasy. W pierwszym wierszu dla pola id_pro-duktu podajemy wartość NULL. Jego deklaracja zakłada, że taka wartość nie może wystąpić, więc MySQL automatycznie przypisze temu polu ko-lejną wartość z auto_increment (będzie to 1).Podobnie sprawa ma się z ostatnim polem – zostanie mu przypisana wartość CURRENT_

Rysunek 2. Klient tekstowy mysql

Page 16: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

16

MySQL od podstaw

www.phpsolmag.org 17

Jak widać, zmieniamy wartość pola opis. Kluczowym wyrażeniem jest tu WHERE

– określone są po nim warunki, jakie mu-si spełniać wiersz, by do niego została za-stosowana zmiana. W naszym przypadku określamy id_produktu (które jest unikalne z założenia tabeli), zmiana będzie więc do-tyczyła jednego wiersza. Jeśli usuniemy wa-runek, zmiana będzie dotyczyć wszystkich rekordów:

UPDATE tabela SET opis='Napoj gazowany o

smaku coli';

Wyrażenie WHERE jest niezwykle istotne i sto-suje się je także w innych poleceniach. Aby usunąć wiersze, stosujemy:

DELETE FROM tabela WHERE id_produktu='3'

LIMIT 1;

Ponownie, polecenie będzie dotyczyło tyl-ko jednego wiersza. Dodaliśmy wyraże-nie LIMIT jedynie w celu demonstracyj-nym. Często jednak stosuje się je „na wszel-ki wypadek”. Praktyczne zastosowanie ma natomiast, jeśli nie określimy jednoznacz-nie wierszy. Na przykład, posiadamy tabe-lę, która zawiera historię cen produktów. Niech ma taką strukturę:

Pierwsze pole zawiera identyfikator jed-noznaczny każdego rekordu. Drugi – numer produktu, którego dotyczy (wartości tego po-la mogą się więc powtarzać – np. dwa wpi-sy mające ten sam id_produktu będą ozna-czać dwie jego ceny, określone w czasie przezdata_dodania). Załóżmy, że jeden z produk-tów bardzo często zmienia cenę. Chcemy więc usunąć jego 15 najstarszych cen z hi-storii. Niech jego id_produktu będzie wyno-siło 20. Aby określić, że usunięte mają być najstarsze dane, musimy je najpierw posorto-wać. Służy do tego wyrażenie ORDER BY:

DELETE FROM ceny WHERE id_produktu='20'

ORDER BY data_dodania ASC LIMIT 15;

Po określeniu, którego pola ma dotyczyć sortowanie, podajemy DESC lub ASC – odpo-wiednio malejąco i rosnąco. Limit 15 okre-śla, że maksymalnie usuniętych zostanie 15 rekordów.

Sortowanie będzie wykonane jedynie dla tego zapytania – po jego wykonaniu, da-ne będą w takiej kolejności, jak przedtem.Silnik MySQL wykona więc kolejno nastę-pujące czynności: wyselekcjonuje rekordy, których id_produktu równa się 20, posortu-je je rosnąco według daty, a następnie usunie maksymalnie 15 pierwszych rekordów z ta-kiej posortowanej listy.

Do większości wyrażeń (np. WHERE lub ORDER BY) można dodawać kilka warunków i łączyć je (np. w celu wyszukiwania frag-

Listing 2. Wyświetlanie najnowszych filmów

<?

mysql_connect('localhost', 'nazwa_bazy', 'haslo_bazy');

mysql_select_db('numa');

//select videos

$query_videos=mysql_query('SELECT links.image_link, links.video_link,

performers.performer_name AS performer, songs.song_title AS title,

songs.song_artist AS artist, lastadded.video_id FROM links, performers,

songs, videos, ( SELECT video_id, stamp FROM added ORDER BY stamp DESC LIMIT '.

(($page-1)*8) .', 8) AS lastadded WHERE videos.video_id = lastadded.video_id AND

links.video_id = lastadded.video_id AND performers.performer_id =

videos.performer_id AND songs.song_id = videos.song_id ORDER

BY lastadded.stamp DESC');

//count all videos

$query_count=mysql_query('SELECT count(*) AS count from added');

$count=mysql_fetch_assoc($query_count);

$videos_count=$count['count'];

if($count['count']<8)

{

$total_pages=1;

}

elseif ($count['count']%8==0)

{

$total_pages=$count['count']/8;

}

else

{

$total_pages=(intval($count['count']/8))+1;

}

if($page>$total_pages)

{

die();

}

//count all users

$query_users_count=mysql_query('SELECT count(*) AS count from accounts');

$users_counter=mysql_fetch_assoc($query_users_count);

$users_count=$users_counter['count'];

mysql_query('UPDATE overall_stats SET views_count=views_count+1');

$query_views_count=mysql_query('SELECT views_count from overall_stats');

$views_counter=mysql_fetch_assoc($query_views_count);

$views_count=$views_counter['views_count'];

?>

TIMESTAMP, czyli aktualny znacznik czasu. W drugim wierszu robimy coś, czego raczej nie powinno się robić w przypadku pól auto_increment – podajemy jego wartość. Nie jest to zabronione, jest więc możliwe, ale ma sens jedy-nie, gdy usuniemy rekord ze „środka” tablicy i bę-dziemy chcieli ponownie zająć jego numer. Efekt możemy zobaczyć, wykonując zapytanie:

SELECT * FROM tabela;

Operacja SELECT jest najważniejszą w ca-łym systemie bazodanowym i omówimy ją szerzej później. Polecenie UPDATE jest szcze-gólnie przydatne w przypadku przechowy-wania danych statystycznych wewnątrz ta-bel. Zmieńmy lekko treść rekordu numer dwa:

UPDATE tabela SET opis='Napoj gazowany o

smaku coli' WHERE id_produktu='3';

Page 17: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

16

MySQL od podstaw

www.phpsolmag.org 17

mentu tekstu). Najczęściej stosuje się jed-nak operator AND:

UPDATE ceny SET cena='5' WHERE

id_produktu='4' AND id='5';

Takie zapytanie ma sens jedynie wtedy, gdy chcemy być pewni, czy numer wiersza o id 5 na pewno odpowiada produktowi o id 4. Później przekonamy się jednak, że WHEREw połączeniu z AND tworzą trzon zapytań w MySQL. Jeśli chcemy usunąć wszystkie wier-sze z tabeli, wystarczy wydać polecenie:

TRUNCATE TABLE tabela;

Trzeba być jednak bardzo pewnym tego, co się robi.

Zapytania wybierające w MySQLWreszcie, docieramy do tego, co najważniejsze – sposobu pobierania danych z bazy. Do wykona-nia zapytania służy instrukcja SELECT. Raz już jej użyliśmy. W jej najprostszej postaci, wyświetlanie wszystkich danych z tablicy uzyskamy dzięki:

SELECT * FROM tabela;

Oczywiście, sensu nabiera to zazwyczaj dopie-ro po dodaniu klauzuli WHERE:

SELECT * FROM tabela WHERE cena<500;

Symbol gwiazdki oznacza pobranie wartości wszystkich kolumn dla danych wierszy. Mo-żemy także pobrać jedynie określone kolum-ny używając:

SELECT cena, nazwa FROM tabela WHERE

cena<500;

Oczywiście, jeśli mamy kilka tabel, możemy użyć jednego zapytania do pobrania danych z wszystkich z nich:

SELECT tabela.cena, tabela.nazwa,

kontrahenci.nazwa FROM tabela,

kontrahenci;

W powyższym zapytaniu wybieramy kolum-ny: cena i nazwa z tabeli tabela oraz nazwa z tabeli kontrahenci. By uniknąć problemu tych samych nazw kolumn, poprzedzamy ich nazwy nazwą tabeli, z której mają być pobra-ne, i kropką. Prawdziwą siłą zapytania SELECT jest połączenie z WHERE w celu określenia relacji między tabelami:

SELECT tabela.cena, tabela.nazwa,

kontrahenci.nazwa FROM tabela,

kontrahenci WHERE tabela.

id_zamawiajacego=kontrahenci.

id_kontrahenta AND tabela.

id_zamowienia=15;

Listing 3. Wyświetlanie wyników zapytań

<div id="main">

<h1>

<?=$lang_latest_videos?>

</h1>

<h2 class="count">

<?

if($page!=1){

print '<a href="index.php?page='. ($page-1) .'">'.$lang_previous.'</a> | ';

}

print $lang_page.' '.$page.' '.$lang_of.' '.$total_pages;

if($page<$total_pages){

print ' | <a href="index.php?page='. ($page+1) .'">'.$lang_next.'</a>';

}

?></h2><ul><?

$i=1;

while($video=mysql_fetch_assoc($query_videos)){

?><li>

<a href="view.php?id=<?=$video['video_id']?>">

<span>

<img src="<?=$video['image_link']?>" width="105px" height="105px"

alt="<?=stripslashes($video['performer'])?>" />

p><?=stripslashes($video['performer']).' '.$lang_in.'<br />'.stripslashes(

$video['artist']).' - '.stripslashes($video['title'])?>

</span>

</p>

</a>

</li>

<?

if($i==4)

{

?></ul><ul><?

}

$i++;

}

while($i<=8){

if($i==5){

?>

</ul>

<ul>

<?

}

?>

<li>

<img src="images/box.jpg" width="105px" height="105px"

alt="Image unavailable" class="box" />

<h2>

&nbsp;

</h2>

</li>

<?

$i++;

}

?>

</ul>

</div>

Page 18: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

18

MySQL od podstaw

www.phpsolmag.org 19

Jeśli założymy, że w tabeli tabela ma-my zamówienia produktów wraz z kolum-ną id _ zamawiającego posiadającą numer klienta, do którego są przypisane dane (np. nazwa) klienta w tabeli kontrahenci, takie zapytanie zwróci nam nazwę i cenę zamó-wienia numer 15 wraz z nazwą kontrahen-ta, który zamówienia dokonał. Takie po-wiązanie dwóch lub więcej tabel nazywa się relacją.

MySQL posiada tak że użyteczną funkcjo-nalność zwaną aliasami. Jeśli nie podoba nam się nazwa tabeli, np. z powodu jej długości, mo-żemy na czas wykonania zapytania przypisać jej inną nazwę:

SELECT t1.nazwa FROM tabela AS t1;

Tabeli tabela nadaliśmy alias t1, którego na-stępnie używamy w zapytaniu SELECT. Efekt jest taki sam, jak zapytania:

SELECT tabela.nazwa FROM tabela;

W tym konkretnym przypadku, skrót nie ma sensu, jednak przydaje się, jeśli używamy za-pytań zagnieżdżonych. Po klauzuli WHERE możemy bowiem umieścić nie tylko nazwy istniejących tabel, ale także kolejne zapyta-nie SELECT (w nawiasie). Wynikiem tego po-dzapytania będzie oczywiście tabela, do któ-rej pól możemy odwoływać się przypisując jej wcześniej alias. Zapytania zagnieżdżone są niezwykle istotne przy większych projektach i zademonstrujemy je później na przykładzie z życia wziętym. Wróćmy jednak do aliasów.Możemy je przypisać nie tylko tabelom, ale tak-że wierszom:

SELECT tabela.nazwa AS pole1 FROM tabela;

Powyższe zapytanie zwróci tabelę, której ko-lumna nie będzie miała nazwy nazwa, lecz pole1. Zmieniliśmy więc nazwę wynikowej kolumny. Będzie to miało znaczenie w połą-czeniu z PHP, bowiem nazwy kolumn zwra-canych przez zapytanie są zamieniane na in-deksy tablic z wynikami w PHP. Przykład możemy jednak podać także bez użycia PHP. MySQL posiada wiele wbudowanych funkcji, których często nie używa się, chociaż są szyb-sze i równie sprawne, jak te zaimplemento-wane w PHP:

SELECT count(*) AS rekordow FROM tabela;

Zapytanie takie zwróci wartość funkcji count(*), która to z kolei zwraca ilość rekor-dów w tablicy. Posługiwanie się jednak na-stępnie wynikami takiego zapytania z ko-lumną o nazwie count(*) byłoby co najmniej niewygodne. Dlatego, przypisujemy jej alias rekordow. Wynikiem będzie jeden rekord z liczbą wierszy tabeli tabela. Funkcji count() za argument możemy podać także nazwę ta-beli lub pewne zapytanie. Na przykład, je-śli poprzedzimy nazwę kolumny słowem distinct, wszystkie duplikaty zostaną zre-dukowane do jednej pozycji:

SELECT count(distinct nazwa) FROM tabela;

Jeśli w tabeli tabela będziemy mieć kilka re-kordów o tej samej wartości w polu nazwa, zwrócona zostanie ilość tylko rekordów uni-kalnych. Wszelkie duplikaty nie zostaną doli-czone do wyniku.

Kolejną przydatną klauzulą jest LIMIT. Jak już pokazaliśmy, określa, ilu maksymalnie re-kordów ma dotyczyć zapytanie. Jeśli użyje-my jej w SELECT, będzie określać maksymalną

ilość zwróconych wyników. Klauzula LIMIT może jednak przyjmować także dwa parame-try. W takim wypadku, pierwszy parametr określa punkt startowy, od którego należy li-czyć ilość maksymalnych wyników określoną przez drugi parametr. Ma to sens, gdy stroni-cujemy dane, np. po 50 na wyników na stro-nie. Dla trzeciej strony wyników, wykonamy zapytanie:

SELECT * FROM tabela ORDER BY cena

DESC LIMIT 2*50, 50;

Takie zapytanie posortuje tabelę względem ceny (malejąco), a następnie zwróci pozycje od 100 do 150, a więc maksymalnie 50 wyni-ków, lecz odpowiednich dla trzeciej strony.

Jeśli za drugi parametr podamy -1, otrzy-mamy wszystkie wyniki począwszy od punktu określonego przez pierwszy parametr.

Zanim przejdziemy do przykładu z ży-cia, nauczmy się jeszcze trochę teorii – cho-ciaż może niezbyt ciekawej, jednak niezwy-kle istotnej. Jeśli nie opanujemy zagadnienia normalizacji bazy danych, już przy pierw-szym projekcie będziemy zmuszeni ponow-nie projektować bazę danych, ponieważ zapy-tania będą wykonywać się zbyt długo i będą zbyt skomplikowane.

Optymalizacja bazy danychW przypadku używania bazy danych, istot-ny jest czas wykonywania zapytań. Jest on w większości przypadków zależny od pro-jektu (architektury) bazy danych – rozkładudanych między tabelami (czy nawet całymi bazami danych na różnych serwerach). Klu-czowe są więc dwa składniki: dobrze zapro-jektowana baza oraz dobrze skonstruowane zapytania. Jakkolwiek to drugie użytkownik musi sam zagwarantować (MySQL umożli-wia podgląd wolnych zapytań w dzienniku), tak przy projektowaniu bazy warto trzymać się pewnych zasad. Jeśli baza jest z nimi zgod-na, zwie się ją znormalizowaną. Zanim wyja-śnię to znaczenie, dodam, że prędkość dzia-łania bazy danych można zwiększyć przy po-mocy polecenia:

OPTIMIZE TABLE tabela;

oraz tworząc indeksy (warto z nich korzy-stać, jednak jeśli użyjemy ich do nieodpo-wiednich danych, tzn. nie będą wykorzysty-wane, będą jedynie zajmować zasoby) oraz stosując minimalistyczne typy danych dla kolumn.

Pierwsza postać normalnaZwana także 1NP, określa, że dane w kolum-nie muszą być niepodzielne. Dla przykładu mamy tabelę z pracownikami (pracownicy). W jednej z jej kolumn (języki) mamy nazwy języków, które umie dany pracownik. DlaRysunek 3. Widok strony głównej http://eldoras.com

Page 19: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

18

MySQL od podstaw

www.phpsolmag.org 19

rekordu Jana Kowalskiego wartość tego po-la będzie miała postać Angielski, Niemiecki,Polski. Taka baza danych jest nieznormalizo-wana – jeśli bowiem będziemy chcieli póź-niej wyszukać pracowników znających an-gielski, będziemy musieli wykonać skompli-kowane i długie czasowo zapytanie. Aby ta-ka baza (w tym wypadku tabela) była zgodna z pierwszą postacią normalną, musimy roz-dzielić języki – po jeden, dla każdego rekor-du. Oznacza to, że po znormalizowaniu bę-dziemy mieć trzy wpisy (dla każdego z języ-ków) dla Jana Kowalskiego! Pozostałe warto-ści pól będą więc się powtarzać dla każdego wpisu – uzyskaliśmy znormalizowanie, ale kosztem zwiększenia ilości danych. Opty-malniej będzie więc, jeśli utworzymy osob-ną tabelę jezyki, a w niej pola id_pracownikaoraz jezyk. Będziemy więc mieć trzy wpi-sy dla Jana Kowalskiego w tej tabeli, jednak jej zawartość będzie niewielka – pierwsze pole będzie zawierało zawsze jego identyfi-kator (taki sam, jak w macierzystej tabeli),natomiast drugie – pojedynczy język. Warto za-znaczyć, że ponieważ wartość id_pracownikabędzie mogła być powielana, nie może on być kluczem głównym tabeli. W macierzystejtabeli pozostanie natomiast jeden wiersz dla Jana Kowalskiego, z którego całkowicie usu-niemy kolumnę języki. Od teraz, by znaleźć jego umiejętności językowe, wykonywać bę-dziemy zapytanie:

SELECT jezyki.jezyk FROM jezyki,

pracownicyWHERE pracownicy.

id_pracownika=13 AND jezyki.id

pracownika=pracownicy.id_pracownika;

Oczywiście, moglibyśmy od razu zapisać ... AND jezyki.id _ pracownika=13, jed-nak jeśli zapytanie będzie bardziej rozbu-dowane, zmiana numeru będzie konieczna we wszystkich miejscach, warto więc od ra-zu posłużyć się relacją. W klauzuli FROM wy-mieniliśmy tabelę pracownicy, chociaż z niej nie pobieramy bezpośrednio danych. Słu-ży nam ona jednak do wykonania zapytania przy budowie relacji, musimy więc ją wy-mienić. Tym sposobem znormalizowaliśmy względem pierwszej zasady bazę danych. Dla przykładu, jeśli teraz będziemy szukać anglojęzycznych pracowników, wykonamy zapytanie:

SELECT pracownicy.imie, pracownicy.nazwisko

FROM pracownicy, jezyki WHERE

jezyki.jezyk='angielski' AND

jezyki.id_pracownika=pracownicy.

id_pracownika;

Postaci normalnych jest kilka, z należy znać pierwszą, a warto – trzy pierwsze. Kolejne postacie są jednak związane z kluczami tabel, są więc zagadnieniem bardziej skomplikowa-

nym i wykraczającym poza granice tego arty-kułu. Jeśli chcemy projektować bazy danych używane często, powinniśmy zaznajomić sięz tymi terminami.

Baza filmów na przykładzie http://www.eldoras.comPrzeanalizujmy teraz strukturę bazy da-nych serwisu skupiającego filmy – http://www.eldoras.com. Dobra architektura pozwoli-ła w tym wypadku na generowanie bardzo róż-norodnych statystyk przy ogromnych możliwo-ściach wyszukiwania. Oczywiście, każda baza danych da się bardziej zoptymalizować, jed-nak ta struktura okazuje się bardzo wydajna na-wet w przypadku 3000 unikalnych odwiedzin dziennie. Kod odpowiedzialny za stworzenietabel widać na Listingu 1.

Listing został wygenerowany przez phpMy-Admin, ma więc trochę inną strukturę od tej, której się uczyliśmy (np. klucz podstawowy jest definiowany dopiero po kolumnach) – efekt jest jednak taki sam. Przeanalizujmy po kolei wszystkie tabele:

• accounts – zawiera dane użytkowników, odpowiednio zakodowane;

• added – posiada id filmu oraz datę jego dodania do serwisu;

• comments_en_0 oraz comments_pl_0 – ko-mentarze w różnych językach zostały,

rozdzielone na dwie tabele, by przyspie-szyć wyszukiwanie. Przy większym obcią-żeniu, można bardziej rozszerzyć podział. Występuje tu tzw. „klucz obcy” – poster_id. Jest to numer konta osoby, która umie-ściła komentarz (jej nick pobierany jest z tabeli accounts);

• links – zawiera odnośniki do miniatur i filmów dla każdego rekordu będącego osobnym filmem;

• performers – zawiera nazwę wykonawcy, wraz z jego identyfikatorem (NIE jest to identyfikator konta, bowiem serwis zakła-da, że wykonawca nie musi być zarejestro-wanym użytkownikiem);

• songs – zawiera nazwy piosenek oraz ich wykonawców wraz z ich identyfikatora-mi. Podział na wykonawców i tytuły po-zwala na generowanie statystyk według wykonawców, natomiast identyfikator jednoznacznie określa jedną piosenkę. Przyspiesza to także wyszukiwanie sa-mych wykonawców lub samych tytu-łów;

• stats – zawiera dane statystyczne dla każ-dego filmu: ilość oglądnięć, ocenę, ilość głosów oraz ich sumę (przydatne w gene-rowaniu nowej oceny);

• videos – jest kluczową tabelą, określają-cą pewne relacje. Dla każdego rekordu(filmu), przypisana jest piosenka (a więc

Listing 4. Tworzenie tabeli o nazwie tabela

CREATE TABLE IF NOT EXISTS tabela (

id_produktu int not null auto_increment primary key,

nazwa varchar(30),

opis text,

data_dodania timestamp not null default CURRENT_TIMESTAMP

) TYPE=MyISAM;

Listing 5. Dodanie pierwszych wierszy w tabeli

INSERT INTO tabela VALUES

(

NULL,

'Cola',

'Napoj gazowany',

NULL

),

(

3,

'Pepsi',

'Napoj gazowany',

NULL

);

Listing 6. Tworzenie tabeli o nazwie ceny

CREATE TABLE IF NOT EXISTS ceny (

id int not null auto_increment primary key,

id_produktu int not null,

cena int not null,

data_dodania timestamp not null default CURRENT_TIMESTAMP

) TYPE=MyISAM;

Page 20: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

20

tytuł i artysta), wykonawca oraz konto użytkownika, który film dodał.

Jak widzimy, zastosowany został silnik InnoDBw celu blokowania na poziomie wierszy– MyISAM wyraźnie nie zdawał egzaminu.

PHP i MySQLPHP w wersji 5 oferuje kilka sposobów po-łączenia się z bazą danych MySQL. Najko-rzystniejszymi rozwiązaniami wydają się PDO i mysqli, które jednak wymagają zna-jomości zagadnienia programowania zo-rientowanego obiektowo (opartego na kla-sach), które z kolei jest techniką dosyć za-awansowaną i nieprzydatną w przypadku małych projektów. Po opanowaniu najważ-niejszych funkcji PHP jest to jednak nie-zbędny przystanek na drodze do zostania poważnym deweloperem. Kod serwisu http://www.eldoras.com został jednak napisany pod PHP 4 (działa także pod PHP 5) i z tegowzględu używa wciąż przydatnych i ułatwiają-cych naukę funkcji.

Przypatrzmy się, jak wygląda obsługa serwi-su z poziomu PHP. Na Listingu 2. widać kododpowiedzialny za wyświetlanie najnowszych filmów na stronie głównej serwisu.

Jak widać, najpierw jest inicjowane połącze-nie z bazą danych znajdującą się pod adresem localhost (czyli na komputerze, na którym uru-chomiony jest serwer HTTP). Nazwa i hasło do-stępu do bazy zostały wycięte z oczywistych względów. Następnie, wybieramy bazę danych (nasz katalog tabel) przy pomocy kolejnej wbu-dowanej funkcji.

Wykonanie zapytania MySQL zlecamy funkcji mysql_query, która zwraca wyniki do zmiennej $query_videos (i kilku innych w dalszych poleceniach). Kod wygląda na nie-zmiernie skomplikowany, jest jednak bardzo intuicyjny. Po SELECT określamy, co chce-my otrzymać. Do wyświetlenia miniatur fil-mów, niezbędne będą: linki do miniaturyi filmu, nazwa wykonawcy, tytuł piosenkii nazwa artysty (przypisywane są od razu alia-sy, by mieć wygodniejsze nazwy indeksówtablic w PHP) oraz id danego filmu – do stwo-rzenia linka do obejrzenia filmu.

Dalej mamy klauzulę FROM – musimyzawrzeć w niej wszystkie tabele, z których ko-rzystamy do pobrania powyższych danych. Sprawdzamy więc, gdzie leżą powyższe da-ne i dopisujemy je. Jedną z tabel jest wy-nik podzapytania, któremu dajemy nazwęlastadded.

Jest ono odpowiedzialne za pobranie iden-tyfikatorów ośmiu ostatnio dodanych fil-mów (stronicowanie realizowane jest co 8filmów, więc zmienna $page określa, na której stronie wyników jesteśmy; jest ona wcześniej odpowiednio filtrowana – domyślnie jest to zmienna $_GET['page']). Dalej, określone są relacje w klauzuli WHERE. Ustalamy więc

wspólne powiązania dla wszystkich danych z tabel wymienionych w FROM – video_idmusi być wspólny, zaś tabela videos zawiera id odpowiadające id piosenek i wykonawców w tabelach z ich danymi tekstowymi, więc głównie z nią tworzymy powiązania. Powią-zanie pierwsze (z lastadded.video_id) gwa-rantuje, że pod uwagę bierzemy tylko osiem ostatnich rekordów i dane z nimi związane. Na końcu, ponownie sortujemy dane wzglę-dem daty dodania. Wynik tego zapytania przechowywany jest w $query_videos.

Kolejne zapytanie zwraca łączną ilość fil-mów w serwisie. Dalszy kod służy do okre-ślenia, jaka liczba ma być przypisana do wy-rażenia na stronie Strona X z Y – X jest zdefi-niowane w odfiltrowanym $page, natomiast Y jest właśnie określane w zmiennej $total_pages – jest to ilość wyników podzielona przez osiem. Intval() służy do zaokrągle-nia wartości – jeśli reszta z dzielenia równa się 0, to przypisanie Y jest łatwe. W innym przypadku filmów jest mniej niż wielokrot-ność 8, więc stosujemy widoczne obliczenie by określić prawidłową ilość stron. Użyliśmy funkcji mysql_fetch_assoc() z argumentembędącym wynikiem zapytania MySQL – wy-nikiem tego jest z kolei tablica o indeksachbędących nazwami kolumn w MySQL (lub ich aliasów, jeśli zostały przypisane).

Jeśli ktoś próbował być sprytny i podał w adresie strony podstronę z wynikami, która nie ma prawa istnieć z racji braku filmów, skrypt kończy działanie. Kolejne zapytania odpowiedzialne są za zliczenie ilości zareje-strowanych użytkowników dodanie jednego wyświetlenia do statystyk oraz pobranie ilo-ści wyświetleń. Czas na zaprezentowanie wy-ników zapytań. Odpowiedzialny za to kod widać na Listingu nr 3.

Widzimy tu niezbyt estetyczny, pomiesza-ny kod PHP i xHTML. Wprawdzie wygląda to fatalnie, jednak przy odpowiednim forma-towaniu i podświetlaniu składni jest całkiem czytelne.

Trzeba jednak zaznaczyć, że pisząc kod w projekcie kilkuosobowym, należy podzielić aplikację na kilka „warstw". Na przykład, sche-mat MVC (ang. Model – View – Controller) za-kłada podział na warstwę biznesową, wyglądu i kontrolną. Dzięki takiemu podziałowi, kod odpowiedzialny za obliczenia nie jest zmiesza-ny z kodem odpowiedzialnym za wygląd itd... Przydatne w tym celu jest także opanowanie użycia szablonów, jest to jednak przydatne w większych, zespołowych projektach. Najpierw napotykamy na dziwny zlepek:

<?=$lang_latest_videos?>

Jest to skrócony odpowiednik <? print

$lang _ latest _ videos; ?>. Zmienne o przedrostku $lang oznaczają tekst zależny od języka – w zależności od wybranej wer-

sji językowej serwisu, wczytywane są róż-ne dane. Następnie napotykamy instrukcję warunkową, która określa, że jeśli jesteśmy na pierwszej lub ostatniej stronie wyników, nie pojawią się napisy Dalej i Wstecz.

Dalej, deklarujemy zmienną $i, która bę-dzie oznaczać ilość filmów wyświetlonych w jednym wierszu (cztery) na stronie z wy-nikami. Używamy pętli while, która przej-dzie przez każdy rekord wyniku. Jest to typowa konstrukcja w połączeniu z mysql_fetch_assoc(), która zawiera wielowymia-rową tablicę (osiem rekordów z filmami, każdy z nich ma swoje dane – tablica więc jest dwuwymiarowa).

Każde wywołanie pętli while przypisze zmiennej $video wartość rekordu z jednym filmem. Dalej znajduje się kod odpowiedzial-ny za wyświetlenie danych w odpowiednich znacznikach (które są potem formatowane pod kątem wyglądu za pomocą arkuszy sty-li CSS). Jeśli pętla while przechodzi czwarty raz, oznacza to, że należy przejść do kolejnego wiersza – kończymy więc listę UL i rozpoczy-namy nową. Na końcu inkrementujemy $i.

Nie jest jednak powiedziane, że zostanie zwróconych dokładnie 8 wyników – jeśli bowiem jesteśmy na ostatniej stronie, mo-że ich być np. 6. Wtedy pętla while zakoń-czy się z $i równym 6. Dlatego definiuje-my kolejną pętlę while – jeśli zostało wy-świetlonych mniej niż 8 miniatur z przypi-sami, zapełniamy pozostałe miejsca obraz-kami „Brak grafiki”. Efekt tego kodu widać na Rysunku 3.

PodsumowanieMySQL oferuje ogromne możliwości, które połączone z PHP tworzą to, co widzimy każ-dego dnia w Internecie – Wikipedię, Yahoo itd... Trudno o lepsze argumenty przema-wiające za nauką tworzenia stron w ramach pakietu AMP (Apache + MySQL + PHP). Tak PHP, jak MySQL zawiera jeszcze mnó-stwo przydatnych funkcjonalności, jednak już informacje zawarte w tych dwóch arty-kułach mogą wystarczyć do stworzenia wła-snej wideogralerii, jak na http://eldoras.com. Zachęcamy do nauki i realizowania swoich pomysłów!

KRZYSZTOF TRYNKIEWICZStudiuje Informatykę na Uniwersytecie Jagielloń-skim. Od wielu lat zajmuje się tworzeniem witryn w technologii PHP oraz Flash. Obecnie rozwija kil-ka równoległych projektów autorskich dostęp-nych na witrynach http://eldoras.com i http://ajax.eldoras.comKontakt z autorem: [email protected]

Page 21: PHP Solutions 05 2007 PL
Page 22: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

22

WordPress

www.phpsolmag.org 23

Tworząc szablon dla nowego bloga, zo-baczymy, że WordPress oferuje bar-dzo wiele funkcji. Nasze szablony

mogą mieć nie tylko inny wygląd niż domyśl-ne, ale także większą funkcjonalność. W ni-niejszym artykule będziemy wykorzystywać wersję 2.2, która w chwili pisania artykułu była najnowszą wersją.

Jak działa system szablonów System skórek w WordPressie oparty jest o szablony i style CSS. Każda skórka posiada własny podkatalog w katalogu wp-content/themes/, w którym znajdują się wszyst-kie pliki skórki. Gdy wchodzimy na stronę, skrypt wybiera odpowiedni plik, a następnie go wyświetla. Obrazki, style i inne dodatko-we pliki także powinny być zawarte w tym katalogu.

Pliki szablonów są zwykłymi plikami PHP, w których wymieszany jest kod xHTMLz kodem PHP. Dzięki licznym funkcjom udo-stępnianym przed WordPress możemy w bardzo prosty sposób wyświetlać różne ele-menty na stronie. Nasza skórka musi posia-dać przynajmniej 2 pliki: index.php i style.css.Pierwszy posłuży jako szablon domyśl-ny wszystkich podstron, a drugi oprócz te-

go oczywiście, że definiuje wygląd strony,zawiera także informacje o skórce. Metada-ne o skórce umieszczamy w komentarzu w pliku style.css. WordPress użyje tych danych do wyświetlenia informacji o skórce w paneluadministratora. Komentarz rozpoczynamy uży-wając /*, a kończymy używając */. Metadane określamy, używając formatu: nazwa: wartość. Do określenia nazwy skórki używamy identy-fikatora: Theme Name. Jeżeli chcemy nazwać naszą skórkę „Moja pierwsza skórka”, odpo-wiednia linia w pliku style.css będzie wyglą-dała jak poniżej.

Theme name: Moja pierwsza skórka Używając podobnych identyfikatorów, może-my dodać następujące informacje:

• adres strony domowej skórki – (Theme URI); • opis skórki – (Description); • wersje – (Version) – opcjonalnie;• autora – (Author);• adres strony autora – (Author URI);• skórka nadrzędna – (Template) – opcjo-

nalnie.

Przypatrzmy się uważniej identyfikato-rowi Template. Jeżeli chcemy, aby nasza skórka dziedziczyła szablony z określo-nej skórki, jako wartość po identyfikato-rze umieszczamy nazwę katalogu wybra-nej skórki. Określenie skórki nadrzędnej spowoduje użycie wszystkich szablonów wybranego schematu, co oznacza, że sza-

blony, które znajdują się w katalogu naszej skórki, zostaną zignorowane.

Hierarchia szablonów Szablony odgrywają w skórkach kluczową ro-lę. Jedne z nich, takie jak nagłówek czy stopkę, wykorzystujemy wielokrotnie. Inne natomiast służą nam do wyświetlenia tylko poszczegól-nych stron. Projektując szablony, musimy wie-dzieć, jakiego pliku użyje WordPress do wy-świetlenia konkretnej podstrony.

Na początku WordPress używa danych przesłanych przez przeglądarkę do zidenty-fikowania typu zapytania. W tym momen-cie dowiaduje się, co chcemy wyświetlić: czy jest to strona (Page), post, kategoria czy może inna podstrona. Później WordPress wyszuku-je kolejno szablony, które może wykorzystać. Jeżeli szablon o zadanej nazwie istnieje, to on zostaje wybrany.

Dla strony głównej naszego bloga Word-Press w pierwszej kolejności użyje plikuhome.php. Jeżeli taki plik nie istnieje, poszuka index.php. Pojedynczy post w pierwszej kolej-ności będzie szukał pliku single.php, a jeśli ta-kiego nie ma, zostanie użyty index.php. Stro-ny (Page) dają nam większe możliwości kon-figuracji. W pierwszej kolejności WordPressbędzie szukał pliku szablonu wybranego w pa-nelu administratora. Abyśmy mogli wybrać szablon z panelu administratora, musimy na początku pliku szablonu (np custom.php) w ko-mentarzu po identyfikatorze Template Name: umieścić nazwę naszego szablonu, np.:

/*

Template Name: My custom template

*/

Jeżeli taki plik nie zostanie znaleziony, WordPress będzie szukał pliku page.php,a na końcu index.php. Dla każdej katego-rii możemy użyć osobnego pliku szablonu.Jeżeli WordPress znajdzie plik o nazwie

WordPress

WordPress jest skryptem bloga oferującym ogromne możliwości tworzenia i przystosowania strony do swoich potrzeb. W Internecie jest wiele szablonów gotowych do wykorzystania na naszym blogu. Co jednak,gdy żaden nam nie odpowiada? Pozostaje nam wykonanie własnego.

Dowiesz się...• W jaki sposób zbudować własną skórkę do sys-

temu WordPress. • Jak zbudowane są szablony oraz jakie możliwo-

ści daje nam stworzenie własnej skórki. • Poznamy najczęściej używane funkcje i nauczy-

my się wykorzystywać komentarze.

Powinieneś wiedzieć...• Czytelnik powinien znać podstawy języków

PHP i SQL. Przydatna też będzie podstawowa znajomość systemu WordPress.

Poziom trudności

Tworzymy skórkę

Page 23: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

22

WordPress

www.phpsolmag.org 23

category-numer_kat.php, gdzie numer_kat jest numerem ID kategorii (np category-3.php), to ten plik zostanie wykorzystany. Jeżeli nie utworzyliśmy takiego pliku, WordPress będzie sprawdzał kolejno pliki: category.php,archive.php, a na końcu index.php. Do wyświe-tlenia strony o autorze WordPress kolejno sprawdza szablony: author.php, archive.php oraz index.php. Do wyświetlenia postów z okre-ślonej daty WordPress będzie sprawdzał pli-ki date.php, archive.php i na końcu index.php. W przypadku wyszukiwania zostanie użyty plik search.php. Jeśli takiego pliku nie znale-ziono, WordPress użyje domyślnego szablonuindex.php. Jeżeli dana podstrona nie została zna-leziona, zostanie wykorzystany plik 404.php,a jeśli takiego nie ma – index.php.

Umiejętne wykorzystanie hierarchii sza-blonów pozwala na budowę zaawansowa-nych i bardzo zróżnicowanych stron. W ram-ce Linki znajduje się adres strony, na której znajdziemy diagram ilustrujący tę hierarchię. Oprócz hierarchii szablonów WordPress ofe-ruje także specjalne funkcje, które pozwa-lają na poziomie konkretnego pliku szablo-nu sprawdzić, która strona jest wyświetlana.Z tymi funkcjami zapoznamy się w dalszej części artykułu.

Struktura strony Zastanówmy się nad tym, jak wygląda struk-tura pojedynczej podstrony bloga. Najprost-sza strona zawiera takie elementy jak nagłó-wek, treść i stopka strony. Na początku każ-dego pliku musi znaleźć się wywołanie funk-cji get_header, która wyświetli nam odpo-wiedni nagłówek. Jeżeli w katalogu skór-ki znajduje się plik header.php, zostanie on wczytany. Jeżeli go nie stworzyliśmy, zosta-nie dołączony domyślny plik. Podobnie jak w przypadku nagłówka – jeżeli stworzyliśmy plik footer.php zostanie on dołączony zamiast domyślnej stopki.

Większość stron zawiera także panel boczny. Jego kod znajduje się w pliku sidebar.php. Jeże-li takiego pliku nie umieścimy w katalogu skór-ki, to ponownie zostanie użyty domyślny plik. Aby dołączyć panel boczny, używamy funkcji get_sidebar.

Strona może zawierać także inne elemen-ty. Do ich dołączania stosujemy na przykład funkcję include. Gdybyśmy – dajmy na to – chcieli dołączyć formularz wyszukiwania zawarty w pliku searchform.php, musielibyśmy wywołać funkcję w następujący sposób:

include( TEMPLATEPATH .'/searchform.php' );

Stała TEMPLATEPATH zawiera ścieżkę do katalo-gu naszej skórki.

Plik szablonu Stworzymy teraz nasz pierwszy plik szablonu. Index.php w katalogu naszej skórki to domyślny

szablon, który jest używany, gdy nie znaleziono bardziej szczegółowego szablonu. Wiemy już, jak wyświetlić nagłówek i stopkę. Wykorzysta-my do tego funkcje get_header i get_footer. W tym momencie domyślne pliki są wystar-czająco dobre. Domyślny plik z nagłówkiemwyświetli nam oprócz odpowiednich tagówmeta tytuł i opis bloga. Domyślna stopka wy-świetli nam oprócz informacji niezbędnych w stopce liczbę zapytań oraz czas generowania strony.

WordPress przed wyświetleniem szablonu wy-wołuje zapytanie do bazy, pobierając odpowied-nie dane za bazy danych. Sam analizuje infor-macje przesłane do skryptu i tworzy odpowied-nie zapytanie. Aby sprawdzić, czy dostępne są ja-kieś posty, używamy funkcji have_posts. Jeżeli są dostępne do wyświetlenia posty, zwraca true, jeśli nie ma zwraca false. Używając tej funkcji w pętli while jako warunku, sprawdzamy, czy dostępne są kolejne posty do wyświetlenia, a na-stępnie używając funkcji the_post, „pobieramy” post. Automatycznie aktualizuje ona niezbęd-ne dane, tak abyśmy mogli wykorzystywać funk-cje takie jak the_title czy the_ID. Tytuł postawyświetlamy, używając funkcji the_post, a aby wyświetlić treść, używamy funkcję the_content. Więcej przydatnych funkcji poznamy w dalszej części artykułu.

Zbierzmy teraz wszystkie poznane infor-macje i stwórzmy nasz pierwszy szablon. Jego

kod możemy zobaczyć na Listingu 1. Szablon ten jest bardzo wybrakowany. Brakuje w nim nawigacji między poszczególnymi strona-mi, nawigacji między postami ijakiejkolwiekobsługi komentarzy. Odpowiednie funkcje do wykonania tych zadań poznamy w dalszej części artykułu.

Przydatne funkcje Poznamy teraz wybrane funkcje, które po-zwolą nam tworzyć szablony. Pierwszą będzie bloginfo. Wyświetla ona opcje ustawionew panelu admina. Jako argument przyjmu-je nazwę opcji, której wartość ma wyświetlić.Jako parametr możemy podać:

• name – funkcja wyświetli nazwy blogu; • description – funkcja wyświetli opisu

blogu; • url – funkcja wyświetli URL do strony

bloga; • rdf _ url – funkcja wyświetli URL do

RDF/RSS 1.0;• rss _ url – funkcja wyświetli URL do

RSS 0.92;• rss2 _ url – funkcja wyświetli URL do

RSS 2.0;• atom _ url – funkcja wyświetli URL do

Atom feed?;• comments _ rss2 _ url – funkcja wyświe-

tli URL do RSS 2.0 feed dla komentarzy;

Listing 1. Pierwszy szablon

<?php

//Wyświetlamy nagłówek

get_header();

?>

<?php

//Sprawdzamy, czy są dostępne jakiekolwiek posty

if (have_posts()) :

//Wyświetlamy posty w pętli

while (have_posts()) : the_post();

?>

//Wyświetlamy tytuł i treść

<div class="post" id="post-<?php the_ID(); ?>">

<h3 class="title"><?php the_title(); ?></h3>

<div class="content">

<?php the_content(); ?>

</div>

</div>

<?php endwhile; else: ?>

<p>

<?php

//Wyświetlamy informację o braku postów

echo 'Brak postów spełniających twoje kryteria';

?>

</p>

<?php endif; ?>

<?php

//Wyświetlamy stopkę

get_footer();

?>

Page 24: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

24

WordPress

www.phpsolmag.org 25

• pingback _ url – funkcja wyświetli URL dla Pingback;

• admin _ email – funkcja wyświetli adres e-mail administratora;

• charset – funkcja wyświetli nazwę kodo-wania znaków;

• version – funkcja wyświetli wersję WordPressa;

• html _ type – funkcja wyświetli zawar-tość „Content-type”;

• wpurl – funkcja wyświetli URL dla insta-lacji WordPressa;

• template _ url – funkcja wyświetli URL do używanego szablonu;

• template _ directory – funkcja wyświetli URL do katalogu szablonu;

• stylesheet _ url – funkcja wyświetli adres URL do pliku style.css w twoim szablonie;

• stylesheet _ directory – funkcja wy-świetli URL do katalogu ze stylami.

Jeżeli zamiast wyświetlenia powyższychinformacji chcemy je pobrać do zmiennej, po-winniśmy użyć funkcji get _ bloginfo.

Gdy będziemy chcieli wyświetlić tytułbloga, zastosujemy wp_title. Jako pierwszyargument możemy podać separator między poszczególnymi fragmentami tytułu. Drugi parametr określa, czy chcemy wyświetlić ty-tuł (true, wartość domyślna), czy też wolimy, aby funkcja zwróciła tytuł jako wynik działania funkcji (wartość false).

Poznamy teraz kilka funkcji, które są szcze-gólnie przydatne przy budowie własnegopanelu bocznego. Bardzo często pierwszym elementem, który zauważamy na panelu bocznym, jest kalendarz. WordPress oferuje nam funkcję get_calendar, która pozwala go wyświetlić. Przyjmuje ona jeden parametr, który określa, czy WordPress ma użyć tyl-ko pierwszej litery nazwy tygodnia (wartość true, np. S zamiast Sun), czy skrótu (wartość false, np. Sun w niedziele). Przy wyświetla-niu dni tygodnia WordPress opiera się o wy-brany język.

Jeżeli zechcemy wyświetlić łącza do archi-wum, najprawdopodobniej wykorzystamy do tego funkcję wp_get_archives. Domyślnie wyświetli ona łącza do archiwum danego mie-siąca, używając listy HTML (znaczniki <li>). Funkcja ta przyjmuje jednak parametry, uży-wając formatu query string, czyli podobnegodo tego, w jakim przekazywane są zmien-ne w adresie URL metodą GET. Poszczególneparametry rozdzielone są znakiem &, a wartość poprzedzona jest nazwą parametru i znakiem =.Jeżeli chcemy wyświetlić linki określonego rodzaju, używamy identyfikatora type. War-tościami, które możemy użyć dla tego argu-mentu. są:

• yearly – wyświetla łącza do archiwum grupowanego po latach;

• monthly – wyświetla łącza do archiwum grupowanego po miesiącach (wartośćdomyślna);

• daily – wyświetla łącza do archiwum dziennego;

• weekly – wyświetla łącza do archiwumtygodniowego;

• postbypost – wyświetla łącza do postów.

Możemy także ograniczyć liczbę wyświetla-nych linków. W tym celu używamy identyfi-katora limit. Do wyświetlenia ostatnich 12 miesięcy możemy użyć poniższego wywoła-nia funkcji.

wp_get_archives('type=monthly&limit=12');

Listing 2. Plik sidebar.php. Kod panelu bocznego

<div id="sidebar">

<ul>

<li><h2>Archiwa</h2>

<ul>

<?php

//Wyświetlamy archiwa

wp_get_archives('type=monthly'); ?>

</ul>

</li>

<li><h2>Kategorie</h2>

<ul>

<?php

//Wyświetlamy kategorie

wp_list_categories('sort_column=name&optioncount=1&hierarchical=0'); ?>

</ul>

</li>

<?php

//Wyświetlamy linki

get_links_list(); ?>

<li><h2>Meta</h2>

<ul>

<?php

//Wyświetlamy link do rejestracji (domyslnie wyświetla link pomiędzy <li> i </li>

wp_register(); ?>

<li><?php

//Wyświetlamy link logowania/wylogowania

wp_loginout(); ?>

</li>

</ul>

</li>

</ul>

</div>

Listing 3. Fragment kodu, który pozwala wyeksponować posty z kategorii 3

<?php

if (have_posts()) :

while (have_posts()) : the_post();

//Sprawdzamy, czy post należy do kategorii 3. i wyświetlamy odpowiedni tekst

if( in_category(3) )

echo '<div class=”featured”>';

else

echo '<div class=”post”>';

//Wyświetlamy post

?>

<h1><?php the_title();?></h1>

<div class=”content”><?php the_content(); ?></div>

</div>

<?php

endwhile;

endif;

?>

Page 25: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

24

WordPress

www.phpsolmag.org 25

Kolejnym parametrem, który możemy podać, jest format. Identyfikator format może przyj-mować następujące wartości:

• html – domyślna wartość, używa listy HTML do wyświetlenia linków (znacznik <li>);

• option – używany jest do budowania pól SELECT (znacznik <option>);

• link – używa znacznika <link>;• custom – używa wartości podanych dla

parametrów before i after.

Ostatnim identyfikatorem jest show _ post _

count, który odpowiada za wyświetlenie licz-by postów w archiwach. Działa ze wszyst-kimi wartościami type oprócz postbypost.Jeżeli wartość show _ post _ count wynosi 1, WordPress wyświetli liczbę postów. Jeżeli nie chcemy wyświetlenia tej informacji, podaje-my wartość zero. Wartość zero jest domyśl-ną wartością, więc jeśli nie podamy tego para-metru, to WordPress domyślnie nie wyświetli liczby postów.

Gdy chcemy wyświetlić listę stron (Pages), powinniśmy użyć funkcji wp_list_pages. Funkcja ta przyjmuje parametry w sposób identyczny jak wp_get_archives, używając tak formatu zapytania (ang. query string). Aby określić wyświetlany tytuł listy, usta-wiamy odpowiednią wartość dla identyfika-tora title_li. Możemy także posortować strony, używając identyfikatora sort_column oraz odpowiednich wartości:

• post _ title – domyślne, sortowaniewedług tytułu strony;

• menu _ order – sortowanie według tzw. Page Order ustawianego w panelu admini-stratora;

• post _ date – sortowanie według daty utworzenia;

• post _ modified – sortowanie według daty ostatniej modyfikacji;

• ID – sortowanie według numeru ID;• post _ author – sortowanie według nu-

meru ID autora;• post _ name – sortowanie według tzw.

Post slug.

Kolejność sortowania ustawiamy, używając identyfikatora sort _ order i wartości:

• asc – sortowanie rosnące;• desc – sortowanie malejące.

Używając identyfikatora exclude, możemy wykluczyć pewne strony, podając jako war-tość rozdzielone przecinkami ich numery ID. Jeżeli chcemy wyświetlić tylko wybrane stro-ny, możemy to uczynić, wykorzystując iden-tyfikator include. Jako wartość przyjmu-je on – podobnie jak identyfikator exclude – rozdzielone przecinkami numer ID stron.

Identyfikator depth określa pokazywaną głę-bokość drzewa strony. Ustawienie wartości na 1 pokaże tylko strony najwyższego rzędu, wartości 2 – strony najwyższego rzędu i ich podstrony. Domyślnie ustawiona jest wartość 0, co sprawia, że wyświetlane jest pełne drze-wo wraz z wcięciami. Ustawienie wartości na -1 powoduje wyświetlenie wszystkich stron bez wcięć.

Gdy chcemy wyświetlić tylko podstrony konkretnej strony, używamy identyfikatora child_of. Jako wartość możemy podać nu-mer ID strony, której podstrony chcemy po-kazać. Kolejnym ważnym atrybutem jest echo, które określa, czy lista ma zostać wyświetlo-na (domyślnie wartość 1) czy zwrócona przez funkcję, (wartość 0). Funkcja ta jest odpowie-dzialna za jeszcze kilka innych atrybutów, np.wyświetlanie daty, obsługę pól Custom Field Key i Custom Field Value.

Aby wyświetlić listę kategorii, powinni-śmy użyć wp_list_categories. Podobnie jak przy kilku poprzednich funkcjach tu-taj także przekazujemy argumenty, używa-jąc odpowiednio sformatowanego ciągu zna-ków. Do określenia kolejności wyświetlania kategorii służy identyfikator orderby, który pozwala sortować według numeru ID (war-tość id), nazwy kategorii (wartość name) oraz liczby postów (wartość count). Aby określićkolejność wyświetlania, używamy identyfi-

katora order, podając jako wartość ASC (ko-lejność rosnąca) lub DESC (kolejność maleją-ca). Aby pokazać puste kategorie, ustawiamywartość hide_empty na 0 (domyślnie Word-Press używa wartości 1). Do wyświetle-nia podkategorii wybranej kategorii uży-wamy identyfikatora child_of, który jako wartość przyjmuje numer ID kategorii nad-rzędnej. Podobnie jak w przypadku funkcji wp_list_pages – możemy użyć identyfikato-rów title_li, include oraz exclude. Mamy również możliwość ograniczenia liczby wy-świetlanych kategorii, używając identyfikato-ra number i podając jako wartość maksymalną liczbę wyświetlanych kategorii.

Aby wyświetlić listę autorów, używamy funkcji wp_list_authors. Przyjmuje ona jeden parametr, który określa różne opcje. Formattego ciągu znaków jest identyczny jak w przy-padku funkcji wp_list_pages. Najważniejszy-mi parametrami, które możemy ustawić, są: optioncount (wartości 1 lub 0) – umożliwiają-ca pokazanie listy postów, oraz show_fullname (wartości 1 lub 0) – nakazująca wypisanie imie-nia i nazwiska autora zamiast nicka.

Do zakończenia budowy naszego panelu bocz-nego brakuje nam jeszcze tylko listy linków. Do ich wyświetlenia służy funkcja get_links_list. Jako jedyny parametr możemy przekazać to, we-dług jakiej kolumny zostaną uporządkowane linki. Poprawnymi ergumentami są wartości:

Listing 4. Dwie pętle z postami z użyciem rewind_post

<?php

if (have_posts()) :

while (have_posts()) : the_post();

if( in_category(3) )

echo '<div class=”featured” >'.the_content().'</div>;

endwhile;

endif;

rewind_posts();

if (have_posts()) :

while (have_posts()) : the_post();

if( !in_category(3) )

echo '<div class=”post” >'.the_content().'</div>;

endwhile;

endif;

?>

Listing 5. Wykorzystanie funkcji query_posts

<?php

//Zapytanie i dalej standardowe wyświetlenie treści

query_posts('cat=3&showposts=3');?>

<?php if (have_posts()) : ?>

<?php while (have_posts()) : the_post(); ?>

<div class="item">

<a href="<?php the_permalink() ?>" rel="bookmark" title="Stały link do <?php the_

title(); ?>"><?php the_title(); ?></a><br/>

<?php the+excerpt(); ?>

<?php endwhile; ?>

<?php endif; ?>

</div>

Page 26: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

26

WordPress

www.phpsolmag.org 27

id (numer ID linku) oraz name (tytuł linku). Do-myślnie linki zostaną wyświetlone w kolejności rosnącej. Aby wyświetlić je w kolejności maleją-cej, musimy poprzedzić nazwę kolumny znakiem _ (np _id lub _name). Na Listingu 2. możemy zo-baczyć przykładowy kod panelu bocznego.

Wśród licznych funkcji szablonów możemy znaleźć takie, które działają tylko w pętli z po-stami (w szczególności po użyciu the_post).

W poprzedniej części poznaliśmy kilka ta-kich funkcji. Były to funkcje umożliwiające wyświetlenie ID posta (the_ID), tytuł posta (the_title) czy treść (the_title). WordPress oferuje wiele podobnych funkcji. Do najważ-niejszych należą:

• the _ category – wyświetlenie kategorii (jako pierwszy parametr podajemy separa-

tor kategorii, jako drugi sposób wyświetla-nia linków podkategorii);

• the _ permalink – adres trwałego adresu do postu;

• the _ excerpt – wyświetla fragment tre-ści posta;

• the _ autor – autor posta (warto poznać także inne funkcje, jak np.: the _ author _

firstname, the _ author _ lastname, the _

author _ ID, itd.);• oraz the _ time – czas dodania posta

(jako argument przyjmuje format czasu i daty zgodny z funkcją date).

Większość tych funkcji ma także odpowied-niki, które zwracają dane zamiast je wyświe-tlać np. get _ the _ content, get _ the _

title, itd. WordPress oferuje także funk-cje pozwalające na nawigację miedzy posta-mi. Next _ post _ link wyświetla następny post, a _ post _ link – poprzedni. Pierwszy parametr to format wyświetlania linku, a miejsce wstawienia znacznika <a> oznacza-my tekstem %link. Jeżeli podamy wartość Przejdź do %link, to WordPress wygeneruje kod Przejdź do <a href=”...”>...</a>. Do-myślnie skrypt przyjmuje format &raquo; %link. Jako drugi parametr podajemy for-mat tytułu linku. Miejsce wstawieniatytułu posta oznaczamy tekstem %title. Domyślnie wyświetlany jest tylko tytuł posta. Trzeci argument określa, czy na-stępny post ma być tylko z tej samej kate-gorii (wartość true), czy także z innych(wartość false). Domyślnie WordPress przyjmuje wartość false, jeżeli nie podamy wartości tego parametru. Ostatni parametr, jaki możemy przekazać, to numery ID kate-gorii, jakie chcemy wykluczyć. W wersji 2.2 numery ID powinny być rozdzielone prze-cinkiem (np. '4, 5, 6'). We wcześniejszych wersjach separatorem był tekst and (np.'4 and 5 and 6').

WordPress udostępnia jeszcze szereg innych funkcji. Dodatkowo pewne rozszerzenia doda-ją własne funkcje, których możemy używać w szablonach. W tej części artykułu poznaliśmy te najczęściej wykorzystywane. W dalszej czę-ści artykułu pojawią się jeszcze inne przydat-ne funkcje.

Pętla z postami i własne zapytania Pętla z postami jest najważniejszym elemen-tem treści niemal każdej strony WordPressu. Wcześniej poznaliśmy kilka przydatnych funk-cji, które możemy wykorzystywać tylko w pętli. Kluczowymi funkcjami dla pętli są have_posts i the_post, które zostały opisane już wcześniej. Teraz nauczymy się rozwiązywać przy pomocy wybranych funkcji różne problemy.

Załóżmy, że na stronie głównej nasze-go bloga chcielibyśmy wyeksponować w szczególny sposób posty z jednej kategorii.

Listing 6. Przykład pliku szablonu komentarzy

<h2 id="comments"><?php comments_number(__('Brak komentarzy'), __('1 komentarz'),

__('% komentarzy')); ?>

<?php

//Sprawdzamy, czy są komentarze

<?php if ( $comments ) : ?>

<ol id="comments">

<?php

//Przechodzimy pętlą po komentarzach i wyświetlamy komentarze

foreach ($comments as $comment) : ?>

<li id="comment-<?php comment_ID() ?>">

<?php comment_text() ?>

<p><cite>przez <?php comment_author_link() ?>, <?php comment_date() ?>

<?php comment_time() ?></cite> <?php edit_comment_link('Edytuj', ' |'); ?></p>

</li>

<?php endforeach; ?>

</ol>

<?php else : // Jeżeli nie ma jeszcze komentarzy ?>

<p>Brak komentarzy</p>

<?php endif; ?>

<?php

//Jeżeli można komentować

if ( comments_open() ) : ?>

<h2 id="postcomment">Komentuj</h2>

<form action="<?php echo get_option('siteurl'); ?>/wp-comments-post.php" method="post"

id="commentform">

<?php

//Jeżeli użytkownik jest zalogowany

if ( $user_ID ) : ?>

<p>Zalogowany jako <a href="<?php echo get_option('siteurl'); ?>/wp-admin/

profile.php"><?php echo $user_identity; ?></a>.</p>

<?php else : ?>

<p><input type="text" name="author" id="author" value="<?php echo $comment_author; ?>"

/>

<label for="author"><small>Autor</small></label></p>

<p><input type="text" name="email" id="email" value="<?php echo $comment_author_email;

?>" />

<label for="email"><small>Adres e-mail (nie zostanie opublikowany) </small></label></

p>

<p><input type="text" name="url" id="url" value="<?php echo $comment_author_url; ?>"

/>

<label for="url"><small>Strona WWW</small></label></p>

<?php endif; ?>

<p><textarea name="comment" id="comment"></textarea></p>

<p><input name="submit" type="submit" id="submit" value="Komentuj" />

<input type="hidden" name="comment_post_ID" value="<?php echo $id; ?>" />

</p>

<?php do_action('comment_form', $post->ID); ?>

</form>

<?php else : // Komentowanie nie jest dozwolone ?>

<p>Nie można komentować tego postu</p>

<?php endif; ?>

Page 27: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

26

WordPress

www.phpsolmag.org 27

W jaki sposób możemy to osiągnąć? Każ-dy post może znajdować się w kilku kate-goriach. Wyobraźmy sobie, że dysponujemykategorią „Featured”, której posty chcemy wyeksponować. Załóżmy też, że kategoria ta ma ID równe 3. Do sprawdzenia, czy dany post jest w naszej kategorii, użyjemy funk-cji in_category. Przyjmuje ona jako para-metr numer ID kategorii i sprawdza, czyaktualnie przetwarzany post w pętli należy do tej kategorii. Na Listingu 3. możemy zoba-czyć, jak wygląda odpowiedni fragment ko-du. Wszelkie elementy nawigacyjne zostały celowo pominięte.

Warto wspomnieć jeszcze, że gdybyśmy używali tego pliku jako szablonu nie tyl-ko dla strony głównej, ale także dla innych podstron, musimy sprawdzić, czy jesteśmy na stronie głównej. Aby to uczynić, używa-my funkcji is_home. Gdybyśmy o tym za-pomnieli, nie moglibyśmy wyświetlić listypostów z kategorii „Featured”. Odpowiedni warunek powinien wyglądać tak:

if( is _ home() && in _ category(3) )

Wyobraźmy sobie, że chcielibyśmy wy-świetlić najpierw najnowsze posty z kate-gorii „ Featured”, a dopiero potem pozosta-łe. Wykorzystamy tutaj ponownie funk-cję in _ category. Aby „przewinąć” posty i rozpocząć pętlę od nowa, musimy wyko-rzystać funkcję rewind _ posts. Dzięki te-mu znowu będziemy mogli w taki sam spo-sób jak w pierwszym przypadku wykorzy-stać funkcje have _ posts i the _ post. Na Listingu 4. możemy zobaczyć kod wykonu-jący dwie pętle.

Może się zdarzyć jednak tak, że oprócz naj-nowszych postów będziemy chcieli wyświetlić dokładnie 5 najnowszych postów z jednej wy-branej kategorii. Wtedy będziemy musieli – po-dobnie jak wcześniej – wykonać pętlę dwa razy. Musimy jednak wywołać nowe zapytanie. W tym celu wykorzystamy funkcję query_posts. Jako jedyny parametr przyjmuje ona ciąg zna-ków określający, co ma zostać pobrane, ile po-stów i jak mają zostać uszeregowane ponow-nie, wykorzystując format query string. Z tych informacji WordPress sam tworzy zapytanie SQL. Jakie zatem informacje możemy przeka-zać tej funkcji?

Rozpoczniemy od podania kategorii, z któ-rej chcemy pobrać posty. Do tego służy identy-fikator cat. Odpowiednie wywołanie tej funk-cji, aby pobrać tylko posty z kategorii 3., wyglą-dałoby następująco.

query_posts('cat=3');

Jeśli chcielibyśmy pobrać posty z kilku katego-rii, musimy oddzielić je przecinkami. Poniż-szy kod pozwala nam wyświetlić posty z kate-gorii 3., 5. oraz 7.

query_posts('cat=3,5,7');

Jeżeli chcemy wykluczyć pewną kategorię,dodajemy znak – przed numerem kategorii. Używając poniższego wywołania, wyświetlimy posty z wszystkich kategorii z wyjątkiem 3.

query_posts('cat=-3');

Jeżeli chcemy zamiast ID kategorii podać jej nazwę, musimy zamiast cat zastosować category _ name. Poniższe wywołanie funkcji wyświetli posty z kategorii „Featured clips”.

query_posts('category_name=Featured clips')

Możemy także wyświetlić posty konkretne-go autora. Używając author i podając ID au-tora lub używając author _ name i podając wartość z kolumny user _ nicename w tabeli użytkowników.

query_posts('author=3');

query_posts('author_name=Kodie');

Możemy także pobrać pojedynczy post uży-wając p i jako argument podając numer IDposta lub używając name i podając tzw. post slug, czyli uproszczoną tytuł posta. Poniższe wywołanie funkcji spowoduje pobranie posta o numerze ID równym 3.

query_posts('p=3')

Natomiast wywołanie

query_posts('name=first-post');

spowoduje pobranie posta o uproszczonej na-zwie first-name. Możemy także chcieć pobrać wybraną stronę. W tym celu wykorzystamy page _ id.

query_posts('page_id=2');

Przy pomocy query _ posts możemy tak-że podać, ile postów chcemy pobrać (np. showposts=3) oraz ile najnowszych postów ma zostać pominiętych (offset=5). Wszyst-kie powyższe części możemy łączyć, używa-jąc znaku &. Aby pobrać np. 5 najnowszych postów z kategorii 3., wywołamy funkcję query _ posts w następujący sposób.

query_posts('cat=3&showpost=5');

Możemy także posortować posty względem innego parametru niż data dodania. Służą do tego orderby do określenia kolumny sor-towania oraz order do określenia porządku sortowania. Funkcja query _ posts ma jesz-cze kilka przydatnych opcji, z którymi war-to się zapoznać.

Są to między innymi wyświetlenie po-stów z wybranego roku (year=2005), miesią-ca (monthnum=6), dnia (day=15) oraz kilka in-nych. Wymienione zostały te najważniejsze parametry, z tymi rzadziej wykorzystywa-nymi możemy zapoznać się w Internecie na stronie: http://codex.wordpress.org/Template_Tags/query_posts.

Wiemy już, w jaki sposób pobrać no-we posty. Teraz zostaje tylko ponownie wykorzystać znane nam już dobrze funk-cje have_posts i the_post. Na Listingu 5.

Listing 7. Plik functions.php

<?php

function my_get_the_image($content){

//Szukamy znacznika <img

$img = strpos( $content, '<img');

//Jeżeli takiego nie ma, zwracamy pusty ciąg znaków

if( $img === false ) return '';

//Szukamy ciąg src=”

$srcpos = strpos( $content. 'src=”', $img ) + 5; /* = strlen('src=”') */

//Pobieramy adres obrazka

$path = substr($content, $srcpos, strpos($content, '”', $srcpos) - $srcpos);

//Zwracamy nową wartość

return '<img src=”'.$path.'” class=”thumb”/>';

}

?>

Listing 8 Komentarze WordPress

$query = new WP_Query();

$query->query('cat=3');

if( $query->have_posts() )

while( $query->have_posts() ){

$query->the_post();

/* ... */

}

Page 28: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

28

WordPress

www.phpsolmag.org 29

możemy zobaczyć kod wykorzystujący funk-cję query_posts.

Co jednak stanie się, jeżeli zapragniemy wykorzystać query_post przed wywołaniem pierwszej pętli? Nie będziemy mogli już wy-korzystać wyniku pierwszego zapytania, gdyż zostanie nadpisany drugim. Jak wybrnąć z tej sytuacji? Są dwa rozwiązania. Musimy wie-dzieć, że funkcje query_posts, have_posts i the_post zapisu wykorzystują wyniki zapisa-ne w zmiennej $wp_query. Możemy zatem za-pisać kopię zmiennej $wp_query w zmiennej tymczasowej, wywołać zapytanie, a potem przywrócić oryginalną zawartość $wp_query. W PHP 4 wystarczy przypisać zmienną$wp_query do innej zmiennej. W ten sposób zostanie stworzona jej kopia:

$temp_query = $wp_query;

Jednak w PHP 5 operator = przypisuje referen-cje, a nie kopie obiektu. Musimy zastosować nowy operator klonowania obiektu: clone.

$temp_query = clone $wp_query;

Następnie wystarczy po pętli przypisać do $wp _ query zawartość $temp _ query.

Niestety, używając tej metody, musimy albo znać wersję PHP, na której będzie umieszczo-na strona, albo sprawdzać wersję przed stworze-niem kopii. Jest jednak inne rozwiązanie. Funk-cje query_posts, have_posts oraz the_post są tylko funkcjami ułatwiającymi działanie na obiekcie WP_Query przechowywanym w zmien-nej $wp_query. Zamiast używać tych funk-cji możemy samodzielnie stworzyć obiekt WP_Query i zamiast funkcji query_post wykorzy-stać metodę query. Zamiast funkcji have_posts i the_post używamy metod tego obiektu o tej sa-mej nazwie. Zatem wywołanie funkcji wraz z pę-tlą powinno wyglądać następująco:

Niektóre wtyczki mają problemy z radze-niem sobie z wieloma zapytaniami. Aby temu zaradzić, powinniśmy wywołać update_post_caches($posts) po wywołaniu metody lub funkcji the_post.

Wiemy już, jak wykonać osobne zapytania i jak poradzić sobie z problemami związanymi

z nadpisywaniem wyników i używaniem wty-czek, teraz zajmijmy się komentarzami.

Komentarze Nieodłączną częścią każdego bloga są komenta-rze. WordPress oferuje gotowe funkcje i rozwią-zania do wykorzystania. Szablon komentarzy znajduje się w pliku comments.php, jeśli jednak go nie stworzymy, WordPress użyje domyślne-go szablonu ze skórki default.

Aby dołączyć komentarze do naszej stro-ny, musimy posłużyć się funkcją comments_template. Jako jedyny parametr możemy po-dać jej nazwę naszego szablonu. Jeśli jej nie po-damy, WordPress automatycznie uzna, że sza-blon nazwaliśmy comments.php. Funkcja ta za-działa tylko, jeżeli nasza strona wyświetla stro-nę, post lub zmienna $withcomments ustawio-na jest na true.

Rozpocznijmy tworzenie szablonu z ko-mentarzami. Przechowywane są one w zmien-nej $comments. Musimy także wiedzieć, że większość funkcji pobierająca lub wyświetla-jąca dane wybranego komentarza pobiera je ze zmiennej $comment. W takim razie naszapętla wyświetlająca komentarze powinna wy-glądać tak:

Na początku tego kodu sprawdzamy, czy są jakieś komentarze, a następnie w pętli wyświe-tlamy je. Teraz poznamy funkcje pozwalające wyświetlić poszczególne elementy komenta-rza. Są one bardzo podobne do funkcji wyświe-tlającej posty. Comment_ID wyświetla numer ID komentarza. Comment_date wyświetla datę do-dania komentarza. Jako jedyny parametr po-dajemy format daty zgodnie z formatem funk-cji date. Jeśli nie podamy formatu, WordPresspobierze format z ustawień bloga. Podobną funkcją jest comment_time, która wyświetla czas dodania posta. Jeżeli nie podamy żadne-go parametru lub przekażemy pusty ciąg zna-ków, WordPress ponownie pobierze formato-wanie czasu z ustawień bloga. Jako drugi para-metr możemy podać, czy chcemy wyświetlić czas lokalny (wartość false), czy czas GMT (wartość true). Domyślnie wyświetlany jest czas lokalny.

Aby wyświetlić autora wraz z podanym przez niego adresem URL, wykorzystujemy

funkcję comment_author_link. Jeżeli nie chcemy wyświetlać linku, a tylko samego au-tora, możemy posłużyć się funkcją comment_author. Sam adres URL możemy wyświe-tlić, używając funkcji comment_author_url, a cały link – comment_author_url_link. Do wyświetlenia kolejno adresu e-mail i łącza do niego używamy odpowiednio comment_author_email i comment_author_email_link. Nie powinniśmy jednak wyświetlać adresue-mail autora komentarzy ze względu na ochronę naszych użytkowników przed spa-mem. Możemy jeszcze pokazać link do edycji komentarza. W tym celu wykorzystamy funk-cję edit_comment_link. Przyjmuje ona trzyargumenty: tekst łącza, tekst wyświetlany przed znacznikiem <a> oraz jako trzeci tekst wyświetlany po znaczniku </a>. Funkcję tę możemy wywołać w następujący sposób:

edit_comment_link('edycja komentarza',

'<p>', '</p>');

Najważnieszją funkcją jest wyświetlenie tre-ści komentarza. W tym celu posłużymy się comment _ text. To są najważniejsze funk-cje obsługi pojedynczego komentarza, warto też zapoznać się z tymi rzadziej używanymi.Informacje o nich możemy znaleźć w plikuwp-includes/comment_template.php oraz w doku-mentacji na stronie http://codex.wordpress.org/Template_Tags#Comment_tags.

Wyświetlając posty, musimy także spraw-dzić, czy dany post nie czeka w kolejce do moderacji. Aby to uczynić, sprawdzamy wartość pola $comment->comment_approved.Jeżeli wynosi ona zero, oznacza to, że komen-tarz nie przeszedł jeszcze procesu moderacji. W tym wypadku powinniśmy wyświetlić sto-sowną informację.

Zajmijmy się teraz innymi funkcjami. Funkcja comments_number oferuje nam moż-liwość wyświetlenia liczmy komentarzy. Przyjmuje ona dokładnie trzy argumenty: tekst do wyświetlenia w przypadku braku komentarzy, tekst do wyświetlenia w przy-padku jednego komentarza oraz tekst do wy-świetlenia w przypadku wielu komentarzy. W tym ostatnim przypadku, aby wskazać miejsce wystąpienia liczby, używamy znaku %. Zatem wywołanie funkcji:

comments_number('Brak komentarzy',

'Tylko jeden komentarz', '% komentarzy');

spowoduje wyświetlenie tekstu „Brak komen-tarzy”, jeżeli nie ma jeszcze żadnego komenta-rza, wyświetlenie tekstu „Tylko jeden komen-tarz”, jeżeli dodano tylko jeden komentarz oraz „15 komentarzy” jeśli dodano 15 komen-tarzy. Inną przydatną funkcją jest comments_rss_link. Jak łatwo się domyślić, pozwala onawyświetlić łącze do kanału RSS z komen-tarzami. Jako pierwszy parametr podajemy

Listing 9. Wywoływanie metody the_post

while( $query->have_posts() ):

$query->the_post();

update_post_caches($posts);

endwhile;

Listing 10. Pętla wyświetlająca komentarze

if( $comments ):

foreach( $comments as $comment ):

//wyświetlenie danych

endforeach;

endif;

Page 29: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

28

WordPress

www.phpsolmag.org 29

tytuł łącza, jako drugi możemy podać skrypt generujący plik RSS. Jeżeli nie podamy te-go pliku, WordPress użyje domyślnego pliku wp-commentsrss2.php. Przed wyświetleniem formularza dodającego komentarze musi-my sprawdzić, czy możemy dodawać ko-mentarze. Jeśli tak, to wartość pola $post->comment_status będzie wynosić “open”.

Jeśli można dodawać komentarze, to wyświe-tlamy formularz. Jako parametr action formu-larza ustawiamy plik wp-comments-post.php. Aby podać dokładny adres, używamy funk-cji get_option podając jako parametr tekst siteurl. Znacznik otwierający formularz mo-że wyglądać następująco:

<form action="<?php echo get_option('

siteurl'); ?>/wp-comments-post.php"

method="post" id="commentform">

Dalej wyświetlamy pola formularza. Pamię-tajmy, że dla zalogowanych użytkowników nie musimy wyświetlać pól: autor, e-mail i strona WWW. Aby to sprawdzić, spraw-dzamy, czy $user _ ID nie jest fałszem lub nie jest pusty. Pola formularza mają ściśle określone nazwy. Zatem pole autor ma na-zwę author, pole e-mail nazwę email, pole strony WWW nazwę url, a pole treści ko-mentarza nazwę comment. Musimy jeszczedodać ukryte pole o nazwie comment _

post _ ID o wartości $id. Jeśli chcemy wy-świetlić listę znaczników, które mogą zostać użyte w komentarzach, używamy funkcji allowed _ tags. Przed znacznikiem zamy-kającym formularz powinniśmy jeszcze dać wywołanie funkcji do _ action, co umożli-wi wtyczkom modyfikacje naszego formula-rza. Wywołanie tej funkcji powinno wyglą-dać następująco:

do_action('comment_form', $post->ID);

Na Listingu 6. możemy zobaczyć przykłado-wy plik wyświetlający komentarze. Gdy pro-jektujemy własną skórkę, zwykle łatwiej jest skopiować plik comments.php ze skórki default i zmodyfikować ten plik niż tworzyć całość od początku.

Functions.php Poznaliśmy już, jak tworzyć pliki szablo-nów. WordPress oferuje nam także dodatko-wy plik functions.php, który jest wczytywany przed szablonami. Może on posłużyć np. do zdefiniowania własnych funkcji wykorzysty-

wanych w szablonach albo do dodania strony w panelu administratora. Kiedy możemy wy-korzystywać taki plik? Możliwości jest wiele. My wykorzystamy tę funkcjonalność, aby wy-świetlić miniaturkę pierwszego obrazka postu(o ile istnieje) przed fragmentem tekstu.

Zastanówmy się, w jaki sposób może dzia-łać taka funkcja? Przede wszystkim pobierze-my adres pierwszego obrazka z treści. Aby pobrać treść, wykorzystamy funkcję get_the_content. Należy pamiętać, że większość wty-czek, które modyfikują treść posta, nie zadzia-ła, gdy używamy get_the_content. Gdy za-leży nam na zadziałaniu wtyczek (np. wtycz-ki galerii), to najłatwiejszym wyjściem będzie użycie funkcji ob_start, wywołanie the_

content, później zapisanie treści do zmien-nej używając funkcji ob_get_contents oraz użycie ob_end_clean do wyczyszczenia bufo-ra. Skupmy się teraz na odnalezieniu obrazka. Mając treść strony, wystarczy odnaleźć znacz-nik <img> a w nim atrybut src. Dla uprosz-czenia przyjmijmy, że dokument jest sforma-towany poprawnie i wartość atrybutu znaj-duje się w cudzysłowie po znaku równości.Takie zadanie można wykonać na wiele spo-sobów. My wykorzystamy najprostszy z uży-ciem funkcji strpos i substr. Nazwiemynaszą funkcję my_get_the_image i zapiszemy ją w pliku functions.php. Jej kod możemy zoba-czyć na Listingu 7.

W kodzie pliku home.php w miejscu, gdzie powinien pojawić się adres obrazka, wstawia-my poniższą linię

echo my_get_the_image( get_the_content() );

Oczywiście obrazek należy przeskalować. Można w tym momencie skorzystać np. ze skryptu phpThumb lub samodzielnie utworzyć miniaturkę i zapisać ją na dysku i zwrócić do niej ścieżkę. Zachęcam do samodzielnego wy-konania takiej funkcji. Dla naszych potrzeb zadowolimy się przeskalowaniem za pomo-cą stylów CSS. Trzeba jednak pamiętać, że to rozwiązanie ma swoje wady.

Używając pliku functions.php, możemy rów-nież dodać własne funkcje do akcji. Służy do tego funkcja add_action. Dzięki niej mamy ogromne możliwości modyfikacji wyświetla-nych elementów zarówno na stronie bloga, jak i w panelu administratora. Nasza funkcjazostanie wywołana w odpowiednim momen-cie, tak że będziemy mogli dodać własne dane.Zobaczymy, jak moglibyśmy dodać do sekcji HEAD naszej strony kod JS lub CSS. Użyjemy

akcji wp_head, która wywołana jest tuż przed zamknięciem znacznika head. Przyjmijmy, że nazwiemy naszą funkcję my_custom_head. Przykładowe wywołanie funkcji add_action może wyglądać tak jak kod poniżej.

add_action('wp_head', 'my_custom_head');

Nasza funkcja my _ custom _ head będzie wy-pisywała treść, używając funkcji echo. Jej kod możemy zobaczyć na Listingu 6. Warto wspo-mnieć, że do jednej akcji możemy przypisać wiele funkcji, które mają zostać uruchomio-ne. Oprócz akcji możemy używać także fil-trów, które pozwalają nam zmodyfikować da-ne przed wyświetleniem. Przykładem takie-go zastosowania może być na przykład filtr zmieniający w treści posta emotikony na ob-razki przed wyświetleniem. Najważniejszą różnicą między filtrami a akcjami jest to, że filtry przyjmują wartość do zmiany jako pa-rametr i zwracają ją używając return. Akcje natomiast nie przyjmują argumentów i wy-świetlają wynik swojego działania bezpośred-nio na stronie (np. przez funkcję echo). Pełnąlistę filtrów i akcji znajdziemy pod adresami podanymi w ramce. Filtry i akcje są przydat-ne, lecz częściej niż w szablonach wykorzystu-je się je przy budowie wtyczek. Pozostaje pyta-nie, kiedy używać wtyczek, a kiedy pliku func-tions.php. Używając obu rozwiązań, możemy stworzyć rozwiązania o podobnej funkcjonal-ności. Musimy pamiętać jednak o tym, że plik functions.php działa tylko dla określonego sza-blonu, a nie dla wszystkich, jak ma to miej-sce z wtyczkami. Plik functions.php jest tylkoformą pomocy szablonom w wyświetlaniudanych czy też stworzeniu pewnych opcji kon-figuracji szablonu. Jednak do modyfikacji da-nych powinniśmy raczej używać wtyczek.

Podsumowanie W artykule starałem się pokazać, że tworzenie szablonów w WordPressie daje znacznie więk-sze możliwości niż tylko zmiana wyglądu. Od-powiednio wykorzystana wiedza pozwala prze-kształcić nasz blog nie do poznania. Niniejszy artykuł jest tylko wprowadzeniem w tematy-kę tworzenia skórek. Zapoznanie się ze strona-mi podanymi w ramce na pewno pomoże jesz-cze rozszerzyć wiedzę i zobaczyć więcej intere-sujących przykładów oraz rozwiązań.

KONRAD GOŁUCHOWSKI Autor od wielu lat zajmuje się programowaniem w języku PHP. Jest administratorem i redaktorem serwisów CompZone.Org i TryCMS.org. Progra-muje także w językach Java i C++. Kontakt z autorem:[email protected], [email protected]

W Sieci

• http://codex.wordpress.org/ – strona dla administratorów i deweloperów WordPressa • http://codex.wordpress.org/Template_Hierarchy – hierarchia szablonów • http://codex.wordpress.org/Plugin_API/Filter_Reference – filtry w WordPressie • http://codex.wordpress.org/Plugin_API/Action_Reference – akcje w WordPressie

Page 30: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

30

Symfony Framework

www.phpsolmag.org 31

Symfony jest frameworkiem typu RAD, dzięki któremu można tworzyć w bardzo szybki i przyjemny sposób aplikacje bazo-

danowe. Pozwala na łączenie się z ważniejszymi typami bazy danych. Proces tworzenia przyszłej aplikacji polega na utworzeniu aplikacji frontend, a następnie modułów użytkownik, posty, osym-fony, kontakt. Podczas tworzenia modułu gene-rowana jest podstawowa klasa akcji, którą w póź-niejszej fazie programowania będziemy systema-tycznie uzupełniać. Będziemy projektowali pro-sty system rejestracji nowego użytkownika, logo-wania oraz dodawania przez niego postu z moż-liwością edycji, jak widać na Rysunku 1. Struk-tura naszego projektu będzie składała się z nastę-pujących katalogów apps, batch, cache, config, da-ta, doc, lib, log, plugins, test, web. W tym artyku-le będziemy głównie zajmować się katalogiem apps, który z kolei będzie składał się na katalog frontend a w nim będą zawarte config, i18n, lib, modules, templates. Następnie katalog modules bę-dzie zawierał wygenerowane moduły m.in. uzyt-kownik. W module tym będą katalogi actions, config, lib, templates i validate. W actions zawar-ta będzie klasa actions.class.php odpowiedzialna za główną funkcjonalność modułu uzytkownik. Z kolei templates będzie zawierał szablony, które będą wykorzystywane w metodach klasy actions. Katalog validate służy do umieszczania plików,

które będą miały za zadanie odpowiednie spraw-dzanie formularzy.

Instalacja frameworku SymfonyDo testów użyjemy wersji sf_sandbox. Jest to pełen pakiet, który wystarczy skopiować na ser-wer i uruchomić w przeglądarce internetowej. W tym celu:

• Pobieramy sf_sandbox.tgz z http://www.symfony-project.com/get/sf_sandbox.tgz, rozpa-kowujemy i kopiujemy do katalogu, z które-go będzie miał dostęp Apache;

• Konfigurujemy wirtualny host w pliku konfiguracyjnym serwera Apache, jak wi-dać na Listingu 1;

• Przeładowujemy serwer Apache.

Jest to bardzo dobre rozwiązanie do testów, nie jest jednak zalecane do aplikacji końcowych, które mają być dostępne szerszemu gronu użyt-kowników. W tym celu lepiej zainstalować symfony ręcznie, jak jest to opisane poniżej.

Instalacja pod LinuksemAby zainstalować poprawnie działające Sym-fony z biblioteką Propel i Creole, należy wyko-nać następujące czynności z poziomu shella z uprawnieniami roota:

• instalujemy PEAR::Log poleceniem cffff;• następnie przy pomocy PEARA instaluje-

my Propel poleceniami:• pear install http://propel.phpdb.org/

pear/propel_generator-current.tgz;

• pear install http://propel.phpdb.org/pear/propel_runtime-current.tgz;

• aktualizujemy bibliotekę PEARA polece-niem pear upgrade PEAR,

podczas aktualizacji PEARA może poja-wić się problem, jeśli mamy starą bibliotekArchive_Tar (poniżej wersji 1.3.1), wtedy na-leży najpierw aktualizować ten pakiet pole-ceniem

pear upgrade http://download.pear.php.net/

package/Archive_Tar-1.3.1.tgz,

kolejnym krokiem jest instalacja Symfony przy pomocy następujących poleceń:

pear channel-discover pear.

symfony-project.com

pear install symfony/symfony

pear install http://phing.info/pear/

phing-current.tgz

(instalujemy pakiet, jeśli nie był wcześniej zainstalowany). Po wykonaniu polecenia symfony – V powinien pojawić się napis z wersją Symfony, w moim przypadku Symfonyversion 1.0.4

Konfigurację wirtualnego hosta wykonu-jemy tak samo jak w przypadku sf_sandbox.Jeśli wszystko przebiegło prawidłowo i instala-cja Symfony zakończyła się sukcesem, to po wy-wołaniu strony ukaże się nam informacja jak na Rysunku 2.

UwagaBardzo ważne jest, aby serwer Apache miał możliwość zapisywania w katalogu cache i log.

Konfigurujemy Symfonyi bazę danychTworzymy bazę danych tak jak na Listingu 2, a następnie konfigurujemy pliki database.ymli propel.ini, jak to widać na Listingu 3. i 4.

Symfony Framework

Frameworki wyrosły jak grzyby po deszczu, ciężko jest czasami dobrać odpowiedni dla danego zadania. Prezentujemy szybki framework Symfony, którego domeną jest to, że został wybrany przez Yahoo! Do stworzenia systemu zakładek dla swoich użytkowników. Żmudne programowanie przechodzi powoli do lamusa.

Dowiesz się...• W jaki sposób zainstalować framework Symfony

oraz jak skonfigurować go do swoich projektów.• Poznasz modułową zasadę budowania aplika-

cji. Przekonasz się, jak prosto możesz budować i sprawdzać formularze.

Powinieneś wiedzieć...• Powinieneś znać podstawy programowania

obiektowego w PHP 5 oraz podstawy Propela. Znajomość CSS może pomóc w analizie strony wizualnej. Umiejętność edycji pliku konfigura-cyjnego Apache'a.

Poziom trudności

Część pierwsza – Moduł Użytkownika

Page 31: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

30

Symfony Framework

www.phpsolmag.org 31

UwagaPrzy konfiguracji plików z rozszerzeniem yml należy używać wyłącznie spacji, nie można używać tabulatora, w przeciwnym razie będzie zgłaszany błąd. W przyszłości więc jeśli będzie-my edytować pliki register.yml i login.yml spraw-dzanie formularza nie zadziała prawidłowo,należy najpierw sprawdzić plik yml, czy czasa-mi nie ma gdzieś tabulatora. Następnie musimy założyć kilka tabel w bazie danych, które będą niezbędne w naszym projekcie. Przede wszyst-kim należy przeanalizować problem. Ogólna funkcjonalność aplikacji będzie następująca:

• rejestracja nowego konta;• panel logowanie;

Rysunek 1. Nasza przyszła aplikacja, widok na formularz rejestracji

Listing 1. Konfiguracja wirtualnego hosta w pliku httpd.conf Apache'a

<VirtualHost 127.0.0.1:80>

# nazwa hosta, po którym będzie wywołanie strony w przeglądarce internetowej

# http://symfony

ServerName symfony

# w tym miejscu podajemy ścieżkę dostępu do katalogu symfony i podkatalogu web

DocumentRoot „/var/www/html/symfony/web”

# definiujemy, że jako pierwszy ma być przeczytany index.php

DirectoryIndex index.php

# definiujemy alias do katalogu sf znajdującego się w katalogu web

Alias /sf /var/www/html/symfony/web/sf

# umożliwiamy dostęp do katalogu web

<Directory „/var/www/html/symfony/web”>

AllowOvveride All

</Directory>

</VirtualHost>

Listing 2. Tworzenie nowej bazy danych i użytkownika.

Logujemy się jako administrator bazy danych MySQL

# mysql -u root -p

Enter password:

Tworzymy bazę danych Symfony

mysql> CREATE DATABASE projekt_symfony;

mysql> use mysql;

Tworzymy nowego użytkownika user bazy danych projekt_symfony o haśle haslo

mysql> GRANT select,insert,update,delete,create,drop,alter ON projekt_symfony.*

to user@localhost IDENTIFIED BY 'haslo';

Query OK, 0 rows affected (0.02 sec)

mysql> FLUSH PRIVILEGES;

Listing 3. Konfiguracja pliku database.yml. Domyślną zawartość pliku zastępujemy poniższymi liniami kodu

All:

propel:

class: sfPropelDatabase

param:

datasources: symfony

Wybieramy bazę danych MySQL

phptype: mysql

hostspec: localhost

Nazwa bazy danych

database: projekt_symfony

Nazwa użytkownika bazy danych

username:user

Hasło użytkownika user

password: haslo

Listing 4. Fragment pliku propel.ini. Ustawiamy następujące linie:

propel.target = lib.model

propel.packageObjectModel = true

;Ustawiamy nazwę taką samą jak baza danych

propel.project = symfony

;Wybieramy typ bazy danych

propel.database = mysql

;Dodajemy użytkownika, hasło i nazwę bazy danych, aby PROPEL mógł się połączyć

;z bazą danych

propel.database.createUrl = mysql://user:haslo@localhost

propel.database.url = mysql://user:haslo@localhost/projekt_symfony

propel.home = .

;Ustawiamy główny katalog naszego projektu

propel.output.dir = /var/www/html/symfonyRysunek 2. Widok po poprawnej instalacji Symfony

Page 32: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

32

Symfony Framework

www.phpsolmag.org 33

• edycja profilu;• przypomnienie hasła (wygenerowanie no-

wego i przesłanie e-mailem);• pisanie nowych postów;• walidacja formularzy;• wysyłanie e-maila z potwierdzeniem zało-

żenia konta;• edycja podstron serwisu przy pomocy apli-

kacji backend.

Będą zatem potrzebne cztery tabele: użytkow-nik, posty, osymfony i kontakt. Wobec tego edy-tujemy plik schema.xml, który znajduje się w katalogu config.

UwagaJeśli w katalogu istnieje plik schema.yml, nale-ży go usunąć, gdyż będziemy korzystać z pli-ku zapisanego w XML-u. Jeśli plik schema.xml nie istnieje, tworzymy go. Na początku musi-my zdefiniować typ dokumentu xml. Ustawia-my również odpowiednie kodowanie znaków: <?xml version=”1.0” encoding=”ISO-8859-2”?>

Strukturę bazy danych zawieramy w znaczni-ku <database></database>:

<database package=”lib.model” name=”propel”

defaultIdMethod=”native” noxsd=”true”>

</database>

Następnie dodajemy tabelę użytkownikmiędzy znacznikami <database></database>.Poszczególne kolumny będą zawarte w znacz-niku <table></table>, gdzie phpName oznacza nazwę, do której będziemy się odwoływać w języku PROPEL. Dla wygody phpNameustawiamy na nazwę tabeli z tą różnicą, że pierwsza litera jest duża. Dzięki temu nazew-nictwu unikniemy wielu problemów, któ-re w późniejszym okresie tworzenia aplika-cji mogą okazać się trudne do zlokalizowania i naprawienia.

<table name=”uzytkownik”

phpName=”Uzytkownik”>

</table>

Do tabeli dodajemy kolumny id, created_at, sha1_password, salt, nick, strona_www, email, pod-pis, potwierdzenie i status. Każda kolumna będzie zapisana w znaczniku <column /> – Listing 5,W podobny sposób dodajemy pozostałe tabele sto-sując się do podanych wytycznych: tabela posty:

• kolumna id typu integer z zaznaczoną opcją required, podstawowym kluczem primaryKey oraz automatyczną numera-cją autoIncrement;

• kolumna id_uzytkownika typu integerz zaznaczoną opcją required;

• kolumna created_at typu timestamp;• kolumna tytul typu varchar o rozmiarze

size równym 255 znakom;• kolumna tresc typu longvarchar.

Listing 5. Zapisanie kolumn w znacznikach <column />

<column name=”id” type=”integer” required=”true”

primaryKey=”true” autoIncrement=”true” />

<column name=”created_at” type=”timestamp” />

<column name=”sha1_password” type=”varchar” size=”40” />

<column name=”salt” type=”varchar” size=”32” />

<column name=”nick” type=”varchar” size=”20” />

<column name=”strona_www” type=”varchar” size=”100” />

<column name=”email” type=”varchar” size=”100” />

<column name=”podpis” type=”varchar” size=”255” />

<column name=”potwierdzenie” type=”integer” size=”5” />

<column name=”status” type=”integer” size=”1” default=”0” />

Listing 6. Metoda executeRegister(). Odpowiedzialna za wyświetlanie formularza rejestracji i zapisywania nowego użytkownika do bazy danych

public function executeRegister() {

// jeśli został wysłany formularz, zapisujemy nowego użytkownika do bazy danych,

// jeśli nie to formularz rejestracji

if ($this->getRequest()->getMethod() == sfRequest::POST) {

// generowanie liczby z podanego przedziału, wymagane przy potwierdzeniu konta

$potwierdzenie = mt_rand(10000,99999);

// zapisywanie danych do tabeli uzytkownik

// tworzymy nowy obiekt klasy Uzytkownik

$uzytkownik = new Uzytkownik();

$uzytkownik->setNick($this->getRequestParameter('nick'));

$uzytkownik->setStronaWww($this->getRequestParameter('strona_www'));

$uzytkownik->setEmail($this->getRequestParameter('email'));

$uzytkownik->setPodpis($this->getRequestParameter('podpis'));

$uzytkownik->setPotwierdzenie($potwierdzenie);

// zapisujemy dane do tabeli użytkownika

$uzytkownik->save();

// wysyłanie e-maila z potwierdzeniem tworzymy nowy obiekt klasy sfMail

$mail = new sfMail();

$mail->initialize();

// ustawiamy sendmail jako główny program do wysyłania poczty, w systemach Linux

// sendmail może być aliasem do np. Postfixa

$mail->setMailer('sendmail');

// ustawiamy kodowanie

$mail->setCharset('iso-8859-2');

// ustawiamy nadawcę

$mail->setSender('[email protected]','Redaktor');

$mail->setFrom('[email protected]','Redaktor');

// dodajemy adres e-mail rejestrowanego użytkownika

$mail->addAddress($this->getRequestParameter('email'));

// ustawiamy temat e-maila

$mail->setSubject('Rejestracja konta i potwierdzenie');

// ustawiamy treść e-maila

$mail->setBody('

Witamy w testowym projekcie opartym na frameworku Symfony,\n\n

Prosimy o potwierdzenie konta. W tym celu kliknij w poniższy link:\n

http://symfony/uzytkownik/potwierdzenie/kod/'.$potwierdzenie.'\n\n

Pozdrawiamy,\n

Redakcja projektu Symfony w akcji ');

// wysyłamy e-mail

$email->send();

// ustawiamy nowy szablon z komunikatem dla użytkownika tuż po pomyślnym

wysłaniu formularza

$this->setTemplate('nowy_uzytkownik');;

} else {

// wyświetlanie formularza z szablonu registerSuccess.php

return sfView::SUCCESS;

}

Page 33: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

32

Symfony Framework

www.phpsolmag.org 33

Tabela osymfony:

• kolumna tresc typu longvarchar.

Tabela kontakt:

• kolumna tresc typu longvarchar.

Kolejnym krokiem jest wywołanie polecenia:

/symfony propel-build-model

w głównym katalogu projektu. Dzięki temu zostanie utworzony podstawowy model klas obsługujących zapytania do poszczególnych tabel w bazie danych, tak jak jest to widocz-ne na Rysunku 3.

Kolejnym krokiem jest stworzenie tabelw bazie danych. Dokonujemy tego przy pomo-cy następujących poleceń:

./symfony propel-build-sql

./symfony propel-insert-sql

Po tych operacjach możemy w końcu zabrać się za programowanie naszej aplikacji. Teraz będzie naprawdę ciekawie.

Aplikacja frontend i jej modułyAplikacja frontend będzie składała się na nastę-pujące moduły:

• użytkownik;• posty;

• osymfony;• kontakt.

Tworzymy moduły za pomocą poleceń:

./symfony init-module frontend uzytkownik

./symfony init-module frontend posty

./symfony init-module frontend osymfony

./symfony init-module frontend kontakt

Każdy z modułów składa się na pięć katalo-gów: actions, config, lib, templates i validate.Najczęściej będziemy używać katalogówactions, templates i validate. W actions za-pisana jest klasa actions.class.php odpowie-dzialna za akcje modułu. Templates zawie-ra szablony poszczególnych metod z klasy actions. Validate z kolei zawiera pliki służą-ce do sprawdzania formularzy.

Moduł użytkownik – klasa actions.class.phpNasza pierwsza metoda executeRegister() będzie umożliwiała wyświetlanie formula-rza rejestracji oraz zapisanie nowego użyt-kownika do tabeli uzytkownik. Metoda za-mieszczona jest na Listingu 6. Z chwilą do-dania użytkownika do bazy danych zo-stanie wysłany e-mail z prośbą potwier-dzenia konta. Wyświetlanie formularza odbywa się za pomocą szablonu register-Success.php, który należy utworzyć w katalo-gu templates modułu użytkownik. Następnie poddajemy go edycji. W pierwszej kolejno-ści definiujemy możliwość sprawdzania for-mularza poprzez plik register.yml umieszczo-ny w katalogu validate. W tym celu plik ten rozpoczynamy linią:

<?php use_helper('Validation') ?>.

Następnie tworzymy nowy formularz przy pomocy funkcji:

<?php echo form_tag('uzytkownik/register') ?>.

Kolejnym krokiem jest utworzenie pola nick:

<?php echo form_error('nick') ?>

<div class=”input-default”>

<label for=”nick”>Nick:</label>

<?php echo input_tag('nick') ?>

</div>.

Jak zwróciliśmy uwagę została wykorzysta-na warstwa z klasą input-default, otóż w na-stępnym artykule jak będziemy już kończyć naszą aplikację zaczniemy pisać style CSS. W identyczny sposób tworzymy pozostałe pola formularza email, podaj hasło, powtórz hasło, strona www i podpis stosując się donastępujących zaleceń (zmieniamy tylko form_error i input_tag): pole email:

Rysunek 3. Utworzenie modelu klas obsługi tabel

Rysunek 4. Widok na formularz rejestracji

Page 34: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

34

Symfony Framework

www.phpsolmag.org 35

Następnie określamy wymogi dla zmiennej nick:

names:

nick:

required: true

required_msg: to pole jest wymagane,

Rysunek 5. Widok niewypełnionego formularza po przesłaniu

<?php echo form_error('email') ?>,

<?php echo input_tag('email')

Pole haslo:

<?php echo form_error('haslo') ?>,

<?php echo

input_password_tag('haslo')

Pole powtorz_haslo:

<?php echo form_error('powtorz_haslo')

?>, <?php echo

input_password_tag('powtorz_haslo')

Pole strona_www:

<?php echo form_error('strona_www') ?>,

<?php echo input_tag('strona_www') ?>

Pole podpis:

<?php echo form_error('podpis') ?>,

<?php echo input_tag('podpis') ?>

Następnie dodajemy ukryte pole referer funkcją:

<?php echo input_tag('referer') ?>.

Musimy również dodać przycisk umożliwia-jący wysłanie formularza. Użyjemy funkcji: <?php echo submit _ tag('zarejestruj') ?>

i na samym końcu dodajemy oczywiście znacz-nik </form>. W trakcie pracy nad różnymi for-mularzami odkryjemy, że wbudowane funk-cje w szablony Symfony są bardzo pomocne i często skracają czas pracy. W tabeli 1 przed-stawiamy tagi html wykorzystywane przy tworzeniu formularzy. Proszę zwrócić uwa-gę jak czytelne i krótkie są tagi Symfony.W pierwszej kolumnie są przedstawione funk-cje umożliwiające tworzenie formularzy a w drugiej zwracane wartości przez nie w posta-ci kodu html.

Mamy już formularz rejestracji. Teraz przy-dałaby się możliwość jego sprawdzania przy wysyłaniu. Zatem tworzymy wcześniej wspo-mniany plik register.yml w katalogu validate.Zaczynamy od definicji zmiennych, które mają być przekazywane z formularza nick, email, haslo, powtorz_haslo, strona_www i podpis:

methods:

post: [nick, email, haslo, powtorz_haslo,

strona_www, podpis]

Wszystkie definicje pól będą występować po sło-wie kluczowym names (wpisujemy tylko raz).

Listing 7. Dodanie nickValidator i checkdbnickValidator

nickValidator:

class: sfStringValidator

param:

min: 4

max: 20

min_error: nazwa użytkownika musi składać się przynajmniej z 4 znaków

max_error: nazwa użytkownika nie może mieć więcej niż 20 znaków

checkdbnickValidator:

class: myRegisternicklValidator

param:

nick: nick

nick_error: nazwa użytkownika już istnieje

Listing 8. Komunikaty zawarte w dyrektywach min_error i max_error

strona_wwwValidator:

class: sfStringValidator

param:

min: 4

max: 255

min_error: adres internetowy musi składać się przynajmniej z 4 znaków

max_error: adres internetowy nie może mieć więcej niż 255 znaków

podpisValidator:

class: sfStringValidator

param:

min: 4

max: 255

min_error: podpis musi składać się przynajmniej z 4 znaków

max_error: podpis nie może mieć więcej niż 255 znaków

Listing 9. Zazwartość pliku myRegisternickValidator.class.php. Proszę zwrócić uwagę na proste odwołania SQL w postaci języka PROPEL

<?php

class myRegisternickValidator extends sfValidator {

public function initialize($context, $parameter = null) {

parent::initialize($context);

$this->setParameter('nick_error', 'Invalid input');

$this->getParameterHolder()->add($parameters);

return true;

}

public function execute(&$value, &$error) {

$nick = $value

// szukanie takiego samego nicka w tabeli uzytkownik, zapytanie w języku PROPEL

$c = new Criteria();

// odwołanie do tabeli uzytkownik do pola nick

$c->add(UzytkownikPeer::NICK, $nick);

$uzytkownik = UzytkownikPeer::doSelectOne($c);

// jeśli istnieje taki nick, to zostanie wywołany błąd

if ($uzytkownik) {

$error = $this->getParameter('nick_error');

return false;

}

return true;

}

?>

Page 35: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

34

Symfony Framework

www.phpsolmag.org 35

wypełnij je

validators: [nickValidator,

checkdbnickValidator]

Pole required określa czy zmienna jest wyma-gana. Z kolei required_msg zwraca informa-cję w przypadku gdy pole będzie puste. Dzię-

Listing 10. Zawartość pliku myRegisteremailValidator.class.php. Proszę zwrócić uwagę na proste odwołania SQL w języku PROPEL

<?php

class myRegisteremailValidator extends sfValidator {

public function initialize($context, $parameter = null) {

parent::initialize($context);

$this->setParameter('email_error', 'Invalid input');

$this->getParameterHolder()->add($parameters);

return true;

}

public function execute(&$value, &$error) {

$email = $value

// szukanie takiego samego e-maila w tabeli uzytkownik,

// zapytanie w języku PROPEL

$c = new Criteria();

// odwołanie do tabeli uzytkownik do pola email

$c->add(UzytkownikPeer::EMAIL, $email);

$uzytkownik = UzytkownikPeer::doSelectOne($c);

// jeśli istnieje taki e-mail, to zostanie wywołany błąd

if ($uzytkownik) {

$error = $this->getParameter('email_error');

return false;

}

return true;

}

?>

Listing 11. Metoda setPassword() odpowiedzialna jest za kodowanie hasła. Nową funkcjonalność dodajemy w pliku Uzytkownik.php, który znajduje się w katalogu symfony/lib/model

public function setPassword($password) {

$salt = md5(rand(100000, 999999).$this->getNick().$this->getEmail());

$this->setSalt($salt);

$this->setSha1Password(sha1($salt.$password));

}

Listing 12. Metoda executePotwierdzenie(), dzięki której nowe konto użytkownika jest akceptowane lub nie

public function executePotwierdzenie() {

// tworzenie nowego obiektu klasy Uzytkownik

$uzytkownik = new Uzytkownik();

$uzytkownik = UzytkownikPeer::retrieveByKod($this->getRequestParameter('kod'));

// gdy zostanie znaleziony użytkownik o podanym kodzie, jego status będzie

// zamieniony z 0 na 1

if($uzytkownik) {

$uzytkownik->setStatus(1);

$uzytkownik->save();

// ustawiamy szablon nowy_uzytkownik_okSuccess.php

$this->setTemplate('nowy_uzytkownik_okSuccess.php');

} else {

// w przeciwnym razie zostanie wyświetlony komunikat o błędzie

// ustawiamy szablon nowy_uzytkownik_errorSuccess.php

$this->setTemplate('nowy_uzytkownik_errorSuccess.php');

}

}

ki dyrektywie validators możemy zdefiniować na jednej zmiennej kilka możliwości testów.W przypadku zmiennej nick zostały wpro-wadzone dwa validatory nickValidator oraz checkdbnickValidator, które będą opisane w dal-szej części artykułu. Podobnie dodajemy wymo-gi dla zmiennej email:

email:

required: true

required_msg: twój email jest wymagany

validators: [checkdbemailValidator]

Następnie ustawiamy wymagania dla zmien-nych haslo i powtorz_haslo:

haslo:

required: true

required_msg: twoje hasło jest wymagane

powtorz_haslo:

required: true

required_msg: ponowienie twojego hasła jest

wymagane

validators: [checkhasloValidator]

Definicja dla zmiennej haslo jest troszkę in-na w porównaniu do powtorz_haslo dlate-go, że validator checkhasloValidator będzie sprawdzał zgodność tych dwóch zmiennych. W momencie gdy hasła nie będą się zgadza-ły zostanie zwrócona informacja o błędziez właśnie tego validatora.

Następne zmienne strona_www i podpis rzą-dzą się tymi samymi wymaganiami, z tą różni-cą, że używają innych nazw validatorów ale o tych samych właściwościach zmienna strona:

strona_www:

required: false

validators: [strona_wwwValidator]

zmienna podpis:

podpis:

required: false

validators: [podpisValidator]

W przypadku tych dwóch zmiennych dyrekty-wy required zaznaczone są na false. Dzięki temu ustawieniu sprawdzane są tylko i wyłącznie w momencie gdy zostaną wypełnione a validatory będą zwracać stosowne informacje w przypad-ku nie spełnienia ich zależności. Następnym krokiem jest tworzenie odpowiednich validato-rów, i tak dla sprawdzania hasła wpisujemy:

checkhasloValidator:

class: sfCompareValidator

param:

check: haslo

compare_error: hasła nie zgadzają się

Prawda, że proste. W wymaganiach do zmien-nej powtorz_haslo utworzyliśmy powyższy validator i przekazaliśmy do niego zmienną haslo, dzięki temu zostały porównane dwie zmienne. Dodajemy kolejne dwa validatory nick-Validator i checkdbnickValidator odpowiedzial-ne za sprawdzanie zmiennej nick – Listing 7.Validator checkdbnickValidator używa kla-sy, która jeszcze nie istnieje. Będzie to nasze pierwsze rozszerzenie, w którym zdefiniu-

Page 36: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

36

Symfony Framework

www.phpsolmag.org 37

jemy metody do sprawdzania zmiennej nickw bazie danych. Jak tego dokonać, dowiemy się w dalszej części artykułu. Kolejne valida-tory strona_wwwValidator i podpisValidatorróżnią się tylko zmienną. Zasada działania ich jest taka, że sprawdzany jest ciąg zna-ków. Minimalnie zmienna może zawierać 4 znaki a maksymalnie 255. W momencie gdy nie zostaną spełnione warunki pojawią się odpowiednie komunikaty zawarte w dy-rektywach min_error i max_error – Listing 8. Ostatnim validatorem będzie checkdbema-ilValidator, w którym również została użyta nasza własna klasa, dzięki której będziemy mogli sprawdzić zawartość zmiennej email w bazie danych. Tego typu sprawdzanie jest potrzebne z oczywistych względów, nie mo-że być dwóch takich samych maili, tak samo jak nicków.

checkdbemailValidator:

class: myRegisteremailValidator

param:

email: email

email_error: ten e-mail jest już wpisany

do naszej bazy danych

Jeśli chcemy zobaczyć dotychczasowy wynik na-szej pracy, to należy w głównym katalogu Sym-fony uruchomić polecenie /symfony clear-cache, wyczyści ono utworzony cache pliku register.yml. Następnie wchodzimy pod adres http://symfony/frontend.php/uzytkownik/register, wyświetli się pro-sty formularz rejestracji nowego użytkownika, jak dotąd jeszcze nieopisany w CSS. Wygląd tego for-mularza przedstawiamy na Rysunku 4. Jeśli klik-niemy zarejestruj, pojawi się błąd, gdyż nie utwo-rzyliśmy nowych klas do sprawdzania pewnych elementów formularza. Niedługo je wprowadzimy i wtedy będziemy mogli przetestować rejestrację użytkownika. Jak pewnie zauważyliście odwołanie do metody executeRegister() jest bardzo pro-ste, Symfony bowiem posiada wsparcie dla SEO (przyjaznych linków dla wyszukiwarek). Dobrze,teraz uzupełnimy nasze klasy. Wprowadzimy kolej-no myRegisternickValidator.class.ph oraz myRegisteremailValidator.class.php. Zasada jest taka: abyśmy mogli korzystać z tych klas, należy jet stworzyć w katalogu lib aplikacji frontend, więc jeśli jesteśmy w module uzytkownik, wychodzi-my z niego i wchodzimy do katalogu lib. Tworzy-my dwa pliki myRegisternickValidator.class.php i myRegisteremailValidator.class.php,następnie uzupełniamy je w podany kod w Li-stingu 9. i 10. Jak widać, sprawdzanie formularzy po krótkim zapoznaniu się ze strukturą plików validate oraz nowych klas do sprawdzania w ba-zie danych jest przejrzyste i czytelne. Dzięki takiej metodzie programowania nasz czas pracy znacznie się skraca. Na koniec dodajmy jeszcze plik nowy_uzytkownikSuccess.php z informacją dla użyt-kownika po wysłaniu poprawnego formularza. Oczywiście, plik tworzymy w katalogu templates modułu uzytkownik.

Listing 13. Metoda retrieveByKod(). Sprawdza, czy istnieje użytkownik o takim kodzie w polu potwierdzenie tabeli uzytkownik

// metoda odpowiedzialna za sprawdzanie kodu potwierdzenia

public static function retrieveByKod($kod, $con=null) {

if($con === null) {

$con = Propel::getConnection(self::DATABASE_NAME);

}

$criteria = new Criteria(UzytkownikPeer::DATABASE_NAME);

$criteria->add(UzytkownikPeer::POTWIERDZENIE, $kod);

$v = UzytkownikPeer::doSelect($criteria, $con);

return !empty($v) > 0 ? $v[0] : null;

}

Listing 14. Metoda executeLogin(). Odpowiedzialna jest za logowanie użytkownika

public function executeLogin() {

if ($this->getRequest()->getMethod() == sfRequest::POST) {

$uzytkownik = new Criteria(); // Sprawdzanie loginu, hasła i statusu

$uzytkownik->add(UzytkownikPeer::NICK, $this->getRequestParameter('login'));

$uzytkownik->add(UzytkownikPeer::STATUS, „1”);

$uzytkownik = UzytkownikPeer::doSelectOne($uzytkownik);

if ($uzytkownik) {

// Porównywanie hasła z formularza i bazy danych jeśli się zgadzają, następuje

// nadanie prawa subscriber oraz prawidłowa autoryzacja

if (sha1($uzytkownik->getSalt().$this->getRequestParameter('password'))

== $uzytkownik->getSha1Password())

$uzytkownik = $this->getUser();

$uzytkownik->addCredential('subscriber');

$uzytkownik->setAuthenticated(true);

} else {

// Wyświetlenie szablonu loginError.php w przypadku, gdy pola nie będą

// spełniały warunków zawartych w pliku login.yml

$error = $this->getParameter('login_error');

return false;

}

} else {

// Wyświetlenie szablonu login_uzytkownik_errorSuccess.php w momencie,

// gdy zostanie podany zły login i hasło

$this->setTemplate('login_uzytkownik_error');

}

} else {

return sfView::SUCCESS; // Wyświetlanie szablonu logowania

}

}

Listing 15. Zawartość pliku loginSuccess.php. Formularz logowania

<?php use_helper('Validation') ?>

<?php echo form_tag('uzytkownik/login') ?>

<!-- NICK (login) -->

<?php echo form_error('login') ?>

<div class=”input-default”>

<label for=”login”>Podaj swój nick:</label>

<?php echo input_tag('login') ?>

</div>

<!-- HASŁO -->

<?php echo form_error('password') ?>

<div class=”input-default”>

<label for=”password”>Podaj hasło:</label>

<?php echo input_password_tag('password') ?>

</div>

<?php echo input_hidden_tag('referer') ?>

<?php echo submit_tag('zaloguj się ->') ?>

</form>

Page 37: PHP Solutions 05 2007 PL

05/2007

Dla początkujących

36

Symfony Framework

www.phpsolmag.org 37

uzytkownik_errorSuccess.php (są to pliki in-formacyjne, na razie zostawiamy je puste) w ka-talogu templates oraz register.yml w katalo-gu validate modułu uzytkownik. Gdy zaloguje-my się poprawnie, zostanie wyświetlony szablon login_uzytkownik_okSuccess.php. Jest to dla nas potwierdzenie, że się udało. Jeśli chcielibyśmy zobaczyć, czy rzeczywiście została utworzona sesja, możemy to sprawdzić dzięki frontend_dev.php, gdzie dev jest skrótem od deweloper. Wybieramy zakładkę vars&config (Rysunek 6.), następnie glo-bals i na samym dole odszukujemy linijkę zaczyna-jącą się od session. Powinno wyglądać to tak:

session:

symfony/user/sfUser/attributes:

symfony/user/sfUser/authenticated: 1

symfony/user/sfUser/credentials:

subscriber

PodsumowanieTo już wszystko. Gratuluję, jeśli dotąd dotarłeś. Dowiedzieliśmy się wiele z tego artykułu. Pozna-liśmy proste zastosowanie praw. W następnej czę-ści zajmiemy się pozostałymi modułami oraz do-damy możliwość wylogowania się i przypomnie-nia hasła. Dowiemy się również, jak należy przypi-sać prawa do konkretnych modułów i metod.

Tabela 1. Tagi html w szablonach Symfony

form _ tag('moduł/metoda') <form method=”post” action=”/frontend.php/moduł/metoda”>

input _ tag('nazwa','wartość') <input type=”text” name=”nazwa” id=”nazwa” value=”wartość” />

input _ tag('nazwa','wartość','maxlenght=30') <input type=”text” name=”nazwa” id=”nazwa” value=”” maxlenght=”30” />

textarea _ tag('nazwa','wartość','size=15x30') <textarea name=”nazwa” id=”nazwa” cols=”15” rows=”30”>wartość</textarea>

checkbox _ tag('opcja','wartość',true),checkbox _ tag('opcja2','wartość2',false)

<input type=”checkbox” name=”opcja” id=”opcja” value=”wartość” checked=”checked” />,<input type=”checkbox” name=”opcja2” id=”opcja2” value=”wartość2” />

radiobutton _ tag('opcja[]','wartość1',true),radiobutton _ tag('opcja[]','wartość2', false)

<input type=”radio” name=”opcja[]” id=”opcja _ wartość1” value=”wartość1” checked=”checked” />,<input type=”radio” name=”opcja[]” id=”opcja _ wartość2” value=”wartość2” />

select _ tag('opcja','<option selected=”selected”>wartość1</option><option>wartość2</option>')

<select name=”opcja” id=”opcja”><option selected=”selected”>wartość1</option><option>wartość2</option></select>

select _ tag('imie', options _ for _select(array('lukasz' => 'Łukasz','janek' => 'Janusz','jarek' => 'Jarek','michal' => 'Michał'), 'janek'))

<select name=”imie” id=”imie”><option value=”lukasz”>Łukasz</option><option value=”janek”selected=”selected”>Janek</option><option value=”jarek”>Jarek</option><option value=”michal”>Michał</option></select>

input _ file _ tag('nazwa') <input type=”file” name=”nazwa” id=”nazwa” value=”” />

input _ password _ tag('nazwa','wartość') <input type=”password” name=”nazwa” id=”nazwa” value=”wartość” />

input _ hidden _ tag('nazwa','wartość') <input type=”hidden” name=”nazwa” id=”nazwa” value=”wartość” />

submit _ tag('Zapisz'),submit _ image _ tag('button.jpg')

<input type=”submit” name=”submit” value=”Zapisz” />, <input type=”image” name=”submit”src=”/images/button.jpg” />

select _ country _ tag('panstwo','PL') <select name=”panstwo” id=”panstwo”><option value=”AF”>Afghanistan</option>...<option value=”PL”selected=”selected”>Poland</option> ... </select>

ŁUKASZ KLEJNBERGAutor jest studentem informatyki w Wyż szej Szko-le Informatyki i Zarządzania w Rzeszowie. Pracuje jako projektant stron WWW w firmie Helionet.pl. Kontakt z autorem: [email protected] potrzeby artykułu został opracowany layout graficzny przez Bartłomieja Kina, który również pracuje w firmie Helionet.pl.

Metoda setPassword()Musimy jeszcze dodać jedną metodę set-

Password(), gdyż została ona użyta podczas za-pisywania hasła nowego użytkownika. Metoda ta ma na celu zapisanie hasła w kodowaniu MD5(Listing 11). Jeśli chcemy użyć naszych metod w klasie akcji, należy umieścić je w odpowiednim miejscu, w naszym przypadku jest to główny_katalog_symfony/lib/model/Uzytkownik.php. Sprawdzamy nasz formularz rejestracji. Aby wpro-wadzone zmiany były aktywne, ponownie wyko-nujemy polecenie w głównym katalogu projektu ./symfony clear-cache. Wciśnijmy najpierw button zarejestruj i zobaczmy, jakie komunikaty się pojawią – Rysunek 5. Teraz dodajemy nowe-go użytkownika. Klikamy zarejestruj. Jeśli nie zo-stały zgłoszone błędy, zostanie wywołany szablon nowy_uzytkownikSuccess.php, w którym będzie informacja o przesłanym e-mailu z linkiem do po-twierdzenia konta. Jest to swego rodzaju zabez-pieczenie przed spamerami. Dodaliśmy nowego użytkownika, sprawdźmy teraz, jak zachowa się formularz rejestracji w przypadku podania tego samego e-maila i nicka. Zostaną wyświetlo-ne ostrzeżenia o istniejącej nazwie i e-mailu w bazie danych. Nasz formularz jest już gotowy. Ale utworzone konto nie jest jeszcze aktywne. W liście wysłanym do użytkownika został za-warty link potwierdzający rejestrację, który był przedstawiony na Listingu 5.: http://symfony/uzytkownik/potwierdzenie/kod/'.$potwierdzenie.' Musimy do klasy akcji dodać nową metodę executePotwierdzenie(), która będzie pobie-rała unikalny dla każdego użytkownika kod z linku. Następnie po poprawnym zatwier-dzeniu zostanie wyświetlony szablon nowy_uzytkownik_okSuccess.php, w którym będzie informacja o poprawnym dodaniu konta i o jego

aktywacji. Nową metodę dodajemy na końcu klasy actions.class.php (Listing 12).

Metoda executePotwierdzenie()Aby nasza nowa metoda działała po-prawnie musimy, dodać kolejną o nazwie retrieveByKod(), która będzie umieszczona w pliku UzytkownikPeer.php znajdującym się w katalogu /katalog_główny_projektu/lib/model/ (Listing 13.).

Metoda retrieveByKod()Teraz zatem sprawdźmy, jak działa akceptacja nowego konta. Patrzymy do bazy danych (ta-bela Uzytkownik -> pole Potwierdzenie) lub do e-maila, na którego został wysłany link z potwier-dzeniem, i w oknie przeglądarki internetowej wklejamy link: http://symfony/uzytkownik/potwierdzenie/kod/87286 Jeśli kod okazał się poprawny, to konto zostało aktywowane i od tej pory użytkownik może się logować do systemu.

Metoda executeLogin()Skoro mamy już działający poprawnie formularz re-jestracji, który potrafi analizować pola poprzez plik register.yml, oraz poprawnie działającą aktywa-cję konta, to teraz czas najwyższy zająć się nową me-todą odpowiedzialną za logowanie użytkownika i przypisanie mu prawa subscriber, aby miał do-stęp do swojego menu i zamieszczonego w nim lin-ku do profilu, który będzie mógł edytować. Użyt-kownik zalogowany będzie mógł również pisać no-we komentarze. Na końcu plik actions.class.php dodajemy metodą executeLogin(), która przed-stawiona jest na Listingu 14. Następnie należy utworzyć pliki loginSuccess.php (Listing 15.), loginError.php (kopia pliku loginSuccess.php), login_uzytkownik_okSuccess.php i login_

Page 38: PHP Solutions 05 2007 PL

05/2007

Praktyka

38

Statystyki

www.phpsolmag.org 39

Przy poszukiwaniu – adekwatnych dorealizacji postawionych sobie zadań – narzędzi, może okazać się, że te do-

stępne na rynku, nie odpowiadają stawianym wobec nich oczekiwaniom, a ich budowa jest zbyt skomplikowana do samodzielnej modyfi-kacji. Może zdarzyć się, że zależy nam na śle-dzeniu ruchu robotów sieciowych (wyszukiwa-rek internetowych czy spambotów), który niezostanie zarejestrowany z poziomu JavaScript (a więc przez zewnętrzne serwisy, vide Google Analytics).

W takich sytuacjach jedyną alterna-tywą jest stworzenie własnego systemustatystyk, którego głównymi zaletami są prostota, możliwość bezproblemowej rozbu-dowy i dostosowania do konkretnej stronyinternetowej.

Co należy wiedzieć?PHP daje możliwość dostępu do szeregu pre-definiowanych zmiennych, wśród nich znaj-dują się superglobalne, wchodzące w skład ta-blicy $_SERVER (zawierającej m. in. nagłówki HTTP), która dostarcza nam garść informacji dotyczących odwiedzającego witrynę interne-

tową użytkownika. Na potrzeby naszej aplika-cji przydatnymi mogą się okazać:

• $ _ SERVER['HTTP _ ACCEPT'] – lista akcep-towanych przez przeglądarkę typów MIME;

• $ _ SERVER['HTTP _ ACCEPT _ LANGUAGE'] – preferencje użytkownika dotyczące do-myślnego języka strony;

• $ _ SERVER['HTTP _ REFERER'] – adres strony, za pośrednictwem której nastąpiło odwołanie do naszej (np. poprzez kliknię-cie odnośnika);

• $ _ SERVER['HTTP _ USER _ AGENT'] – ciąg znaków identyfikujący przeglądarkę i sys-tem użytkownika;

• $ _ SERVER['HTTP _ X _ FORWARDED _ FOR'] – jeżeli użytkownik łączy się za pośred-nictwem proxy, a ustawienia serwera na to pozwalają, zawiera właściwe IP;

• $ _ SERVER['REMOTE _ ADDR'] – adres IP użykownika wyświetlającego stronę lub proxy.

PHP jako przedstawiciel języków server-side po-siada ograniczoną wiedzę o komputerze klien-ta, dlatego nie ma bezpośredniej możliwości pobrania rozdzielczości ekranu przy jego uży-ciu. Jeśli taka informacja jest nam potrzebna, pobieramy ją z poziomu JavaScriptu i przekazu-jemy poprzez COOKIE, którego odczytanie nie sprawia problemów. Zwracamy szczególną uwa-

gę na ewentualny brak wsparcia dla JavaScriptu w przeglądarce, dlatego nie uzależniamy od nie-go rejestracji pozostałych statystyk.

Warto zainteresować się także funkcją get_browser(), dostarczającą w postaci ta-blicy takich informacji, jak: platforma sys-temowa, typ przeglądarki, wersja oraz tewynikające z ustawień przeglądarki. Wiedzę o tym, jak uzyskać dostęp do poszczególnych elementów, dostarczy nam wynik działania skryptu, tworzącego tablice $params i zwra-cającego wartości wraz z indeksami:

$params=get_browser(null, true);

print_r($params);

Dzięki jej zastosowaniu nie ma potrzeby ręcz-nego parsowania nagłówków, niestety dla pra-widłowego działania niezbędne jest istnienie pliku browscap.ini po stronie serwera. W nie-których przypadkach jest to trudne do zre-alizowania, dlatego w dalszej części artykułu, przyjęliśmy, iż nie istnieje możliwość zastoso-wania funkcji.

Funkcje rozszerzające możliwościPrzykładem oryginalnej i niewystępującej w ty-powych systemach statystyk opcji jest niewąt-pliwie sprawdzanie aktualnej pozycji w wy-szukiwarkach pod hasła, na które pozycjo-nujemy naszą witrynę. Dzięki zestawieniu tych statystyk ze zgromadzonymi do tej porymożemy łatwo stwierdzić, w jaki sposób są one ze sobą powiązane i czy faktycznie opła-ca się zabiegać o jak najwyższą lokatę pod da-ne słowo kluczowe w wynikach organicznychwyszukiwarek. Listing 1. przedstawia przykła-dową implementację funkcji zwracającej pozycjew Google, która jest zachętą do dalszych prac nad budowaniem naszego autorskiego systemu.

Analogicznie do podanego przykładu z ła-twością wykonamy podobne funkcje dlainnych wyszukiwarek. Niekiedy z jakiegoś po-wodu pragniemy zamaskować informacje

Własny system statystyk

Niniejszy artykuł prezentuje sposoby pozyskiwania danych, niezbędnych do monitorowania ruchu na naszej witrynie internetowej oraz przedstawia przykładową implementacje prostego systemu statystyk.

Dowiesz się...• Jak pobrać dane dotyczące systemu, przeglą-

darki i rozdzielczości gościa.• Jak oddzielić ruch pochodzący z wyszukiwarek

od tego z innych witryn polecających.• Jak śledzić słowa kluczowe, kierujące ruch

z wyników wyszukiwarek oraz pozycje zajmo-wane pod nimi w wynikach organicznych.

• Jak zaimplementować prosty system statystyk.

Powinieneś wiedzieć...• Czytelnik powinien posiadać podstawową

wiedze z zakresu obiektowego projektowania aplikacji w PHP oraz rozumieć budowe wyra-żeń regularnych.

Poziom trudności

Alternatywa dla rozbudowanych i uniwersalnych skryptów

Page 39: PHP Solutions 05 2007 PL

05/2007

Praktyka

38

Statystyki

www.phpsolmag.org 39

o tym, że dane pobierane są z poziomu skryp-tu przez parsowanie strony z wynikami. Może-my tego dokonać, zastępując funkcję file_get_contents() naszą własną, bazującą na CURL-u, który daje możliwość ustawienia User Agenta(Listing 2.).

Przykładowy system statystykZwracamy uwagę przede wszystkim na wyni-ki organiczne wyszukiwarek (Google, Onet, Wirtualna Polska, MSN oraz Yahoo!) i anali-zę stron polecających. Prowadzenie pozosta-łych statystyk dotyczących przeglądarek czy systemów operacyjnych na potrzeby niniej-szego artykułu zostanie zredukowane do mi-nimum. Skupiamy się na źródłach ruchu, nie badamy natomiast popularności poszczegól-nych podstron naszej witryny. Kod systemu statystyk umieszczamy w czterech plikach we-dług schematu:

• functions.inc.php;• class.inc.php;• stats.inc.php;• display.php.

Dysponujemy serwerembez wsparcia dla funkcjiget_browser() Dane przechowujemy w bazie utworzonejzapytaniem przedstawionym na dołączonym do gazety nośniku. W zależności od ilości od-wiedzających witrynę przyjmujemy adekwat-ne wartości maksymalne pól.

Warto wspomnieć o budowie pierwszego z plików – functions.inc.php. Posiada on, oprócz podstawowych funkcji regulujących opera-cje na bazie danych, inną nazwaną check(). Jej zadaniem jest zwrócenie nazwy pola tabe-li na podstawie badania zawartości elementów tablicy $array w ciągu $string. Zwracana jest wartość „PX”, gdzie „X” jest indeksem elemen-

tu spełniającego zależność, z uwagi na przyję-te w bazie nazewnictwo (Rysunek 1.) domyślą wartością jest „P99”. Taka operacja będzie wy-konywana niejednokrotnie, np. podczas przy-porządkowywania typowi preglądarki czy wy-szukiwarki nazwy odpowiedniego do uaktual-nienia pola.

Pobieranie danychTo na pozór łatwe zadanie zrealizują trzy me-tody klasy userinfo. Pierwsza z nich – Listing 3 – odpowiedzialna jest za pobranie adresu IP lub wyciągnięcie go zza proxy, jeśli zajdzie taka ko-nieczność. Ważnym elementem jest walidacja poprawności, której pominięcie nie jest rozsąd-ne ze względu na to, że dane te mogą zostać z ła-twością zmanipulowane przez złośliwego użyt-kownika.

Na potrzeby metody unique(), zwraca-jącej wartość typu bool, utworzyliśmy jużtabelę „today”, zawierającą adresy IP osób zli-czonych w danym dniu, co pozwala na branie pod uwagę jedynie unikalnych wizyt. W celu zmniejszenia liczby zapytań – jeśli ustawie-nia przeglądarki na to pozwalają – wysyłamy plik COOKIE o ważności do północy trwają-cego dnia.

W przypadku zwrócenia wartości true przechodzimy do pobrania kolejnej daw-ki informacji, a więc kolejno: detekcji ty-pu przeglądarki, systemu operacyjnego oraz przeparsowania nagłówka HTTP_REFERER,zawierającego adres URL strony, za pośred-nictwem której internauta trafił na naszą. Jeśli jego zawartość sugeruje przejście z wy-szukiwarki, na podstawie znajdującej się w

get_all() tablicy budowane jest wyrażenie regularne (odpowiadające strukturze lin-ków) w celu wyciągnięcia słowa kluczowe-go, zaś samo ID wyszukiwarki zapisane zosta-je jako kolejny z parametrów. W celu zabez-pieczenia przed atakami XSS zastosowaliśmy funkcję strip_tags().

Zapis danychNa Listingu 5 widoczna jest przykładowa implementacja metody db(), odpowiedzial-nej za zapis pobranych wcześniej danych.W przypadku konieczności utworzenia ko-lejnych rekordów, równoznacznej z odwie-dzinami pierwszego w ciągu danej doby użytkownika, czyszczony jest rejestr adre-sów IP.

Alternatywą jest automatyczne utworzenie niezbędnych wpisów za pośrednictwem unik-sowego daemona cron. Ogranicza się to do usu-nięcia pierwszej instrukcji if z metody, utwo-rzenia odrębnego pliku cron.php oraz dodania do rejestru crontab:

0 0 * * * wget -q http://www.nasza-

strona.pl/sciezka/cron.php > /dev/null

Co zostanie umożliwone, poprzez wydaniew lini poleceń, komendy:

/usr/bin/crontab -e

W celu przeglądu już istniejących wpisów użytkownika wystarczy:

/usr/bin/crontab -l

Listing 1. Funkcja sprawdzająca pozycję strony w Google pod dane słowo kluczowe

function get_links ($url, $q) {

// Budowanie prawidłowego URL-a (zamiana spacji na “+”)

$q=str_replace(' ', '+', $q);

// Pobieranie 100 pierwszych witryn z wyników organicznych

preg_match_all ('|href=(.*)>|U', file_get_contents('http://www.google.pl/ie?q='.$q.'

&hl=pl&num=100&start=0'), $table, PREG_PATTERN_ORDER);

// Znajdowanie na liście interesującego nas adresu

for ($n=0; $n<100; $n++)

if (ereg($url, $table[1][$n]))

break;

return $n+1;

}

Listing 2. Alternatywa dla file_get_contents()

function file_get_plus($url) {

// User Agent IE 6.0

$ua='Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)';

$ch=curl_init($url)

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

curl_setopt($ch, CURLOPT_useragent, $ua);

$content=curl_exec($ch);

return $content;

curl_close($ch);

}

Rysunek 1. Przyjęte nazewnictwo pól i rekordy z przykładową zawartością

����

��������

��������

��������

���

��

��

��

��

��

��

��

��

��

Rysunek 2. Wykres wygenerowany przy użyciu metody drawtab()

������

��

�������

��

�������

Page 40: PHP Solutions 05 2007 PL

05/2007

Praktyka

40

Statystyki

www.phpsolmag.org 41

Listing 3. Klasa userinfo (część pliku class.inc.php)

class userinfo {

var $ip, $br, $os, $gr, $se, $kw;

// Podstawowe dane

function get_basic() {

// Pobieranie IP

$this->ip=$_SERVER['HTTP_X_FORWARDED_FOR'] ? $_SERVER[

'HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];

// Validacja IP

if (ereg("^[0-9]{1,3}(.[0-9]{1,3}){3}$",$this->ip))

foreach(explode(".", $this->ip) as $block) {

if( $block<0 || $block>255) {

$this->ip='0.0.0.0';

break;

}

}else $this->ip='0.0.0.0';

}

// Czy unikalny odwiedzający

function unique() {

if (!isset($_COOKIE['stats'])&&!check_exist('today', 'ip',

$this->ip)) {

// ile sekund do końca dnia

$waznosc = (23- date('H'))*3600 + (59-date('i'))*60 + 59-

date('s');

setcookie('stats', '1', time()+$waznosc);

mysql_query('INSERT INTO today VALUES ("'.$this->ip.'")');

return true;

}else return false;

}

// Pozostałe dane

function get_all(){

// Pobieranie UA

$ua=strtolower(strip_tags($_SERVER['HTTP_USER_AGENT']));

// Detekcja przeglądarki

$br=array( 'opera', 'msie', 'konqueror', 'firefox', 'safari',

'netscape', 'aol', 'slurp', 'googlebot', 'msnbot',

'onetszukaj', 'netsprint');

$this->br = check($br, 11, $ua);

// Detekcja systemu

$os=array('windows', 'linux', 'mac', 'unix');

$this->os = check($os, 3, $ua);

// Pobieranie HR

$this->hr=strip_tags($_SERVER['HTTP_REFERER']);

// Sprawdzanie, czy HR to wyszukiwarka; jeśli tak, to jaka

$ref=array( array ( 'search.yahoo.com', 'google' ,

'search.msn.com', 'live.com', 'szukaj.onet.pl',

'szukaj.wp.pl'),

array ('p', 'q', 'q', 'q', 'qt', 'szukaj') );

$this->se = check($ref[0], 5, $this->hr);

// Wyciąganie słowa kluczowego

if ($this->se !='P99' ) {

preg_match('/'.$ref[1][$this->se].'=(.*?)(&|$)/',

$this->hr,$arr);

$this->kw=strtolower($arr[1]);

$this->kw=str_replace('+', ' ', $this->kw);

}

// Zamienianie HR na informacje o hoście

preg_match('|http://(.*)/|U',$this->hr, $arr);

$this->hr = $arr[1];

}

// Zapis danych

function db() {

$date=date("Ymd");

// Czy strona została dziś odwiedzona

if (!check_exist('cache', 'date', $date)) {

// Tworzenie rekordów, jeśli to konieczne

mysql_query('INSERT INTO cache SET date="'.$date.'"');

mysql_query('INSERT INTO browser SET date="'.$date.'"');

mysql_query('INSERT INTO system SET date="'.$date.'"');

// Kasowanie nieaktualnych IP

mysql_query('DELETE FROM today');

}

// Cache

if ($this->se!='P99') $t='o';

else if ($this->hr!='') $t='r';

else $t='d';

update ('cache', $t, 'date', $date);

// Zapis informacji o przeglądarce i systemie

update('browser', $this->br, 'date', $date);

update('system', $this->os, 'date', $date);

// Czy znany jest HR

if ($this->hr!='') {

// Zapis informacji o wyszukiwace

if ($this->se!='P99') {

// Zapis słowa kluczowego

if (!check_exist('keywords', 'val', $this->kw))

mysql_query('INSERT INTO keywords VALUES("", "'.$this->kw.'")');

// Pobieranie ID

$id=get_id('keywords', $this->kw);

// Tworzenie rekordów, jeśli to konieczne

if (!check_exist('sengine', 'date', $date, 'word', $id,

'engine', $this->se))

mysql_query('INSERT INTO sengine VALUES("'.$date.'",

"'.$id.'", "'.$this->se.'", "")');

// Zapis informacji

update('sengine', 'val', 'date', $date, 'word', $id, 'engine',

$this->se);

} else {

// Zapis informacji o HR, jeśli nie jest SE

// Zapis referera

if (!check_exist('referer', 'val', $this->hr))

mysql_query('INSERT INTO referer VALUES("", "'.

$this->hr.'")');

// Pobieranie ID

$id=get_id('referer', $this->hr);

// Tworzenie rekordów, jeśli to konieczne

if (!check_exist('refhosts', 'date', $date, 'referer', $id))

mysql_query('INSERT INTO refhosts VALUES("'.$date.'",

"'.$id.'", "")');

// Zapis informacji

update('refhosts', 'val', 'date', $date, 'referer', $id);

}

}

}

}

Page 41: PHP Solutions 05 2007 PL

05/2007

Praktyka

40

Statystyki

www.phpsolmag.org 41

(Listing 5) do strony serwisu, np. poprzez include_once(). Od tej pory dane dotyczące źródeł ruchu na naszej WWW są śledzone.

Obróbka statystykPo pobraniu i zapisaniu danych niezbędnych do prowadzenia analizy przyszedł czas na ich pre-zentację. Warstwą odpowiedzialną za dostar-czenie zgromadzonych informacji do aplikacji jest klasa view z metodami get_single() orazget_multi(). Różnią się one jedynie sposobem pobierania danych z bazy, cechą wspólną jest wyjście zgodne z przyjętym standardem – dwie tablice; zawierajace wartości i opisy zmiennych. W przypadku metody get_single tę drugą two-rzymy ręcznie. Obsługę najłatwiej omowić na przykładzie, a zatem skupmy się na skrypcie dy-namicznie generującym wykresy – Listing 5.

W celu zachowania przejrzystości ko-du HTML-ową tabelę, tworzoną metodądrawtab(), umieszczamy w zmiennej $exit, by na koniec zwrócić ją w odpowiednim miejscu szablonu strony.

PodsumowanieArtykuł nie zamyka tematu, a dostarcza jedynie bazy, na której można zbudować własny system statystyk, który z pewnością spełni nasze ocze-kiwania w stopniu większym niż „uniwersalne” i publicznie dostępne rozwiązania.

Listing 5. Plik stats.inc.php

// Połączenie z bazą

$db = mysql_connect('localhost',

'admin', 'passwd') or die

('Błąd: '.mysql_error());

mysql_select_db('stats') or die

('Błąd: '.mysql_error());

// Załadowanie niezbędnych plików

include ('functions.inc.php');

include ('class.inc.php');

$info = new userinfo;

$info->get_basic();

if ($info->unique()) {

$info->get_all();

$info->db();

}

// Zamykanie połączenia

mysql_close($db);

Listing 4. Plik display.php

<?php

// Połączenie z bazą

$db = mysql_connect('localhost', 'admin', 'passwd') or die ('Błąd: '.mysql_error());

mysql_select_db('stats') or die ('Błąd: '.mysql_error());

// Załadowanie niezbędnych plików

include ('functions.inc.php');

include ('class.inc.php');

// Tworzenie obiektu klasy

$view=new view;

// Przyjęcie danych podanych metodą $_GET lub domyślnych

$view->date1 = is_numeric($_GET['d1']) ? $_GET['d1'] : 00000000;

$view->date2 = is_numeric($_GET['d2']) ? $_GET['d2'] : 99999999;

// Pobieranie danych metodą get_single

$view->get_single('browser',13);

$view->arrayd=array('Nieznana', 'Opera', 'MSIE', 'Konqueror', 'Firefox', 'Safari',

'Netscape', 'AOL', 'Yahoo', 'Google', 'MSN', 'Onet', 'Netsprint');

$exit=$view->drawtab();

$view->get_single('system', 5);

$view->arrayd=array('Nieznany', 'Windows', 'Linux', 'Mac', 'UNIX');

$exit.=$view->drawtab();

$view->get_single('cache', 3);

$view->arrayd=array('Direct, 'Organic', 'Referal');

$exit.=$view->drawtab();

// Pobieranie danych metodą get_multi

$view->get_multi('sengine', 'word', 'val', 'keywords');

$exit.=$view->drawtab();

$view->get_multi('refhosts', 'referer', 'val', 'referer');

$exit.=$view->drawtab();

// Zamykanie połączenia

mysql_close($db);

?>

<html><head><title>Przeglądanie statystyk</title>

<style type="text/css">

body {

background : rgb(0, 0, 0);

font : 16px Trebuchet MS, Verdana, Arial, Helvetica, sans-serif;

color : #cccccc;

}

td {

text-align: center;

}

</style></head><body>

<?php

echo $exit;

?>

</body></html>

Drugie rozwiązanie jest dużo lepsze pod względem wydajności, jednak z uwagi na za-mierzoną prostotę skryptu nie przyjęliśmy go jako domyślne.

Integracja z witrynąW celu rozpoczęcia rejestrowania statystyk za pośrednictwem napisanego do tej porykodu wystarczy załączenie pliku stats.inc.php

ŁUKASZ BORCHMANNCzłonek grupy Rainfox, oferującej usługi z zakre-su tworzenia aplikacji webowych. Autor bloga do-tyczącego zastosowania PHP i środowiska Apa-che do optymalizacji witryn pod kątem wyszuki-warek internetowych. Kontakt: [email protected]; Blog: http://www.veal.pl/.

Page 42: PHP Solutions 05 2007 PL

05/2007

Technika

42

Operacje na tekście

www.phpsolmag.org 43

Po pierwsze, musimy wiedzieć, jak wyglą-da wyświetlanie tekstu w PHP. Wszelkie operacje na tekście będziemy wykonywać

za pomocą funkcji na zmiennej, zawierający da-ny tekst. Powinno być oczywiste, że do wyświetla-nia tekstu w PHP służą funkcje echo i print. Są to identyczne funkcje, więc dlaczego nie mogłaby być tylko jedna? Dlatego, że w PHP wiele funkcji wystę-puje np. pod dwoma nazwami, aby upodobnić się do wielu języków programowania, by programi-stom było po prostu wygodniej nauczyć się PHP. Mniejsza o to. Wyświetlmy testowo tekst „Ala ma kota, a kot ma Alę” na kilka możliwości – Listing.1. Wszystkie te zapisy są poprawne, a dla wyświetla-nia tekstu nie zawierającego żadnych zmiennych najlepszym zapisem będzie ostatni – z użyciem za-mknięcia i ponownego otwarcia kodu PHP między tekstem. W tym przypadku nie jest używana żadna funkcja PHP, gdyż tekst stanowi wtedy część doku-mentu. Jest to zatem zapis najbardziej optymalny. Oczywiście, do tekstu możemy także wprowadzić zmienne. Dodajmy zmienną $cat z tekstem „kot” i użyjmy ją w naszym kodzie. Dodajmy także zapis z podwójnym cudzysłowiem ( “” ) – Listing 2. Przy-kład ten pokazuje, że w przypadku użycia pojedyn-czego cudzysłowia ( ' ' ) zmienne nie są parsowane

przez PHP, dlatego też musieliśmy tekst podzielić na dwie części, a między nimi „dokleić” zawartość zmiennej $cat. Jak widać, tekst łączy się znakiem kropki. W piątej linijce naszego kodu zastosowano skróconą wersję funkcji echo, za którą odpowiada znak równości po pytajniku (<?=$cat?> wyświe-tla więc tekst „kot”). Ostatnie linijki pokazują, że w przypadku użycia podwójnego cudzysłowia zmien-ne w nim zawarte są parsowane, jednak rozwiąza-nie to nie jest zalecane, gdyż wszystkie benchmar-ki PHP pokazują, że jest to sposób najwolniejszy. W tekście wyświetlanym z pojedynczym cudzysło-wem możemy bez problemu wstawić cudzysłów podwójny, jednak aby wstawić pojedynczy, musi-my poprzedzić go znakiem backslash ( \ ). Analo-gicznie wygląda sytuacja z użyciem wyświetlania tekstu w podwójnym cudzysłowie, tyle że odwrot-nie. Przykład:– Listing 3.Jak widać sytuacja ta nie występuje, gdy wyświetlamy tekst bezpośrednio w dokumencie. Zajmijmy się podstawową „obróbką” tekstu. Najlepiej jest pracować z użyciem zmien-nych. Zapisujemy więc nasze zdanie do zmiennej $tekst. Jeśli nie mamy takiej możliwości (np. tekst strony jest generowany przez PHP, a chcemy prze-robić cały tekst wynikowy), możemy użyć bufora. Wygląda to tak – Listing 4. Pierwsza funkcja, która może nam się przydać, to substr. Pozwala ona na skrócenie tekstu do określonej liczby liter. Ponadto pozwala obciąć tekst nie tylko z jednej strony, ale i z dwóch. Składnia funkcji wygląda tak:

substr(tekst, start, [długość]);

Pierwszy parametr to nasz tekst, podajemy tam naszą zmienną. Drugi parametr określa, od którego znaku tekst ma się zaczynać (np. jeśli wstawimy 1 [liczenie od 0] to tekst zosta-nie ucięty do „la ma kota, a kot ma Alę”). Trzeci parametr (długość) jest opcjonalny, jednak bar-dzo często używany, gdyż to on potrafi skró-cić tekst. Przykład – Listing 5. Przydaje się to często, jeśli np. tworzymy system newsów na stronę i chcemy skrócić treść newsu na stronie głównej do określonej ilości znaków. Przykła-dowo chcemy skrócić każdą wiadomość, która ma treść dłuższą niż 200 znaków. Do spraw-dzenia długości tekstu służy funkcja strlen. Jej użycie jest proste – strlen(tekst). Zasto-sujemy prosty warunek skracający news. Załóż-my, że w zmiennej $news mamy treść newsa:

if (strlen($news) > 200) {

echo substr($news, 0, 200).'[...]';

}

else

{echo $news;

}

Jeśli news będzie dłuższy niż 200 znaków, zosta-nie skrócony właśnie do 200 znaków, a na jego końcu zostanie dodany kwadratowy nawias z wie-lokropkiem. Możemy także z tekstu wyciągnąć je-den interesujący nas znak. Przydaje się to w róż-nych sytuacjach, np. jeśli chcemy zakodować jakąś informację i wplatamy interesujące nas znaki po-między znaki „śmieci", a nasz skrypt PHP będzie te znaki wyciągał i układał w logiczną całość. Uży-wamy w tym przypadku klamr przy zmiennej, a między nimi podajemy konkretny indeks znaku. Przykład takiego zastosowania:

$kod = 'fksmajso3jr0lf0es3a32fsgc';

$klucz = $kod{4}.$kod{12}.$kod{18};

echo $klucz;

Powyższy przykład wyświetli imię „ala”. Jak widać, wszystko jest małymi literami – co

Operacje na tekście

Operacje na tekście w locie są bardzo często potrzebne na naszych stronach WWW. W innych językach programowania służą nam do tego zazwyczaj standardowe funkcje jakiejś biblioteki. PHP ma ogromny zasób wbudowanych funkcji, dzięki którym zrobimy z tekstem, co tylko chcemy.

Dowiesz się...• Jak dzielić fragmenty tekstu.• Jak usunąć niepotrzebne litery/ciągi, na pod-

stawie danych kryteriów.• Jak przeszukiwać tekst.• Jak sprawdzić czy w tekście istnieje dany ciąg

znaków.• Jak zamienić znaki w tekście.• Jak zmienić kodowanie tekstu za pomocą funkcji

Powinieneś wiedzieć...• Należy znać podstawową składnię języka PHP.• Należy wiedzieć, co to jest zmienna i warunki

w PHP.• Skrypty PHP uruchamiamy na serwerze (może

być lokalny) z zainstalowanym PHP.• Należy wiedzieć, co to jest kodowanie strony

WWW.

Poziom trudności

Wprowadzenie

Page 43: PHP Solutions 05 2007 PL

05/2007

Technika

42

Operacje na tekście

www.phpsolmag.org 43

Listing 1. Kilka możliwości wyświetlenia danego tekstu

<?

echo 'Ala ma kota, a kot ma Alę';

print 'Ala ma kota, a kot ma Alę';

?>Ala ma kota, a kot ma Alę<?

?>

Listing 2. Dodanie zmiennej $cat oraz podwójnego cudzysłowia

<?

$cat = 'kot';

echo 'Ala ma kota, a '.$cat.' ma Alę';

print 'Ala ma kota, a '.$cat.' ma Alę';

?>Ala ma kota, a <?=$cat?> ma Alę<?

echo “Ala ma kota, a $cat ma Alę”;

print “Ala ma kota, a $cat ma Alę”;

?>

Listing 3. Wyświetlanie tekstu w podwójnym cudzysłowie

<?

echo 'Ala ma \'kota\', a “kot” ma Alę';

echo “Ala ma 'kota', a \“kot\” ma Alę”;

?>Ala ma 'kota', a “kot” ma Alę<?

?>

Listing 4. Możemy użyć bufora

<?

// funkcja rozpoczynająca buforowanie

ob_start();

/*

tutaj wszystkie funkcje wyświetlające

tekst wynikowy

*/

// pobieranie już sparsowanego wyniku

// powyższych operacji do zmiennej

$tekst = ob_get_contents();

// zamykanie i czyszczenie bufora

ob_end_clean();

?>

Listing 5. Zastosowanie funkcji substr

<?

$tekst = 'Ala ma kota, a kot ma Alę';

// Wyświetli “ ma kota, a kot ma “ - gdy obcinamy tekst z obu stron, należy

// w parametrze długość użyć znaku minus

echo substr($tekst, 3, -3);

?>

Listing 6. Przykład zastosowania funkcji strtoupper oraz strtolower

<?

$kod = 'fksmajso3jr0lf0es3a32fsgc';

$klucz = $kod{4}.$kod{12}.$kod{18};

echo strtoupper($klucz); // Wyświetli ALA

echo strtoupper($kod{4}).$kod{12}.$kod{18}; // Wyświetli Ala

echo strtolower(strtoupper($klucz)); // Wyświetli ala

?>

Listing 7. Przykład zastosowania funkcji strstr oraz stristr

<?

$tekst = 'Ala ma kota, a kot ma Alę';

// Zwróci “Wyraz 'ala' znajduje się w podanym tekście”

if (stristr($tekst, 'ala')) {

?>Wyraz 'ala' znajduje się w podanym tekście <?

} else {

?>Wyraz 'ala' nie znajduje się w podanym tekście <?

}

// Zwróci “Wyraz 'ala' nie znajduje się w podanym tekście” (małe litery)

if (strstr($tekst, 'ala')) {

?>Wyraz 'ala' znajduje się w podanym tekście<?

} else {

?>Wyraz 'ala' nie znajduje się w podanym tekście <?

}

// Zwróci “Wyraz 'Ala' znajduje się w podanym tekście”

if (strstr($tekst, 'Ala')) {

?>Wyraz 'Ala' znajduje się w podanym tekście<?

} else {

?>Wyraz 'Ala' nie znajduje się w podanym tekście <?

}

?>

// Przykład użycia funkcji eregi i ereg

<?

$tekst = 'Ala ma kota, a kot ma Alę';

if (ereg('ala', $tekst)) { // Wyświetli "Nie znaleziono 'ala'"

?>Znaleziono 'ala'<?

} else {

?>Nie znaleziono 'ala'<?

}

if (eregi('ala', $tekst)) { // Wyświetli "Znaleziono 'ala'”

?>Znaleziono 'ala'<?

} else {

?>Nie znaleziono 'ala'<?

}

?>

teraz zrobić, aby daną literę bądź całe sło-wo zamienić na wersaliki? Z pomocą przy-chodzi nam funkcja strtoupper oraz jej sio-stra strtolower, która zamienia litery na ma-łe. Przykład zastosowania – Listing 6. Klam-ry możemy też wykorzystać do wyświetla-nia tekstu pionowo. W tym celu najlepiej napisać własną funkcję. Nazwijmy ją po pro-stu “ustaw_tekst_pionowo”. Głównym środ-kiem użytym w naszej funkcji będzie pętla. Funkcja najpierw policzy ile podany tekst ma znaków, aby pętla mogła tyle razy prze-biec i wyświetlać każdy znak z tagiem <br /> (przejście do następnej linii w HTML).

function ustaw_tekst_pionowo($tekst)

{

for ($i = 0, $n = strlen($tekst); $i <

$n; $i++) {

echo $tekst{$i}.'<br />';

}

}

Jak widać zmienna-index $i jest ustawia-na na 0, a zmienna $n przyjmuje wartość długości zmiennej $tekst. Pętla wykonu-

je się dopóki warunek, iż $i jest mniejszeod $n jest spełniony. Za każdym przebie-giem pętli zmienna $i zwiększana jest o 1. Zastosować funkcję można bardzo łatwo– ustaw _ tekst _ pionowo($zmienna) ub bezpośrednio, np.

ustaw_tekst_pionowo('Mój tekst').

Page 44: PHP Solutions 05 2007 PL

05/2007

Technika

44

Operacje na tekście

www.phpsolmag.org 45

nych nie będę się tu rozpisywał – jest to temat rozległy i został opisany w numerze 6/2006 PHP Solutions w artykule „Przyjazne URLe w PHP" . Dlatego też w przykładzie zostało zapisane tylko jedne potrzebne nam wyrażenie – Listing 8.

Przeszukiwanie i zamiana tekstuPHP umożliwia także zamienianie pewnych fragmentów tekstu na inne. Najczęściej uży-wane, a zarazem najprostsze umożliwiające to funkcje to str_replace i preg_replace. Skład-nia tych funkcji jest identyczna z tą różnicą, że preg_replace używa wyrażeń regularnych, dzięki czemu możemy tą funkcją zrobić wię-cej. Jednakże to ma też swoją wadę – funkcja ta jest wolniejsza. Do zamiany stałych fragmen-tów tekstu dużo lepsza jest str_replace. Oto jej składnia: str_replace(szukaj, zamień,

tekst);Funkcje te są bardzo proste w użyciu i pozwalają na podanie im tablic do przeszuka-nia i zamiany, co wpływa pozytywnie na wydaj-ność (lepiej użyć raz funkcji z tablicą niż dzie-sięć razy osobno).Przykład – Listing 9. Funkcję str_replace często wykorzystuje się także do wycięcia danego fragmentu tekstu. Wtedy, jako pierwszy parametr podajemy szukany ciąg, a ja-ko drugi tylko pusty cudzysłów (''), np.

str_replace('Ala ma kota', '', $tekst)

spowoduje usunięcie wszystkich ciągów "Ala ma kota" w zmiennej $tekst. Teraz bardziej praktycznie rozwiązanie – przykładowo chce-my do wszystkich linków w danym tekście do-dać klasę link, tak aby każdy link wyglądający tak <a href=”...”>...</a> przyjął postać

<a href=”...” class=”link”>...</a>

– Listing 10.

W szablonie wyrażenia regularnego przy prze-szukiwaniu każdy odnaleziony ciąg znaków (wyrażenie (.*?) ) odpowiada kolejnej cyfrze po dwóch znakach slash w zamianie (np. \1, \2).

Zmiana kodowania tekstuza pomocą funkcjiCzęsto bywa tak, że mamy problem z kodowa-niem znaków narodowych na naszych dynamicz-nych stronach. Na poprawne wyświetlanie takich znaków na stronie wyświetlającej tekst z bazydanych wpływa kodowanie bazy danych, kodo-wanie danej tabeli bazy danych, kodowanie plikuwyświetlającego tekst oraz kodowanie samegodokumentu HTML. Gdy jeden wariant jest niepo-prawny, znaki nie zawsze wyświetlają się popraw-nie (lub ich część). Ratować możemy się sami, pisząc odpowiednią funkcję. Funkcja change_encoding będzie zmieniała kodowanie danego tekstu na podstawie znaków zawartych w tabli-cach (każdy znak ma swój kod, dlatego też funkcja działa w każdym kodowaniu – Listing 11.

Funcja działa na prostej zasadzie – jako pierwszy parametr podajemy tekst do przeko-

Listing 8. Przykład użycia funkcji preg_match

<?

$tekst = 'Gitarzysta gra na gitarze. Jego gitara jest stara';

if (preg_match(“/\sg(.*?)a\s/s”, $tekst)) {

?>W podanym tekście występują wyrazy

zaczynające się na g i kończące na a<?

}

else {

?>W podanym tekście nie występują wyrazy

zaczynające się na g i kończące na a<?

}

?>

Listing 9. Przykłady użycia funkcji str_replace

<?

$szukaj = 'kot';

$zamien = 'kocur';

$tekst = 'Ala ma kota, a kot ma Alę';

/*

Stosujemy funkcję str_replace bezpośrednio na zmiennej $tekst. Równie dobrze nie

musimy tego robić – wystarczy od razu wyświetlić funkcję (echo str_replace($szukaj,

$zamien, $tekst); )

*/

$tekst = str_replace($szukaj, $zamien, $tekst);

echo $tekst; // Wyświetli "Ala ma kocura, a kocur ma Alę"

?>

<?

// Przykład z użyciem tablic

$szukaj = array('kota', 'kot ');

$zamien = array('koteczka', 'kocur ');

$tekst = 'Ala ma kota, a kot ma Alę';

$tekst = str_replace($szukaj, $zamien, $tekst);

echo $tekst; // Wyświetli “Ala ma koteczka, a kocur ma Alę”

?>

Listing 10. Dodanie klasy link do wszyskich linków w tekście

<?

$linki = '<a href=”index.php”>Strona główna</a> <a href=”index.php?p=firma”>O firmie</a>

| <a href=”index.php?p=kontakt”>Kontakt</a>';

$linki = preg_replace(“#<a(.*?)href=\”(.*?)\”>(.*?)</a>#si”, '

<a\\1 href=”\\2” class=”link”>\\3</a>', $linki);

echo $linki;

// Przykład wyświetli “<a href=”index.php” class=”link”>Strona główna</a>

// | <a href=”index.php?p=firma” class=”link”>O firmie</a> <a href=

// ”index.php?p=kontakt” class=”link”>Kontakt</a>”

?>

Warunki na podstawie danego tekstuJak sprawdzić, czy dany tekst zawiera podany ciąg znaków? Możemy posłużyć się funkcjami stristr i strstr. Funkcje robią to samo z tym wyjątkiem, że stristr nie zwraca uwagi na wielkość liter. Odpowiednikami tych funkcji są także eregi i ereg z takim wyjątkiem, że funk-cje mogą, aczkolwiek nie muszą, zostać uży-te z wyrażeniami regularnymi ( np. aby okre-ślić konkretny zakres sprawdzania w danym tekście, zamiast sprawdzać w całości). Funckja eregi nie zwraca uwagi na wielkość liter.

Funkcji możemy użyć zatem tak – Listing 7. Funkcje te można użyć także do wielu innych

rzeczy, np. do sprawdzania poprawności adre-sów e-mail podczas rejestracji użytkowników. W artykule nie będziemy podawać przykładów, które wymagają użycia dużo bardziej zaawanso-wanych wyrażeń regularnych. Funkcje te są zwy-kłymi funkcjami pozwalającymi na sprawdzenie tylko „na sztywno” podanego ciągu znaku. Co jednak, jeśli chcemy sprawdzić, czy w tekście znajduje się jakikolwiek wyraz zaczynający się np. na literę g, a kończący na literę a? Przykład – nie znamy treści tekstu, chcemy wiedzieć, czy zawiera słowo według podanych kryteriów, mo-że to być np. gitara czy glazura. Z pomocą przyj-dzie nam funkcja preg_match korzystająca z wy-rażeń regularnych. Na temat wyrażeń regular-

Page 45: PHP Solutions 05 2007 PL

05/2007

Technika

44

Operacje na tekście

www.phpsolmag.org 45

MICHAŁ GACKIJest programistą-samoukiem. Od najmłodszych lat interesuje się informatyką, a najbardziejprogramowaniem, które jest jego pasją. Pracu-je w założonej przez siebie grupie (obecnie nie tylko programistycznej) pod nazwą Bil Software(www.bilsoftware.com).Kontakt z autorem: [email protected]

Listing 11. Tworzenie funkcji change_encoding

<?

function change_encoding($string, $type)

{

$win2utf = array(

"\xb9" => "\xc4\x85", "\xa5" => "\xc4\x84", "\xe6" => "\xc4\x87", "\xc6" => "\

xc4\x86", "\xea" => "\xc4\x99", "\xca" => "\xc4\x98",

"\xb3" => "\xc5\x82", "\xa3" => "\xc5\x81", "\xf3" => "\xc3\xb3", "\xd3" => "\

xc3\x93", "\x9c" => "\xc5\x9b", "\x8c" => "\xc5\x9a",

"\xbf" => "\xc5\xbc", "\x8f" => "\xc5\xbb", "\x9f" => "\xc5\xba", "\xaf" => "\

xc5\xb9", "\xf1" => "\xc5\x84", "\xd1" => "\xc5\x83",

);

$iso2utf = array(

"\xb1" => "\xc4\x85", "\xa1" => "\xc4\x84", "\xe6" => "\xc4\x87", "\xc6" => "\

xc4\x86", "\xea" => "\xc4\x99", "\xca" => "\xc4\x98",

"\xb3" => "\xc5\x82", "\xa3" => "\xc5\x81", "\xf3" => "\xc3\xb3", "\xd3" => "\

xc3\x93", "\xb6" => "\xc5\x9b", "\xa6" => "\xc5\x9a",

"\xbc" => "\xc5\xba", "\xac" => "\xc5\xb9", "\xbf" => "\xc5\xbc", "\xaf" => "\

xc5\xbb", "\xf1" => "\xc5\x84", "\xd1" => "\xc5\x83"

);

if ($type == 'ISO-8859-2->UTF-8') {

return strtr($string, $iso2utf);

}

else if ($type == 'UTF-8->ISO-8859-2') {

return strtr($string, array_flip($iso2utf));

}

else if ($type == 'WINDOWS-1250->UTF-8') {

return strtr($string, $win2utf);

}

else if ($type == 'UTF-8->WINDOWS-1250') {

return strtr($string, array_flip($win2utf));

}

else if ($type == 'ISO-8859-2->WINDOWS-1250') {

return strtr($string, "\xa1\xa6\xac\xb1\xb6\xbc", "\xa5\x8c\x8f\xb9\x9c\x9f");

}

else if ($type == 'WINDOWS-1250->ISO-8859-2') {

return strtr($string, "\xa5\x8c\x8f\xb9\x9c\x9f", "\xa1\xa6\xac\xb1\xb6\xbc");

}

}

?>

Listing 12. Zmiana kodowania

<?

$tekst = 'Tekst z polskimi znakami ęóąśłżźćń zapisany w ISO-8859-2';

$tekst = change_encoding($tekst, 'ISO-88592->WINDOWS-1250');

echo $tekst;

?>

Listing 13. Użycie składni heredoc

<?

$a = 5;

$b = 'Ala ma kota';

$c = 'a kot ma Alę';

$d = array('test1', 'test2');

echo <<<EOT

“Witamy panie i panowie”

'To jest nasz test składni heredoc',

Teoretycznie nie ma tu ograniczeń.

3 + $a = 8.

$b,$cMożemy także wyświetlać zawartości

tablic, np. $d[0] EOT;

?>

Listing 14. Przykład zastosowania znaków specjalnych

<?

$tekst = “Ten tekst\n przechodzi

do\n nowej linii. Mam dużo

\$becho $tekst;

/*

Przykład wyświetli:

Ten tekst

przechodzi do

nowej linii

Mam dużo $

*/

?>

dowania, a jako drugi typ kodowania. W za-leżności od tego jak kodowany jest tekst i jaki chcemy otrzymać wynik, podajemy odpowied-ni parametr. Do dyspozycji mamy zamianę ISO-8859-2 na UTF-8 i odwrotnie, WINDOWS-1250 na UTF-8 i odwrotnie oraz ISO-8859-2 na WINDOWS-1250 i odwrotnie. Przykład użycia funkcji – Listing 12.

CiekawostkiDo zapisywania większej ilości tekstu wraz ze zmiennymi i znakami specjalnymi bez obaw o cudzysłowy możemy użyć składni heredoc. Po funkcji echo lub print zamiast cudzysłowu

otwieramy operator <<< po którym umieszcza-my identyfikator składni (tym samym identy-fikatorem należy zamknąć tekst). W przypad-ku prostszego tekstu posłuży nam identyfika-tor EOT. Przykład zastosowania – Listing 13.Możemy także wyświetlać zawartości tablic.

Pamiętajmy o tym, że identyfikator kończący tekst musi zaczynać się w pierwszej kolumnie nowej linii, by nie spowodował błędu składni,a po nim może znajdować się tylko średnik.

Warto wiedzieć, że wyświetlanie tekstu w podwójnym cudzysłowie pozwala na używa-nie znaków specjalnych (cytowanych). Może-my zapisać przejście do nowej linii, akapit itp.

Oto lista najczęściej używanych znaków spe-cjalnych:

• \n — nowa linia;• \r — powrót karetki;• \t — tabulacja pozioma;• \\ — odwrotny ukośnik;• \$ — znak dolara.

Jak widać, znaki te zapisuje się poprzedzającje backslashem. Przykład zastosowania – Li-sting 14.

PodsumowaniePrzedstawione tu informacje o operacjach na tek-ście to tylko podstawy. Przy pomocy bardziej za-awansowanych funkcji oraz wyrażeń regularnych możemy z tekstem robić dużo więcej. Mamynadzieję, że artykuł naprowadzi Cię na dalsze kro-ki w tej dziedzinie, która jest bardzo przydatnaw większości skryptów PHP.

Page 46: PHP Solutions 05 2007 PL

05/2007

Technika

46

Formularz internetowy

www.phpsolmag.org 47

Odpowiednie połączenie tych techno-logii, które potrafi asynchronicznie, czyli niewidocznie dla użytkownika,

wywoływać skrypty umieszczone na serwerze, nazywamy AJAX-em. Aby przybliżyć Ci za-lety i tajniki AJAX-u, wykonamy formularz,w którym wprowadzone dane sprawdzimy już w momencie jego wypełniania.

Przygotowanie formularza HTMLZałóżmy, że nasz formularz ma posłużyć do wysyłania wiadomości e-mail. Będziemypotrzebować pola: Imię i Nazwisko, Twój adres e-mail i Wiadomość. Na samym początku zaj-mijmy się podstawowym przygotowaniem for-mularza przy wykorzystaniu tagów HTML-a. Kod naszej propozycji formularza zaprezento-wany jest w Listingu 1. Oczywiście istnieje do-wolność w formatowaniu wyglądu, należy jed-nak zwrócić uwagę na pewne istotne punkty.

Kluczowymi elementami formularza są wywołania funkcji check w momencie, gdy użytkownik opuści dane pole, i funkcja send_message() w momencie, gdy wyśle wiado-mość. Założenie zatem jest dość proste: w mo-mencie, gdy użytkownik wpisze dane, spraw-dzimy je i w razie potrzeby wyrazimy naszą dezaprobatę. Tutaj jednak napotykamy na pe-wien problem. Cóż bowiem się stanie, jeżeli internauta jest niebywałym mistrzem klawia-tury i bardzo szybko przechodzi z jednego po-la do drugiego? Wtedy istnieje możliwość, że

wzrośnie obciążenie serwera i nadchodzące informacje przyjdą w niewłaściwej kolejności. Dlatego stworzymy kolejkę, która będzie prze-chowywać zgłoszenia.

AJAX – asynchroniczny JavaScript i XMLNadszedł długo oczekiwany moment. Stworzy-my główną część naszej aplikacji, której zada-niem będzie niewidoczne dla użytkownika wy-wołanie skryptów PHP umieszczonych na ser-werze. Na starcie stwórzmy zmienną przecho-wującą obiekt XMLHttpRequest. Robimy tow pliku index.html – dokładnie tym, w którym umieściliśmy nasz formularz. W tym momencie używać będziemy języka JavaScript, zatem waż-ne jest, aby prawidłowo umieścić go w struktu-rze dokumentu HTML. JavaScript wstawiamyw sekcji HEAD pomiędzy znaczniki:

<script language="javascript"

type="text/javascript">/*

<![CDATA[ */ TU TU TU będzie nasz

skrypt JavaScript /* ]]> */</script>

Jeżeli istnieje taka potrzeba możemy również wczytać kod z innego pliku, właśnie tak:

<script type="text/javascript"

src="nazwa_pliku.js"></script>

również w sekcji HEAD

Wracając jednak do deklaracji zmiennej prze-chowującej obiekt XMLHttpRequest, wykonu-jemy to w następujący sposób: var xmlHttp = createXmlHttpRequestObject();.

Potrzebna będzie także zmienna przecho-wująca adres wywoływanego skryptu var

script_name =”checker.php”; i tablica służą-ca jako pamięć podręczna żądania var memory = new array. Niestety, funkcja createXmlHttpRequestObject(); nie należy do standardu i trzeba samemu ją napisać. Pamiętając o obsłu-dze błędów, np. za pomocą bloku try/catch, spróbujmy stworzyć obiekt XMLHttpRequest: xmlHttp = new XMLHttpRequest();. Takie wy-wołanie powinno odnieść pożądany skutek we wszystkich przeglądarkach oprócz magiczne-go Internet Explorera od wersji szóstej w dół.W przypadku IE skorzystamy z następującego kodu: xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); Oczywiście, powinniśmy także sprawdzić, czy nasze działania zostały wykonane w poprawny sposób. Na Listingu 2. przedstawiliśmy, jak powinna wyglądać funk-cja createXmlHttpRequestObject();

Następnym krokiem jest utworzenie funk-cji check. Na samym początku pamięta-my oczywiście, aby dodać nowo wprowadzo-ne przez użytkownika dane do pamięci pod-ręcznej. Robimy to, używając polecenia push:memory.push("inputValue="+inputValue

+"&fieldID="+fieldID);. Po tym zabiegu sprawdzamy już tylko, czy obiekt XMLHttp-Request jest wolny i czy nasza pamięć podręcz-na nie jest pusta, gdyż w przeciwnym wypad-ku nie mielibyśmy czego sprawdzać. Teraz wy-starczy już pobrać dane oczekujące w kolejce i wykonać żądanie. Jak wykonać funkcję check, przedstawiliśmy na Listingu 3.

Tak naprawdę wykonaliśmy właśnie ser-ce aplikacji AJAX. Winni jesteśmy pań-stwu jeszcze jedno wyjaśnienie. Użyta meto-da readyState zwraca status żądania, gdzie 0 oznacza, że nie zainicjalizowano żądania,1 – trwa ładowanie, 2 – załadowano, 3 – in-teraktywne, 4 – zakończono żądanie. Teraz należy stworzyć skrypt, który odbierze przy-chodzące informacje. Wykona to funkcja handleRequestStateChange. Nieocenioną umie-jętnością w tym przypadku jest poprawne po-sługiwanie się strukturą XML. Spójrzcie, jak

Formularz internetowy

Ostatnimi czasy zaistniała potrzeba tworzenia coraz doskonalszych aplikacji internetowych, w których interfejs usprawnia pracę internauty. Kolejnym krokiem do zapewnienia lepszej funkcjonalności witryn WWW jest zastosowanie połączenia kilku technologii: JavaScript, PHP lub innego języka strony serwera, DOM, XMLHttpRequest.

Dowiesz się...• Jak sprawdzić dane formularza bez przełado-

wania witryny.

Powinieneś wiedzieć...• Znać HTML wraz z DOM, PHP, JavaScript.

Poziom trudności

Walidacja z wykorzystaniem AJAX

Page 47: PHP Solutions 05 2007 PL

05/2007

Technika

46

Formularz internetowy

www.phpsolmag.org 47

Listing 1. Podstawowa konstrukcja formularza

<FORM id="form_kontakt"><!--Nie musimy podawać argumentu dla action, gdyż wywołamy odpowiednie pliki asynchronicznie (czyli w tle),

wtedy też podamy, jaki plik wywołujemy i jakiej metody użyjemy do przekazania danych-->

<div style="float:left; display:inline; padding-right:10px;"><!--diplasy:inline użyte tylko po to, aby naprawić błędne

wyświetlanie elementów float przez Internet Explorera-->

<label style="padding-bottom:2px;" for="nazwa_klienta">Imi&#281; i nazwisko:</label>

<label style="padding-bottom:2px;" for="adres_email">Adres e-mail:</label>

<label style="padding-bottom:2px;" for="wiadomosc">Wiadomo&#347;&#263;:</label>

</div>

<div style="float:right: display:inline; ">

<input style="padding-bottom:2px;" id="nazwa_klienta" name="nazwa_klienta" type="text" value="" onblur="check(this.va

lue,this.id)"/><!--wywołanie funkcji check spowoduje połączenie z odpowiednim skryptem na serwerze, który

sprawdzi dane i prześle odpowiedź-->

<input style="padding-bottom:2px;" id="adres_email" name="adres_email" type="text" value="" onblur="check(this.value,thi

s.id"/>

<textarea style="padding-bottom:2px;" name="wiadomosc" cols="19" rows="10" id="wiadomosc"></textarea>

</div>

<div style="padding-top:5px;padding-bottom:5px; font-size:14px;">

<div id="comunication"><!--Tutaj wyświetlimy komunikaty nadesłane z serwera--></div></div>

<div style="clear:both; padding-left:209px;">

<INPUT type="button" name="ok" value="Wy&#347;lij" id="wyslij" onclick="send_message();"><!--Funkcja send_message() uruchomi

odpowiedni skrypt na serwerze, który wyśle wiadomość-->

</div>

</FORM>

Listing 2. Sprawdzanie poprawności stworzenia obiektu XMLHttpRequest

var xmlHttp;

if (window.ActiveXObject)

{

try

{

xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); //w ten sposób robimy to specjalnie dla IE

}

catch (e)

{

xmlHttp = false; //jeżeli nie udało się stworzyć – z reguły znaczy to, że ktoś nie używa IE

}

}

else

{

try

{

xmlHttp = new XMLHttpRequest(); //tu powołujemy obiekt XMLHttpRequest dla wszystkich innych przeglądarek poza IE

}

catch (e)

{

xmlHttp = false;

}

}

if (!xmlHttp)

{

alert("Bе┌д┘d podczas tworzenia obiektu XMLHttpRequest.");

}

else

{

return xmlHttp;

}

Page 48: PHP Solutions 05 2007 PL

05/2007

Technika

48

Formularz internetowy

www.phpsolmag.org 49

Listing 3. Funkcja check realizująca asynchroniczne wywołanie serwera

function check (inputValue,fieldID) //inputValue – wartość pola, fieldID – ID pola

{

//o ile obiekt jest stworzony

if (xmlHttp)

{

//o ile podaliśmy ID

if (fieldID)

{

inputValue = encodeURIComponent(inputValue);

fieldID = encodeURIComponent(fieldID);

memory.push("inputValue="+inputValue+"&fieldID="+fieldID);//dodajemy wprowadzone dane do kolejki

}

try

{

if ((xmlHttp.readyState == 4 || xmlHttp.readyState == 0) && memory.length > 0)

{

var EntryInformation = memory.shift(); //pobieramy dane z kolejki

xmlHttp.open("POST",script_name,true);

xmlHttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded");

//onreadystatechange użyliśmy do ustawienia funkcji zwrotnej, która obsługuje zmiany statusu żądania

xmlHttp.onreadystatechange = handleRequestStateChange;

xmlHttp.send(EntryInformation);

}

}

catch(e)

{

}

}

}

Listing 4. Odbieramy nadchodzące dane

// wcześniej sprawdzamy, czy dane już zostały dostarczone, robimy to dzięki xmlHttp.status, którego wartość powinna wynosić 200,

// i xmlHttp.readyState, którego wartość powinna wynosić 4

var hellomessage,myDiv;

//zwraca odpowiedź serwera w postaci dokumentu XML, możemy również użyć responseText, a jako zwrot otrzymay łańcuch znaków

xmlResponse = xmlHttp.responseXML;

xmlDocumentElement = xmlResponse.documentElement;

hellomessage = xmlDocumentElement.firstChild.data;

myDiv = document.getElementById("comunication");

//wstawiamy otrzymaną wiadomość do bloku opatrzonego id comunication

myDiv.innerHTML = hellomessage;

setTimeout(”check();”,500) //ponowne wywołanie funkcji check, żeby obsłużyła kolejne żądania oczekujące w kolejce

Listing 5. Zabezpieczenie przed wstrzyknięciem kodu

function form_cleaner()

{

$_POST['inputValue']=htmlspecialchars($_POST['inputValue']);

$_POST['fieldID']=htmlspecialchars($_POST['inputfieldID']);

}

Listing 6. Konstruowanie odpowiedzi XML

header('Content-Type: text/xml');

echo '<?xml version="1.0" encoding="utf-8" standalone="yes"?>';

echo '<response>';

//tutaj wykonujemy funkcje sprawdzające poprawność danych

echo'</response>';

Page 49: PHP Solutions 05 2007 PL

05/2007

Technika

48

Formularz internetowy

www.phpsolmag.org 49

odebraliśmy nadchodzące informacje na Li-stingu 4.

Dokonaliśmy zatem niezbędnych czynności po stronie klienta. Teraz zajmiemy się walida-cją wprowadzanych danych i stworzeniem od-powiedzi w postaci struktury XML. Przedsta-wione wyżej skrypty nie są kompletne z uwagi na ich rozpiętość. Zapraszamy jednak do sko-rzystania ze źródeł znajdujących się na płycie dołączonej do wydania.

PHP i walidacja danychformularzaStwórzmy teraz plik checker.php. Na wstę-pie przetwarzania wpisanych przez użyt-kownika informacji należałoby przekształ-cić znaki <,>,” i & na nieszkodliwe odpowied-niki, przeciwdziałając w ten sposób możli-wym atakom. Użyjmy do tego celu funkcji htmlspecialchars, tak jak jest to zaprezento-wane na Listingu 5.

Następnie napiszmy skrypt, który wywo-ła odpowiednią funkcję sprawdzająca w za-leżności od tego, jakie pole jest aktualnie pod-dawane kontroli. Wystarczy, jeżeli w tym ce-

lu użyjemy instrukcji switch lub zapytań wa-runkowych if. Kiedy już to zrobimy, może-my tworzyć poszczególne funkcje dla każ-dego rodzaju przesyłanych danych. I tak: dla pola Imię i Nazwisko stwórzmy funkcję check_name($value). Jego walidacja polegać będzie na sprawdzeniu, czy składa się wyłącz-nie z liter. Musimy także sprawdzić, czy dany tekst nie jest za długi i czy w ogóle został wpi-sany. Aby sprawdzić, czy zmienna zawiera-wyłącznie litery, użyliśmy funkcji ereg (lub preg_match) i kombinacji wyrażeń regular-nych: ereg('^[a-zA-Z]+$',$value). Do po-równania długości z wartością minimalną i maksymalną wystarczyła zwykła konstrukcja zapytania warunkowego. W przypadku po-myślnej walidacji funkcja nie powinna nic ro-bić, jeżeli jednak wprowadzony tekst nie prze-szedł pomyślnie naszych testów, to zwracamy komunikat o błędzie.

Podobnie ma się zachowywać funkcja spraw-dzająca poprawność wprowadzonego adre-su e-mail. Tutaj również ograniczymy się do sprawdzenia długości i struktury adresu. Oto wyrażenie regularne, które zastosowali-

śmy do sprawdzenia struktury adresu e-mail:'^[0-9a-zA-Z_.-]+@([0-9a-zA-Z-]+.)+

[a-zA-Z]{2,4}$'.Najważniejszym jednak elementem z punk-

tu widzenia aplikacji AJAX jest teraz popraw-ne skonstruowanie odpowiedzi w formacie do-kumentu XML. Operacja ta przedstawiona jest na Listingu 6.

Tak stworzony skrypt zapewni nam spraw-dzenie i odebranie danych przez wcześniej na-pisaną aplikację. Ostatnim etapem będzie wy-słanie wiadomości. Całą tę operację zapiszmy w pliku mail.php. Aby wysłać e-mail, musimy skorzystać z funkcji mail: @mail($email,

$subject, $message, $headears). Nie zapo-mnijmy także o ponownej walidacji danych for-mularza. Jest to konieczne z uwagi na potrzebę wzmocnienia poziomu zabezpieczeń. W prze-ciwnym wypadku jakiś internetowy złoczyń-ca mógłby skorzystać z naszego formularzaw sobie tylko znanych niecnych zamiarach.Jeżeli nigdy nie korzystaliście z funkcji @mail, to chcielibyśmy uczulić przede wszystkim na zawartość nagłówka. Istotnym jest, abyście wprowadzili niezbędne tam informacje, tak jak na Listingu 7.

W pliku index.html dodajmy do naszego skryptu JavaScript funkcję send_message. Tak naprawdę powinniście umieć już ją wykonać. Aby wywołać skrypt PHP w pliku mail.php w sposób niewidoczny dla użytkownika, utwórz-cie nowy obiekt var xmlHttp1 = createXmlHttpRequestObject(); i postępujcie identycz-nie jak przy tworzeniu funkcji check, zmienia-jąc tylko nazwę obiektu xmlHttp na xmlHttp1. Przy wysyłaniu wiadomości nie potrzebujemy pamięci podręcznej, gdyż mamy zamiar obsłu-giwać tylko jedno żądanie. Na Listingu 8 poka-zaliśmy funkcję send_message. Wysłaliśmy tam asynchroniczne żądanie i przesłaliśmy da-ne do podanego skryptu, ale tym razem meto-dą GET. Zabieg ten nie był podyktowany jakimiś praktycznymi względami, chcieliśmy po pro-stu pokazać, jak wysłać dane zarówno metodą POST, jak i GET.

PodsumowanieW naszym artykule skupiliśmy się na poka-zaniu, jak działają asynchroniczne wywołania serwera. Ważnym jest, abyś wiedział, jak wy-słać i pobrać informacje ze skryptu PHP oraz jak skonstruować przy pomocy tegoż skryptu poprawną odpowiedź w formacie dokumentu XML. Sama walidacja danych została przed-stawiona w podstawowym zakresie, ale to już jest temat na kolejny, sami przyznacie, bardzo ciekawy artykuł.

TOMASZ ROSZKOAutor jest studentem III roku informatyki na Uniwer-sytecie w Białymstoku.Kontakt z autorem: [email protected]

Listing 7. Zawartość nagłówka wiadomości e-mail.

$headears="From: Wiadomość od:".$email_od."\n";

$headears.="Reply-To:".$email_od."\n";

$headears.="MIME-Version: 1.0\n";

$headears.="Content-Type: multipart/alternative; boundary=\"$mime_boundary\"\n";

$message = "--$mime_boundary\n";

$message .= "Content-Type: text/plain; charset=UTF-8\n";

$message .= "Content-Transfer-Encoding: 8bit\n\n";

$message .= "$subject:\n\n";

$message .= "E-mail z Twojej strony internetowej\n";

Listing 8. Funkcja send_message().

function send_message()

{

if (xmlHttp1.readyState == 4 || xmlHttp1.readyState == 0)

{

nazwa = encodeURIComponent(document.getElementById("nazwa_klienta").value);

mail = encodeURIComponent(document.getElementById("adres_email").value);

wiadomosc = encodeURIComponent(document.getElementById("wiadomosc").value);

var parametry;

parametry = "?nazwa="+nazwa+"&mail="+mail+"&wiadomosc="+wiadomosc;

//tutaj dane przesy&#322;amy getem - w&#322;a&#347;ciwie dla tego tylko,

//&#380;eby pokaza&#263; &#380;e si&#281; da

xmlHttp1.open("GET","mail.php"+parametry,true);

xmlHttp1.onreadystatechange = handleRequestStateChange1;

xmlHttp1.send(null);

}

else

{

alert ('Serwer chwilowo zajęty');

}

Page 50: PHP Solutions 05 2007 PL

05/2007

Technika

50

CakePHP

www.phpsolmag.org 51

Wielokrotne generowanie stron, których zawartość nie zmienia się, powoduje zbędne obciążenie

serwera WWW i bazy danych. Każde żąda-nie od użytkownika musi zostać przetworzo-ne, a wynik skierowany do przeglądarki in-ternetowej, jednak za każdym razem serwer pobiera te same dane. Nie stanowi to proble-mu, gdy strony przegląda kilku użytkowni-ków, ale kłopoty mogą się pojawić wraz ze wzrostem popularności serwisu. Im więcej użytkowników, tym bardziej serwer będzie obciążony wykonywaniem zadań, które pro-wadzą do wyświetlenia stron nieróżniących się od siebie wcale lub różniących się tylko w niewielkim stopniu. Naturalnym rozwią-zaniem problemu spadku wydajności jest zapisywanie „na boku” generowanych stron i udostępnianie ich innym użytkownikom. Zamiast za każdym razem generować tą samą zawartość strony wystarczy przy pierwszym wyświetleniu zapamiętać ją i używać przy kolejnych wywołaniach. Technika ta nazywa się page caching (pol. buforowanie stron) i jest najprostszą metodą przyspieszania działania serwisu. Przy pierwszym wyświetleniu stro-na zostaje zapisana do pliku HTML. Każde następne odwołanie do tej samej strony po-woduje, iż do przeglądarki użytkownika zo-stanie wysłana zawartość bezpośrednio z ca-

che z pominięciem wszystkich etapów, dzię-ki którym strona została pierwotnie wygene-rowana. W praktyce odbywa się to prawie tak szybko jak udostępnianie statycznych stron HTML.

Cache przeglądarki internetowejZamiast za każdym razem generować tę sa-mą zawartość strony wystarczy przy pierw-szym wyświetleniu zapamiętać ją i używać przy kolejnych wywołaniach – Rysunek 1. Właściwie w każdej nowoczesnej przeglą-darce internetowej znajdują się ustawienia związane z cache. Najczęściej jest to para-metr określający miejsce zarezerwowane na pliki tymczasowe przeglądanych stron WWW. Zasady aktualizacji cache są proste. Przeglądarka za pomocą znaczników HTTP sprawdza, czy przechowywane dane są aktu-alne – Listing 1. i w miarę możliwości korzy-sta z nich zamiast pobierać dane z Internetu. Dzięki temu powrót do poprzednio wyświe-tlanej strony WWW w przeglądarce (przy-cisk „wstecz”) powoduje szybkie wyświetle-nie informacji, najczęściej bez konieczności pobierania danych z sieci.

Kontrola aktualności cache odbywa się za pomocą znaczników: Cache - Control, Expires, Last-Modified oraz ETag przesyła-nych w nagłówku stron WWW:

Expires: DATA_GMT – Znacznik określa ter-min możliwej zmiany lub wygaśnięcia ważno-ści dokumentu. Po upływie określonego cza-su (liczonego względem GMT) dokument mo-że ulec zmianie lub zostać usunięty. Najprost-szym sposobem wymuszenia, by plik nie zo-

stał umieszczony w cache, jest przypisanie da-ty, która minęła. Znacznik idealnie nadaje się do określania polityki tworzenia cache plików graficznych, które bardzo rzadko ulegają zmia-nie – Listing 1.

Cache-Control: DYREKTYWA – Znacznik określa sposób zachowania mechanizmu bu-forowania plików (zarówno serwera WWW, jak i serwerów proxy) mówiącego, co powin-no być buforowane i co może być przechowy-wane w cache. Najbardziej przydatne są poniż-sze dyrektywy:

• max-age=[sekundy] – określa maksy-malny czas, w którym dane są traktowa-ne jako aktualne (liczony względem da-ty określonej przez znacznik Last-Modi-fied);

• public – wymusza buforowane (przy-datne dla stron, które w normalnych warunkach nie są umieszczane w ca-che);

CakePHP

Cache jest mechanizmem umożliwiającym zredukowanie opóźnienia w dostarczaniu danych do użytkownika oraz zmniejszenia obciążenia serwera. W aplikacjach internetowych często zachodzi konieczność wyświetlania tych samych informacji wielokrotnie. Np. sklep internetowy wyświetla listę dostępnych produktów w odpowiedzi. na każde żądanie potencjalnych klientów.

Dowiesz się...• Poznasz różne techniki buforowania stron

WWW oraz możliwość ich praktycznego zasto-sowania w CakePHP.

Powinieneś wiedzieć...• Wymagana jest znajomość framework Cake-

PHP. Przydatna będzie znajomość języka SQLi umiejętność administrowania bazą danych MySQL.

Poziom trudności

Buforowanie stron

Listing 1. Przykładowe żądanie i odpowiedź serwera WWW

HEAD / HTTP/1.1

HOST: localhost

HTTP/1.1 200 OK

Date: Mon, 02 Jul 2007 21:24:12 GMT

Server: Apache

Accept-Ranges: bytes

X-Powered-By: PHP/4.2.2

Set-Cookie: PHPSESSID=

ac35d9b842215f4fb23ca419337af4b8;

path=/

Expires: Thu, 19 Nov 1981 08:52:00 GMT

Cache-Control: no-store, no-cache,

must-revalidate

Pragma: no-cache

Connection: close

Content-Type: text/html

Content-Language: pl

Page 51: PHP Solutions 05 2007 PL

05/2007

Technika

50

CakePHP

www.phpsolmag.org 51

• no-cache – wymusza pominięcie cache (przeglądarki i serwerów proxy) i pobiera-nie danych bezpośrednio z Internetu;

• no-store – wymusza usunięcie danych z cache zaraz po przesłaniu ich do użytkow-nika;

• must-revalidate – wymusza sprawdzanie stanu przedawnionych dokumentów znaj-dujących się w cache.

Last-Modified: DATA _ GMT – Znacznik okre-śla datę ostatniej modyfikacji dokumentu – Listing 1. ETag: ZNACZNIK – Znacznik jest unikalnym identyfikatorem strony genero-wanym przez serwer WWW, który zmie-nia się za każdym razem, gdy przesyłane da-ne ulegną modyfikacji. Znaczniki możemy wysyłać do przeglądarki za pomocą funkcji header() – Listing 2.

Page cacheMechanizm page cache jest powiązany z adre-sem URL. To na jego podstawie w kontrolerze zapada decyzja, czy strona ma być wygenero-wana dynamicznie, czy też ma być użyty ca-che. Jeżeli użytkownik już wcześniej odwoły-wał się do określonego adresu WWW, to zo-stał wygenerowany plik cache – o ile prezen-towane informacje nie uległy zmianie, to po-nowne odwołanie do tego samego adresu spo-woduje udostępnienie danych wcześniej wy-generowanych. Z tego faktu wynikają pewne ograniczenia. Strony zależące od parametrów przekazywanych w URL lub od informacji przechowywanych w sesji nie będą mogły skorzystać z page cache, podobnie jak strony, których zawartość jest uzależniona od czasu. Adres WWW takich witryn może być za każ-dym razem inny i w ten sposób ciągle byłyby tworzone kolejne pliki cache. Sposób działa-nia page cache (buforowanie całej strony) do-skonale sprawdza się w przypadku stron, któ-rych zawartość nie zmienia się często. Jest to trudne do osiągnięcia, jeżeli prezentowanych jest wiele danych z różnych źródeł. Jeżeli zaj-dą zmiany w dowolnym źródle danych, plik cache będzie musiał zostać ponownie wyge-

nerowany, co przy częstych zmianach sta-wia pod znakiem zapytania sens używania tej technologii. W praktyce jednak strony WWW można podzielić na fragmenty, które są statyczne, oraz na takie, które zmieniają się często, i poddać buforowaniu tylko te ostat-nie. Wynikowa strona jest wtedy „składana” z mniejszych bloków, z których część będzie znajdowała się w cache. Jest to bardzo intu-icyjne – w przypadku sklepu internetowego lista produktów jest fragmentem, który naj-rzadziej ulega zmianom, a z kolei koszyk za-kupów klienta może zmieniać się dynamicz-nie. Wykorzystanie page cache w CakePHP zaczniemy od zapoznania się konfiguracją serwera. Domyślnie cache widoków jest za-blokowane. By je aktywować, musimy zmie-

nić na TRUE wartość stałej CACHE_CHECK w pliku /app/config/core.php. define ('CACHE_

CHECK', true); W kontrolerze powiązanym z widokiem, dla którego włączamy cache, mu-simy dodać CacheHelper przez umieszczenie kodu: var $helpers = array('Cache');

Następnie należy określić, co chcemy umie-ścić w cache. Do zmiennej $cacheAction mu-simy przypisać tablicę zawierającą akcje, któ-re mają być buforowane, oraz czas (w sekun-dach), przez który cache ma być aktualny (można używać liczb lub zapisów „1 day” czy „60 seconds”) – Listing 3.

W praktyce zdarza się, że pewne fragmen-ty strony są wypełniane dynamicznymi dany-mi i nie mogą znaleźć się w cache ze względu na wyświetlane dane. Wystarczy wówczas, że

Rysunek 1. Przesyłanie do użytkowników stron WWW

�����������������������������������

�����������

����������

�����������������������������������������

����������������������������������

�����������

����������

�����

Listing 2. Przykładowe znaczniki wysyłane do przeglądarki za pomocą funkcji header()

// strona nie będzie umieszczona w cache przeglądarki

// wymaga obsługi protokołu HTTP/1.1

header("Cache-Control: no-cache, must-revalidate");

// data z przeszłości

header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");

Lisgin 3. Definiowanie akcji, które mają zostać buforowane przez CakePHP

// Możemy zdefiniować cache dla wszystkich parametrów akcji

var $cacheAction = array('view/' => 86400);

// lub dla każdego parametru oddzielnie.

var $cacheAction = array(

'view/23/' => 21600,

'view/48/' => 21600

);

QUERY-CACHENowoczesne bazy danych są najczęściej wy-posażone w mechanizm QUERY-CACHE po-wodujący zapamiętanie zapytań kierowa-nych do bazy oraz skojarzonych z nimi wyni-ków. Takie samo zapytanie SQL do bazy da-nych spowoduje przekazanie wyników prze-chowanych w QUERY-CACHE bez koniecz-ności odczytu informacji z plików bazoda-nowych. Cache jest czyszczony w przypad-ku zmian w tabelach do których zapytania się odwołują. MySQL4 posiada błąd, któ-ry powoduje czyszczenie całego QUERY-CA-CHE w przypadku zmian w dowolnej tablicy, co niekorzystnie wpływa na czas odpowiedzi serwera na te same zapytania.

Page 52: PHP Solutions 05 2007 PL

05/2007

Technika

52

CakePHP

www.phpsolmag.org 53

fragment kodu, który nie powinien być bu-forowany, określimy za pomocą znaczników

<cake:nocache> oraz </cake:nocache> – Li-sting 4.

Czyszczenie cachePrzy korzystaniu z cache kluczowym momen-tem w funkcjonowaniu serwisu jest sposób reakcji na zmiany w prezentowanych danych. Jeżeli nie będzie sprawnego mechanizmu in-formowania aplikacji, które pliki cache nale-ży usunąć, to użytkownikowi zostaną zapre-

zentowane błędne nieaktualne dane. W naj-prostszym przypadku w reakcji na zmiany możemy usunąć wszystkie pliki cache. Roz-wiązanie to ma jednak tę wadę, że nawet nie-wielka zmiana spowoduje konieczność czaso-chłonnego generowania cache na nowo. Zde-cydowanie lepiej jest usuwać tylko te pliki ca-che, które prezentują dane, które uległy zmia-nie. CakePHP wykorzystuje fakt, że prezen-towane na stronach WWW dane są powią-zane z modelem danych, a cache jest związa-ny z widokiem (który z kolei może wyświe-

tlać dane z różnych modeli danych). Zmia-na danych dowolnego modelu spowoduje więc automatyczne usunięcie cache dla całe-go widoku. CakePHP automatycznie usuwa cache, gdy nastąpi zmiana w stanie aplikacji. Jeżeli użytkownik spowoduje działanie, któ-re będzie skutkowało zmianami w bazie da-nych (INSERT, UPDATE, DELETE), zosta-nie usunięte cache dla widoku powiązane-go z kontrolerem odwołującym się do mode-li danych, które uległy zmianie. Istnieje moż-liwość ręcznego sterowania cache i usuwania nieaktualnych danych za pomocą funkcji cle-arCache(). Jako parametr przekazujemy na-zwę kontrolera, kontrolera i akcji lub kontro-lera, akcji i parametrów identyfikujących plik cache – Listing 5.

W prosty sposób można zaimplemento-wać dodatkowy scenariusz czyszczenia cache sprawdzający się w sytuacjach, gdy nie może-my zapobiec cyklicznemu tworzeniu się no-wych plików cache, ale jednocześnie chcemy uniknąć ciągłego usuwania plików. Zamiast spowalniać działanie aplikacji ciągłym testo-waniem aktualności cache możemy urucho-mić dodatkowy proces działający w tle. Pro-ces ten będzie odpowiedzialny za cykliczne czyszczenie cache co określony (konfiguro-walny) przedział czasowy niezależnie od ob-ciążenia aplikacji.

Składowanie danychNajprostszym sposobem przechowywania cache są pliki umieszczone bezpośrednio w systemie operacyjnym (jest to jedyny sposób przechowywania cache obsługiwany automa-tycznie przez CakePHP w wersji 1.1.xx). Wy-generowana strona HTML zostanie umiesz-czona w publicznie dostępnym obszarze ser-wera (w miejscu określonym ścieżką dostępu wskazywaną przez stałą $CACHE) – każde od-wołanie do tej samej strony będzie wówczas przekierowywane na plik cache. Rozwiąza-nie takie oprócz oczywistych zalet, takich jak prostota implementacji czy dobra wydajność, ma podstawową wadę: nie jest rozwiązaniem skalowalnym. Jeżeli nasz serwis będzie się rozwijał i zajdzie konieczność uruchomie-nia dodatkowego serwera WWW, to stanie-my przed problemem związanym z dystry-bucją plików cache i ich synchronizacją. Za-pytania od użytkowników mogą być kierowa-ne do dowolnego serwera, każdy będzie więc z nich tworzył pliki cache niezależnie. Poza tym usunięcie cache z jednego serwera nie spowoduje usunięcia plików z pozostałych serwerów. Można co prawda utworzyć jeden wspólny sieciowy filesystem na potrzeby ca-che wszystkich serwerów, ale rozwiązanie traci wtedy swoją prostotę. Innym rozwiąza-niem jest umieszczanie danych cache w bazie danych. Jest to elastyczniejsze od plików ca-che w systemie operacyjnym – głównie kosz-tem dodatkowego obciążenia zasobów serwe-

MemcachedSystem cache przechowujący dane w pamięci RAM, umożliwiający zapisywanie danych i obiek-tów. Wysoce wydajny i skalowalny umożliwia łączenie serwerów działających w oparciu o różne architektury systemowe. Stosowany m.in. w serwisach LiveJournal i Wikipedia. System memca-ched jest dostępny w repozytoriach wielu dystrybucji Linuksa (kod źródłowy można pobrać z ad-resu http://www.danga.com/memcached). Obsługę memcached w PHP zapewnia binarne rozsze-rzenie, dostępne na pecl.php.net.Krótki przegląd metod API:

• Memcache::add – dodaje element do serwera.• Memcache::addServer – dDodaje serwer memcached do listy wykorzystywanych serwerów.• Memcache::close – zamyka połączenie.• Memcache::decrement – zmniejsza wartość elementu.• Memcache::delete – usuwa element z serwera.• Memcache::flush – usuwa wszystkie elementy z serwera.• Memcache::get – zwraca element z serwera.• Memcache::getExtendedStats – statystyki wszystkich serwerów memcached.• Memcache::getServerStatus – zwraca stan serwerów memcached.• Memcache::getStats – statystyki serwerów.• Memcache::getVersion – zwraca wersję serwera memcached.• Memcache::increment – inkrementuje wartość elementu.• Memcache::pconnect – otwiera stałe połączenie.• Memcache::replace – zmienia wartość podanego elementu.• Memcache::set – zapisuje dane na serwerze.• Memcache::setCompressThreshold – włącza automatyczną kompresję dużych wartości.• Memcache::setServerParams – zmienia parametry i stan serwera.

Listing 4. Przykład wyłączenia buforowania fragmentu kodu widoku

<h1> Ostatnie 10 wiadomości! </h1>

<cake:nocache>

<ul>

<?php foreach($recentMessages as $message): ?>

<li>$message['title']</li>

<?endforeach;?>

</ul>

</cake:nocache>

Listing 5. Funkcja clearCache() usuwająca nieaktualne pliki cache

// Usuń wszystkie strony z cache, bazując na nazwie kontrolera

clearCache('controller');

// Usuń wszystkie strony z cache, bazując na nazwie kontrolera i nazwie akcji

clearCache('controller_action/');

// Usuń wszystkie strony z cache, bazując na nazwie kontrolera, nazwie akcji

// i parametrze

// Do funkcji clearCache() można przekazywać wiele parametrów

clearCache(array('controller_action_params','controller2_action_params));

Page 53: PHP Solutions 05 2007 PL

05/2007

Technika

52

CakePHP

www.phpsolmag.org 53

Listing 6. Struktury danych wykorzystane do przechowywania cache

mysql> desc dbcache;

mysql> desc dbcache;

+------------+------------------+------+-----+---------+------

----------+

| Field | Type | Null | Key | Default |

Extra |

+------------+------------------+------+-----+---------+------

----------+

| key | int(10) unsigned | NO | PRI | NULL |

|

| value | text | YES | | NULL |

|

| expires_at | datetime | YES | | NULL |

|

+------------+------------------+------+-----+---------+------

----------+

// przykładowy model danych obsługujący buforowanie stron

// w bazie danych klasa Cache

class MyCache extends AppModel

// jeżeli używamy PHP4, musimy zdefiniować zmienną

// zawierającą nazwę klasy

var $name = 'MyCache';

// korzystamy z bazy danych – tabeli o nazwie cache

var $useTable = ‘cache’;

// metoda wyszukująca wartość skojarzoną z kluczem

// i zwracająca dane jako zmienną PHP

function find($key)

{

$rc = $this->query("select * from cache where key =

'$key' and expires_at < NOW() limit 1");

return unserialize($rc['cache'][0]['value']);

}

// metoda przechowująca w bazie danych klucz razem

// z wartością

function store($key, $value, $expires=null)

{

// jutro

if (is_null($expires)) $expires = time()+(24 * 60 * 60));

$expires = date('Y-m-d H:i:s', $expires);

$value = serialize($value);

return $this->query("replace cache set value = '$value',

$expires_at = '$expires' where key = '$key');

}

// metoda usuwająca przechowywane dane na podstawie klucza

// wyszukiwania

function delete($key)

{

return $this->query("delete * from cache where key =

'$key'");

}

// metoda usuwająca z bazy danych wszystkie dane,

// których termin ważności minął

function purge()

{

return $this->query("delete * from cache where

expires_at < NOW()");

}

}

// przykładowy kontroler wykorzystujących cache

// klasa MessagesControler wyświetlająca wiadomości

// internetowego forum

class MessagesController extends AppController{

var $name = 'Messages';

var $helpers = array('Html','Time');

var $layout = 'default';

var $uses = array('Forum','Topic','Comment','Message',

'MyCache');

function view($id)

{

// pobierz informacje o temacie dyskusji i powiązaną

// z nim listę komentarzy jeżeli temat nie istnieje,

// ponownie wyświetl listę działów

$topic = $this->MyCache->find('topic'.$id);

if (empty($topic)) {

$topic = $this->Topic->find(array('Topic.id'=>

$id, 'Topic.topic_id'=>0));

if (! empty($topic)) {

// przechowuj przez tydzień

$this->MyCache->store('topic'.$id, $topic, time()

+ (7 * 24 * 60 * 60));

}

else

{

$this->redirect('/forums/index');

exit();

}

}

// przekaż zmienną $topic do widoku

$this->set('topic', $topic);

}

function remove($id)

{

// usuń temat dyskusji wraz ze wszystkimi wiadomościami

// usuń zbędne pliki z cache

if ($this->Topic->drop($id, true)) $this->

MyCache->delete('topic'.$id);

$this->redirect('/forums/index');

exit();

}

Page 54: PHP Solutions 05 2007 PL

05/2007

Technika

54

ra mechanizmami zapisu i odczytu danych (zobacz ramka QUERY-CACHE). Oczywi-ście istnieje możliwość skalowania takiego rozwiązania, jednak nie jest ono proste po-nieważ wymaga żmudnej konfiguracji (repli-kacja baz danych) i nie jest standardowo ob-sługiwane przez CakePHP 1.1.xx. Przykłado-wy model danych obsługujący przechowywa-nie cache w bazie danych został przedstawio-ny w Listingu 6.

Najbardziej elastycznym sposobem prze-chowywania danych jest mechanizm mem-cached przechowujący dane w pamięci RAM serwera (zobacz ramka). W miarę, jak dane będą zapełniały przydzieloną pamięć, system będzie automatycznie usuwał te, które były najrzadziej używane (można też usuwać da-ne za pomocą odpowiednich wywołań syste-mowych).

Memcached został także wyposażony w mechanizmy równoważące obciążenie, gdy wykorzystywanych jest kilka serwerów, któ-re można w prosty sposób przyłączać (lub

odłączać) bez konieczności przerywania dzia-łania serwisu (obsługa memcached zostanie włączona do CakePHP począwszy od wer-sji 1.2.xx).

Typowa sesja połączenia z memcached z po-ziomu PHP została przedstawiona na Listin-gu 7. W pierwszej kolejności następuje zdefi-niowanie puli dostępnych serwerów pracują-cych pod kontrolą memcached. Każdy z nich będzie wykorzystywany proporcjonalnie do wagi określonej podczas inicjalizacji połącze-nia i w razie awarii zastąpiony przez kolejny serwer z listy.

Każdy dostęp do danych poprzedzony jest testem, czy informacje są dostępne w cache. Tylko w przypadku negatywnym nastąpi ko-nieczność wykonania czasochłonnych obli-czeń, po których wynik zostanie zapisany w cache (kolejne zapytania będą więc korzysta-ły z bufora). Memcached sam zatroszczy się o usunięcie zbędnych (przestarzałych) danych z pamięci – w naszym przykładzie po 10 se-kundach.

Memcached ma jeszcze jedną zaletę – moż-na go użyć jako mechanizm przechowują-cy dane nie tylko na potrzeby buforowania całych stron (page cache). Możemy w cache umieszczać dowolne dane (wyniki obliczeń, dynamicznie zmieniające się relacje między danymi) i na ich podstawie generować stro-ny WWW.

Podobnie jak w przypadku page cache pro-blemem jest określenie, czy strony na których były prezentowane dane, które uległy mo-dyfikacji, znajdują się w cache. Memcached umożliwia przechowywanie wyników obli-czeń, więc nie możemy polegać na prostej za-leżności mówiącej, że w przypadku zmian w modelu danych należy usunąć cache związa-ny z widokiem prezentującym dane.Zamiast tego można posłużyć się sztuczką polegają-cą na powiązaniu przechowywanych danych przechowywanych w cache ze znacznikiem sygnalizującym zmiany. Wystarczy, że utwo-rzymy model danych, który będzie dostarczał informacje o stanie aplikacji, np. unikalny nu-mer, niezmienny tak długo, jak długo nie na-stąpiły zmiany w bazie danych lub innych ob-liczeniach.

Jeżeli tym numerem będziemy posługiwa-li się przy obsłudze cache, to jego zmiana wy-musi ponowne przeliczenie zmienionych da-nych. Jeżeli przestrzeń przeznaczona na bu-for będzie się zmniejszać, memcached auto-matycznie usunie dane, które przez określo-ny czas nie były aktywne.

Rozwiązanie takie sprawdzi się przede wszystkim przy skomplikowanych stronach WWW opartych o przechowywane w cache dane pochodzące z czasochłonnych obliczeń. Koszt zaangażowania zasobów serwera w wy-krycie zmian w stanie aplikacji będzie wów-czas zrównoważony przez oszczędności wy-nikające z braku potrzeby wykonywania cza-sochłonnej generacji strony.

PodsumowanieMechanizm page cache w CakePHP w połą-czeniu z umiejętnym wykorzystaniem cache przeglądarki internetowej może zredukować opóźnienia w dostarczaniu danych do użyt-kownika oraz zmniejszyć obciążenie serwera WWW i bazy danych. Warto zwrócić szcze-gólną uwagę na technologię memcached, któ-ra zapewnia szybki dostęp do danych zawar-tych w cache, jest elastyczna i skalowalna, dzięki czemu może „rosnąć” razem z naszym serwisem.

PIOTR GAPIŃSKIAutor w wolnych chwilach zajmuje się programo-waniem w różnych językach (głównie Rebol, Ruby, PHP i AmigaE).Kontakt z autorem: [email protected]

Listing 7. Wykorzystanie memcached z poziomu PHP

// utworzenie puli dostępnych serwerów memcached

// każdy z nich będzie wykorzystywany proporcjonalnie

// do listy wag przekazanych jako prametr do metody Mamcache::addServer()

$memcache = new Memcache;

$memcache->addServer('memcache_host1', 11211, 50);

$memcache->addServer('memcache_host2', 11211, 25);

$memcache->addServer('memcache_host3', 11211, 25);

$memcache = new Memcache;

$memcache->connect('localhost', 11211) or die ('Nie mogę się połączyć');

if ($get_result = $memcache->get('key'))

{

// jeżeli obiekt jest w cache, to skorzystaj z niego

echo '<b>Dane z serwera</b>:<br/>';

echo $get_result->str_attr.'<br />';

echo $get_result->int_attr;

}

else

{

// w cache nie ma żądanych danych, zapisz dane

$tmp_object = new stdClass;

$tmp_object->str_attr = 'test';

$tmp_object->int_attr = time();

$memcache->set('key', $tmp_object, false, 10) or die (

'Nie udało się zapisać elementu');

echo 'Zapisane dane zostaną usunięte po 10 sekundach<br/>';

echo 'Odśwież stronę, by zobaczyć dane zapisane na serwerze memcached';

}

Page 55: PHP Solutions 05 2007 PL
Page 56: PHP Solutions 05 2007 PL

05/2007

Narzędzia

56

SciTE

www.phpsolmag.org 57

PHP jest bardzo popularnym językiem programowania, co przekłada się rów-nież na liczbę dostępnych dla niego na-

rzędzi programistycznych. Niestety, szeroka gama aplikacji tak naprawdę utrudnia wybór, ponieważ sama znajomość funkcji progra-mu nie wystarczy aby stwierdzić czy to ten „właściwy”. W praktyce edytor można uznać za sprawdzony dopiero po napisaniu kliku skryptów podczas codziennej pracy.

SciTE to prosty, szybki i konfigurowalny edy-tor tekstu wyposażony w wiele funkcji przydat-nych programistom przy tworzeniu kodu. Po-czątkowo był on tylko programem demonstru-jącym możliwości komponentu Scintilla (na którym bazuje), z czasem jednak rozrósł się tak, że obecnie konkuruje z wieloma narzędziami programistycznymi.

Edytor ten został wyposażony w wie-le funkcji ułatwiających programowanie. Trudno byłoby wymienić je wszystkie, dla-tego poniżej znajdują się tylko ważniejsze z nich:

• kolorowanie składni dla większości popu-larnych języków;

• numerowanie linii;• uzupełnianie składni języka;• podpowiedzi;• zwijanie kodu;• tworzenie zakładek w kodzie;

• możliwość podpięcia zewnętrznych pro-gramów (np. Debuggera);

• bufory pozwalające na pracę z kilkoma pli-kami jednocześnie;

• mechanizm zapamiętywania sesji;• dwupanelowa budowa (panel edycji i pa-

nel wyjścia).

SciTE może pracować zarówno w środowisku Windows, jak i pod kontrolą większości syste-mów unixowych (w tym Linuksa), co dla wie-

lu twórców będzie miłą wiadomością. Należy również wspomnieć że SciTE jest programem o otwartym kodzie źródłowym rozprowadza-nym za darmo.

Instalacja edytora nie powinna przysporzyć trudności. Binarne wersje zarówno dla systemu Windows jak i Linux można ściągnąć ze strony domowej programu http://scintilla.sourceforge.net/SciTEDownload.html

Pierwsze uruchomieniePo pierwszym uruchomieniu edytora można się trochę rozczarować prostotą jego interfej-su. W najprostszej konfiguracji SciTE przy-pomina program Notatnik z systemu Win-dows. Nie dajmy się jednak zwieźć pozorom. Przy odrobinie wytrwałości to z pozoru ubo-gie narzędzie, może zmienić się w funkcjonal-

SciTE

Wcześniej czy później każdy programista stanie przed koniecznością wyboru edytora, pozwalającego na sprawne tworzenie kodu. Odpowiednie narzędzie to nie tylko wygoda, ale przede wszystkim oszczędność czasu.

Dowiesz się...• Jak wykorzystać zawansowane możliwości edy-

totora SciTE oraz jak skonfigurować go do pra-cy z językiem PHP.

Powinieneś wiedzieć...• Podstawowa znajomość PHP i narzędzi do two-

rzenia kodu.

Poziom trudności

„Lekki” edytor o dużych możliwościach

Rysunek 1. Okno edytora SciTE

Page 57: PHP Solutions 05 2007 PL

05/2007

Narzędzia

56

SciTE

www.phpsolmag.org 57

ny i wygodny edytor kodu tak jak to przedsta-wia Rysunek 1.

KonfiguracjaKonfiguracja edytora SciTE nie należy do naj-prostszych, ponieważ polega na edycji plików tekstowych, a to z kolei wymaga studiowania dokumentacji. Warto jednak poświęcić na te czynności trochę czasu, ponieważ efekt mo-że być imponujący. Podstawowa konfigu-racja SciTE zawiera się w pliku SciTEGlo-bal.properties znajdującym się przeważnie w katalogu instalacyjnym aplikacji. Aby go nie szukać możemy otworzyć plik wybiera-jąc z menu programu Options | Open Glo-bal Options File. Tutaj dokonujemy odpo-wiednich poprawek zgodnie z dokumentacją dostępną na stronie http://scintilla.sourceforge.net/SciTEDoc.html. SciTE oferuje również możliwość stworzenia własnej konfiguracji bez wprowadzania modyfikacji w pliku glo-balnym. Dokonujemy tego poprzez utworze-nie pliku użytkownika SciTEUser.properties wybierając z menu programu opcję Options | Open User Options File. Plik ten będzie pu-sty, tak więc najlepszym rozwiązaniem bę-dzie skopiowanie do niego ustawień global-nych a następnie wykonanie potrzebnych zmian. Wybrane opcje konfiguracyjne zosta-ły przedstawione na Listingu 1.

Dwa paneleEdytor SciTE umożliwia korzystanie z dwóch paneli: panelu edycji – w którym tworzony jest kod, oraz panelu wyjścia – gdzie można obser-wować komunikaty programów zewnętrznych oraz uruchamiać polecenia systemowe. Jest to bardzo przydatne gdy korzystamy z debugge-ra lub programu sprawdzającego poprawność składni. W dalszej części artykułu dowiemy się jak skonfigurować sprawdzanie składni dla języka PHP.

Obsługa języków programowaniaSciTE obsługuje większość popularnych języ-ków programowania w tym PHP, a każdy ob-sługiwany język posiada swój własny plik kon-figuracyjny w którym możemy modyfikować ustawienia takie jak kolorowanie składni czy listę funkcji oraz słów kluczowych. Pliki kon-figuracyjne poszczególnych języków znajdu-ją się w katalogu instalacyjnym edytora i po-siadają rozszerzenie .properties. Jeśli nie chce-my, bądź nie możemy modyfikować tych pli-

ków (np. nie posiadamy uprawnień zapisu) w katalogu instalacyjnym możemy utworzyć plik z konfiguracją języka w innej lokalizacji. Na przykład w systemach uniksowych może-my stworzyć w katalogu domowym folder o nazwie .SciTE i w nim umieszczać pliki z wła-snymi ustawieniami. Aby wczytać konfigura-cję dla danego języka z innej lokalizacji niż ka-talog instalacyjny należy skorzystać z opcji im-port podając pełną ścieżkę do wczytywanego pliku pomijając rozszerzenie .properties. Od-powiedni wpis (np. dla języka HTML) w pliku

konfiguracyjnym użytkownika mógłby wyglą-dać następująco:

import .SciTE/html

lub

import /home/nazwauzytkownika/.SciTE/html

W domyślnej instalacji SciTE nie posiada osobnego pliku konfiguracyjnego dla języka PHP. Oczywiście można go utworzyć, jednak

Listing 1. Wybrane opcje konfiguracyjne edytora

command.name.13.*=example

command.mode.13.*=subsystem:lua,

savebefore:no,groupundo

command.shortcut.13.*=Ctrl+E

command.13.*=example

Rysunek 2. Uzupełnianie składni

Rysunek 3. API PHP i podpowiedzi w edytorze SciTE

Page 58: PHP Solutions 05 2007 PL

05/2007

Narzędzia

58

lepszym rozwiązaniem będzie skorzystanie z pliku ustawień dla języka html, który posiada również odpowiednie instrukcje dla PHP.

Domyślnie nowo zainstalowany edytor bę-dzie kolorował tylko słowa kluczowe języ-ka PHP. Jeśli chcielibyśmy dodać pozostałe funkcje dostępne w PHP należy odpowiednio

zmodyfikować ustawienia dla opcji keywordc-lass.php dodając oddzielone spacją nazwy inte-resujących nas funkcji.

Podpowiedzi i autouzupełnianieBardzo ciekawym i przydatnym rozszerzeniem w omawianym edytorze jest możliwość korzy-

stania z podpowiedzi w postaci „dymków” oraz autouzupełniania w postaci listy pasujących słów. Udogodnienia te najlepiej ilustrują Ry-sunki 2 i 3.

Aby włączyć te funkcje należy zaopatrzyć się w plik .api danego języka. Można go stwo-rzyć ręcznie lub pobrać ze strony http://scintilla.sourceforge.net/SciTEExtras.html. Zakładając żeplik ten został umieszczony w podkatalogu.SciTE pod nazwą php.api wystarczy dodać na-stępujące linijki do pliku konfiguracyjnegojęzyka HTML:

api.$(file.patterns.php)=

$(SciteUserHome)/.SciTE/php.api

autocomplete.hypertext.start.characters=

_$(chars.alpha)

Sprawdzanie składniSprawdzanie poprawności kodu na bieżąco po-zwala uchronić się przed błędami składniowy-mi i dzięki temu znacznie skrócić pracę nad skryptem. W PHP realizujemy to poprzez wy-wołanie progamu – interpretera „php” z opcją – l nazwa pliku. SciTE może to robić za każ-dym razem gdy naciśniemy przycisk Compile. Wystarczy dodać poniższą linijkę do pliku kon-figuracyjnego języka HTML:

command.compile.$(file.patterns.php)=

php -l "$(FileNameExt)"

Przy poprawnej konfiguracji rezultat pole-cenia powinien wyświetlić się w panelu wyj-ścia za każdym razem gdy naciśniemy przy-cisk Compile.

Rozszerzenia w języku LUAJeżeli ktoś wciąż czuje niedosyt funkcjonal-ności edytora SciTE, mam wesołą wiadomość - aplikację tą możemy rozszerzać przy pomocy języka LUA. Daje to ogromne możliwości takie jak tworzenie makr i wyzwalaczy czy reakcji na określone zdarzenia. Tak więc należy utworzyć plik o dowolnej nazwie np. extensions.lua i po-informować SciTE o jego lokalizacji umieszcza-jąc w pliku konfiguracyjnym linijkę, która mo-że wyglądać w następująco:

extension.*=$(SciteUserHome)/extensions.lua

Przykładowy fragment pliku lua znajduje się na Listingu 2. Skrypt ten jest oparty na zda-rzeniu onChar edytora SciTE (fukcja wywo-ływana przy każdym wpisaniu znaku), a jego zadanie polega na dopisywaniu zamykających nawiasów, cudzusłowów etc. Oczywiście moż-liwości edytora można rozbudowywać przy użyciu bardziej rozbudowanych skryptów lua. Mamy na przykład możliwość tworzenia funkcji które zostaną wywołane z poziomu menu Tools aplikacji lub przy użyciu skrótu klawiaturowego. Wystarczy w pliku konfigu-racyjnym dodać linie – Listing 1.

Listing 2. Zawartość przykładowego pliku rozszerzeń – example.lua

# ustaw szerokość edytora na starcie na 800 pikseli

position.width=800

# ustaw wysokość edytora na starcie na 600 pikseli

position.height=600

# podziel okno na panele: 0 – w poziomie, 1 -w poziomie

split.vertical=0

# ustal szerokość dla panelu wyjścia na 100 pikseli

output.vertical.size=100

# ustal szerokość dla panelu wyjścia na 100 pikseli

output.vertical.size=100

# ustal wysokośćkość dla panelu wyjścia na 100 pikseli

output.horizontal.size=100

# pokaż pasek z buforami (otwarte pliki)

tabbar.visible=1

# pokaż pasek narzędziowy

toolbar.visible=1

# pokaż pasek statusu

statusbar.visible=1

# włącz numerowanie linii

line.margin.visible=1

# automatycznie zapisz pliki gdy okno traci aktywność

save.on.deactivate=1

# zapamiętaj sesję po zamknięciu programu

save.session=1

# włącz funkcję zwijania kodu

fold=1

# włącz kodowanie unicode

codepage=65001

LC_CTYPE=pl_PL.UTF-8

output.codepage=65001

Listing 2

function OnChar(c)

if c == '"' or c == '\'' then

editor:InsertText(editor.CurrentPos, c);

elseif c == '(' then

editor:InsertText(editor.CurrentPos, ')');

elseif c == '[' then

editor:InsertText(editor.CurrentPos, ']');

elseif c == '<' then editor:InsertText(editor.CurrentPos, '>');

elseif c == '?' and string.char(editor.CharAt[editor.CurrentPos - 2]) ==

'<' then editor:InsertText(editor.CurrentPos, 'php ?');

editor:GotoPos(editor.CurrentPos + 4);

elseif c == '{' then local indent = editor.LineIndentation[editor:

LineFromPosition(editor.CurrentPos)] / editor.TabWidth;

editor:InsertText(editor.CurrentPos, "\n\n" .. string.rep("\t",

indent + 1) .. "\n" .. string.rep("\t", indent) .. "}");

editor:GotoPos(editor.CurrentPos + (indent + 3));

end

return false;

end

Page 59: PHP Solutions 05 2007 PL

www.phpsolmag.org

Powyższy przykład spowoduje umieszczenie w menu Tools pozycji o nazwie example, która będzie odpowiedzialna za uruchomienie funk-cji example z pliku rozszerzeń lua.

Szczególnie przydatna może okazać się moż-liwość reakcji na zdarzenie onSave (podczas za-pisywania pliku). Tworząc odpowiedni skrypt możemy np. automatycznie wysyłać pliki na zdalny serwer poprzez protokół FTP używając zewnętrznych programów.

Pod adresem http://lua-users.org/wiki/SciteScripts można znaleźć kilka gotowych do użycia skryp-tów. Zachęcam również do zapoznania się z do-kumentacją tworzenia rozszerzeń z użyciem LUA pod adresem http://scintilla.sourceforge.net/SciTELua.html.

SciTE w ojczystym językuWielu z pewnością zadowoli fakt możliwo-ści spolszczenia interfejsu edytora SciTE. Wy-starczy ze strony http://scintilla.sourceforge.net/SciTETranslation.html pobrać odpowiedni plik, zmienić jego nazwę na locale.properties i skopio-wać do katalogu instalacyjnego.

PodsumowanieJak widać edytor SciTE jest bardzo zaawan-sowanym narzędziem w swojej klasie. Choć niektórym programistom będzie tu brakować funkcji takich jak zarządzanie projektami czy klient FTP. Można te braki oczywiście obejść stosując dodatkowe oprogramowanie, bądź pisząc rozszerzenia w języku LUA.

Niewątpliwie największą zaletą tej aplikacji jest konfigurowalność, pozwalająca na dosto-sowanie jej do własnych potrzeb programisty i chyba właśnie to docenia większość użytkow-ników najbardziej. Należy również wspomnieć o stabilności programu i niskim zapotrzebowa-niu na zasoby systemowe.

Poza stroną domową http://scintilla.sour-ceforge.net/SciTE.html w sieci nie znajdzie-my zbyt wiele na temat opisywanego edy-tora – najprawdopodobniej początkujących użytkowników odstrasza konfiguracja tego narzędzia. Warto również zajrzeć na grupę dyskusyjną http://mailman.lyra.org/mailman/listinfo/scite-interest – można tu odnaleźć wie-le odpowiedzi na pytania nurtujące nie tylko początkujących użytkowników tego imponu-jącego narzędzia.

ROBERT ZAJDAAutor artykułu jest właścicielem firmy APISOFT zajmującej się bezpieczeństwem systemów in-formatycznych i wdrażaniem oprogramowania open-source oraz nauczycielem przedmiotów in-formatycznych w Zakładzie Doskonalenia Zawo-dowego w Radomiu.Kontakt z autorem: [email protected]

Page 60: PHP Solutions 05 2007 PL

05/2007

Narzędzia

60

jQuery

www.phpsolmag.org 61

Dodatkowo bardzo intuicyjne two-rzy grupy obiektów i kolekcje, dając możliwość iteracji po wybranych ele-

mentach grupy. Wyposażenie jQuery w możli-wość wykonywania zapytań asynchronicznych w połączeniu z łatwością manipulacji dowolny-mi obiektami lub grupami obiektów wewnątrz strony HTML daje silne i łatwe w użyciu narzę-dzie do tworzenia atrakcyjnych wizualnie stron w technologii AJAX.

Instalacja i konfiguracjaInstalacja jest bardzo prosta, a właściwie nie ma jej wcale. Wystarczy ściągnąć plik jqu-ery.js i włączyć go jako skrypt do własnej stro-ny. Od tego momentu można zacząć używać funkcjonalności, jaką daje nam ta bibliote-ka. Pobieramy plik jQuery.js z adresu: http://code.jquery.com/jquery-latest.js i umieszcza-my go w katalogu roboczym razem z plikiem tworzonej przez nas strony. Następnie włą-czamy w kod strony zawartość ściągniętego pliku, deklarując w części nagłówkowej na-szej strony:

<script src="jquery-latest.js" type="text/

javascript"></script>

Tworzymy kod powodujący, iż po kliknięciu na dowolny link w obrębie strony HTML po-

jawi się komunikat (alert) „Hello Word”.Ta konstrukcja odpowiada klasycznemu kodo-wi HTML i JavaScript

<a href="" onclick="alert('Hello

world')">Link</a>

z tą różnicą, że jeden wpis odnosi się automa-tycznie do wszystkich linków na tworzonej przez nas stronie.

Selektory HTML i CSSPełne wsparcie dla CSS (1, 2, 3) oraz wszystkich selektorów HTML stanowi podstawę dobrego imienia omawianej biblioteki. Możliwość defini-cji kolekcji selektorów i iteracji po dowolnej z nich jest dużym ułatwieniem w tworzeniu logiki apli-kacji webowej po stronie klienta. Przypuśćmy że chcemy odwołać się do elementu o ID „ordere-dlist”. W klasycznym skrypcie JavaScript musie-libyśmy użyć konstrukcji: document.getElementById("orderedlist") by wydobyć interesujący nas element. Przy wsparciu jQuery mamy do dys-pozycji konstrukcję:

$("#orderedlist").addClass("red");

Przy czym możemy na wybranym obiekcie wywołać dowolne funkcje dostarczane z oma-wianą biblioteką, w tym konkretnym przykła-dzie została wywołana funkcja

addClass("red");

Funkcja ta, jak łatwo się domyślić, doda klasę „red” do elementu o ID „orderedlist”.

Można się pokusić o stwierdzenie, że nie wydaje się to wielkim ułatwieniem, wszak JavaScript sam w sobie daje możliwość wy-selekcjonowania wybranego elementu, ale dla konstrukcji, która ma za zadanie wybrać wszystkie elementy listy <li> dla obiektu o wybranym ID, kod JavaScript będzie wy-magał odrobiny programowania, a w przy-padku jQuery wystarczy zastosować wy-starczy zastosować konstrukcję prezento-waną w listingu 8. Innym ciekawym przy-kładem selekcji elementów strony HTMLmoże być ujęcie w kolekcję wszystkich od-nośników mających ustawiony parametr „name”. Kod w jQuery napisać można w na-stępującej postaci:

$("a[@name]").css("background", "#eee"

);

Łatwo się domyślić, że wykonanie tego frag-mentu kodu spowoduje zmianę koloru tła wyselekcjonowanych elementów. Bardziej użytecznym selektorem jednak wydać się może taki, który wybiera linki zawierają-ce w swym adresie wskazanie do np. kata-logu „galery”.

$("a[@href*=/gallery]").click(function() {

// do something with all links that

// point somewhere to /gallery

});

Można długo opisywać różne ciekawe kom-binacje metod selekcji elementów strony, za-mieszczone tutaj przykłady zostały wybra-ne, by pokazać spektrum możliwości, jakie na tym polu daje nam jQuery.

Walidacja formularzyCzęstym i bardzo eleganckim rozwiązaniem stosowanym na różnego rodzaju stronach jest walidacja formularzy. Nie trzeba chyba nikogo przekonywać, że duże formularze na

jQuery

Co to jest jQuery? Wikipedia podaje następującą definicję: lekka Biblioteka programistyczna dla języka JavaScript, ułatwiająca współdziałanie JavaScript oraz HTML. Ale to nie wszystko. Jest to biblioteka, która wszystkie obiekty strony HTML „ubiera” w dodatkowe zdarzenia, własności i metody.

Dowiesz się...• Artykuł pokazuje jak w łatwy i szybki sposób

dodać do strony zaawansowane efekty wizual-ne, a także możliwości wykonywania asynchro-nicznych zapytań http.

Powinieneś wiedzieć...• Wymagana jest dobra znajomość html, css,• Podstawowa znajomość JavaScript, php.

Poziom trudności

Pisz mniej, rób więcej

Page 61: PHP Solutions 05 2007 PL

05/2007

Narzędzia

60

jQuery

www.phpsolmag.org 61

witrynach pełnych elementów graficznych lepiej walidować bez kosztownego przełado-wywania całej strony, gdyż słabe łącze mo-że spowodować, że wypełniający formularz zirytuje się długim oczekiwaniem na odpo-wiedź serwera z informacją, że wpisana war-tość jest niewłaściwa. Zacznijmy od przykła-du, w którym będziemy walidować numer PESEL, a w przypadku wpisania niewłaści-wej wartości ostrzeżemy użytkownika, ale pozwolimy mu wypełniać dalej formularz i ostatecznie zaakceptujemy wadliwy nu-mer PESEL. Najpierw przygotujemy funk-cję, która dokona sprawdzenia, czy przeka-zana jako argument wartość jest poprawnym numerem PESEL.

Część funkcji zmieniona w komen-tarz umożliwia w szybki sposób rozszerze-nie funkcjonalność testu o sprawdzenie po-prawności podanej w innym polu formula-rza płci, w tym celu należy dodać kolejnyargument funkcji o nazwie „plec” (dozwolo-ne wartości to M lub K) i usunąć znaki ko-mentarza z odpowiedniego fragmentu kodu. Mając tak przygotowaną funkcję, możemy sprawić, by została wywołana przy wysłaniuformularza. Aby na nasze żądanie mimo błędnego wpisu dane zostały wysłane, zasto-sujemy jeszcze jedną funkcję pomocniczą. Teraz szybie powiązanie funkcji do zdarzenia wysłania formularza przy pomocy jQuery.

$('#frmDaneOsobowe').ajaxForm( {

beforeSubmit: validate } );

Czegóż wybredny programista chciał-by mieć więcej w tej sytuacji? Możliwość sprawdzenia, czy dany numer PESEL istnie-je w naszej lokalnej bazie danych? Wywoła-nie asynchronicznego zapytania get i uzy-skanie odpowiedzi od przygotowanego od-powiednio skryptu PHP wydaje się właści-we. W podobny sposób można sprawdzić adres e-mail i w czasie, gdy użytkownik wy-pełnia resztę formularza, możemy przesłaće-mail do przygotowanego skryptu PHP, który nie tylko sprawdzi poprawność adre-su pod względem budowy (ciąg znaków, li-terka at, kolejny ciąg znaków), lecz także odpyta serwery DNS o rekordy MX czy na-wet system pocztowy o poprawność adresue-mail. Aby jednak to zrobić, zapoznajmy się z funkcjonalnością zapytań asynchronicz-nych oferowaną przez jQuery.

GET I POST asynchronicznieTo, co czyni z omawianej biblioteki uniwersal-ną do różnych zastosowań, to możliwość wy-syłania asynchronicznych zapytań. Biblioteka ta ma gotowy zbiór funkcji wspierających za-pytania asynchroniczne i – co bardzo użytecz-ne – funkcje te jako jeden z parametrów przyj-mują tzw. callback, a dokładnie nazwę funk-cji, która ma być wywołana na zakończenie

działania danego zapytania. Pierwszą użytecz-ną funkcją, związaną z dziedziną „ajax”, jest funkcja get. Parametrami jej wywołania są: .get(url, params, callback), gdzie url to URL, params określa parametry przekazywa-ne w zwykłym URL-u za znakiem zapytania, u nas będą w postaci:

{ name: "PHPSolution", id: "3/2007" }

Ostatnim parametrem może być nazwa funk-cji, którą chcemy wywołać po wykonaniu za-pytania, może być to funkcja formatująca treść stronę w zależności od uzyskanej odpo-wiedzi. Dla przykładu podamy kod, który po

załadowaniu danych ze skryptu PHP, załaduje je do znacznika HTML o ID =”wynik” (<div id=”wynik”><div>)

W pliku test.php przetwarzamy zapyta-nie oraz przekazane parametry – Listing 11. Oczywiście, taki prosty przykład jest dobry, ale dla nas dobry to za mało, potrzebne nam są zapytania, które odpowiednio zareagują w przypadku niepowodzenia zapytania, a tak-że – co być może ważniejsze – zrealizują od-powiednie czynności po pełnym wykonaniu zapytania, czyli wtedy, kiedy „spłyną” już wszystkie dane od serwera. Jest to potrzeb-ne w przypadku przetwarzania przez serwer skomplikowanych operacji bazodanowych.

Listing 1. Funkcja sprawdzająca poprawność numeru PESEL

function validPESEL(pesel)

{

if(pesel == 0)return true;

if (pesel.length > 11) return false;

var rePesel = /(\d{11})/;

if (rePesel.test(pesel)) pesel = RegExp.$1;

else return false;

pesel = pesel.split("");

//if(plec)

//{

// if(pesel[9]%2 && plec.search("K")!=-1) return false;

// else if (!pesel[9]%2 && plec.search("M")) return false;

//}

var wagi = new Array(1,3,7,9,1,3,7,9,1,3);

var suma=0;

for(i=0;i<=9;i++) suma += pesel[i]*wagi[i];

suma %= 10;

var sumaKontrolna = (10-suma) % 10;

if (pesel[10]==sumaKontrolna) return true;

else return false;

}

Listing 2. Formularz HTML z logowaniem asynchronicznym

<form method="get" class="cmxform" id="form" action="form.php">

<fieldset>

<p>

<label for="login">Twój login</label>

<input id="user" name="user" title="Podaj login, przynajmniej trzy znaki"

class="{required:true,minLength:3}" />

</p>

<p>

<label for="pass">Hasło</label>

<input type="password" name="password" id="password" class="{

required:true,minLength:5}" />

</p>

<p>

<input class="submit" type="submit" value="Login"/>

</p>

</fieldset>

</form>

Page 62: PHP Solutions 05 2007 PL

05/2007

Narzędzia

62

jQuery

www.phpsolmag.org 63

Taką funkcją jest: $.ajax(params); Parame-try przekazywane tej funkcji wymagają do-kładniejszego omówienia.

Przede wszystkim forma przekazywanych parametrów jest w postaci par klucz: „war-tość”. Nie wszystkie parametry są obowiąz-kowe, a te, które nie będą ustawione, zostaną zastąpione wartościami domyślnymi. Pierw-szym niezbędnym parametrem jest URL. Na-zwa skryptu bądź strony, która zostanie wy-wołana, np. url: „test.php”, Kolejnym jest „type”, w naszym przypadku upewnimy się, że będzie to typ domyślny, czyli „GET”, type: „GET”, następnie czas na przesłanie danych do skryptu:

data: „name=PHPSolution&id=3/2007” § (znany jako $QUERY_STRING)

następnie funkcja, która zostanie wywoła-na, w przypadku wykonania skryptu z suk-cesem

success: callback_success,

Po ukończeniu zapytania, czyli wtedy kie-dy spłynęły już wszystkie dane i serwer za-mknął połączenie, zostanie wywołana in-na funkcja:

complete: callback_complete,

całość będzie wyglądała zgodnie z listin-giem. Wartymi zauważenia funkcjami z dzia-łu „ajax” są:

• $.ajaxStart(callback); • $.ajaxStop(callback);• $.ajaxSend(callback); • $.ajaxSuccess(callback);• $.ajaxComplete(callback).

Wszystkie wymienione tu funkcje związu-ją wybraną przez nas funkcję oznaczoną ja-ko „callback” z odpowiednim zdarzeniem. IAjaxStart spowoduje, że będzie wykonywa-na wybrana przez nas funkcja zawsze, kiedy bę-dzie zaczynało wykonywać się jakieś zapytanie „ajax”. AjaxStop spowoduje, że wykona się od-powiednia funkcja w chwili zakończenia zapyta-nia typu „ajax”. Funkcja ajaxSend wykona przy-pisaną funkcję przed każdym wykonaniem za-pytania „ajax”, ajaxSuccess odpowiednio po każdym zapytaniu zakończonym sukcesem, a ajaxComplete będzie wywoływał funkcję za-wsze, kiedy zakończą napływać dane z serwera.

Ponadto funkcja $.ajax wspiera zapyta-nia, które jako odpowiedź przesyłają dane w następującej postaci: XML, HTML, script, JSON. Kiedy już poznaliśmy funkcję odpo-wiedzialną za zapytania asynchroniczne, mo-żemy napisać kod aplikacji webowej, realizu-jącej zaawansowaną walidację formularza. Ni-kogo nie trzeba przekonywać, że korzystanie

Listing 3. Wywołania funkcji dokonujących walidacji

<script type="text/javascript">

jQuery(function() {

// show loading indicator

var loader = jQuery('<div id="loader"><img src="images/loading.gif"

alt="Czekaj..." /></div>')

.css({position: "relative", top: "1em", left: "25em"})

.hide()

.appendTo("body");

jQuery().ajaxStart(function() {

loader.show();

}).ajaxStop(function() {

loader.hide();

});

jQuery().ajaxError(function(a, b, e) {

throw e;

});

jQuery.validator.setDefaults({

debug: true

});

var v = jQuery("#form").validate({

submitHandler: function(form) {

jQuery(form).ajaxSubmit({

dataType: "json",

after: function(result) {

if(result.status) {

v.showErrors(result.data);

v.focusInvalid();

}

}

});

}

});

jQuery("#reset").click(function() {

v.resetForm();

});

});

</script>

Listing 4. Przykładowy plik php symulujący logowanie użytkownika

<?php // oczekiwanie, w celu sumulacji opóźnień w przetwarzaniu I przesyłaniu

// danych

sleep(1);

$user = $_REQUEST['user'];

$pw = $_REQUEST['password'];

if($user && $pw && $pw == "pass")

echo "{'status': 0, 'data':'Cześć $user, Witamy ponownie.'}";

else

echo "{'status': 1, 'data': {'password':

'Przykro mi, Twoje hasło jest błędne.'}} ;

?>

Page 63: PHP Solutions 05 2007 PL

05/2007

Narzędzia

62

jQuery

www.phpsolmag.org 63

z gotowych funkcji i tego, co już zostało napi-sane, znacznie przyśpiesza tworzenie własnej aplikacji. Z tego właśnie powodu sięgniemy po gotowe funkcje przygotowane do walida-cji formularzy, a dostarczone jako plugin jQu-ery o nazwie: validation. Pobieramy plik z ad-resu: http://jquery.bassistance.de/validate/jqu-ery.validate.zip Po rozpakowaniu włączamy w kod naszej strony następujące pliki:

W katalogu “js” umieszczamy następujące pli-ki pomocnicze: cmxforms.js, jquery.js, form.js, test.js, testrunner.js. Zaczniemy od przygotowa-nia formularza logowania, który wyślemy asyn-

chronicznie do przetestowania poprawności da-nych. Kod formularza zawarty jest w Listingu 2.

Kodem zawarty w Listingu 3. oprogramuje-my elementy formularza, wykorzystując funk-cje dostarczane przez jQuery.

Współpraca jQuery zPHPUmieszczamy w kodzie HTML potrzebne ele-menty, takie jak:

<button id="reset">Programowy reset formy

</button>

<div id="result">Oczekiwany rezultat</div>

Akcją wykonywaną przy wysłaniu formula-rza jest asynchroniczne wywołanie skryptu PHP form.php, w którym możemy umieścić uwierzytelnienie użytkownika, i włączenie sesji, tak by dalsze wędrowanie po stronie odbywało się w spersonalizowany sposób. Dla potrzeb testów ograniczymy się do pro-stej symulacji prawdziwego logowania. Kod pliku form.php zawiera Listing 4.To, co wy-różnia jQuery od innych podobnych biblio-tek, to fakt, że wokół jQuery nabudowało się wiele ciekawych i darmowych bibliotek, które rozszerzają i wzbogacają jego funkcjo-nalność. Jednym z takich przykładów jest jQPie, który:

• w prosty sposób wykonuje i przetwarza dane ze skryptów PHP, używając metody $.getJSON,

• włącza wygenerowany przez PHP kod HTML przy pomocy funkcji $.(element).load,

• wywołuje funkcje PHP bezpośrednio ze strony przy pomocy funkcji $.jqpie,

• wywołuje funkcje jQuery z pozio-mu PHP w odpowiedzi na wywołania $.jqpie.

Innym bardzo dobrym pluginem, integrują-cym PHP z jQuery, jest „PQuery". Jest do kla-sa w PHP, która umożliwia korzystanie na po-ziomu skryptów php w funkcjonalności jQu-ery. Aby zainstalować i używać tego modułu, musimy włączyć plik

<script src="pq/js/jquery.js" type="text/

javascript"></script>

w kod strony a także w skrypcie php włączyć plik

include("pquery/pquery.php");

Listing 7. „Helo world” w jQuery

<script>

$(document).ready(function() {

$("a").click(function() {

alert("Hello world!");

});

});

</script>

Listing 8. Przykład iteracji po elementach strony

$("#orderedlist > li").

addClass("blue");

// Kolejnym ułatwieniem jest możliwość

// iteracji po wszystkich tak

// wyselekcjonowanych obiektach.

$("#orderedlist > li").

each(function(i) {

$(this).append( " Test " + i );

});

Listing 5. Strona z „jQuery callendar”

<HTML><HEAD>

<script type="text/javascript" src="http://code.jquery.com/jquery-latest.pack.js">

</script>

<style type="text/css">@import url(jquery-calendar.css);</style>

<script type="text/javascript" src="jquery-calendar.js"></script>

<script type="text/javascript">

$(document).ready(function () {

$('.calendarFocus').calendar();

popUpCal.prevText = '<< ';

popUpCal.nextText = '>> ';

popUpCal.minDate = new Date(2005, 1 - 1, 1);

popUpCal.maxDate = new Date(2008, 12 - 1, 31);

});

</script>

</HEAD>

<body>

<form>

<input type=”text” class="calendarFocus" id=”MyInputData” name=”testData1”>

</form>

<body>

Listing 6. Przykład efektu graficznego w jQuery

<HTML><HEAD>

<script type="text/javascript" src="http://code.jquery.com/jquery-latest.pack.js">

</script>

<script type="text/javascript">

$(document).ready(function () {

$("input").mousedown(function() {

//alert("");

$(this).animate({

opacity: 'hide'

}, "slow");});

//$("input").mouseout(function(){

// $(this).animate({

// opacity: 'show'

// }, "slow");;});

});

</script>

</HEAD>

<body>

<form>

<input type=”text” class="calendarFocus" id=”MyInputData” name=”testData1”>

</form>

<body>

Page 64: PHP Solutions 05 2007 PL

05/2007

Narzędzia

64

Po stworzeniu instancji klasy $pquery = new PQuery(); możemy używać własności jQuery w skrypcie php.

<?=$pquery->form_remote_tag( array

('url'=>'index.php?task=ajax',

'update'=>'#idtoupdate'));?>

Podaj tekst :

<input type="text" name="field" /><br />

<input type="submit" /> </form>

Wartość wpisana do pola tekstowego zostanie przekazana zapytaniem asynchronicznym do

sktyptu php, a jego odpowiedź zmodyfikuje-element http o id='idtoupdate'. ***

EfektySilną stroną biblioteki jQuery są efekty graficzne uzyskiwane za jej pomocą. Mimo że samo jądro dostarcza tylko podstawową i bardzo ubogą funk-cjonalność w tej dziedzinie, to jednak narosło bar-dzo wiele pluginów i dodatków, które możemy włączyć w nasz kod i cieszyć się graficznie i dy-namicznie zaawansowanym zachowaniem naszej strony. Przykładem efektownego wykorzystania możliwości omawianej biblioteki może być wy-godny sposób wstawiania daty w wymagane po-le tekstowe, za pomocą kalendarza jQuerycallen-dar. Aby zaimplementować tą funkcjonalność, należy pobrać i włączyć w kod HTML oprócz pli-ku jquery dodatkowo http://marcgrabanski.com/code/jquery-calendar/jquery-calendar.css oraz http://marcgrabanski.com/code/jquery-calendar/jquery-calendar.js Następnie na wybranym elemencie „in-put”, wyselekcjonowanym w jQuery, wywołuje-my metodę callendar(). W zdarzeniu:

$('.callendarFocus').calendar();

A w kodzie HTML umieszczamy formularz z

elementem

<input type=”text” class=”callendarFocus” § name=”testData”>

Do pełnego zadowolenia brakuje nam jeszcze konfiguracji kalendarza, tak by domyślna war-tość była tą, którą chcemy widzieć w danym miejscu, i format też był odpowiedni.

JQuery ma zaimplementowanych wiele róż-nych funkcji odpowiedzialnych za wizualne efekty i animacje elementów strony. Prostym przykładem może być tutaj stopniowe nada-wanie przezroczystości dowolnemu elemento-wi strony. Kod w Listingu 6. pokazuje, jak po ustawieniu kursora w polu tekstowym formu-larza sprawić, by pole to zniknęło.

PodsumowaniejQuery daje możliwość zarówno budowania szyb-kich i sprawnych efektów graficznych, jak i łatwe-go generowania zapytań asynchronicznych i mo-dyfikacji treści strony po stronie przeglądarki.

Przytoczone tu przykłady stanowią cząstkę możliwości jakie daje ta biblioteka. Celem te-go artykułu było jednak zapoznanie czytelni-ka z funkcjonowaniem i sposobem użycia tej bi-blioteki na własnej stronie i mamy nadzieję, że cel ten został osiągnięty. Mocną stroną bibliote-ki jest mnogość pluginów i szeroka rzesza pro-gramistów gotowa wesprzeć technicznie kiedy mamy jakiś problem, tak więc warto wypróbo-wać to co nam ta biblioteka oferuje.

DARIUSZ DUSZYŃSKIAutor jest programistą w toruńskiej firmie JADE Sp. z o.o.Kontakt z autorem: [email protected]

Listing 9. Funkcja pomocnicza do dolidacji numeru PESEL

function validate(data, jqForm, opts) {

var form = jqForm[0];

var pesel = form.pesel.value;

var vl= validPESEL(pesel);

if (!vl)

{

return confirm(„PESEL nie poprawny, czy mimo to wysłać dane?”)

}

else return true;

}

Listing 10. Zapytanie asynchroniczne get

$.get("test.php",

{ name: "PHPSolution", id: "3/2007" },

function(data){

$("#wynik").html(data);

}

);

Listing 11. Przykładowy plik php, generujący odpowiedź dla zapytania asynchronicznego

<?php

If(isset($_GET[‘name’] && $_GET[‘name’] === „PHPSolution” ) echo $_GET[‘name’].

” To dobry magazyn”;

If(isset($_GET[‘id’] && $_GET[‘id’] === „3/2007”) echo $_GET[‘id’].

” To ciekawy numer”;

?>

Listing 12. Przykładowe zapytanie ajax

$.ajax(

{

url: „test.php”,

data: „name=PHPSolution&id=3/2007”,

type: „GET”,

success: function(data){

$("#wynik").html(data);

},

complete: callback_complete,

});

Listing 13. Włączenie potrzebnych plików js dla validatora jQuery

<script src="js/jquery.js" type="text/javascript"></script>

<script src="js/cmxforms.js" type="text/javascript"></script>

<script src="jquery.metadata.js" type="text/javascript"></script>

<script src="jquery.validate.js" type="text/javascript"></script>

Page 65: PHP Solutions 05 2007 PL
Page 66: PHP Solutions 05 2007 PL

05/2007

Narzędzia

66

WordPress i punBB

www.phpsolmag.org 67

Wordpress to znany system CMS/Blog, a punBB to popularne i bar-dzo funkcjonalne forum dysku-

syjne. WordPress jest stosowany we wszyst-kich serwisach grupy jakilinux.org (jakili-nux.org, polishlinux.org, wolnakultura.info i inne), natomiast punBB jest wykorzystywa-ne jako forum dyskusyjne na jakilinux.org (w przyszłości także w innych serwisach). Ce-lem jest zintegrowanie systemu użytkowni-ków obu skryptów, tak by czynności jak lo-gowanie, rejestracja, wylogowanie, zmiana ha-sła i przypomnienie hasła wpływały na oba skrypty. Integracje tego typu możemy prze-prowadzać na kilka sposobów:

• zastąpić system użytkowników i upraw-nień jednego skryptu systemem z dru-giego;

• dokonać krzyżowej integracji (czynności jak logowanie wykonuje identyczną czyn-ność dla drugiego skryptu);

• dokonać jednostronnej integracji (jeden skrypt przy np. logowaniu loguje również do drugiego skryptu).

Pierwszy przypadek jest dość trudny do realizacji jeżeli system użytkowników i uprawnień jest rozbudowany. JPortal ma od-dzielonych zwykłych użytkowników od ad-minów, przez co łatwo dla tego skryptu do-konać integracji typu pierwszego (istnieją-

cy niegdyś jPortal Entropia zintegrowany z IPB). W naszym przypadku będzie to opcja druga lub trzecia. Celem jest integracja, któ-ra nie wprowadza dużych zmian w kodzie skryptów, tak by nie było problemów z in-stalacją rozszerzeń czy aktualizacją. PunBB oferuje swój mechanizm do integracji, lecz idealny nie jest i próba zastosowania go z WordPressem skończyłaby się kolizją kodu i błędnym działaniem CMS-a. Musimy więc niezależnym od punBB kodem zintegrować oba skrypty, modyfikując kod Wordpressa (jednostronna integracja) – Listing 1.

Integracja – wersja IDla tabeli użytkowników punBB dodajemy no-wą kolumnę „wp_id”, która przechowuje nu-mer ID danego użytkownika z tabeli użytkow-ników WordPressa. Kod SQL wygląda nastę-pująco:

ALTER TABLE `pun_users` ADD `wp_id`

INT UNSIGNED NOT NULL DEFAULT 0;

Oczywiście prefiks „pun _ ” do tabel punBB może być inny i w takim przypadku edytu-jemy nazwy tabel w zapytaniach z prezento-wanych w tym artykule fragmentów kodu. Zaprezentowane na listingu 1 zmiany skut-kują tym, że rejestracja, logowanie, wylogo-wanie, resetowanie hasła i zmiana hasła w WordPressie spowoduje dokładnie to samo w punBB. Integracja jest skończona, chociaż ma jedną wadę – wprowadziliśmy modyfi-kacje do kodu WordPressu, co utrudni jego aktualizację. Skrypt ten jednak posiada sys-tem wtyczek – „plugins”. API tego systemu jest na tyle rozbudowane, że możemy prze-

nieść kod integracji z kodu CMS-a do wtycz-ki. Zaprezentowana wtyczka pisana była z myślą o serwisie jakilinux.org – instalacji Wordpressa z różnymi wtyczkami i istnie-jącą pulą użytkowników. Wtyczka musia-ła uwzględniać istniejących użytkowników (po stronie Wordpressa i punBB), ingerencja w kod CMSa odpadała – znacznie utrudnia to aktualizację, a także fakt iż Wordpress haszuje hasła za pomocą MD5, a punBB za pomocą SHA1. Hasze haseł do synchroni-zowanej tabeli użytkowników punBB moż-na tworzyć jedynie przy logowaniu, gdy API wtyczek Wordpressa przekazuje do wtyczki hasło a nie jego hasz.

Integracja – wersja IIW katalogu WordPressu /wp-content/plugins/ stwórz katalog jl-punbb, a w nim plikjl-punbb.php o kodzie przedstawionym na Li-stingu 2.

Teraz w Panelu Admina WordPressuw zakładce Plugins włącz wtyczkę i goto-we. Dodatkowo nie wymaga ona edycjitabeli punBB.

Synchronizacja użytkownikówPowyższa wtyczka zajmuje się rejestracją,logowaniem, wylogowaniem i zmianą has-ła dla użytkowników, którzy zarejestrowa-li się w WP po jej zainstalowaniu. W punBB nie będziemy mieć dostępu do istniejące-go konta admina. Musimy zsynchronizo-wać tabelę użytkowników punBB z tabelą użytkowników WordPressu. Usuwamy ist-niejących użytkowników (poza „Gościem” DELETE FROM pun_users WHERE id > 1; Te-raz musimy pobrać wszystkich użytkowni-ków z tabeli Wordpressu i w pętli dodać je do tabeli punBB według zapytania z funk-cji wtyczki punbb_user_register. W tabe-li użytkowników punBB kolumna group_id określa przynależność do grupy. Dla admi-nistratora Wordpressu nadajemy wartość 1,

WordPress i punBB

Seria artykułów „Warsztat Programisty” ma na celu przedstawienie rozwiązań ciekawych i nietypowych problemów, o których nie piszą w podręcznikach. Dowiesz się np., jak zintegrować użytkowników dwóch niezależnych skryptów, jak stworzyć zaawansowany parser tagów i wiele więcej.

Dowiesz się...• Jak rozwiązywć różne programistyczne pro-

blemy.

Powinieneś wiedzieć...• Znajomość podstaw PHP i HTML.

Poziom trudności

Integrowanie

Page 67: PHP Solutions 05 2007 PL

05/2007

Narzędzia

66

WordPress i punBB

www.phpsolmag.org 67

Listing 1. Integracja skryptów

// Otwórz plik wp-login.php i znajdź:

switch ($action) {

case 'logout' :

// punbb wylogowanie

include '../punbb/config.php';

setcookie($cookie_name, NULL, time()-3600, '/', '', '0');

// Następnie znajdź

do_action('password_reset');

// Generate something random for a password...

// md5'ing current time with a rand salt

$new_pass = substr( md5( uniqid( microtime() ) ), 0, 7);

$wpdb->query("UPDATE $wpdb->users SET user_pass = MD5(

'$new_pass'), user_activation_key = '' WHERE user_login =

'$user->user_login'");

// Poniżej wstaw

/*

punBB – resetowanie hasła

*/

$wpdb->query("UPDATE pun_users SET password='".sha1(

$new_pass)."' WHERE username = ".$user->user_login."");

/* */

// Następnie znajdź:

if ( wp_login($user_login, $user_pass, $using_cookie) ) {

if ( !$using_cookie )

wp_setcookie($user_login, $user_pass, false, '', '',

$rememberme);

// Poniżej wstaw:

/*

punBB – logowanie usera

*/

$user = $wpdb->get_row("SELECT id FROM pun_users WHERE username

= '".mysql_real_escape_string($user_login)."' LIMIT 1");

include '../punbb/config.php';

setcookie($cookie_name, serialize(array($user->id, md5(

$cookie_seed.sha1($user_pass)))), time() + 31536000,

$cookie_path, $cookie_domain, $cookie_secure, true);

/* */

// Otwórz plik wp-includes/registration.php i znajdź:

// Are we updating or creating?

if ( !empty($ID) ) {

$ID = (int) $ID;

$update = true;

} else {

$update = false;

// Password is not hashed when creating new user.

// Poniżej wstaw:

/*

punBB – zachowujemy czyste hasło, punbb używa sha

*/

$user_pass2 = $user_pass;

/* */

// Następnie znajdź:

if ( $update ) {

$query = "UPDATE $wpdb->users SET user_pass=

'$user_pass', user_email='$user_email',

user_url='$user_url', user_nicename =

'$user_nicename', display_name = '$display_name'

WHERE ID = '$ID'";

$query = apply_filters('update_user_query', $query);

$wpdb->query( $query );

// Poniżej wstaw:

// punBB – aktualizacja emaila

$wpdb->query("UPDATE pun_users SET email='".$user_email."'

WHERE wp_id = ".$ID."");

/* */

// Następnie znajdź:

$query = apply_filters('create_user_query', $query);

$wpdb->query( $query );

$user_id = (int) $wpdb->insert_id;

Poniżej wstaw:

// punBB – rejestracja

$wpdb->query('INSERT INTO pun_users (username, group_id,

password, email, email_setting, save_pass, timezone,

language, style, registered, registration_ip, last_visit,

wp_id) VALUES(\''.$user_login.'\', 4, \''.sha1(

$user_pass2).'\', \''.$user_email.'\', 1, 1, 1 ,

\'Polish\', \'Oxygen\', '.time().', \''.strip_tags(

$_SERVER['REMOTE_ADDR']).'\', '.time().', '.

$user_id.')');

/* */

// Następnie znajdź:

// If password is changing, hash it now.

if ( ! empty($userdata['user_pass']) ) {

$plaintext_pass = $userdata['user_pass'];

$userdata['user_pass'] = md5($userdata['user_pass']);

// Poniżej wstaw:

// punBB – zmiana hasła

$wpdb->query("UPDATE pun_users SET password='".sha1(

$plaintext_pass)."' WHERE wp_id = ".$ID."");

/* */

Page 68: PHP Solutions 05 2007 PL

05/2007

Narzędzia

68

Listing 2. Prosta wtyczka zapewniająca integrację z punBB

<?php

/*

Plugin Name: punBB integrator

Plugin URI: http://www.rkblog.rk.edu.pl

Description: Allows Wordpress to manager punBB users

- login/logout/register/password change etc.

Version: 0.0.1

Author: Riklaunim

Author URI: http://www.rkblog.rk.edu.pl

*/

add_action ('profile_update', 'punbb_profile_update');

add_action ('wp_logout', 'punbb_wp_logout');

add_action ('wp_authenticate', 'punbb_wp_authenticate', 1, 2);

add_action ('user_register', 'punbb_user_register');

function punbb_profile_update($id) {

global $wpdb;

$wpuser = $wpdb->get_row("SELECT user_login, user_email

FROM ".$wpdb->users." WHERE ID = ".$id." LIMIT 1");

$wpdb->query("UPDATE pun_users SET email='".$wpuser->

user_email."' WHERE username = ".$wpuser->

user_login."");

}

function punbb_wp_logout() {

include '../punbb/config.php';

setcookie($cookie_name, NULL, time()-3600, '/', '', '0');

}

function punbb_wp_authenticate($user_login, $user_pass) {

global $wpdb;

$user = $wpdb->get_row("SELECT id, password FROM pun_users

WHERE username = '".mysql_real_escape_string(

$user_login)."' LIMIT 1");

$wpuser = $wpdb->get_row("SELECT user_pass FROM ".

$wpdb->users." WHERE user_login =

'".mysql_real_escape_string($user_login)."' LIMIT 1");

include '../punbb/config.php';

IF($user->password == 'BRAK' and md5($user_pass) ==

$wpuser->user_pass OR sha1($user_pass) !=

$user->password) {

$wpdb->query("UPDATE pun_users SET password=

'".sha1($user_pass)."' WHERE username =

'".mysql_real_escape_string($user_login)."'");

}

setcookie($cookie_name, serialize(array($user->id,

md5($cookie_seed.sha1($user_pass)))), time() +

31536000, $cookie_path, $cookie_domain,

$cookie_secure, true);

}

function punbb_user_register($id) {

global $wpdb;

$wpuser = $wpdb->get_row("SELECT * FROM ".$wpdb->users."

WHERE ID = ".$id." LIMIT 1");

$wpdb->query('INSERT INTO pun_users (username,

group_id, password, email, email_setting, save_pass,

timezone, language, style, registered,

registration_ip, last_visit) VALUES(\''.$wpuser->

user_login.'\', 4, \'BRAK\', \''.$wpuser->

user_email.'\', 1, 1, 1 , \'Polish\', \'Oxygen\

', '.time().', \''.strip_tags($_SERVER[

'REMOTE_ADDR']).'\', '.time().')');

}

?>

czyli prawa admina forum. A co jeśli inte-grujemy forum zawierające już wielu użyt-kowników i tematy/posty? Powyższy sposób synchronizacji spowoduje sporo zamieszania w przynależności wiadomości do określonego autora. Musimy zastosować nieco inne podej-ście. Nie usuwamy użytkowników. Ci, którzy mieli odmienne loginy, stracą dotychczasowe konta na punBB. Dodajemy do tabeli punBB tych użytkowników WP, których nie ma na forum. By pobrać takich użytkowników, wy-starczy zapytanie:

SELECT * FROM wp_users WHERE user_login

NOT IN (SELECT username

FROM pun_users)

Dodajemy według zapytania z funk-cji wtyczki punbb_user_register, a następ-nie nadajemy prawa admina na forum ad-minowi z WP. Cały ten bałagan migracji rozwiąże nasza wtyczka do Wordpressu.Możemy dodać kod, który zostanie wykona-ny przy jej aktywacji i zsynchronizuje tabe-

le. Do kodu wtyczki wystarczy dodać kodz Listingu 3.

Ostatni etap integracji to usunięcie odno-śników w punBB do logowania i rejestracji lub ustawienie przekierowań na odpowied-nie strony w WordPressie: Na początku plikuregister.php forum wstaw: header('Location: http://www.url/do/wordpress/wp-login.

php?action=register'); Dla pliku login.php na początku wstaw:

header('Location: http://www.url/do/

wordpress/wp-login.php');

W profilu musimy wyłączyć zmianę hasła– edytuj profile.php i znajdź:

if ($action == 'change_pass')

{

Poniżej dodaj:

header('Location: http://www.url/do/

wordpress/wp-admin/profile.php');

Nie tylko punBBZaprezentowane rozwiązanie nie ogranicza się do punBB. Taką samą metodę możemy zastosować do integrowania skryptów innego typu. W niektó-rych przypadkach, gdy skrypt udostępnia API do integracji zadanie będziemy mieć ułatwione. Dla przykładu dla forum Invision Power Board istnie-je projekt IPB SDK umożliwiający szeroką integra-cję a także wykorzystywanie komponentów forum. IPB SDK możemy pobrać ze strony http://www.ipbsdk.sourceforge.net. Dzięki tej klasie w prosty sposób otrzymamy dostęp do systemu logowania i użyt-kowników, właściwości forum takich jak BBco-de, ankiety, szukanie, posty, tematy i wiele wię-cej. By używać IPB SDK musimy mieć odpowied-nią wersję forum jak i klasy SDK – 1.6 dla IPB 2.1.* lub 1.5 dla IPB 2.0.*. Gdy już wszystko gotowe nale-ży ustawić ścieżkę do katalogu z forum IPB w pliku ipbsdk_conf.inc.php ( $root_path i $board_url). Teraz możemy korzystać z IPB SDK. Zaczynamy od stworzenia obiektu klasy:

require_once 'ipbsdk_class.inc.php';

$SDK =& new IPBSDK();

Page 69: PHP Solutions 05 2007 PL

www.phpsolmag.org

Listing 3. Kod wtyczki WP synchronizujący użytkowników

add_action('activate_jl-punbb/jl-punbb.php', 'punbb_sync_tables');

function punbb_sync_tables() {

global $wpdb;

// copy users from WP to punBB that doesn't have account on punBB

$q = $wpdb->get_results("SELECT * FROM wp2_users WHERE user_login NOT IN (

SELECT username FROM pun_users)");

foreach($q as $u)

{

IF($u->ID == 1)

{

$gid = 1;

} else {

$gid = 4;

}

$wpdb->query('INSERT INTO pun_users (username, group_id, password, email,

email_setting, save_pass, timezone, language, style, registered,

registration_ip, last_visit) VALUES(\''.$u->user_login.'\', '.$gid.',

\'BRAK\', \''.$u->user_email.'\', 1, 1, 1 , \'Polish\', \'Oxygen\',

'.time().', \''.strip_tags($_SERVER['REMOTE_ADDR']).'\', '.time().')');

}

// turn off emails for "dectivated" accounts on forum

$wpdb->query('UPDATE pun_users SET email_setting = 2 WHERE username NOT IN (

SELECT user_login FROM wp_users) AND id > 1');

}

PIOTR MALIŃSKIAutor jest studentem Politechniki Warszawskiej na kierunku technologia chemiczna, a także pro-gramistą PHP i Python.Kontakt z autorem: [email protected]

Po czym możemy dowolnie wykorzystywać API klasy w naszych skryptach. Oto prosty przykład:

if ($SDK->is_loggedin()) {

echo 'jesteś zalogowany';

} else {echo 'jesteś niezalogowany';}

By zalogować użytkownika wystarczy użyć metody login:

$sdk->login($LOGIN. $HASŁO);

Wylogowanie to metoda logout:

$sdk->logout();

Za stworzenie nowego użytkownika na forum odpowiada metoda create_account:

$sdk->create_account(LOGIN, HASŁO, EMAIL);

PodsumowanieIntegrowanie skryptów wymaga dość dobrej znajomości ich kodu oraz starannego rozplano-wania integracji. Niektóre skrypty, np. Word-Press, łatwo rozszerzać, dodając kod integra-cji jako wtyczkę. Inne, np. forum IPB, posia-dają odpowiednie narzędzia do integracji (IPB SDK). Mimo ułatwień integracja rozbudowa-nych skryptów jest prawdziwym wyzwaniem dla programisty, testującym jego inteligencję i zdolność wyszukiwania informacji.

WordPress i punBB

Page 70: PHP Solutions 05 2007 PL

Testy konsumenckie

70 05/2007

Statystyki zewnętrzne

71www.phpsolmag.org

eCoder to młoda firma tworząca rozbudowa-ne pod względem funkcjonalnym i techno-logicznym serwisy WWW. Stawiamy na mo-del usługi firma dla firmy z terenem działania obejmującym cała Polskę. Nasze usługi opiera-my na autorskim systemie CMS, który cieszy się już od ponad 2 lat dużym uznaniem wśród klientów.

Na początku tego roku podjęliśmy decyzję o zaimplementowaniu do naszego CMS-a ze-wnętrznego systemu statystyk. Ustaliliśmy czte-ry główne kryteria, jakie system taki musi speł-nić: obsługa statystyk w języku polskim, łatwość implementacji skryptów usługodawcy do nasze-go kodu, duża gama prezentowanych danych wynikowych statystyk oraz końcowa cena, ja-ką będzie musiał za usługę zapłacić nasz klient.Poszukiwania prowadziliśmy, przeczesując za-soby Internetu. Po kilku dniach pod uwagębraliśmy trzy firmy: 7point (wówczas jeszcze pod nazwą NeoStat), MyStat oraz stat24.

MyStat odrzuciliśmy jako system mało roz-wijany, z licznymi ograniczeniami i nieatrak-

cyjnym interfejsem. Funkcjonalność serwi-sów 7point i stat24 była bardzo podobna.Podobne zestawienia, analizy, funkcje raportu-jące – wszystko to uporządkowane w intuicyj-ne kategorie. Porównując wygląd obu syste-mów, odnieśliśmy jednak wrażenie, że inter-fejs stat24 – przez to, że został wykonany cał-kowicie w technologii Flash – jest mniej przej-rzysty i czytelny dla użytkownika. Z naszej analizy cen usług wynikało również jedno-znacznie, że znacznie tańsze rozwiązania ofe-ruje serwis 7point, jednak ze względu na do-syć dużą popularność stat24 postanowiliśmy wypróbować również i tę usługę.

Po rozpoczęciu prac implementujących wy-magane skrypty do naszego CMS-a okazało się, że system stat24 posiada pewne ograniczenie, a mianowicie ilość skryptów zliczających, czyli ilość monitorowanych stron serwisu, jest ogra-niczona w tańszych wersjach statystyk. Ogra-niczenia tego nie miały statystyki oferowane przez 7point nawet w wersji najtańszej. To osta-tecznie wpłynęło na naszą decyzję.

Dużym plusem dla 7point okazała się też możliwość zdalnego przekazywania nazw po-szczególnych stron monitorowanego serwisu do serwera kolekcjonującego dane o odwiedzi-nach. Osoba obsługująca dodawanie i edycjękolejnych stron z poziomu CMS-a nie musi zmieniać i definiować tych nazw po raz drugi w systemie statystyk. Wszystko odbywa się auto-matycznie w sposób niewidoczny zarówno dla redaktora strony, jak i dla internauty. Dzięki te-mu przeglądając statystyki redaktor widzi zna-jome nazwy, jakie występują na stronie, zamiast niewiele mówiących adresów internetowych. Minusem jest to, że funkcja ta nie jest dostępna w najtańszej wersji statystyk.

Kilka słów od strony użytkownika statystyk. Możemy wybrać dowolny przedział czasowy, w jakim dane będą nam prezentowane. Wszyst-ko jest posegregowane w jasno nazwane katego-rie i podkategorie, dzięki czemu łatwo odna-leźć potrzebne informacje. Dane są przejrzyścieprezentowane dzięki subtelnemu mieszaniu

technologii DHTML i Flash. Większość wykre-sów posiada ładne animacje, co nie koliduje jed-nak w żaden sposób z podstawowym przezna-czeniem, czyli prezentacją danych. Przydatnym w praktyce rozwiązaniem jest możliwość ekspor-tu oglądanych danych do formatu MS Excel. Jest też mechanizm generujący i wysyłający na poda-ny e-maila zdalne, okresowe raporty tworzonych według indywidualnych upodobań.

7point oferuje również ciekawy program partnerski, z którego aktywnie korzystamy. Dzięki niemu mamy możliwość nadzorowania przypisanych kont statystyk poprzez równie czytelny co w samych statystykach panel za-rządzający. Możemy rejestrować nowych klien-tów i rejestrować dla nich usługi. Korzysta-jąc z tej formy zakupu, mamy możliwość uzy-skania atrakcyjnych rabatów lub prowizji na usługi 7point. Bardzo dobrym rozwiązaniemz naszego punktu widzenia jest też możliwość wyboru, czy rozliczenie z klientem nastąpiw formie odsprzedaży usługi przez naszą fir-mę, czy też umowa sprzedaży zostanie za-warta bezpośrednio pomiędzy 7point a klien-tem końcowym. Niezależnie od wybranej for-my nam, jako uczestnikowi programu part-nerskiego, przysługuje bardzo atrakcyjny rabatalbo prowizja od sprzedaży.

Od początku współpracy zarejestrowaliśmy już kilka kont, z których korzystają nasi klien-ci. Jak do tej pory nie mieliśmy żadnych zgło-szeń świadczących o nieprawidłowym działa-niu czy jakichkolwiek błędach w serwisie 7po-int. Otrzymujemy natomiast sygnały od klien-tów o tym, że wreszcie doczekali się systemu statystyk z prawdziwego zdarzenia.

Osobiście jestem zdania, że wybór 7point był bardzo dobrą decyzją. Jestem zadowolony z do-tychczasowej współpracy i polecam ich usłu-gi firmom, które zastanawiają się nad profesjo-nalnym systemem statystyk dla własnej strony WWW czy też jako moduł/dodatek do autor-skich rozwiązań IT.

Ocena: «««««

Artur Undro

eCoder.pl

Ocena: «««««

Testy konsumenckie

W testach konsumenckich poruszamy tematykę statystyk zewnętrznych. Ponizej prezentujemy wypowiedzi osób, które korzystają z usług firm świadczących takie rozwiązanie.

Statystyki zewnętrzne

Page 71: PHP Solutions 05 2007 PL

Testy konsumenckie

70 05/2007

Statystyki zewnętrzne

71www.phpsolmag.org

O statystykach 7point dowiedzieliśmy się z prze-słanej do nas propozycji współpracy. Przeanalizo-waliśmy wszystkie dostępne na rynku oferty i zde-cydowaliśmy się na korzystanie z tego właśnie pro-duktu. Przekonała nas przede wszystkim bogata funkcjonalność oraz intuicyjna nawigacja, której brakowało konkurencyjnym rozwiązaniom. Po-zytywne wrażenie zrobiła na nas również szybka i profesjonalna pomoc administratora, a także czy-telny, szczegółowy opis wszystkich dostępnych

w systemie funkcji oraz tabelaryczne zestawienie możliwych opcji dla każdego pakietu.

Z usługi 7point korzystamy od kilku miesię-cy i stale utwierdzamy się w przekonaniu, że był to najlepszy wybór. Opinię podzielają rów-nież nasi partnerzy, którym polecamy produkt i wdrażamy w realizowanych projektach.

Statystyki stosujemy głównie do oceny po-pularności serwisu oraz analizy skuteczności prowadzonych działań marketingowych. Wy-soka precyzja prezentacji wyników pomaga w określeniu zachowań i preferencji użytkowni-ków odwiedzających naszą stronę. Dzięki temu szybciej docieramy do ich potrzeb i pod tym ką-tem optymalizujemy witrynę, jej zawartość me-rytoryczną, ofertę, a także użyteczność.

Ze względu na to, że prowadzimy intensyw-ne kampanie promocyjne w wyszukiwarkach, najbardziej istotnymi danymi prezentowanymi w systemie są odsłony serwisu według słów klu-czowych, które użytkownicy wpisują w wyszu-kiwarki internetowe. Dają nam one pełen obraz w określeniu ich trafności.

Dużą zaletą jest również gromadzenie infor-macji na temat kolejności wyświetlania pod-stron serwisu, czyli ścieżek, po których wędro-wali internauci, a także szczegóły ich wizyt. Sta-tystyki 7point pokazują, w jaki sposób użyt-kownicy wchodzą na stronę (bezpośrednio, z wyszukiwarki, z katalogu czy innego adresu), ile czasu na niej spędzili, jakiego typu informa-

cji poszukiwali oraz skąd pochodzili. Zebrane w ten sposób dane pozwalają odpowiedzieć na pytanie, w jakim stopniu przygotowana przez nas oferta jest dobrana do ich potrzeb.

Wiedzę tę poszerzają wykresy wyświetlają-ce informacje o odbiciach, nowych i powraca-jących użytkownikach, stronach wejściowych i wyjściowych. Natomiast informacje technicz-ne, takie jak stosowane przeglądarki, ustawie-nia rozdzielczości ekranów, pomagają nam w definiowaniu standardów, wykorzystywa-nych przez internautów.

Dodatkowym atutem są tygodniowe rapor-ty przesyłane na podany adres e-mailowy, na pod-stawie których możemy analizować zmiany w za-chowaniach internautów i tworzyć archiwum.

Przyjemnym dla oka dodatkiem są wykresy 3D, a miły kontakt z pracownikami firmy, na których pomoc zawsze można liczyć, jest waż-nym uzupełnieniem całej usługi. Firma 7point otwarta jest na nowe pomysły i propozycje part-nerów, które są rozważane i wdrażane do syste-mu. Dzięki temu jest on stale rozwijany, oferu-jąc nowe funkcje przydatne użytkownikom.

Statystyki 7point posiadają dużo więcej opcji przydatnych właścicielom witryn, specjalistom ds. marketingu, programistom, dlatego powinny być nieodłącznym elementem każdej strony WWW, która ma przynosić korzyści, a nie tylko być.

Emilia Adamczyk właściciel, specjalista ds. e–biznesu

e–solution agencja interaktywna

Ocena: «««««

Statystyki internetowe. Niektórzy się zastana-wiają: czy ktoś z tego jeszcze korzysta? Oczy-wiście tak! Łatwo sobie wyobrazić firmę, któ-ra wydaje miesięcznie kilkaset złotych (czasami kilka tysięcy!) na pozycjonowanie swojej witry-ny w Google. Jak najprościej wykazać, że wyda-wane środki dają efekty, a zwiększająca się licz-ba telefonów w firmie to nie efekt dobrej ko-niunktury, ale cierpliwej i skutecznej pracy in-formatyka?

Nasza firma często spotyka się z tego typu problemami i poniżej podzielę się swoimi spo-strzeżeniami.

Na polskim rynku odnaleźć można jedynie kilka ciekawych rozwiązać spośród firm ofe-rujących profesjonalne statystyki WWW. Wy-bór jest więc mocno zawężony a co za tym idzie – łatwiejszy.

Problem zaczyna się w momencie, kiedy to nie sam użytkownik, ale jego klient oczeku-je jasnych, rzetelnych i dobrze zaprezentowa-nych informacji dotyczących jego strony inter-netowej (w przypadku sklepów temat jest jesz-cze poważniejszy).

Nasza firma tworzy m.in. serwisy i sklepy in-ternetowe, a zatem to, co w końcowym efekcie zobaczy nasz klient i jakie korzyści będzie czer-pał ze współpracy z nami, staje się sprawą najważ-niejszą. Po wpisaniu w wyszukiwarce hasła „sta-tystyki WWW” zwróciłem uwagę na kilka adre-sów. Wśród nich był link sponsorowany statystyk7point. Link sponsorowany w potocznej opinii oznacza „są drodzy, bo ich stać”. Sam nie wiem, dlaczego, ale tak jest. Początkowo poświęciłem im trochę czasu tylko dlatego, że firma znajduje się w moim mieście – Bielsku-Białej. Po analizie i omó-wieniu strony ze znajomymi doszedłem do wnio-sku, że warto. Kilka przykładów: 7point oferuje dosyć duży wybór parametrów (ma też dobrze wykonany podział na kategorie). W porównaniu z konkurencją cena za ich usługi jest atrakcyjna. Co ważne, system nie ogranicza ilości zbieranych da-nych, a do jednego konta można „podpiąć” kilka domen. Na początek wystarczy.

Oczywiście na rynku są inne firmy i warto o nich pamiętać. Wystarczy wspomnieć moc-no reklamowany stat24 oraz zapomniany już przeze mnie (kiedyś właściwie jedyny dostęp-ny, teraz wyglądający nieco archaicznie) MyStat.

Dzisiaj trzeba rozwijać swoją ofertę i pamiętać, że nie wszędzie Internet dostarczany jest z pręd-kością powyżej 1 Mbit/s. Nieatrakcyjny wygląd bądź ciągnące się w nieskończoność oczekiwanie na otwarcie strony na słabym EDGE-u (w Bielsku-Białej nie brakuje takich miejsc) odbiera chęć dal-szych poszukiwań.

Moja opinia przypomina tekst sponsorowany, ale od momentu rozpoczęcia współpracy z 7point nasi klienci nie zgłosili praktycznie żadnych uwag co do jakości oraz rzetelności przedstawianych danych. To komfort, który bardzo doceniam!

Dodatkwe plusy? Oto kilka z nich: usługa umożliwia określenie dowolnego zakresu czasu podczas odczytu, a prezentacja danych jest bar-dzo przejrzysta. Ponadto 7point oferuje szeroki wybór miesięcznych limitów odsłon (łatwo do-stosować do potrzeby konkretnej witryny). Wy-kresy też są nieźle wykonane.

Ostatnia sprawa to promocje – ważne w na-szym przypadku (firma proponuje upusty dla stałych klientów oraz program partnerski).

Żeby nie było zbyt słodko są i minusy. Przy-kład? Ostatnio firma zmieniła swoją nazwę i przebudowała główny układ witryny. Ponieważ istnieją od niedawna, takie zmiany wprowadza-ją zamieszanie. Dla stałych bywalców to norma, ale ciekawscy dyrektorzy marketingu lub preze-si firm nie przepadają za takimi zmianami.

Paweł Puzoń

Ocena: «««««

Page 72: PHP Solutions 05 2007 PL

Testy konsumenckie

72 05/2007

Statystyki zewnętrzne

73www.phpsolmag.org

W 2005 roku firma Lege Artis rozpoczęła prace nad nowym systemem statystyk internetowych. Zespół naszych programistów przez rok przygo-towywał mechanizmy nowego systemu. W mar-cu 2006 roku uruchomiony został system staty-styk internetowych pod domeną NeoStat.pl, któ-ry był zarazem projektem pilotażowym. Od maja tego roku możemy już zaoferować naszym klien-tom właściwą wersję statystyk – system 7point. Mamy nadzieję, że cały czas i praca, które zostały włożone w stworzenie systemu, przyniosą przede wszystkim satysfakcję klientom, a nam pewność, że dostarczamy w pełni profesjonalnych usług. Przy opracowywaniu systemu kierowaliśmy się kilkoma, istotnymi według nas względami – pro-stotą obsługi, z czym ściśle wiązała się również es-tetyka i klarowność prezentowanych widoków. Dołożyliśmy więc wszelkich starań, by interfejs był przyjazny i łatwy w odbiorze, a co za tym idzie, odczytanie danych nie stanowiło problemu nawet dla osób, które nie mają na co dzień do czy-nienia ze statystykami internetowymi.

Ponadto zależało nam, by klienci mogli korzy-stać ze statystyk za rozsądną cenę oraz by płacili tylko za to, czego naprawdę potrzebują. Dlatego w ramach naszego systemu klient może wybrać trzy rodzaje usług – Echo, Monitor lub Horizon. Każ-da z nich jest pomyślana w ten sposób, aby użyt-kownik mógł wybrać optymalną wersję dla po-trzeb i charakteru swojej witryny. Usługi zróżni-cowane są pod względem miesięcznego limitu od-słon, ilości prezentowanych parametrów oraz ce-ny. W ramach poszczególnych wersji użytkow-nik może utworzyć w swoim serwisie określo-

ną ilość subkont, aby móc udostępnić statystyki swojej witryny innym osobom. Może także two-rzyć określoną ilość niezależnych stref, by przeglą-dać statystyki stron swojego serwisu w prostszy i wygodniejszy sposób. Dodatkowo system 7point umożliwia monitorowanie większej liczby domen w ramach jednego konta. Warto również zwrócić uwagę na fakt, że nasz system liczy również aliasy/mirrory domen bez konieczności ich rejestrowa-nia (o ile zawartość ich stron nie różni się od za-wartości stron zarejestrowanej domeny głównej).

Kolejną, jedną z najistotniejszych dla nas kwestii, było stworzenie systemu, który dostar-czałby kompleksowych informacji o serwisie je-go użytkownikowi, tak aby w efekcie informa-cje te znajdowały swoje zastosowanie w prak-tycznych działaniach naszych klientów. Sta-raliśmy się, aby gama dostępnych danych by-ła jak największa. Dzięki dostarczanym przez nas statystykom klient może więc zbadać lojal-ność użytkowników wobec swojego serwisu, je-go jakość i atrakcyjność oraz sprawdzić jak jego serwis funkcjonuje. Ponadto klient ma dostęp do informacji na temat pozycji swojego serwisu w wyszukiwarkach oraz lokalizacji i zachowania użytkowników.

Dane, jakie liczy 7point, dotyczą wielu istot-nych dla właścicieli serwisów kwestii związa-nych ze statystykami. Są to między innymi szcze-gółowe informacje na temat użytkowników, wi-zyt i odsłon. Mogą być one przeglądane w róż-nych kontekstach: słów kluczowych, ilości i czę-stotliwości powrotów, odbić czy też stron starto-wych i końcowych wizyt. Kolejne kategorie, pre-zentowane przez system 7point, to niezbędne w planowaniu kampanii w sieci zestawienia słów kluczowych i wyszukiwarek. Ogromną wagę ma-ją również informacje dotyczące genezy ruchu na stronie – dzięki statystykom 7point użytkownik ma dostęp do przeglądania zestawień na temat wyszukiwarek, wejść ze stron, katalogów stron, poczty WWW, jak również zestawień dotyczą-cych geolokalizacji użytkowników. Niebagatelną

rolę odgrywają dane dotyczące ścieżek jakimi po-ruszają się po stronie użytkownicy – dzięki takim informacjom właściciel witryny może prześle-dzić zestawienie najbardziej typowych schema-tów poruszania się użytkowników po serwisie. Ponadto istotną funkcjonalnością jest możliwość wyboru dowolnego zakresu czasu prezentowa-nych statystyk – dzięki tej funkcji użytkownik może przeglądać dane z dowolnego dnia, miesią-ca lub roku. System 7point dostarcza również in-formacji technicznych o komputerze internau-ty (system, przeglądarka, rozdzielczość ekra-nu itp.). Dodatkowo warto wspomnieć o jednej z istotnych funkcji naszego serwisu jaką jest, cie-sząca się dużym zainteresowaniem wśród na-szych klientów, możliwość otrzymywania bez-płatnych tygodniowych raportów, które zawie-rają zestawienie najistotniejszych statystyk wraz z tendencjami.

Już od początku tworzenia systemu wiedzie-liśmy, że nasi klienci powinni być w pełni usa-tysfakcjonowani nie tylko oferowanym przez nas produktem, lecz także całością świadczo-nych przez nas usług. Dlatego mogą oni liczyć na pomoc techniczną zarówno na etapie insta-lacji statystyk, jak i w trakcie ich użytkowania. Poza tym staramy się być w stałym kontakcie z klientami , aby znać ich sugestie i opinie na te-mat użytkowania statystyk. Dzięki temu może-my na bieżąco rozwijać nasz produkt.

Mimo że system 7point jest nową propozy-cją na rynku statystyk internetowych, do dziś miało okazję korzystać z niego aż kilkaset ser-wisów. Już wersja pilotażowa odznaczała się szybkością oraz stabilnością działania. W efek-cie z naszych statystyk korzystają do dziś klien-ci zarejestrowani wkrótce po uruchomieniu wersji pilotażowej. Wśród monitorowanych przez nasz system serwisów można znaleźć za-równo te, których miesięczną liczbę odsłon li-czy się w milionach, jak i małe serwisy gene-rujące w miesiącu od kilku do kilkudziesięciu tysięcy odsłon.

wsparcie internautów w podejmowaniu decyzji zakupowych. Oprócz porównania cen użytkow-nicy Ceneo mają możliwość przeczytania opinii o produktach i sklepach pisanych przez samych internautów oraz recenzji tworzonych przez eks-pertów portali tematycznych. Najnowszą funk-cjonalnością są przewodniki zakupowe – specjalna sekcja pomagająca dobrać produkt do potrzeb przyszłego użytkownika.

Ceneo działa na rynku od ponad dwóch lat, współpracuje z niemal 500 sklepami han-dlującymi w przestrzeni wirtualnej, na stro-nach serwisu prezentowanych jest około 400 tysięcy unikalnych produktów, co daje w su-mie niemal 1,5 miliona porównywanych ofert sklepów.

Serwis jest odwiedzany przez 1,7 mln użyt-kowników miesięcznie, którzy generują kilka-naście milionów odsłon.

Ze stat24 współpracujemy od ponad dwóch lat. Kiedy rozpoczynaliśmy współpracę, stat24 był liderem na rynku, co było głównym powo-dem, dla którego zdecydowaliśmy się na usługi tej właśnie firmy. Poszukiwania serwisu zajmu-jącego się monitoringiem stron WWW prowa-dziły w prostej linii do stat24, który dość mocno zaznaczał swoją obecność w Sieci.

Jesteśmy bardzo zadowoleni z usługi stat24, badania oceniamy jako bardzo rzetelne. Tym niemniej pragnę podkreślić, że stat24 jest jed-nym z naszych strategicznych partnerów. Na-sza współpraca ma więc troszkę inny charakter niż w przypadku przeciętnego posiadacza wi-tryny internetowej.

W przyszłości, zarówno bliższej jak i dalszej, Ceneo nadal zamierza korzystać z usług stat24.Ceneo to platforma gromadząca informacje o pro-

duktach i sklepach działających w polskim Inter-necie. Celem porównywarki jest kompleksowe Ocena: ««««

Ustosunkowanie prezesa Rafał Wawak

Marcin Chwalik

ceneo

Page 73: PHP Solutions 05 2007 PL

Testy konsumenckie

72 05/2007

Statystyki zewnętrzne

73www.phpsolmag.org

Obecnie naszym partnerem w dziedzinie ofe-rowania statystyk zewnętrzych jest stat24. W zasadzie rozwiaząnie to wśród komercyj-nych nie ma odpowiednika na polskim rynku.

Współpraca była więc tak naprawdę pewnym naturalnym rozwiązaniem – innowacyjne rozwiązanie lidera polskiego rynku statystyk (Gemius) oraz usług webhostingowych (ho-me.pl). Usługę oferujemy w tej chwili w trzech wariantach: bezpłatnym dla wszystkich klien-tow oraz dwóch płatnych, stosowanych przy

zaawansowanych serwisach WWW. Sygnały, które otrzymujemy od naszych użytkowni-ków, są niezwykle pozytywne i pozwalają oce-nić nasza współpracę jako udane przedsięwzięcie.

Dlaczego zewnętrzne rozwiązanie statystyk? Przede wszystkim należy podkreślić, że nie zrezygnowaliśmy z oferowania własnego roz-wiązania. Bazuje ono jednak na logach serwera WWW, a zatem nie umożliwia np. określenia, czy dany użytkownik po raz pierwszy odwiedza nasz serwis, czy też powraca. Informacje udo-stępniane za pomocaą naszych statystyk i roz-wiązan stat24 są więc komplementarne. Z dru-giej strony – świadomość, że rozwojem systemu statystyk zewnętrznych zajmuje się wyspecja-lizowana firma, pozwala zakładać, że produkt ten będzie stale rozwijany, czyli uwzgledniającyaktualne trendy rynkowe czy aktualizujący in-formacje geolokalizacyjne. Uzyskanie takiej spe-cjalizacji po naszej stronie byłoby czaso– i kosz-tochłonne.

Model współpracy opierający się o koope-rację usługodawcy i wyspecjalizowanego pod-miotu jest w naszym odczuciu niezwykle efektywny, a doświadczenia innych rynków pozwalają stwierdzić, że także efektywny. W ten sposób dostawca usług internetowych jest w stanie zapewnić dodatkową funkcjo-nalność swoim klientom, a firma – dostaw-ca rozwiązania – otrzymuje dodatkowy kanał dystrybucji swoich produktówOcenę pozostawiamy naszym użytkownikom.

Krystian Sypuła

home.pl

R E K L A M A

Page 74: PHP Solutions 05 2007 PL

Testy konsumenckie

74 05/2007

Z usługi monitoringu serwisów internetowych, dostarczanej przez firmę stat24 Sp. z o.o., korzystamy już drugi rok. stat24 zapewniają monitoring większości witryn internetowych z portfolio MTV Networks Polska, tj. m.in. http://www.mtv.pl, http://www.vh1.pl i http://www.

comedycentral.pl. W przyszłości planujemy pod-piąć statystyki stat24 również pod nowe projek-ty internetowe MTV.

Usługi stat24 nie odbiegają standardem od li-dera na rynku, jakim jest Gemius. Są wiarygodne, rzetelne i spełniają standardy badawcze. Dużą za-letą jest także czytelny interfejs, pozwalający na ła-twe analizowanie wyników oglądalności również przez osoby nieposiadające dużego doświadcze-nia w pracy z Internetem. Łatwo administrować stat24, powielać skrypty. Dodatkowo stat24 ofe-rują bardzo dogodny system rozliczeń dla klienta.

Zdecydowanym minusem korzystania z us-ługi jest ograniczona liczba skryptów monitoru-jących, których liczba uzależniona jest od opłat cennikowych. Nawiązując jeszcze do strony ad-ministracyjnej stat24, w ostatnim okresie zmie-niła się opcja geolokalizacji. Obecny sposób pre-zentacji wyników jest mniej czytelny dla użyt-kownika. Również w sekcji Katalogi Odsyłające przydałaby się łatwiejsza nawigacja.

Jesteśmy ze stat24 prawie od początku istnienia firmy i z przyjemnością obserwuje-

my jej dynamiczny rozwój na polskim rynku. stat24 dowodzą, że nie tylko monopoliści na rynku są w stanie zaproponować usługi stoją-ce na najwyższym poziomie zarówno techno-logicznym, jak i konsumenckim. Jak wspomi-nałam, nowe produkty MTV Networks Pol-ska również korzystać będą z tych rozwiązań. Zapewniana przez stat24 możliwość pełnego, wiarygodnego monitoringu witryn MTV w cy-klach dziennych pozwala nam oferować inter-nautom jeszcze lepszy produkt, który nie tylko jest uzupełnieniem oferty antenowej kanałów z portfolio MTV Networks Polska , ale również – dzięki specjalnym konkursom, odsłuchom płyt, forom i sekcjom społecznościowym, po-zwala Internautom aktywnie uczestniczyć w „życiu” MTV.

Beata WęgrzynMenager ds. Internetu MTV Networks Polska

Sp. z o. o.

Ocena: ««««

stat24 tworzy grupa młodych ludzi, połączo-nych wspólną pasją, jaką jest Internet. Dziś, po 3 latach dynamicznego rozwoju, stat24 to uznana marka w zakresie statystyk dla małych i średnich serwisów internetowych. Monito-rujemy ponad 900 000 witryn, odnotowując miesięcznie ponad 3 mld odsłon, a dzięki in-tegracji z platformą technologiczną firmy Ge-mius SA – lidera w dziedzinie badań Interne-tu – nasze statystyki są rzetelne, wiarygodne i spełniają wymogi europejskich standardów badawczych.

Powstanie stat24 było decyzją Gemius SA i wiązało się z umożliwieniem oferowania ma-łym i średnim przedsiębiorstwom profesjonal-nego narzędzia badawczego, jakim miał być stat24. Główną misją stat24 jest poszerzanie za-sięgu badawczego, zarówno w Polsce, jak i w Eu-ropie Środkowo-Wschodniej.

stat24 świadczy bezpłatne oraz płatne usłu-gi monitorowania serwisów WWW. Są to pro-dukty o unikalnej jakości i użyteczności, któ-rych wykorzystanie przyczynia się do rozwo-ju działalności biznesowej w Internecie i słu-ży do zwiększania wartości posiadanych zaso-bów medialnych. Przestrzeń serwisów, które są monitorowane bezpłatnie, jest wykorzysty-wana do wykonywania badań ankietowych In-ternetu. Stat24 nigdy nie emitował reklam ko-mercyjnych.

Poza statystykami stat24 udostępnia każde-mu klientowi bezpłatny licznik internetowy oraz możliwość korzystania z rankingu. Dzię-ki rankingowi stat24 każdy właściciel staty-styk WWW może porównać oglądalność swo-jej witryny z innymi serwisami w danej katego-rii oraz klasyfikacji generalnej. Ponadto stat24 udostępnia raporty badawcze, które podsu-mowują miesięczną oglądalność monitorowa-nych serwisów WWW. Raporty tworzone są na podstawie danych zbieranych poprzez sta-tystyki stat24.

stat24 cały czas unowocześnia oferowaną usługę. Na początku 2007 roku została wpro-wadzona nowa geolokalizacja, która graficznie obrazuje oglądalność w poszczególnych kra-jach. Ponadto powstała szczegółowa mapa Pol-ski, która dzięki mechanizmowi wyszukiwaw-czemu pozwala na znalezienie użytkowników pochodzących nawet z niewielkich miejscowo-ści w kraju.

Aktualnie stat24 prowadzi prace nad nowym produktem, który zostanie wprowadzony na ry-nek w drugiej połowie 2007 roku.

stat24 jest w Polsce symbolem wiarygodno-ści, niezawodności i łatwego dostępu do przy-datnych informacji dotyczących witryny po-przez czytelny interfejs po korzystnej i konku-

rencyjnej cenie lub całkowicie bezpłatnie. Po-nadto największymi zaletami stat24 są spraw-dzona metodologia wykonywania badań oraz fakt, że dane udostępniane przez stat24 są w pełni akceptowalne przez agencje reklamo-we oraz uznawane za wiarygodne źródło wie-dzy o serwisie.

W pełni zgadzam się z opinią home.pl. Ofer-ta stat24 uzupełnia ofertę produktową firmy gwarantując jej i jej klientom dostęp do profesjo-nalnego narzędzia badawczego.

Współpraca z MTV układa się bardzo po-myślnie. Cieszy mnie, że MTV w przyszłości planuje objąć monitoringiem inne swoje wi-tryny. Ustosunkowując się do punktu, w któ-rym jest mowa o małej ilości skryptów udo-stępnianych za pośrednictwem stat24, chciała-bym zaznaczyć, że stat24 jest produktem stwo-rzonym z myślą o małych i średnich przedsię-biorstwach – stąd ograniczona ilość skryptów. Zgodnie z polityką firmy klientom, dla których udostępniana ilość skryptów zliczających jest niewystarczająca, proponujemy statystyki ge-miusTraffic.

Zgadzam się z wypowiedzią – jesteśmy part-nerem Ceneo, jednocześnie porównywarka ko-rzysta z naszego narzędzia badawczego. Cie-szy mnie, że Ceneo jest zadowolone z naszych usług.

4 gwiazdki to świetna ocena – mobilizu-je nas do pracy, wprowadzania na rynek no-wych rozwiązań oraz ulepszania dotychczaso-wej usługi.

Ustosunkowanie Dyrektor Zarządzający stat24Żaneta Tarnowska

Page 75: PHP Solutions 05 2007 PL

75

Recenzja

Dzisiaj, gdy zapotrzebowanie na strony WWW rośnie z dnia na dzień, coraz więcej osób chce zająć się ich tworzeniem. Jednak obecnie odchodzi się od prostych, statycznych witryn, standardem są strony dynamiczne.

Materiał składa się z 15 lekcji, które nie są oddzielnymi zagadnie-niami, a składają się na jeden duży projekt. I tak na wstępie poznamy charakterystykę fikcyjnej firmy turystycznej Newland Tours. Firma co prawda posiada już własną stronę WWW, jednak nie jest z niej zadowolona. Wynika to z tego że informacje na stronie są nieaktu-alne, a wszelkie zmiany na witrynie wymagają skorzystania z usług webmastera, co nie jest ani tanie, ani praktyczne. Dlatego Newland Tours zleca przebudowę strony na dynamiczną, z prostym CMSem za pomocą którego jej pracownicy będą mogli w prosty sposób uak-tualniać ofertę. I to jest zadanie dla Ciebie drogi Czytelniku. Dzięki takiemu podejściu, mamy możliwość zapoznać się z potencjalnymi problemami webmastera.

Przechodząc przez kolejne lekcje, nauczymy się: tworzyć formu-larze służące do wysyłania maili, zbudujemy kalkulator cen wycie-czek, pracować z bazą danych, budowania interfejsów wyszukiwa-nia, tworzyć prosty CMS do aktualizacji stron, ponadto instalować środowiska ASP, PHP oraz ColdFusion. Każdy rozdział w sposób charakterystyczny dla serii Oficjalny Podręcznik, poprzedzony jest informacją co zrobimy podczas danej lekcji, przewidywany czas na jej ukończenie, oraz jakie pliki potrzebne będą do jej wykonania.

Książka nie wyczerpuje całej problematyki a jest jedynie wstę-pem do tematyki związanej z tworzeniem dynamicznych stron WWW. Podsumowując ten podręcznik jest bardzo dobrą pozycją, który w przystępny sposób, opisuje przebudowę witryny. Małym niedopatrzeniem jest to, że na płycie CD pliki źródłowe są w wer-sji angielskiej, a książka opisuje polską wersję witryny. Jednak na szczęście, polskie pliki źródłowe są do pobrania na strony wydaw-nictwa Helion, pod adresem http://helion.pl/ksiazki/d8apof.htm.

Wydawnictwo: HelionAutor: Jeffrey BardzellStron: 472Cena: 69 złISBN: 83-246-0309-3

Książka Macromedia Flash 8 Professinal Oficialny Podręcznik to obowiązkowa lektura, przeznaczona szczególnie dla osób które za-wodowo zamierzają zajmować się projektowaniem i przygotowy-waniem materiałów na potrzeby Internetu oraz coraz dynamicz-niej rozwijającego się rynku aplikacji mobilnych.

Po podręcznik powinny sięgnąć osoby, posiadające przynajm-niej podstawową wiedzę na temat obsługi programu Flash 8 lub ze-tknęły się w swojej pracy z wcześniejszą wersją programu. Dzięki tej książce będą mogły rozwinąć swoje dotychczasowe umiejętno-ści, a tworzone projekty wzbogacić zarówno o elementy audio, wi-deo lub dynamicznie uaktualniać zawartość stron przygotowywa-nych we Flashu. Osobom rozpoczynającym swoją przygodę z Fla-shem polecam do lekturę książki „Macromedia Flash 8 Oficialny Podręcznik”, który wprowadza i wyjaśnia podstawowe zagadnienia związane z programem Flash 8.

Materiał podręcznika został podzielony na 13 ciekawych lekcji (materiały źródłowe są dołączone na CD). Materiał zostały tak do-

brany, że poziom trudności rośnie wraz z kolejnymi przerabianymi lekcjami. Przystępny język, wprowadzenia do lekcji, podsumowa-nia każdej lekcji, a także przybliżony czas potrzebny na wykonanie ćwiczenia sprawia że nauka jest przyjemna i wiemy czego się na-uczymy przerabiając kolejną lekcję.

Zostały wyjaśnione tutaj takie tematy jak: praca z tekstem, grafi-ką, wideo, podstawy Action Script 2.0 oraz ich praktyczne zastoso-wanie np. do prezentacji slajdów, tworzenia animacji, przygotowy-waniem aplikacji na urządzenia przenośne itp.

Na uwagę zasługuje to, że większość kolejnych lekcji, wyma-ga od czytelnika wykorzystania umiejętności zdobytych przy wcześniejszych ćwiczeniach. Bardzo przydatne są również wskazówki oraz komentarze, dotyczące standardów oraz za-sad projektowych przyjętych przez osoby zajmujące się zawodo-wo Flashem. Sprawia to, że wykonując kolejne ćwiczenia szyb-ko i coraz lepiej opanowujemy obsługę praktycznego wykorzy-stania Flasha.

Dreamweaver 8 z ASP, PHP i ColdFusion Oficjalny podręcznik

Wydawnictwo: HelionAutorzy: Tom Green, Jordan L. ChilcottStron: 480Cena: 59 złISBN: 83-246-0313-1

Macromedia Flash 8 Professional Oficjalny Podręcznik

Page 76: PHP Solutions 05 2007 PL

05/2007

Recenzja

76

Delphi for PHP

www.phpsolmag.org 77

W marcu 2007 roku na rynku poja-wiło się pierwsze w swoim rodza-ju narzędzie RAD dla PHP. W Pol-

sce było ono reklamowane na konferencji Code-Gear w Warszawie. Niestety na ten program po-święecono zaledwie dwadzieścia minut i w tym czasie natrafiono na kilka niespodziewanych problemów.Jak to zwykle bywa, pierwsze pre-zentacja pozostawiła duży niedosyt. Teraz nad-szedł czas na zaspokojenie tego głodu wiedzy.

Instalacja i pierwsze spostrzeżeniaPo włożeniu nośnika z oprogramowaniem ma-my wybór składników instalacji – Delphi for PHP oraz InterBase 2007. To dobre rozwiązanie, gdyż nie każdy jest zainteresowany InterBase'em, a jed-nocześnie ci, którzy chcą, mogą go sobie doinsta-lować i mieć w pełni sprawną aplikację opartą o profesjonalną bazę danych. Instalacja przebiega bezproblemowo, ale jak zdążyłem zauważyć, w razie próby reinstalacji programu pojawia się kło-pot. Z rejestru systemu nie są usuwane informa-cje o bibliotekach standardowych, przez co przy ponownej instalacji są pomijane przez instalator i program nie może działać właściwie. Warto więc pamiętać o czyszczeniu rejestru przed ponow-nym zainstalowaniem.

Po pomyślnej instalacji możemy uruchomić nasze oprogramowanie. Jako pierwszy zobaczy-my widok okienka na wzór borlandowskiego Buildera, od którego przecież wywodzi się fir-ma CodeGear (Rysunek 1.).

Po lewej stronie mamy okienka, w których możemy obserwować wykorzystywane elemen-ty umieszczone na formie oraz właściwości po-szczególnych elementów. Po prawej stronie na-tomiast znajduje się widok komponentów, baz danych oraz elementów do wykorzystania w aplikacji. W centralnym okienku natomiast możemy zobaczyć kilka podstawowych infor-macji o ostatnio edytowanych projektach, parę nowinek na temat produktu oraz kilka najczę-ściej wykorzystywanych funkcji programu, np. otwarcie nowego pliku.

Pierwsze próbyJak to zwykle bywa, zanim zaczniemy pracę z nowym oprogramowaniem, należy zapoznać się z dokumentacją. Tutaj natrafiamy na pierwszy zawód. Dokumentacja jest całkiem spora, ma-my opisane poszczególne klasy, metody i bi-blioteki, ale liczbę przykładów można policzyć na palcach obu rąk. A przecież, jak wiadomo, człowiek najlepiej uczy się na przykładach. Aby więc rozpocząć pracę z tym programem, warto jest znać chociaż ogólne zasady działania aplika-

cji tego typu, inaczej mówiąc, mieć już jakieś doświadczenie w pracy z innymi programami Borlanda. Po zapoznaniu się z głównym oknem możemy spróbować coś wykreować. Tworzymy nowy projekt i metodą „przeciągnij i upuść” prze-rzucamy kilka elementów z okienka z kompo-nentami na formę. Przykładowo umieszczone elementy widać na Rysunku 2.

Po umieszczeniu elementów możemy wreszcie spróbować coś wyświetlić. Wciska-

my przycisk Run i możemy już zaobserwować pewne rozbieżności. Strona wygląda tak jak w naszym programie, ale niektóre z wykorzy-stanych elementów, które działają pod IE, nie działają pod Firefoksem. Widać więc pewną stronniczość w przygotowywaniu tych kom-ponentów. Nie zadbano o właściwą współpra-cę z obecnie wykorzystywanymi przeglądar-kami. O ile elementy typu checkbox czy button działają poprawnie, o tyle np. pasek menu zo-

Delphi for PHP

Rysunek 1. Główne okno Delphi for PHP

Rysunek 2. Przykładowo dodane komponenty

Page 77: PHP Solutions 05 2007 PL

05/2007

Recenzja

76

Delphi for PHP

www.phpsolmag.org 77

baczymy tylko w IE. Jest to niedopuszczalne przy realizacji stron WWW, ponieważ straci-libyśmy możliwość nawigacji. Po dodaniu ele-mentów możemy je oprogramować, przecho-dząc do widoku kodu widocznego na Rysun-ku 3. Pierwsze, co dostrzeżmy w tym wido-ku, to liczba dołączanych plików. Jest ich cał-kiem sporo jak na stronę, która tak naprawdę nic jeszcze nie robi. Przechodząc dalej, mamy zadeklarowane zmienne reprezentujące doda-ne elementy. Aby dodać jakąś akcję, musimy w trybie Design wybrać element i np. kliknąć na niego dwa razy, co spowoduje wygenerowa-

nie kodu dla metody podwójnego kliknięcia,w której będziemy mogli oprogramować żąda-ne zadanie. Do poszczególnych elementów od-wołujemy się poprzez następujący schemat:

$this->nazwa_elementu;

Gdy chcemy zmienić lub ustawić jakąś war-tość dla elementu, po prostu rozszerzamy po-przedni kod do postaci:

$this->nazwa_elementu->

nazwa_parametru=wartość parametru;

Przykładowy projektFirma CodeGear udostępniła przykładowy pro-jekt e-commerce wykonany w Delphi for PHP oparty o bazę InterBase. Może pobrać go każdy, warunkiem jest tylko zarejestrowanie się na ich stronie. Po utworzeniu bazy oraz umieszczeniu plików strony na naszym serwerze Apache, któ-ry został automatycznie zainstalowany z Delphi for PHP, możemy zobaczyć pobraną aplikację. Jedyne słowa, które się nasuwają po zobaczeniu tej strony, to „żart” albo „porażka marketingo-wa”. Stronę pokazano na Rysunkach 4. i 5.

Strony są nader proste i zrobienie takich wi-tryn w PHP również nie powinno zająć du-żo czasu, choć tę stronę zapewne wykonano w znacznie krótszym czasie niż normalnie, ale nie wygląda ona interesująco i na pewno nie po-kazuje możliwości, którymi tak przecież chwa-li się CodeGear. Strona ma funkcjonalność ni-by–sklepu internetowego pozbawionego rze-czywistych funkcji. Pliki tej aplikacji mają obję-tość 1 MB (nie wliczając obrazków), co samo w sobie może zaszokować każdego. O ile wygląd łatwo zmienić, bo witryna wykorzystuje szablo-ny, o tyle zdecydowanie nie można pochwalić takiego rozmiaru strony WWW. Niestety tak duży rozmiar jest odczuwalny podczas korzy-stania z tej strony, jej wygenerowanie nawet na localhoście trwa parę chwil. Edycja projektu nie sprawia żadnych problemów, o ile znamy PHP, natomiast całkowite przebudowanie takiej stro-ny może okazać się dość skomplikowanym za-daniem.

PodsumowanieMożemy stwierdzić, że program Delphi for PHP jest na pewno ciekawym rozwiązaniem, któ-re w przyszłości może znaleźć swoje miejsce na rynku. Niestety, w tym momencie sprawia wrażenie oprogramowania robionego w pośpie-chu, a jak wiadomo nawet przemyślany projekt z braku czasu może wiele stracić na swojej funk-cjonalności. Poza tym brakuje źródeł, z których można czerpać wiedzę na temat tworzenia apli-kacji w tym programie – dokumentacja pozba-wiona przykładów, dwa krótkie filmy oraz nie-wielka strona to zdecydowanie za mało.

Rysunek 3. Widok kodu

Rysunek 4. Strona dla zwykłego użytkownika

Rysunek 5. Logowanie do panelu administratora

W sieci

• http://www.codegear.com/products/delphi/php – strona producenta, można z niej dowie-dzieć się paru rzeczy o produkcie oraz zobaczyć dwa filmy z przykładami wykorzystania pro-gramu.

• http://dn.codegear.com/article/36362 – strona producenta z przykładową stroną WWW.

ŁUKASZ SKOWROŃSKIAutor jest studentem informatyki na Uniwersyte-cie w Białymstoku oraz pracuje jako programista PHP w firmie E–Studio.Kontak z autorem: [email protected]

Page 78: PHP Solutions 05 2007 PL

Felieton

05/200778

Wszyscy zapewne wiemy, że na two-rzeniu witryn internetowych moż-na całkiem nieźle zarobić. Ale czy

kiedykolwiek zastanawialiście się, czy istnieją oso-by, które zrobią to dla nas „for free”? Surfując po Internecie, spotkałem wiele ogłoszeń „Zrobię stronęinternetową za...”, „Profesjonalna firma stworzy stro-nę internetową za...”. Dziwnie się poczułem... Oso-by, które chcą w jakiś sposób zaistnieć w cyber-świecie, muszą za to płacić? Nie mogą sami stwo-rzyć strony w jakimś darmowym oprogramowa-niu? Programów jest sporo, trzeba tylko umieć je znaleźć. Ale czy taka strona będzie chętnie ogląda-na? Wydaje się, że nie bardzo... Zazwyczaj nie wy-gląda ona najlepiej. A co z wyszukiwarkami? Sami wiecie, jak to wszystko jest zbudowane...

Wracając do tworzenia witryn, czy ktośbędzie się uczył HTML-a, PHP, JS, obsługi Photoshopa czy Gimpa tylko po to, aby stwo-rzyć sobie własną stronę, gdzie np. umieści gale-rię zdjęć? To byłoby zbyt pracochłonne. Ile cza-su musimy poświęcićna to, aby w jakimś stop-niu pojąć możliwości programów graficznych?

Ile straconych nerwów przy nauce programowa-nia? Sam zajmuje się tworzeniem witryn inter-netowych i wiem, co to wszystko znaczy.

W „interesie” siedzę już około 5 lat i do tej pory pamiętam początki. Spotkałem tylko ma-łą garstkę ludzi, którzy byli mi w stanie pomóc całkowicie za darmo. Reszta oferowała mi swo-ją pomoc za 50, 100, 200, a nawet 1000 zł.Skąd nastolatek miał wziąć choćby 100 zł? Dzięki właśnie tej grupie osób prowadzę jed-nak poważny portal internetowy... i za to im dziękuję. Tak naprawdę powyższy wstęp nie jest głównym tematem tego felietonu. Pragnę pokazać, że czasami to 500 zł jest mniej warte od satysfakcji za podziękowania od osób, któ-rym pomogliśmy. Być może dzięki naszej po-mocy ktoś zostanie sławnym programistą, bo go to wszystko zainteresuje. A może zostanie sławnym fotografem, bo stworzy prostą gale-rię zdjęć.

Przejdźmy dalej, dlaczego nie chcemy robić stron „for free”? Zapewne, jak wiemy, pieniądze szybko „uciekają”, więc musimy jakoś zarabiać,

a fakt, że informatyk to całkiem opłacalny za-wód, czerpiemy spore zarobki. Ale tak napraw-dę, dlaczego? Może to chciwość... jedna z gor-szych z ludzkich cech. Zazwyczaj informatycy nie mają czasu i chęci, aby zajmować się osoba-mi początkującymi. Idąc dalej, ludzi dziwi to, że zakładamy portale dla internautów, nie dla zarob-ku. Mówię w tej chwili, o osobach które siedzą w tym wszystkim „po uszy”. Moi przyjaciele twier-dzą, że nie opłaca mi się prowadzić serwisu, bo nie przynosi praktycznie żadnych zysków i pyta-ją, dlaczego to robią. Zgadnijcie, co odpowiadam...Dla satysfakcji, że ktoś może się dowiedziećtego, czego szukał. Daje mi to, można powiedzieć, ogromną satysfakcję, że ktoś, komu pomogłem, przysyła mi zwyczajne „dziękuję za pomoc” lub inne miłe słowa. Myślę, że czasami warto...

Witryny internetowe– tylko dla zarobku?

WOJCIECH SAPIECHOWSKIOd kilku lat zajmuje się tworzeniem witryn interneto-wych. Jest jednym z administratorów portalu WebHat.Kontakt z autorem: [email protected]

Page 79: PHP Solutions 05 2007 PL

v

Pren

umer

ata

PRO

Wię

cej i

nfor

mac

ji: patrycja.wadolowska@

software.com.pl

tel.:

022

887

-13-

45

v

Page 80: PHP Solutions 05 2007 PL

Zaprenumeruj swoje ulubione magazyny i zamów archiwalne numery!

Już teraz w kilka minut możesz zaprenumerować swoje ulubione pismo.Gwarantujemy:- preferencyjne ceny- bezpieczną płatność on-line- szybką realizację Twojego zamówienia Bezpieczna prenumerata on-line wszystkich tytułów Wydawnictwa Software!

www.buyitpress.com zamówienie prenumeraty

Page 81: PHP Solutions 05 2007 PL

Prosimy wypełnić czytelnie i przesłać faksem na numer: (22) 887 10 11 lub listownie na adres: Software-Wydawnictwo Sp. z o.o., Bokserska 1, 02-682 Warszawa, e-mail: [email protected]. Przyjmujemy też zamówienia telefoniczne: (22) 887 14 44

Imię i nazwisko............................................................................................ ID kontrahenta..........................................................................................

Nazwa firmy................................................................................................. Numer NIP firmy.......................................................................................

Dokładny adres....................................................................................................................................................................................................................

Telefon (wraz z numerem kierunkowym)................................................... Faks (wraz z numerem kierunkowym) ....................................................

E-mail (niezbędny do wysłania faktury)............................................................................................................................................................................

zamówienie prenumeraty

1 Cena prenumeraty rocznej dla osób prywatnych 2 Cena prenumeraty rocznej dla osób prenumerujących już Software Developer’s Journal lub Linux+3 Cena prenumeraty dwuletniej Aurox Linux

Jeżeli chcesz zapłacić kartą kredytową, wejdź na stronę naszego sklepu internetowego:

www.buyitpress.com

automatyczne przedłużenie prenumeraty

Suma

Tytuł Ilość numerów

Ilość zamawianych prenumerat

Od numeru pisma lub miesiąca

Opłata w zł

z VATSoftware Developer’s Journal (1 płyta CD)– dawniej Software 2.0Miesięcznik profesjonalnych programistów

12 250/1801

SDJ Extra (od 1 do 4 płyt CD lub DVD)– dawniej Software 2.0 Extra!Numery tematyczne dla programistów

6 150/1352

Linux+DVD (2 płyty DVD)Miesięcznik o systemie Linux 12 199/1791

Linux+Extra! (od 1 do 7 płyt CD lub DVD)Numery specjalne z najpopularniejszymi dystrybucjami Linuksa 8 232/1982

PHP Solutions (1 płyta CD)Dwumiesięcznik o zastosowaniach języka PHP 6 135

hakin9, jak się obronić (1 płyta CD)Miesięcznik o bezpieczeństwie i hakingu 12 1991/219

.psd (2 płyty CD)Dwumiesięcznik użytkowników programu Adobe Photoshop 6 140

.psd numery specjalne (.psd Extra + .psd Starter Kit) 6 140

Page 82: PHP Solutions 05 2007 PL

W sprzedaży od października

W następnym numerze PHP Solutions 6/2007 (23)

I wiele innych artykułów, których nie możesz przeoczyć!

ROZWIĄZANIA

n Forum internetowe z wykorzystaniem Vanilla alternatywa dla phpBB

Redakcja zastrzega sobie możliwość zmiany zawartości pisma.

UWAGA!! KOLEJNY TEST KONSUMENCKI

n UPS

NOWE ARTYKUŁY W DZIAŁACH

n dla początkujących

n dla zaawansowanych

n kasa dla webmastera

NARZĘDZIA

n PHP jako front-end dla systemu SAP

n Module i theme czyli stworzymy własny moduł do Drupala

Page 83: PHP Solutions 05 2007 PL
Page 84: PHP Solutions 05 2007 PL