PHP/Wersja do druku
Z Wikibooks, biblioteki wolnych podręczników.
Aktualna, edytowalna wersja tego podręcznika jest dostępna w Wikibooks, bibliotece wolnych podręczników pod adresem
http://pl.wikibooks.org/wiki/PHP
Całość tekstu jest objęta licencją GNU Free Documentation License.
Witamy w kursie PHP na Wikibooks. Podręcznik nie jest jeszcze ukończony, jeśli czujesz się na siłach - chętnie przyjmiemy twoją pomoc.
[edytuj] Spis treści
[edytuj] Wprowadzenie
[edytuj] Instalacja
[edytuj] Podstawy języka
- Pierwszy skrypt

- Zmienne i tablice

- Formularze

- Struktury kontrolne
- Funkcje

- Inne elementy składni

- Każdy popełnia błędy

- Korzystanie z dokumentacji

- Studium przypadku: Księga gości

[edytuj] Rozmaitości
- Przetwarzanie tekstu

- Podstawy wyrażeń regularnych

- Obsługa ciastek

- Sesje

- Wysyłanie e-maili

- Internacjonalizacja

- System plików

- Data i czas

[edytuj] Bazy danych
- Wstęp do baz danych
- Wstęp do programowania obiektowego

- Biblioteka PDO

- Jak to się robiło kiedyś?

- phpMyAdmin

- Studium przypadku: System newsów

- Bazy danych - co dalej?

[edytuj] Systemy szablonów
- Czym jest system szablonów?

- Smarty

- Open Power Template

- Sztuczki
- Studium przypadku: system newsów na Smarty
- Studium przypadku: system newsów na OPT
[edytuj] Programowanie obiektowe
- Klasy i obiekty

- Konstruktory i destruktory

- Dziedziczenie

- Interfejsy
- Wyjątki
- Iteratory
- Metody magiczne
- Studium przypadku: Hackowanie PDO
[edytuj] Dobra aplikacja
- Dlaczego nie piszemy ciurkiem?

- Wzorce projektowe
- Gdzie trzymać konfigurację?

- Przenośność
- Silnik
- Filtry
- Kontrola formularzy
- Autoryzacja i logowanie
- DAO
- MVC
[edytuj] Standard PHP Library
[edytuj] Bezpieczeństwo
[edytuj] Inne
- Konfiguracja PHP
- PHP w służbie systemu
- Edytory PHP

- Pomoc
- Autorzy

- Dla twórców podręcznika

- Wgrywanie plików na serwer
[edytuj] O podręczniku
Podręcznik ten nie jest "jeszcze jednym kursem PHP". To zbiorowe opracowanie, które nie tylko uczy, jak programować w języku PHP, lecz również pokazuje, jak robić to dobrze. Staramy się demonstrować najnowsze możliwości projektu, stawiając na aktualność. Podręcznik może być czytany przez wszystkich: zarówno przez osoby, które jeszcze nigdy nie miały do czynienia z programowaniem, jak i tych znających już jakieś języki.
PHP, choć na pierwszy rzut oka prosty do nauczenia, jest w rzeczywistości bardzo rozbudowanym projektem i zwyczajne wypisanie listy konstrukcji oraz funkcji nie wystarczy, aby powiedzieć o sobie "jestem doświadczonym programistą". Doświadczenie przychodzi z czasem, jednak w tym podręczniku staramy się to maksymalnie ułatwić, koncentrując się także na praktycznej stronie programowania. Pokazujemy, czego należy unikać, na co uważać, a w "Studiach przypadku" wyjaśniamy, jak zdobytą wiedzę wykorzystać do napisania skryptu realizującego konkretne zadanie.
Od czytelników tego podręcznika wymagamy pewnej znajomości języka HTML i orientowania się w tworzeniu stron WWW. Związane jest to z omawianym tu materiałem ukierunkowanym w głównej mierze na pisanie dynamicznych witryn internetowych. Jeżeli czujesz, że brakuje Ci wiedzy, zacznij swoją przygodę od poznania języka HTML. Znajomość programowania i algorytmów nie jest konieczna, aczkolwiek pozwoli na szybsze przyswajanie materiału.
Po przeczytaniu podręcznika będziesz wiedział:
- Co to jest PHP i jak działa?
- Jak postawić na własnym komputerze prosty serwer WWW.
- Jak pisać aplikacje w języku PHP
- Jak zarządzać bazami danych
- W jaki sposób napisać profesjonalny i łatwy w rozbudowie dynamiczny serwis WWW
- Jak poprawić bezpieczeństwo swych aplikacji i ustrzec się przed włamywaczami?
Podręcznik poprawiać i uzupełniać może każdy. Jeśli tylko masz czas i nie widzisz żadnych przeszkód w liberalnej licencji GNU FDL, pomóż w jego rozwoju: dopisz rozdział, zaktualizuj zawartość, popraw błędy (dalsze informacje). Każda pomoc jest ważna, gdyż tworzą to ludzie dla ludzi. Pamiętaj jednak, aby nie używać żadnych materiałów objętych prawem autorskim, a wszelkie większe edycje konsultować w dyskusji z innymi.
[edytuj] Czym jest PHP?
PHP to skryptowy język programowania - programy w nim napisane nie są kompilowane do postaci kodu maszynowego zrozumiałego dla procesora, lecz wykonywane przez specjalną aplikację zwaną interpreterem PHP. PHP został pierwotnie stworzony do wspomagania tworzenia dynamicznych stron WWW, lecz obecnie można w nim pisać także zwyczajne aplikacje dla systemu operacyjnego.
PHP jest projektem open-source. Każdy może pobrać za darmo jego kopię, zainstalować i używać bez żadnych ograniczeń. Do kodu źródłowego zapewniony jest pełny dostęp - jeżeli programista ma odpowiednie zdolności, może go modyfikować do woli oraz nadsyłać własne propozycje zmian do osób nadzorujących projekt. Dzięki takiej wolności PHP rozwija się bardzo dynamicznie, a w Internecie można znaleźć setki modyfikacji oraz dodatkowych modułów.
Mimo otwartości kodu, nad rozwojem oficjalnej wersji projektu czuwa firma Zend Company z Izraela założona przez twórców PHP. Zapewnia ona dodatkowe narzędzia i opiekę prawną, a także wyznacza kierunki rozwoju projektu.
Bardzo istotną cechą PHP jest skalowalność. Może go używać z powodzeniem zarówno początkujący programista, dzięki uproszczeniom składni, jak i ekspert, który znajdzie tutaj wszystkie zaawansowane narzędzia niezbędne do napisania rozbudowanej aplikacji. PHP znaleźć można wszędzie: od prywatnych stron domowych przez gry internetowe aż do potężnych witryn korporacji lub portali. Opanowanie języka jest proste także dlatego, iż Internet pełen jest przewodników oraz artykułów pokazujących, jak w praktyce wykorzystywać wiele z jego możliwości.
Kiedy zaczynaliśmy pisać podręcznik, pod sam koniec 2005 roku, najnowszą dostępną wersją PHP była 5.1.1. Obecnie (czerwiec 2009 roku) dostępna jest wersja 5.3.0. Jej możliwości są zbliżone do tych oferowanych przez konkurencyjne języki skryptowe dla stron WWW: ASP, JSP. Staramy się, aby podręcznik opisywał możliwie najnowszą wersję, jednak z powodu dużej ilości materiału niekiedy mogą pojawiać się fragmenty odnoszące się do starszych wydań. W podręczniku nie opisujemy PHP 4.x, praktycznie nierozwijanego, ale wciąż popularnego na serwerach oraz wśród starszych programistów.
PHP jest wykonywany po stronie serwera. Oznacza to, że PHP nie jest interpretowany (przetwarzany) przez program do oglądania Internetu, lecz przez specjalny program na serwerze! Co innego HTML, JavaScript w wersji wykonywanej po stronie klienta. Jak działa interpretator PHP? To proste - przerabia instrukcje PHP na HTML i JavaScript w wersji do interpretacji po stronie klienta, to trafia na Twój komputer i wiesz co się dzieje. A przy okazji - wiesz że Wikibooks i jej siostrzany projekt - Wikipedia są napisane w PHP?
[edytuj] Jak PHP współpracuje ze stroną WWW?
PHP jest językiem server-side, tj. pracuje po stronie serwera WWW. Przeciwieństwem są języki client-side pracujące po stronie przeglądarki użytkownika (np. JavaScript w wersji wykonywanej po stronie klienta). Aby wykorzystywać go na własnej stronie, musisz upewnić się, że twój serwer ma zainstalowaną jego obsługę. Zanim przejdziemy dalej, należy zrozumieć zasadę, na jakiej PHP generuje dynamiczne strony WWW.
Kiedy wpisujemy adres w przeglądarce internetowej, żądanie wyświetlenia strony kierowane jest do serwera HTTP zwanego także serwerem WWW. Jeśli stwierdzi na podstawie rozszerzenia pliku, że dany dokument zawiera kod PHP, serwer kieruje do jego interpretera żądanie przetworzenia podanego pliku. Interpreter wyszukuje w jego treści tzw. wstawki PHP wplecione w statyczny kod HTML i zastępuje je wynikiem ich wykonywania. Utworzony kod HTML jest zwracany serwerowi, a ten wysyła go z powrotem do internauty.
W tym procesie kod PHP nigdy nie opuszcza serwera. Internauta zawsze otrzyma wyłącznie utworzony przez PHP kod HTML. Oto przykład. Jeśli mamy plik PHP o następującej treści:
<html> <body> <?php echo 'Podaj hasło'; ?> </body> </html>
To internauta zobaczy jedynie dokument o takiej treści:
<html> <body> Podaj hasło </body> </html>
Cały PHP zniknie, a na jego miejscu pojawi się utworzony przez niego kod HTML.
Dzięki pracy po stronie serwera, PHP idealnie nadaje się do tworzenia złożonych aplikacji zarządzających dużymi ilościami danych: forami dyskusyjnymi, systemami zarządzania treścią, sklepami internetowymi. Generują one odpowiedni kod HTML dla przeglądarek internautów, a w momencie, kiedy oni go przeglądają, PHP już zakończył nad nim swą pracę. Jest to bardzo istotne, ponieważ wszelkie dalsze reakcje na poczynania użytkownika należy albo pozostawić przeglądarce, albo obsłużyć je za pomocą języka JavaScript.
W PHP stworzono m.in. aplikację MediaWiki, za pomocą której podręcznik ten jest oficjalnie dostępny w ramach projektu WikiBooks. PHP zarządza tutaj pobieraniem treści żądanej strony z bazy, sformatowaniem jej, a w przypadku kliknięcia na opcję "Edytuj" - dodaniem nowej wersji tekstu. JavaScript pracujący po stronie przeglądarki użytkownika ułatwia edycję tekstu, obsługując przyciski automatycznie wstawiające niektóre rodzaje formatowania oraz znaki narodowe. Jest to dobry przykład współpracowania tych dwóch języków w naprawdę dynamicznej aplikacji internetowej.
[edytuj] Historia projektu
Pierwsza wersja PHP, rozpowszechniana pod nazwą PHP/FI ("Personal Home Page/Forms Interpreter"), została stworzona przez Rasmusa Lerdorfa w roku 1994 jako zestaw skryptów Perla służący do monitorowania internautów odwiedzających jego witrynę. Gdy ruch stał się zbyt duży, przepisał je w języku C, dodając przy tym nowe opcje. Niedługo później ludzie zaczęli prosić go o możliwość użycia tych narzędzi na swoich stronach, zatem 8 czerwca 1995 roku autor udostępnił publicznie kod źródłowy (PHP Tools 1.0). Już kilka miesięcy później projekt przekształcił się w zalążek znanego obecnie języka programowania, gdy został połączony z innym narzędziem Rasmusa Lerdorfa - "Form Interpreter", które dało drugi człon nazwy. W 1997 roku pojawiło się PHP/FI 2.0, posiadające wtedy kilka tysięcy aktywnych użytkowników na całym świecie oraz obsługujące 50 tys. domen. Co ciekawe, wersja ta spędziła większość "życia" na beta testach. Oficjalne wydanie było tylko jedno i ukazało się w listopadzie 1997 roku.
W 1997 roku projektem zainteresowali się dwaj izraelscy programiści: Zeev Suraski i Andi Gutmans. Odkryli oni, że PHP/FI ma zbyt małe możliwości na potrzeby aplikacji eCommerce, którą tworzyli na uniwersytecie. Zdecydowali wtedy, że przepiszą kod PHP całkowicie od nowa, korzystając z pomocy już istniejącej społeczności PHP. W czerwcu 1998 roku ogłosili PHP 3.0 jako następcę PHP/FI, którego dalszy rozwój został wtedy zatrzymany. Był to wielki krok naprzód. PHP 3.0 posiadało całkowicie nową architekturę, która znacznie zwiększała wydajność. Pojawiły się w niej zalążki programowania obiektowego, ale najważniejszą cechą aplikacji była jej modularność. Użytkownicy mogli rozszerzać teraz funkcjonalność języka poprzez dodawanie nowych modułów.
Krótko po wydaniu PHP 3, w zimie 1998 Zeev Suraski oraz Andi Gutmans jeszcze raz zabrali się za przepisywanie kodu źródłowego PHP, korzystając z doświadczeń nabytych przy pracach nad poprzednią wersją. Za główne cele obrali poprawienie modułowości oraz wydajności złożonych aplikacji. Choć dotychczasowa wersja potrafiła sobie z nimi poradzić, nie była jednak stworzona do tego celu i przegrywała przez to z innymi rozwiązaniami.
W połowie roku 1999 ukazał się oficjalnie Zend Engine, nowy silnik języka skryptowego, wokół którego niedługo później zaczęto budować PHP 4. Jego nazwa to kompromisowe połączenie imion twórców projektu. Nowa, oparta o niego wersja PHP, ukazała się w maju 2000 roku. Tak jak poprzednio, był to potężny krok naprzód. Programiści mieli do dyspozycji teraz wiele nowych narzędzi, konstrukcji językowych oraz bezpieczniejszy system wejścia/wyjścia. Od strony administracyjnej pojawiło się oficjalne wsparcie dla wielu nowych serwerów. Przez cztery lata od chwili wydania ukazały się trzy kolejne edycje tej wersji oznaczone numerami: 4.1, 4.2 oraz 4.3. W każdej z nich odczuwalne było zwiększenie bezpieczeństwa, szybkości działania oraz możliwości. W 2004 roku obsługiwały one łącznie 20% wszystkich domen sieciowych. Również obecnie, dwa i pół roku po premierze PHP 5, "czwórka" jest bardzo chętnie wykorzystywana przez administratorów ze względu na dużą stabilność.
W 2002 roku Zeev Suraski oraz Andi Gutmans ponownie rozpoczęli znaczącą modernizację silnika PHP mającą na celu dodanie do tego języka modelu obiektowego z prawdziwego zdarzenia. W lutym 2003 ukazała się pierwsza wersja alpha nowej wersji PHP oznaczonej numerem 5.0.0. Stabilna wersja ukazała się prawie półtora roku później, w lipcu 2004 roku. Nowości sprawiły, że PHP może konkurować teraz z innymi rozwiązaniami server-side, jak równy z równym. Pojawił się całkowicie nowy model programowania obiektowego, przez co niestety została utracona część kompatybilności z poprzednimi wersjami PHP w niektórych skryptach. Jest to spowodowane zmianą sposobu reprezentacji obiektów. Przebudowano także wiele modułów, w tym do obsługi XML'a i komunikacji z bazą danych, czyniąc je bardziej przyjaznymi dla programistów.
W połowie roku 2005 zaczęły pojawiać się oficjalne sygnały, że rozpoczęto wstępne prace nad PHP 6. Obecnie publicznie dostępne są codzienne snapshoty rozwojowego repozytorium kodu źródłowego, które można ściągnąć i przetestować. Głównym celem jest dalsze dążenie do ujednolicenia projektu, wprowadzenia dalszych możliwości wymaganych przez złożone projekty (m.in. pełne wsparcie unicode czy system cache'owania kodu). Usuwane są też kolejne archaiczne rozwiązania pochodzące jeszcze z czasów PHP/FI oraz PHP3, co w przypadku najstarszych skryptów ponownie spowoduje problemy z kompatybilnością.
Na podstawie Wikipedii
[edytuj] Możliwości
[edytuj] Budowa PHP
PHP ma budowę modułową. Jądro projektu zapewnia obsługę wszystkich elementów języka oraz dostęp do podstawowego zestawu funkcji. Aby dodać więcej możliwości, instaluje się dodatkowe moduły. Część z nich ma charakter oficjalny i jest dołączona do każdej dystrybucji, pozostałe zgrupowane są w tzw. repozytorium PECL i należy je samodzielnie pobrać.
PHP, dzięki otwartości kodu źródłowego, pracuje bez problemu na niemal każdym istniejącym obecnie 32-bitowym systemie operacyjnym (z Uniksem oraz Windowsem na czele) oraz pozwala na łatwą integrację z większością serwerów WWW, np. Apache, IIS, OmniHTTPD.
[edytuj] Co PHP może zrobić?
PHP 5.1 oferuje swoim programistom wiele możliwości:
- Komunikacja z wieloma popularnymi bazami danych poprzez jednolity interfejs
- Obsługa wielu popularnych protokołów sieciowych, m.in. SSL, IMAP, SMTP, IRC.
- Profesjonalne wsparcie standardu XML
- Tworzenie obrazków w wielu popularnych formatach graficznych
- Wiele funkcji obróbki tekstu
- Wyrażenia regularne
- Bardzo elastyczne tablice o mieszanych kluczach
- Wsparcie dla usług sieciowych (SOAP, XML-RPC)
- Zaawansowany model programowania obiektowego
- Możliwość zintegrowania z platformą .NET
- Obsługa obiektów COM w Windows
- Funkcje kryptograficzne
- Funkcje konwersji kalendarza
- Funkcje konwersji kodowań
- Algorytmy kompresji: GZip oraz BZip2.
PHP jest nie tylko językiem internetowym. Można go także używać do pisania aplikacji na konsolę systemu operacyjnego, a nawet programów okienkowych z graficznym interfejsem użytkownika. Dlatego stanowi atrakcyjną alternatywę dla języków takich, jak Perl. Instalator napisany w PHP, uruchamiany z konsoli systemowej, posiada m.in. repozytorium PEAR.
[edytuj] Wady PHP
Początkowe lata rozwoju miały jednak w sobie coś z pospolitego ruszenia. Z tamtego okresu do dziś przetrwało trochę niekonsekwencji oraz nieścisłości, które sprawiają pewne kłopoty programistom. Twórcy powoli je usuwają, ale potrwa to jeszcze wiele lat.
- Ujednolicenie nazewnictwa - większość starszych modułów korzysta z różnych standardów nazywania poszczególnych funkcji, co utrudnia ich zapamiętywanie.
- Kilka niepotrzebnych rozwiązań utrudniających życie programistom: magic quotes oraz register globals
Konkurencja ma jeszcze jedną przewagę nad PHP. Interpretery języków takich, jak ASP, kompilują skrypty do bardziej czytelnej dla nich postaci, oszczędzając wiele czasu. PHP za każdym uruchomieniem wykonuje całą pracę od zera i choć jego interpreter jest niezwykle wydajny, z powodu nadkładania sobie pracy szybko traci zapas mocy. Możliwość kompilacji skryptów dostępna jest wyłącznie jako rozszerzenie.
Twórcy PHP świadomi są tych ograniczeń i starają się nie ignorować programistów. Problemem jest zachowanie wstecznej kompatybilności z olbrzymią bazą już stworzonego kodu PHP, który musi na nowych wersjach działać, najlepiej bez przeróbek. Zmiany są wprowadzane stopniowo i dlatego każdy, kto chciałby związać swoją karierę programisty WWW z językiem PHP, powinien śledzić aktualności pojawiające się na stronach www.zend.com oraz www.php.net.
[edytuj] Popularność PHP
PHP jest mimo swoich wad niezwykle popularny. Wykonane w nim są dosłownie wszystkie witryny: od stron domowych przez gry internetowe aż do komercyjnych portali oraz witryn wielkich korporacji. Polska społeczność PHP jest bardzo aktywna i napisała do tego języka wiele poradników co w połączeniu z łatwym dostępem do książek, daje naprawdę atrakcyjny produkt, jeśli chodzi o wsparcie. Nietrudno jest także zlokalizować dobry hosting, który za niewielką opłatą udostępni na serwerze konto z PHP oraz bazą danych.
[edytuj] Jak się uczyć?
Nauka PHP to długi i ciągły proces. Nie należy się spodziewać, że podręcznik ten nauczy kogokolwiek wszystkiego, ponieważ nie jest to jego założenie. Materiały pomocnicze pełnią funkcję wprowadzenia, zainteresowania i pokazania tematu, a cała reszta zależy już od konkretnego człowieka. Pamiętaj, że bez praktyki dużo nie osiągniesz. Dyskutuj z innymi programistami, analizuj cudze skrypty, podpatruj rozwiązania lub też wymyślaj własne. Eksperymenty nie niosą ze sobą żadnych poważnych skutków ubocznych i powinny być praktykowane przez każdego.
Nie przejmuj się, że twoje pierwsze projekty będą miały w sobie nieco z chaotyczności. To normalny proces, niemniej nie można na tym poprzestać. Za drugim razem zadaj sobie pytanie: co poprzednio mogłem zrobić lepiej? Czego mnie ten projekt nauczył? W ten sposób stopniowo dojdziesz do wprawy.
Duże znaczenie ma również rozumienie samych komputerów i algorytmów. PHP jest normalnym językiem programowania i pewnych spraw nie da się ominąć. Teoria bardzo dobrze uzupełnia praktykę, dlatego dokształcaj się także w tym względzie. Pozwoli Ci to zrozumieć, dlaczego dany problem rozwiązywany jest tak, a nie inaczej lub dlaczego w ogóle się nim zajmujemy.
Cała wiedza o projekcie PHP zgromadzona jest w obszernej dokumentacji zawierającej m.in. opis wszystkich modułów. Należy nauczyć się sprawnego poruszania po niej i orientowania się, gdzie co leży. Temu zagadnieniu poświęcony został jeden z rozdziałów podręcznika. Do dokumentacji warto zaglądać regularnie, gdyż jest ona zawsze zgodna z najnowszą dostępną wersją PHP. Stamtąd najszybciej się dowiesz, czy nie pojawiły się nowe funkcje, parametry, możliwości wykorzystania istniejących elementów. Jeżeli zamierzasz wiązać się na dłużej ze środowiskiem PHP, adres www.php.net powinien nawet znaleźć się wśród najczęściej przeglądanych przez Ciebie witryn. Stanie w miejscu, kiedy projekt ten posuwa się naprzód, jest kiepską alternatywą i bez orientowania się w zachodzących zmianach szybko odkryjesz, że twoje skrypty z tajemniczych powodów nie chcą pracować na nowych wersjach, co przyprawia o złość klienta.
[edytuj] Teoria czy praktyka?
Wielu początkujących programistów zadaje sobie pytanie - czy skupić się na teorii, czy też raczej na praktyce? Odpowiedź jest prosta: nie można się skupiać wyłącznie na jednym zagadnieniu. Ważna jest bowiem wiedza jak działa ten język programowania, ale nie można nie wiedzieć jak go wykorzystać.
Przed przystąpieniem do pisania skryptu powinniśmy rozrysować sobie jego projekt na papierze oraz ustalić pewne konwencje, jakich będziemy się trzymali w kodzie. Istotne jest, aby planowane przedsięwzięcie umieć zrealizować w praktyce. Choć projekty często tworzymy, aby nauczyć się czegoś nowego, poprzeczka nie powinna być windowana zbyt wysoko, gdyż wtedy nigdy go nie ukończymy. O wiele lepsze jest stopniowe poznawanie coraz bardziej złożonych elementów.
W książce tej duży nacisk kładziony jest na ponowne wykorzystywanie raz napisanego kodu. Uważamy, że nie ma sensu raz po raz na nowo odkrywać koła i tworzyć wszystkiego od nowa. Dobry programista po pewnym czasie utworzy sobie zbiór bibliotek z najczęściej używanymi funkcjami, które będzie przenosić między kolejnymi projektami, oszczędzając sobie zbędnej pracy. Praktyki te także powinny być w naszym planie uwzględnione. Inne sprawy, na które powinniśmy zwrócić uwagę, to:
- Nazewnictwo - czy stosujemy nazwy polskie, czy angielskie. W jakim stylu je zapisujemy? nazwa_nazwa czy nazwaNazwa?
- Jednolity styl kodowania
- Jakie biblioteki zewnętrzne wykorzystamy? W jakim stopniu?
- Ogólna budowa całej aplikacji:
- Jaką drogę pokonują dane podczas tworzenia strony?
- W jaki sposób dane są przetwarzane?
- Jak są dane wyświetlane?
- Jak połączone są ze sobą poszczególne elementy aplikacji?
- Czy przewidujemy w przyszłości dalszą rozbudowę projektu? Jeśli tak, jakie kroki podejmiemy, aby ją maksymalnie ułatwić?
Projekty aplikacji można sporządzać również na komputerze. Pomocne będą tu wszelkie edytory tekstu, generatory diagramów itd. Po głębszym poznaniu języka PHP można zaznajomić się również z diagramami UML wykorzystywanymi w zawodowych zespołach programistycznych.
[edytuj] Fora dyskusyjne
Niestety, nie każdy problem będziemy w stanie rozwiązać samodzielnie. Wtedy z pomocą przyjdą nam fora dyskusyjne, których dużo jest w polskim Internecie. Pamiętaj, że często aby uzyskać odpowiedź, musisz zastosować się do reguł forum. Wielu administratorów nie lubi użytkowników, którzy umieszczają tematy nie tam, gdzie trzeba oraz nie stosują się do widocznych wszędzie informacji. Zanim zadasz jakieś pytanie, poświęć przynajmniej kilka minut na zapoznanie się z opisami poszczególnych forów, wstawkami zatytułowanymi "Zanim napiszesz posta" itd. Ich przeczytanie to chwilka, ale zapoznanie się z nimi może oszczędzić ci przykrości przy kontakcie z moderatorem. Zamieszczamy ten krótki poradnik, gdyż mnóstwo początkujących programistów nie zwraca na te niby oczywiste rzeczy uwagi, co skutkuje długim oczekiwaniem na pomoc lub konfliktem z moderatorem na samym wstępie.
Zanim napiszesz post, upewnij się, że:
- Umieszczasz go na odpowiednim forum. Niektóre serwisy posiadają podział na forum dla spraw podstawowych i zaawansowanych. Zdecydowanie zalecamy pisanie na tym pierwszym. Wciskanie się na siłę na forum dla zaawansowanych programistów z tematami "Jak połączyć się z bazą XXX" niemal na pewno zostaną źle odebrane przez internautów.
- O niektóre sprawy w ogóle nie powinno się pytać, np. "czy można zrobić...", "czy jest jakiś skrypt", "gdzie mogę znaleźć XXX". Prawie na pewno dostaniesz link do wyszukiwarki Google ze złośliwą adnotacją. I to w zasadzie jest prawda. Z pomocą wyszukiwarki często znajdziesz odpowiedź znacznie szybciej, niż gdybyś miał oczekiwać na odpowiedź na forum. Wielu programistów traktuje nawet forum - miejsce rozwiązania problemu jako ostateczność, kiedy inne źródła zawiodły.
- Problem nie jest wyjaśniony w jakimś artykule w serwisie. Wiele serwisów posiada bazę artykułów i porad, które wyjaśniają niektóre sprawy. Warto się z nimi zapoznać przed przystąpieniem do pisania.
Aby szybko uzyskać odpowiedź, upewnij się, że:
- Podałeś wersje oprogramowania
- Podałeś fragment kodu źródłowego powodujący błąd
- W załączonym kodzie nie ma jakichś ważnych danych osobowych, haseł itd.
- Wyjaśniłeś dokładnie, co jest z nim nie tak. Nie używaj nigdy pojedynczego zwrotu nie działa, gdyż jest on wieloznaczny i prawie zawsze zostaniesz zapytany o więcej szczegółów
- Twój post jest poprawnie napisany pod względem ortograficznym. Zła interpunkcja lub robienie błędów w podstawowych wyrazach jest oznaką ignorancji, a tłumaczenia, że to nie jest lekcja polskiego, mogą budzić niesmak. Jeżeli nie jesteś pewny, pisz posty w edytorze tekstu ze sprawdzaniem pisowni.
Oczywiście istotna jest także sprawa tytułu tematu. Najlepsze są te streszczające istotę problemu, np. "Problem z połączeniem z bazą danych". Nazwa "Mam problem, pomocy!" nie jest już dobra, gdyż nie mówi, co jest w środku. W efekcie temat może zostać niezauważony przez osobę znającą rozwiązanie. Zwróć też uwagę, czy forum nie wymaga dodania jakiegoś krótkiego prefiksu, np. "[php]" w celu łatwiejszego skatalogowania tematu.
Na koniec pamiętaj - nawet jeżeli zostaniesz przez moderatora publicznie upomniany, nie jest to jeszcze powód do rozpaczy. Zadaniem moderatora jest utrzymanie porządku i nie oznacza to, iż żywi on do Ciebie przez to jakąś osobistą urazę. Po prostu przeproś i w przyszłości staraj się unikać podobnego błędu.
[edytuj] Opis instalacji
PHP jest dostępny na niemal każdym popularnym systemie operacyjnym, lecz do efektywnego korzystania z niego potrzebne jest dodatkowe oprogramowanie.
[edytuj] Serwer WWW
PHP pracuje po stronie serwera, dlatego aby móc testować nasze skrypty w przeglądarce internetowej, musimy postawić taki na własnym komputerze. Nasz wybór padł na program o otwartych źródłach Apache HTTP Server rozwijany przez Apache Foundation (httpd.apache.org). Jest to najchętniej wykorzystywany serwer WWW na świecie, obsługujący prawie 60% wszystkich witryn, a przy tym bezpieczny i łatwy w konfiguracji. Dostępne jest wiele wersji na różne systemy operacyjne.
PHP komunikuje się z serwerem WWW na trzy sposoby:
- CGI - (Common Gateway Interface). PHP jest tu uruchamiane przez serwer jako samodzielna aplikacja na czas wykonywania skryptu. Rozwiązanie to jest bardzo mało wydajne przy większym natężeniu ruchu, ponieważ dla każdego żądania musi zostać uruchomiona nowa, niezależna kopia interpretera, jednak oferuje administratorowi zmiany wielu ustawień dotyczących bezpieczeństwa wykonywanych skryptów.
- Moduł serwera - do najpopularniejszych serwerów można podłączyć PHP jako moduł. Znacząco zwiększa wydajność, lecz utrudnia zachowanie odpowiedniego poziomu bezpieczeństwa na serwerze.
- FastCGI - rozwiązanie łączące w sobie zalety CGI oraz wydajność modułów.
W tym podręczniku opisane zostaną pierwsze dwa sposoby. Wsparcie dla FastCGI jest w PHP bardzo słabo udokumentowane oraz wymaga skorzystania z nieoficjalnych dodatków, przez co stanowi wyzwanie nawet dla doświadczonych administratorów.
[edytuj] Baza danych
PHP sprawdza się najlepiej przy przetwarzaniu danych, lecz do ich przechowywania używana jest najczęściej niezależna aplikacja, np. relacyjna baza danych. Dane w bazie przechowywane są jako rekordy w tabelach o określonej strukturze, a słowo "relacyjny" oznacza, iż mogą istnieć wszelkiego rodzaju powiązania między tabelami. Do komunikacji z bazami wykorzystywany jest specjalny język: SQL, którego podstawy także niebawem poznamy.
Wśród programistów PHP największą popularnością cieszy się baza danych darmowa do użytku domowego MySQL produkowana przez szwedzką firmę MySQL AB (www.mysql.com). Także i ona współpracuje z każdym popularnym systemem operacyjnym, a przy tym nietrudno znaleźć pomoc w przypadku ewentualnych kłopotów.
[edytuj] Biblioteki dodatkowe
Jeżeli pracujesz na systemie Unix/Linux i chciałbyś zainstalować PHP ze źródeł, musisz upewnić się, że posiadasz zainstalowane wszystkie dodatkowe biblioteki. Zazwyczaj powinny być one już dostępne np. jako pakiety, ale na wszelki wypadek podamy adresy, pod którymi można je zdobyć:
- Zlib (www.gzip.org/zlib)
- Libxml2 (www.xmlsoft.org)
- Libmysql (dostarczana razem z MySQL)
Instalując PHP w systemach Windows, wszystkie biblioteki dostarczane są razem z pakietem instalacyjnym.
| Porada Jeżeli któryś z adresów będzie nieaktywny, pamiętaj o wykorzystaniu wyszukiwarki Google do odnalezienia potrzebnego Ci pliku. |
[edytuj] Proces instalacji
Instalacja PHP zostanie przeprowadzona w kilku etapach:
- Instalacja Apache
- Instalacja MySQL
- Instalacja PHP
- Integracja PHP z serwerem WWW
- Testowanie
Skupiliśmy się na dwóch najpopularniejszych platformach:
- Unix/Linux (instalacja ze źródeł)
- Windows (przy pomocy instalatorów)
W przypadku instalacji na systemie Windows wszystkie aplikacje kopiowane będą do jednego katalogu: D:\Serwer\. Systemy uniksowe same wybiorą odpowiednie katalogi zgodnie ze swą własną strukturą.
Wszystkie liczące się dystrybucje systemu Linux posiadają wymienione tu aplikacje dostępne jako pakiety. W podręczniku nie będziemy opisywać, jak je z nich zainstalować, ponieważ opis rozrósłby się do niewyobrażalnych rozmiarów. Jeżeli jesteś zainteresowany tym sposobem instalacji, poszukaj odpowiedniego poradnika w dokumentacji twojej dystrybucji.
[edytuj] Alternatywny sposób instalacji
Możemy również posłużyć się gotowym pakietem WAMP, który zawiera wszystkie wymienione wyżej składniki. Przykładowym zestawem tego typu jest program VertrigoServ, lub KrasnalServ, który zawiera polskie tłumaczenie i znacznie ułatwia instalacje i zarządzanie poszczególnymi komponentami serwera.
W Linux Debian robimy: apt-get install apache2 ;)
W zeszycie ćwiczeń podręcznika informatyki dla gimnazjum znajduje się dział Serwer WWW wraz z bazą danych i PHP opisujący w skrócie instalację i uruchomienie pakietu XAMPP.
[edytuj] Instalacja Apache HTTP Server
Aby wygodnie testować skrypty PHP i generowane przez nie witryny internetowe, należy postawić na własnym komputerze prywatny, testowy serwer WWW. Zadanie to jest bardzo proste i sprowadza się do zainstalowania jednego programu.
Najpopularniejszym wykorzystywanym w Internecie serwerem WWW jest program o otwartych źródłach Apache HTTP Server rozwijany przez Apache Foundation. Zarówno dokumentację, jak i pakiety instalacyjne można znaleźć pod adresem httpd.apache.org.
Dokument ten pokazuje, jak zainstalować Apache na różnych systemach operacyjnych.
[edytuj] Instalacja w systemach Unix/Linux
[edytuj] Kompilacja ze źródeł
Wydajemy polecenia w konsoli: su [enter] hasło administratora
Przechodzimy do katalogu z kodem źródłowym
./configure make make install
[edytuj] Pakiety
Serwer Apache oraz inne niezbędne programy można także zainstalować z pakietów dołączonych do Twojej dystrybucji Linuksa, jednak z powodu ich różnorodności nie będziemy w tym podręczniku omawiać szczegółowo instalacji na każdym z nich. Podajemy jedynie ogólne wskazówki, jak przystosować pakiet do uruchamiania przykładów z podręcznika. Bardzo prawdopodobne, że na stronie internetowej twojej dystrybucji znajduje się artykuł opisujący dokładnie instalację zestawu Apache + PHP, dlatego tam też odsyłamy po szczegóły.
Aby zainstalować serwer Apache z pakietu, upewnij się, że twoja dystrybucja go zawiera (pamiętaj, aby wybrać wersję 2.x), a następnie postępuj zgodnie z instrukcją instalacji dla twojego menedżera pakietów. Przeważnie pakiet automatycznie konfiguruje też serwer do pracy. Pliki konfiguracyjne odnajdziesz najczęściej w katalogu /etc/apache, natomiast strony WWW można otwierać z /var/www. Zmienimy sobie ten ostatni katalog tak, aby każdy użytkownik systemu miał na swoim koncie katalog www.
- Otwórz plik httpd.conf w katalogu /etc/apache lub jeśli w poprzednim nie ma za dużo treści - plik apache2.conf w tym samym katalogu
- Znajdź dyrektywę UserDir i ustaw ją na /home/*/www
- Poniżej znajduje się znacznik <Directory> - ustaw w nim identyczną ścieżkę (konfiguracja katalogu kont)
W ten sposób, jeżeli w swoim katalogu domowym utworzysz katalog WWW, będziesz mieć dostęp do znajdujących się w nim plików poprzez adres http://localhost/~nazwa_uzytkownika/.
Możesz teraz uruchomić serwer Apache poprzez skrypt startowy demona znajdujący się wraz z innymi (np. w katalogu /etc/rc.d/ albo /etc/init.d/ w zależności od dystrybucji). Pamiętaj, że jest to na razie tylko serwer, do którego wciąż musimy podpiąć PHP. Zajmiemy się tym w następnym rozdziale.
[edytuj] Instalacja w systemach Windows
Pobieramy ze strony httpd.apache.org najnowszą wersję instalacyjną (MSI Installer) dla systemu Windows (w chwili pisania - 2.0.55). Uruchamiamy instalator i po trzech kliknięciach "Next" pojawia się formularz, w którym wpisujemy następujące dane:
| Network Domain | localhost |
|---|---|
| Server Name | localhost |
| Administator's Email Address | admin@localhost |
Zaznaczanie opcji for all users jest niekonieczne, ponieważ stawiamy serwer testowy na prywatnym komputerze. Jeżeli jest on podłączony do sieci lokalnej, jest to nawet niewskazane, ponieważ później zainstalujemy obsługiwany z przeglądarki menedżer baz danych, który ktoś mógłby wykorzystać do zniszczenia nam projektów.
Na następnym ekranie zaznaczamy opcję "Typical" i wybieramy miejsce, do którego ma być zainstalowany serwer: D:\Serwer. Instalator automatycznie utworzy tam katalog Apache2 i skopiuje wszystkie pliki.
Teraz serwer musimy przystosować do pracy i skonfigurować. Choć instalator utworzył nam już katalog D:\Serwer\Apache2\htdocs, z którego domyślnie odczytywane są strony, nie jest najlepszym rozwiązaniem umieszczać tam wszystkich naszych dokumentów. Pozostawimy go tylko do późniejszego zainstalowania w nim różnych menedżerów serwera (np. do zarządzania bazą danych) obsługiwanych z poziomu przeglądarki. Dla naszych własnych plików utworzymy katalog D:\Serwer\www.
Otwórz teraz plik konfiguracyjny serwera: D:\Serwer\Apache2\conf\httpd.conf lub też "D:\Serwer\Apache2\conf\default\httpd.conf". Odnajdź w nim następujące dyrektywy i przypisz im wartości:
| Listen | 127.0.0.1:80 |
|---|---|
| ServerName | localhost |
| DirectoryIndex | index.html index.php index.php5 |
| UserDir | "D:/Serwer/www" |
| DocumentRoot | "D:/Serwer/www" |
Należy tu również pamiętać, że zmieniając DocumentRoot, trzeba dokonać zmian również około 20 linijek poniżej. Nakazuje to natępujący komentarz : "# This should be changed to whatever you set DocumentRoot to".
UWAGA: W niektórych wersjach apache dyrektywa UserDir ma oddzielny plik. Znajduje się on w "D:\Serwer\conf\extra\httpd-userdir.conf" . Należy wtedy odkomentować linijkę # Include conf/extra/httpd-userdir.conf (tzn. usunąć znak #), jak również linijkę "# LoadModule userdir_module modules/mod_userdir.so"
Pod dyrektywą UserDir dodaj jeszcze poniższy blok:
<Directory "D:/Serwer/www"> AllowOverride All Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec ExecCGI Order allow,deny Allow from all </Directory>
Jego zadaniem jest powiadomienie serwera, jakie akcje są dozwolone wewnątrz tego katalogu.
Po instalacji i uruchomieniu aplikacji powinno pokazać się dosowe okno. Świadczy ono o tym, że serwer pracuje. Należy pamiętać o tym, aby nie zamykać tego okna ponieważ zakończy to działanie serwera.
Teraz testujemy czy wszystko przebiegło poprawnie. Otwórz przeglądarkę i wpisz w niej http://localhost/. Powinna pojawić ci się domyślna strona startowa serwera. Aby przetestować katalog "UserDir", utwórz w nim katalog np. test, zapisz w nim plik index.html z napisem "Hello World" i uruchom w przeglądarce: http://localhost/~test/. Jeżeli wyświetlił się napis, wszystko jest OK. W systemach 2000/XP serwer Apache może pracować jako usługa systemu operacyjnego. Zamiast okienka dosowego, dostępna będzie w trayu ikonka programu "Apache Monitor" pozwalająca na włączanie, wyłączanie oraz restartowanie zainstalowanych usług Apache. Jeśli taka ikonka nie wyświetla się w trayu systemowym, należy uruchomić "D:\Serwer\bin\ApacheMonitor.exe"; aby mieć do niej dostęp na stałe, można dodać skrót do tego programu do Autostartu w Menu Start.
Należy pamiętać, że domyślna konfiguracja serwera Apache może różnić się znacząco od tych, które są ustawione w całych zestawach instalacyjnych (Krasnal, WebServ itd.) - np. często wykorzystywany mod_rewrite należy samemu włączyć po instalacji Apache'a, poprzez edycję pliku httpd.conf (znajdujemy linijkę #LoadModule rewrite_module modules/mod_rewrite.so i usuwamy # na początku.)
[edytuj] Instalacja w systemie Mac OS X
Standardowo Mac OS X ma zainstalowany serwer Apache -- możesz go włączyć w panelu preferencji Sharing jako Personal Web Sharing.
Jeśli chcesz nowszą wersję Apache, możesz zainstalować pakiet Apache2 za pomocą programów Fink oraz FinkCommander.
Apache można również skompilować samemu. Potrzebna będzie instalacja dodatku Developer Tools z płyty systemu Mac OS X, dalej należy postępować jak w przypadku systemów Unix/Linux.
[edytuj] Instalacja MySQL 5.0
Jeśli chcesz stronę oddać do użytku to na serwerze pewnie już jest, ale tutaj opiszemy jak zainstalować MySQL na swoim komputerze.
[edytuj] Instalacja w systemach Unix/Linux
[edytuj] Kompilacja ze źródeł
$ ./configure --prefix=/usr/local/mysql $ make # make install
[edytuj] Pakiety
Także serwer baz danych MySQL można zainstalować z pakietów. Przeważnie jest on już wtedy w pełni gotowy do pracy i wymaga jedynie uruchomienia. Po szczegóły instalacji odsyłamy do instrukcji twojego menedżera pakietów. Jeśli pakiet MySQL nie był zainstalowany domyślnie w Twoim komputerze, być może będziesz musiał sam skonfigurować i uruchomić serwer.
[edytuj] Konfiguracja i uruchomienie MySQL pod Linuksem
Poniższy opis został sprawdzony w dystrybucji Slackware Linux. Pod innymi dystrybucjami może pójść tylko łatwiej. Żeby przekonać się, czy serwer MySQL działa, należy spróbować się na niego zalogować. Jako root:
$ mysql -u root -p
Jeśli MySQL zapyta o hasło to znaczy, że działa. Gdy zaś otrzymamy komunikat w stylu "cannot connect", musimy sprawdzić i poprawić/uzupełnić/dokończyć konfigurację:
Czy w katalogu /etc jest plik my.cnf? Jeśli go nie ma, należy skopiować w któryś z gotowych plików my-[small|medium|large|huge].cnf. Przykładowo dla prostej bazy danych na zwykłym domowym PC do ćwiczeń w zupełności wystarczy model small - nie zużyje wielkich połaci RAM-u.
# cp /etc/my-small.cnf /etc/my.cnf
Teraz należy utworzyć grupę mysql i użytkownika mysql (niewykluczone, że już istnieje), nadać mu hasło i prawa do katalogów i programów związanych z MySQL.
# useradd mysql # passwd mysql # groupadd mysql # chown mysql:mysql /usr/bin/mysql*
Nieskonfigurowany MySQL daje nam możliwość łatwego wyboru miejsca, w którym będą przechowywane bazy. Można wybrać standardowe miejsce w /var/lib/mysql, ale byłoby dobrze nie przechowywać ważnych danych na partycji systemowej. Wielu użytkowników ma katalog /home domyślnie przez dystrybucję lub z własnego wyboru umieszczony na innej partycji lub nawet innym dysku. Warto wtedy utworzyć w /home katalog na bazy, który przeżyje każdą katastrofę łącznie z ponowną instalacją systemu. Obojętnie, które rozwiązanie wybierzesz, dalej będę się posługiwał nazwą /ścieżka/do/bazy.
# mkdir /ścieżka/do/bazy (np. /home/mysql_bazy) # chown -R mysql:mysql /sciezka/do/bazy
Następnie należy przygotować katalog roboczy MySQL-a. Należy to zrobić jako użytkownik mysql - inaczej będziemy mieli trudności z uruchomieniem i działaniem serwera, prawami dostępu etc.
# su - mysql $ mysql_install_db --datadir="/ścieżka/do/bazy" $ exit (wyjście do roota)
Jeśli instalowaliśmy bazę w innym niż domyślny katalogu, należałoby uwzględnić tę zmianę w skrypcie startowym serwera. W tym celu otwieramy w dowolnym edytorze plik /etc/rc.d/rc.mysqld, odnajdujemy zapis mysqld_safe --datadir=/var/lib coś_tam_dalej i zmieniamy go na mysqld_safe --datadir=/ścieżka/do/bazy coś_tam_dalej. Serwer musi wiedzieć, z jakim katalogiem będzie pracował. W innych dystrybucjach (np. Debian) skrypty startowe umieszczane są w katalogu /etc/init.d/. Przy okazji zajmiemy sie automatycznym uruchamianiem serwera na przyszłość.
# chmod 755 /etc/rc.d/rc.mysqld
Teraz już możemy uruchomić serwer: Edytujemy plik /var/lib/mysql/mysqld.conf (w innych dystrybucjach np. /var/lib/mysql/mysqld.conf)i, jeśli baza będzie służyć tylko nam na naszym komputerze (a tak zakłądamy w podręczniku), należy odkomentować (usunąć znak # z początku) linijkę, w której znajduje sie zapis "skip-networking". W ten sposób odetniemy użytkownikom innych komputerów możliwość zdalnego logowania się do naszej bazy.
# su -mysql $ /etc/rc.d/./rc.mysqld start &
Serwer powinien się uruchomić.
Jeśli chcemy zwiększyc bezpieczeństwo, możemy użyć skryptu mysql_secure_installation. Jest on dobrze opisany podczas wykonywania - prawie, że wizard. Pozostawi jedynie konto roota dla bazy, wyrzuci konta anonimowe i bazę test wraz z zawartymi w niej tabelami.
[edytuj] Instalacja w systemach Windows
Poniższy sposób instalacji dotyczy systemów Windows 2000 oraz Windows XP.
- Wejdź na stronę www.mysql.com
- W dziale "Downloads" zlokalizuj kategorię "Windows" i pobierz plik oznaczony jako "Windows (x86)" (34.9 MB). Upewnij się, że ściągasz najnowszą wersję.
- Rozpakuj archiwum i uruchom plik Setup.exe
- Wybierz rodzaj instalacji (Custom)
- Wybierz katalog, do którego chcesz zainstalować serwer
- Zaakceptuj proponowane przez instalator komponenty pakietu.
- Poczekaj, aż wszystkie pliki zostaną skopiowane.
- Instalator zaproponuje Ci rejestrację w witrynie mysql.com. Wybierz opcję Skip Sign-up, chyba że posiadasz już konto lub chcesz takie utworzyć.
- Zaznacz opcję "Configure the MySQL Server now" i kliknij "Finish". Uruchomione zostanie narzędzie konfiguracji serwera.
- Wybierz "Reconfigure Instance" i kliknij "Next"
- Wybierz "Standard configuration" i kliknij "Next"
- W kolejnym kliknij na "Next" bez zaznaczania czegokolwiek.
- Zaznacz opcję "Modify Security Settings" i wpisz we wszystkie pola hasło "root".
- Kliknij "Execute", aby zapisać zmiany.
MySQL został zainstalowany i jest dostępny jako usługa systemu Windows. Możesz go zatrzymywać lub restartować poprzez Panel Sterowania > Narzędzia administracyjne > Usługi.
Poniższy sposób instalacji dotyczy systemu Windows 98.
- Wejdź na stronę www.mysql.com
- W dziale "Downloads" zlokalizuj kategorię "Windows" i pobierz plik oznaczony jako "Windows (x86)" (34,9 MB). Upewnij się, że ściągasz najnowszą wersję.
- Rozpakuj archiwum i uruchom plik Setup.exe
- Wybierz rodzaj instalacji (Custom)
- Wybierz katalog, do którego chcesz zainstalować serwer
- Zaakceptuj proponowane przez instalator komponenty pakietu.
- Poczekaj, aż wszystkie pliki zostaną skopiowane.
- Zaznacz opcję "Configure the MySQL Server now" i kliknij "Finish". Uruchomione zostanie narzędzie konfiguracji serwera.
- Kliknij "Next", wybierz "Standard configuration" i ponownie kliknij "Next"
- W kolejnym kliknij na "Next" bez zaznaczania czegokolwiek.
- Kliknij "Execute", aby zapisać plik konfiguracyjny.
MySQL został zainstalowany. Jego jedynym użytkownikiem jest "root" z pustym hasłem. Aby uruchomić bazę, należy uruchomić plik "mysqld.exe". Możesz zatrzymywać bazę poprzez komendę "mysqladmin.exe -uroot -p shutdown". W razie problemów z "Call to undefined function mysqli_connect()" skopiuj libmysql.dll z folderu "php5" do "Apache2\bin"
Aby dokonać w przyszłości aktualizacji bazy danych MySQL do nowszej wersji, zatrzymaj serwer MySQL, zrób na wszelki wypadek kopię zapasową katalogu data, a następnie powtórz wszystkie powyższe kroki, instalując nową wersję na starą.
| Do zrobienia: instalacja na systemie Windows Me - jest tam inna procedura |
[edytuj] Instalacja w systemie Mac OS X
Wystarczy pobrać gotowy pakiet (.dmg) ze strony domowej MySQL. Zamieszczony jest na nim standardowy instalator. Dodatkowo warto kliknąć na plik .prefPane, który doda do systemowych preferencji możliwość startowania i zatrzymywania serwera.
Po instalacji Developer Tools z płyty systemu Mac OS X można skompilować bazę ze źródeł, tak samo jak w innych systemach uniksowych.
[edytuj] Korzystanie z bazy
[edytuj] Instalacja PHP 5.2
Na samym końcu instalujemy główny program, czyli interpreter PHP oraz podłączamy go do już zainstalowanego serwera Apache.
[edytuj] Instalacja w systemach Unix/Linux
[edytuj] Kompilacja ze źródeł
Na początek pobieramy źródła projektu dostępne na stronie www.php.net. Rozpakowujemy je w katalogu /usr/src:
tar -xvf php-5.2.0.tar.gz
Jeżeli ściągnąłeś inną wersję projektu albo źródła są skompresowane innym algorytmem, odpowiednio zmodyfikuj nazwę swojego pliku. Po rozpakowaniu powinien pojawić się katalogu php-5.2.0. Przechodzimy do niego:
cd php-5.2.0
Teraz rozpoczynamy konfigurowanie naszej kompilacji poprzez podanie odpowiednich dyrektyw do skryptu configure.
./configure --with-apxs2=/usr/local/apache2/bin/apxs --with-config-file-path=/etc/apache --with-zlib --with-mysql=/usr/local/mysql --with-mysqli=/usr/local/mysql/bin/mysql_config --with-pdo-mysql=/usr/local/mysql/bin/mysql_config --with-gd --enable-gd-native-ttf --with-libxml-dir=/usr/local/libxml
Oto opis poszczególnych opcji:
- --with-apxs2=/usr/local/apache2/bin/apxs - informujemy, że chcemy skompilować PHP jako moduł serwera Apache i podajemy ścieżkę do programu apxs dostarczanego wraz z nim.
- --with-config-file-path=/etc/apache - informujemy, że plik konfiguracyjny PHP znajdować się będzie w katalogu /etc/apache
- --with-zlib - dodajemy bibliotekę Zlib potrzebną niektórym modułom
- --with-mysql=/usr/local/mysql - aktywujemy najstarsze rozszerzenie do obsługi baz danych MySQL, podając ścieżkę do katalogu, w którym znajduje się folder z nagłówkami C serwera.
- --with-mysqli=/usr/local/mysql/bin/mysql_config - aktywujemy nowe rozszerzenie do obsługi baz danych MySQL, podając ścieżkę do programu mysql_config generującego odpowiednie pliki.
- --with-pdo-mysql=/usr/local/mysql/bin/mysql_config - aktywujemy sterownik dla bazy MySQL dla biblioteki PHP Data Objects omawianej w tym podręczniku. Ścieżka także prowadzi do programu mysql_config.
- --with-gd - aktywujemy bibliotekę GD do generowania obrazków
- --enable-gd-native-ttf - aktywujemy wbudowaną obsługę czcionek TTF w bibliotece GD.
- --with-libxml-dir=/usr/local/libxml - podajemy ścieżkę do katalogu biblioteki libxml, dzięki czemu aktywne będą moduły do obsługi XML-a w PHP.
Kiedy skrypt konfiguracyjny zakończy działanie, wydajemy dwa kolejne polecenia kompilacji oraz instalacji PHP:
make make install
Kopiujemy plik konfiguracyjny PHP do odpowiedniego katalogu:
cp php.ini-recommended /etc/apache/php.ini
Otwieramy ten plik i modyfikujemy w nim następujące dyrektywy:
- error_reporting - ustawić na E_ALL | E_STRICT
- doc_root - ustawiamy ścieżkę do katalogów kont, w których zamierzamy docelowo używać PHP: /home/*/www (ta sama, co w Apache)
Otwieramy plik konfiguracji serwera Apache: /etc/apache/httpd.conf. Sprawdzamy, czy program "apxs" nie dodał nigdzie linijki
LoadModule php5_module modules/libphp5.so
Jeżeli nie, dopisujemy ją. Ponadto musimy dodać
AddType application/x-httpd-php .php .php5
Aby serwer wiedział, jakie pliki należy przepuszczać przez interpreter PHP. Zapisujemy zmiany i na koniec restartujemy/uruchamiamy serwer Apache:
/usr/local/apache2/bin/apachectl start
Pora na przetestowanie działania PHP. Do katalogu /usr/local/apache2/htdocs kopiujemy plik phpinfo.php o treści:
<?php phpinfo(); ?>
W przeglądarce wpisujemy http://localhost/phpinfo.php - powinien ukazać się bardzo długi raport nt zainstalowanej wersji PHP (wersja, konfiguracja, moduły itd.). Jeżeli zamiast tego pokazany zostanie wpisany wyżej kod, oznacza to, że instalacja nie przebiegła poprawnie i na którymś etapie został popełniony błąd.
| Opisana tutaj procedura kompilacji ze źródeł jest identyczna także dla wersji 5.2.x, a z dużą dozą prawdopodobieństwa także i dla przyszłych wydań. |
[edytuj] Pakiety
Niemal wszystkie dystrybucje systemu Linux mają w swoich zasobach pakiety PHP. Jeżeli nie czujesz się na siłach instalować projektu ze źródeł, możesz skorzystać z tego rozwiązania i stosując się odpowiednio do instrukcji systemu pakietów używanego w twojej dystrybucji. Pakiety mają jednak kilka wad, a najważniejszą z nich jest konieczność polegania na intuicji autora pakietów. Interpreter nie zawsze jest skonfigurowany tak, jak byśmy chcieli, a ponadto należy ręcznie uaktywnić sobie dodatkowe moduły. Choć nowe wersje PHP ukazują się przeważnie w kilkumiesięcznych odstępach, niekiedy dystrybucje wyposażone są w naprawdę stare wersje interpretera. Przypominamy, że podręcznik ten pisany jest pod wersje PHP 5.1.x/5.2.x, więc jeśli twój pakiet zainstaluje Ci starszą, niektóre przykłady mogą nie działać prawidłowo.
Po zainstalowaniu PHP z pakietu odnajdź w swoim systemie plik php.ini i upewnij się, że następujące dyrektywy są ustawione na:
- error_reporting = E_ALL | E_STRICT - poziom raportowania błędów
- doc_root = "/home/*/www" - katalogi kont użytkowników
- register_globals = Off
- magic_quotes_gpc = Off
- magic_quotes_runtime = Off
Trzy ostatnie dyrektywy służą do zachowania kompatybilności ze skryptami pisanymi pod PHP 3 i pierwsze wersje PHP 4, aktualnie zdecydowanie odradza się korzystanie z nich nie tylko ze względów bezpieczeństwa, ale też z powodu planów ich wycofania w PHP 6. Dlatego pozostawiamy je wyłączone i tak też będziemy pisać nasze skrypty.
Upewnij się ponadto, że PHP ładuje moduły php_pdo.so, php_pdo_mysql.so, php_mysql.so, php_zlib.so oraz php_gd2.so.
[edytuj] Instalacja w systemach Windows
Instalacja PHP jako jedyna nie polega na klikaniu dalej. Taka możliwość oczywiście istnieje, lecz wtedy nie można skonfigurować interpretera do pracy jako moduł serwera.
By zacząć działać, musimy ściagnąć najnowszą wersję PHP ze strony www.php.net w wersji dla systemu Windows. W chwili powstawania tego tekstu najnowszą wersją było PHP 5.1.1 (obecna wersja to 5.2.11). Ściągnięty plik ( tzw. binarki, przykładowo wyglądające: 'php-5.2.6-Win32.zip' ) rozpakowujemy do katalogu D:/Serwer/php5/. Następnie zmieniamy rozszerzenie pliku php.ini-recommended na php.ini i zabieramy się za jego edytowanie:
- Edycja poziomu błędów: znajdź linię error_reporting = E_ALL i zmień ją na error_reporting = E_ALL | E_STRICT. Ponadto odszukaj display_errors i ustaw wartość na On.
- W dyrektywie doc_root wprowadzamy ścieżkę do katalogu "D:/Serwer/www" utworzonego przy okazji instalowania serwera Apache. Tu będziemy trzymać nasze projekty.
- W dyrektywie extension_dir wprowadzamy ścieżkę do katalogu D:/Serwer/php5/ext, aby PHP mógł zlokalizować dodatkowe moduły.
- Przechodzimy do sekcji ; Dynamic Extensions ; gdzie ustawiamy, jakie dodatkowe moduły mają być ładowane przy starcie PHP. Należy usunąć średnik sprzed następujących linii:
extension=php_gd2.dll extension=php_mysql.dll extension=php_mysqli.dll extension=php_pdo.dll extension=php_pdo_mysql.dll
Pierwszy moduł to biblioteka obsługi obrazków. Dwa następne zapewniają możliwość komunikowania się z bazą MySQL starszym skryptom PHP. Ostatnie moduły to nowa biblioteka PHP Data Objects służąca komunikacji z bazami danych skonfigurowana do działania z bazą danych MySQL. Jeżeli jakiejś linijki brakuje w twym pliku, dopisz ją.
Ostatnim krokiem jest podłączenie PHP do serwera Apache. Jeżeli zamierzamy zrobić to jako CGI, na koniec pliku konfiguracyjnego Apache dopisujemy:
ScriptAlias /php5/ "D:/Serwer/php5/" AddType application/x-httpd-php .php Action application/x-httpd-php "/php5/php-cgi.exe"
W przypadku modułu linijek jest nieco mniej:
LoadModule php5_module "D:/Serwer/php5/php5apache2_2.dll" AddType application/x-httpd-php .php
Należy jeszcze dodać jeszcze jedną linijkę:
PHPIniDir "D:/Serwer/php5"
aby wskazać lokalizację pliku php.ini ponieważ domyślna jego lokalizacja w serwerze Apache to C:\Windows. Jeśli więc tego nie zrobimy nasze moduły nie zostaną załadowane. Teraz restartujemy serwer i zabieramy się za sprawdzenie, czy wszystko przebiegło poprawnie. Umieść w katalogu (wskazanym w pliku konfiguracyjnym serwera - opis znajdziesz w rozdziale Instalacja Apache) D:/Serwer/Apache2/htdocs/ plik phpinfo.php z taką magiczną linijką:
<?php phpinfo(); ?>
W przeglądarce wpisz http://localhost/phpinfo.php - powinien pokazać Ci się bardzo długi raport nt zainstalowanej wersji PHP (wersja, konfiguracja, moduły itd.). Jeżeli zamiast tego ujrzysz wpisany wyżej kod, oznacza to, że coś zrobiłeś źle na którymś z etapów podpinania PHP do serwera.
[edytuj] Instalacja w systemie Mac OS X
Można zainstalować gotowy pakiet PHP5 za pomocą Fink i FinkCommander.
Kompilacja ze źródeł będzie wymagała instalacji dodatkowych bibliotek (dla ułatwienia można je zainstalować FinkCommanderem) oraz Developer Tools z płyty systemowej Mac OS X. Dalej kompilacja przebiega tak samo jak dla Unix/Linux.
[edytuj] Pierwszy skrypt
W tym rozdziale napiszemy pierwszy skrypt PHP.
[edytuj] Wprowadzenie
Jak wspomnieliśmy wcześniej, skrypty PHP możemy mieszać ze zwykłym kodem HTML. Kod naszych algorytmów zamykany jest wewnątrz specjalnych wstawek wyłapywanych przez interpreter oraz zmienianych później na wygenerowany tam kod. Tak też zrobimy w naszym pierwszym skrypcie, który tradycyjnie wyświetli na ekranie przeglądarki napis "Hello world!".
<?xml version="1.0" encoding="utf-8" standalone="no"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Pierwszy skrypt PHP</title> </head> <body> <?php echo 'Hello world!'; ?> </body> </html>
W powyższym przykładzie widzimy skrypt PHP osadzony za pomocą znaczników <?php oraz ?> w zwyczajnym kodzie HTML, który w następnych przykładach będziemy już pomijać, aby nie marnować miejsca. Wewnątrz mamy jedną linijkę:
echo 'Hello world!';
Nakazuje ona wyświetlenie tekstu "Hello world!" w przeglądarce. Tekst do wyświetlenia ograniczyliśmy apostrofami. Średnik na końcu informuje o zakończeniu komendy. Możemy rozbić to na kilka linijek, ale dla PHP nie będzie to miało większego znaczenia - końcem komendy jest właśnie średnik.
<?php echo 'Hello world!'; ?>
Przejście do nowej linii poza apostrofami jest jednym z tzw. białych znaków ignorowanych przez interpreter. Innymi są spacja oraz tabulacja. Między tekstem, a komendą echo możemy wstawić niezliczoną liczbę tabulatorów i zejść do nowej linii, ale nie zmieni to w żaden sposób tego, jak PHP wykona nasz skrypt, gdyż znaki te zostaną zignorowane.
W skrypcie możemy umieścić więcej wyrażeń, oczywiście odseparowanych średnikami:
<?php echo 'To jest tekst 1'; echo 'To jest tekst 2'; echo 'A to jest tekst 3'; ?>
Zauważ, że choć w skrypcie mamy trzy komendy wyświetlenia trzech tekstów, przeglądarka wyświetli je nam w jednej linijce. To dlatego, że przecież PHP nie wysłał żadnego znacznika HTML nakazującego ładne połamanie wyniku - obejrzyj sobie źródło strony w przeglądarce i przekonaj się o tym. Oto poprawiona wersja skryptu:
<?php echo 'To jest tekst 1<br/>'; echo 'To jest tekst 2<br/>'; echo 'A to jest tekst 3<br/>'; ?>
| Mieszanie kodu PHP z HTML-em spotykane jest najczęściej jedynie w prostych skryptach. W złożonych aplikacjach znacznie utrudnia jakiekolwiek modyfikacje wyglądu. W dalszych rozdziałach tego podręcznika nauczymy się korzystać z szablonów, które pomogą nam całkowicie oddzielić jedno od drugiego, lecz na razie wszystko będzie wymieszane. |
[edytuj] Komentarze
Kiedy twoje skrypty staną się bardziej rozbudowane, w kodzie przyda się pewna organizacja. Pomogą tu z pewnością komentarze służące do opisywania, co robi dana część algorytmu, jak działa, jakie ma wymagania itd. Mogą także pomóc w usystematyzowaniu całości lub też zamieszczeniu informacji o autorze oraz prawach autorskich.
Istnieją trzy rodzaje komentarzy:
<?php /* komentarz wieloliniowy może być rozbijany na wiele linijek. Cały ten tekst jest ignorowany przez interpreter */ // to jest komentarz jednoliniowy - obowiązuje do końca danej linijki # to jest jeszcze jeden komentarz jednoliniowy ?>
Oto przykładowe zastosowanie komentarzy:
<?php /* Generator miniaturek obrazków Wersja: 1.0 Autor: Adam Kowalski (adres@email.com) Licencja: GNU GPL Użycie: tu opis użycia... */ // początek generowania obrazka echo 'Tu są jakieś komendy'; echo 'Dużo komend...'; // zmniejszanie echo 'Tu obrazek się zmniejsza'; // wysyłanie wyniku echo 'Tu wysyłamy obrazek do przeglądarki'; ?>
Cały kod został opisany i dzięki temu nawet po wielu miesiącach autor będzie wiedział, co do czego służy. Komentarze przydają się przy zbiorowych pracach nad projektem. Można w nich umieszczać informacje, co należy w danym fragmencie poprawić albo jak on funkcjonuje. W procesie usuwania błędów (debugowania) komentarzy można używać do chwilowego wyłączania kawałków kodu, aby zobaczyć, czy to przypadkiem one nie powodują problemu oraz jak aplikacja radzi sobie bez nich. Stosowanie komentarzy należy do dobrych praktyk programistycznych i nigdy nie należy o nich zapominać.
[edytuj] Zmienne i tablice
W tym rozdziale dowiesz się, jak PHP przechowuje dane oraz pozwala na zarządzanie nimi.
[edytuj] Dane
PHP, jak każdy inny język programowania, operuje na danych. Niektóre z nich są zapisane na sztywno w skrypcie. Niemniej każda rzecz, która reprezentuje jakąkolwiek informację, zwana jest wyrażeniem. Oto prosty przykład:
8
To jest wyrażenie liczbowe reprezentujące liczbę całkowitą.
6.454
To jest wyrażenie liczbowe reprezentujące ułamek, czyli liczbę zmiennoprzecinkową.
0x6F44
To jest wyrażenie reprezentujące liczbę zapisane w systemie szesnastkowym.
07647
To jest wyrażenie reprezentujące liczbę zapisane w systemie ósemkowym.
'To jest tekst bez znaków specjalnych' "To też jest tekst, ale ze znakami specjalnymi"
Powyżej mamy dwa wyrażenia reprezentujące tekst. Pomiędzy nimi istnieje istotna różnica. Apostrofy korzystają z jednego tylko znaku specjalnego: \' - pozwala on oznaczyć wewnątrz tekstu znak apostrofu. Gdybyśmy zapomnieli o poprzedzającym backslashu, PHP uznałby, że w tym momencie kończy się wyrażenie tekstowe i dalej jest już normalny skrypt.
Cudzysłów posiada więcej takich kodów formatujących:
"\n - tak robimy zejście do nowej linijki w systemach Linux" "\r - tak w systemach Mac" "\r\n - a tak w systemach operacyjnych Windows" "Wyświetlimy cudzysłów: \" ..." "Wstawimy tabulator: \t" "Wstawiamy zmienną $zmienna"
W obu przypadkach, aby wyświetlić backslash, należy napisać go podwójnie:
'Oto backslash: \\ '
Otrzymamy dzięki temu tekst:
Oto backslash: \
Oto bardziej skomplikowane wyrażenie:
5 + 7
Reprezentuje ono sumę dwóch mniejszych wyrażeń.
'Tekst A '.'Tekst B'
Oto wyrażenie będące połączeniem dwóch mniejszych wyrażeń tekstowych. Znaki kropka oraz plus to tzw. operatory. Wykonują one pewne operacje na dwóch innych wyrażeniach i zwracają jakąś wartość, zatem same także stają się wyrażeniem. Podobnie, jak w matematyce, obowiązuje pewna kolejność ich wykonywania, a zmieniać ją możemy za pomocą nawiasów:
5 * (6 + 8)
| Wyrażeniem nazywamy dowolny element języka PHP reprezentujący jakąś informację, którą można przetwarzać. |
Pamiętaj, że PHP potrafi automatycznie rozpoznawać typ wyrażenia i w razie potrzeby odpowiednio go konwertować. Dla przykładu instrukcja echo wymaga danych tekstowych. W skrypcie:
<?php echo 7; ?>
Interpreter dokona konwersji liczby na tekst, a dopiero potem spowoduje jego wyświetlenie.
[edytuj] Funkcje
Informatyka wiele zawdzięcza matematyce. W programowaniu występuje wiele pojęć zaczerpniętych bezpośrednio od królowej nauk. Jednym z nich jest funkcja, do której możemy wprowadzać parametry, a w zamian otrzymujemy jakiś wynik. Poniższy skrypt będzie pierwszym "naprawdę" dynamicznym, jaki stworzymy. Skorzystamy w nim z dwóch funkcji, aby wyświetlić aktualny czas:
<?php
echo 'Dzisiaj mamy: '.date('d.m.Y').'<br/>';
echo 'Od 1.1.1970 minęło: '.time().' sekund';
?>
Jak widać, składnia funkcji jest następująca: nazwaFunkcji(parametry). Jeśli funkcja nie posiada parametrów, nawiasy są puste. Jeżeli jest ich więcej, niż jeden, oddzielamy je od siebie przecinkiem. Zaś sam parametr jest niczym innym, jak... wyrażeniem. Jest nim także funkcja, dlatego możemy ją wpleść w nasz tekst za pomocą operatora kropki.
| Uwaga! echo oraz jego synonim print nie są funkcjami ani wyrażeniami! Są to instrukcje języka PHP i to tłumaczy, dlaczego nie musimy stosować przy nich nawiasów. |
[edytuj] Zmienne
Innym pojęciem matematycznym jest zmienna reprezentująca informację nieznaną w trakcie pisania skryptu. Jest to taki pojemnik, do którego możemy w trakcie wykonywania skryptu wstawiać wszelkie informacje, zapamiętując je w ten sposób. Bez zmiennych nie można mówić o jakimkolwiek przetwarzaniu danych, ponieważ komputer musi mieć jakąś możliwość umieszczania gdzieś wyników obliczeń oraz przechowywania niezbędnych programom danych.
Każda zmienna posiada własną, unikalną nazwę, która jednoznacznie ją identyfikuje. Język PHP wymaga, aby zaczynała się ona od znaku dolara, a następnie od litery lub podkreślenia. Dalsze znaki mogą być także cyframi. Można stosować duże litery, ale dla interpretera ma to istotne znaczenie. $zmienna i $Zmienna to dwie różne zmienne. Przykłady prawidłowych nazw zmiennych:
$a, $b, $foo, $_50, $_foo, $moja_zmienna, $mojaZmienna3
Przykłady nieprawidłowych nazw:
$5, $
| PHP zezwala na używanie w nazwach zmiennych także znaków o kodach od 128 do 255, wśród których znajdują się m.in. polskie litery. |
Aby przypisać wartość do zmiennej, należy ułożyć wyrażenie z operatorem =. Po lewej stronie umieszczamy naszą zmienną, a po prawej inne wyrażenie określające wstawianą wartość. Oto, jak wygląda to w praktyce:
<?php // inicjujemy zmienna $czas aktualnym czasem w sekundach od 1.1.1970 $czas = time(); echo 'Od 1.1.1970 minęło '.$czas.' sekund<br/>'; echo 'Od 1.1.1970 minęło '.($czas / 60).' minut<br/>'; echo 'Od 1.1.1970 minęło '.($czas / 3600).' godzin'; ?>
W powyższym przykładzie stworzyliśmy zmienną $czas, wprowadzając do niej aktualny czas. Następnie wykorzystaliśmy ją w obliczeniach. Poznaliśmy w ten sposób jedno z zastosowań zmiennych. Zachowaliśmy w nich wynik działania jednej sekcji programu, aby potem używać go wielokrotnie gdzie indziej bez konieczności każdorazowego odwoływania się do funkcji i zbędnego liczenia po sto razy tego samego. Wprawdzie pobieranie aktualnego czasu nie jest czasochłonną operacją, ale na razie jesteśmy jeszcze na początkowym etapie nauki i musimy dbać o prostotę przykładów.
PHP, w przeciwieństwie do innych języków programowania, ma bardzo liberalne reguły stosowania zmiennych. W ogóle nie trzeba ich nigdzie deklarować, a interpreter sam na bieżąco dopasuje typ informacji do naszych potrzeb. Dana zmienna jest tworzona podczas pierwszego jej wykorzystania w skrypcie. Jest sporo sytuacji, w których zachowanie to jest pożądane, lecz normalnie może utrudnić ono pracę. Wyobraź sobie taką sytuację: programista potrafi najczęściej bardzo szybko pisać i od czasu do czasu zdarza mu się wcisnąć dwa klawisze w złej kolejności. Powstaje literówka. Jeżeli zostanie ona popełniona podczas wpisywania nazwy, PHP w teorii nie zgłosi tego jako błędu i programista będzie musiał spędzić dużo czasu na jej odnalezienie. Słowo "teoria" nie znalazło się tu przypadkowo. Podczas instalowania PHP wspominaliśmy o poziomach raportowania błędów. Im wyższy poziom, tym większej ilości rzeczy czepia się PHP. Na poziomie E_ALL zdefiniowanym w rekomendowanym pliku php.ini takie beztroskie podejście do zmiennych nie jest tolerowane. Tutaj PHP wymaga już, aby podczas pierwszego użycia zmiennej została jej przypisana jakaś wartość, ponieważ inaczej otrzymamy powiadomienie (ang. notice) o próbie odwołania się do nieistniejącej zmiennej. Popatrzmy sobie na ten przykład:
<?php echo $a * 16 + 5; ?>
Zmienna $a nie została tu wcześniej zadeklarowana. Otwórz swój plik php.ini, odnajdź dyrektywę error_reporting i zmień jej wartość na E_ALL | ~E_NOTICE. Wyłączysz w ten sposób wyświetlanie powiadomień. Zrestartuj serwer i uruchom powyższy skrypt. Wynikiem powinno być "5". PHP bez pytania podstawił do $a wartość neutralną 0. Przywróć teraz poprzedni poziom (E_ALL | E_STRICT) i ponownie uruchom ten skrypt. Oprócz wyniku, ujrzysz też komunikat:
Notice: Undefined variable: a in D:\Serwer\www\katalog\twojskrypt.php on line 3
Sytuację można rozwiązać, ręcznie inicjując zmienną $a wartością 0:
<?php $a = 0; echo $a * 16 + 5; ?>
Zalecane jest, aby wszystkie skrypty pracowały bezproblemowo przy włączonych powiadomieniach. Jeżeli zajdzie sytuacja, że odwołanie się do zmiennej, która może jeszcze nie istnieć, jest potrzebne, istnieje kilka sposobów "oszukania" PHP, lecz poznamy je w dalszej części podręcznika.
Początkujący programiści mają tendencję do tworzenia dużej liczby tzw. zmiennych tymczasowych, które nie wnoszą absolutnie niczego do programu poza wydłużeniem kodu i zmniejszeniem wydajności. Po każdym etapie przetworzenia jakiejś informacji, umieszczana jest ona w nowej zmiennej. Takie podejście jest nieprawidłowe. Oto przykłady "złych" skryptów:
<?php $tekst = 'To jest jakiś tekst'; $tekstMaly = strtolower($tekst); $tekstBezpieczny = addslashes($tekstMaly); echo $tekstBezpieczny; ?>
<?php $format = 'd.m.Y'; echo date($format); ?>
W pierwszym skrypcie niepotrzebnie po każdym etapie przetwarzania tekstu tworzymy dla niego nową zmienną. Możemy to poprawić na dwa sposoby. Pierwszy:
<?php $tekst = 'To jest jakiś tekst'; $tekst = strtolower($tekst); $tekst = addslashes($tekst); echo $tekst; ?>
Drugi, w którym w ogóle opuszczamy zmienne:
<?php
echo addslashes(strtolower('To jest jakiś tekst'));
?>
W drugim "złym" skrypcie w ogóle niepotrzebnie tworzymy zmienną; przecież format daty możemy wpisać bezpośrednio do funkcji.
<?php
echo date('d.m.Y');
?>
W tym jednak przypadku może być to uznane za kwestię dyskusyjną. Jeżeli nasz skrypt bardzo często będzie formatować różne daty, a my będziemy chcieli mieć możliwość jej prostego konfigurowania, warto pokusić się o zapisanie formatu w jakiejś zmiennej. W ten sposób poprzez zmianę jej wartości w jednym miejscu zostanie ona uwzględniona w całym skrypcie.
Nauczenie się, kiedy warto użyć zmiennych, a kiedy nie, to kwestia praktyki. W niniejszym podręczniku będziemy zwracali na tę kwestię baczną uwagę. Jeśli zajdzie potrzeba użycia zmiennych tymczasowych - wyjaśnimy, dlaczego, bowiem całkowite rezygnowanie z ich użycia także może rodzić wiele problemów.
[edytuj] Typy zmiennych
W sekcji poświęconej rodzajom danych w PHP dowiedziałeś się, że istnieje pewne rozróżnienie na tekst i liczby. Skoncentrujemy się teraz na poznaniu większej ilości typów oraz pokazaniu, jak PHP dokonuje konwersji między nimi.
Istnieją trzy kategorie typów: wielkości skalarne, typy złożone oraz typy specjalne. Dokumentacja wymienia jeszcze jedną, lecz stworzoną na jej własne potrzeby do zaznaczania niektórych rzeczy. Powiemy o niej później.
[edytuj] Wielkości skalarne
Pierwszym typem skalarnym jest liczba całkowita. Jej angielskim określeniem jest integer albo int. Może być ona zapisana w trzech systemach liczbowych: dziesiętnym, szesnastkowym albo ósemkowym:
<?php $a = 1234; // liczba całkowita $a = -123; // liczba całkowita ujemna $a = 0123; // zapis ósemkowy (odpowiednik dziesiętnego 83) $a = 0x1A; // zapis szesnastkowy (odpowiednik dziesiętnego 26) ?>
Możemy także korzystać z wartości ułamkowych zwanych liczbami zmiennoprzecinkowymi (ang. floating point numbers albo skrótowo float). Przy ich zapisywaniu obowiązują reguły języka angielskiego, więc części całkowite od ułamkowych oddzielamy za pomocą kropki. Także i tu mamy do wyboru kilka sposobów zapisu:
<?php $a = 1.234; $a = 1.2e3; $a = 7E-10; ?>
Specjalnie wyróżniony został tzw. typ logiczny (boolean) przyjmujący jedynie wartości FALSE i TRUE. Jest on bardzo ważny, ponieważ wiele funkcji generuje właśnie w nim rezultat, czy operacja się powiodła. Wyrażenia porównawcze także generują wartości logiczne.
Ostatnim z typów skalarnych jest ciąg tekstowy (ang. string). Zdążyliśmy już powiedzieć nieco o nim, np. że istnieją dwie składnie zapisywania ciągów. Ta oparta na apostrofach posiada minimalny zestaw kodów formatujących pozwalających na wstawienie do tekstu innych apostrofów oraz ukośników wstecznych:
<?php
echo 'To jest tekst zapisany w apostrofach. Kody formatujące pozwalają
umieścić w tekście wyłącznie inne apostrofy: \' albo backslashe: \\. Wszystko inne,
np. \n zostanie wyświetlone jako zwyczajny tekst';
?>
Uruchom powyższy skrypt i porównaj sobie wynik z przeglądarki z treścią wpisaną w apostrofy. Spróbuj usunąć backslash sprzed apostrofu i zobacz, co się stanie. Skrypt się nie uruchomi, ponieważ wystąpił błąd składni. PHP myśli, że tekst kończy się już tutaj i dalej jest inna część algorytmu, co nie jest oczywiście prawdą. Więcej możliwości formatowania posiada tekst ograniczony cudzysłowami:
<?php
echo "To jest tekst zapisany w cudzysłowach. Za pomocą kodów formatujących możemy
umieszczać wiele rzeczy: \" \\ \n \r \$";
?>
Cudzysłowy zezwalają na "proste" umieszczanie w nich wartości zmiennych (zm. tymczasowa stworzona dla zilustrowania zagadnienia):
<?php $czas = time(); echo "Aktualny czas w sekundach: $czas sek."; ?>
Jednak nie nadużywaj tego. Wstawianie zmiennych w ten sposób jest kilka razy wolniejsze, niż łączenie ich z ciągiem operatorem kropki.
<?php // mozna takze użyć apostrof echo "Aktualny czas w sekundach: ".time()." sek."; ?>
Niektórzy początkujący programiści niezbyt rozumieją ideę tej możliwości i próbują wykorzystywać ciągi do wprowadzania wartości zmiennych jako parametrów do funkcji (zm. tymczasowa użyta dla zilustrowania zagadnienia):
<?php
$formatDaty = 'd.m.Y';
echo date("$formatDaty");
?>
Unikaj takiej konstrukcji, jak ognia. Choć PHP ją akceptuje, nie jest to prawidłowe użycie tej struktury języka. Więcej... przy złożonych typach powoduje zniekształcenie danych. Jeżeli spotkasz kogoś piszącego w ten sposób, poinformuj go o tym.
[edytuj] Inne typy
Typami złożonymi są w PHP tablice oraz obiekty. Tablice poznamy jeszcze w tym rozdziale, natomiast obiektami oraz samym programowaniem obiektowym zajmiemy się w dalszej części podręcznika.
Istnieją jeszcze dwa typy specjalne: resource oraz NULL. Pierwszy z nich reprezentuje wszelkiego rodzaju połączenia z bazami danych, otwarte przez PHP pliki itd. Drugi to wartość pusta. Za jego pomocą możemy "zasymulować", że zmienna nie istnieje lub nie zawiera wartości. Pojawia się w trzech sytuacjach:
- Do zmiennej przypisana została stała NULL.
- Do zmiennej nie została przypisana jeszcze żadna wartość (zgłaszane jest wtedy powiadomienie)
- Zmienna została zniszczona poleceniem unset().
Przyjrzyjmy się wspomnianemu w trzecim punkcie poleceniu. Czasem jakąś zmienną trzeba zniszczyć. Najlepiej nadaje się do tego polecenie unset():
<?php $zmienna = 4856; unset($zmienna); echo $zmienna; ?>
Zmiennej już nie ma, dlatego polecenie echo nie pokaże nic.
[edytuj] Konwersja typów
PHP potrafi sam rozpoznać typ informacji przypisanej do zmiennej oraz automatycznie konwertować go w zależności od potrzeb. Przykładowo liczby ułamkowe użyte tam, gdzie potrzeba całkowitych, są zaokrąglane w górę do zera. Wartości logiczne mogą być reprezentowane cyframi 0 (FALSE) oraz 1 (TRUE). Ciągi tekstowe mogą być konwertowane do liczb, jeżeli pierwszy z ich (pomijając wiodące białe znaki) znaków jest cyfrą. W przeciwnym przypadku PHP dobiera wartość 0.
Możemy sami wymusić konwersję typów:
<?php // wyświetl liczbę całkowitą jako ułamek echo (float) 10; ?>
W nawiasie przed wartością piszemy angielską nazwę typu: integer, int, string, boolean, float, double (liczba zmiennoprzecinkowa mogąca przybierać znacznie większe wartości). Unikaj jakiejkolwiek konwersji typów złożonych: tablic, obiektów, zasobów, gdyż w każdym z tych przypadków informacje zostają całkowicie utracone; zamiast nich zwracana jest po prostu nazwa typu złożonego - to dlatego ostrzegaliśmy przed wprowadzaniem wartości zmiennych do funkcji przez cudzysłowy.
PHP posiada funkcję gettype() zwracającą nazwę aktualnego typu danych zawartych w zmiennej:
<?php $a = 547; echo 'Typ zmiennej $a to: '.gettype($a); ?>
[edytuj] Więcej o operatorach
Dotychczas poznałeś już kilka operatorów, np. + czy =. Jak zdążyłeś już zauważyć, po obu stronach takiego operatora stoją wyrażenia, na których wykonuje on operacje i zwraca wynik. Sam jest zatem wyrażeniem. Operatory mają określoną kolejność wykonywania wzorowaną na matematyce. Możemy ją naginać do własnych potrzeb, stosując nawiasy.
Oto wykaz najciekawszych operatorów na początek.
| Operator | Nazwa | Składnia | Opis |
|---|---|---|---|
| / | Dzielenie | wyrażenie / wyrażenie | Reprezentuje wynik dzielenia. Drugie wyrażenie nie może być zerem. |
| % | Dzielenie modulo | wyrażenie % wyrażenie | Reprezentuje resztę z dzielenia. Drugie wyrażenie nie może być zerem. |
| * | Mnożenie | wyrażenie * wyrażenie | Reprezentuje iloczyn dwóch wyrażeń. |
| + | Dodawanie | wyrażenie + wyrażenie | Reprezentuje sumę dwóch wyrażeń. |
| - | Odejmowanie | wyrażenie - wyrażenie | Reprezentuje różnicę dwóch wyrażeń. |
| . | Łączenie (Konkatenacja) | wyrażenie . wyrażenie | Reprezentuje połączenie dwóch wyrażeń w ciąg tekstowy. |
| ++ | Inkrementacja (zwiększenie) | $zmienna++ | Reprezentuje wartość zmiennej, a następnie zwiększa ją o 1. |
| ++ | Inkrementacja (zwiększenie) | ++$zmienna | Reprezentuje wartość zmiennej powiększoną o 1. |
| -- | Dekrementacja (zmniejszenie) | $zmienna-- | Reprezentuje wartość zmiennej, a następnie zmniejsza ją o 1. |
| -- | Dekrementacja (zmniejszenie) | --$zmienna | Reprezentuje wartość zmiennej pomniejszoną o 1. |
Zwróć uwagę na cztery ostatnie pozycje. Wyszczególnione zostały tam tzw. operatory jednoargumentowe, czyli takie, które operują wyłącznie na jednym wyrażeniu. W dodatku koniecznie musi być ono zmienną, ponieważ modyfikują one jej wartość, powiększając lub zmniejszając o jeden. Każdy z tych operatorów został podany podwójnie, ponieważ w zależności od tego, czy postawimy go przed, czy po zmiennej, otrzymamy nieco inne rezultaty. Porównaj:
<?php // najpierw składnia $zmienna++ $zmienna = 5; echo 'Stan 1: '.($zmienna++).'<br/>'; echo 'Stan 2: '.$zmienna.'<br/><br/>'; // teraz składnia ++$zmienna echo 'Restart zmiennej...<br/>'; $zmienna = 5; echo 'Stan 1: '.(++$zmienna).'<br/>'; echo 'Stan 2: '.$zmienna.'<br/>'; ?>
Skrypt ten wygeneruje nam kilka linijek:
Stan 1: 5 Stan 2: 6 Restart zmiennej... Stan 1: 6 Stan 2: 6
Przeanalizuj wyniki działania. Okazuje się, że w składni $zmienna++ najpierw dostajemy wartość zmiennej, a dopiero potem zwiększamy ją o jeden (dlatego zmiana widoczna jest dopiero w drugim stanie). ++$zmienna najpierw powiększa, potem zwraca, w efekcie czego otrzymujemy w obu stanach liczbę "6". Identyczna zasada obowiązuje operator --.
Zajmijmy się teraz przypisywaniem danych do zmiennej. Wiemy już, że operator przypisania po lewej stronie wymaga zmiennej, po prawej wyrażenia, którego wartość trzeba w niej umieścić. Skoro operator, jest to też wyrażenie, lecz jaką wartość ono reprezentuje. Okazuje się, że tę, która jest przypisywana. Możemy wobec tego zastosować sprytną sztuczkę, o której wbrew pozorom wie niezbyt wielu programistów. Zainicjujmy pięć zmiennych naraz tą samą wartością:
<?php $a = $b = $c = $d = $e = 5; ?>
PHP najpierw przypisze "5" do zmiennej $e, zwracając jednocześnie "5" tak, by mogło być ono przypisane do $d, potem do $c, $b i na końcu $a. W ten sposób jednym wielkim wyrażeniem zainicjowaliśmy pięć zmiennych naraz.
Poznany już operator przypisania nie jest jedynym, jaki istnieje w PHP. Aby ułatwić modyfikację wartości zmiennych o liczby inne niż jeden, stworzono całą gamę operatorów łączących w sobie przypisywanie oraz jakąś operację matematyczną. Oto i one.
| Operator | Składnia | Równoważna postać | Opis |
|---|---|---|---|
| /= | $zmienna /= wyrazenie | $zmienna = $zmienna / wyrażenie | Dzieli zmienną przez wyrażenie i umieszcza w niej wynik. Wyrażenie nie może być zerem. |
| %= | $zmienna %= wyrazenie | $zmienna = $zmienna % wyrażenie | Umieszcza w zmiennej resztę z dzielenia tej zmiennej przez wyrażenie, które oczywiście nie może być zerem. |
| *= | $zmienna *= wyrazenie | $zmienna = $zmienna * wyrażenie | Mnoży zmienną przez wyrażenie i zapisuje w niej wynik. |
| += | $zmienna += wyrazenie | $zmienna = $zmienna + wyrażenie | Dodaje do zmiennej wyrażenie i zapisuje w niej wynik. |
| -= | $zmienna -= wyrazenie | $zmienna = $zmienna - wyrażenie | Odejmuje od zmiennej wyrażenie i zapisuje w niej wynik. |
| .= | $zmienna .= wyrazenie | $zmienna = $zmienna . wyrażenie | Dołącza do zmiennej tekstowej nowy fragment. |
Te operatory po prostu skracają zapis i czynią go czytelniejszym. Warto o nich pamiętać szczególnie przy operacjach na ciągach tekstu, kiedy nasz algorytm składa jakąś treść, doczepiając kolejne jej partie do wybranej zmiennej:
<?php $tekst = 'Litwo, ojczyzno moja! Ty jesteś jak zdrowie<br/>'; $tekst .= 'Ile Cię trzeba cenić, ten tylko się dowie<br/>'; $tekst .= 'Kto Cię stracił, dziś piękność twą w całej ozdobie<br/>'; $tekst .= 'Widzę i opisuję, bo tęsknię po tobie.<br/>'; echo $tekst; ?>
Rezultat:
Litwo, ojczyzno moja! Ty jesteś jak zdrowie Ile Cię trzeba cenić, ten tylko się dowie Kto Cię stracił, dziś piękność twą w całej ozdobie Widzę i opisuję, bo tęsknię po tobie.
Kolejne partie tekstu były doklejane do właściwej zmiennej tak, że na końcu otrzymaliśmy spójny i kompletny tekst. Identycznie jest z pozostałymi operatorami. += dodaje wartość do zmiennej, -= odejmuje itd.
Na tym zakończymy na razie temat operatorów, lecz to jeszcze nie wszystko. Już niedługo zapoznamy się z operatorami służącymi do porównywania danych oraz podstawami operatorów logicznych. Będą nam one potrzebne przy omawianiu instrukcji warunkowych i pętli.
[edytuj] Tablice
Zaznajomimy się teraz z tablicami, pierwszym złożonym typem danych. Cofnijmy się do czasów szkoły podstawowej/gimnazjum na lekcję matematyki i przypomnijmy nasz pierwszy kontakt z pojęciem funkcji. Była tam mowa, że funkcję można przedstawiać w postaci tabelki:
| x | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|---|
| y | 5 | 3 | 8 | 7 | 9 | 24 | 15 | 2 | 19 |
Spróbujmy przenieść taką tabelkę w świat programowania. Widzimy, że wszystkie "igreki" są ze sobą powiązane, ponieważ odgrywają podobne role bycia wynikami funkcji. Gdyby to zapisać jako zupełnie osobne zmienne, mielibyśmy problem z późniejszym dostaniem się do nich. Popatrz na to w ten sposób: użytkownik wprowadza argument, a my mamy dla niego odnaleźć wartość spośród tych podanych. Skąd PHP może wiedzieć, że akurat ta grupa zmiennych jest ze sobą w jakiś sposób powiązania, a tym bardziej znać regułę wybierania "tej właściwej"? Na razie nie jest to w ogóle możliwe. Co nam pozostaje? Zapisać wszystkie wartości w strukturze zwanej tablicą.
| Tablica to zmienna zbiorcza, grupująca pojedyncze elementy mające właściwości zmiennych, do których odwoływać możemy się za pomocą indeksów. |
Wiemy już, że tablica jest zmienną, która grupuje sobie mniejsze zmienne opisywane przez ich indeksy (wartości x w naszej tabelce funkcji). Najważniejsze jest jednak to, że przy odwoływaniu się do nich, potrzebny nam indeks możemy określić zwyczajnym wyrażeniem! To oznacza, że zadanie wymienione akapit wyżej staje się realne. Zanim je zaprogramujemy, nieco informacji o składni tablic. Na początek utwórzmy pustą tablicę:
<?php $tablica = array(); ?>
Spróbujmy wprowadzić do niej wartości naszej funkcji:
<?php $tablica = array(0 => 5, 3, 8, 7, 9, 24, 15, 2, 19); ?>
Początkowe "0" określa, od jakiej liczby zacząć numerowanie kolejnych elementów. Po strzałce wymieniamy ich wartości oddzielone przecinkiem. Teraz spróbujmy dostać się do jakiejś z nich.
<?php $tablica = array(0 => 5, 3, 8, 7, 9, 24, 15, 2, 19); echo 'Pod numerem 5 kryje się wartość '.$tablica[5]; ?>
Pomiędzy nawiasami kwadratowymi wprowadzić musimy wyrażenie określające indeks tablicy, który chcielibyśmy odczytać. Możemy teraz pokusić się o napisanie skryptu losującego elementy:
<?php $tablica = array(0 => 5, 3, 8, 7, 9, 24, 15, 2, 19); echo 'Pod numerem 5 kryje się wartość '.$tablica[rand(0, 8)]; ?>
W tym skrypcie za wybranie indeksu tablicy odpowiada funkcja rand() zwracająca losową[1]wartość z tablicy.
Indeksy tablicy wcale nie muszą być numeryczne. PHP dopuszcza także tekstowe wersje. Mamy wtedy do czynienia z tablicami asocjacyjnymi.
<?php
$artykul = array(
'tytul' => 'Tytuł artykułu',
'data' => date('d.m.Y'),
'tresc' => 'To jest treść artykułu'
);
echo '<h1>'.$artykul['tytul'].'</h1>';
echo '<p>Napisany dnia '.$artykul['data'].'</p>';
echo '<p>'.$artykul['tresc'].'</p>';
?>
Mogą istnieć także tablice mieszane, w których występują zarówno indeksy tekstowe, jak i numeryczne. Pamiętaj, że każdy element tablicy zachowuje się, jak zwykła zmienna, dlatego także możesz przypisywać do niego dowolne wartości już po utworzeniu tablicy.
<?php $tablica = array(0 => 5, 3, 8, 7, 9, 24, 15, 2, 19); // modyfikuj losowy element tablicy $tablica[rand(0, 8)] = 6; echo '<pre>'; var_dump($tablica); echo '</pre>'; ?>
Najpierw przypisaliśmy do losowego elementu tablicy nową wartość: 6. Całość wyświetlamy funkcją var_dump(). Przydaje się ona przy poszukiwaniu błędów w skrypcie. Potrafi zaprezentować w czytelnej formie każdy typ danych, więc możemy za jej pomocą kontrolować, czy na danym etapie wykonywania rezultaty są takie, jakie być powinny. Podobne działanie ma funkcja print_r().
Zobacz teraz, jak zachowuje się $tablica, kiedy próbujemy ją wywołać samodzielnie:
<?php $tablica = array(0 => 5, 3, 8, 7, 9, 24, 15, 2, 19); echo $tablica; ?>
Skrypt ten pokaże nam tylko jeden napis: Array, czyli... nazwę typu. Właśnie z tego powodu ostrzegaliśmy przed wprowadzaniem danych do funkcji jako funkcja("$zmienna");. Gdyby w zmiennej była tablica, do funkcji zamiast niej dotarłby napis Array i całość przestałaby działać. Osobną kwestią jest wydajność takiego zmuszania do konwersji do tekstu, a potem z powrotem na tekst właściwy.
Upewnij się, że rozumiesz już istotę działania tablic, gdyż bardzo przydadzą się nam one w następnym rozdziale.
[edytuj] Przypisy
- 1 Tak naprawdę komputer nie potrafi losować liczb. Za całą tą zasłonką kryją się różne skomplikowane wzory matematyczne inicjowane najczęściej aktualnym czasem, dające wrażenie losowości wyników.
[edytuj] Formularze
W tym rozdziale zajmiemy się podstawami komunikacji skryptu PHP z przeglądarką.
[edytuj] Ogólnie o danych wejściowych
Interaktywne aplikacje PHP odbierają od użytkownika setki informacji. Pozwalają one nie tylko zorientować się, kto nas odwiedził, ale także odczytywać zawartość formularzy oraz adresów URL. Mechanizm ich odbierania ewoluował stopniowo. Pierwsze wersje rejestrowały wszystkie nadesłane parametry jako zwyczajne zmienne, lecz począwszy od PHP 4 zarzucono to, gdyż zagrażało bezpieczeństwu wielu skryptów. Dodając nowe parametry można było rejestrować nowe zmienne, które mogły nadpisać zmienne używane w kodzie aplikacji powodując jego błędne działanie (np. zyskanie praw administratora strony przez atakującego). Programista nie mógł także policzyć, ile danych w ogóle do niego trafiło i w jakich zmiennych są zawarte.
Obecnie wszystkie pola są rejestrowane w specjalnych, tworzonych przez skrypt, tablicach asocjacyjnych posegregowane według miejsca, z którego nadeszły. PHP potrafi odbierać informacje:
- Metodą GET (parametry w adresach URL)
- Metodą POST (formularze)
- z serwera
- z ciasteczek
- z sesji (emulowanych przez PHP)
Tutaj zajmiemy się trzema pierwszymi pozycjami. Ciastka oraz sesje zostaną omówione w dalszych rozdziałach podręcznika.
[edytuj] Adresy URL
Dane pochodzące z adresu URL przechowywane są w tablicy $_GET.
Tę metodę należy używać tylko, gdy skrypt nie wykonuje operacji mających efekty uboczne (np. może być wyszukiwanie, ale już nie dodawanie lub usuwanie rekordów). W przeciwnym wypadku roboty indeksujące stronę i proxy ładujące strony z wyprzedzeniem mogą niechcący wykonywać operacje na serwerze. Z drugiej strony cache przeglądarki i dostawców internetowych może spowodować zignorowanie zapytań.
Możemy prosto obejrzeć sobie jej konstrukcję:
<?php var_dump($_GET); ?>
Wywołując skrypt normalnie: http://localhost/~programowanie_php/nazwaskryptu.php otrzymamy następujący rezultat:
array(0) { }
Oznacza to, że nie otrzymaliśmy tą metodą żadnych danych. Dodajmy teraz do adresu ciąg ?parametr=wartosc. Po odświeżeniu zobaczymy:
array(1) { ["parametr"]=> string(7) "wartosc" }
Tablica zawiera jeden element o nazwie parametr przechowujący wpisaną w adresie wartość.
Napiszemy teraz prosty skrypt wyświetlający informacje powitalne na podstawie danych z adresu:
<?php
if(count($_GET) == 2)
{
echo 'Witaj, '.$_GET['imie'].' '.$_GET['nazwisko'].'!';
}
else
{
echo 'Nieprawidłowa liczba parametrów!';
}
?>
Nie przejmuj się istnieniem konstrukcji, której jeszcze nie poznaliśmy. Niektórzy pewnie domyślili się, co ona robi, ale szczegóły będą podane już w następnym rozdziale. Na razie wpiszmy ją tak, jak jest. Funkcja count() pojawiająca się w kodzie zwraca ilość elementów w tablicy. Sprawdzamy w ten sposób, czy użytkownik podał to, co trzeba. Kontrola nadchodzących danych jest niezwykle istotna i nigdy nie wolno jej zlekceważyć. Pominięcie tego aspektu zazwyczaj kończy się dla skryptu tragicznie, bo jeżeli coś jest do zepsucia, na pewno znajdzie się ktoś, kto tego dokona.
Wywołując skrypt z parametrami "imie" oraz "nazwisko" możemy wpływać na wyświetlane informacje: http://localhost/~programowanie_php/nazwaskryptu.php?imie=Adam&nazwisko=Kowalski. Dla lepszego efektu stwórzmy prosty formularz XHTML wysyłający dane metodą GET:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Formularz HTML</title>
</head>
<body>
<form method="get" action="nazwaskryptu.php">
<p>
<label>Podaj imię: <input type="text" name="imie"/></label>
</p>
<p>
<label>Podaj nazwisko: <input type="text" name="nazwisko"/></label>
</p>
<p>
<input type="submit" value="OK"/>
</p>
</form>
</body>
</html>
Choć tworzenie formularzy teoretycznie pasuje do następnej sekcji, tak naprawdę w tym momencie zwyczajnie oszukujemy. Wypełnij ten formularz i wyślij go, a zobaczysz, że przeglądarka dokleiła do pliku podanego w znaczniku FORM parametry utworzone na podstawie pól! Tak więc nasz skrypt nawet nie ma możliwości stwierdzenia, skąd dane do niego przyszły! Poznajmy zatem metodę POST...
[edytuj] Formularze
Obsługa formularzy z prawdziwego zdarzenia, którymi można przesyłać setki informacji, odbywa się dosyć podobnie, jak adresów. Różnica jest taka, że wszystko wysyła się wyłącznie z formularza, który posiada parametr "method" ustawiony na "post" oraz że korzysta się z tablicy $_POST wewnątrz samego skryptu. Przeróbmy nasze ostatnie dzieło tak, aby pracowało w ten sposób.
<?php
if(count($_POST) == 2)
{
echo 'Witaj, '.$_POST['imie'].' '.$_POST['nazwisko'].'!';
}
else
{
echo 'Nieprawidłowa liczba parametrów!';
}
?>
W skrypcie podmieniamy jedynie nazwy tablic na $_POST. W formularzu musimy jeszcze zmienić metodę:
<html>
<head>
<title>Formularz HTML</title>
</head>
<body>
<form method="post" action="nazwaskryptu.php">
Podaj imię: <input type="text" name="imie"/><br/>
Podaj nazwisko: <input type="text" name="nazwisko"/><br/>
<input type="submit" value="OK"/>
</form>
</body>
</html>
I gotowe. Wyślij teraz formularz. Zauważ, że żadna informacja nie jest doklejana do adresu, ponieważ transmisja odbywa się niejako innym kanałem.
Z kursów języka HTML wiadomo, że istnieją różne typy pól formularzy. Oto, jakie wartości otrzymuje od nich PHP:
-
<input type="text" name="nazwa"/>
- skrypt otrzymuje $_POST['nazwa'] z wartością wpisaną w pole formularza. -
<input type="hidden" name="nazwa"/>
- skrypt otrzymuje $_POST['nazwa'] z wartością wpisaną w danym znaczniku. Użyteczne do przesyłania formularzem ukrytych informacji, o których typowy użytkownik wiedzieć nie musi. -
<input type="radio" name="nazwa" value="wartosc"/>
- pozycje należące do tej samej grupy muszą mieć identyczną nazwę. '$_POST['nazwa'] będzie zawierać wartość tej pozycji, która jest aktualnie zaznaczona. -
<input type="checkbox" name="nazwa"/>
- jeśli pole jest zaznaczone, $_POST['nazwa'] zawierać będzie słowo "on". -
<select name="nazwa">...</select>
- $_POST['nazwa'] zawierać będzie wartość wybranego z listy elementu. -
<input type="submit" name="nazwa"/>
- zmienna $_POST['nazwa'] zostanie utworzona, jeżeli akurat ten przycisk zostanie wciśnięty.
Dzięki temu można do formularzy wstawiać kilka przycisków "submit" i reagować inaczej w zależności od tego, który z nich został naciśnięty.
[edytuj] Serwer
Na sam koniec zostawiliśmy sobie kilka informacji przekazywanych interpreterowi przez serwer WWW (np. Apache). Zacznijmy od określenia adresu IP gościa:
<?php echo 'Witaj, twój adres IP to '.$_SERVER['REMOTE_ADDR'].'!'; ?>
Ten skrypt pokaże nam adres IP komputera lub sieci, w której znajduje się internauta. W tym drugim przypadku dalsze informacje są niepewne. Serwerowi potrzebny jest wyłącznie ten adres, aby móc gdzieś wysłać rezultat, a pola $_SERVER['HTTP_X_FORWARDED_FOR'] lub $_SERVER['HTTP_CLIENT_IP'] są bardzo niepewne - generuje je właśnie serwer proxy, ale nic nie stoi na przeszkodzie, by podszył się pod nie hacker. Uznawanie ważności informacji tu zawartych było przyczyną wielu błędów bezpieczeństwa w popularnym skrypcie forów dyskusyjnych phpBB i jeżeli naprawdę zależy Ci na nim, pozostań przy REMOTE_ADDR, a resztę traktuj wyłącznie jako ciekawostkę.
Korzystając z funkcji gethostbyaddr() możemy uzyskać nazwę hosta, którego dotyczy adres IP:
<?php echo 'Witaj, twój host to '.gethostbyaddr($_SERVER['REMOTE_ADDR']).'!'; ?>
Spróbujmy zidentyfikować przeglądarkę użytkownika:
<?php echo 'Twoja przeglądarka została zidentyfikowana jako: '.$_SERVER['HTTP_USER_AGENT'].'!'; ?>
Pole to zawiera wyłącznie surowe informacje. Każda przeglądarka wpisuje tu co innego i dlatego wyciągnięcie ładnych i pogrupowanych w odpowiednie kategorie danych to zadanie na więcej, niż jeden artykuł. Pozostańmy zatem przy takiej postaci, a przetwarzaniem nagłówków przeglądarek zajmiemy się kiedy indziej.
Adres strony, z której przybyliśmy do naszego skryptu, znajduje się w zmiennej $_SERVER['HTTP_REFERER']. Można wykorzystać go do utworzenia linków "Cofnij" albo do detekcji, jakich słów kluczowych używają ludzie trafiający do nas z wyszukiwarek. Zauważ - szukane frazy zazwyczaj dołączane są właśnie do adresów URL i w ten sposób można je zdobyć.
Uwaga: nie można polegać na obecności i prawidłowości tej informacji. Istnieją przeglądarki i zapory ogniowe, które ją usuwają lub wstawiają tam dowolny adres podany przez użytkownika.
<?php echo 'Przybyłeś do nas ze strony: '.$_SERVER['HTTP_REFERER'].'!'; ?>
W chwili obecnej to tyle, jeżeli chodzi o pobieranie danych. Dalszych informacji dowiemy się w trakcie następnych rozdziałów w miarę potrzeby.
[edytuj] Struktury kontrolne
Programy wykonujące szereg zawsze tych samych instrukcji nie pozwalają rozwinąć skrzydeł. Jeżeli aplikacja ma być w pełni interaktywna, musi umieć reagować na poczynania użytkownika lub sytuacje kryzysowe na podstawie zadanych warunków. Tu do akcji wkraczają instrukcje kontrolne. Są one podstawowym budulcem wielu algorytmów i ich poznanie jest niezbędne. Za to my zyskamy pełną kontrolę nad wszystkim, mogąc efektywnie reagować na wszystkie zdarzenia.
Jedną z instrukcji kontrolnych - if - widziałeś już w poprzednim rozdziale. Zauważyłeś też pewnie, iż nie była ona zakończona średnikiem, a zamiast tego pojawiały się przy niej nawiasy klamrowe tworzące tzw. blok kodu. Jest to ciąg komend złożony z wyrażeń i innych struktur kontrolnych, czytelnie oznakowany. W połączeniu z odpowiednią instrukcją, PHP może decydować, czy wykonać go, czy nie, czy też powtórzyć raz jeszcze. Klamry pełnią tutaj rolę drogowskazu pokazującego, co się danej instrukcji tyczy.
PHP posiada siedem struktur kontrolnych, lecz my poznamy sześć z nich. Siódma jest tak rzadko stosowana, że większość zaawansowanych programistów nie umie z niej korzystać, a ponadto wymaga ona od nas pewnej dodatkowej wiedzy. Oto lista wszystkich instrukcji:
- Instrukcja if
- Instrukcja switch
- Instrukcja for
- Instrukcja while
- Instrukcja do while
- Instrukcja foreach
[edytuj] Instrukcja if
Z tą instrukcją zetknęliśmy się już przy okazji omawiania formularzy. Przypomnimy jeszcze raz ten przykład:
<?php
if(count($_GET) == 2)
{
echo 'Witaj, '.$_GET['imie'].' '.$_GET['nazwisko'].'!';
}
else
{
echo 'Nieprawidłowa liczba parametrów!';
}
?>
Instrukcja if pozwala na wykonanie części kodu tylko wtedy, kiedy spełniony jest określony warunek i opcjonalne dodawanie alternatywnych instrukcji w razie jego fałszywości. Z if-a korzysta się niezwykle często, ponieważ niemal zawsze musimy sprawdzać, czy dane informacje są prawidłowe, czy funkcja poprawnie połączyła się z plikiem itd. Poniżej napiszemy skrypt, który będzie potrafił rozwiązywać równanie kwadratowe y = ax2 + bx + c. Przypomnijmy, że ilość jego rozwiązań rzeczywistych zależy od wartości tzw. współczynnika Δ (Δ = b2 − 4ac). Jeżeli obliczony wynik (Δ) jest dodatni, istnieją dwa rozwiązania. Jeżeli ujemny - nie ma żadnych. Dla zera równanie daje jedno rozwiązanie (podwójne).
<?php
// 1
if(!isset($_GET['a']))
{
$_GET['a'] = 0;
}
if(!isset($_GET['b']))
{
$_GET['b'] = 0;
}
if(!isset($_GET['c']))
{
$_GET['c'] = 0;
}
// 2
if($_GET['a'] == 0)
{
die('Nieprawidłowy parametr A!');
}
// 3
$delta = pow($_GET['b'], 2) - 4 * $_GET['a'] * $_GET['c'];
// 4
if($delta > 0)
{
// 5
echo 'Delta dodatnia. Dwa rozwiązania:<ul>';
echo '<li>'.round((-$_GET['b']-sqrt($delta))/(2*$_GET['a']), 5).'</li>';
echo '<li>'.round((-$_GET['b']+sqrt($delta))/(2*$_GET['a']), 5).'</li>';
echo '</ul>';
}
else if($delta < 0)
{
// 6
echo 'Delta ujemna. Brak rozwiązań!';
}
else
{
// 7
echo 'Delta = 0. Jedno rozwiązanie: '.round((-$_GET['b'])/(2*$_GET['a']), 5);
}
?>
Jest to pierwszy tak długi kod zawarty w tym podręczniku, dlatego omówimy go sobie w punktach. Numery odpowiednich fragmentów zaznaczone są w kodzie komentarzami jednolinijkowymi.
- Na początek sprawdzamy, czy użytkownik podał wszystkie parametry. Funkcja isset() zwraca wartość TRUE, jeżeli zmienna istnieje, a operator negacji (!) sugeruje, że kod w nawiasie chcemy wykonać wtedy, gdy tej zmiennej nie ma. Musimy wtedy podstawić za nią neutralną wartość 0, ponieważ inaczej skrypt będzie nam zgłaszać powiadomienia.
- Tutaj zaczynamy właściwy algorytm. Najpierw sprawdzamy, czy rzeczywiście mamy do czynienia z równaniem kwadratowym. Parametr a musi być różny od zera. W razie problemów instrukcją die() zatrzymujemy skrypt w tym miejscu.
- Liczymy współczynnik Δ. Funkcja pow(liczba, potega) podnosi podaną liczbę do odpowiedniej potęgi i działa szybciej, niż ręczne mnożenie wartości.
- Pierwszy wariant - kiedy Δ jest dodatnia...
- Oblicz każde z dwóch rozwiązań równania odpowiednim wzorem. Funkcja round(liczba, miejsca) zaokrągla nam wynik do określonej ilości miejsc po przecinku, natomiast sqrt(liczba) zwraca pierwiastek z podanej liczby. Zwróć uwagę na użycie nawiasów do zasugerowania właściwej kolejności działań.
- Gdy Δ jest ujemna, równanie nie ma rozwiązania.
- Ostatni z wariantów jest oczywisty, dlatego nie piszemy już warunku. To przecież ostatnia z możliwości. Równanie ma tylko jedno rozwiązanie i także je wyliczamy.
Jest to nasz pierwszy prawdziwie dynamiczny skrypt, który potrafi reagować inaczej w zależności od sytuacji. Poznaliśmy tutaj nie tylko kilka nowych funkcji, ale także sporo operatorów i zasadę działania instrukcji if. Jej formalna składnia jest następująca:
<?php
if(wyrazenie)
{
// blok kodu
}
else if(wyrazenie)
{
// blok kodu
}
else
{
// blok kodu
}
?>
Obowiązkowe jest podawanie pierwszego z członów zaczynającego się od if. Dwa pozostałe są opcjonalne, przy czym ilość else if może być dowolna. Kolejność podawania kolejnych typów członów ukazana jest na przykładzie.
- if - wykonuje się, gdy spełniony został podany warunek
- else if - jeżeli nie został spełniony poprzedni warunek, PHP testuje aktualny i jeżeli jest prawdziwy, wykonuje ten kawałek kodu.
- else - wykonywane, jeżeli żaden z powyższych warunków nie został spełniony.
Jeżeli blok kodu zawiera tylko jedną instrukcję, PHP dopuszcza możliwość opuszczenia nawiasów klamrowych, np.
<?php
if($zmienna == 6)
echo 'Wartość zmiennej wynosi 6';
?>
W tym podręczniku jednak nie będziemy jej stosować z powodu pogorszenia czytelności kodu, niemniej warto wiedzieć, iż składnia ta jest w pełni poprawna, ponieważ część programistów stosuje ją w praktyce.
Wyrażenie warunkowe powinno przyjmować wartości logiczne TRUE lub FALSE. Oto kilka przydatnych operatorów:
| Operator | Nazwa | Składnia | Opis |
|---|---|---|---|
| == | Równość | wyrażenie == wyrażenie | Zwraca prawdę, jeżeli oba wyrażenia mają identyczną wartość. |
| === | Równość | wyrażenie === wyrażenie | Zwraca prawdę, jeżeli oba wyrażenia mają identyczną wartość oraz typ. |
| != | Nierówność | wyrażenie != wyrażenie | Zwraca prawdę, jeżeli oba wyrażenia mają różne wartości. |
| !== | Nierówność | wyrażenie !== wyrażenie | Zwraca prawdę, jeżeli oba wyrażenia mają różne wartości i/lub typ. |
| < | Mniejsze niż | wyrażenie < wyrażenie | Zwraca prawdę, jeżeli lewe wyrażenie ma mniejszą wartość od prawego. |
| > | Większe niż | wyrażenie > wyrażenie | Zwraca prawdę, jeżeli lewe wyrażenie ma większą wartość od prawego. |
| <= | Mniejsze lub równe | wyrażenie <= wyrażenie | Zwraca prawdę, jeżeli lewe wyrażenie ma mniejszą lub równą wartość prawemu. |
| >= | Większe lub równe | wyrażenie >= wyrażenie | Zwraca prawdę, jeżeli lewe wyrażenie ma większą lub równą wartość prawemu. |
| ! | Negacja (nie) | !wyrażenie | Zwraca prawdę, jeżeli wyrażenie jest fałszywe i fałsz, jeśli prawdziwe. |
| && | Koniunkcja logiczna (i) | wyrażenie && wyrażenie | Zwraca prawdę, jeżeli oba wyrażenia są prawdziwe. |
| || | Alternatywa logiczna (lub) | wyrażenie || wyrażenie | Zwraca prawdę, jeżeli przynajmniej jedno z wyrażeń jest prawdziwe. |
A teraz kilka przykładów...
<?php
$liczba1 = $_POST['liczba1']; //Zakładamy, że do skryptu wysłano formularz z polami liczba1
$liczba2 = $_POST['liczba2']; // i liczba2. Pobieramy je...
if($liczba1 == 1 && $liczba2 == 2)
{
die('Liczba 1 wynosi 1, a liczba 2 wynosi 2');
}
?>
Powyższy skrypt sprawdza, czy zmienna "liczba1" wynosi 1 i zmienna "liczba 2" wynosi 2.
<?php
$liczba1=$_POST['liczba1']; //Zakładamy, że do skryptu wysłano formularz z polami liczba1
$liczba2=$_POST['liczba2']; // i liczba2. Pobieramy je...
if($liczba1==1 || $liczba2==1)
{
die('Liczba 1, lub liczba 2 wynosi 1');
}
?>
Natomiast ten skrypt sprawdza, czy zmienna "liczba1", lub zmienna "liczba2" wynosi 1.
Wszystkie operatory podane wcześniej w tabelce przydają się przy konstruowaniu warunków. Pewnego wyjaśnienia domagają się == oraz ===. Popatrz sobie na taki przykład:
<?php
if(FALSE == 0)
{
echo 'Prawda!';
}
?>
PHP automatycznie sprowadzi tu sobie obie wartości do identycznego typu i wtedy dopiero je porówna. Dlatego skrypt wyświetli napis "Prawda!". Zamień teraz ten operator na ===. Po odświeżeniu zobaczymy, że teraz nic się nie pokazało. To dlatego, że zażądaliśmy, aby i typy obu wyrażeń były identyczne, podczas gdy nie są. Operator ten przydaje się przy niektórych funkcjach zwracających różne typy wartości w zależności od powodzenia operacji.
Oprócz tego w warunkach przyda się nam kilka funkcji:
- isset($zmienna) - zwraca prawdę, jeżeli zmienna istnieje.
- empty($zmienna) - zwraca prawdę, jeżeli zmienna ma wartość NULL.
- is_null($zmienna) - zwraca prawdę, jeżeli zmienna ma wartość NULL.
- is_string($zmienna) - zwraca prawdę, jeżeli zmienna jest ciągiem tekstowym.
- is_integer($zmienna) - zwraca prawdę, jeżeli zmienna jest liczbą całkowitą.
- is_float($zmienna) - zwraca prawdę, jeżeli zmienna jest liczbą zmiennoprzecinkową.
- is_numeric($zmienna) - zwraca prawdę, jeżeli zmienna jest liczbą.
- is_bool($zmienna) - zwraca prawdę, jeżeli zmienna ma wartość logiczną.
- is_array($zmienna) - zwraca prawdę, jeżeli zmienna jest tablicą.
[edytuj] Instrukcja switch
Instrukcja switch zwana jest także instrukcją wyboru. Jej działanie jest podobne do szczególnego przypadku poznanej ostatnio instrukcji warunkowej. Pokażemy to na przykładzie.
Nietrudno znaleźć witryny internetowe, które w jednym pliku grupują kilka różnych zadań wykonywanych w zależności od parametru, np. "index.php?act=dodaj", "index.php?act=usun" itd. Możemy to zaprogramować za pomocą dużego ifa:
<?php
if($_GET['act'] == 'dodaj')
{
echo 'Dodawanie danych';
}
elseif($_GET['act'] == 'edytuj')
{
echo 'Edycja danych';
}
elseif($_GET['act'] == 'usun')
{
echo 'Usuwanie danych';
}
else
{
echo 'Wyświetlanie danych';
}
?>
Rozwiązanie to nie jest wygodne nie tylko ze względu na objętość takiego kodu i brak czytelności. Przypuśćmy, że z jakiegoś powodu musimy zmienić miejsce, z którego pobieramy informację o akcji. Trzeba to zrobić w czterech miejscach, a tych może być w teorii jeszcze więcej. Znacznie łatwiejszą w użyciu, a przy tym wydajniejszą alternatywą jest instrukcja switch. Działa ona w ten sposób, że wybiera spośród dostępnego zbioru określoną wartość na podstawie wartości pewnego wyrażenia i wykonuje zdefiniowany dla niej kod. Przepiszmy raz jeszcze powyższy przykład:
<?php
switch($_GET['act'])
{
case 'dodaj':
echo 'Dodawanie danych';
break;
case 'edytuj':
echo 'Edycja danych';
break;
case 'usun':
echo 'Usuwanie danych';
break;
default:
echo 'Wyświetlenie danych';
}
?>
W nawiasie polecenia switch definiujemy, jakiemu wyrażeniu pragniemy sprawdzić wartość. Wewnątrz nawiasów klamrowych używamy struktury case wartość:, aby zdefiniować dopuszczalne wartości, czyli stany wyrażenia. Po dwukropku piszemy odpowiedni kod. Jeżeli żaden ze stanów nie spełnia naszych oczekiwań, istnieje także klauzula default: pisana na samym końcu opisująca domyślne zachowanie. Jej już nie musimy dodawać, jeżeli tego nie potrzebujemy, niemniej często się ona przydaje. Zwróć uwagę na komendę break; stojącą na końcu każdego kodu przypisanego do case. Mówi ona, że wykonywanie przerywane jest w tym miejscu i PHP ma skoczyć do końca switcha, a nie przypadkiem wykonać kolejny stan. Taka oryginalna budowa wynika z jednego prostego powodu: jeżeli chcemy dla trzech różnych stanów wykonać to samo zadanie, po prostu piszemy pod rząd trzy case'y, potem kod i na końcu przerwanie. W powyższym przykładzie dodaliśmy alternatywną nazwę pierwszej akcji: "dod". Kod po przeróbkach wygląda tak:
<?php
switch($_GET['act'])
{
case 'dod':
echo 'Jak nie damy komendy "break", to pokaże nam się też...<br/>';
case 'dodaj':
echo 'Dodawanie danych';
break;
case 'edytuj':
echo 'Edycja danych';
break;
case 'usun':
echo 'Usuwanie danych';
break;
default:
echo 'Wyświetlenie danych';
}
?>
Wywołując skrypt jako nazwapliku.php?act=dodaj, zobaczymy:
Dodawanie danych
Wywołując skrypt jako nazwapliku.php?act=dod, zobaczymy:
Jak nie damy komendy "break", to pokaże nam się też...
Dodawanie danych
PHP wykonał zarówno stan "dod", jak i następujący po nim "dodaj", gdyż w tym pierwszym brakowało komendy break.
Kod stanu może zawierać inne struktury kontrolne:
<?php
switch($_GET['act'])
{
case 'dodaj':
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
echo 'Dodawanie danych';
}
else
{
echo 'Formularz dodawania';
}
break;
case 'edytuj':
echo 'Edycja danych';
break;
case 'usun':
echo 'Usuwanie danych';
break;
default:
echo 'Wyświetlenie danych';
}
?>
Tutaj dodaliśmy instrukcję if, która sprawdza, czy żądanie nadeszło do nas z formularza HTTP, który należy przetworzyć, czy normalnie (wtedy wyświetlamy formularz). $_SERVER['REQUEST_METHOD'] zawiera nazwę metody, za pomocą której odbyło się żądanie HTTP.
Instrukcję switch warto stosować, kiedy wybieramy konkretną możliwość z określonego w kodzie zbioru. Dla PHP zaletą jest, że "wie", jakie są wszystkie stany. Instrukcja warunkowa if jest bardziej ogólna i tam interpreter sprawdza po prostu po kolei wszystkie warunki, aż nie natrafi na pasujący, nie zagłębiając się w jakieś większe zależności między danymi. Switcha nie należy używać, kiedy mamy tylko dwa stany, gdyż takie zagranie przypominałoby wytoczenie haubicy do zabicia komara. Instrukcja ta nie sprawdza się także przy wszystkich bardziej ogólnych warunkach rodzaju "mniejszy, większy".
[edytuj] Instrukcja for
[edytuj] Pętle
Wszystkie kolejne struktury kontrolne, jakie poznamy, określa się jednym wspólnym terminem: pętle.
| Pętlą nazywamy strukturę kontrolną powtarzającą dany kod do czasu spełnienia określonego warunku. |
Wiemy już, że pętla powtarza w kółko pewien fragment kodu. Różnice między poszczególnymi rodzajami dotyczą tego, jak i kiedy jest ona przerywana. Na początek zajmiemy się pętlą for. Pokazuje ona pazurki, kiedy zliczamy ilość wywołań pętli i na podstawie tego określamy, czy trzeba ją przerwać, czy nie. W for definiujemy trzy wyrażenia:
- Startu - najczęściej inicjuje licznik wywołań
- Końca - warunek zakończenia
- Iteracji - najczęściej zwiększa licznik wywołań
Oddzielone są one średnikami. Pokażemy to na przykładzie skryptu wyświetlającego liczby od 0 do 9.
<?php
for($i = 0; $i < 10; $i++)
{
echo $i.'<br/>';
}
?>
Warunek startu tworzy nową zmienną $i z wartością zero. Następnie określamy, że dopóki $i jest mniejsze od 10, pętla ma się powtarzać. Przy każdym cyklu należy zwiększyć wartość $i o 1.
[edytuj] Proste wyświetlanie tablic
Pętla for jest użyteczna przy wyświetlaniu tablic z indeksami numerycznymi. Mamy plik tekstowy z zawartością:
Litwo, ojczyzno moja! Ty jesteś jak zdrowie, Ile cię trzeba cenić, ten tylko się dowie, Kto cię stracił, dziś piękność twą w całej ozdobie Widzę i opisuję, bo tęsknię po tobie.
Zastosujemy funkcję file(), aby wczytać go do pamięci z jednoczesnym rozbiciem na poszczególne wiersze zapisane w tablicy. W ten sposób będziemy je mogli wyświetlić jako elementy listy wypunktowanej:
<?php
$plik = file('plik.txt');
echo '<ul>';
for($i = 0, $x = count($plik); $i < $x; $i++)
{
echo '<li>'.trim($plik[$i]).'</li>';
}
echo '</ul>';
?>
Do określenia ilości wierszy użyliśmy poznanej już wcześniej funkcji count(). Przy wyświetlaniu stosujemy jeszcze jedną: trim(). Usuwa ona z początku i końca każdego wiersza białe znaki, tj. spacje, zejścia do nowej linii, tabulatory. Wynikiem działania skryptu jest zawartość pliku wyświetlona w liście wypunktowanej.
Zwróć uwagę na specyficzną budowę wyrażenia inicjacji pętli. Pragniemy utworzyć dwie zmienne, dlatego oddzielamy je przecinkami. Podobną sztuczkę możemy zastosować również w wyrażeniu iteracyjnym. Można się zapytać, dlaczego zastosowaliśmy tak rozbudowaną konstrukcję. Przecież dopuszczalne jest także napisanie:
for($i = 0; $i < count($plik); $i++)
W typowych sytuacjach obie konstrukcje zachowają się podobnie, lecz warto pamiętać o pewnym niuansie technicznym. Pierwsza z konstrukcji pobiera ilość elementów tablicy na samym początku. Jeżeli któryś cykl pętli doda jakiś element, nie zostanie on uwzględniony. W drugim przypadku ilość ta jest pobierana po każdym cyklu, zatem pętla dysponuje bieżącymi informacjami o wielkości tablicy i wszelka jej zmiana zostanie uwzględniona w ilości wykonanych cykli. Sposób ten jest jednak mniej wydajny od pierwszego.
[edytuj] Break i Continue
Przy okazji omawiania instrukcji switch poznaliśmy komendę break. Ma ona bardzo duże zastosowanie przy pętlach, które potrafi przerywać. Istnieje także kolejne polecenie: continue. Przerywa ono jedynie aktualny cykl pętli i powoduje rozpoczęcie następnego.
Mamy prosty ciąg tekstowy:
Komenda; Komenda; Komenda; Komenda. To już pomijamy.
Wiemy o nim trzy rzeczy:
- Spacje ignorujemy
- Kropka oznacza koniec wprowadzania komend
- Średnik separuje komendy
Naszym zadaniem jest wprowadzenie komend do tablicy, aby można je było łatwiej przetwarzać. Skrypt ten będziemy pisać kawałek po kawałku. Na początek stwórzmy sobie parę zmiennych:
<?php $tekst = 'Komenda; Komenda; Komenda; Komenda. To już pomijamy.'; $tablica = array(0 => ''); $t = 0;
$tekst to tekst do przetworzenia. $tablica jest miejscem docelowym komend z "firmowo" utworzonym pierwszym pustym elementem. $t to licznik mówiący, do którego elementu tablicy wprowadzamy znaki.
Rozpoczynamy pętlę. Do pobrania długości ciągu użyjemy funkcji strlen(). $i to licznik położenia w ciągu tekstowym. Wskazuje na aktualnie przetwarzany znak:
for($i = 0; $i < strlen($tekst); $i++)
{
Implementujemy możliwość pierwszą. Spacje ignorujemy, dlatego przy ich napotkaniu przerywamy aktualny cykl pętli komendą continue i przechodzimy do następnego:
if($tekst{$i} == ' ')
{
continue;
}
Zauważ, jak odwołujemy się do określonego znaku wewnątrz ciągu: $tekst{$i}. Numer znaku (począwszy od zera) podajemy jako indeks w nawiasach klamrowych.
Druga możliwość - po napotkaniu kropki przerwać pętlę wcześniej:
if($tekst{$i} == '.')
{
break;
}
Przechodzimy do ewentualności trzeciej. Przy średniku należy przesunąć się na nowy element tablicy wynikowej i zainicjować go pustym ciągiem. Każdy inny znak wprowadzamy do aktualnego elementu tablicy:
if($tekst{$i} == ';')
{
$t++;
$tablica[$t] = '';
}
else
{
$tablica[$t] .= $tekst{$i};
}
Teraz dopełnienie formalności, tj. zamknięcie pętli i wyświetlenie zawartości tablicy funkcją print_r():
} echo '<pre>'; print_r($tablica); echo '</pre>'; ?>
Zapytajmy się, jak przerwać pętlę, jeżeli jesteśmy aktualnie w instrukcji switch? Wywołanie break i continue będzie się przecież odnosiło do niej, a tego nie chcemy. Rozwiązaniem jest podanie po nich numeru określającego, której instrukcji wzwyż dotyczy wywołanie. Przepiszmy jeszcze raz powyższy kod z wykorzystaniem instrukcji wyboru (notabene nawet bardziej pasującej w tym przypadku):
<?php
$tekst = 'Komenda; Komenda; Komenda; Komenda. To już pomijamy.';
$tablica = array(0 => '');
$t = 0;
for($i = 0; $i < strlen($tekst); $i++)
{
switch($tekst{$i})
{
case ' ':
continue 2;
case '.':
break 2;
case ';':
$t++;
$tablica[$t] = '';
break;
default:
$tablica[$t] .= $tekst{$i};
}
}
echo '<pre>';
print_r($tablica);
echo '</pre>';
?>
Przy stanach spacji oraz kropki wywołujemy komendy continue oraz break z parametrem 2, aby podkreślić, że dotyczą one pętli for, a nie instrukcji switch. break w kodzie obsługi średnika nie ma parametru, więc odnosi się do instrukcji switch.
[edytuj] Instrukcja while
Kolejną pętlą jest while, będąca znacznie prostszą odmianą poznanej ostatnio pętli for. Wymagany jest tu jedynie warunek zakończenia, a pętla wykonuje się, dopóki jest on prawdziwy. Oto prosty przykład:
<?php
while(rand(0,10) != 8)
{
echo 'Jeszcze nie wylosowałem!<br/>';
}
?>
Pętla ta będzie wykonywała się, dopóki funkcja rand() nie wylosuje liczby 8. Jeżeli dostaniemy ją już w pierwszym sprawdzeniu, napis nie pojawi się w ogóle.
Ze względu na taką ogólną konstrukcję while przydaje się tam, gdzie musimy coś powtarzać do czasu osiągnięcia pewnego stanu. Sztandarowym przykładem jest czytanie pliku, gdzie takim specyficznym stanem, w którym musimy przerwać naszą pracę, jest jego koniec. Zastosowanie pętli while będzie tu o wiele lepsze, niż obliczanie na podstawie wielkości pliku, ile "segmentów" musimy pobrać i zabawa z licznikami.
<?php
$f = fopen('plik.txt', 'r'); // 1
while(!feof($f)) // 2
{
echo fgets($f, 16); // 3
}
fclose($f); // 4
?>
Opis działania:
- Otwieramy plik do odczytu. Uchwyt do niego zapisujemy w zmiennej $f. W ten sposób poznaliśmy nowy typ danych: Resource, czyli zasób.
- Dopóki nie osiągniemy końca pliku...
- Pobieraj z niego kolejne 16-znakowe bloki.
- Na koniec zamykamy połączenie z plikiem.
Pętlę while można przerobić na pętlę for bez większych trudności. Oto nowa wersja pierwszego przykładu z rozdziału poprzedniego:
<?php
$i = 0;
while($i < 10)
{
echo $i.'<br/>';
$i++;
}
?>
Tu także można stosować komendy break oraz continue poznane w poprzednim rozdziale.
Pętla while przyda się nam, gdy zaczniemy omawiać komunikację z bazami danych. Zostanie tam wykorzystana do pobierania rekordów.
[edytuj] Instrukcja do while
W przeciwieństwie od normalnego while, tutaj warunek sprawdzany jest na końcu, tak więc pętla zostanie wykonana przynajmniej raz. Nie jest to często wykorzystywana właściwość, ale warto o niej pamiętać.
Składnia pętli do while jest dość specyficzna. Przed nawiasem klamrowym pojawia się jedynie słowo kluczowe do, a while z warunkiem znajduje się na samym końcu. Przedstawimy to na przykładzie takiego oto skryptu:
<?php
do
{
echo "Podaj i: \n";
fscanf(STDIN, "%d\n", $i); // 1
}
while($i < 10);
?>
Nie będziemy uruchamiali go w przeglądarce, ale w linii komend. Powyższy skrypt pracuje w konsoli systemowej i może pobierać stamtąd dane poprzez wiersz zaznaczony jako 1 (nie przejmuj się, że nie rozumiesz jego budowy. Poznamy ją dalej). Aby uruchomić skrypt, uruchom konsolę i ustaw się poleceniem cd na katalogu, w którym zainstalowałeś PHP. Następnie wydaj następujące polecenie:
php -f /scieżka/do/skrypt.php
Jako wartość parametru -f musisz podać pełną ścieżkę do skryptu, który chcesz uruchomić.
Zauważ, że dzięki użyciu pętli do while, nie musimy umieszczać w skrypcie dwa razy kodu do pytania się o zmienną $i. Oto analogiczny kod z wykorzystaniem normalnego while:
<?php
echo "Podaj i: \n";
fscanf(STDIN, "%d\n", $i);
while($i < 10)
{
echo "Podaj i: \n";
fscanf(STDIN, "%d\n", $i);
}
?>
Tutaj musimy powielić kod dwa razy, bo przecież przed sprawdzeniem warunku wypada się choć raz zapytać użytkownika, co należy sprawdzić. W poprzednim przykładzie mogliśmy do tego celu użyć kodu wewnątrz pętli, ponieważ mieliśmy zagwarantowane wykonanie jej kodu przynajmniej raz.
[edytuj] Instrukcja foreach
Ostatnią pętlą jest foreach. Ma ona specyficzne zastosowanie, ponieważ służy wyłącznie do przeglądania zawartości typów złożonych: tablic oraz obiektów. Kod wewnątrz niej jest powtarzany dla każdego z elementów tablicy, a on sam jest na ten czas przenoszony do tworzonej przez pętlę zmiennej. Wróćmy do naszego przykładu z pętlą for odczytującego zawartość pliku. Przepiszemy go z wykorzystaniem foreach:
<?php
$plik = file('plik.txt');
echo '<ul>';
foreach($plik as $linia)
{
echo '<li>'.trim($linia).'</li>';
}
echo '</ul>';
?>
Teraz skrypt ma o wiele bardziej przejrzystą budowę. Przyjrzyjmy się deklaracji pętli:
foreach($plik as $linia)
Mówi nam ona, że pętla ma analizować tablicę $plik, a aktualnie przetwarzany element ma być zapisany w zmiennej $linia.
Foreach umożliwia nam także zwracanie nazw indeksów elementów:
<?php
$plik = file('plik.txt');
echo '<ul>';
foreach($plik as $numer => $linia)
{
echo '<li>Linia #'.$numer.': '.trim($linia).'</li>';
}
echo '</ul>';
?>
Foreach ma tę przewagę nad innymi pętlami, że wie, jakie elementy należą do tablicy i zawsze przetworzy tylko je. Gdybyśmy przed wyświetleniem pliku usunęli z niego np. linijkę 1, pętla for nie dałaby rady, próbując przetworzyć nieistniejący element. Nie robi oczywiście tego dla złośliwości, lecz dlatego, że operuje na liczniku i nie wie, do czego jest on przez nas dalej wykorzystywany.
<?php
$plik = file('plik.txt');
unset($plik[1]); // usuwamy linijkę o indeksie 1
echo '<ul>';
foreach($plik as $numer => $linia)
{
echo '<li>Linia #'.$numer.': '.trim($linia).'</li>';
}
echo '</ul>';
?>
Tworzone przez foreach zmienne są jedynie kopiami oryginalnych wartości, dlatego próba ich modyfikacji wewnątrz pętli w żaden sposób nie wpłynie na zawartość tablicy:
<?php
$plik = file('plik.txt');
foreach($plik as $linia)
{
$linia = 'Próba skasowania';
}
echo '<pre>';
print_r($plik);
echo '</pre>';
?>
Wewnątrz pętli próbujemy przypisać wartość do zmiennej $linia. Owszem, udaje nam się to, ale nowa treść nie trafia w ogóle do tablicy i systemowe wyświetlenie jej zawartości ukazuje brak jakiejkolwiek reakcji. Czy zatem możliwe jest dokonywanie przypisań wewnątrz foreach? Oczywiście. Są dwie sztuczki. Pierwsza polega na wykorzystaniu zwracanego przez pętlę indeksu. Pousuwajmy z tablicy zbędne białe znaki:
<?php
$plik = file('plik.txt');
foreach($plik as $i => $linia)
{
// jeszcze jakiś napis sobie doklejmy
$plik[$i] = trim($linia).' [OK]';
}
echo '<pre>';
print_r($plik);
echo '</pre>';
?>
Rozwiązanie to jest nieco trikowe, ale działa. Możemy jednak zastosować coś innego. PHP posiada pewien element zwany referencją. Ogólnie rzecz biorąc jest to odnośnik do zmiennej, który zachowuje się tak, jak ona. Modyfikacja referencji powoduje także modyfikację oryginalnego elementu. Począwszy od PHP 5, referencje można używać w pętli foreach. Wystarczy poprzedzić w jej deklaracji zmienną $linia znakiem &:
<?php
$plik = file('plik.txt');
foreach($plik as &$linia)
{
$linia = trim($linia).' [OK]';
}
echo '<pre>';
print_r($plik);
echo '</pre>';
?>
Teraz modyfikacja zmiennej $linia jest równoznaczna z modyfikacją aktualnego elementu w tablicy $plik, ponieważ zmienna jest takim właśnie odnośnikiem. O referencjach szerzej powiemy w rozdziale Inne elementy składni.
[edytuj] Funkcje
Funkcja jest pojęciem znanym z matematyki: to zbiór operacji przypisujący danej grupie parametrów jakiś rezultat. Posiada własną nazwę, za pomocą której można się do niej odwoływać, pobiera dane i generuje wynik ponownie zwracany do programu. PHP posiada bardzo dużą ilość predefiniowanych funkcji, lecz nie zawsze mogą one zaspokoić nasze potrzeby. Załóżmy, że mamy w kodzie witryny WWW jakiś często powtarzający się algorytm. Pisanie go za każdym razem od nowa naraża nas na stratę czasu i ryzyko błędów. Możemy jednak napisać sobie własną funkcję z jego kodem i następnie odwoływać się do niej, gdy będzie to potrzebne. Gdybyśmy w przyszłości chcieli dodać jakąś nową możliwość lub wykryli błąd, poprawiamy go w jednym miejscu i zmiana widoczna jest wszędzie. W tym rozdziale omówione zostaną wszystkie tajniki tworzenia własnych funkcji i przyłóż do niego szczególną uwagę, ponieważ jest to rzecz, której nie można nie znać.
[edytuj] Tworzenie własnych funkcji
Tworzenie funkcji w PHP jest niezwykle proste. Stosujemy następującą konstrukcję:
function nazwaFunkcji(parametry)
{
// kod funkcji
}
Od tego miejsca możemy wywoływać naszą funkcję w identyczny sposób, jak te dostępne w PHP.
<?php
function formatujTekst($tekst)
{
echo '<font color="red">'.strtoupper($tekst).'</font>';
}
formatujTekst('to jest tekst 1');
formatujTekst('to jest tekst 2');
?>
Stworzyliśmy tutaj funkcję formatujTekst, dzięki której ustalimy jednolite formatowanie dla tekstów prezentowanych na stronie. Pobiera ona jeden parametr: $tekst. Zauważ, że nazwę tę piszemy ze znakiem dolara. Gdybyśmy chcieli podać więcej parametrów, oddzielamy je od siebie przecinkami. Można też nie brać żadnego, zostawiając puste nawiasy. Deklaracje te mówią skryptowi, pod jakimi zmiennymi wprowadzane wartości mają być widoczne wewnątrz funkcji.
Kod funkcji jest dowolnym poprawnym kodem PHP i możemy w nim wykonywać wszystkie operacje, z tworzeniem nowych funkcji włącznie. Jednak zauważ, że nasza pierwsza funkcja nie zwraca wartości. Zamiast tego wynik wysyła od razu na ekran i próba wykonania
$zmienna = formatujTekst('to jest tekst 1');
nic by nam nie dała. Aby zwrócić cokolwiek jako wynik, musimy skorzystać z komendy return:
<?php
function formatujTekst($text)
{
return '<font color="red">'.strtoupper($text).'</font>';
}
echo formatujTekst('to jest tekst 1').'<br/>';
echo formatujTekst('to jest tekst 2').'<br/>';
?>
Po return podajemy wyrażenie generujące wartość do zwrócenia.
Zwróćmy uwagę na fakt, iż przy deklarowaniu parametrów nie podajemy żądanego typu danych. O to musimy zadbać sami, umieszczając na początku funkcji odpowiednie instrukcje warunkowe i w razie kłopotów zgłosić błąd. Napiszmy sobie skrypt wyświetlający zawartość katalogu. Stworzymy w nim jedną funkcję zwracającą zawartość podanego katalogu jako tablicę. Druga funkcja będzie uniwersalnym wyświetlaczem tablic. Dlaczego tak - zaraz wyjaśnimy.
<?php
function wyswietlKatalog($sciezka, $tylkoPliki = 0) // 1
{
$dir = opendir($sciezka); // 2
$wynik = array();
while($f = readdir($dir)) // 3
{
if(is_file($sciezka.$f)) // 4
{
$wynik[] = $f; // 5
}
elseif(is_dir($sciezka.$f) && $f != '.' && $f != '..' && !$tylkoPliki) // 6
{
$wynik[] = $f;
}
}
closedir($dir); // 7
return $wynik; // 8
} // end wyswietlKatalog();
function pokazListe(Array $lista) // 9
{
echo '<ul>';
foreach($lista as $element)
{
echo '<li>'.$element.'</li>';
}
echo '</ul>';
} // end pokazListe();
pokazListe(wyswietlKatalog('./katalog1/')); // 10
echo '<br/>';
pokazListe(wyswietlKatalog('./katalog2/', true)); // 11
?>
Opis skryptu:
- Oto deklaracja funkcji wyświetlania katalogów. Znak równości oraz wartość po drugim parametrze oznacza, że jest on opcjonalny. Jeżeli go nie podamy przy wywołaniu, przyjmie on wartość domyślną. Opcjonalnych parametrów może być więcej, z tym że podajemy je zawsze na końcu. W naszej funkcji opcjonalny parametr określa, czy funkcja ma zwracać wszystko (domyślny stan: 0), czy jedynie pliki (stan 1).
- Otwieramy katalog o podanej ścieżce
- Pętla pobierająca kolejne elementy katalogu, dopóki istnieją.
- Sprawdzamy, czy zwrócony element jest plikiem. Zauważ, że do zwróconej nazwy elementu musimy dokleić ścieżkę, ponieważ funkcja is_file() jest niezależna od opendir() i nie obchodzi jej, że w takim kontekście ją wywołujemy. Jeżeli rzeczywiście mamy plik, dodajemy go do tablicy wynikowej jako kolejny element.
- Niepodanie indeksu oznacza: "utwórz nowy element o indeksie MAX+1".
- Warunek sprawdzający, czy mamy do czynienia z katalogiem, jest dość skomplikowany. Użyliśmy tu operatorów && (logiczne "oraz"), aby zagwarantować, że wszystkie muszą być spełnione, aby dodać element do listy. Mamy tu kolejno: czy element jest katalogiem, czy nie ma on nazwy "." i ".." oraz czy funkcja ma od programisty zezwolenie na pobieranie katalogów.
- Zamykamy katalog
- Zwracamy tablicę jako wynik
- A oto mała niespodzianka. Począwszy od PHP 5 można definiować typy parametrów obiektowych, a od PHP 5.1 - tablic, które również są typem złożonym. Robimy to właśnie w taki sposób. Jeśli próbowalibyśmy wysłać tutaj np. liczbę, PHP zgłosiłby błąd.
- Wywołanie funkcji z jednym parametrem i przekierowanie wyniku do funkcji wyświetlającej listę.
- Ponowne wywołanie, lecz tym razem żądamy wyłącznie plików.
W ten sposób poznaliśmy już niemal wszystko, co dotyczy definiowania parametrów. Pozostała jeszcze jedna rzecz, a mianowicie pobieranie ich zupełnie nieokreślonej liczby. Uruchom taki oto skrypt:
<?php
function funkcja($a)
{
echo $a;
}
funkcja(1, 2, 3, 4, 5);
?>
Nasza funkcja pobiera tylko jeden parametr, lecz my podajemy mu pięć. Mogłoby się wydawać, że spowodujemy tym samym błąd, jednak tak się nie stanie. PHP nadmiarowych parametrów nie ignoruje. Choć nie zadeklarowaliśmy żadnego z nich podczas tworzenia funkcji, istnieje pewien sposób, aby je wydostać. Jest to funkcja func_get_args() zwracająca tablicę z wartościami wszystkich parametrów, które przekazaliśmy do funkcji.
<?php
function funkcja()
{
$parametry = func_get_args();
echo '<ul>';
foreach($parametry as $id => $wartosc)
{
echo '<li>'.$id.' - '.$wartosc.'</li>';
}
echo '</ul>';
}
funkcja(1, 2, 3, 4, 5);
?>
Istnieje także func_get_arg(numer) pobierająca wartość konkretnego parametru. Obie te funkcje operują bezpośrednio na funkcji, dlatego PHP nakłada kilka ograniczeń na ich stosowanie. Najlepiej jest pobrać wszystkie niezbędne parametry na samym początku tworzonej funkcji, aby uniknąć kłopotów.
[edytuj] Widzialność zmiennych
Napiszmy taki skrypt:
<?php
$zmienna = 'To jest zmienna';
function funkcja()
{
echo $zmienna.'<br/>';
}
funkcja();
echo $zmienna.'<br/>';
?>
Próbuje on wyświetlić dwa razy wartość tej samej zmiennej: z wnętrza funkcji oraz bezpośrednio w skrypcie. Po uruchomieniu okazuje się, że tylko bezpośrednie wyświetlenie podało nam prawidłowy wynik. echo wewnątrz funkcji nie pokazało żadnej wartości. Dlaczego? Zmienna przecież istnieje. I owszem, lecz tylko w tej części skryptu, w której została utworzona. PHP ma zaimplementowaną tzw. widzialność zmiennych - dla każdej funkcji tworzony jest osobny stos, niezależny od drugiego. Jeżeli więc utworzymy zmienną bezpośrednio w skrypcie, nie będzie ona istnieć w żadnej z naszych funkcji, gdyż te mają własne stosy. Ma to wyeliminować konflikty nazewnictwa.
Istnieje jednak sposób na powiedzenie PHP, że używana w funkcji zmienna jest już stworzona w stosie głównym skryptu. Po lekkiej modyfikacji skryptu otrzymujemy:
<?php
$zmienna = 'To jest zmienna';
function funkcja()
{
global $zmienna;
echo $zmienna.'<br/>';
}
funkcja();
echo $zmienna.'<br/>';
?>
Słowo kluczowe global informuje PHP, że wymienione po nim zmienne mają zostać zaimportowane ze stosu głównego.
[edytuj] Static
Inną przydatną rzeczą jest przenoszenie niektórych zmiennych między wywołaniami tej samej funkcji. Dzięki temu nie musimy zapamiętywać ich wartości w globalnych tablicach, narażając się na konflikty nazewnictwa. Aby tego dokonać, wystarczy zadeklarować wybrane zmienne jako static, a ich wartość zostanie zapamiętana do następnego wywołania.
<?php
function koloruj()
{
static $i = 0;
$i++;
if($i % 2 == 0)
{
return '#ffffff';
} else {
return '#cccccc';
}
} // end koloruj();
echo '<table width="30%">';
for($x = 0; $x < 10; $x++)
{
echo '<tr><td bgcolor="'.koloruj().'">'.$x.'</td></tr>';
}
echo '</table>';
?>
Powyższy przykład koloruje naprzemianlegle wiersze w tablicy. Sztuczka ta nie wymaga finezji: po prostu zwiększamy licznik i sprawdzamy, czy dzieli się bez reszty przez dwa. Jeśli tak, wstawiamy jeden kolor, jeśli nie - drugi. Z pomocą instrukcji switch można rozszerzyć algorytm na więcej kolorów.
Tutaj kolor jest zwracany przez odpowiednią funkcję. Zapamiętuje ona sobie stan wewnętrznego iteratora $i między kolejnymi wywołaniami przy pomocy słowa kluczowego static. Gdybyś usunął tę linijkę, funkcja cały czas zwracałaby ten sam kolor, gdyż zmienna tworzona byłaby w kółko od nowa z domyślną wartością 0.
[edytuj] Rekurencja
Rekurencja w programowaniu oznacza odwoływanie się funkcji do samej siebie. Jest użyteczna, w niektórych sytuacjach wręcz niezbędna, lecz pochłania znacznie więcej zasobów i należy się z tym liczyć.
Za pomocą rekurencji możemy wyświetlić w PHP drzewo katalogów:
<?php
function wyswietlKatalog($sciezka)
{
$dir = opendir($sciezka);
echo '<ul>';
while($f = readdir($dir))
{
if(is_dir($sciezka.$f) && $f != '.' && $f != '..')
{
echo '<li>'.$f;
wyswietlKatalog($sciezka.$f.'/'); // 1
echo '</li>';
}
}
echo '</ul>';
closedir($dir);
} // end wyswietlKatalog();
wyswietlKatalog('../../');
?>
Funkcja wyswietlKatalog() w przypadku napotkania katalogu w aktualnie sprawdzanej ścieżce, wywołuje samą siebie (1), z doklejoną do dotychczasowej ścieżki nazwą tego katalogu. W ten sposób możemy pobrać całe drzewo, niemniej w przypadku rozbudowanych struktur może trwać to nawet kilkanaście sekund! Musimy być także świadomi, że funkcja nie może zagnieżdżać się rekurencyjnie w nieskończoność. W pewnym momencie w PHP zwyczajnie zapcha się stos i wykonywanie takiego skryptu zostanie przerwane. Dlatego stosuj rekurencję z rozwagą i nie nadużywaj jej w miejscach, w których nie jest konieczna.
[edytuj] Użyteczne funkcje
PHP dysponuje kilkoma funkcjami do zarządzania funkcjami. Brzmi to może dość śmiesznie, lecz w praktyce bywa bardzo przydatne.
Na początek zastanówmy się, kiedy PHP sprawdza, że funkcja nie istnieje. Okazuje się, że nie dzieje się to w momencie kompilacji, lecz wykonywania skryptu. Ma to swoje uzasadnienie przy konstruowaniu modułowych skryptów (zajmiemy się nimi w następnym rozdziale). Pierwszy plik PHP odwołuje się do funkcji zdefiniowanych w drugim, lecz ten z kolei ładowany jest później. Gdyby z powodu nieistnienia jednej z nich skrypt byłby przerywany w momencie kompilacji, skrypt nie miałby żadnych szans na działanie. Ponadto nie dałoby się pracować ze skryptami korzystającymi z rozszerzeń, których na serwerze nie ma. Ma to sens, przecież instrukcją warunkową możemy zdefiniować alternatywny kod dla tych uboższych serwerów.
PHP ułatwia nam zadanie jeszcze bardziej. Za pomocą function_exists() możemy sprawdzić, czy podana przez nas funkcja istnieje. Narzędziem tym można sondować zarówno nasze własne, jak i definiowane przez rozszerzenia funkcje. W poniższym przykładzie wykorzystamy to do sprawdzenia, czy serwer posiada obsługę protokołu IMAP:
<?php
if(function_exists('imap_open'))
{
echo 'IMAP dostępny';
}
else
{
echo 'IMAP niedostępny';
}
?>
Innym sposobem sprawdzenia, czy rozszerzenie jest załadowane, jest skorzystanie z funkcji extension_loaded(), która ma tę przewagę, że działa także z rozszerzeniami obiektowymi, w których zwykłych funkcji nie ma:
<?php
if(extension_loaded('imap'))
{
echo 'IMAP dostępny';
}
else
{
echo 'IMAP niedostępny';
}
?>
| Do zrobienia: Napisać o call_user_func() (z sensownym przykładem!) |
| Do zrobienia: Napisać sensowną funkcję. |
[edytuj] Inne elementy składni
[edytuj] Include i require
Tworzenie dynamicznych stron byłoby bardzo kłopotliwe, gdybyśmy musieli kopiować wszystkie pracowicie utworzone przez nas funkcje do każdego pliku PHP z osobna. Na szczęście PHP udostępnia mechanizmy na dołączanie jednego skryptu do drugiego. To instrukcje include, require, include_once oraz require_once.
Rozpatrzmy taką sytuację: mamy dwa pliki z funkcjami definiującymi wygląd treści: normalny.php oraz opcjonalny.php. Stworzone w nich są identyczne funkcje różniące się jedynie zawartością. We właściwym skrypcie dołączamy jeden z tych plików decydując o tym, w jakim stylu zaprezentowane zostaną dane na stronie. Oto kod dwóch dołączanych plików.
normalny.php:
<?php
function pokazTytul($tytul)
{
echo '<h1>'.$tytul.'</h1>';
} // end pokazTytul();
function pokazParagraf($tekst)
{
echo '<p>'.$tekst.'</p>';
} // end pokazParagraf();
function pokazListe(Array $tablica)
{
echo '<ul>';
foreach($tablica as $element)
{
echo '<li>'.$element.'</li>';
}
echo '</ul>';
} // end pokazListe();
?>
opcjonalny.php:
<?php
function pokazTytul($tytul)
{
echo '<h1>'.$tytul.'</h1>';
} // end pokazTytul();
function pokazParagraf($tekst)
{
echo '<p style="font-weight:bold;">'.$tekst.'</p>';
} // end pokazParagraf();
function pokazListe(Array $tablica)
{
echo '<ol>';
foreach($tablica as $element)
{
echo '<li>'.$element.'</li>';
}
echo '</ol>';
} // end pokazListe();
?>
Jeszcze raz zwracamy uwagę, że oba pliki tworzą funkcje o takich samych nazwach, dlatego naraz może być załadowany tylko jeden z nich. Oto plik index.php, który na podstawie tego, czy ustawiony jest parametr "styl", decyduje, który z powyższych skryptów zostanie załadowany:
<?php
if(!isset($_GET['styl']))
{
require('./normalny.php');
}
else
{
require('./opcjonalny.php');
}
pokazTytul('Tytuł');
pokazParagraf('To jest paragraf');
pokazListe(array(0 =>
'To',
'Jest',
'Lista'
));
?>
Choć require wywołuje się identycznie, jak funkcję, funkcją nie jest. Różnica między nim, a include jest taka, że pierwsza w przypadku nieznalezienia pliku generuje komunikat Fatal error zatrzymujący skrypt, druga tylko ostrzeżenie. Istnieją także include_once oraz require_once, które są ignorowane, jeśli próbujemy po raz drugi dołączyć ten sam plik.
Budowanie kompletnej strony z mniejszych plików jest bardzo pożyteczne. Generalnie nie zaleca się pisania wszystkiego ciurkiem bez podziału na funkcje, mniejsze moduły itd. gdyż zmniejsza to odporność skryptu na błędy, wprowadza chaos i utrudnia dodawanie/modyfikowanie nowych opcji. Przyjrzyjmy się, jak zatem zorganizować naszą witrynę. Przede wszystkim zróbmy sobie jeden katalog na wszystkie pliki z funkcjami. Może on się nazywać np. includes. Umieszczamy w nim funkcje ułatwiające komunikację z bazą danych, przetwarzanie tekstu, autoryzację i wykonujące wszystkie inne rutynowe operacje. W katalogu głównym zawierającym skrypty dostępne z przeglądarki (np. index.php, rejestracja.php itd.), umieszczamy także jeden plik o nazwie common.php. Ma on następującą postać:
<?php
require('../includes/database.php');
require('../includes/text.php');
require('../includes/session.php');
require('../includes/authorize.php');
require('../includes/templates.php');
require('../includes/functions.php');
require('../includes/layout.php');
require('../includes/main.php');
initSystem();
?>
common.php ma jedno tylko zadanie: załadować cały silnik i zainicjować jego pracę. Teraz w plikach typu index.php generujących właściwą zawartość strony wpisujemy na początku:
<?php
require('./common.php');
// tutaj kod strony
?>
W taki właśnie sposób wprowadziliśmy organizację w strukturę naszej witryny. Jeżeli silnikowi przybędzie dodatkowy plik, dopisujemy go jedynie do common.php, a będzie on dostępny na wszystkich podstronach.
Przyszła pora na przyjrzenie się wydajności procesu dołączania. Wiele innych źródeł zachęca do dołączania plików bez podawania ścieżki, np.
require('plik.php');
Wbrew pozorom, niepodanie przed nazwą zwyczajnego ./ ma wpływ na wydajność.
require('./plik.php');
Różnice leżą w przetwarzaniu każdej z tych ścieżek przez PHP. W konfiguracji interpretera jest dyrektywa include_path. Informuje ona, jakie ścieżki mają być przeszukiwane po kolei w przypadku niezdefiniowania położenia pliku. Tak więc przy pierwszym sposobie PHP testuje każdy z wprowadzonych tam katalogów, a sprawdzanie to nieco trwa. Owszem, istnieją sytuacje, kiedy trzeba się tym posłużyć (np. przy dołączaniu bibliotek PEAR instalujących się w systemie w dziwnych miejscach), ale na co dzień pożałowanie tych dwóch dodatkowych znaków może nas kosztować nawet dwukrotny spadek wydajności już dla zaledwie kilku dołączanych w ten sposób skryptów. ./ wyraźnie informuje interpreter: "interesuje mnie tylko bieżący katalog. Nie szukaj nigdzie indziej."
Kolejną istotną kwestią jest bezpieczeństwo. W sieci wciąż można spotkać wiele artykułów, które polecają taki sposób wykonania modułowości:
<?php
include($_GET['co']); // NIGDY TEGO NIE UŻYWAJ!
?>
Jest to jawne pogwałcenie zasad bezpieczeństwa - nigdy nie zezwalać użytkownikowi na dostęp do jakiegokolwiek pliku. Oczywiście, dla większości osób adres www.strona.pl/?co=informacje.html nie będzie podejrzany, ale wystarczy tu już nieco lepsza znajomość PHP i systemów operacyjnych, by wpisać www.strona.pl/?co=/etc/passwd i w ten sposób odczytać listę haseł serwera. Wydawać się to może śmieszne i prostackie, ale przez zwyczajne niedbalstwo programistów "padło" wiele witryn, w tym także tych należących do publicznych instytucji. Proponowanie powyższego sposobu jako recepty na modułowość to najprostsze w świecie zaproszenie hackera do zniszczenia naszej witryny. Jeżeli naprawdę potrzebujemy czegoś takiego, __musimy__ sprawdzić, że podana nazwa zawiera wyłącznie dozwolone znaki. Nie pomoże dodawanie nazwy katalogu przed ścieżką (atakujący może to ominąć podając ".."), ani doklejanie rozszerzenia do nazwy plików (przez bug w PHP można je odciąć za pomocą znaku NUL (%00 w URL)).
[edytuj] Stałe
Spójrzmy raz jeszcze na przykład z plikiem common.php. Jak nietrudno się domyślić, większe projekty składają się z pewnej liczby katalogów. Pojawia się tu problem, skąd skrypt ma wiedzieć, gdzie leżą potrzebne mu pliki? Teoretycznie możemy ścieżki wpisywać ręcznie przy każdej konieczności, lecz jest to bardzo nieefektywne. Jeżeli dodatkowo aplikacja posiada jakiś silnik ze zbiorem używanych w różnych miejscach funkcji, może nawet okazać się to niemożliwe. Typowa strona internetowa składa się bowiem z paru sekcji, np. panelu administracyjnego, części głównej, forum, zlokalizowanych najczęściej w osobnych podkatalogach. Gdybyśmy na stałe zdefiniowali jakąś ścieżkę w silniku, nie wiedzielibyśmy, do której sekcji ona prowadzi.
Zdefiniujmy zatem wszystkie używane w projekcie ścieżki w pliku common.php za pomocą zmiennych. Już na pierwszy rzut oka widać, iż rozwiązanie to nie jest najlepsze, bowiem każdą zmienną ze ścieżką musimy przenosić do funkcji z użyciem global, a ponadto istnieje ryzyko, że w którymś miejscu omyłkowo ją nadpiszemy. Remedium na te kłopoty są stałe. Są to aliasy na pewne wartości, których po utworzeniu nie można już modyfikować. Najogólniej wykorzystuje się w je dla często powtarzających się w skrypcie wartości. Przyjrzyjmy się poniższemu skryptowi:
<?php
define('DIR_GLOWNY', './');
define('DIR_SILNIK', './includes/');
define('DIR_ZDJECIA', './zdjecia/');
require(DIR_SILNIK.'autoryzacja.php');
require(DIR_SILNIK.'funkcje.php');
?>
Do zdefiniowania stałych używamy funkcji define(), w której definiujemy nazwę stałej oraz jej wartość. Zwyczajowo stałe mają nazwy złożone z samych dużych liter, a wartościami mogą być wyłącznie typy skalarne (czyli nie tablice, nie obiekty oraz nie zasoby). Podczas wywoływania stałych nie poprzedzamy znakiem dolara.
Oto wszystkie cechy stałych:
- Stałe nie mają znaku dolara ($) przed nazwą
- Stałe mogą być definiowane oraz używane wszędzie bez zważania na zasady dotyczące zakresu ich dostępności
- Stałe nie mogą być ponownie definiowane lub "oddefiniowane" po tym jak raz zostały zdefiniowane
- Stałe mogą zawierać tylko wartości skalarne
Oprócz przechowywania ścieżek do katalogów, stałe mają zastosowanie podczas pisania bibliotek programistycznych. Często zdarza się, że do funkcji musimy przekazać jakąś wartość liczbową identyfikującą konkretny stan. Ponieważ spamiętywanie cyferek jest uciążliwe, tworzy się dla nich stałe o bardziej czytelnych nazwach. Możemy to pokazać na podstawie funkcji error_reporting() pozwalającej ustawić poziom raportowania błędów przez PHP. Da się ją wywoływać w ten sposób:
<?php error_reporting(1 | 2 | 4 | 8); ?>
Lecz dla osoby postronnej cyfry połączone operatorem alternatywy bitowej są zwyczajnie niezrozumiałe. Zamiast cyfr, można użyć odpowiadające im stałe zdefiniowane przez PHP:
<?php error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE); ?>
Kolejnym ciekawym zagadnieniem, którego realizację ułatwiają stałe, jest przekazywanie parametrów do funkcji w sposób pokazany powyżej. Rozpatrzmy przypadek systemu raportowania stanu pojazdu. Stworzyliśmy sobie funkcję raportującą pobierającą pięć wartości logicznych (prawda-fałsz) opisujących, które elementy są sprawne, a które nie. Nasz skrypt wygląda tak:
<?php
// Raportowanie stanu pojazdu
function stan($silnikOK, $kolaOK, $swiatlaOK, $skrzyniaOK, $paliwoOK)
{
if($silnikOK)
{
echo 'Silnik jest sprawny<br/>';
}
if($kolaOK)
{
echo 'Koła są sprawne<br/>';
}
if($swiatlaOK)
{
echo 'Światła są sprawne<br/>';
}
if($skrzyniaOK)
{
echo 'Skrzynia jest sprawna<br/>';
}
if($paliwoOK)
{
echo 'Paliwo jest w baku<br/>';
}
} // end stan();
stan(true, false, false, true, true);
?>
Znów mamy identyczny problem: wywołując funkcję gdzieś w skrypcie musimy pamiętać, jaka jest kolejność parametrów, a postronna osoba już w ogóle nie zrozumie, co do czego ma iść. Wykorzystajmy więc fakt, iż komputer zapisuje wszystko w postaci zerojedynkowej i prześlijmy wszystkie parametry jako jedną liczbę o długości pięciu bitów (od 0 do 32). W stałych zdefiniujemy nazwy poszczególnych elementów przypisując im kolejne potęgi dwójki. Następnie za pomocą operatora alternatywy bitowej zbudujemy z nich liczbę:
<?php
define('SILNIK', 1);
define('KOLA', 2);
define('SWIATLA', 4);
define('SKRZYNIA', 8);
define('PALIWO', 16);
// Raportowanie stanu pojazdu
function stan($stan)
{
if($stan & SILNIK)
{
echo 'Silnik jest sprawny<br/>';
}
if($stan & KOLA)
{
echo 'Koła są sprawne<br/>';
}
if($stan & SWIATLA)
{
echo 'Światła są sprawne<br/>';
}
if($stan & SKRZYNIA)
{
echo 'Skrzynia jest sprawna<br/>';
}
if($stan & PALIWO)
{
echo 'Paliwo jest w baku<br/>';
}
} // end stan();
stan(SILNIK | SKRZYNIA | PALIWO);
?>
Operatorem koniunkcji bitowej możemy sprawdzić, czy dany element jest sprawny. Cały ten sposób jest określany mianem ustawiania flag (każda stała to jedna flaga) i jest powszechnie wykorzystywany w programowaniu ze względu na wydajność. Efektywniej jest przesłać pięć wartości jednym parametrem, niż tyle samo pięcioma. Przyjrzyjmy się zatem tajemnicy tego sposobu. Najpierw - jak reprezentowana jest każda flaga w postaci binarnej:
SILNIK (1): 00001 KOLA (2): 00010 SWIATLA (4): 00100 SKRZYNIA (8): 01000 PALIWO (16): 10000
W każdej liczbie będącej potęgą dwójki "zapalony" jest tylko jeden bit - z tej własności korzystamy. Kiedy składamy kilka potęg dwójki operatorem alternatywy bitowej, zapalamy tym samym poszczególne bity:
SILNIK | SKRZYNIA | PALIWO: SILNIK (1): 00001 SKRZYNIA (8): 01000 PALIWO (16): 10000 ------------------- REZULTAT: 11001
Jeżeli w danej kolumnie którakolwiek z wartości "zapala" bit, będzie on zapalony także w rezultacie. W operatorze koniunkcji bitowej używanym do testowania, dany bit rezultatu jest zapalany jedynie w wypadku jego obecności naraz w obu podanych liczbach:
$rezultat & SWIATLA
REZULTAT: 11001
SILNIK (1): 00001
-------------------
00001
Otrzymujemy liczbę większą od zera, czyli prawdę logiczną. Pytając się o flagę SWIATLA, zostanie wykonane takie działanie:
$rezultat & SILNIK
REZULTAT: 11001
SWIATLA (4): 00100
-------------------
00000
Teraz żadna z jedynek nie powtarza się w obu wierszach naraz, więc działanie da nam wynik 0, czyli fałsz. To jest cała tajemnica tego sposobu.
[edytuj] Referencje
Referencja to swego rodzaju alias na zmienną, dzięki czemu może ona występować pod dwoma nazwami. Popatrzmy na taki przykład:
<?php $a = 5; $b = &$a; // tworzymy referencje echo 'B: '.$b.'; A: '.$a.'<br/>'; $b = 6; echo 'B: '.$b.'; A: '.$a.'<br/>'; $a = 7; echo 'B: '.$b.'; A: '.$a.'<br/>'; ?>
Referencję można utworzyć, stawiając przed zmienną źródłową znak &. Po uruchomieniu przykładu zobaczymy, że modyfikując jedną z tych zmiennych, zmieniała się także druga. Jest tak dlatego, że obie te zmienne reprezentują w rzeczywistości tę samą wartość.
Referencje są bardzo użyteczne przy pracy z dużymi zbiorami danych. Przeprowadzając operację:
$b = &$a;
nie wykonujemy żadnego żmudnego kopiowania tych danych, lecz tworzymy do nich kolejny odnośnik. Jest to wielokrotnie szybsze, lecz sprawia, że modyfikacja "pseudo kopii" odbije się także na oryginale.
Referencje w połączeniu z innymi strukturami nadają im pewne dodatkowe właściwości. Przypomnij sobie pętlę foreach. Wspominaliśmy tam, iż dodatkowe zmienne reprezentujące indeks oraz wartość elementu tablicy są kopiami, dlatego nie można ich jawnie stosować do modyfikowania przetwarzanej tablicy. W PHP 5 ten problem zniknął, ponieważ możemy poinformować PHP, że zmienna wartości ma być referencją. Dzięki temu możliwe będzie modyfikowanie przez nią zawartości tablicy:
<?php
$tablica = array(0 => 1, 2, 3, 4, 5, 6);
foreach($tablica as $id => &$element)
{
$element = rand(1, 6);
}
print_r($tablica);
?>
W tym przykładzie wypełniamy tablicę losowymi numerami poprzez zmienną $element tworzoną przez pętlę. Jest to możliwe, gdyż zadeklarowaliśmy znakiem &, iż ma to być referencja do właściwego elementu. Gdyby usunąć ten znak, funkcja print_r() pokazałaby nam dalej pierwotną zawartość tablicy. Tak się jednak nie dzieje, zatem sposób okazuje się skuteczny.
Dzięki referencjom funkcje mogą pozornie zwracać więcej wartości - poprzez referencyjne parametry. Napiszemy sobie teraz funkcję do kolorowania tekstów dłuższych, niż 5 znaków. W zależności od długości funkcja zwraca wartość 1 lub 0. Zmodyfikowany tekst jest oddawany tą samą drogą, którą się dostał, tj. referencyjnym parametrem. W celu przetestowania przetworzymy sobie dwa ciągi. Jeden liczy sobie trzy znaki, drugi jest wyraźnie dłuższy.
<?php
function przetworz(&$tekst)
{
if(strlen($tekst) > 5)
{
$tekst = '<font color="red">'.$tekst.'</font>';
return 1;
}
return 0;
} // end przetworz();
// Probujemy przerobic krotki tekst
$tekst = 'Jan';
if(przetworz($tekst))
{
echo 'Przetworzony tekst: '.$tekst.'<br/>';
}
else
{
echo 'Błąd: za krótki tekst! '.$tekst.'<br/>';
}
// A teraz dlugi
$tekst = 'Ala ma kota';
if(przetworz($tekst))
{
echo 'Przetworzony tekst: '.$tekst.'<br/>';
}
else
{
echo 'Błąd: za krótki tekst! '.$tekst.'<br/>';
}
?>
Sposób ten także działa, ponieważ w drugim (dobrym) przypadku po wywołaniu funkcji przetworz($tekst) w zmiennej znalazła się zmodyfikowana postać.
Przekazywanie parametrów poprzez referencje ma pewne ograniczenia. Nie można do takiego parametru podać wartości stałej, ponieważ referencja musi operować na zmiennych.
<?php
function funkcja(&$a)
{
}
// proba 1
funkcja('test');
// proba 2
$zmienna = 'test';
funkcja($zmienna);
?>
Pierwszy zapis spowoduje błąd, gdyż nie przekazaliśmy wartości w postaci zmiennej. Wystrzegaj się także wywoływania funkcji ze zmuszaniem do przekazywania referencji, gdy funkcja sobie wyraźnie tego nie życzy: funkcja(&$zmienna). Zapis taki był poprawny jeszcze w PHP 4, lecz w PHP 5 został wycofany i domyślnie pokazuje się ostrzeżenie przy napotkaniu gdziekolwiek takiego fragmentu.
[edytuj] Eval
Niektóre algorytmy pisane przez programistów PHP generują kod w tym języku, który następnie jest wykonywany. Można go zapisać do pliku i załadować instrukcją include, lecz nie zawsze jest to optymalne rozwiązanie. PHP oferuje swoim programistom instrukcję eval, która wykonuje podany w ciągu tekstowym kod jako skrypt PHP.
<?php $zmienna = 5; eval("echo $zmienna;"); ?>
Eval najpierw spowoduje kompilację ciągu echo $zmienna;, a następnie jego wykonanie, czego efektem będzie wyświetlenie się w przeglądarce wartości zmiennej. Nie nadużywaj tej własności zbyt często. Wykonywanie tworzonego przez nas kodu PHP jest ciekawą opcją, lecz mogącą prowadzić do spadku wydajności, trudnych do zauważenia błędów i luk bezpieczeństwa. PHP jest tak zaprojektowany, że nigdy nie potrzeba w nim używać eval(). Oto dwa przykłady, kiedy nie trzeba stosować eval.
1. Chcemy wykonać funkcję, której nazwę mamy zapisaną w zmiennej $funkcja. Choć możemy napisać
eval($funkcja."($a, $b, $c)");
do dyspozycji mamy o wiele lepszy sposób:
$funkcja($a, $b, $c);
2. Chcemy wstawić w wyrażeniu wartość zmiennej, której nazwa zapisana jest w innej zmiennej (np. $zmienna). Zamiast pisać
eval('echo $'.$zmienna.';');
możemy zrobić:
echo $zmienna;
Oba powyższe przykłady wykonają się szybciej, ponieważ są kompilowane wraz z właściwym skryptem. W przypadku eval PHP musi natomiast po raz drugi uruchamiać kompilator.
| Uwaga! Nigdy nie przekazuj do instrukcji eval danych z tablic $_POST, $_GET albo $_COOKIE, ponieważ realnie zagrażasz w ten sposób zarówno swojej aplikacji, jak i serwerowi. |
[edytuj] Każdy popełnia błędy
Błąd w programowaniu jest wynikiem pomyłki/literówki w trakcie pisania aplikacji albo niedostrzeżeniem jakiejś cechy projektowanego algorytmu. Wielu początkujących programistów jest przerażonych, gdy na ekranie pojawią im się tajemnicze komunikaty, a w parze z tym idzie niezaradność. Dlatego w tym rozdziale pragniemy skupić się na zagadnieniu błędu. Omówimy komunikaty zgłaszane przez PHP, techniki pomagające zlokalizować i usunąć błędy, a także powiemy nieco o pakietach testowych od strony teoretycznej, ponieważ do praktyki pozostało nam wciąż parę zagadnień związanych ze składnią.
[edytuj] Komunikaty błędów PHP
Przetworzenie skryptu PHP składa się w istocie z dwóch faz:
- Kompilacji - kod skryptu tłumaczony jest na wewnętrzny zestaw instrukcji interpretera. Faza ta często jest także zwana parsowaniem.
- Wykonywania - właściwe wykonanie skryptu.
Każda z nich generuje nieco inne komunikaty błędów, dzięki czemu wiemy, gdzie szukać przyczyny problemów. Napiszmy sobie taki skrypt:
<?php
if(isset($zmienna)
{
echo $zmienna;
}
?>
Po jego uruchomieniu powinniśmy ujrzeć następujący komunikat:
Parse error: syntax error, unexpected '{' in /home/uzytkownik/www/kurs/aplikacja.php on line 4
Jest to błąd składni powiązany z fazą kompilacji. PHP nie może skompilować skryptu, ponieważ w podanym pliku w linii 4 natrafił na nieprawidłową konstrukcję składniową. Nie oznacza to jednak, że błąd jest akurat w tej linii. Zazwyczaj powoduje go jakaś pomyłka nieco wyżej. Przyjrzyjmy się komunikatowi. Mówi on, że PHP natknął się na niespodziewany nawias klamrowy w linii 4. Rzeczywiście, znajduje się on na swym miejscu tak, jak być powinien. Spójrzmy jednak linijkę wyżej: if(isset($zmienna) - okazuje się, że nie zamknęliśmy jednego z nawiasów. PHP oczekiwał znaku ), lecz zamiast tego dostał { i zgłosił błąd. Po dodaniu brakującego nawiasu skrypt zaczyna poprawnie działać.
Po usunięciu pierwszego błędu postanowiliśmy rozbudować skrypt i wywołaliśmy funkcję inicjującą naszą aplikację WWW.
<?php
if(isset($zmienna))
{
echo $zmienna
inicjujAplikacje();
}
?>
Po uruchomieniu znów pojawił nam się komunikat błędu!
Parse error: syntax error, unexpected T_STRING, expecting ',' or ';' in /home/uzytkownik/www/kurs/aplikacja.php on line 6
Owo tajemnicze T_STRING jest tzw. tokenem. Kompilatory mają tendencję do ułatwiania pracy zarówno sobie, jak i programiście je tworzącemu. Wszystkie elementy pełniące identyczną funkcję, np. zmienne, grupowane są pod jedną wspólną nazwą zwaną tokenem. Tworzenie składni jest teraz bardzo proste. Jeżeli chcemy, aby w jakimś miejscu można było podać dowolną zmienną, używamy tokenu i kompilator już wie, co może tam umieszczać. Informacje o błędnym użyciu tokenu PHP wyświetla w sposób jawny, jak widać na powyższym przykładzie. Komunikat informuje nas teraz, że PHP natknął się na ciąg tekstowy, oczekując przecinka albo średnika. Spójrzmy linijkę wyżej - rzeczywiście, brakuje nam średnika. Mógłbyś zapytać - czemu ciąg tekstowy, skoro niżej jest funkcja? Kompilator po prostu nie dotarł jeszcze do występujących dalej nawiasów, więc wstępnie zaklasyfikował nazwę funkcji jako tekst. Gdyby udało mu się tam dotrzeć, połączyłby z nią nawiasy i powstał nowy token: T_FUNCTION.
Poprawiliśmy usterkę i zainicjowaliśmy zmienną $zmienna jakąś wartością, jednak nasz skrypt nadal nie działa. Tym razem mamy do czynienia z problemem innego kalibru:
Fatal error: Call to undefined function inicjujAplikacje() in /home/uzytkownik/www/kurs/aplikacja.php on line 6
Jest to błąd wykonywania skryptu. PHP poinformował nas w ten sposób, że dotarł do wywołania funkcji inicjujAplikacje(), lecz taka nie istnieje. Pozostało nam jedynie odkryć przyczynę tego stanu - może nie dołączyliśmy jakiejś ważnej biblioteki? Zwróć uwagę, że PHP sprawdza istnienie funkcji w momencie wykonywania skryptu, a nie kompilacji. Gdybyśmy nie zainicjowali zmiennej $zmienna, kod wewnątrz instrukcji if nie zostałby wykonany i problem pozornie by nie wystąpił. To oczywiście tylko złudzenie. On tam jest, lecz PHP nie wykonał tego kawałka kodu i go nie zaraportował. Spróbuj wykonać taki eksperyment. Praktyka ta ma swoje uzasadnienie - przypomnij sobie instrukcję eval z końca poprzedniego rozdziału. Tam nazwa funkcji była ustalana w momencie wykonywania, gdyż tylko wtedy mają prawo istnieć jakiekolwiek zmienne.
Komunikaty Fatal error pojawiają się zawsze wtedy, gdy w trakcie wykonywania wystąpi problem uniemożliwiający dalszą pracę interpreterowi. Przykładem jest podanie do instrukcji require nazwy nieistniejącego pliku. PHP go nie odnajdzie i zatrzyma wykonywanie. Warto nadmienić, że użycie include spowoduje "tylko" pokazanie się ostrzeżenia, a skrypt będzie dalej wykonywany.
Innym rodzajem komunikatu o błędzie jest Warning, czyli ostrzeżenie. W odróżnieniu od Fatal error lub Parse error, Warning nie przerywa działania naszego skryptu. Przykładem pojawiania się tego komunikatu może być podanie nieprawidłowych parametrów w jakiejś funkcji:
<?php $tekst = 'to jest jakiś tekst'; sort($tekst); ?>
Widzimy, że zmienna $tekst nie jest tablicą, więc użycie tej zmiennej w funkcji sort, która służy do sortowania tablic, wywołuje pojawienie się komunikatu o błędzie typu Warning:
Warning: sort() expects parameter 1 to be array, string given in /home/uzytkownik/www/kurs/aplikacja.php on line 3
Mówi on, że funkcja sort() jako parametr pierwszy może przyjąć tylko tablice, a dostała co innego.
Ostatnim z omówionych komunikatów błędów będą tzw. notices, czyli powiadomienia. Mają one niski priorytet i służą do informowania o miejscach, które potencjalnie mogą stanowić źródło problemu. Oto jedna z takich sytuacji:
<?php echo $zmienna; ?>
Skrypt próbuje tutaj wyświetlić zawartość zmiennej $zmienna, lecz nie jest ona zdefiniowana. Może to być zarówno świadome działanie (wiemy, że zmienna może w tym miejscu nie istnieć, ale dopuszczamy to) lub też wynik literówki w jej nazwie, co oczywiście niosłoby dla nas poważne konsekwencje. Dlatego interpreter na wszelki wypadek wyświetli w tym miejscu komunikat:
Notice: Undefined variable: zmienna in /home/uzytkownik/www/kurs/aplikacja.php on line 2
Podobny komunikat pojawi się, gdy spróbujemy odczytać wartość nieistniejącego elementu tablicy. Do dobrych zwyczajów należy takie pisanie skryptów, aby nie generowały one tego typu komunikatów z kilku powodów:
- Jeśli nasz skrypt będą też rozwijać inni programiści, mogą mieć oni ustawiony wysoki poziom raportowania błędów, przez co właściwa treść strony będzie im ginąć w setkach powiadomień.
- Wiele serwerów, pomimo ryzyka związanego z bezpieczeństwem, pozostawia włączony wysoki poziom raportowania błędów. Z identycznego powodu nie będzie można używać tam naszego skryptu.
- Brak komunikatów jest wyrazem dbałości o sytuacje wyjątkowe oraz możliwość ich obsługi.
Używaj komendy isset() do sprawdzania, czy wymagane w danym fragmencie zmienne istnieją.
[edytuj] Operator @
Jeżeli dowolne wyrażenie PHP poprzedzimy znakiem @, interpreter nie wyświetli żadnego komunikatu, jeżeli wykryje w nim błąd. Nie oznacza to naturalnie, że błąd ten nagle znika. On tam jest, lecz my nie jesteśmy o tym powiadamiani. Dlatego należy bardzo rozważnie postępować z jego użyciem. @ przydaje się głównie przy funkcjach do komunikacji z zewnętrznymi źródłami danych, np. plikami. Jeżeli PHP wykryje np. brak żądanego pliku, nie tylko generuje określony wynik, ale także pokazuje komunikat Warning, co nie zawsze jest zjawiskiem pożądanym. Operatorem tym możemy także "zakazać" wyświetlania komunikatów Notice o niezdefiniowaniu zmiennej, jeżeli tego naprawdę potrzebujemy. Poniżej pokazany przykład ma za zadanie pokazać datę modyfikacji pliku lub nasz własny komunikat, jeżeli on nie istnieje. Na początek wariant najprostszy:
<?php
echo filemtime('plik.txt');
?>
Kiedy plik.txt nie będzie istnieć, interpreter wygeneruje stosowny komunikat, którego my w takiej formie oglądać nie chcemy. Dlatego wcześniej sprawdzimy drugą funkcją istnienie pliku.
<?php
if(file_exists('plik.txt'))
{
echo filemtime('plik.txt');
}
else
{
echo 'Podany plik nie istnieje.';
}
?>
Co dwie funkcje to nie jedna, lecz pojawia się nam tu problem wydajności. Odczyt czegokolwiek z twardego dysku stanowi duży narzut czasowy dla oprogramowania, dlatego powinniśmy się starać wykonywać jak najmniej takich operacji. Wiemy z dokumentacji, że samo filemtime() zwraca nam false, jeśli plik nie istnieje, przeszkadza nam tu jedynie "firmowe" ostrzeżenie. Dlatego właśnie skorzystamy z @, aby się go pozbyć:
<?php
$wynik = @filemtime('plik.txt');
if($wynik !== FALSE)
{
echo $wynik;
}
else
{
echo 'Podany plik nie istnieje.';
}
?>
Wynik funkcji zapisujemy w zmiennej, ponieważ jest on nam potrzebny w paru miejscach. Aby nie dostawać komunikatu Warning, poprzedziliśmy wywołanie funkcji operatorem @.
Wszystko można zapisać inaczej.
<?php
@echo filemtime('plik.txt') or die('Podany plik nie istnieje.');
?>
| Uwaga! W powyższym przykładzie założono, że po poleceniu echo nic więcej nie ma. Jeśli jest inaczej, dalsza część skryptu nie zostanie wykonana! |
| Porada Zanim użyjesz operatora @, trzy razy się zastanów, czy naprawdę jest Ci on w tym miejscu do czegoś konkretnego potrzebny. |
[edytuj] Poziom raportowania błędów
Poziom raportowania błędów określa, na które zdarzenia PHP ma reagować wyświetleniem stosownego komunikatu. Docelowo ustawia się go w pliku konfiguracyjnym php.ini, w dyrektywie error_reporting. Podczas instalacji według tego podręcznika, zaleciliśmy użycie najwyższego możliwego poziomu E_ALL | E_STRICT, który zgłasza dosłownie wszystko. Dzięki temu możemy w porę reagować na wszystko i mieć pewność, że po zainstalowaniu aplikacji na innym serwerze nie zostaniemy zasypani toną komunikatów. Jednak takie ustawienie należy stosować tylko w fazie tworzenia/testowania naszych skryptów. Gdy już umieścimy je na stronie internetowej, należy wyłączyć wyświetlanie wszystkich błędów, ponieważ takie komunikaty często ujawniają wiele informacji o skrypcie i używanym oprogramowaniu, które mogą ułatwić atak na nasz serwis.
Poziom raportowania można też tymczasowo zmieniać z poziomu skryptu funkcją error_reporting():
<?php error_reporting(E_ALL ^ E_NOTICE); echo 'Teraz komunikaty Notice nie działają: '.$nieistniejacaZmienna; ?>
Funkcja ta ma jeszcze jedną użyteczną właściwość, mianowicie zwraca jako rezultat dotychczasowy poziom raportowania, dzięki czemu możemy go potem przywrócić:
<?php $poprzedni = error_reporting(E_ALL ^ E_NOTICE); echo 'Teraz komunikaty Notice nie działają: '.$nieistniejacaZmienna; error_reporting($poprzedni); echo 'A teraz już tak: '.$nieistniejacaZmienna; ?>
[edytuj] Techniki debugowania
Inny rodzaj błędów związany jest z algorytmami produkującymi niewłaściwe rezultaty. Odnaleźć przyczynę problemu jest tu znacznie trudniej, ponieważ nie jesteśmy raczeni żadnymi komunikatami i musimy sami dochodzić, co do czego. Jedynym sposobem jest poumieszczanie w kodzie na chwilę własnych informacji, które dokładnie powiadomią nas, które fragmenty są wykonywane w jakiej kolejności i z jakimi danymi. Metoda wywodzi się w prostej linii z języka C/C++, gdzie w kodzie umieszcza się tzw. asercje. Jeżeli kompilujemy kod programu w trybie debug (wykrywanie błędów), w oznaczanych przez nas miejscach dodawane są odpowiednie instrukcje sprawdzające poprawność danych i raportujące ewentualne problemy. Kiedy kompilujemy program w wersji "oficjalnej", preprocesor nie umieszcza już tych instrukcji w kodzie.
PHP żadnego preprocesora nie ma (można nawet powiedzieć, że sam nim jest dla języka HTML), dlatego sami musimy poumieszczać w kodzie odpowiednie wstawki. Najprostszą z nich jest echo 'Test';. Komendę taką umieszczamy, chcąc sprawdzić, czy algorytm dociera do danego miejsca. Po uruchomieniu, jeśli na ekranie pokaże nam się rzeczony napis, oznacza to, iż akurat to miejsce działa dobrze. Instrukcją echo możemy także wyświetlać dane lub sprawdzać kolejność wykonywania operacji, wstawiając różne komunikaty. Komenda die() nie tylko wyświetla komunikat, ale także zatrzymuje skrypt. Do wyświetlania zawartości tablic lub obiektów możemy zastosować funkcje var_dump() albo print_r(). Po usunięciu usterki wszystkie poczynione przez nas wstawki usuwamy z kodu.
PHP posiada specjalną funkcję obsługi asercji: assert(). Generuje ona komunikat błędu, jeżeli podane jej wyrażenie ma wartość false:
<?php $zmienna = 3; assert($zmienna == 6); ?>
Jednak to jeszcze nie wszystko. Musimy funkcją assert_options() włączyć to sprawdzanie:
<?php assert_options(ASSERT_ACTIVE, 1); assert_options(ASSERT_WARNING, 1); $zmienna = 3; assert($zmienna == 6); ?>
Po skończeniu debugowania wcale nie musimy usuwać wywołań funkcji assert(). Wystarczy ustawić opcję ASSERT_ACTIVE na 0 i przestaje być ona aktywna.
Do debugowania skryptów PHP mogą być także przydatne zewnętrzne moduły w stylu Xdebug (dostępny w repozytorium PECL tj. http://pecl.php.net/package/Xdebug a nawet jako paczka w niektórych dystrybucjach Linuksa). Rozwiązania takie są dosyć ciekawe, ponieważ pokazują znacznie więcej użytecznych dla programisty informacji. Przykładowo, Xdebug w momencie wystąpienia błędu pokazuje całą listę wywołań funkcji nadrzędnych. Rozważmy sytuację, gdy pewnej funkcji używamy w kilku modułach, lecz co jakiś czas generuje ona błąd. Dzięki obecności listy możemy natychmiast określić, który moduł wywołuje ją nieprawidłowo. Xdebug można również skonfigurować do pracy z zewnętrznymi środowiskami IDE do tworzenia skryptów (np. PDT wchodzący w skład pakietu Eclipse).
[edytuj] Pakiety testowe
Bardziej złożone aplikacje pisze się w oparciu o tzw. pakiety testowe. Mając projekt naszej aplikacji, programista buduje zwyczajny szkielet tworzonego modułu oraz układa dla niego zestaw testów. Następnie uzupełnia szkielet funkcjonalnym kodem tak, aby zdać wszystkie testy. Wykonywaniem testów nie musi zajmować się samodzielnie, gdyż stworzone zostały odpowiednie pakiety, np. PHPUnit. Naszym zadaniem jest tylko napisanie procedur testowych, korzystając z przygotowanych narzędzi, a opracowaniem wyników zajmie się pakiet.
Programowanie inicjowane testami, bo tak się ten styl pisania nazywa, ma jeszcze jedną użyteczną cechę. Wydając nowe wersje modułu, możemy wykonywać testy do sprawdzenia, czy moduł wciąż zachowuje się zgodnie z wytycznymi i nie sprawi problemów innym częściom aplikacji. Szczegółów implementacji jednak teraz nie poznamy, ponieważ pakiety pisze się z wykorzystaniem programowania obiektowego, którego jeszcze nie znamy. Do tego zagadnienia wrócimy w dalszej części podręcznika.
[edytuj] Korzystanie z dokumentacji
Najważniejszym miejscem dla każdego programisty PHP jest dokumentacja projektu. Jest ona o tyle ważna, że informacja o nowościach w składni w pierwszej kolejności pojawia się właśnie tutaj. Źródła w rodzaju artykułów niekoniecznie są aktualizowane wraz z nowymi wersjami i nie można się na nich w pełni opierać. Dokumentacja przydaje się też przy poszukiwaniu nowych funkcji, modułów lub mało znanych możliwości.
Angielska wersja dokumentacji dostępna jest pod adresem www.php.net/manual/en. Istnieje także polska, lecz jest ona bardzo rzadko uaktualniana, a ilość i jakość przetłumaczonych rozdziałów pozostawiają nieco do życzenia. Przyjrzyjmy się zatem jej strukturze.
Dokumentacja PHP powstaje w oparciu o XML-ową aplikację DocBook, dlatego jej struktura jest bardzo uporządkowana. W całym tekście przyjęte są jednolite konwencje stylistyczne, a nawigacja jest intuicyjna. Kody źródłowe drukowane są na szarym tle, z kolorowaną składnią, jeżeli dotyczy ona źródeł PHP. W wielu miejscach umieszczone są uwagi, ostrzeżenia oraz porady. Na dole każdej strony znajduje się sekcja z komentarzami programistów. Naprawdę warto od niej zaczynać studiowanie dokumentacji, ponieważ często znajdują się tam o wiele ciekawsze przykłady użycia lub naprawdę przydatne sztuczki i algorytmy. Ilość komentarzy do najpopularniejszych funkcji jest ogromna i obejmuje niemal wszystko: własne implementacje, przykłady i komentarze odnośnie użycia, linki do artykułów oraz inne.
[edytuj] Instalacja PHP
Informacje dotyczące instalowania PHP znajdują się w rozdziale II. Opisane zostały tam przykłady oraz dodatkowe wiadomości dla wszystkich platform i serwerów. Nie wystarczą one jednak do skonfigurowania PHP do własnych potrzeb, ponieważ spis dyrektyw znajduje się gdzie indziej.
Spis dyrektyw konfiguracyjnych dla systemów Unix znajduje się w rozdziale IX ("Dodatki") i oznaczony jest literą "F": "Configure options". Nie zawiera on jednak spisu komend aktywujących poszczególne moduły. Te należy sobie odszukać na stronie poświęconej każdemu z nich.
Pozycję niżej znajduje się wykaz dyrektyw pliku php.ini, także bez listy modułów. Ogólnie wszystko, co związane z instalowaniem jakiegokolwiek rozszerzenia, znajduje się na poświęconej mu stronie dokumentacji w sekcji "Installation".
[edytuj] Składnia języka
Aby dowiedzieć się, jak przedstawia się składnia języka, należy zajrzeć do rozdziału "Language reference". Jest to typowy spis dostępnych komend, instrukcji oraz elementów wyrażeń ułożony tematycznie. Najbardziej przydatny będzie dla programistów mających już pewną wprawę w tworzeniu aplikacji oraz skryptów, gdyż opisy nie są tak przystępne, jak w kursach i książkach. Ich zadaniem jest dostarczenie wyczerpującej wiedzy o danym elemencie i wypisanie wszystkich możliwych zagadnień z nim związanych.
Rozdział poświęcony programowaniu obiektowemu jest zdublowany. W PHP 5 ten element został napisany całkowicie od nowa, tracąc część kompatybilności ze starymi wersjami i stąd konieczność podania dwóch osobnych opisów w zależności od posiadanej wersji. Do części dla PHP 4 polecamy już sięgać jedynie z powodów historycznych.
[edytuj] "Security" oraz "Features"
W tych dwóch rozdziałach znajdują się informacje praktyczne pokazujące niektóre aspekty charakterystyczne dla PHP. Tutaj możesz dowiedzieć się o:
- Ładowaniu plików na serwer
- Bezpieczeństwie skryptów
- Kilku niezbyt lubianych przez programistów opcjach: Register globals oraz Magic quotes.
- Metodach raportowania błędów
- Obsłudze ciastek i sesji
- Pisaniu skryptów dla tzw. trybu bezpiecznego wykorzystywanego na produkcyjnych serwerach.
[edytuj] Spis funkcji
Jest to jeden z najważniejszych rozdziałów dla programisty. Zawiera spis wszystkich funkcji pogrupowanych według modułów. Na początku każdego podrozdziału znajduje się skrótowy opis rozszerzenia, wymagania oraz sposób jego zainstalowania. Należy tu zwrócić uwagę na następujące frazy:
No external libraries are needed to build this extension.
To rozszerzenie nie wymaga obecności żadnych dodatkowych bibliotek w systemie.
There is no installation needed to use these functions; they are part of the PHP core.
Funkcje te są częścią jądra PHP i nie da się ich wywalić. Są zatem zawsze dostępne.
The SimpleXML extension is enabled by default. To disable it, use the --disable-simplexml configure option.
Komunikat w tym stylu oznacza, że domyślnie rozszerzenie to jest zawsze aktywne, ale istnieje możliwość jego wyłączenia.
Następnie na stronie znajduje się kilka prostych przykładów i lista stałych definiowanych przez dany moduł. Na końcu wyszczególniony jest alfabetyczny spis wszystkich funkcji modułu wraz z lakonicznie zaznaczonym działaniem. Aby dowiedzieć się więcej, należy po prostu kliknąć na wybraną funkcję.
Opis funkcji zaczyna się od składni. Wygląda on mniej więcej tak:
bool setcookie ( string name [, string value [, int expire [, string path [, string domain [, bool secure]]]]] )
Zaczyna się on od nazwy typu danych zwracanego przez funkcję. W dokumentacji stosowana jest następująca konwencja:
- int, string, float, bool - skalarne typy danych: liczby, ciągi tekstowe, wartości logiczne.
- array - tablica
- mixed - funkcja może zwrócić jeden z kilku rodzajów typów. W tym wypadku trzeba samodzielnie doczytać w opisie, co jest zwracane w jakich sytuacjach.
Jeżeli funkcja zwraca obiekt, podawana jest jego nazwa.
W nawiasach okrągłych podany jest spis parametrów, również z zaznaczonymi typami. Nawiasy kwadratowe oznaczają parametry opcjonalne. Z podanego powyżej przykładu wynika, że możemy podać:
- nazwę
- nazwę i wartość
- nazwę, wartość i czas wygaśnięcia
- nazwę, wartość, czas wygaśnięcia i ścieżkę
- nazwę, wartość, czas wygaśnięcia, ścieżkę i domenę
- nazwę, wartość, czas wygaśnięcia, ścieżkę, domenę oraz nakaz wysłania ciastka połączeniem szyfrowanym
Dokładniejsze informacje, co robi jaki parametr, znajdują się w opisie funkcji. Kończy się on sekcją "See also" odsyłającą do funkcji podobnych lub uzupełniających zawarte tu informacje.
W przypadku nowych, obiektowych rozszerzeń, nazwa funkcji jest dwuczęściowa: obiekt::funkcja.
Do PHP stworzono już bardzo dużo modułów i ogrom ich spisu może przerazić. Oto krótki przewodnik, gdzie co można znaleźć:
- Arrays - wszystko, co związane z tablicami
- ctype - sprawdzanie zawartości ciągów tekstowych
- Exif - wyciąganie dodatkowych informacji z plików JPG i TIFF
- Filesystem - operacje na plikach
- Function handling - zarządzanie funkcjami
- HTTP - kilka funkcji do protokołu HTTP (np. wysyłanie ciastek)
- Image functions - biblioteka GD do generowania obrazków
- Math - funkcje matematyczne
- Misc. - funkcje niepasujące nigdzie indziej (np. kolorowanie składni)
- MySQL - najstarszy zbiór funkcji do komunikacji z bazą MySQL
- mysqli - "Improved MySQL", komunikacja z nowymi wersjami bazy MySQL.
- Network - funkcje sieciowe
- Output Control - buforowanie wyjścia skryptu
- PDO - biblioteka PDO do komunikacji z różnymi bazami danych, którą niebawem poznamy.
- spl - "Standard PHP Library", obiektowe nakładki na wiele podstawowych operacji.
- Strings - operacje na ciągach tekstowych
- Variables handling - rozpoznawanie typów zmiennych itd.
Zachęcamy do przejrzenia tych rozdziałów i zorientowania się w ich strukturze. Dobra orientacja w dokumentacji PHP sprawia, że nie trzeba nawet przykładać dużej wagi do "wkuwania" funkcji na pamięć.
[edytuj] Studium przypadku: Księga gości
Dotychczasowe rozdziały miały w sobie więcej teorii i nie ukazywały prawdziwej istoty tworzenia skryptów dla stron WWW. Najlepszym rodzajem nauki jest praktyczne stworzenie skryptu z prawdziwego zdarzenia i pokazanie w ten sposób, jak poszczególne elementy języka ze sobą się łączą. W tym rozdziale stworzymy samodzielnie funkcjonalną księgę w PHP. Wykorzystamy w niej pętle, tablice, instrukcje warunkowe, formularze oraz dołączanie plików zewnętrznych. Ponadto poznamy kilka interesujących funkcji.
[edytuj] Plan
Księgę gości oprzemy o pliki tekstowe. Każdy wpis będzie charakteryzowany przez:
- Tytuł
- Autora
- Datę
- Adres WWW (opcjonalny)
- Treść
Wszystkie te dane zostaną zapisane w pliku w pojedynczym wierszu, a oddzielone będą znakiem "|". Aby wyeliminować sytuację, gdy ktoś wpisze np. w treść taki znak, albo (co gorsza) naciśnie enter, każda kolumna zostanie zakodowana w formacie Base64. Oczywiście nie będziesz musiał sam pisać odpowiedniego algorytmu - PHP posiada odpowiednie funkcje. Base64 został pierwotnie zaprojektowany do przesyłania 8-bitowych wiadomości w 7-bitowych protokołach. Do zapisu są tu używane wyłącznie litery, cyfry, ukośnik oraz znak równości, zatem nie ma obawy o uszkodzenie formatu pliku księgi. Zakodowana wiadomość jest o ok. 33% dłuższa od oryginału.
Księga nie będzie zapisywać i odczytywać z pliku bezpośrednio. W zewnętrznym pliku napiszemy sobie dwie funkcje zajmujące się wyłącznie tym. Księga będzie miała dostęp do danych jedynie za ich pomocą. Rozwiązanie to jest bardzo użyteczne. Kiedy poznasz już bazy danych, z pewnością zapragniesz przesiąść się właśnie na nie. Nie będziesz musiał przepisywać całej księgi. Po prostu napiszesz do tych dwóch funkcji nowy kod. Dzięki takiemu rozwiązaniu źródła skryptu będą uporządkowane i czytelne, a ponadto bardzo łatwe do modyfikacji.
[edytuj] dane.php
Plik dane.php zawierać będzie dwie funkcje: dodajWpis() oraz pobierzWpisy() zajmujące się wyłącznie operowaniem na danych. Będziemy go pisać po kawałku.
<?php
define('WPISY', './wpisy.txt');
function dodajWpis($tytul, $autor, $www, $tresc)
{
// Ucinanie bialych znakow
$tytul = trim($tytul);
$autor = trim($autor);
$www = trim($www);
$tresc = trim($tresc);
Nazwę pliku zapisaliśmy za pomocą stałej. To na wypadek, gdyby zachciało nam się go kiedyś przenieść albo zmienić mu nazwę. Pierwszy etap obrabiania danych to ucięcie białych znaków (spacji, tabulatorów itp.) z początku i końca każdego ciągu. Nie są nam one do niczego potrzebne, a jedynie utrudniają nam sprawę.
// Kontrola danych
if(strlen($tytul) < 3)
{
return false;
}
if(strlen($autor) < 3)
{
return false;
}
if(strlen($tresc) < 10)
{
return false;
}
if(strlen($www) > 0)
{
// Jesli adres nie zaczyna sie od http:// to dodaj to
if(strpos($www, 'http://') !== 0)
{
$www = 'http://'.$www;
}
}
Tutaj zajmujemy się kontrolą danych. Tytuł i autor muszą mieć przynajmniej trzy znaki, a treść 10. Długość pobieramy funkcją strlen(). Jeżeli podaliśmy adres WWW, za pomocą funkcji strpos() sprawdzamy, czy na jego początku jest na pewno dodany identyfikator protokołu. Zwróć uwagę na użyty operator: !==. Wspomniana funkcja zwraca pozycję pierwszego znaku szukanego ciągu (liczoną od zera), a jeżeli go nie znajdzie, zwraca false. Normalnie 0 i false są równoważne, dlatego musimy wymusić sprawdzenie także typu, bowiem nam chodzi o 0 jako pozycję identyfikatora w adresie, a nie informację, że go nie ma.
Jeżeli któraś z informacji nie będzie prawidłowo podana, funkcja zwróci do skryptu wartość false.
// Dodawanie
$f = fopen(WPISY, 'a');
$dane = array(0 =>
base64_encode(htmlspecialchars($tytul)),
base64_encode(htmlspecialchars($autor)),
time(),
base64_encode(htmlspecialchars($www)),
base64_encode(nl2br(htmlspecialchars($tresc)))
);
fwrite($f, implode('|', $dane)."\r\n");
fclose($f);
return true;
} // end dodajWpis();
Ostatni akord to zapisanie wpisu w pliku. Na początek otwieramy go w trybie dopisywania (parametr a), następnie budujemy tablicę z danymi wpisu. Parametry przybyłe z formularza kodujemy w Base64, środkowa kolumna to czas dodania wpisu w sekundach od 1.1.1970. Dzięki takiemu jego zapisowi, będziemy go mogli później dowolnie formatować funkcją date(). Przy przetwarzaniu tekstu użyliśmy jeszcze kilku funkcji:
- htmlspecialchars() - wszystkie wprowadzone tagi HTML są zamieniane na zwykły tekst (ograniczniki są zastępowane odpowiadającymi im encjami, np. < zmienia się w <). W ten sposób nikt nie rozwali nam księgi złośliwym kodem.
- nl2br() - zamienia znaki nowej linii na znaczniki <br/>.
- implode() - łączy tablicę w ciąg tekstowy, wstawiając pomiędzy poszczególne elementy podany w pierwszym parametrze znak. Na końcu utworzonego przez nią rekordu wpisu musimy dodać samodzielnie znaki zejścia do nowej linii: \r\n, koniecznie w cudzysłowach, a nie w apostrofach.
Zwróć uwagę na rozbudowane wywołania funkcji:
base64_encode(nl2br(htmlspecialchars($tresc)))
Oznacza ona, że najpierw zawartość zmiennej $tresc trafia do funkcji najbardziej w prawo, tj. htmlspecialchars(). Z niej przechodzi do nl2br(), a z niej do base64_encode(). Alternatywne kursy preferują w tym miejscu czytelną, ale mniej wydajną formę zapisu:
$tresc = htmlspecialchars($tresc); $tresc = nl2br($tresc); $tresc = base64_encode($tresc);
Nasz kod ma tę przewagę, że wynik jednej funkcji od razu trafia do drugiego, tymczasem powyżej po drodze trafia do zmiennej, co ma szczególnie negatywny wpływ na wydajność przy dużych danych. Ponadto zapis ten często nie jest opatrzony stosownym komentarzem i wyrabia złe nawyki tworzenia mnóstwa niepotrzebnych zmiennych tymczasowych, o czym już wspominaliśmy. Trzeba zapamiętać, że nawet podawany przez nas wariant konstrukcji księgi nie jest "tym jedynym słusznym". Istnieje jeszcze wiele innych sposobów jej zaprogramowania.
Przyszła kolej na funkcję pobierającą wpisy.
function pobierzWpisy()
{
$wpisy = array_reverse(file(WPISY));
$i = 1;
$rezultat = array();
Wpisy pobieramy funkcją file(), która automatycznie rozbija nam plik na tablicę względem wierszy. Tam jednak najnowsze wpisy są na końcu, a my chcemy je wyświetlić w kolejności odwrotnej: najnowsze na górze. Dlatego od razu przepuszczamy wynik przez funkcję array_reverse() odwracającą tablicę. Następnie inicjujemy licznik wpisów $i oraz tablicę $rezultat, do której będziemy pakować sformatowane odpowiednio wpisy. Ją później przekażemy księdze do wyświetlenia.
foreach($wpisy as $wpis)
{
$wpis = explode('|', trim($wpis));
$rezultat[] = array(
'id' => $i,
'tytul' => base64_decode($wpis[0]),
'autor' => base64_decode($wpis[1]),
'data' => date('d.m.Y, H:i', $wpis[2]),
'www' => base64_decode($wpis[3]),
'tresc' => base64_decode($wpis[4])
);
$i++;
}
return $rezultat;
} // end pobierzWpisy();
?>
Po kolei formatujemy każdy wpis. Do ich pobierania wykorzystaliśmy pętlę foreach(). Na wejściu przepuszczamy je przez funkcję trim(), bowiem file() pozostawia w tablicach znaki zejścia do nowej linii. Dopiero po ich usunięciu możemy funkcją explode() rozbić ciąg z powrotem na tablicę (czyli wykonać operację odwrotną do implode()). Wpis przepuszczamy przez base64_decode(), aby zdekodować informacje, formatujemy datę i pakujemy to do tablicy $rezultat. Później zwracamy ją.
[edytuj] ksiega.php
W pliku tym znajdzie się główna część funkcjonalna księgi. To jego powinniśmy uruchamiać z poziomu przeglądarki. Zaczynamy jego pisanie od nagłówka HTML oraz dołączenia uprzednio napisanego pliku:
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pl" lang="pl">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Księga gości Wikibooks</title>
</head>
<body>
<h1>Księga gości Wikibooks</h1>
<?php
require('./dane.php');
Właściwy kod rozpocznie się instrukcją warunkową, która zadecyduje, czy wysłany został formularz dodawania, czy też wystarczy wyświetlić aktualną zawartość księgi. PHP zapisuje informację o użytej metodzie wysyłania żądania w specjalnej zmiennej $_SERVER['REQUEST_METHOD'], która może przyjąć wartości POST (formularz) albo GET (zwyczajne żądanie). Z niej właśnie skorzystamy:
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
// Dodawanie wpisu
if(dodajWpis($_POST['tytul'], $_POST['autor'], $_POST['www'], $_POST['tresc']))
{
echo '<p>Dziękujemy, wpis został dodany prawidłowo.</p>';
}
else
{
echo '<p>Proszę wypełnić prawidłowo formularz.</p>';
}
echo '<p><a href="ksiega.php">Powrót</a></p>';
}
Kontrola danych jest realizowana w pliku dane.php i nie ma potrzeby jej tu powtarzać. Wprowadzamy do funkcji dodajDane() poszczególne pola formularza i ifem sprawdzamy, jaki jest rezultat, aby móc wygenerować stosowny komunikat.
Jeżeli nie dodajemy aktualnie żadnego nowego wpisu, wyświetlamy te, które już mamy:
else
{
// Wyświetlanie wpisów
$wpisy = pobierzWpisy();
foreach($wpisy as $wpis)
{
echo '<hr /><p><b>Tytuł: <i>'.$wpis['tytul'].'</i>;
Autor: '.$wpis['autor'].'; Data: '.$wpis['data'];
if(strlen($wpis['www']) > 0)
{
echo '; <a href="'.$wpis['www'].'" target="_blank">Strona WWW</a>';
}
echo '</b></p>';
echo '<p>'.$wpis['tresc'].'</p>';
}
?><hr />
Tablicę z wpisami dostajemy z funkcji pobierzWpisy(). Skanujemy ją pętlą foreach i wyświetlamy każdy z elementów. Zauważ, że skrypt potrafi ominąć pole z adresem WWW, jeśli ten nie został podany. Po prostu funkcją strlen() sprawdzamy, czy jego długość jest większa od zera.
Ostatnim akordem będzie dodanie formularza HTML:
<form method="post" action="ksiega.php"> <table border="0" width="50%"> <tr> <td>Tytuł</td> <td><input type="text" name="tytul"/></td> </tr> <tr> <td>Autor</td> <td><input type="text" name="autor"/></td> </tr> <tr> <td>WWW</td> <td><input type="text" name="www"/></td> </tr> <tr> <td>Treść</td> <td><textarea name="tresc" rows="4" cols="50"></textarea></td> </tr> <tr> <td></td> <td><input type="submit" value="Dodaj"/></td> </tr> </table> </form> <?php } ?> </body> </html>
Formularz wysyłamy metodą POST do pliku ksiega.php. Jego wysłanie spowoduje uaktywnienie się dodawania wpisów. Przed wysłaniem ostatnich znaczników musimy dodać jeszcze klamrę kończącą instrukcję warunkową, która decyduje, co należy wykonać. Umieszczenie jej w tym miejscu gwarantuje, że formularz pokaże się tylko podczas wyświetlania wpisów. Jeśli skrypt będzie wysyłać komunikat o dodaniu wpisu, nie zostanie on już dołączony.
To już koniec naszej księgi gości. Utwórz teraz pusty plik wpisy.txt i zacznij korzystanie.
[edytuj] Co dalej?
Napisana tutaj księga nie jest wyrafinowanym szczytem techniki. Jej głównym zadaniem było pokazanie podstawowych mechanizmów tworzenia dynamicznych stron WWW w praktyce. Wraz z powiększaniem się twojej wiedzy o języku PHP możesz rozszerzać ją o nowe możliwości:
- Blokadę przed floodem.
- Kasowanie wpisów.
- Dzielenie wyników na strony.
- Łatwy w modyfikacji wygląd.
Niektóre z tych rzeczy będą znacznie łatwiejsze do wykonania, kiedy poznasz zasady pracy z bazami danych dającymi nieporównywalnie większe możliwości, niż pliki tekstowe. Na bazach danych oparta jest obecnie zdecydowana większość istniejących aplikacji internetowych. Dzięki temu, że odseparowaliśmy pobieranie danych od ich wyświetlania, przepisanie księgi na MySQL będzie jedynie zabiegiem kosmetycznym. Zdecydowanie polecamy korzystanie z takiego rozwiązania, gdyż daje programiście sporą elastyczność. Nie zawsze bowiem zdarza się, że organizacja naszych danych będzie dokładnie taka, jakiej życzyłby sobie klient. Programując wszystko "na sztywno" wraz z wyglądem i kodem funkcjonalnym aplikacji narażamy się na większe ryzyko popełnienia błędu, zwiększamy rozmiar kodu i czynimy go mniej czytelnym.
Na tym kończymy poznawanie podstaw języka PHP. Przyszła pora na dokładniejsze poznanie kilku użytecznych funkcji przydatnych podczas tworzenia własnych skryptów.
[edytuj] Przetwarzanie tekstu
Największa ilość wykonywanych w PHP operacji dotyczy danych tekstowych oraz ich obróbki. Poznamy tutaj kilkanaście funkcji, które będą nam pomocne. W następnym rozdziale zaznajomimy się natomiast z podstawami wyrażeń regularnych pozwalających na znacznie bardziej zaawansowane manipulacje oraz dokładniejszą kontrolę poprawności.
[edytuj] Wyszukiwanie ciągów
Podstawową funkcją wyszukiwania jednego ciągu w drugim jest strpos() posiadająca także wariant stripos(), w którym nie gra roli wielkość liter. Użyliśmy jej już raz podczas pisania księgi gości do sprawdzenia, czy wprowadzony adres WWW zawiera identyfikator protokołu. Przypomnijmy jeszcze raz ten skrypt:
<?php
$adresy = array(0 =>
'http://www.wikibooks.pl',
'www.wikibooks.pl'
);
foreach($adresy as $adres)
{
if(strpos($adres, 'http://') === 0)
{
// jest http://
echo '<p>'.$adres.'</p>';
}
else
{
// nie ma http://
echo '<p>http://'.$adres.'</p>';
}
}
?>
Funkcje działają następująco: za pierwszy parametr podajemy ciąg do przeszukania, za drugi - szukany, a następnie patrzymy na rezultat. strpos() powinien zwrócić nam pozycję, od której zaczyna się tekst, którego szukamy, przy czym znaki liczone są od zera. Jeżeli nic nie zostanie znalezione, dostaniemy wartość false. Z tego powodu powinniśmy zawsze wykorzystywać te funkcje z operatorami === oraz !== sprawdzającymi nie tylko wartość, ale i typ zwracanych danych. Oto interesująca sztuczka, która w jednej linijce pozwoli nam na sprawdzenie, czy ciąg został znaleziony i EWENTUALNE pobranie pozycji jego wystąpienia:
<?php
$zrodlo = 'Litwo, ojczyzno moja!';
$szukany = 'moja';
if(($id = strpos($zrodlo, $szukany)) !== false) // wlasnie tu
{
echo 'Szukany ciąg zaczyna się w znaku '.$id;
}
else
{
echo 'Nie znaleziono szukanego ciągu';
}
?>
Jeśli nie rozumiesz zastosowanej tu sztuczki, przypomnij sobie zabawy z wyrażeniami na początku podręcznika. PHP najpierw wykona przypisanie do zmiennej $id: $id = strpos($zrodlo, $szukany). Operator zwróci przypisywaną wartość, która zostanie wykorzystana z kolei do sprawdzenia, czy coś zostało znalezione: [wyrażenie] !== false. W ten sposób mamy jednocześnie wykonane i sprawdzenie, i pozycję pierwszego wystąpienia w zmiennej $id.
Powyższe dwie funkcje znajdują pierwsze wystąpienie szukanego ciągu. Jeżeli chcemy znaleźć ostatnie, musimy posłużyć się funkcjami strrpos() i strripos().
[edytuj] Modyfikacja ciągów
Zajmiemy się teraz dosyć rozległą kwestią modyfikacji oraz obróbki tekstu. Jest ona niezwykle ważna, jeżeli zamierzasz pisać aplikacje typu forum dyskusyjnego czy księgi gości. Z wiadomych przyczyn nigdy nie powinniśmy zezwolić internaucie na wypisywanie wszystkiego, co mu się podoba i musimy poddać jego tekst elektronicznej obróbce. Za jej pomocą można również ułatwiać formatowanie tekstu. Przykładowo, treść źródłowa tego podręcznika zapisana jest w specjalnym zestawie znaczników dostosowanym do specyfiki Wikibooks. Odpowiednia biblioteka zajmuje się jego przetwarzaniem na język HTML. Tak złożonymi kwestiami nie będziemy się tu jednak zajmować. Wspomniane zostaną przede wszystkim te funkcje dostępne w PHP. Bardziej zaawansowane algorytmy musisz już zaprogramować samodzielnie.
Nagłówki na stronach internetowych przybierają różne style, w zależności od pełnionych funkcji. Część z nich pisana jest kapitalikami, w innych z dużych liter zaczyna się każdy wyraz. Wcale nie musimy zmuszać redaktorów, by to oni zajmowali się wprowadzaniem tytułów w wymaganej postaci. Istnieją odpowiednie algorytmy, które wykonają to za nich. Choć obecnie zadanie sformatowania tytułów można z powodzeniem zrealizować już z wykorzystaniem CSS-a, PHP posiada szereg funkcji do konwersji dużych liter na małe. Pamiętajmy wszak, że PHP to nie tylko strony internetowe.
<?php $tekst = 'php jest językiem programowania skryptowego zaprojektowanego dla stron internetowych'; echo '<p>Kapitaliki: '.strtoupper($tekst).'</p>'; echo '<p>Dużą literą: '.ucfirst($tekst).'</p>'; echo '<p>Każdy wyraz dużymi literami: '.ucwords($tekst).'</p>'; ?>
Mamy tutaj tekst zapisany w zmiennej (symulującej jakieś źródło danych, ponadto wykorzystamy go kilkakrotnie, stąd ta zmienna) złożony z samych małych liter. Za pomocą funkcji strtoupper() zamieniamy je wszystkie na duże. Odwrotną operację wykonuje strtolower(). ucfirst() zamienia na dużą literę początek pierwszego wyrazu ciągu, a ucwords() - początek każdego wyrazu. Doświadczenie podpowiada nam, że otrzymywane dane nie zawsze są tak klarowne. Jeżeli potrzebne nam automatyczne kapitalizowanie początków zdań, musimy sami napisać odpowiedni algorytm.
Jeżeli chcemy wyciąć fragment jednego ciągu, aby np. poddać go bardziej szczegółowej obróbce, z pomocą przychodzi nam substr(). Podajemy w niej pozycję, od której zamierzamy ciąć oraz ilość znaków do pobrania. W przykładzie połączymy ją z funkcjami strpos() oraz strlen() (podaje długość ciągu) do wycięcia nazwiska z personaliów pewnego człowieka.
<?php
$nazwa = 'Janusz Kowalski';
if(($id = strpos($nazwa, ' ')) !== false)
{
$nazwisko = substr($nazwa, $id, strlen($nazwa) - $id);
}
else
{
$nazwisko = '';
}
echo $nazwisko;
?>
W personaliach odnajdujemy spację, która posłuży nam jako marker. strpos() zwróci nam jej pozycję. Przekazujemy ją do substr() jako punkt startowy wycinania i pobieramy ilość znaków równą długości personaliów minus pozycji spacji. W ten sposób w nasze ręce wpadnie nazwisko i będziemy mogli zrobić z nim, co tylko chcemy. Alternatywny sposób rozwiązania tego problemu polega na rozbiciu tego ciągu na tablicę poznaną już funkcją explode(). Przydaje się on, kiedy oprócz nazwiska pragniemy otrzymać także drugi ciąg z imieniem - tu pasuje ona, jak znalazł.
Teraz coś bardziej praktycznego. Na wielu witrynach internetowych można spotkać emotikonki zamieniane na obrazki. Jeżeli nie interesują nas żadne dodatkowe fajerwerki, napisanie konwertera jest bajecznie proste. PHP posiada funkcję str_replace() zamieniającą jeden fragment ciągu na drugi (jej wariant, str_ireplace() nie rozróżnia wielkości liter).
<?php
$post = 'Tak, zaiste jesteś bardzo zdolny :). Jeszcze nad praktyką popracuj :].';
// Pierwszy sposób zamiany
echo '<p>'.str_replace(':)', '<img src="smile.gif"/>', $post).'</p>';
// Drugi, kilka naraz
echo '<p>'.str_replace(array(
':)',
':]',
':('
), array(
'<img src="smile.gif"/>',
'<img src="eye.gif"/>',
'<img src="sad.gif"/>'
), $post);
?>
Pierwszym parametrem jest szukany ciąg, drugim - ciąg docelowy, a trzecim - ciąg, na którym operujemy. Możemy zdefiniować tylko jeden wzorzec do podmiany albo całą grupę, którą podajemy w postaci tablic, jak na przykładzie. Zwróć uwagę, że w przeciwieństwie do omawianego wcześniej substr(), tutaj ciąg, na którym operujemy, wskazywany jest dopiero na końcu.
Zajmując się księgą gości, dbaliśmy, aby do naszych wpisów nie przedostał się HTML. Funkcja htmlspecialchars() zamieniała wtedy znaki specjalne HTML-a na encje, przez co nie mogły być one przetworzone i pojawiały się we wpisie jako statyczny tekst. Okazuje się, że nie jest to jedyne dostępne nam rozwiązanie. Możemy znaczniki wyrzucić całkowicie. Różne warianty prezentuje poniższy przykład:
<?php $post = ' To jest post kilkulinijkowy. Który należy przerobić tak, aby go <b>fajnie wyświetlać</b>'; // Wariant 1 echo '<p>'.nl2br(htmlspecialchars(trim($post))).'</p>'; // Wariant 2 echo '<p>'.nl2br(strip_tags(trim($post))).'</p>'; ?>
Zarówno strip_tags(), jak i trim() mają pewne ciekawe dodatkowe parametry, o których nie wszyscy wiedzą. Przy wycinaniu znaczników możemy określić, które z nich mają być zostawiane, np. strip_tags($tekst, '<b><i><u><a>') pozostawi nam pogrubianie, kursywę, podkreślanie oraz linki. trim() natomiast niekoniecznie musi wycinać białe znaki - wystarczy, że podamy nasz własny zestaw jako drugi parametr, a możemy oszczędzić sobie dużo pracy.
[edytuj] ASCII
Kiedy powstawały komputery, powstała konieczność stworzenia uniwersalnego standardu kodowania znaków alfabetu za pomocą kodów liczbowych rozumianych przez maszyny. Umożliwiłoby to pisanie dokumentów, które da się przenosić między maszynami bez konieczności ich konwersji. Tak narodził się siedmiobitowy standard ASCII zawierający 128 kodów. Jego cechą szczególną było istnienie tzw. kodów sterujących (od 0 do 32), dzięki którym można było nawet sterować pracą niektórych urządzeń. W latach późniejszych wykorzystano także ósmy bit (kody od 128 do 255). Jego wykorzystanie różni się w zależności od wariantu systemu kodowania.
PHP udostępnia kilka funkcji ułatwiających pracę z ASCII. Na początku zapoznamy się z funkcjami chr() oraz ord() konwertujących kod na odpowiadający mu znak i vice versa. Oto prosty skrypt , wykonujący to zadanie i pobierający dane z formularza HTML.
<?php
if($_SERVER['REQUEST_METHOD'] == 'POST')
{ // 1
if($_POST['kierunek'] == 0) // 2
{
if(strlen($_POST['dane']) != 1)
{
die('<p>Nieprawidłowe dane. Proszę podać JEDEN znak!');
}
echo '<p>Kod znaku <b>'.$_POST['dane'].'</b> to '.ord($_POST['dane']).'</p>';
}
else
{ // 3
if(!ctype_digit($_POST['dane']))
{
die('<p>Nieprawidłowe dane. Proszę podać kod znaku!');
}
echo '<p>Kodowi <b>'.$_POST['dane'].'</b> odpowiada znak '.chr($_POST['dane']).'</p>';
}
}
else
{ // 4
echo '<form method="post" action="ascii.php">
<h2>Informator ASCII</h2>
<select name="kierunek">
<option value="0">Sprawdź kod znaku</option>
<option value="1">Sprawdź znak pod kodem</option>
</select>
<input type="text" name="dane"/>
<input type="submit" value="OK"/>
</form>';
}
?>
Formularz przesyła nam dwie informacje:
- Kierunek - określa, czy konwertujemy znak na kod, czy też kod na znak.
- Dane - znak albo kod do zamiany.
Oto omówienie działania:
- Dotarły do nas dane z formularza.
- Konwersja ze znaku na kod. Sprawdzamy, czy pole dane zawiera dokładnie jeden znak. Jeśli tak, wyświetlamy wynik.
- Konwersja z kodu na znak. Funkcją ctype_digit() sprawdzamy, czy wprowadzony kod składa się wyłącznie z cyfr. Jeśli tak, wyświetlamy wynik.
- Formularz do komunikacji ze skryptem.
W powyższym przykładzie wykorzystaliśmy funkcję ctype_digit(), która szybko bada, czy podany ciąg składa się wyłącznie z liczb. Istnieje kilkanaście funkcji z serii ctype. Oto kilka z nich:
- ctype_alpha($tekst) - sprawdza, czy tekst składa się wyłącznie z liter.
- ctype_lower($tekst) - sprawdza, czy tekst składa się wyłącznie z małych liter.
- ctype_upper($tekst) - sprawdza, czy tekst składa się wyłącznie z dużych liter.
- ctype_xdigit($tekst) - sprawdza, czy tekst zawiera wyłącznie znaki do zapisu liczb w systemie szesnastkowym (heksadecymalnym).
Jeśli żądany przez Ciebie zestaw znaków nie jest udostępniany przez żadną z funkcji, możesz skorzystać z odpowiedniego algorytmu:
<?php
function ctype($ciag, $zestaw)
{
for($i = 0; $i < strlen($ciag); $i++)
{
if(strpos($zestaw, $ciag{$i}) === FALSE)
{
return false;
}
}
return true;
} // end ctype();
?>
Algorytm jest bardzo prosty. Przeszukujemy wprowadzony ciąg znaków $ciag i sprawdzamy funkcją strpos(), czy każdy jego znak zawiera się w zestawie $zestaw. Jeśli nie, przerywamy pracę i zwracamy false. Oto przykładowe użycie:
echo ctype('555-1234', '0123456789-');
Jeżeli poszukujesz jeszcze bardziej złożonych związków między znakami, musisz posłużyć się wyrażeniami regularnymi, z których podstawami zaznajomi Cię następny rozdział.
[edytuj] Formatowanie tekstu
Korzenie PHP znajdują się w języku C, więc nic dziwnego, że dziedziczy on po nim funkcję printf() służącą do prezentacji sformatowanych danych.
<?php
printf('Liczba szesnastkowa: %x', 3342);
?>
Funkcja odnajduje w podanym ciągu specjalne kody formatujące rozpoczynające się od znaku procentu i umieszcza na ich miejscu dane z kolejnych parametrów. W powyższym przykładzie zastosowaliśmy kod %x, który spowoduje wyświetlenie się liczby 3342 w systemie szesnastkowym. Przeglądarka wyświetli zatem:
Liczba szesnastkowa: d0e
Za pomocą kodów formatujących możemy też np. określić precyzję wyświetlania ułamków. Aby wyświetlić liczbę π do 4 miejsc po przecinku, napiszemy:
<?php
printf('Liczba PI: %0.4f', M_PI);
?>
To są jedynie podstawy kodów formatujących. Szczegółowy ich opis znajduje się w dokumentacji PHP. Warto nadmienić także istnienie odmian funkcji printf(), np.
- sprintf() - zwraca wynik jako ciąg tekstowy.
- vprintf() - pobiera dane do kodów z tablicy przekazanej jako drugi parametr.
- vsprintf() - pobiera dane do kodów z tablicy przekazanej drugim parametrem i zwraca wynik jako ciąg tekstowy.
[edytuj] Kodowanie
Teraz nieco o przechowywaniu haseł użytkowników przez PHP. Generalnie nigdy nie składuje się ich w postaci jawnej oraz nie koduje się algorytmem dwukierunkowym (czyli dającym teoretyczną możliwość ich rozszyfrowania). Powszechna praktyka poleca stosowanie tzw. funkcji haszujących generujących unikalne, równej długości sygnatury niszczące oryginalny przekaz, przez co nie da się ich już rozszyfrować metodą inną, niż sprawdzenie wszystkiego na wszystkim. Wbrew początkowemu wrażeniu sens takiego działania jest bardzo oczywisty. Kiedy użytkownik rejestruje się w serwisie, haszujemy jego hasło i zapisujemy w profilu. Próbując się zalogować, przysyła nam swoje hasło jeszcze raz. Haszujemy je i wynik porównujemy z tym, co mamy w bazie. Jeżeli uzyskamy identyczne hasze, znaczy to, że użytkownik podał hasło poprawnie i może zostać zalogowany.
PHP posiada wbudowanych kilka algorytmów haszujących:
- crypt() - najstarszy, generuje 8-znakowe hasze. Nie polecamy do celów autoryzacji.
- md5() - do niedawna najpopularniejszy algorytm. Generował 32-znakowe ciągi, lecz niedawno ujawniono w nim poważne dziury.
- sha1() - aktualnie najbezpieczniejszy algorytm haszujący dostępny domyślnie w PHP. Generuje 40-znakowe ciągi.
Zwróć uwagę na jedną rzecz: hasze mają stałą długość, dlatego ilość możliwych kombinacji jest ograniczona. Tymczasem możliwych ciągów jest nieskończenie wiele. Dlatego może się zdarzyć, że dwa różne ciągi generują ten sam hasz i zwiemy to kolizją. W dobrym algorytmie, aby doszło do takiej sytuacji, muszą to być naprawdę różne ciągi. Ponadto zmiana już pojedynczego znaku wewnątrz haszowanego ciągu musi powodować utworzenie zupełnie innego wyniku. Siła algorytmu zależy od tego, jak dużo czasu potrzeba na wykrycie kolizji metodą brute-force, czyli sprawdzenia wszystkiego na wszystkim. Algorytm MD5 został już złamany na tyle, że zwyczajny komputer PC jest w stanie znaleźć kolizję już w ciągu zaledwie ośmiu godzin. Dla SHA1 ilość niezbędnych do sprawdzenia kombinacji jest rzędu 2^64, z czym nie jest w stanie poradzić sobie żaden istniejący obecnie komputer.
Przypuśćmy, że $uzytkownicy jest tablicą asocjacyjną taką, że indeks jest nazwą użytkownika, a wartość hasłem zaszyfrowanym w SHA1. Aby sprawdzić, czy użytkownik wpisał poprawne hasło, musimy wykonać następującą czynność:
<?php
if(isset($uzytkownicy[$_POST['login']]) && $uzytkownicy[$_POST['login']] == sha1($_POST['haslo']))
{
echo 'Dziękujemy, podałeś dobre hasło.';
}
else
{
echo 'Nieprawidłowy login i/lub hasło.';
}
?>
Skonstruowanie systemu autoryzacji wymaga także zaimplementowania mechanizmu sesji, aby przekazać fakt bycia zalogowanym między poszczególnymi żądaniami. Zajmiemy się tym w dalszych rozdziałach.
[edytuj] Podstawy wyrażeń regularnych
W poprzednim rozdziale poznaliśmy proste techniki wyszukiwania i manipulacji danymi tekstowymi. Ponadto dowiedzieliśmy się o funkcjach z serii ctype pozwalających na prostą kontrolę zawartości ciągu pod kątem występujących w nim znaków. Jak sam zapewne zdążyłeś zauważyć, narzędzia te nie posiadają jednak żadnych zaawansowanych funkcji. Czy wobec tego możliwe jest manipulowanie ciągami o złożonej strukturze? Okazuje się, że tak - dzięki wyrażeniom regularnym, których omówieniem zajmuje się niniejszy rozdział.
[edytuj] Istota wyrażeń regularnych
Mechanizm wyrażeń regularnych (ang. regular expressions, czasem skracane do regexp) jest tak naprawdę parserem pewnego języka służącego do precyzyjnego definiowania dozwolonego formatu ciągu. Korzystanie z wyrażeń regularnych polega na stworzeniu za jego pomocą tzw. wzorca, a następnie jego porównania odpowiednimi funkcjami z interesującym nas ciągiem. Na wyjściu otrzymujemy informację, czy ciąg pasuje do wzorca, czy też nie.
Wyrażenia regularne mają jeszcze większe możliwości. Dzięki nim wyciągnięcie dowolnych interesujących nas informacji z ciągu nie stanowi kłopotu. Wystarczy, że znamy wzorzec go opisujący, a system wyrażeń zwróci nam dodatkowo tablicę uzyskanych z jego wnętrza danych, których potrzebujemy. Wyrażenia regularne dają nam także dostęp do znacznie bogatszego w możliwości kuzyna funkcji str_replace() z poprzedniego rozdziału. O ile tamta funkcja bezmyślnie zamieniała wszystkie napotkane wystąpienia jakiegoś fragmentu na inny, dzięki wyrażeniom regularnym możemy zdefiniować naprawdę wymyślne mechanizmy zamiany uwzględniające wiele dodatkowych czynników.
Jak widać, wyrażenia regularne to potężne narzędzie, jednak przez to też skomplikowane. Niemniej każdy szanujący się programista powinien znać przynajmniej jego podstawy, ponieważ praktyka zawodowa pokazuje, iż wykorzystywane są one bardzo często.
[edytuj] Pierwszy przykład
Nasze pierwsze spotkanie praktyczne z wyrażeniami regularnymi rozpoczniemy od prostego sprawdzenia, czy wypełnione pole formularza zawiera dokładnie jedną cyfrę. Do porównywania wzorca z ciągiem służy funkcja preg_match(), która zwraca true, jeżeli zachodzi zgodność.
<?php
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
if(preg_match('/^[0-9]$/D', $_POST['cyfra']))
{
echo '<p>Wpisałeś cyfrę '.$_POST['cyfra'].'</p>';
}
else
{
echo '<p>Nieprawidłowe dane! Skrypt wymaga podania cyfry!</p>';
}
}
else
{
echo '<form method="post" action="preg1.php">
Podaj cyfrę: <input type="text" name="cyfra"/><input type="submit" value="OK"/>
</form>';
}
?>
Wykorzystaliśmy tutaj wzorzec /^[0-9]$/. Zawarty jest on wewnątrz ograniczników /. Poza nimi mogą znajdować się jedynie dodatkowe flagi kontrolne i nic więcej. Znak ^ oznacza początek ciągu, a znak $ koniec lub "prawie" koniec zezwalając na zakończenie wyrażenia przejściem do nowej linii \n. Zastosowane dodatkowo D wymusza interpretację $ jako bezwzględnego końca wyrażenia (możliwość dodania \n w niektórych przypadkach może być luką w bezpieczeństwie skryptu). [0-9] definiuje klasę dozwolonych znaków, jakie mogą pojawić się w danym miejscu. Ostatecznie wzorzec ten opisuje wszystkie ciągi składające się z DOKŁADNIE jednego znaku będącego cyfrą z przedziału 0 do 9.
Istnieje jeszcze jeden sposób powiadomienia parsera, ile znaków chcemy tam widzieć. Jest nim użycie kwantyfikatorów zasięgu. Ich składnia jest następująca:
- {długość} - dozwolona długość określona jest dokładnie.
- {długość_min,długość_max} - podany jest przedział dozwolonych długości
- {długość_min,} - określona jest minimalna długość
- {,długość_max} - określona jest maksymalna długość
Kwantyfikator umieszczamy po znaku lub klasie dozwolonych znaków, zatem nasze wyrażenie będzie miało postać /^[0-9]{1}$/. W wyrażeniach regularnych można stosować kilka predefiniowanych kwantyfikatorów:
- * - 0 lub więcej
- + - 1 lub więcej
- ? - 0 lub 1 (uwaga: znak ten jest także wykorzystywany w innym kontekście)
[edytuj] Klasy znaków
Nauczymy się teraz bardziej dokładnego definiowania klas znaków, jakich można używać w danym miejscu ciągu. Zasada podstawowa jest bardzo prosta: jeśli w jakimś miejscu napiszemy "a", to parser będzie się tam spodziewać litery "a" występującej dokładnie jeden raz. Jeżeli zastosujemy klasę znaków, definiujemy w ten sposób listę dozwolonych na danej pozycji znaków dokładnie jeden raz. W obu przypadkach "dokładnie jeden raz" można zmienić na dowolną inną długość za pomocą omówionych wyżej kwantyfikatorów. Zatrzymajmy się jednak dokładniej przy tym zwrocie. Skoro dokładnie jeden raz, czemu w takim razie podany wyżej przykład dla wyrażenia /[0-9]/ akceptuje ciągi liczb o dowolnej długości? Aby lepiej pokazać, co naprawdę wtedy ma miejsce, wpisz w formularzu tekst "9a" - o dziwo także i on zostanie przyjęty, mimo że na drugiej pozycji mamy literę! Co jest nie tak? Nic - wyrażenie działa prawidłowo. Parser po prostu osiągnął jego koniec przy sprawdzeniu pierwszego znaku ciągu i resztę przepuścił bez żadnej kontroli. Dlatego istotne jest powiadomienie o tym, gdzie ma znajdować się koniec ciągu.
Tworząc klasę znaków, możemy stosować się do następujących reguł:
- Wypisujemy w nawiasach kwadratowych wszystkie dopuszczalne znaki, np. [abcdefgh]
- Wprowadzamy zakres: [a-h] (dopuszczalne małe litery od "a" do "h")
- Wprowadzamy kilka zakresów: [a-hA-H] (dopuszczalne duże i małe litery od "a" do "h" i od "A" do "H").
Aby wprowadzić jakiś znak specjalny do klasy, poprzedzamy go backslashem: [a-hA-H\-] - znaki duże i małe od "a" do "h" wraz z pauzą. Dysponując tymi wiadomościami, jesteśmy już w stanie napisać pierwszą funkcję kontrolującą (w ograniczonym stopniu) poprawność adresu e-mail:
<?php
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
if(preg_match('/^[a-zA-Z0-9\.\-\_]+\@[a-zA-Z0-9\.\-\_]+\.[a-z]{2,4}$/D', $_POST['email']))
{
echo '<p>Wpisałeś e-mail '.$_POST['email'].'</p>';
}
else
{
echo '<p>Nieprawidłowe dane! Skrypt wymaga podania adresu e-mail!</p>';
}
}
else
{
echo '<form method="post" action="preg2.php">
Podaj adres e-mail: <input type="text" name="email"/><input type="submit" value="OK"/>
</form>';
}
?>
Omówmy sobie poszczególne partie tego wyrażenia:
- /^[a-zA-Z\.\-\_]+ - początek adresu składa się z dowolnych znaków alfanumerycznych, kropki, pauzy oraz podkreślenia i jego długość musi wynosić minimum 1 znak.
- \@ - później ma być małpa
- [a-zA-Z0-9\.\-\_]+ - analogicznej klasy używamy do zdefiniowania domeny.
- \.[a-z]{2,4}$/ - domena musi kończyć się kropką, po której spodziewamy się domeny nadrzędnej (np. .pl, .com).
W pełni poprawne wyrażenie sprawdzające poprawność adresu jest znacznie bardziej skomplikowane. Zainteresowanych odsyłamy do odpowiedniego dokumentu RFC definiującego je.
PCRE posiada kilka klas predefiniowanych:
- . - kropka symbolizuje dowolny znak (za wyjątkiem przełamania linii).
- \d - dowolna cyfra dziesiętna
- \D - dowolny znak niebędący cyfrą
- \s - biały znak (np. spacja, tabulator)
Predefiniowane klasy można ze sobą łączyć wewnątrz nawiasów kwadratowych: [\d\s] - dozwolone cyfry dziesiętne oraz białe znaki. Jeżeli po otwierającym nawiasie kwadratowym pojawi się symbol ^, będzie to oznaczać negację klasy: "wszystkie znaki, które NIE należą do wymienionych". Jak zdefiniowałbyś "dowolny znak niebędący cyfrą", czyli klasę \D w tradycyjny sposób?
[edytuj] Grupy
Poszczególne fragmenty ciągu mogą być ze sobą łączone w większe grupy, ujmowane w okrągłych nawiasach. Są one wykorzystywane w dwóch celach. Po pierwsze, można do nich zbiorczo zastosować kwantyfikator, żądając, aby np. jakiś fragment powtarzał się od 3 do 5 razy. Za pomocą grup eksportujemy także do PHP interesujące nas dane. Przykładowo, do wyrażenia /^(abc)+$/ pasują ciągi "abc", "abcabc", "abcabcabc" itd.
Przedstawimy teraz, jak wykorzystać wyrażenia regularne w innych dziedzinach, niż tylko kontrola formularzy. Załóżmy, że zlecono nam zadanie przeprojektowania bazy danych, ponieważ stara nie spełnia stawianych jej wymagań. Oczywiście musimy napisać jakiś konwerter, który przeniesie automatycznie dane do nowej bazy. Natknęliśmy się jednak na problem: daty utworzenia rekordów zapisywane są w postaci tekstowej, np. "12 Dec 2006, 16:34", zamiast w łatwych do przetwarzania sekundach od 1.1970. Do rozbicia ciągu na poszczególne fragmenty wykorzystamy wyrażenia regularne:
<?php
$date = '12 Dec 2006, 16:34';
if(preg_match('/^(\d{1,2}) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4})\, (\d{1,2})\:(\d{1,2})/', $date, $found))
{
// Co nam zwrocilo...
echo '<h3>Dane: "'.$date.'"</h3>';
echo '<p>Dzien: '.$found[1].'</p>';
echo '<p>Miesiac: '.$found[2].'</p>';
echo '<p>Rok: '.$found[3].'</p>';
echo '<p>Godzina: '.$found[4].'</p>';
echo '<p>Minuta: '.$found[5].'</p>';
$monthConverter = array('Jan' => 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4, 'May' => 5,
'Jun' => 6, 'Jul' => 7, 'Aug' => 8, 'Sep' => 9, 'Oct' => 10, 'Nov' => 11, 'Dec' => 12);
echo '<p>Unix timestamp: '.mktime($found[4], $found[5], 0, $monthConverter[$found[2]], $found[1], $found[3]).'</p>';
}
else
{
echo '<p>Nieprawidłowy format daty!</p>';
}
?>
W zastosowanym wyrażeniu regularnym pojawia się symbol | - jest to operator wyboru. Ciąg Jan|Feb|Mar oznacza, że w tym miejscu chcemy mieć "Jan" ALBO "Feb" ALBO "Mar". Zauważ, że wszystkie istotne elementy daty zawarliśmy w grupach, a do samej funkcji preg_match() podaliśmy trzeci parametr. Do podanej tam zmiennej zostanie przypisana tablica z treścią pasującego ciągu na indeksie 0 oraz wartościami wszystkich użytych grup na kolejnych indeksach. Teraz możemy już łatwo przekonwertować funkcją mktime() naszą datę na format uniksowy.
Ta sekcja jest zalążkiem. Jeśli możesz, rozbuduj ją
[edytuj] Obsługa ciastek
[edytuj] Czym są nagłówki HTTP?
Serwer w odpowiedzi na żądanie HTTP wysyła nie tylko kod HTML, lecz także zestaw nagłówków pomagających przeglądarce na zidentyfikowanie dostarczanych treści. Nagłówki precyzują typ MIME danych, np. text/html dla dokumentu HTML, kodowanie znaków, zachowanie się serwerów proxy, ustawienia cache'owania danych itd. PHP posiada funkcję header() pozwalającą skryptowi wysłać własne nagłówki. Muszą być one jednak zdefiniowane przed wysłaniem jakiegokolwiek kodu HTML (są to przecież NAGŁÓWKI). W przeciwnym wypadku dostaniemy niemiły komunikat Cannot add header information. Programiści często wykorzystują nagłówki do zdefiniowania typu oraz kodowania w nadsyłanym dokumencie:
<?php
// Bedziemy wysylac dokument HTML z kodowaniem UTF-8
header('Content-type: text/html;charset=utf-8');
?>
Ale nie tylko. Wiele stron udostępnia swe archiwa nie poprzez bezpośredni dostęp do katalogu, w którym są trzymane, ale poprzez specjalny skrypt, który wysyła ich zawartość do ściągnięcia. Nagłówki umożliwiają powiadomienie przeglądarki, że teraz będzie szedł plik o takiej i takiej nazwie, który internauta chce pobrać na swój dysk twardy. Nie będzie on jednak ujawniać katalogu na serwerze, gdzie on się znajduje. Cała komunikacja prowadzona jest za pośrednictwem PHP.
<?php
if(!isset($_GET['plik'])) // 1
{
die('Podaj nazwę pliku!');
}
$_GET['plik'] = basename($_GET['plik']); // 2
if(@is_file('./pdf/'.$_GET['plik']))
{
// 3
header('Content-type: application/pdf');
header('Content-Disposition: attachment; filename="'.$_GET['plik'].'"');
readfile('./pdf/'.$_GET['plik']);
}
else
{
// 4
header('HTTP/1.1 404 Not Found');
exit('Nie znaleziono pliku '.$_GET['plik']); // możesz wypisać całą treść strony z komunikatem o błędzie.
}
?>
Powyższy kod może być częścią jakiegoś serwisu, który swoje artykuły udostępnia także w formacie PDF do ściągnięcia, gdyż aktualnie tylko takie pliki można nim wysyłać. Przeanalizujmy go krok po kroku.
- Na początku dokonujemy sprawdzenia, czy ktoś w ogóle zainteresował się podaniem nazwy dokumentu do pobrania.
- Bezpieczeństwo na miejscu pierwszym - wszystko, co jest nazwą pliku i pochodzi od internauty, powinno być przepuszczone przez funkcję basename(), która wyciągnie z niego wyłącznie nazwę i odrzuci jakieś przejścia między katalogowe, co mogłoby zagrozić bezpieczeństwu. Wyobraź sobie, że ktoś wpisze sobie np. ../strona_hasla.php. Bez tego zabezpieczenia dostałby hasła dostępu do naszej strony, lecz basename() odrzuci niebezpieczny fragment ../. Mamy więc pewność, że internauta będzie ściągać TYLKO i WYŁĄCZNIE to, co chcemy, aby ściągał.
- Jeśli stwierdzimy, że plik istnieje, powiadamiamy nagłówkami, że oto nadejdzie dokument PDF jako załącznik o odpowiedniej nazwie. Funkcją readfile() wysyłamy jego zawartość.
- Gdyby ktoś podał niewłaściwą nazwę pliku, możemy wysłać mu komunikat błędu 404.
Powyższy przykład możemy nieco przerobić tak, aby z powodu podania błędnej nazwy internauta odsyłany był do naszego własnego komunikatu. Nagłówki umożliwiają robienie przekierowań HTTP i właśnie pragniemy pokazać, jak to się robi.
<?php
if(!isset($_GET['plik']))
{
die('Podaj nazwę pliku!');
}
$_GET['plik'] = basename($_GET['plik']);
if(@is_file('./pdf/'.$_GET['plik']))
{
header('Content-type: application/pdf');
header('Content-Disposition: attachment; filename="'.$_GET['plik'].'"');
readfile('./pdf/'.$_GET['plik']);
}
else
{
header('Location: http://localhost/~kurs/notfound.php');
exit;
}
?>
Źródło jest zasadniczo podobne do poprzedniego przykładu. Zmiany widać jedynie w bloku else, gdzie wysyłamy tym razem nagłówek Location. Informuje on przeglądarkę, że treści nie będzie i powinna raczej skontaktować się z podanym plikiem. Innymi słowy, robimy przekierowanie internauty pod inny adres. Protokół HTTP 1.1 wymaga, aby w nagłówku był podany pełen adres do żądanego zasobu. HTTP 1.0 nie miał takich ograniczeń.
| Uwaga! Po wysłaniu nagłówka Location powinniśmy wywołać komendę exit; albo die(), aby zatrzymać nasz skrypt! |
Kiedy omówiliśmy sobie już właściwości oraz niektóre możliwości nagłówków, możemy przejść do ciastek (ang. cookies) ustawianych właśnie za ich pomocą.
[edytuj] Ciastka w PHP
Chyba każdy internauta słyszał o ciastkach i wyolbrzymianych "zagrożeniach" z nimi związanych. W rzeczywistości są to zwyczajne informacje umieszczane przez witryny WWW w przeglądarkach po to, aby był do nich dostęp między wywołaniami kolejnych podstron w obrębie witryny. Jedyny problem może pojawić się z tzw. ciastkami publicznymi, które może odczytać każda strona w Internecie. Jednak poza tym jest to bardzo pożyteczne narzędzie wykorzystywane m.in. w autoryzacji użytkowników.
Ciastka są ustawiane za pomocą nagłówków HTTP i mają pewien określony termin ważności. Po jego upływie przestają istnieć. Do wysyłania ciastek służy w PHP funkcja setcookie(), a do pobierania wartości tych ustawionych przez wcześniejsze żądania HTTP - specjalna tablica $_COOKIE. Napiszemy teraz prosty skrypt, który robił furorę kilka lat temu, w okresie popularyzacji dynamicznych witryn WWW. Chodzi o umieszczenie prostej informacji dot. ostatniej wizyty internauty u nas. Aby to wykonać, wystarczy przy pierwszym wejściu umieścić na np. miesiąc ciastko z datą ostatniej wizyty, po czym ją sukcesywnie odczytywać.
<?php
if(!isset($_COOKIE['wizyta']))
{
setcookie('wizyta', time(), time() + 30 * 86400);
echo 'Witaj, gościu.';
}
else
{
setcookie('wizyta', time(), time() + 30 * 86400);
echo 'Witaj, ostatni raz odwiedziłeś nas '.date('d.m.Y, H:i', $_COOKIE['wizyta']);
}
?>
Trzy pierwsze parametry setcookie() są najważniejsze (ma ona ich trochę więcej). Jest to kolejno: nazwa ciastka, jego wartość oraz data ważności w sekundach od 1.1.1970. Data, a nie okres ważności, stąd przy jego ustawianiu przydaje się funkcja time(). W powyższym skrypcie sprawdzamy, czy ustawialiśmy już ciastko dla danego internauty. Jeśli nie, tworzymy je i wyświetlamy komunikat powitania. W przeciwnym wypadku także aktualizujemy wartość, ale też wyświetlamy datę ostatniej wizyty odczytaną właśnie z ciastka.
Zauważ, że wywołanie setcookie() nie nadpisuje wartości w tablicy $_COOKIE. Dlatego bez problemu można uprościć powyższy skrypt:
<?php
setcookie('wizyta', time(), time() + 30 * 86400);
if(!isset($_COOKIE['wizyta']))
{
echo 'Witaj, gościu.';
}
else
{
echo 'Witaj, ostatni raz odwiedziłeś nas '.date('d.m.Y, H:i', $_COOKIE['wizyta']);
}
?>
Aby istniejące ciastko skasować, wywołujemy funkcję setcookie() z jakąś przeszłą datą ważności:
<?php
setcookie('wizyta', '', 0);
?>
[edytuj] Funkcje buforowania wyjścia
Istnieją sytuacje, kiedy musimy wysłać jakiś kod HTML przed wysłaniem nagłówków HTTP, jednak tradycyjne metody nie pozwalają na to. W PHP można ten problem omijać, używając funkcji buforowania wyjścia. Ogólnie rzecz biorąc, przechwytują one treść wysyłaną przez echo albo print, zapisując ją do specjalnego bufora. Opróżniamy go samodzielnie na samym końcu skryptu, symulując efekt równoczesnego wysyłania nagłówków i kodu HTML. Pokażemy to na przykładzie:
<?php
ob_start();
echo '<h1>Tytuł witryny</h1><p>I inne komendy HTML.</p>';
setcookie('wizyta', time(), time() + 30 * 86400);
if(!isset($_COOKIE['wizyta']))
{
echo '<p>Witaj, gościu.</p>';
}
else
{
echo '<p>Witaj, ostatni raz odwiedziłeś nas '.date('d.m.Y, H:i', $_COOKIE['wizyta']).'</p>';
}
ob_end_flush();
?>
Zwróć uwagę, że przed stworzeniem ciastka skrypt wysyła już kod HTML. Dlatego wszystkie instrukcje zawarliśmy między funkcjami ob_start() i ob_end_flush(). Pierwsza inicjuje buforowanie wyjścia, a druga kończy je, wysyłając jego zawartość do przeglądarki.
Buforowanie wyjścia może też posłużyć do celów algorytmicznych, kiedy musimy przechwycić wysyłany kod, aby go jeszcze dodatkowo obrobić. Naraz można mieć otwartych kilka buforów działających zgodnie z zasadą stosu, tj. ostatni otwarty bufor będzie pierwszym, z którego pobierzemy zawartość. Poniżej prezentujemy mały skrypt cenzorski. Przechwytuje on tekst i cenzuruje go, chyba że internauta zna sposób aktywujący prawdziwą treść. Jest to coś w sam raz dla walczących z dyktaturami opozycjonistów.
<?php
ob_start();
echo '<p>Pan Jan Nowak jest bardzo nieprzyzwoitym człowiekiem. Powiada, że dzień bez łapówki to
dzień stracony. Pracuje w urzędzie miejskim Obiektowa i nie wstydzi się swych podejrzanych interesów.</p>';
// CENZURA
// Pobieramy zbuforowany tekst
// I **dla czytelności** przykładu zapisujemy go w zmiennej
$kod = ob_get_clean();
if(isset($_GET['real']))
{
// Wtajemniczeni znają całą prawdę
echo $kod;
}
else
{
// Reszta może się tylko domyślać
echo str_replace(array(
'Jan Nowak',
'Obiektowo',
'Obiektowa'
), array(
'Alojzy Kromka',
'Hyzia Wólka',
'Hyziej Wólki'
), $kod);
}
?>
Buforowanie wyjścia jest też podstawą tzw. kompresji GZip. Jest to kompresowanie treści strony przed wysłaniem tak, aby zajmowała mniejszą objętość, przez co użytkownik szybciej ją pobierze. Kompresję wspierają wszystkie nowoczesne przeglądarki (np. Opera, Firefox).
Aby uruchomić kompresję GZip, twoja wersja PHP musi mieć doinstalowaną bibliotekę zlib. Wtedy możesz rozpocząć buforowanie poniższym kodem:
ob_start('ob_gzhandler');
ob_implicit_flush(0);
Parametr przekazywany do ob_start() to nazwa tzw. uchwytu (handlera) służącego do modyfikacji zbuforowanej treści. ob_gzhandler jest jednym z predefiniowanych uchwytów, zajmującym się właśnie kompresją GZip. Druga z funkcji nakazuje wywołanie uchwytu dopiero, gdy będziemy mieli już cały kod HTML. Musimy o niej pamiętać dlatego, że nie można skompresować danych wyjściowych partiami - musi to być przeprowadzone w sposób ciągły. PHP samodzielnie wykrywa, czy przeglądarka użytkownika posiada obsługę tej możliwości, dlatego nie musisz o tym pamiętać.
[edytuj] Ciastka a bezpieczeństwo
Ciastka są podstawą wielu systemów autoryzacji użytkowników dzięki możliwości przesyłania za ich pomocą danych między stronami. Jednak wielu początkujących programistów PHP nie rozumie lub nie wie, jak robić to bezpiecznie. Ciastko jest zwyczajnym nagłówkiem HTTP i naprawdę nie stanowi dużego problemu przechwycenie jego treści. Dlatego pamiętaj, aby nigdy nie przesyłać nim loginów, haseł zalogowanego użytkownika, ani żadnych innych danych potencjalnie pomocnych przy autoryzacji. Jest to całkowicie zła droga, gdyż systemy autoryzacji pisze się z wykorzystaniem sesji. Sesja jest pewnym rekordem z informacjami identyfikacji danego internauty przechowywanymi w bazie albo w pliku tekstowym na serwerze. Posiada długi, alfanumeryczny identyfikator i to on przesyłany jest ciastkiem. Porównując ID z ciastka, a także takie parametry, jak adres IP czy używana przeglądarka, można wyeliminować przypadki kradzieży ID sesji. Mechanizmu sesji nie musisz pisać samemu, gdyż PHP posiada własny. Omówimy go w następnym rozdziale.
[edytuj] Sesje
Protokół HTTP jest protokołem bezstanowym. Oznacza to, że serwer WWW rozpatruje każde żądanie niezależnie od innych, nie szukając żadnych powiązań w stylu wysyłania ich przez tego samego internautę. Utrudnia to teoretycznie tworzenie wszelkich systemów autoryzacji, które wymagają śledzenia poczynań użytkownika na naszej stronie i przenoszenia jego danych autoryzacyjnych między kolejnymi żądaniami, czyli krótko mówiąc - wymagają obecności systemu sesji. Używając PHP lub innego dynamicznego języka server-side można je jednak zasymulować. "Nasz" język jest o tyle prosty, iż posiada już zaimplementowane stosowne funkcje. My tylko musimy zacząć ich używać.
Działanie sesji w PHP jest bardzo proste. W momencie pierwszego trafienia na stronę interpreter tworzy specjalny, losowy oraz unikalny identyfikator przesyłany między żądaniami za pomocą ciastek lub parametru PHPSESSID doklejanego automatycznie do adresów URL. Na jego podstawie odczytywany jest później odpowiedni plik z danymi sesji zapisany gdzieś na serwerze. Pod koniec przetwarzania żądania wszystkie wprowadzone przez skrypt zmiany są z powrotem zapisywane do wspomnianego pliku tak, aby były widoczne przy wejściu na kolejną podstronę. I tak to się toczy.
[edytuj] Wprowadzenie do sesji
Czas na trochę praktyki. Aby zainicjować mechanizm sesji, wystarczy wywołać funkcję session_start(), najlepiej na początku naszej aplikacji. Od tego momentu do naszej dyspozycji zostaje oddana super globalna tablica $_SESSION - wszystkie zapisane do niej dane są przesyłane między kolejnymi żądaniami. Popatrzmy na pierwszy, bardzo prosty przykład licznika odwiedzonych już podstron:
<?php
session_start(); // 1
if(!isset($_SESSION['licznik'])) // 2
{
$_SESSION['licznik'] = 0;
}
$_SESSION['licznik']++; // 3
echo 'Odwiedziłeś już '.$_SESSION['licznik'].' podstron!'; // 4
?>
Oto analiza:
- Inicjujemy sesje
- Jeżeli jest to pierwsza wizyta, tablica z sesjami nie zawiera żadnych danych. Dobrym zwyczajem jest ich inicjowanie, aby nie zostać zaatakowanym tysiącami komunikatów Notice.
- Zmieniamy dane sesji
- Odczytujemy dane sesji
Po odświeżeniu strony zauważymy, że licznik wskazuje już "2", po kolejnym - "3". Oznacza to, że PHP zapisuje zmienną $licznik i przesyła ją między naszymi żądaniami. Różnica pomiędzy sesjami, a ciastkami jest taka, że dane te w ogóle nie opuszczają serwera WWW, są przez to (w teorii) bezpieczniejsze.
[edytuj] Prosta autoryzacja użytkowników
Napiszemy teraz prosty skrypt do autoryzacji użytkowników bazujący na sesjach. Jak wspomnieliśmy w poprzednim rozdziale, nie powinno się przesyłać pomiędzy żądaniami haseł oraz loginów użytkowników, nieważne czy w formie ciastek, czy sesji. Alternatywą jest ich ID. Jak przekonasz się w następnych rozdziałach, bazy danych, gdzie większość aplikacji WWW trzyma swoje informacje, wcale nie identyfikują rekordów jakimiś abstrakcyjnymi rzeczami typu login lub tytuł. Operują na zwyczajnych, automatycznie nadawanych i unikalnych liczbach zwanych identyfikatorami (w skrócie pisze się "id"). Dzięki temu można szybko je do siebie porównać, co ma szczególne znaczenie w przypadku ogromnej liczby rekordów. Takie też ID przypisane użytkownikom przesyłane są w sesjach. Oczywiście, zgodnie z praktyką stosowaną w bazach danych, numerację rekordów rozpoczynamy od 1. Rekord o ID równym 0 nie istnieje.
Zatem, jak taki system logowania działa? Kiedy sesja jest już załadowana, skrypt sprawdza zapisany w niej ID użytkownika. Jeżeli jest on większy od zera, ktoś jest już zalogowany i wystarczy tylko pobrać skądś dane jego profilu. W przypadku ID równego 0 mamy do czynienia z kimś anonimowym. Tu, w zależności od sytuacji możemy mu wyświetlać ogólnodostępne treści albo formularz logowania. Pisanie skryptu zaczniemy od stworzenia sobie namiastki bazy danych. Będzie nią tablica $uzytkownicy przechowująca loginy oraz hasła użytkowników. Ponadto napiszemy funkcję czyIstnieje() znajdującą ID użytkownika o podanym loginie i haśle lub false, kiedy takowy nie istnieje. Hasła naturalnie haszujemy poznanym już algorytmem sha1:
<?php
$uzytkownicy = array(1 =>
array('login' => 'user1', 'haslo' => sha1('ppp')),
array('login' => 'user2', 'haslo' => sha1('ddd')),
array('login' => 'user3', 'haslo' => sha1('fff'))
);
function czyIstnieje($login, $haslo)
{
global $uzytkownicy;
$haslo = sha1($haslo);
foreach($uzytkownicy as $id => $dane)
{
if($dane['login'] == $login && $dane['haslo'] == $haslo)
{
// O, jest ktos taki - zwroc jego ID
return $id;
}
}
// Jeżeli doszedłeś a tutaj, to takiego użytkownika nie ma
return false;
} // end czyIstnieje();
Teraz zaczniemy właściwy skrypt. Zainicjujemy sesję i obsłużymy sytuację pierwszej wizyty - w takim wypadku gościa oznaczymy jako osobę anonimową (niezalogowaną):
// Wlasciwy skrypt
session_start();
if(!isset($_SESSION['uzytkownik']))
{
// Sesja się zaczyna, wiec inicjujemy użytkownika anonimowego
$_SESSION['uzytkownik'] = 0;
}
Krótka piłka - ID większy od 0? Ktoś jest zalogowany:
if($_SESSION['uzytkownik'] > 0)
{
// Ktos jest zalogowany
echo 'Witaj, '.$uzytkownicy[$_SESSION['uzytkownik']]['login'].' na naszej stronie!';
}
else
{
Jednak jeśli nie jest, to musimy zaprogramować zarówno pokazywanie formularza logowania, jak i autoryzację użytkownika na podstawie danych z niego. Zaczynamy od tej drugiej opcji. Korzystając z funkcji czyIstnieje() pobieramy ID użytkownika. Jeżeli jest on różny od false, wpisujemy go do sesji, tym samym logując go.
// Niezalogowany
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
if(($id = czyIstnieje($_POST['login'], $_POST['haslo'])) !== false)
{
// Logujemy uzytkownika, wpisal poprawne dane
$_SESSION['uzytkownik'] = $id;
echo 'Dziekujemy, zostales zalogowany! <a href="sesje_2.php">Dalej</a>';
}
else
{
echo 'Podales nieprawidlowe dane, zegnaj! <a href="sesje_2.php">Dalej</a>';
}
}
else
{
Kiedy dane nie nadeszły z formularza, znaczy to, że trzeba go wyświetlić:
echo '<form method="post" action="sesje_2.php"> Zaloguj sie: <input type="text" name="login"/><input type="password" name="haslo"/> <input type="submit" value="OK"/></form>'; } } ?>
I gotowe. Wszystko fajnie, wszystko pięknie, nasza witryna sobie działa, użytkownicy się logują, dodając coraz to nowe treści, lecz pewnego dnia dostajesz e-maila z ostrzeżeniem, że ktoś włamał się do serwisu i wszystko rozwalił. Co nawaliło? Czyżby zawiódł mechanizm sesji?
[edytuj] Bezpieczeństwo sesji
Prawdopodobnie tak. Sytuacja z mechanizmem sesji standardowo dostępnym w PHP jest o tyle śmieszna, że jego autorzy właśnie nam zostawili swobodę działania odnośnie tego, jak go zabezpieczyć przed kradzieżą. Normalnie użyty jest on bowiem dziurawy jak sito. Kradzież sesji nie nastręcza wielkich trudności, lecz na nieszczęście, wiele osób o tym nie pamięta (także autorów artykułów pokazujących, jak z niego korzystać!).
Włamania wykorzystujące dziury w mechanizmach sesji mają swoje fachowe nazwy. Pierwszą z nich jest Session Fixation. Jej działanie jest bardzo proste. Wykorzystujemy tutaj właściwość, że kiedy podamy skryptowi jakiś nieistniejący ID sesji, PHP automatycznie dorobi dane i nie przejmie się tym, że tak naprawdę to my go wygenerowaliśmy, a nie jakiś algorytm losująco-mieszający. Teraz popatrz: podszywasz się pod pracownika jakiegoś serwisu internetowego i każesz nieświadomemu użytkownikowi odwiedzić jakiśtam adres, najlepiej wymagający zalogowania. Do adresu URL doczepiasz ciąg ?PHPSESSID=abcdef. "abcdef" jest wymyślonym przez nas identyfikatorem. Kiedy użytkownik posłusznie się zaloguje, jest zdany na naszą łaskę. Mamy ID jego sesji i możemy działać tak, jakbyśmy byli nim.Jeszcze więcej cwaniactwa i możemy uzyskać nawet sesję administratora, co dla serwisu oznacza oczywiście katastrofę. Aby się przed tym zabezpieczyć, wystarczy po zainicjowaniu mechanizmu sesji dokleić bardzo prosty kod:
<?php
session_start();
if (!isset($_SESSION['inicjuj']))
{
session_regenerate_id();
$_SESSION['inicjuj'] = true;
}
?>
Przy tworzeniu nowej sesji, dzięki funkcji session_regenerate_id() mamy pewność, że sesja dostanie losowy ID. Teraz nawet, jeżeli podamy zmyślnie ?PHPSESSID=abcdef, nic nam to nie da, bo PHP i tak sobie wszystko wygeneruje po swojemu i zostaniemy w tym samym miejscu, co byliśmy. Jednak nie spoczywaj jeszcze na laurach. ID nadal może zostać wykradziony. Wystarczy, że jakiś ciamajda skopiuje znajomemu link do jakiegoś zasobu, mając przy tym wyłączone ciastka. Oczywiście w linku znajdzie się wtedy jego LOSOWY identyfikator sesji, który w ten sposób zostanie ujawniony przed światem. Znajomy może teraz wędrować sobie po serwisie wykorzystując sesję ciamajdy. session_regenerate_id() tutaj nie zadziałała, bo przecież ta sesja już istnieje. Ataki tego rodzaju określa się mianem Session Hijacking. Zabezpieczenie się przed nimi także jest proste. Wystarczy w sesji przesyłać np. adres IP komputera wraz z nazwą przeglądarki, spod których została ona utworzona, a następnie porównywać je przy kolejnych wizytach z danymi dostarczonymi przez serwer. Jeżeli wystąpi niezgodność, ktoś próbuje użyć cudzej sesji.
<?php
session_start();
if (!isset($_SESSION['inicjuj']))
{
session_regenerate_id();
$_SESSION['inicjuj'] = true;
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
}
if($_SESSION['ip'] != $_SERVER['REMOTE_ADDR'])
{
die('Proba przejecia sesji udaremniona!');
}
?>
Powyższy przykład zawiera łatki przeciwko obu włamaniom (co prawda w przypadku tej drugiej o wiele lepszym rozwiązaniem byłoby utworzenie sesji na nowo w przypadku niezgodności, niż obwieszczanie całemu światu naszego "odkrycia"). Pamiętaj jednak, że najsłabszym ogniwem jest zawsze człowiek. Jeżeli hacker zdobędzie twoje hasło, zabezpieczenia na nic się nie zdadzą, chyba że akurat masz stały adres IP i tak sobie wszystko napisałeś, że na twoje konto można się tylko z niego logować. Tylko kto stosuje tak rygorystyczne i nieporęczne środki bezpieczeństwa?
[edytuj] Podsumowanie
Sesje PHP są bardzo dobrym rozwiązaniem, jednak paradoksalnie wiele profesjonalnych aplikacji pisze własne systemy sesji całkowicie od zera. Powodów jest kilka:
- Integracja ze strukturą kodu reszty aplikacji
- Niezależność od PHP. Kiedy mechanizm sesji pojawił się w PHP po raz pierwszy, używało się go zupełnie inaczej, niż obecnie. Przyszłość może przynieść różne rzeczy, a nasz własny mechanizm zawsze pozostanie mimo tego taki sam.
- Bezpieczeństwo - kiedy piszesz wszystko od zera, możesz wstawić dodatkowe zabezpieczenia tam, gdzie normalnie nie sięgniesz.
- Elastyczność - dlaczego dane sesji muszą być trzymane akurat w pliku? Baza danych jest przecież równie dobra, jeśli nie lepsza.
- Specyfika zastosowania - wiele systemów sesji służy tylko celom autoryzacji. Stąd też pisze się je od zera pod to jedno zastosowanie, co umożliwia wprowadzenie pewnych uproszczeń oraz optymalizacji.
Jednak własny system sesji jest odpowiedzialnym kawałkiem kodu, gdyż tam to ty musisz wszystko od A do Z zaprogramować. Na razie jedynie sygnalizujemy taką możliwość jako alternatywę dla systemu sesji wbudowanego w PHP. Który z nich wybierzesz, to zależy tylko od Ciebie.
[edytuj] Wysyłanie e-maili
Aby wysyłać e-maile z PHP, interpreter musi być skonfigurowany do pracy z demonem pocztowym. Odpowiednie dyrektywy znajdują się w pliku php.ini w sekcji [mail function]. Należy tam podać odpowiednie parametry w zależności od systemu operacyjnego:
- Dla systemów Win32 - host oraz port, pod jakim pracuje demon pocztowy protokołu SMTP. Możesz próbować łączyć się z twoją własną skrzynką e-mail u jednego z providerów, lecz jeżeli znajdujesz się za firewallem, prawdopodobnie będziesz musiał zainstalować serwer poczty na własnym komputerze (np. Mercury for Win32).
- Dla systemów Unix należy podać wywołanie oraz ewentualne parametry programu sendmail.
[edytuj] Funkcja mail()
Aby wysłać e-maila za pomocą PHP, musimy użyć do tego funkcji mail(). Jej składnia jest następująca:
mail(adresat, temat, wiadomość[, nagłówki[, parametry]])
Funkcja zwraca wartość TRUE, jeżeli wiadomość została poprawnie przekazana serwerowi poczty.
[edytuj] Biblioteka pear::mail
Dla osób które lubią używać gotowych bibliotek można polecić moduł mail z pear. Szczegóły znajdziesz pod adresem: http://pear.php.net/package/Mail . Biblioteka jest dosyć prosta i posiada w swojej dokumentacji jasne przykłady.
Wysyłanie załączników jest równie proste - zainteresuj się klasą pear::Mail_Mime.
[edytuj] Przykłady
Następujący przykład spowoduje wysłanie wiadomości e-mail na adres jan_testowy@serwer.pl o temacie "Witaj" i treści "Oto test funkcji mail":
<?php
if(mail('jan_testowy@serwer.pl', 'Witaj', 'Oto test funkcji mail'))
{
echo 'Wiadomość została wysłana';
}
?>
Nagłówki pozwalają na ustawienie dodatkowych informacji o wiadomości, np. jej nadawcy lub kodowaniu znaków: Możemy również użyć zmiennych:
<?php
$naglowki = "From: moj@mail.pl".PHP_EOL."Reply-To: moj@mail.pl".PHP_EOL."Content-type: text/plain; charset=iso-8859-2";
if(mail('jan_testowy@serwer.pl', 'Witaj', 'Oto test funkcji mail', $naglowki))
{
echo 'Wiadomość została wysłana';
}
?>
Nagłówki można podać zarówno wewnątrz cudzysłowów, jak i apostrofów. Użyto tutaj stałej PHP_EOL, zmiennej środowiskowej. Przejść do nowej linii można na dwa sposoby. Pierwszy, uniwersalny i mniej popularny to ta stała, drugi - zależny od systemu serwera - \r\n dla Windows, \n dla Linuksa i \r dla Mac'a. Wewnątrz edytora najlepiej podawać wszystkie nagłówki w jednym ciągu i zejścia zaznaczać przy użyciu właśnie tych kodów. Inaczej może to spowodować wysłanie niepoprawnej wiadomości, gdyż niektóre edytory mają tendencję do zniekształcania tych zejść.
Z poziomu PHP można także wysyłać e-maile w formacie HTML. W tym celu należy dodać do wiadomości odpowiednie nagłówki:
<?php
// Naglowki mozna sformatowac tez w ten sposob.
$naglowki = "Reply-to: moj@mail.pl <moj@mail.pl>".PHP_EOL;
$naglowki .= "From: moj@mail.pl <moj@mail.pl>".PHP_EOL;
$naglowki .= "MIME-Version: 1.0".PHP_EOL;
$naglowki .= "Content-type: text/html; charset=iso-8859-2".PHP_EOL;
//Wiadomość najczęściej jest generowana przed wywołaniem funkcji
$wiadomosc = '<html>
<head>
<title>Wiadomość e-mail</title>
</head>
<body>
<p><b>Treść wiadomości</b>: To jest treść wiadomości z formatowaniem HTML.</p>
</body>
</html>';
if(mail('jan_testowy@serwer.pl', 'Witaj', $wiadomosc, $naglowki))
{
echo 'Wiadomość została wysłana';
}
?>
[edytuj] Dokumentacja
[edytuj] Internacjonalizacja
W tym rozdziale przyjrzymy się zagadnieniom związanym z dostosowywaniem naszych witryn do konkretnych języków i ustawień regionalnych. PHP posiada odpowiedni zestaw narzędzi, który czyni to zadanie nietrudnym w wykonaniu. Lecz uważaj! Nieuważne skonfigurowanie mechanizmu może być przyczyną błędów!
[edytuj] Dlaczego "Ż" jest literą?
Uruchom pewien prosty skrypt:
<?php
echo strtolower('ŻÓŁTA WODA');
?>
W zależności od komputera, pokaże on napis żółta woda albo ŻóŁta woda. Zastanówmy się, skąd jeden interpreter wiedział, że Ż jest literą polskiego alfabetu, która również podlega zamianie, a drugi nie? Odpowiedź kryje się w ustawieniach regionalnych tych maszyn, znanych z terminologii linuksowej pod nazwą locale. PHP domyślnie wykonuje wszystkie operacje na tekstach, ustawia formatowanie walut oraz liczb na podstawie ustawień systemowych. Aby zmienić opcje regionalne dla aktualnie wykonywanego skryptu, należy skorzystać z funkcji setlocale(). Za pierwszy parametr podajemy flagi określające, które aspekty regionalizacji chcemy zmodyfikować, a wszystkie kolejne to identyfikatory możliwych ustawień. PHP będzie próbował każdego z nich po kolei, aż trafi na taki, który jest zainstalowany w systemie. Dzięki temu jedną funkcją można przystosować skrypt do pracy zarówno z serwerami opartymi o Linux, jak i Windows. Poniżej przedstawiamy jeszcze raz skrypt strtolower() skonfigurowany do pracy z polskimi ustawieniami:
<?php
setlocale(LC_ALL, 'pl_PL', 'pl', 'Polish_Poland.28592');
echo strtolower('ŻÓŁTA WODA');
?>
Pierwsze dwa identyfikatory (pl_PL oraz pl) dotyczą systemu Linux. Trzeci przeznaczony jest dla rodziny Windows. Listę aktualnie zainstalowanych ustawień można znaleźć w Panel sterowania → Opcje regionalne → Zaawansowane.
[edytuj] Polska data
Pisząc aplikację przeznaczoną na rynek międzynarodowy, powinniśmy pomyśleć także o formatowaniu dat. Poznana już przez nas funkcja date() nawet przy pracy z polskimi ustawieniami wyświetli nam angielskie nazwy miesięcy. Sprawę rozwiązuje strftime(), przy której musimy pamiętać o innej składni formatowania daty. Przyjrzyjmy się przykładowi:
<?php
setlocale(LC_ALL, 'pl_PL', 'pl', 'Polish_Poland.28592');
// Dzień - nazwa miesiąca - rok
echo strftime('%d %B %Y');
?>
Po uruchomieniu okazuje się, że nawet ten skrypt nie jest doskonały, ponieważ wprawdzie generuje spolszczoną datę, ale niepoprawną gramatycznie! Otrzymujemy komunikat np. 17 kwiecień 2006, a tymczasem poprawną formą jest 17 kwietnia 2006. W niuanse gramatyczne wgłębiać się nie będziemy - po prostu jest tak, a nie inaczej i musimy samodzielnie napisać sobie funkcję, która nam to przeformatuje. Umożliwiając dodawanie obsługi nowych języków do aplikacji, powinniśmy pomyśleć o możliwości jej podmiany tak, aby twórca nakładki językowej mógł zaprogramować datę zgodnie z wymogami swego języka. Oto przykład takiej funkcji dla języka polskiego. Jest to modyfikacja strftime() dodająca nowy znacznik - %F będący właśnie odmienioną nazwą miesiąca:
<?php
function localStrftime($format, $timestamp = 0)
{
if($timestamp == 0)
{
// Sytuacja, gdy czas nie jest podany - używamy aktualnego.
$timestamp = time();
}
// Nowy kod - %F dla odmienionej nazwy miesiąca
if(strpos($format, '%F') !== false)
{
$mies = date('m', $timestamp);
// odmienianie
switch($mies)
{
case 1:
$mies = 'stycznia';
break;
case 2:
$mies = 'lutego';
break;
case 3:
$mies = 'marca';
break;
case 4:
$mies = 'kwietnia';
break;
case 5:
$mies = 'maja';
break;
case 6:
$mies = 'czerwca';
break;
case 7:
$mies = 'lipca';
break;
case 8:
$mies = 'sierpnia';
break;
case 9:
$mies = 'września';
break;
case 10:
$mies = 'października';
break;
case 11:
$mies = 'listopada';
break;
case 12:
$mies = 'grudnia';
break;
}
// dodawanie formatowania
return strftime(str_replace('%F', $mies, $format), $timestamp);
}
return strftime($format, $timestamp);
} // end localStrftime();
echo localStrftime('%d %F %Y');
?>
Teraz otrzymujemy prawidłowy tekst: 17 kwietnia 2006.
Oto kilka przydatnych kodów dla funkcji strftime():
- %A - pełna nazwa dnia tygodnia.
- %B - pełna nazwa dnia miesiąca.
- %d - numer dnia miesiąca (od 1 do 31)
- %H - godzina w formacie 24-godzinnym
- %m - numer miesiąca (od 1 do 12)
- %M - minuta
- %S - sekunda
- %T - aktualny czas (równoważnik %H:%M:%S)
- %Y - rok jako liczba czterocyfrowa
- %% - znak %
Więcej kodów można znaleźć na stronie dokumentacji PHP.
[edytuj] Wielojęzyczny interfejs
Kolejnym krokiem na drodze umożliwienia obcokrajowcom przeglądania naszych witryn jest stworzenie kilku wersji językowych. Realizuje się to, tworząc nakładki językowe, w których poszczególnym komunikatom interfejsu przypisane są odpowiednie identyfikatory. Następnie w kodzie aplikacji, zamiast pisać odpowiednie teksty, wywołujemy specjalną funkcję i podajemy ID tekstu do wstawienia. Ta wprowadza w tym miejscu odpowiedni tekst w zależności od wybranego języka. Ponieważ nie znamy jeszcze programowania obiektowego, będziemy musieli napisać odpowiednią bibliotekę strukturalnie:
<?php
$preferencje = array();
$tekstyI18n = array();
function zainstalowanyJezyk($jezyk)
{
// Tutaj mozemy umiescic kod sprawdzajacy, czy nasz serwis posiada
// wersje w podanym jezyku
switch($jezyk)
{
case 'pl':
case 'de':
case 'fr':
case 'en':
return true;
}
return false;
} // end zainstalowanyJezyk();
function uzyjJezyk($jezyk)
{
global $preferencje;
if(zainstalowanyJezyk($jezyk))
{
$preferencje['jezyk'] = $jezyk;
}
} // end uzyjJezyk();
function wybierzGrupe($grupa)
{
global $preferencje, $tekstyI18n;
$tekstyI18n[$grupa] = @parse_ini_file('jezyki/'.$preferencje['jezyk'].'/'.$grupa.'.php');
} // end wybierzGrupe();
function wstaw($grupa, $id)
{
global $tekstyI18n;
if(isset($tekstyI18n[$grupa][$id]))
{
return $tekstyI18n[$grupa][$id];
}
return strtoupper($grupa.':'.$id);
} // end wstaw();
?>
W powyższym kodzie tekstom przypisane są nie tylko identyfikatory - podzielono je także na ładowane oddzielnie grupy. W ten sposób strona z newsami może załadować sobie tylko treści komunikatów dla newsów, z pominięciem np. tekstów przeznaczonych dla forum dyskusyjnego. Oto opis funkcji:
- zainstalowanyJezyk() - zwraca true, jeżeli podany język jest obsługiwany przez nasz skrypt.
- uzyjJezyk() - ustawia podany język.
- wybierzGrupe() - ładuje podaną grupę na podstawie aktualnych ustawień języka. Do wczytania grupy używamy funkcji parse_ini_file() przetwarzającej podany plik identycznym parserem, jak ten, który przetwarza php.ini. Dzięki temu zyskujemy duże możliwości małym kosztem.
- wstaw() - wstawia tekst o podanym ID należący do odpowiedniej grupy na podstawie aktualnych ustawień językowych.
Pliki językowe należy umieszczać w katalogu jezyki/kodjezyka/nazwagrupy.php. Oto przykładowy plik jezyki/pl/global.php:
komunikat = "Komunikat" hello_world = "Witaj swiecie!"
Korzysta z niego poniższy przykład:
<?php
require('./i18n.php');
uzyjJezyk('pl');
wybierzGrupe('global');
echo '<h2>'.wstaw('global', 'komunikat').'</h2>';
echo '<p>'.wstaw('global', 'hello_world').'</p>';
?>
Na początku ładujemy naszą bibliotekę i wybieramy język. Później wczytujemy grupę i wyświetlamy komunikaty. To wszystko - jesteśmy posiadaczami wielojęzycznego interfejsu. Pamiętaj, że jest to tylko przykładowy kod. Aby był on w pełni sprawny, powinieneś umożliwić użytkownikowi zmianę języka, a także zadbać o prawidłową obsługę błędów. Zwróć też uwagę, że dane obecnego systemu przechowywane są w tablicach globalnych. Nie jest to rozwiązanie najlepsze, ale na tym etapie wiedzy powinno nam wystarczyć. Kiedy poznamy programowanie obiektowe, będziesz mógł przepisać ten kod z jego wykorzystaniem, dzięki czemu zyska on na elegancji i bezpieczeństwie.
Do PHP można doinstalować także uniksowy moduł do obsługi wielojęzycznych interfejsów o nazwie gettext. Korzystanie z niego jest jednak dość ryzykowne, ponieważ wiele serwerów nie obsługuje go i aplikacje oparte o ten moduł nie będą działać.
[edytuj] Automatyczna detekcja języka
Przeglądarka internetowa to cenne źródło informacji o internaucie. Przesyła ona m.in. informację o języku, w jakim ona pracuje. Możemy przyjąć, że jest to rodzimy język użytkownika i na podstawie tego automatycznie ustawić mu odpowiednią wersję językową strony. Całość zawarta jest w $_SERVER['HTTP_ACCEPT_LANGUAGE'].
<?php
$zainstalowane = array( // 1
'pl' => 'polski',
'en' => 'angielski',
'fr' => 'francuski'
);
$jezyki = explode(';', $_SERVER['HTTP_ACCEPT_LANGUAGE']); // 2
$jezyki = explode(',', $jezyki[0]);
$uzyty = null;
foreach($jezyki as $jezyk)
{
if(isset($zainstalowane[$jezyk])) // 3
{
$uzyty = $jezyk;
break;
}
}
if(is_null($uzyty)) // 4
{
$uzyty = 'en';
}
echo 'Wybrany język to '.$zainstalowane[$uzyty];
?>
Analiza powyższego skryptu:
- Tablica z nazwami zainstalowanych języków - tylko do celów kontrolnych.
- Najpierw musimy usunąć niepotrzebne nam informacje. HTTP_ACCEPT_LANGUAGE posiada odpowiedni format informacji: języki;ważności, gdzie kody języków oddzielone są przecinkami. Druga część nie jest nam w ogóle potrzebna i możemy ją usunąć. Stąd w drugim explode pojawia się odwołanie $jezyki[0] wskazujące po prostu na pierwszą, istotną część przekazu.
- Pętlą przelatujemy całą tablicę. Jeśli zauważymy, że dany język jest zainstalowany, ustawiamy go i przerywamy pętlę.
- Jeżeli zmienna $uzyty ma w tym miejscu dalej domyślną wartość null, oznacza to, że żaden z języków użytkownika nie jest obsługiwany. W tym wypadku zmuszamy go do czytania po angielsku.
Naturalnie wypadałoby zadbać, aby powyższy skrypt był bardziej przyjazna użytkownikowi i na pierwszym miejscu sprawdzał informacje zapisane w ciastkach. Dopiero kiedy takowych nie będzie, można pobawić się w detekcję. Inaczej użytkownik nie będzie w stanie zmienić "narzuconego" mu języka nawet, jeśli jego własny będzie obsługiwany przez naszą witrynę.
[edytuj] Internacjonalizacja plikami .mo
Takie wykonanie skryptu jest niewygodne. Najbardziej popularnym sposobem jest budowanie tego na plikach .mo. Utwórz plik lang.php: <?php <?php if ($_SERVER['HTTP_ACCEPT_LANGUAGE'] == "pl_PL") // 1 { setlocale(LC_ALL, 'pl_PL'); } else { setlocale(LC_ALL, 'en_US'); } bindtextdomain("domena", "./locale"); //2 bind_textdomain_codeset("domena", 'UTF-8'); //3 textdomain("domena"); //4 ?> ?>
- Przeglądarki wysyłają informajce o języku. Jeśli użytkownik ma ustawiony w przeglądarce język polski, zobaczy polską wersję strony, jeśli nie - angielską.
- Przygotowuje domenę tekstową. Zmień "domena" na coś innego.
- Ustawia domenę na kodowanie UTF-8. Zmień "domena" na coś innego.
- Wybiera domenę. Zmień "domena" na coś innego.
Ten plik dołączamy do każdych plików, które wymagają internacjonalizacji funkcją include("./lang.php"); Teraz należy zamienić wszystkie teksty tak jak tu: <?php // echo "Polska wiadomość"; // zmieniamy na echo _("Polska wiadomość"); Pobieramy edytor plików .po, np. Poedit i tworzymy pliki .mo (obsługa poniżej).
[edytuj] Obsługa PoEdit
Obsługa jest bardzo prosta - wybieramy Plik > Nowy katalog, uzupełniamy dane. Zakładka ścieżki jest bardzo ważna. Pierwsze pole NIE może być ścieżką do katalogu z naszymi plikami, lecz do wcześniejszego katalogu (np. dla D:\Serwer\www\gettext\gettext podajemy D:\Serwer\www\gettext) a w polu niżej za pomocą przycisku nr. 2 dodajemy ścieżkę D:\Serwer\www\gettext\gettext oraz jeśli potrzebne jej podkatalogi.
[edytuj] Testowanie
Aby przetestować skrypty, należy zainstalować GNU gettext i:
- W przypadku systemu Windows - dodać php_gettext.dll do podkatalogu extensions katalogu PHP i aktywować ją w php.ini,
- W przypadku linuxów należy ją dokompilować do php, XAMPP dla Linuksa zawiera ją wkompilowaną.
[edytuj] Zakończenie
Internacjonalizacja jest bardzo ważnym zagadnieniem. Jeśli naprawdę myślimy o ekspansji na rynki zagraniczne, nie lekceważmy jej. Czas zainwestowany w uczynienie serwisu bardziej przyjaznym obcokrajowcom w przyszłości zaprocentuje ich dobrymi wrażeniami z pobytu u nas.
[edytuj] System plików
Przez wcześniejsze rozdziały często przewijały się nam różne funkcje odczytu danych z dysku twardego. Przyszedł czas na zebranie informacji o nich oraz ich usystematyzowanie. Pierwszą rzeczą, o której należy pamiętać, jest wydajność. Wszelkie odwołania do systemu plików są dosyć powolne i często stanowią nawet wąskie gardło w szybkości naszego kodu. Dlatego powinieneś starać się wykonywać ich tak mało, jak tylko się da i buforować wyniki działania niektórych z nich, aby późniejszy kod mógł odwoływać się do nich.
[edytuj] Odczyt danych
Zanim zaczniemy, utwórz sobie plik plik.txt z jakąś długą zawartością (najlepiej w kilku linijkach).
Zawartość pliku można odczytać w PHP na kilka sposobów. Oto pierwszy z nich, wywodzący się jeszcze z języka C:
<?php
$f = fopen('plik.txt', 'r');
while(!feof($f))
{
echo fread($f, 1024);
}
fclose($f);
?>
Każdy dostęp do pliku musi rozpocząć się od jego otwarcia. Zadaniem tym zajmuje się funkcja fopen(). Parametr r nakazuje otwarcie pliku do odczytu. Następnie w pętli pobieramy plik po kawałkach o wielkości jednego kilobajta. W ten sposób dane mogą być przetwarzane "równolegle" z odczytem. Funkcja feof() służy do sprawdzenia, czy osiągnęliśmy koniec pliku. Po zakończonej pracy nasze połączenie z plikiem trzeba zamknąć. Odpowiada za to fclose().
| Uwaga! Zawsze zamykaj swoje pliki. Inaczej możesz zablokować innych użytkowników korzystających ze strony! |
Z powyższego kodu możemy wyrzucić pętlę i pobrać wszystko za jednym zamachem. Wystarczy tylko użyć funkcji filesize(), aby podała nam rozmiar pliku:
<?php
$f = fopen('plik.txt', 'r');
echo fread($f, filesize('plik.txt'));
fclose($f);
?>
Zwróćmy uwagę na jakość podanych przykładów. Zmień nazwę plików, do których się odwołujemy, na jakiś nieistniejący. Oba skrypty wtedy zgłupieją. Pierwszy zaleje nas falą ostrzeżeń przez 30 sekund (potem przestaną się pojawiać), drugi zrobi ich "tylko" kilka (przyczyną jest brak pętli). Dlatego powinniśmy tak przygotować wszystko, abyśmy sami panowali nad komunikatami. Czas stworzyć prymitywną obsługę błędów. Wykorzystamy tutaj operator @, aby zagłuszyć funkcję fopen() i sprawdzić zwracany wynik. Powinna ona zwrócić nam połączenie z plikiem, tj. wartość typu Resource. Zobaczmy:
<?php
$f = @fopen('inny_plik.txt', 'r') or die('Wystąpił błąd.');
echo fread($f, filesize('inny_plik.txt'));
fclose($f);
?>
| Uwaga! Jeśli skrypt odczytujący zacznie się dziwnie zachowywać, pierwszym krokiem powinno być tymczasowe usunięcie operatora @ - inaczej nigdy nie dowiesz się, co jest przyczyną problemów! |
| Porada W celu uproszczenia twojego kodu możesz napisać sobie własny wariant funkcji fopen() posiadający twoją obsługę błędów. Uprości to zarządzanie kodem projektu. |
Od PHP 4.3.0 nie trzeba już rozpisywać się, aby wczytać zawartość pojedynczego pliku. Cała czynność jest zautomatyzowana w funkcji file_get_contents(). Aby tu sprawdzić poprawność otwarcia, wystarczy porównać zwrócony wynik z wartością false, która jest zwracana w przypadku błędu:
<?php
$tresc = @file_get_contents('plik.txt') or die('Wystąpił błąd.');
echo $tresc;
?>
Pisząc księgę gości, poznaliśmy funkcję file(), która zwracaną zawartość rozbijała od razu na tablicę poszczególnych linijek. Dzięki tej właściwości wyświetlimy plik jako listę wypunktowaną HTML bez większych trudności:
<?php
$tresc = @file('plik.txt') or die('Wystąpił błąd.');
echo '<ul>';
foreach($tresc as $linia)
{
echo '<li>'.$linia.'</li>';
}
echo '</ul>';
?>
Pamiętaj, że file() nie gubi znaków końca linii - te są nadal zapisane w poszczególnych linijkach. Dlatego gdy będziesz chciał z powrotem połączyć wszystko w całość, powinieneś napisać
implode('', $tresc);
zamiast np.
implode("\n", $tresc);
Pod żadnym pozorem nie odczytuj plików w ten sposób:
implode('', file('plik.txt'));
Sens takiego kodu można streścić w prostym porównaniu: pakować się tylko po to, by się natychmiast rozpakować. Nie służy to niczemu, a konsumuje niezbędny czas. Aby przekonać się, jak mało wydajne jest takie rozwiązanie, spróbuj załadować tak plik tekstowy o wielkości megabajta, następnie powtórz to samo z wykorzystaniem file_get_contents() i porównaj wrażenia.
| Jeżeli pragniesz odczytywać pliki binarne, otwieraj je z parametrem rb zamiast r. |
Powinniśmy jeszcze wspomnieć o rozwiązaniu tzw. "wg pana od informatyki" (aczkolwiek bardzo go szanujemy). Rozwiązanie najlepiej pokazać na przykładzie:
<?php
$plik='plik.txt';
if (file_exists($plik))
{
$zawartosc=file($plik);
if (count($zawartosc)>0)
{
//nie rob nic. ta linijka jest tu po to, zeby to glupio nie wygladalo.
}
else
{
die("plik: $plik jest pusty");
}
}
else
{
die("plik: $plik nie istnieje");
}
# ... dalszy kod aplikacji ...
?>
Metoda na "pana nauczyciela" polega na pełnym obsłużeniu wszystkich możliwości jakie mogą wystąpić podczas czytania pliku oraz posłużenia się funkcjami typu file_exists(). Wadą takiego rozwiązania jest jednak to, że pisząc kod możemy zakopać się w if'ach gubiąc główny wątek programu ... a także nie jesteśmy w stanie wymyślić wszystkich możliwych sytuacji, które mogą się zdarzyć.
| Uwaga! Staraj się nie stosować na produkcyjnych stronach rozwiązań w stylu "plik: $plik". W ten sposób ujawniasz napastnikowi strukturę swojego serwisu ! |
[edytuj] Zapis danych
Zapis danych wygląda analogicznie do odczytu. Różne jest tylko miejsce docelowe danych. Sposób pierwszy polega na otwarciu pliku funkcją fopen() i skorzystaniu z fwrite() do dodania nowej zawartości. Plik otwieramy z parametrem w (nadpisujemy starą zawartość) lub a (dopisujemy coś do pliku). W przypadku operowania danymi binarnymi, dodajemy jeszcze literę b.
<?php
$f = fopen('./plik.txt', 'w');
fwrite($f, 'To jest nowa zawartosc pliku');
fclose($f);
?>
Po uruchomieniu tego skryptu w plik.txt powinna pojawić nam się nowa zawartość. W przypadku pracy na systemie Linux/Unix sprawdź, czy PHP ma uprawnienia do edycji plików w twoim katalogu roboczym.
W PHP 5.0.0 pojawiła się funkcja file_put_contents(), która upraszcza całą sprawę. Zwraca ona liczbę zapisanych do pliku bajtów i możemy wykorzystać to do kontroli, czy operacja dopisywania faktycznie się udała. Funkcja pobiera dwa parametry: nazwę pliku oraz tekst do wpisania i "firmowo" nie zniekształca danych binarnych.
<?php
if(file_put_contents('./plik.txt', 'To jest nowa zawartosc pliku') != 0)
{
echo 'Udalo sie zapisac nowa zawartosc do pliku.';
}
?>
Zadajmy sobie pytanie, co jeśli musimy dopisać dodatkową treść. Naturalnie file_put_contents() także to potrafi. Trzeba tylko skorzystać z trzeciego parametru, w którym możemy ustawiać flagi. FILE_APPEND jest tym, czego potrzebujemy.
<?php
if(file_put_contents('./plik.txt', ' dopisana tresc', FILE_APPEND) != 0)
{
echo 'Udało się dodać zawartość do pliku.';
}
?>
Ten skrypt będzie już dopisywać dane do pliku, zamiast je nadpisywać.
[edytuj] Informacje o plikach
W wielu przypadkach przydaje się wiedza o tym, co w zasadzie w katalogach mamy. Możemy ją uzyskać, korzystając z rodziny funkcji udostępniających nam różne informacje o plikach. Wszystkie przyjmują za parametr nazwę pliku:
- is_file() - zwraca true, jeśli obiekt jest plikiem.
- is_dir() - zwraca true, jeśli obiekt jest katalogiem.
- is_readable() - zwraca true, jeśli posiadamy prawa do odczytu zawartości obiektu.
- is_writeable() - zwraca true, jeśli posiadamy prawa do zapisu do obiektu.
- file_exists() - zwraca true, jeśli plik/katalog istnieje.
- fowner() - zwraca ID właściciela pliku.
- fgroup() - zwraca ID grupy, do której plik należy.
- fperms() - zwraca uprawnienia pliku.
- filesize() - zwraca wielkość pliku.
- filemtime() - zwraca czas ostatniej modyfikacji pliku lub false, jeśli nie istnieje.
Przy korzystaniu z nich musimy pamiętać o wydajności. Odczyt wszelkich danych z dysku jest dość powolny, dlatego starajmy się jak najwięcej wycisnąć z pojedynczego wywołania funkcji. Oto przykład: załóżmy, że mamy plik A.txt i na jego podstawie generujemy B.txt zawsze, kiedy ulegnie on zmianie (taki kompilator). Musimy zatem napisać mechanizm sprawdzający, czy można uruchomić kompilację, czy też jest ona zbędna.
<?php
if(!file_exists('A.txt'))
{
die('Plik A.txt nie istnieje!');
}
if(file_exists('B.txt'))
{
if(filemtime('B.txt') != filemtime('A.txt'))
{
echo 'Plik A.txt wymaga kompilacji.';
}
else
{
echo 'Można czytać z pliku B.txt';
}
}
else
{
echo 'Plik A.txt wymaga kompilacji';
}
?>
Pozornie wszystko wygląda na poprawne - skrypt prawidłowo raportuje wszystkie sprawy. Jednak robi to zbyt wolno, gdyż przeciążyliśmy go dużą ilością odwołań do dysku twardego. Jeżeli uruchomimy go na witrynie z dużym ruchem, osiągnąłby gorsze wyniki wydajności, niż inne skrypty. Spróbujmy go nieco zmodyfikować. Czy naprawdę potrzebujemy funkcji file_exists()? Okazuje się, że nie. Przecież filemtime() zwróci nam false, jeżeli plik nie będzie istniał i możemy to wykorzystać. Oto poprawiony kod skryptu:
<?php
$czasA = @filemtime('A.txt');
if($czasA === false)
{
die('Plik A.txt nie istnieje!');
}
else
{
$czasB = @filemtime('B.txt');
}
if($czasB !== false)
{
if($czasB != $czasA)
{
echo 'Plik A.txt wymaga kompilacji.';
}
else
{
echo 'Można czytać z pliku B.txt';
}
}
else
{
echo 'Plik A.txt wymaga kompilacji';
}
?>
Zauważmy, w tym przypadku mamy tylko dwa odwołania do dysku, a jeśli plik A.txt nie będzie istnieć, to nawet jedno! Zamiast wykonywania za każdym razem setek nowych funkcji, wykorzystujemy maksymalnie te dane, które już mamy. To jest właściwa filozofia przy pracy z plikami.
[edytuj] Zawartość katalogów
[edytuj] Ścieżki dostępu
[edytuj] Wydajność i bezpieczeństwo
[edytuj] Zakończenie
Plikom poświęciliśmy naprawdę bardzo duży rozdział. Jednak mało która aplikacja PHP wykorzystuje je jako główne źródło danych dla internauty. Znacznie poważniejszym i mającym większe możliwości narzędziem są bazy danych. Zagadnienie to jest omówione w następnej części podręcznika. Czy jednak pliki należy w takim razie wyrzucić? Nie, ze względu na wydajność. Wbrew pozorom, odczyt rekordów z bazy zazwyczaj jest wolniejszy, niż z pliku i w przypadku elementarnych ustawień aplikacji, które nie wymagają złożonego sortowania oraz stosowania rozbudowanych relacji (np. konfiguracja, dane systemowe), można pokusić się o zastąpienie ich plikami.
[edytuj] Wstęp do baz danych
Aplikacje bazodanowe to specjalistyczne aplikacje, których głównym celem jest przechowywanie złożonych informacji, zarządzanie nimi oraz ich udostępnianie. Pod pojęciem "zarządzania" rozumie się ich modyfikację, kasowanie i dodawanie, natomiast "udostępnianie" oznacza możliwość ich pobierania na wszystkie możliwe sposoby we wszystkich kombinacjach, posortowane według dowolnego parametru. Bazy danych wykorzystywane są wszędzie tam, gdzie mamy do czynienia ze złożoną organizacją informacji. Również witryny WWW swą zawartość przechowują najczęściej w bazach, podczas gdy skrypty służą jedynie do ich wyświetlania i przetwarzania. Dzięki temu programista nie musi się martwić pisaniem kodu odpowiedzialnego np. za sortowanie wyników - całą pracę wykonali już za niego twórcy bazy danych.
[edytuj] Przegląd terminologii
Przy pracy z bazami danych będziemy stosowali pewną terminologię. Pora, by się z nią zapoznać. Program, który zarządza bazami danych jest nazywany serwerem baz danych. W tym podręczniku będziemy pisali w skrócie serwer DB (DB od angielskiego słowa database oznaczającego po prostu bazę danych). Dane są w nim ułożone w hierarchiczny sposób, który ilustruje poniższy schemat:
Widzimy tu, że serwer DB może utrzymywać kilka różnych baz danych należących do różnych aplikacji. Pojedyncza baza zawiera tabele o określonej strukturze, w których przechowywane są rekordy zawierające dane. Struktura definiuje pola oraz ich typy, w jakich rekordy mogą przechowywać informacje. Poniżej znajduje się graficzna ilustracja zawartości prostej tabeli. Zawiera ona następujące pola:
- id - unikalny identyfikator rekordu, który pozwala go odnaleźć. Nadawany automatycznie przez serwer DB.
- name - nazwa produktu.
- description - opis produktu
- category - numer kategorii, do jakiej należy produkt.
- counter - ilość produktów, jaką mamy w magazynie.
Oto kilka dodatkowych informacji o terminach użytych powyżej:
- tabela - ang. table
- rekord - zwany też wierszem. Ang. row
- pole - zwana też kolumną. Ang. field
Pomiędzy tabelami w obrębie bazy mogą występować pewne zależności zwane relacjami. W powyższym przykładzie istnieje relacja między produktami, a kategoriami, do których są przypisane (pole category). Wyróżnia się kilka rodzajów relacji:
- Jeden do wielu - jednemu rekordowi z tabeli A przypisanych jest kilka rekordów z tabeli B. Przykładem są nasze kategorie i produkty.
- Wiele do wielu - jednemu rekordowi z tabeli A przypisanych jest kilka rekordów z tabeli B oraz jednemu rekordowi z tabeli B przypisanych jest kilka rekordów z tabeli A. Przykład to książki oraz ich autorzy. Jedna książka może być napisana przez wielu ludzi, jednocześnie pojedynczy człowiek może napisać kilka książek.
- Jeden do jednego - jednemu rekordowi z tabeli A przypisany jest dokładnie jeden rekord z tabeli B. Relacja ta jest rzadko wykorzystywana.
Relacje można odzwierciedlać w strukturze bazy, a także pobierać dane z ich wykorzystaniem (np. pobrać produkty posortowane według tytułów kategorii, które mieszczą się przecież w innej tabeli). Bazy, w których dozwolone są takie operacje, nazywamy relacyjnymi bazami danych, w przeciwieństwie do płaskich baz danych.
Wszystkie operacje na bazach danych wykonujemy, wysyłając do serwera zapytania (ang. query) sformułowane w specjalnym języku SQL (Structured Query Language). Jego podstawy poznamy w niniejszym podręczniku, lecz po bardziej zaawansowane jego możliwości będziesz musiał sięgnąć do innych źródeł. Terminem ANSI SQL określamy nazwę standardu definiującego język SQL. Różne serwery DB implementują jego założenia lepiej lub gorzej, ale w przypadku korzystania ze złożonych możliwości kompatybilność między nimi nie jest zadowalająca.
[edytuj] Przegląd serwerów DB
Oto krótki przegląd niektórych dostępnych serwerów DB:
- MySQL - najpopularniejszy serwer DB do zastosowań WWW stworzony przez szwedzką firmę MySQL AB. Można go używać bez żadnych opłat. MySQL słynie ze swej olbrzymiej wydajności, a najnowsza wersja 5.0, z której będziemy korzystać, obsługuje już prawie cały standard ANSI SQL. Początkowo PHP posiadał wbudowaną obsługę tego serwera, lecz w wyniku zmian licencyjnych musiał zrezygnować z tego i obecnie moduł dla MySQL-a należy dodawać ręcznie.
- PostgreSQL - główny konkurent MySQL-a dostępny na licencji open-source. Jego wydajność jest nieco mniejsza, ale wciąż jest to jedyny darmowy serwer DB, który posiada pełną obsługę standardu ANSI SQL.
- IBM DB2 Express-C - to bezpłatna edycja znanej bazy IBM DB2, udostępniona na systemy Linux i Windows. Jej darmowe, nielimitowane wykorzystanie możliwe jest w odmianach 32 i 64 bitowych, na serwerach z dwoma procesorami i 4 GB RAM. Dostępne są różne metody wykorzystania DB2 z poziomu PHP, w tym również pełne, bezpłatne środowisko Zend Core for IBM oraz wsparcie dla PDO w PHP5. Więcej informacji można odnaleźć na tej stronie: http://www-306.ibm.com/software/data/db2/udb/edition-expressc.html.
- SQLite - ten serwer DB jest dość specyficzny, ponieważ w rzeczywistości jest to biblioteka wbudowywana w aplikacje, które go wykorzystują (np. w interpreter PHP). Stąd też do korzystania z niego nie potrzeba żadnych dodatkowych programów. SQLite jest wbudowany domyślnie w PHP, odkąd zmienił się sposób licencjonowania MySQL-a.
W przeszłości PHP posiadał osobne funkcje do komunikacji z każdą z tych baz, dlatego powstawało wiele napisanych w PHP bibliotek unifikujących interfejs (np. ADODB, Creole). Ponadto dodawały one kilka zwiększających wydajność opcji takich, jak cache'owanie wyników zapytań do plików. W PHP 5.1.0 pojawiła się wreszcie wbudowana biblioteka PHP Data Objects, która także udostępnia jednolite API. Skorzystamy z niej w tym podręczniku, posiłkując się dodatkowo polską nakładką Open Power Driver dodającą możliwość cache'owania wyników zapytań.
[edytuj] Spis treści
Ta część podręcznika rozpocznie się od nauki podstaw języka SQL. Następnie nauczysz się podstaw programowania obiektowego, którego znajomość jest niezbędna, aby korzystać z biblioteki PDO. Dowiemy się także, jak w przeszłości komunikowało się z bazami danych oraz napiszemy przykładową aplikację: system newsów.
- Podstawy języka SQL
- Wstęp do programowania obiektowego
- Biblioteka PDO
- Jak to się robiło kiedyś?
- phpMyAdmin
- Studium przypadku: System newsów
- Bazy danych - co dalej?
[edytuj] Projekt bazy danych
Odstawimy teraz na chwilę PHP i nauczymy się pracować z serwerem baz danych MySQL. Poznamy również podstawy języka SQL do komunikacji z nim.
[edytuj] Wiersz poleceń serwera DB
Aby wykonywać operacje na bazie danych, nie jest konieczne samodzielne tworzenie odpowiednich aplikacji. MySQL posiada specjalny wiersz poleceń, w którym możemy wprowadzać zapytania i oglądać ich wyniki. Serwer DB powinien być już uruchomiony przy starcie systemu operacyjnego, dlatego przystąpimy teraz do rzeczy.
- Jeżeli pracujesz w systemie uniksowym, odszukaj katalog z danymi binarnymi, w którym mogła po instalacji zostać zlokalizowana aplikacja mysql. Uruchom ją poleceniem:
mysql -u root -p
Lub, jeżeli łączysz się poprzez sockety:
mysql -u root -p -S /tmp/mysql.sock
- Użytkownik systemu Windows musi otworzyć systemowy wiersz poleceń i komendą cd przełączyć się na katalog z instalacją MySQLa, a następnie na znajdujący się w nim folder bin. Tutaj należy uruchomić program mysql.exe z parametrami -u root -p. Przykładowe postępowanie wygląda tak:
C:\> D:
D:\> cd Serwer/mysql/bin
D:\Serwer\mysql\bin> mysql.exe -u root -p
Podstawą bezpieczeństwa w bazie danych MySQL są użytkownicy, którzy mają jasno określone przywileje dostępu do baz. Najważniejszym z nich jest root, służący na serwerach produkcyjnych jedynie do administracji. Jednak w domowych warunkach można go wykorzystać także do projektowania własnych baz oraz testowania skryptów. W naszym przypadku podaliśmy podczas instalacji, że hasło tego użytkownika to także root. Podaj je, gdy zostaniesz o to zapytany przez wiersz poleceń.
Jeżeli po wpisaniu hasła ujrzysz znak zachęty mysql>, wszystko poszło OK i jesteśmy gotowi do pracy. W wierszu poleceń obowiązują następujące reguły:
- Zapytania kończymy średnikiem lub sekwencją \g. Jeżeli zabraknie tego elementu, ENTER zwyczajnie będzie Cię przenosił do nowej linijki!
- Pomoc włączamy sekwencją \h
- Opuszczamy wiersz poleceń sekwencją \q
[edytuj] Tworzenie bazy danych
Standardowo po instalacji MySQL posiada stworzone dwie domyślne bazy: test oraz mysql. Pierwsza jest pusta, a druga zawiera ustawienia serwera i lepiej nic tam nie grzebać bez dokładnej znajomości jej budowy. My jednak nie skorzystamy z żadnej z nich.
CREATE DATABASE produkty;
To zapytanie utworzy nam nową bazę danych o nazwie produkty. Musimy się teraz na nią przełączyć:
USE produkty;
Po każdym wykonanym zapytaniu wiersz poleceń wyświetla nam informacje o jego rezultacie. Napis Query OK oznacza, że zostało ono zaakceptowane i poprawnie wykonane. Dalej mogą wystąpić informacje diagnostyczne (ilość dokonanych zmian, czas wykonywania lub lista wyników).
[edytuj] Tworzenie tabeli
Jak powiedzieliśmy, właściwe dane przechowywane są w tabelach. Ich tworzenie polega na definiowaniu szczegółowej struktury rekordów. Utworzymy teraz tabelę produkty przechowującą informacje o różnych produktach:
CREATE TABLE `produkty` ( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `nazwa` VARCHAR(60) NOT NULL, `opis` TEXT NOT NULL, `ilosc` SMALLINT DEFAULT '0', `cena` FLOAT NOT NULL, `jakosc` TINYINT NOT NULL ) TYPE = MYISAM;
W pierwszej linijce nakazujemy utworzenie tabeli o podanej nazwie. Nawias rozpoczyna definicję jej struktury. Opisy budowy poszczególnych pól oddzielone są przecinkami, a informacje do każdego z nich podawane są w następującej kolejności:
- Nazwa pola
- Typ pola
- Czy pole może być puste?
- Wartość domyślna
- Parametry dodatkowe
- Klucze i indeksy
Przyjrzyjmy się zatem poszczególnym polom:
- id - pole to będzie przechowywało liczbowy, jednoznaczny identyfikator rekordu. Dwa rekordy nie mogą posiadać tego samego ID. Pole to powinny posiadać w zasadzie wszystkie tabele, gdyż jest ono podstawą relacji oraz systemów zarządzania bazą. Parametry to:
- INT - typ liczbowy
- NOT NULL - pole nie może być puste
- AUTO_INCREMENT - MySQL automatycznie będzie dbał o nadawanie nowo dodawanym rekordom kolejnych ID.
- PRIMARY KEY - klucz główny określający przeznaczenie tego pola jako podstawy do identyfikacji rekordów.
- nazwa - tutaj będziemy umieszczali nazwę produktu. Parametry to:
- VARCHAR(60) - typ tekstowy. Maksymalna długość to 60 znaków.
- NOT NULL - pole nie może być puste
- opis - opis produktu z dodatkiem wazeliny. Parametry to:
- TEXT - typ tekstowy (maksymalna długość: 64 kB)
- NOT NULL - pole nie może być puste
- ilosc - określa ilość sztuk produktu w magazynie. Parametry to:
- SMALLINT - typ liczbowy
- DEFAULT '0' - pole może pozostać puste, a domyślnie nowym rekordom nadajemy tutaj wartość 0.
- cena - cena produktu. Parametry to:
- FLOAT - typ liczbowy z obsługą ułamków (aby można było także grosze uwzględniać)
- NOT NULL - pole nie może być puste
- jakosc - liczbowe oznaczenie jakości produktu. Parametry to:
- TINYINT - typ liczbowy
- NOT NULL - pole nie może być puste
Ostatnia linijka zawiera zamknięcie definicji struktury oraz określenie typu tabeli. Nie musisz się nim teraz przejmować. Wystarczy na razie wiedzieć, że domyślnym jest obecnie InnoDB, ale do większości zastosowań używa się wydajniejszego MyISAM.
Zauważyłeś już pewnie, że w zapytaniu wykorzystaliśmy kilka różnych typów liczbowych. Oto dokładniejsza specyfikacja ważniejszych typów:
- TINYINT - liczba jednobajtowa. Wartości od -128 do 127.
- UNSIGNED TINYINT - liczba jednobajtowa bez znaku. Wartości od 0 do 255. Słowo UNSIGNED przed nazwami kolejnych typów liczbowych robi to samo, co w tym przypadku.
- SMALLINT - liczba dwubajtowa. Wartości od -32768 do 32767, a bez znaku od 0 do 65535.
- MEDIUMINT - liczba trzybajtowa. Wartości od -8388608 do 8388607, a bez znaku od 0 do 16777215.
- INT - liczba czterobajtowa. Wartości od -2147483648 do 2147483647, a bez znaku od 0 do 2147483647.
- BIGINT - liczba ośmiobajtowa. Wartości od -9223372036854775808 do 9223372036854775807, a bez znaku od 0 do 18446744073709551615.
- FLOAT - liczba zmiennoprzecinkowa czterobajtowa (tak, jak w PHP).
- VARCHAR(M) - tekst o długości N od 0 do M znaków, gdzie M < 256. W pamięci zajmuje N + 1 bajtów (dodatkowy zawiera długość tekstu).
- CHAR(M) - tekst o długości od 0 do M znaków, gdzie M < 256. W przeciwieństwie do poprzedniego typu, zajmuje w pamięci zawsze M bajtów nawet, jeżeli znajdujący się w nim tekst jest krótszy.
- TEXT - typ tekstowy doskonały do przechowywania dłuższych treści. Można w nim zmieścić aż 64 kB danych (65535 znaków).
- BLOB - typ do przechowywania danych binarnych, np. plików. Maksymalna wielkość to także 64 kB.
- BOOL - typ logiczny, równoważnik zapisu TINYINT(1).
- DATE - wyspecjalizowany typ do przechowywania daty. Bardzo rzadko wykorzystywany w poważniejszych aplikacjach PHP ze względu na jego niewygodne przetwarzanie i formatowanie.
W przypadku typów liczbowych bardzo często spotyka się zapisy np. INT(8) lub MEDIUMINT(6). Liczby w nawiasach nie mają nic wspólnego z wielkością danych, jakie można w nich trzymać. Mówią one bazie danych MySQL, że jeśli dana liczba jest krótsza niż np. 8 znaków, to należy ją dopełnić spacjami do tej długości, kiedy będziemy na niej operować, jak na tekście.
[edytuj] Specyfika języka SQL
Napisaliśmy już kilka zapytań i widać z nich, że SQL w wielu miejscach przypomina zwyczajny mówiony angielski. Pora poznać kilka jego cech:
- Wielkość liter nie odgrywa żadnej roli, lecz programiści używają ich ze względów estetycznych. Zapis create table `produkty` także jest poprawny.
- Odwrócone apostrofy są używane do określania nazw tabel, baz i pól, ale nie są one niezbędne. Zapisy create table produkty oraz id int not null są poprawne, lecz trzeba tu zwrócić uwagę na jedną rzecz, którą zademonstrujemy na przykładzie. Otóż chcielibyśmy mieć w tabeli pole "order". Piszemy więc order int not null, ale nagle okazuje się, że MySQL zgłasza w tej linijce błąd. Co jest nie tak? "order" jest jednym ze słów kluczowych języka SQL i aby tak nazwać tabelę, musimy koniecznie umieścić ją w odwróconych apostrofach. Wielu programistów, aby zapytania nie przypominały grochu z kapustą, z definicji używa ich zatem wszędzie, unikając w ten sposób wszystkich niespodzianek, a także niekompatybilności z nowymi wersjami, które dodają coraz więcej słów kluczowych.
[edytuj] Ćwiczenia
W ramach treningu utwórz następujące tabele:
- Tabela klientów sklepu z polami id, imie, nazwisko, wiek, miejscowosc, ulica, numer_domu, numer_mieszkania, telefon.
- Tabela dostawców sklepu z polami id, nazwa, dzien_tyg_dostawy, naleznosc
- Tabela kategorii produktów z polami id, nazwa, opis, ilosc_produktow.
Pamiętaj, że możesz sprawdzić strukturę już utworzonej tabeli poleceniem:
DESCRIBE tabela;
Jeżeli coś Ci nie wyszło, wykonaj
DROP TABLE tabela;
aby skasować tabelę.
[edytuj] Zarządzanie rekordami
Wiemy już, jak utworzyć bazę danych oraz strukturę tabel. Niewątpliwie bez tych operacji nie można zacząć, lecz później podstawowymi operacjami stają się te, dzięki którym możemy zarządzać danymi. Właśnie w tym odcinku dowiemy się, jak dodawać, modyfikować oraz usuwać niepotrzebne rekordy. Na początek proste polecenie, które pozwoli nam na wyświetlenie obecnej zawartości tabeli:
SELECT * FROM `tabela`
Gdzie zamiast tabela wstawiamy nazwę naszej tabeli. Szczegółowy opis tego zapytania poznamy w następnych dwóch odcinkach. Teraz posłuży nam on jedynie do sprawdzania, czy wszystko przebiega poprawnie.
[edytuj] Dodawanie rekordów
Aby dodać rekord do tabeli, należy wysłać zapytanie INSERT. Ma ono generalnie dwie możliwe składnie:
INSERT INTO `tabela` VALUES('Wartość pola 1', 'Wartość pola 2', 'Wartość pola 3');
INSERT INTO `tabela` (`pole1`, `pole2`, `pole3`) VALUES('Wartość pola 1', 'Wartość pola 2', 'Wartość pola 3');
Oba powodują utworzenie nowego rekordu w podanej tabeli, lecz istnieje między nimi pewna różnica. W pierwszym zapytaniu musimy bezwzględnie podać wartości wszystkich pól nowego rekordu, jakie mamy zdefiniowane w strukturze tabeli, w identycznej kolejności. Drugie zapytanie pozwala nam w pierwszym z nawiasów wymienić listę pól, jakie nas interesują i dopiero potem podać ich wartości. W praktyce znacznie częściej używa się właśnie jego, gdyż nie trzeba podawać wartości pól ID, które nadawane są przez bazę automatycznie. Wróćmy zatem do naszej tabeli produkty. Wstawmy do niej kilka rekordów:
INSERT INTO `produkty` (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES( 'Długopisy niebieskie', 'Długopisy z niebieskim wkładem firmy XXX', 100, 2.15, 3); INSERT INTO `produkty` (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES( 'Długopisy czerwone', 'Długopisy z czerwonym wkładem firmy XXX', 50, 2.15, 3); INSERT INTO `produkty` (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES( 'Zszywacze', 'Metalowy zszywacz + 100 zszywek.', 30, 9.50, 4); INSERT INTO `produkty` (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES( 'Karteczki samoprzylepne', 'Samoprzylepne kartki koloru żółtego 10x10 cm w kompletach po 100 sztuk', 200, 3.60, 2);
Istnieje także możliwość wstawienia kilku rekordów naraz za pomocą jednego zapytania INSERT:
INSERT INTO `produkty` (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES ('Strugaczki', 'Czerwone, do dwóch rozmiarów ołówków', 60, 0.90, 4), ('Gumki do ścierania', 'Gumki do ścierania ołówków firmy ZZZ', 97, 0.50, 3), ('Spinacze do papieru', 'Metalowe spinacze do papieru w kompletach po 50 sztuk.', 68, 0.50, 4);
Tutaj po VALUES podajemy kilka bloków wartości dla kolejnych rekordów, każdy z nich w nawiasach i oddzielony przecinkiem.
Pamiętaj, że wartości tekstowe musimy zawsze podawać w apostrofach. Liczby można podawać zarówno z nimi, jak i bez, lecz podczas programowania apostrofami obejmuje się najczęściej wszystkie wstawiane ze skryptu wartości.
[edytuj] Modyfikowanie rekordów
Czasem zachodzi konieczność zmodyfikowania niektórych informacji. Tu pomocne będzie zapytanie UPDATE. Ma ono bardzo prostą składnię:
UPDATE `tabela` SET `pole1` = 'Nowa wartość', `pole2` = 'Nowa wartość';
Jednak uważaj! Gdybyś wykonał powyższe zapytanie, podstawiając odpowiednie dane, okazałoby się, że zmiany zostały wprowadzone we wszystkich rekordach! Bardzo rzadko jest to pożądana rzecz, ponieważ o wiele częściej chcemy zmodyfikować pewną, konkretną grupę. Do jej uwzględnienia użyjemy nowej klauzuli: WHERE wyrażenie umieszczanej po liście pól do podmiany. Wyrażenie jest dowolnym poprawnym wyrażeniem języka SQL, a układa się je podobnie, jak te w PHP. Szczegółowe informacje o budowaniu wyrażeń poznamy w następnym odcinku, teraz ograniczymy się do kilku prostych sztuczek.
Na początek zmienimy opis i cenę produktu o ID 1:
UPDATE `produkty` SET `opis` = 'Długopisy niebieskie firmy YYY', `cena` = '2.45' WHERE `id` = '1';
Teraz coś trudniejszego: nasz sklep planuje podwyżkę cen najlepszych produktów (jakość 4) o 50 groszy. Wiele osób próbuje robić to, pobierając ceny wszystkich interesujących je produktów i zmieniając je setkami zapytań UPDATE, po jednym dla jednego rekordu. Jest to bardzo niepraktyczne, ponieważ język SQL jest na tyle zaawansowanym narzędziem, że radzi sobie z tym bez trudu:
UPDATE `produkty` SET `cena` = (`cena` + 0.5) WHERE `jakosc` = 4;
Powinniśmy teraz ujrzeć informację, że zmodyfikowane zostały trzy rekordy.
[edytuj] Usuwanie rekordów
Niepotrzebne rekordy kasujemy zapytaniem DELETE. Podobnie, jak w poprzednim przypadku, należy zastosować klauzulę WHERE, aby określić, które z nich chcemy usunąć. Inaczej możemy pożegnać się z całą zawartością tabeli.
DELETE FROM `produkty` WHERE `id` = 3;
Zdarza się, że tabelę trzeba rzeczywiście wyczyścić, np. z danych testowych, aby aplikacja mogła być używana na normalnym serwerze. Jednak wtedy nie powinno się wykorzystywać polecenia DELETE FROM `produkty`. Do tego celu służy specjalne zapytanie:
TRUNCATE `produkty`;
Różnica pomiędzy pierwszym i drugim jest podobna, jak pomiędzy zaznaczeniem wszystkich plików na dysku i kliknięciu "Delete", a uruchomieniem jego formatowania.
Wykonując wiele zapytań DELETE oraz INSERT zauważysz, że pozostają Ci luki w numeracji. Spróbuj dodać teraz jakiś rekord. Jeżeli wykonałeś wcześniej zapytanie kasujące ten o ID 3, MySQL pozostawi tam lukę, nadając nowemu rekordowi następny w kolejności ID - 4. Prawdopodobnie z przyczyn estetycznych niektórym nowym programistom to zachowanie przeszkadza, lecz jest to zupełnie błędne podejście do problemu. Jak wspomnieliśmy bowiem, MySQL jest relacyjną bazą danych, w której rekordy z jednej tabeli mogą być połączone odpowiednimi relacjami z rekordami w drugiej. Wyobraźmy sobie więc, co by się stało, gdybyśmy skasowali jakąś kategorię np. newsów w naszym serwisie, a potem dodali nową i okazało się, że MySQL zaliczył w jej poczet wszystkie newsy z tej usuniętej, gdyż nowy rekord zajął pustą lukę w numeracji. Działanie takie stwarza poważne zagrożenie synchronizacji bazy i wprowadza w nią element losowości. Dlatego zaufaj twórcom serwerów DB, oni naprawdę znają się na rzeczy i nie podejmuj zbędnych prób zmieniania na gorsze tego, co wyraźnie służy zarówno tobie, jak i twojej bazie.
[edytuj] Zamiana rekordów
Bardzo przydatną operacją jest automatyczne podmienianie rekordów. Polega ono na tym, że jeżeli wstawiamy rekord A do tabeli, w której znajduje się już rekord B o podobnym kluczu, jest on automatycznie nadpisywany przez system. MySQL posiada dwa zapytania, które są pomocne przy podmienianiu rekordów, jednak zanim się do nich dobierzemy, musimy utworzyć sobie nową tabelę.
Aby MySQL mógł zdecydować, czy rekord należy nadpisać, musi wiedzieć, które pola tabeli przechowują wartości unikalne, czyli takie, że nie można znaleźć dwóch rekordów o identycznej wartości w tym polu. Wiemy już, że taką właściwość powinno mieć pole id, i rzeczywiście - informujemy o tym bazę, tworząc dla niego tzw. klucz główny (PRIMARY KEY). Dba on o to, aby wartości się nie powtarzały. Jednak klucz ten można nałożyć tylko na jedno pole naraz, a operacja REPLACE przy rekordach mających z definicji nie tylko unikalne, ale też nadawane automatycznie ID, zbyt dużego sensu nie ma. Dlatego istnieje indeks UNIQUE, który może być nałożony na dowolnie dużo pól i także sprawi, że zyskają one tę właściwość. Aby pokazać to w praktyce, utwórzmy tabelę dla elektronicznego słownika (w wersji uproszczonej, oczywiście):
CREATE TABLE `slownik` ( `id` MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `haslo` VARCHAR(40) NOT NULL, `znaczenie` VARCHAR(255) NOT NULL, UNIQUE( `haslo` ) ) ENGINE = MYISAM;
Mamy tu trzy pola: id, haslo oraz znaczenie. ID służy jedynie celom administracyjnym (wyszukiwanie względem liczby jest zawsze szybsze, niż względem tekstu). Dla użytkownika naszego słownika najważniejsze będzie jednak pole haslo, które także powinno mieć wartości unikalne. Indeks UNIQUE tworzymy tak, jak na przykładzie: po liście pól wstawiamy słowo kluczowe UNIQUE, w którym w nawiasie wymieniamy pola posiadające taką właściwość.
Jeżeli jesteśmy administratorami słownika, mogącymi dodawać do niego nowe hasła, unikalność obsługiwana przez MySQL ma dla nas duże znaczenie. Kiedy będzie w nim bowiem już dużo słów, ze zwykłej pomyłki możemy spróbować dodać po raz drugi to samo. Tradycyjne polecenie INSERT zareagowałoby błędem powiadamiającym, że próbujemy utworzyć rekord, w którym powtarza się unikalna wartość. Dlatego możemy zastosować inne zapytanie: REPLACE. Wstawiamy nim pierwsze trzy hasła:
REPLACE slownik (haslo, znaczenie) VALUES('tree', 'drzewo'); REPLACE slownik (haslo, znaczenie) VALUES('house', 'dom (budynek)'); REPLACE slownik (haslo, znaczenie) VALUES('sign', 'znak');
W tym momencie działają one identycznie, jak znane nam zapytanie INSERT (mają nawet podobną składnię, chociaż dozwolone jest stosowanie także składni polecenia UPDATE). Możemy zapytać się, jaka jest zawartość tabeli i pokażą nam się wszystkie trzy wymienione rekordy. Spróbujmy teraz po raz drugi dodać istniejące hasło (np. tree):
REPLACE slownik (haslo, znaczenie) VALUES('tree', 'drzewo, drzewko');
Query OK, 2 rows affected (0.00 sec)
Zwróć uwagę, jaki komunikat kontrolny pokazał MySQL: zmodyfikowane zostały dwa rekordy. Wyświetlmy zawartość tabeli: okazuje się, że pierwotny rekord tree (z ID równym 1) został skasowany, a na jego miejscu pojawił się nowy, któremu serwer nadał ID 4. Mamy zatem sformułowaną zasadę działania polecenia REPLACE:
- Spróbuj dodać nowy rekord.
- Jeśli nie powiedzie się z powodu duplikacji unikalnej wartości, usuń stary rekord powodujący kolizję.
- I ponownie dodaj nowy rekord.
| Uwaga! Polecenie REPLACE nie jest częścią standardu ANSI SQL, dlatego najprawdopodobniej nie będzie zaimplementowane na bazach innych, niż MySQL! |
REPLACE nie jest jedynym poleceniem realizującym zamianę rekordów. Od wersji 4.1.0 MySQL obsługuje także rozszerzenie zapytania INSERT o element ON DUPLICATE KEY UPDATE, zgodne ze standardem ANSI SQL. Dzięki niemu nie trzeba kasować kolidującego rekordu, lecz jedynie nadpisać jego wartości nowymi. Dodajmy ponownie jakieś istniejące hasło:
INSERT INTO slownik (haslo, znaczenie) VALUES('house', 'dom (budynek), rodzaj obiektu mieszkalnego.') ON DUPLICATE KEY UPDATE znaczenie=VALUES(znaczenie);
Query OK, 2 rows affected (0.02 sec)
Pierwsza część tego zapytania to znany nam już dobrze INSERT. Próbuje on wstawić nowy rekord. Kiedy jednak zajdzie kolizja, do akcji wkracza nowa część: ON DUPLICATE KEY UPDATE, w której możemy zdefiniować sposób nadpisania starego rekordu według składni nazwa_pola = nowa_wartosc, nazwa_pola = nowa_wartosc, .... Możemy tutaj ustawiać statyczne wartości, np. pole = 1, lub też odwołać się do wartości pola, która miała być wstawiona poleceniem INSERT. Służy do tego funkcja VALUES(nazwa_pola). Wykorzystaliśmy ją w powyższym przykładzie, aby wprowadzić nową definicję hasła na miejsce starej.
Dzięki możliwości nadpisywania istniejących rekordów, nasza baza stanie się bardziej elastyczna, a my nie będziemy musieli samodzielnie realizować sprawdzania, czy przypadkiem nie spowodujemy jakiejś kolizji. Jeżeli jednak zamierzamy napisać aplikację, która będzie mogła pracować z różnymi systemami DB, musimy sprawdzić, czy wszystkie one obsługują podaną operację, lub napisać dla każdej z nich części kodu osobno.
[edytuj] Błędy w zapytaniach
Także w języku SQL można popełnić błędy, które uniemożliwią wykonanie zapytania. Każdy serwer DB raportuje je inaczej. Ponieważ sztandarową bazą w tym podręczniku jest MySQL, pokażemy sposoby debugowania zapytań właśnie dla niego.
Zacznijmy od błędów składni. Wykonaj następujące zapytanie:
UPDATE `produkty` SET `opis` = 'Długopisy niebieskie firmy YYY' `cena` = '2.45' WHERE `id` = '1';
Zawiera ono celowo wprowadzony błąd, który powoduje pokazanie się komunikatu
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'cena` = '2.45' WHERE `id` = '1'' at line 1
Komunikat pokazał nam użyteczną informację o miejscu lokalizacji problemu. Według niego coś jest nie tak w okolicy ciągu cena` = '2.45' WHERE `id` = '1'. Niepisana zasada mówi, że najczęściej błąd popełniliśmy bezpośrednio przed nim. Zobaczmy więc, co znajduje się w zapytaniu przed odwołaniem do pola "cena". Okazuje się, że brakuje przecinka oddzielającego je od porzedniej definicji nowej wartości. Kiedy go tam umieścimy, wszystko zaczyna prawidłowo działać.
Inny rodzaj problemu może powstać przy pracy z polami "PRIMARY KEY" oraz "UNIQUE" (o nich dalej). Oba te atrybuty nadają polu właściwość unikalności, czyli nie mogą istnieć dwa rekordy o takich samych wartościach w tym miejscu. Przykładowo wywołajmy takie zapytanie INSERT:
INSERT INTO `produkty` VALUES( '3', 'Długopisy niebieskie', 'Długopisy z niebieskim wkładem firmy XXX', 100, 2.15, 3);
Jest w nim wymieniony ID produktu. Jeżeli wykonywałeś wszystkie zapytania w tym odcinku, prawdopodobnie nic się teraz nie stanie, ponieważ rekord o ID 3 niedawno skasowaliśmy. W takim wypadku wykonaj je jeszcze raz, a baza pokaże ci wtedy
Duplicate entry '3' for key 1
Oznacza on tyle, że w kluczu pierwszym (w naszym przypadku jest to pole "id") próbujemy po raz drugi umieścić wartość 3, co jest niedozwolone.
[edytuj] Pobieranie rekordów
Osobnego omówienia wymaga operacja pobierania rekordów z bazy danych, która jest esencją pracy z tego typu aplikacjami. W poprzednim rozdziale poznaliśmy dla potrzeb testowych zapytanie SELECT * FROM tabela, lecz było ono podane wyłącznie, aby można było sprawdzić, czy modyfikacja zawartości tabel rzeczywiście się powiodła. Tymczasem jego możliwości są dużo bardziej skomplikowane i umożliwiają wykonywanie wielu ciekawych rzeczy. Teraz przyjrzymy się im dokładniej.
[edytuj] Filozofia zapytania SELECT
Zapytanie SELECT to w zasadzie zbiór klauzul, które możemy dodawać i odejmować, działających jak filtry dla danych. Jako rezultat działania otrzymujemy zawsze to, co przejdzie przez wszystkie z nich. Podstawową i jedyną obowiązkową klauzulą jest oczywiście SELECT dane pokazująca, co należy pobrać. Za dane możemy podstawić listę wyrażeń odseparowanych przecinkami, które są nam potrzebne. Oto malutki przykład:
SELECT 1
Jego wykonanie w wierszu poleceń spowoduje wyświetlenie się tekstowej tabelki z wynikiem:
+---+ | 1 | +---+ | 1 | +---+ 1 row in set (0.00 sec)
Pierwszy wiersz zawiera zawsze nazwy wszystkich kolumn w rekordzie. Nazwy te wykorzystamy, kiedy zaczniemy pisanie skryptów łączących się z bazą, aby pobrać wyniki z rekordów. Kolejne wiersze obrazują wszystkie pasujące rekordy wyników.
Z doświadczenia możemy powiedzieć, że pozostawianie bazie danych MySQL spraw nazewnictwa pól na liście wyników nie należy do najszczęśliwszych i w wielu przypadkach zachodzi potrzeba jego ręcznego określenia nazwy. Można to uczynić, dodając po każdej wartości do pobrania dodatku AS `nowa_nazwa`:
SELECT 1 AS `pole`
+------+ | pole | +------+ | 1 | +------+ 1 row in set (0.00 sec)
Pobieranie statycznych danych nie wnosi jednak dużo do funkcjonalności baz danych. O wiele więcej zyskamy, kiedy będziemy mogli pobierać rekordy z tabel. Aby móc wymienić nazwy pól w klauzuli SELECT, musimy dołożyć do zapytania taki dodatek: FROM lista_tabel. lista_tabel to lista tabel odseparowanych przecinkami, z których zamierzamy korzystać. W tym odcinku poprzestaniemy na pojedynczej tabeli i dopiero później powiemy sobie, jak można obsłużyć więcej. Spróbujmy dowiedzieć się zatem czegoś o stworzonej ostatnio tabeli produkty:
SELECT id, nazwa FROM produkty; +----+-------------------------+ | id | nazwa | +----+-------------------------+ | 1 | Dlugopisy niebieskie | | 2 | Dlugopisy czerwone | | 3 | Zszywacze | | 4 | Karteczki samoprzylepne | +----+-------------------------+ 4 rows in set (0.02 sec)
Przypominamy, że zapytania kończymy średnikiem. Tabelka powyżej to wynik jego działania. Widzimy w niej pobrane pola id oraz nazwa wszystkich rekordów w tabeli produkty. Gdybyśmy chcieli pobrać wartości wszystkich pól, moglibyśmy w wykazie danych wpisać po prostu gwiazdkę:
SELECT * FROM produkty;
Okazuje się jednak, że paradoksalnie takie rozwiązanie ma mniejszą wydajność, niż wypisanie wszystkich pól ręcznie! Miej to na uwadze podczas projektowania twoich zapytań.
[edytuj] Klauzula WHERE
Do tej pory MySQL zwracał nam wszystkie rekordy bez wyjątku, lecz w codziennej praktyce na dane nakłada się rozmaite warunki pełniące rolę filtrów. Jeżeli chcemy wiedzieć, którzy użytkownicy naszego serwisu napisali już ponad 200 postów, nie musimy pobierać wszystkiego i dokonywać ręcznej analizy informacji. Wystarczy nałożyć na zapytanie warunek, który nie dopuści do listy wyników tych rekordów, gdzie ilość postów jest mniejsza niż podana wartość. Wszystko to należy do kompetencji klauzuli WHERE warunek, która pojawia się również przy zapytaniach UPDATE oraz DELETE. Najprostszą operacją jest bez wątpienia zwrócenie konkretnego rekordu:
SELECT id, nazwa FROM produkty WHERE id = 3; +----+-------------------------+ | id | nazwa | +----+-------------------------+ | 3 | Zszywacze | +----+-------------------------+ 1 row in set (0.01 sec)
Identycznie, jak w przypadku PHP, warunek jest zbiorem wyrażeń połączonych operatorami, których kolejnością także możemy manewrować wykorzystując nawiasy. Oto lista operatorów logicznych oraz operatorów porównania:
| Operator | Nazwa | Składnia | Opis |
|---|---|---|---|
| = | Równość | wyrażenie = wyrażenie | Zwraca prawdę, jeżeli oba wyrażenia mają identyczną wartość. |
| != | Nierówność | wyrażenie != wyrażenie | Zwraca prawdę, jeżeli oba wyrażenia mają różne wartości. |
| <> | Nierówność | wyrażenie <> wyrażenie | Zwraca prawdę, jeżeli oba wyrażenia mają różne wartości. |
| < | Mniejsze niż | wyrażenie < wyrażenie | Zwraca prawdę, jeżeli lewe wyrażenie ma mniejszą wartość od prawego. |
| > | Większe niż | wyrażenie > wyrażenie | Zwraca prawdę, jeżeli lewe wyrażenie ma większą wartość od prawego. |
| <= | Mniejsze lub równe | wyrażenie <= wyrażenie | Zwraca prawdę, jeżeli lewe wyrażenie ma mniejszą lub równą wartość prawemu. |
| >= | Większe lub równe | wyrażenie >= wyrażenie | Zwraca prawdę, jeżeli lewe wyrażenie ma większą lub równą wartość prawemu. |
| ! | Negacja (nie) | !wyrażenie | Zwraca prawdę, jeżeli wyrażenie jest fałszywe i fałsz, jeśli prawdziwe. |
| NOT | Negacja (nie) | NOT wyrażenie | Zwraca prawdę, jeżeli wyrażenie jest fałszywe i fałsz, jeśli prawdziwe. |
| AND | Koniunkcja logiczna (i) | wyrażenie AND wyrażenie | Zwraca prawdę, jeżeli oba wyrażenia są prawdziwe. |
| OR | Alternatywa logiczna (lub) | wyrażenie OR wyrażenie | Zwraca prawdę, jeżeli przynajmniej jedno z wyrażeń jest prawdziwe. |
Operatory AND oraz OR posiadają także warianty && oraz || (tak samo PHP posiada and i or). Oprócz tego, dostępne są tradycyjne operatory arytmetyczne, a także kilka specjalnych, niewystępujących nigdzie indziej:
[edytuj] pole IN(wartosci)
Prawda, jeśli wartość pola znajduje się na liście wartości podanej w nawiasach. Spróbujemy pobrać nim rekordy o ID 1, 2 oraz 4.
SELECT id, nazwa FROM produkty WHERE id IN(1,2,4);
[edytuj] pole NOT IN(wartosci)
Prawda, jeśli wartość pola NIE znajduje się na liście wartości podanej w nawiasach. Poniżej to samo zapytanie, ale omijające rekordy o podanych ID.
SELECT id, nazwa FROM produkty WHERE id NOT IN(1,2,4);
[edytuj] pole BETWEEN mniejszy AND wiekszy
Wartość pola znajduje się w przedziale od mniejszy do wiekszy. Spróbujemy pobrać nim produkty, których ilość w magazynie waha się od 0 do 100:
SELECT id, nazwa FROM produkty WHERE ilosc BETWEEN 0 AND 100;
[edytuj] pole IS NULL
Podczas tworzenia tabel powiedzieliśmy sobie nieco o polach z dozwolonymi wartościami pustymi (null). Za pomocą tego operatora oraz jego przeczenia IS NOT NULL możemy sprawdzać, czy dane pole zawiera wartość pustą, czy nie. W naszej tabeli tylko pole ilosc zezwala na użycie wartości pustych. Sprawdźmy więc, które rekordy takowe posiadają:
SELECT id, nazwa FROM produkty WHERE ilosc IS NULL;
[edytuj] Klauzula ORDER BY
Ta klauzula dodaje możliwość sortowania wyników według określonego kryterium. Tradycyjnie podajemy ją po WHERE. Jej składnia to: ORDER BY lista_wyrazen. lista_wyrazen to lista oddzielonych przecinkami wyrażeń, według wartości których zostaną posortowane rekordy. Domyślnie obowiązuje kolejność od najmniejszego do największego, ale możemy ją odwrócić, dodając po wyrażeniu słowo DESC. Spróbujmy posegregować nasze rekordy względem ceny.
SELECT id, nazwa, cena FROM produkty ORDER BY cena; +----+-------------------------+------+ | id | nazwa | cena | +----+-------------------------+------+ | 6 | Gumki do scierania | 0.5 | | 7 | Spinacze do papieru | 0.5 | | 5 | Strugaczki | 0.9 | | 1 | Dlugopisy niebieskie | 2.15 | | 2 | Dlugopisy czerwone | 2.15 | | 4 | Karteczki samoprzylepne | 3.6 | | 3 | Zszywacze | 9.5 | +----+-------------------------+------+ 7 rows in set (0.00 sec)
A teraz w odwrotnej kolejności:
SELECT id, nazwa, cena FROM produkty ORDER BY cena DESC; +----+-------------------------+------+ | id | nazwa | cena | +----+-------------------------+------+ | 3 | Zszywacze | 9.5 | | 4 | Karteczki samoprzylepne | 3.6 | | 1 | Dlugopisy niebieskie | 2.15 | | 2 | Dlugopisy czerwone | 2.15 | | 5 | Strugaczki | 0.9 | | 6 | Gumki do scierania | 0.5 | | 7 | Spinacze do papieru | 0.5 | +----+-------------------------+------+ 7 rows in set (0.00 sec)
Możemy też przyjąć kolejne kryterium sortowania, jeśli dwa rekordy będą miały identyczną cenę. Przyjmijmy, że wtedy będą one sortowane pod względem tytułu.
SELECT id, nazwa, cena FROM produkty ORDER BY cena DESC, nazwa; +----+-------------------------+------+ | id | nazwa | cena | +----+-------------------------+------+ | 3 | Zszywacze | 9.5 | | 4 | Karteczki samoprzylepne | 3.6 | | 2 | Dlugopisy czerwone | 2.15 | | 1 | Dlugopisy niebieskie | 2.15 | | 5 | Strugaczki | 0.9 | | 6 | Gumki do scierania | 0.5 | | 7 | Spinacze do papieru | 0.5 | +----+-------------------------+------+ 7 rows in set (0.00 sec)
Widzimy teraz, że rekordy "Długopisy czerwone" oraz "Długopisy niebieskie" zamieniły się miejscami. Jeśli połączymy wszystko z klauzulą WHERE, poczujemy prawdziwą potęgę baz danych. Posortujmy według ceny tylko te rekordy, których jakość oznaczona jest jako 3:
SELECT id, nazwa, cena FROM produkty WHERE jakosc = 3 ORDER BY cena DESC, nazwa; +----+-----------------------+------+ | id | nazwa | cena | +----+-----------------------+------+ | 2 | Dlugopisy czerwone | 2.15 | | 1 | Dlugopisy niebieskie | 2.15 | | 6 | Gumki do scierania | 0.5 | +----+-----------------------+------+ 3 rows in set (0.00 sec)
[edytuj] Klauzula LIMIT
Na stronach internetowych często prezentowane są olbrzymie ilości informacji. Aby wyświetlenie ich spisu nie przeciążało łącza, listę wyników dzieli się na strony (inaczej: porcjuje) tak, że naraz wyświetla się jedynie niewielka jej część, a do reszty możemy się dostać poprzez odpowiednie linki nawigacyjne. Oczywiste jest, że wybieranie tego małego kawałka danych powinno zachodzić po stronie bazy danych, a nie PHP. Tak jest w istocie, dzięki klauzuli LIMIT. Pozwala nam ona na zażądanie jedynie określonego kawałka rekordów pasujących do podanego wyrażenia. Pobiera ona dwie informacje: numer (nie mylić z ID!) rekordu w wynikach, od którego należy zacząć pobieranie oraz interesującą nas ilość rekordów.
Istnieje kilka składni tego polecenia. Pokażemy je na przykładach.
SELECT id, nazwa FROM produkty LIMIT 3; +----+-----------------------+ | id | nazwa | +----+-----------------------+ | 1 | Dlugopisy niebieskie | | 2 | Dlugopisy czerwone | | 3 | Zszywacze | +----+-----------------------+ 3 rows in set (0.02 sec)
LIMIT 3 spowodowało, że zostały pokazane pierwsze trzy rekordy, począwszy od pierwszego. Aby zmienić punkt rozpoczęcia, ilość rekordów poprzedzamy "numerem startowym":
SELECT id, nazwa FROM produkty LIMIT 2, 3; +----+-------------------------+ | id | nazwa | +----+-------------------------+ | 3 | Zszywacze | | 4 | Karteczki samoprzylepne | | 5 | Strugaczki | +----+-------------------------+ 3 rows in set (0.00 sec)
Tym razem wyświetliła się nam dalsza część zbioru wyników. Przypominamy, że rasowi informatycy zaczynają liczenie od zera i tak samo jest w przypadku numerów startowych. Dlatego "2" w przykładzie oznacza w rzeczywistości rozpoczęcie od rekordu trzeciego. W poprzednim przykładzie start od pierwszego rekordu moglibyśmy zapisać jako LIMIT 0, 3.
LIMIT nie jest częścią standardu ANSI SQL, dlatego też inne systemy baz danych mogą używać innej składni lub nawet innych sposobów do wykonywania porcjowania danych. Aby zapewnić pewną kompatybilność, MySQL udostępnia niektóre z nich jako alternatywę. Oto powyższy przykład zapisany z wykorzystaniem składni bazy PostgreSQL, który działa także i tu:
SELECT id, nazwa FROM produkty LIMIT 3 OFFSET 2; +----+-------------------------+ | id | nazwa | +----+-------------------------+ | 3 | Zszywacze | | 4 | Karteczki samoprzylepne | | 5 | Strugaczki | +----+-------------------------+ 3 rows in set (0.00 sec)
Dlatego jeśli planujesz tworzenie aplikacji pod oba te systemy naraz, pamiętaj o różnicach w implementacji języka SQL między nimi. Dla większej ilości baz danych może być konieczne zupełne zrezygnowanie z klauzuli LIMIT na rzecz odpowiednio konstruowanych warunków WHERE oraz dodatkowych pól w tabelach.
[edytuj] Funkcje grupujące
Język SQL umożliwia stosowanie funkcji do częściowej obróbki danych po stronie serwera. Specyficzną grupą funkcji są tzw. funkcje grupujące. W przeciwieństwie do reszty, operują one na zbiorach rekordów, podając o nich różne istotne informacje. Wiąże się z tym kilka ograniczeń użycia, lecz póki co nie będą nas one dotyczyć, gdyż nie potrafimy pobierać jeszcze danych z kilku tabel naraz. Do tego zagadnienia wrócimy w następnym rozdziale.
Pierwszą funkcją, z jaką się zapoznamy, będzie COUNT(). Podaje ona ilość rekordów, które pasują do warunku.
SELECT COUNT(id) FROM produkty; +-----------+ | COUNT(id) | +-----------+ | 7 | +-----------+ 1 row in set (0.00 sec)
Teraz wiemy, że w naszej tabeli jest siedem rekordów. Niektórzy programiści stosują składnię COUNT(*) (pamiętasz, co oznacza gwiazdka w zapytaniach SELECT?), jednak my zdecydowanie odradzamy jej użycie ze względów niższej wydajności.
Zadajmy sobie pytanie, dlaczego COUNT() wymaga podawania konkretnego pola, zamiast np. nazwy tabeli? Wszystko wyjaśni się, kiedy zobaczymy, jak funkcja ta reaguje na pola zezwalające obecność wartości NULL. W naszej tabeli jedynie ilosc zezwala na jej obecność. Wstawmy więc ją do rekordu o ID 5, aby mieć na czym eksperymentować:
UPDATE `produkty` SET `ilosc` = NULL WHERE `id` = 5;
Zobaczmy, co się teraz stanie po wykonaniu funkcji COUNT() na polu ilosc:
SELECT COUNT(ilosc) FROM produkty; +--------------+ | COUNT(ilosc) | +--------------+ | 6 | +--------------+ 1 row in set (0.00 sec)
Niespodzianka, zwróciło nam informację o sześciu rekordach, chociaż żadnego nie kasowaliśmy. Spokojnie, wszystko jest w porządku. COUNT() celowo opuszcza wartości NULL, gdyż tak wynika ich definicji. Po co liczyć coś, czego na dobrą sprawę nie ma? Zauważmy, jak mądrze postąpiliśmy, wprowadzając NULL do naszej bazy. Teoretycznie przy braku towaru w magazynie można by ustawiać rekordom wartości 0, lecz wtedy do pobrania gatunków produktów będących jeszcze na stanie musimy zastosować klauzulę WHERE:
SELECT COUNT(id) FROM produkty WHERE ilosc > 0;
Jeżeli zamiast 0 wprowadzimy wartości NULL, ulegnie ono skróceniu:
SELECT COUNT(ilosc) FROM produkty;
Nie tylko ta funkcja grupująca reaguje na wartość NULL. Udowodnimy to na przykładzie liczenia średniej ilości towaru w magazynie. Służy do tego funkcja grupująca AVG(). Hipoteza jest następująca: jeśli funkcja ta jest wrażliwa na obecność NULL, powinna dawać różne wyniki w zależności od tego, czy w rekordzie towaru niewystępującego w magazynie oznaczymy ilość przez NULL, czy przez 0. Sprawdźmy to. Oto ciąg wykonywanych przez nas operacji:
mysql> UPDATE `produkty` SET `ilosc` = NULL WHERE `id` = 5; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> SELECT AVG(ilosc) FROM produkty; +------------+ | AVG(ilosc) | +------------+ | 90.8333 | +------------+ 1 row in set (0.00 sec) mysql> UPDATE `produkty` SET `ilosc` = 0 WHERE `id` = 5; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> SELECT AVG(ilosc) FROM produkty; +------------+ | AVG(ilosc) | +------------+ | 77.8571 | +------------+ 1 row in set (0.00 sec)
Oto opis poczynionych kroków:
- Ustawiamy w rekordzie 5 pole ilosc na NULL.
- Obliczamy średnią ilość towarów w magazynie. Wynik: 90,8(3)
- Ustawiamy w rekordzie 5 pole ilosc na 0.
- Obliczamy średnią ilość towaru w magazynie. Wynik: 77,86
Jak widać, AVG() dało nam różne wyniki w zależności od tego, co mieliśmy w polu ilosc, potwierdzając tym samym naszą hipotezę. Zjawisko to bardzo łatwo wytłumaczyć. Wartość 0 traktowana jest jak normalna ilość. Zgodnie ze wzorem na średnią arytmetyczną, sumujemy sześć wartości, lecz dzielimy już przez siedem, z uwzględnieniem naszego zera. Wartość NULL wyraźnie mówi bazie danych MySQL: nie licz mnie, ja nie istnieję. Bądźmy świadomi tych różnic w działaniu, gdyż tyczą się one także pozostałych funkcji grupujących.
Ostatnimi funkcjami grupującymi, które poznamy, będą MAX(), MIN() oraz SUM(). Zwracają one kolejno: największą wartość użytą w danym polu, najmniejszą oraz sumę wszystkich wartości.
[edytuj] Ciekawe sztuczki
Na sam koniec pragniemy pokazać pewną ciekawą sztuczkę, która ukaże potęgę języka SQL i być może zachęci wielu z Was do dalszego pogłębiania swej wiedzy o systemach bazodanowych.
Sytuacja prezentuje się następująco: mamy tabelę, w niej pięć pól mogących przyjmować wartości 1 lub 0. Czy da się pobrać rekordy posortowane według tego, ile pól zostało ustawionych na 1? Odpowiedź brzmi: tak.
Zaczynamy od utworzenia tabeli i wypełnienia jej danymi:
CREATE TABLE `stany` ( `id` SMALLINT NOT NULL AUTO_INCREMENT PRIMARY KEY, `stan1` TINYINT(1) NOT NULL DEFAULT '0', `stan2` TINYINT(1) NOT NULL DEFAULT '0', `stan3` TINYINT(1) NOT NULL DEFAULT '0', `stan4` TINYINT(1) NOT NULL DEFAULT '0', `stan5` TINYINT(1) NOT NULL DEFAULT '0' ) ENGINE = MYISAM;
Następnie wypełniamy ją testowymi danymi:
INSERT INTO stany (stan1,stan2,stan3,stan4,stan5) VALUES (1,0,1,1,0), (0,0,1,1,0), (0,1,1,1,1), (1,0,0,0,1), (0,0,0,0,0), (0,0,1,0,0), (1,1,1,1,1), (0,1,1,0,1);
Możemy już zacząć zabawę:
SELECT id FROM stany ORDER BY (stan1+stan2+stan3+stan4+stan5) DESC; +----+ | id | +----+ | 7 | | 3 | | 1 | | 8 | | 2 | | 4 | | 6 | | 5 | +----+ 8 rows in set (0.00 sec)
Zadziwiające? Nie do końca. Jeżeli dokładnie czytałeś rozdział o klauzuli ORDER BY, być może zauważyłeś, że nigdzie nie ma tam wzmianki o konieczności wymieniania pól. Jest wręcz przeciwnie - czarno na białym było tam napisane "lista wyrażeń".
Aby uczynić nasz wynik bardziej przyjaznym, spróbujmy wyświetlić obok ID sumę "włączonych" pól:
mysql> SELECT id, (stan1+stan2+stan3+stan4+stan5) AS `suma` FROM stany ORDER BY `suma` DESC; +----+------+ | id | suma | +----+------+ | 7 | 5 | | 3 | 4 | | 1 | 3 | | 8 | 3 | | 2 | 2 | | 4 | 2 | | 6 | 1 | | 5 | 0 | +----+------+ 8 rows in set (0.00 sec)
Czyżby kolejne zaskoczenie? Wymieniając na liście danych do zwrócenia sumę, oznaczyliśmy ją etykietą "suma". Jak się okazuje, całej operacji sumowania nie musimy później powtarzać w klauzuli ORDER BY, ani żadnej innej. Wystarczy, że odwołamy się do utworzonej wcześniej etykietki.
Opisem języka SQL możnaby zapełnić całą książkę. To, co podaliśmy w tym podręczniku, jest jedynie wstępem do tematu, który ma nam wystarczyć na początek przy programowaniu w PHP. Zanim jednak pokażemy, jak wykorzystać potęgę baz danych w tym języku, czeka nas jeszcze jeden rozdział poświęcony relacjom oraz indeksom.
[edytuj] Relacje i indeksy
Ten rozdział jest już ostatnim na naszej drodze poznawania bazy danych MySQL. Po zapoznaniu się z relacjami oraz istotą działania indeksów, powrócimy do PHP, by nauczyć się wykorzystywać tę moc w naszych skryptach.
[edytuj] Indeksy
Podczas wykonywania polecenia SELECT serwer musi wykonać bardzo dużo operacji: wybieranie danych, sortowanie wyników itd. Ponieważ przeznaczony on jest nie tylko do pracy z tak mikroskopijnymi ilościami rekordów, z jakimi mamy do czynienienia (MySQL pracuje stabilnie nawet przy dziesiątkach milionów rekordów w tabeli), bardzo ważną rolę odgrywa tu optymalizacja wszelkiego rodzaju wyszukiwania oraz sortowania. Popatrzmy: rekordy ułożone są w tabeli w takiej kolejności, w jakiej zostały dodane. Oznacza to, że jeżeli próbujemy wykonać w naszej stworzonej wcześniej bazie wyszukiwanie względem ceny, MySQL musi za każdym razem na nowo przetrząsać od początku do końca wszystko, co tam się znajduje. Kiedy produktów będzie parę tysięcy, może to trwać znacznie dłużej, niż teraz. Tutaj do akcji wkraczają indeksy. Indeks nałożony na pole A jest kopią zawartości tego pola, tyle że posortowaną i odpowiednio ułożoną. Podczas robienia wszelkiego rodzaju poszukiwań względem znajdujących się w nim wartości, MySQL może teraz gwałtownie przyspieszyć. Oto przykład, co mogą nam dać posortowane rekordy: mamy tysiąc wartości posortowanych rosnąco (są one wszystkie większe od zera, górna granica nieustalona). Powiedzmy, że chcemy wiedzieć, jakie rekordy mają wartości z zakresu od 700 do 900. Jako że są one posortowane, MySQL może od razu strzelić sobie w środek tego zbioru i sprawdzić:
- Jeżeli wartość środkowego elementu jest mniejsza od podanego zakresu, przeszukujemy tylko późniejsze rekordy.
- Jeżeli wartość jest większa od podanego zakresu - tylko wcześniejsze.
- Jeżeli mieści się w podanym zakresie - poruszamy się jednocześnie w obu kierunkach, dopóki nie wypadniemy poza zakres.
Dzięki posortowaniu, MySQL odrzuca na wejściu całe tony rekordów, które po prostu nie pasują do kryteriów z samej definicji. Także sortowanie jest prostsze - wystarczy przejechać się po indeksie; nawet nie trzeba żadnej funkcji sortującej uruchamiać. Ceną za takie udogodnienia jest zwiększenie rozmiarów bazy, gdyż sam indeks też potrzebuje trochę miejsca. Dlatego powinniśmy się zastanowić, na których polach indeks nam będzie potrzebny, a na których nie. Ba! Po ich utworzeniu będziemy się nawet mogli przekonać, czy MySQL je wykorzystuje. Wystarczy zapytanie SELECT poprzedzić słowem EXPLAIN, a otrzymamy pełną informację diagnostyczną o jego wykonywaniu. Dodajmy indeks do istniejącej już tabeli:
ALTER TABLE `produkty` ADD INDEX (`cena`);
Zapytanie ALTER służy do modyfikowania struktury już istniejących tabel, lecz nie będziemy poświęcać mu zbyt wiele czasu. Kiedy poznamy pakiet phpMyAdmin służący do zarządzania bazami danych, będzie on wykonywać takie operacje za nas automatycznie. Na razie wystarczy nam wiedzieć, że w ten sposób stworzyliśmy indeks dla pola cena w tabeli produkty. MySQL powinien teraz korzystać z niego, kiedy np. będziemy chcieli sprawdzać przedziały cenowe:
SELECT id, nazwa FROM produkty WHERE cena BETWEEN 2 AND 10; +----+-------------------------+ | id | nazwa | +----+-------------------------+ | 1 | Dlugopisy niebieskie | | 2 | Dlugopisy czerwone | | 4 | Karteczki samoprzylepne | | 3 | Zszywacze | +----+-------------------------+ 4 rows in set (0.00 sec)
A oto informacje diagnostyczne o zapytaniu:
EXPLAIN SELECT id, nazwa FROM produkty WHERE cena BETWEEN 2 AND 10; +----+-------------+----------+-------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+----------+-------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | produkty | range | cena | cena | 4 | NULL | 3 | Using where | +----+-------------+----------+-------+---------------+------+---------+------+------+-------------+ 1 row in set (0.00 sec)
Możemy stąd dowiedzieć się m.in., jakie klucze MySQL mógł wykorzystać (kolumna "possible_keys"), a jakich faktycznie użył (kolumna "key").
Indeksy można tworzyć także przy tworzeniu tabeli. Tak wyglądałoby polecenie utworzenia tabeli produkty, gdybyśmy od razu chcieli wprowadzić tutaj jakiś indeks:
CREATE TABLE `produkty` ( `id` int(11) NOT NULL auto_increment, `nazwa` varchar(60) NOT NULL, `opis` text NOT NULL, `ilosc` smallint(6) default '0', `cena` float NOT NULL, `jakosc` tinyint(4) NOT NULL, PRIMARY KEY (`id`), KEY `cena` (`cena`) ) ENGINE=MyISAM;
Piszemy po prostu słowo key, po nim podajemy nazwę indeksu, a w nawiasie wymieniamy listę należących do niego pól (indeks może składać się z więcej, niż jednego pola, podobnie też w tabeli może być kilka indeksów).
[edytuj] Relacje jeden do wielu
Dotychczas każda tworzona tabela funkcjonowała niezależnie od innych, lecz takie podejście nie wykorzystuje nawet połowy potęgi baz danych. Prawdziwa siła tkwi w tym, że między tabelami mogą istnieć powiązania, czyli relacje, odzwierciedlające różne zależności między danymi. Pokażemy to na przykładzie internetowej biblioteki. Mamy zbiór kategorii książek zawarty w tabeli kategorie. Przechowuje on informacje o nazwie kategorii oraz ilości dostępnych tam pozycji. W prawdziwej bazie danych biblioteki oczywiście dodalibyśmy jeszcze tutaj różne ozdabiacze w stylu opisu kategorii, możliwości wyboru ikonki, różnych dodatkowych informacji statystycznych, lecz na razie nie w tym rzecz. Oprócz tego istnieje druga tabela, ksiazki z polami nazwa, wydawnictwo, cena itp. Jednak posiada ona coś jeszcze: pole kategoria_id, czyli ID kategorii, do której dana książka jest przypisana. W ten sposób utworzyliśmy relację pomiędzy książkami a kategoriami, a ze względu na rodzaj zależności będziemy ją nazywać relacją jeden do wielu: do jednej kategorii możemy przypisać wiele książek, ale do jednej książki może być przypisana tylko jedna kategoria. Jest to najczęściej spotykany typ relacji.
Przejdźmy do utworzenia tabel:
CREATE TABLE `kategorie` ( `id` mediumint(9) NOT NULL auto_increment, `nazwa` varchar(40) NOT NULL, `il_ksiazek` mediumint(9) default NULL, PRIMARY KEY (`id`), KEY `il_ksiazek` (`il_ksiazek`) ) ENGINE=InnoDB; INSERT INTO `kategorie` VALUES (1, 'Literatura polska', 4); INSERT INTO `kategorie` VALUES (2, 'Literatura zagraniczna', 2); CREATE TABLE `ksiazki` ( `id` int(11) NOT NULL auto_increment, `nazwa` varchar(100) NOT NULL, `wydawnictwo` varchar(50) NOT NULL, `cena` float NOT NULL default '0', `kategoria_id` mediumint(9) NOT NULL, PRIMARY KEY (`id`), KEY `kategoria_id` (`kategoria_id`) ) ENGINE=InnoDB; INSERT INTO `ksiazki` VALUES (1, 'Hamlet', 'AAA', 6.5, 2); INSERT INTO `ksiazki` VALUES (2, 'Makbet', 'AAA', 6.8, 2); INSERT INTO `ksiazki` VALUES (3, 'Potop', 'BBB', 18.4, 1); INSERT INTO `ksiazki` VALUES (4, 'Quo vadis', 'BBB', 17.99, 1); INSERT INTO `ksiazki` VALUES (5, 'Pan Tadeusz', 'CCC', 13.78, 1); INSERT INTO `ksiazki` VALUES (6, 'Nad Niemnem', 'CCC', 15.45, 1);
Zwróćmy uwagę na kilka rzeczy:
- W tabeli ksiazki dla pola kategoria_id jest utworzony indeks. Nakładanie indeksów na pola relacji jest bardzo dobrą praktyką i tutaj zysk wydajności związany z ich użyciem będzie najlepiej widoczny.
- Przy każdym rekordzie w tabeli ksiazki pole kategoria_id przechowuje numeryczny ID rekordu kategorii, do którego dana książka jest przypisana.
Naszą przygodę z relacjami zaczniemy od prostego przykładu: chcemy dowiedzieć się, jaka jest nazwa kategorii, do której przypisana jest książka o ID 3:
SELECT kategorie.nazwa FROM ksiazki, kategorie WHERE kategorie.id = ksiazki.kategoria_id AND ksiazki.id = 3; +-------------------+ | nazwa | +-------------------+ | Literatura polska | +-------------------+ 1 row in set (0.02 sec)
W klauzuli FROM wymieniamy aż dwie tabele - w takim wypadku nazwa każdego pola musi być poprzedzona kropką i nazwą stosownej tabeli (dodajmy, że wtedy nie możemy brać takich nazw w odwrócone apostrofy!). Istotą całego zapytania jest ten fragment: kategorie.id = ksiazki.kategoria_id - to właśnie on wiąże ze sobą kategorię z książką.
Powyższe zapytanie można nieco uprościć, stosując aliasy. Alias tworzy się w klauzuli FROM i jest to nic innego, jak skrócona nazwa tabeli, aby zapytanie było krótsze:
SELECT ka.nazwa FROM ksiazki ks, kategorie ka WHERE ka.id = ks.kategoria_id AND ks.id = 3; +-------------------+ | nazwa | +-------------------+ | Literatura polska | +-------------------+ 1 row in set (0.02 sec)
Teraz zamiast pełnej nazwy ksiazki możemy pisać ks, a zamiast kategorie - ka. Aliasy są bardzo przydatne w bardzo dużych zapytaniach, zajmujących niejednokrotnie wiele linijek. Jednak niosą one w sobie coś więcej, niż tylko poprawiają estetykę. Zagłębmy się trochę bardziej w filozofię zadeklarowania chęci użycia danej tabeli w zapytaniu. Zapis FROM ksiazki mówi, że w danym momencie przetwarzania zapytania możemy pracować maksymalnie na jednym rekordzie z podanej tabeli, do którego odnoszą się warunki podane w WHERE. Ale są sytuacje, kiedy naraz musimy pobrać dane z dwóch rekordów w jednej tabeli - aliasy są w tym wypadku konieczne, aby odróżnić jeden rekord od drugiego: FROM ksiazki ks1, ksiazki ks2. Naturalnie może się zdarzyć, że oba aliasy wskażą nam ten sam rekord, ale równie dobrze mogą być one różne, w przeciwieństwie do poprzedniej sytuacji:
SELECT ks1.nazwa AS `ks1_nazwa`, ks2.nazwa AS `ks2_nazwa` FROM ksiazki ks1, ksiazki ks2 WHERE ks1.id = 3 AND ks2.id = 4; +-----------+-----------+ | ks1_nazwa | ks2_nazwa | +-----------+-----------+ | Potop | Quo vadis | +-----------+-----------+ 1 row in set (0.00 sec)
Widać teraz bardzo wyraźnie, że zapisy ksiazki ks1 oraz ksiazki ks2 są od siebie niezależne, chyba że powiążemy je relacją (tak, możliwe jest tworzenie relacji między rekordami tej samej tabeli!). Na koniec najbardziej wymowny przykład korzystania z relacji jeden do wielu. Radzimy przyjrzeć mu się uważnie, ponieważ podobne zapytania pojawiają się naprawdę często. A mowa jest o pobraniu listy książek wraz z nazwą kategorii, do której są przypisane.
SELECT ks.id, ks.nazwa, ks.wydawnictwo, ks.cena, ka.nazwa AS `kat_nazwa` FROM ksiazki ks, kategorie ka WHERE ka.id = ks.kategoria_id ORDER BY ks.cena; +----+-------------+-------------+-------+------------------------+ | id | nazwa | wydawnictwo | cena | kat_nazwa | +----+-------------+-------------+-------+------------------------+ | 1 | Hamlet | AAA | 6.5 | Literatura zagraniczna | | 2 | Makbet | AAA | 6.8 | Literatura zagraniczna | | 5 | Pan Tadeusz | CCC | 13.78 | Literatura polska | | 6 | Nad Niemnem | CCC | 15.45 | Literatura polska | | 4 | Quo vadis | BBB | 17.99 | Literatura polska | | 3 | Potop | BBB | 18.4 | Literatura polska | +----+-------------+-------------+-------+------------------------+ 6 rows in set (0.00 sec)
[edytuj] Relacje wiele do wielu
Kolejny typ relacji, którym się zajmiemy, nosi nazwę wiele-do-wielu. Jest on dokładnie tym, co mówi nazwa. Patrząc na naszą księgarnię, możemy nim przypisać autorów do książek, co jest zgodne z intuicją: książka mogła być napisana przez wielu autorów, a jednocześnie każdy autor mógł napisać wiele książek.
Zacznijmy od stworzenia tabeli z autorami:
CREATE TABLE `autorzy` (
`id` SMALLINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`imie` VARCHAR(30) NOT NULL,
`nazwisko` VARCHAR(40) NOT NULL
) ENGINE = InnoDB;
INSERT INTO `autorzy` (`imie` , `nazwisko`) VALUES
('William', 'Shakespeare'),
('Henryk', 'Sienkiewicz'),
('Adam', 'Mickiewicz'),
('Eliza', 'Orzeszkowa'),
('Jan', 'Kowalski');
Zauważmy, że w relacji wiele-do-wielu nasze tabele nie zawierają pól-kluczy, które mogłyby je spinać. Bynajmniej nie znikają one, a wręcz przeciwnie - istnieją, tyle że w trzeciej tabeli pomocniczej:
CREATE TABLE `autorzy_to_ksiazki` ( `ksiazka_id` INT NOT NULL, `autor_id` INT NOT NULL, `udzial` VARCHAR(16) NOT NULL, INDEX (`ksiazka_id`, `autor_id`) ) ENGINE = InnoDB; INSERT INTO `autorzy_to_ksiazki` VALUES (1, 1, 'autor'); INSERT INTO `autorzy_to_ksiazki` VALUES (1, 5, 'tłumacz'); INSERT INTO `autorzy_to_ksiazki` VALUES (2, 1, 'autor'); INSERT INTO `autorzy_to_ksiazki` VALUES (2, 5, 'tłumacz'); INSERT INTO `autorzy_to_ksiazki` VALUES (3, 2, 'autor'); INSERT INTO `autorzy_to_ksiazki` VALUES (4, 2, 'autor'); INSERT INTO `autorzy_to_ksiazki` VALUES (5, 3, 'autor'); INSERT INTO `autorzy_to_ksiazki` VALUES (6, 4, 'autor');
Relacja wiele-do-wielu może przenosić dodatkowe informacje w tabeli pomocniczej, np. w tym przypadku mówi nam także, czy osoba przypisana do danej książki jest autorem, czy tłumaczem. Teraz, kiedy tabele są już gotowe, możemy pokazać parę przykładów obrazujących korzystanie z tej relacji.
Zaczniemy od prostego wybrania wszystkich autorów "Hamleta":
SELECT a.imie, a.nazwisko, ak.udzial FROM autorzy a, autorzy_to_ksiazki ak WHERE a.id=ak.autor_id AND ak.ksiazka_id=1; +---------+-------------+----------+ | imie | nazwisko | udzial | +---------+-------------+----------+ | William | Shakespeare | autor | | Jan | Kowalski | tlumacz | +---------+-------------+----------+ 2 rows in set (0.00 sec)
Autora z książką łączy nam ten warunek: a.id=ak.autor_id AND ak.ksiazka_id=1. Na liście pól pobieramy imię i nazwisko z tabeli autorzy z dołączoną informacją o udziale przy opracowywaniu akurat tej pozycji: ak.udzial. Bierzemy ją z tabeli pomocniczej.
W podobny sposób można dowiedzieć się, przy jakich książkach pracował podany autor (np. Jan Kowalski).
SELECT k.nazwa, ak.udzial FROM ksiazki k, autorzy_to_ksiazki ak WHERE k.id=ak.ksiazka_id AND ak.autor_id=5; +--------+----------+ | nazwa | udzial | +--------+----------+ | Hamlet | tlumacz | | Makbet | tlumacz | +--------+----------+ 2 rows in set (0.00 sec)
Zapytanie to działa identycznie, jak poprzednie. Podstawiliśmy do niego jedynie inną tabelę. Teraz może coś bardziej zaawansowanego. Dowiedzmy się, kto pracował dla nas jako autor i przy jakich książkach:
SELECT a.imie, a.nazwisko, k.nazwa FROM autorzy a, ksiazki k, autorzy_to_ksiazki ak WHERE a.id = ak.autor_id AND k.id = ak.ksiazka_id AND ak.udzial='autor'; +---------+-------------+-------------+ | imie | nazwisko | nazwa | +---------+-------------+-------------+ | William | Shakespeare | Hamlet | | William | Shakespeare | Makbet | | Henryk | Sienkiewicz | Potop | | Henryk | Sienkiewicz | Quo vadis | | Adam | Mickiewicz | Pan Tadeusz | | Eliza | Orzeszkowa | Nad Niemnem | +---------+-------------+-------------+ 6 rows in set (0.00 sec)
Relacje wiele-do-wielu pojawiają się w bardziej złożonych bazach danych, które muszą odzwierciedlać dużą liczbę zależności. Są bardzo wszechstronnym narzędziem i ich znajomość jest wręcz niezbędna.
[edytuj] Optymalizacja
Im większe zapytanie z większą ilością skomplikowanych instrukcji, tym wolniej jest wykonywane. Dlatego dobra struktura bazy danych nie tylko musi być elastyczna, ale też zoptymalizowana w taki sposób, aby jak najwięcej danych dało się pobrać prostymi zapytaniami, korzystającymi z elementarnych funkcji. Brak tej cechy jest mankamentem baz projektowanych nie tylko przez początkujących, ale i wielu zaawansowanych programistów, co negatywnie rzutuje na wydajność ich aplikacji.
Pamiętajmy, że wolna przestrzeń dyskowa jest aktualnie najtańsza w historii ludzkości, a dane liczbowe zajmują śmiesznie małą jej ilość. Podstawową zasadą jest właśnie buforowanie tego, co da się buforować, a pokażemy to na przykładzie systemu newsów z możliwością komentowania. W systemie takim mamy dwie tabele: newsy oraz komentarze połączone relacją jeden-do-wielu. Zazwyczaj przy wyświetlaniu newsów pragniemy podać też, ile jest w nich komentarzy. Pobranie takich informacji jednym zapytaniem wymaga sprytnego zastosowania funkcji grupującej COUNT(), której użycie w relacjach jest obwarowane paroma ograniczeniami. Jednak to nie wszystko. Kiedy tabele zepniemy "sztywno" w warunku WHERE, na liście pokażą się nam tylko te newsy, w których ktoś już dodał jakiś komentarz! Dlatego musimy zastosować specjalną klauzulę służącą do warunkowego spinania tabel: LEFT JOIN. Jest ona użyteczna, ale dość powolna w działaniu. Ostatecznie otrzymujemy coś takiego:
SELECT n.id, n.tytul, COUNT(k.id) AS `il_komentarzy` FROM newsy n LEFT JOIN komentarze k ON k.news_id=n.id GROUP BY k.news_id ORDER BY n.id;
Zapytanie to było wykorzystywane praktycznie na stronie WWW autora podręcznika. Działa, lecz przy większej ilości rekordów zaczynają się problemy w stylu zrywania połączenia czy nawet blokowania serwera DB. Naprawdę, nie życzymy nikomu, aby znalazł się w podobnej sytuacji, zwłaszcza że problemowi można zaradzić w bardzo prosty sposób. Dlaczego bowiem zliczać ilość komentarzy przy każdym wyświetlaniu strony? Przecież liczba ta nie zmienia się tak znowu często. Dorzućmy do tabeli newsy pole il_komentarzy. Przy dodawaniu newsa inicjujemy je wartością 0. Przy dodawaniu komentarza zwiększamy jego wartość o 1, przy usuwaniu - zmniejszamy. W ten sposób uzyskaliśmy takie zapytanie końcowe:
SELECT id, tytul, il_komentarzy FROM newsy ORDER BY id;
Wykorzystuje ono elementarną składnię, przez co może wytrzymać znacznie większe obciążenie. Tego typu optymalizacja spotykana jest w wielu zaawansowanych aplikacjach, np. systemie forów dyskusyjnych Invision Power Board, który w ten sposób zapamiętuje sobie ilości postów oraz tematów.
O kolejnym rodzaju optymalizacji już wspominaliśmy - jest to prawidłowe użycie indeksów. Zakładamy je wszędzie tam, gdzie dane będą sortowane lub wybierane według różnych kryteriów. Indeksy zawierają informacje ułożone w pewnym porządku, dlatego po ich wstawieniu w takie miejsca MySQL wykorzysta je do przyspieszenia całego procesu.
Optymalizację można przeprowadzać także z poziomu języka programowania, za pomocą którego komunikujemy się z bazą. Jeżeli nasze dane zmieniają się niezbyt często, nie ma potrzeby pobierania ich na nowo za każdym razem. Wystarczy raz pobrane zapisać gdzieś na serwerze w pliku i przy dalszych wejściach czytać właśnie z niego. Jest to jednak optymalizacja typowo programowa, dlatego zajmiemy się nią później.
[edytuj] Zakończenie
Na tym kończymy naszą przygodę z poznawaniem języka SQL. Jeżeli pragniesz poznać go lepiej, bardzo przydatna okaże się z pewnością dokumentacja serwera MySQL. Poświęć trochę czasu na zapoznanie się z jej rozbudowaną strukturą, gdyż jest to istna kopalnia cennych informacji. Przydatne może być także analizowanie cudzych skryptów oraz własne poszukiwania z użyciem Google. Każdy sposób jest dobry, a znajomości SQL-a nigdy za wiele.
[edytuj] Wstęp do programowania obiektowego
Zanim nawiążemy nasze pierwsze połączenie z bazą z poziomu PHP, musimy zostać krótko zaznajomieni z podstawami programowania obiektowego, niezbędnego, aby zacząć naszą zabawę z biblioteką PHP Data Objects.
[edytuj] Czym jest OOP?
Skrót OOP pochodzi od terminu Object-Oriented Programming, czyli po polsku "programowanie zorientowane obiektowo". Określa się nim pewną strukturę i organizację kodu źródłowego. Dotychczas wszystkie nasze przykłady bazowały na programowaniu strukturalnym, opartym o funkcje i zmienne globalne. W programowaniu obiektowym programista operuje na klasach i obiektach. Programowanie obiektowe zyskało bardzo dużą popularność dzięki temu, że znacznie lepiej przystaje do rzeczywistości, jest zatem po prostu intuicyjne. Wytłumaczymy to na przykładzie.
Monitor, na którym najprawdopodobniej czytasz ten tekst, jest pewnym obiektem pewnej klasy ("monitor"). Klasa ta definiuje zachowanie oraz możliwe właściwości wszystkich monitorów na świecie (lub danego producenta, jeśli wolisz), natomiast sam obiekt nadaje tym właściwościom odpowiednie wartości. Spójrzmy na to w ten sposób: ty masz monitor kremowy z odświeżaniem 75 Hz, twój kolega ma monitor w ciemnej obudowie z odświeżaniem 85 Hz. Jednak mimo odmiennych właściwości, nadal są to obiekty tego samego rodzaju: monitor, zatem zachowują się identycznie i mogą być używane w identycznych celach. Niemniej nie znaczy to, że wszystkie NARAZ robią to samo. Klasa "monitor" po prostu opisuje ich zachowanie, a co obiekty robią w danej chwili - zależy tylko od nas, programistów. W programowaniu obiektowym identyczny tok rozumowania został przeniesiony do języka programowania.
[edytuj] Podstawy OOP
Aby móc tworzyć obiekty, potrzebna nam jest jakaś klasa. Tworzy się ją w PHP bardzo prosto. Piszemy słowo kluczowe class, podajemy nazwę klasy i wewnątrz nawiasów klamrowych definiujemy całą jej strukturę składającą się z deklaracji pól (czyli opisu, jakie właściwości mogą posiadać jej obiekty) oraz metod będących w zasadzie funkcjami wewnętrznymi klasy, wykonujących jakieś operacje:
<?php
class osoba
{
public $imie; // 1
public $nazwisko;
public function ustawPersonalia($imie, $nazwisko) // 2
{
$this -> imie = $imie; // 3
$this -> nazwisko = $nazwisko;
} // end ustawPersonalia();
public function personalia()
{
return $this -> imie.' '.$this -> nazwisko;
} // end personalia();
}
$osoba = new osoba; // 4
$osoba -> ustawPersonalia('Adam', 'Kowalski'); // 5
echo $osoba -> personalia(); // 6
?>
Oto opis poszczególnych części kodu:
- Tutaj deklarujemy, że każdy obiekt klasy "osoba" będzie posiadać pola (dalej będziemy posługiwać się właśnie tym terminem, mówiąc o właściwościach) imie oraz nazwisko. Nazwa pola musi zaczynać się od znaku dolara i być poprzedzona słowem kluczowym public (są też inne, ale ich znaczenie omówimy sobie później).
- Tutaj tworzymy metodę klasy (zwaną czasem funkcją wewnętrzną), która będzie wykonywać na jej obiektach określone zadanie - w tym konkretnym przypadku będzie ustawiać personalia. Składnia jest praktycznie identyczna, jak podczas tworzenia normalnych funkcji. Nowością jest słowo kluczowe public na samym początku, i choć tym razem można je bezkarnie opuszczać, jego pisanie jest dobrym zwyczajem.
- Każdy obiekt jest w PHP reprezentowany przez zmienną. Aby dostać się do jego pól oraz metod, musimy użyć specjalnego "wskaźnika" ->, a potem wywołać to, co nam jest potrzebne. Zwróć uwagę, że nazw pól nie poprzedzamy już teraz znakiem dolara! Pora także wytłumaczyć, co to za zmienna $this. Przecież nigdzie jej nie tworzymy, ale z niej korzystamy. $this jest właściwie pewnym trikiem. Wskazuje zawsze na obiekt, który wywołał daną metodę, pozwalając jej na modyfikację jego właściwości (po to przecież istnieje).
- Mając już klasę, możemy przystąpić do tworzenia obiektów. Robi się to w podany tutaj sposób: operator new tworzy nowy obiekt podanej klasy, co pozwala np. przypisać go do określonej zmiennej. W tym wypadku - do zmiennej $osoba.
- Tutaj wywołujemy metodę na stworzonym właśnie obiekcie. Pamiętasz, co napisaliśmy o zmiennej specjalnej $this wewnątrz metody? Będzie ona teraz wskazywała właśnie na obiekt $osoba, dzięki czemu zostaną do niego przypisane imię i nazwisko podane jako parametry metody.
- Wywołujemy kolejną metodę, która zwraca nam personalia naszej osoby.
Skopiuj kod powyższej klasy do nowego pliku - zajmiemy się jeszcze jednym przykładem na niej opartym:
<?php
// tu jest kod klasy
$bazaOsob = array();
// Tworzymy pierwsza osobe
$bazaOsob[0] = new osoba;
$bazaOsob[0] -> ustawPersonalia('Adam', 'Kowalski');
// Tworzymy druga osobe
$bazaOsob[1] = new osoba;
$bazaOsob[1] -> ustawPersonalia('Jan', 'Nowak');
// Tworzymy trzecia osobe
$bazaOsob[2] = new osoba;
$bazaOsob[2] -> ustawPersonalia('Zbigniew', 'Bogacki');
// Zobaczmy, jak skonczyla sie nasza zabawa w Boga
foreach($bazaOsob as $osoba)
{
echo 'Jestem '.$osoba -> personalia().'<br/>';
}
?>
W przykładzie tym stworzyliśmy sobie trzy obiekty osoba i zapisaliśmy do tablicy. Puszczając po niej pętlą foreach możemy zapoznać się z personaliami każdej z nich. Zapis $osoba -> personalia() oznacza po prostu podanie personaliów aktualnie wyświetlanej osoby.
Pamiętaj, że personalia możesz przypisać także bardziej łopatologicznie, np. $osoba -> imie = 'Józef';
[edytuj] Obiekty i zmienne
W naszych dotychczasowych rozważaniach zakładaliśmy, że obiekt jest zmienną. I w sumie mamy rację... ale tylko, mówiąc o przedpotopowym PHP 4. W PHP 5 zmienna obiektowa jest jedynie referencją do właściwego obiektu, zatem napisanie $a = $b, wykona tylko kopię samej referencji, a nie całego obiektu. Obiekt przestaje istnieć, jeżeli nie prowadzi do niego już żadna referencja.
Takie założenie ma dużo zalet. Wystarczy tylko wymienić przekazywanie obiektów jako parametry funkcji/metod lub zwracanie ich jako wynik działania. W PHP 4 było to możliwe jedynie po karkołomnych zabawach z operatorem &. W PHP 5 kod jest czysty i schludny. Jeżeli już naprawdę czujemy potrzebę wykonania kopii obiektu, musimy zapoznać się ze słowem kluczowym clone. Jeszcze raz skopiuj kod naszej klasy do naszego trzeciego przykładu:
<?php
// Tu wstaw kod klasy
$facet1 = new osoba; // 1
$facet1 -> ustawPersonalia('Adam', 'Kowalski');
echo '$facet1 to '.$facet1 -> personalia().'<br/>';
echo '<hr/>';
// Zobaczymy, co sie stanie
$facet2 = $facet1; // 2
$facet2 -> imie = 'Jan';
echo '$facet2 to '.$facet2 -> personalia().'<br/>';
echo '$facet1 to '.$facet1 -> personalia().'<br/>'; // 3
echo '<hr/>';
// Operator przypisania nie działa? Pora, aby do akcji wkroczyła inżynieria genetyczna
// Oto pierwsze w historii klonowanie człowieka:
$facet3 = clone $facet1; // 4
$facet3 -> imie = 'Michał';
echo '$facet3 to '.$facet3 -> personalia().'<br/>'; // 5
echo '$facet1 to '.$facet1 -> personalia().'<br/>';
// Sukces!
?>
Krótki opis:
- Na początek tworzymy sobie jakiegoś faceta i nadajemy mu imię.
- Próbujemy w "tradycyjny" sposób wykonać kopię. Dla potwierdzenia "obiektowi" $facet2 zmieniamy imię.
- Jednak podczas wyświetlania okazuje się, że imię zmieniło się także w $facet1! Wniosek: $facet2 i $facet1 to ten sam obiekt.
- Aby wymusić skopiowanie samego obiektu, a nie tylko referencji, musimy posłużyć się operatorem clone. Znowu dla testu zmieniamy imię w $facet3.
- I osiągamy sukces. W $facet1 imię pozostało niezmienione. Teraz obiekt udało się sklonować.
[edytuj] Krótko o wyjątkach
Dla pełni jasności przy omawianiu biblioteki PDO musimy wspomnieć także o wyjątkach, które zawitały do PHP wraz z pojawieniem się wersji 5.0.0. Wyjątek to obiekt wbudowanej klasy Exception tworzony przez nas, kiedy zajdzie jakaś nieprzewidziana sytuacja. Przechwytuje go specjalny blok kodu, który może wtedy odpowiednio na nią zareagować. Dlaczego po prostu nie napisać sobie do tego zadania prostej funkcji lub posłużyć się poleceniem trigger_error()? Te specjalne bloki przechwytujące wyjątki można w sobie zagnieżdżać. Dzięki temu możliwa jest taka sztuczka: wyobraźmy sobie, że cała nasza aplikacja objęta jest potężnym blokiem, który wyłapuje wszystkie wyjątki związane np. z dostępem do katalogów i prezentuje jednolity komunikat błędu. Jednak fragment tej aplikacji nie traktuje ewentualnych problemów z dostępem w kategoriach życia i śmierci, dlatego tworzymy tam nowy blok, który potraktuje problem łagodniej. Jeżeli teraz wyjątek powstanie wewnątrz niego, zajmie się nim lokalna część aplikacji. Jeśli poza nim - nasz uniwersalny wyłapywacz.
Jeżeli nie za bardzo rozumiesz powyższy opis, nie przejmuj się. Wyjątki będą szeroko omawiane dalej. Teraz informacja ta jest nam potrzebna głównie po to, że biblioteka PDO (i nie tylko) za ich pomocą raportuje kłopoty. Oto przykład działania wyjątków:
<?php
try // 1
{
$tekst = @file_get_contents('plik.txt');
if($tekst === false)
{
throw new Exception('Nie można otworzyć pliku plik.txt!'); // 2
}
echo 'Nasz tekst: '.$tekst; // 3
}
catch(Exception $wyjatek) // 4
{
echo $wyjatek -> getMessage(); // 5
}
?>
Opis skryptu:
- Tak rozpoczynamy blok, w którym chcemy łapać wyjątki.
- Słowem kluczowym throw, po którym podajemy obiekt klasy Exception, wysyłamy wyjątek. Zauważ, że po nazwie klasy podaliśmy parametr, zupełnie jak w przypadku funkcji/metody. Otóż samo tworzenie obiektu też jest w zasadzie metodą i może zostać przez nas oprogramowane, pobierając także parametry. Wrócimy do tego później, na razie wystarczy wiedzieć, że coś takiego jest możliwe.
- Wysłanie wyjątku powoduje automatyczny skok do bloku oznaczonego punktem 4. Dlatego jeżeli skrypt nie znajdzie pliku, ta instrukcja się już nie wykona.
- Do tego miejsca skacze skrypt, kiedy przechwyci wyjątek i tu możemy go oprogramować. W nawiasie podajemy nazwę klasy wyjątku, której obiekty chcemy wyłapywać, a następnie nazwę zmiennej, do jakiej należy zapisać przechwycony wyjątek.
- Wyświetlamy zapisany w wyjątku komunikat błędu.
[edytuj] Zakończenie
Jesteśmy już gotowi do zapoznania się z biblioteką PDO i wykonania naszych pierwszych połączeń z bazą MySQL. Do programowania obiektowego wrócimy później, poświęcając mu cały wielki rozdział tego podręcznika.
[edytuj] Biblioteka PDO
Jeszcze rok temu programiści pragnący komunikować się z bazą danych poprzez PHP musieli zmagać się z wieloma problemami. Każdy serwer DB udostępniał inne API do komunikacji, które zostały na nasze nieszczęście wiernie odtworzone w interpreterze. Jeżeli ktoś chciał napisać elastyczny projekt do uruchamiania na kilku bazach, musiał pisać samodzielnie odpowiednie nakładki, które wybiorą odpowiednią funkcję w zależności od tego, czym się łączymy. Pozostawały też różne gotowe skrypty robiące to zadanie za nas.
PDO to skrót od PHP Data Objects. Jest to zupełnie nowy interfejs języka PHP przeznaczony do komunikacji z bazami danych, po raz pierwszy napisany wyłącznie w OOP. Jego najważniejszą zaletą jest to, że możemy za jego pomocą łączyć się zarówno z bazą danych MySQL, jak i z bazą danych PostgreSQL (o innych systemach DB nie wspominając), nie zmieniając ani linijki kodu. Wersji beta PDO można było używać już w PHP 5.0, natomiast stabilna wersja pojawiła się wraz z PHP 5.1. Gorąco zachęcamy do jego stosowania, gdyż nie tylko jest wygodniejszy od starych rozwiązań, ale też szybszy i bezpieczniejszy - do tego zagadnienia niedługo wrócimy.
[edytuj] Nawiązywanie połączenia
Aby nie być gołosłownym, przejdźmy od słów do czynów. Połączymy się z naszą bazą utworzoną podczas wcześniejszych lekcji:
<?php
try
{
$pdo = new PDO('mysql:host=localhost;dbname=produkty', 'root', 'root');
echo 'Połączenie nawiązane!';
}
catch(PDOException $e)
{
echo 'Połączenie nie mogło zostać utworzone: ' . $e->getMessage();
}
?>
Nawiązywanie połączenia polega po prostu na utworzeniu obiektu klasy PDO. Jako parametry startowe podajemy:
- DSN - specjalny ciąg znaków identyfikujący rodzaj serwera DB (np. mysql), host na jakim jest ona uruchomiona (dla nas localhost) oraz nazwę bazy, z którą chcemy się połączyć. Opcjonalnie można dodać także parametr port. Inne serwery DB mogą wymagać innych parametrów połączeń; po szczegóły odsyłamy do dokumentacji PHP.
- nazwa użytkownika
- hasło użytkownika
Host, nazwę użytkownika i hasło powinieneś dostać od swojego hostingu, kiedy będziesz chciał umieścić swoją stronę. Jeżeli podczas nawiązywania połączenia wystąpi błąd, zostanie on zgłoszony jako wyjątek PDOException, który musimy przechwycić (to ważne - jeśli wyjątek nie zostanie przechwycony, domyślny komunikat o błędzie wygenerowany przez PHP ujawni nazwę użytkownika i hasło!).
Chcąc ustawić od razu system porównań dla bazy danych wystarczy użyć takiego połączenia:
<?php
try
{
$pdo = new PDO('mysql:host=localhost;dbname=produkty', 'root', 'root', array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
echo 'Połączenie nawiązane!';
}
catch(PDOException $e)
{
echo 'Połączenie nie mogło zostać utworzone: ' . $e->getMessage();
}
?>
W miejsce utf8 wstaw swoje kodowanie i voila! Już nie musisz pamiętać o zmianie kodowania na UTF-8 dla przykładu. W ten sposób można przekazać jeszcze inne zapytania już zaraz po połączeniu.
[edytuj] Pobieranie danych
Pobieranie danych w sterownikach baz danych realizuje się w następujący sposób: najpierw wysyłamy zapytanie i uzyskujemy od serwera zbiór wyników. Przelatując po nim pętlą, otrzymujemy kolejne rekordy w postaci tablic. W bibliotece PDO zbiorem wyniku jest obiekt klasy PDOStatement. Naraz możemy mieć otwarty tylko jeden zbiór wyników. Zabezpiecza to przed próbami tworzenia zapytań rekurencyjnych oraz wynika ze specyfiki pracy bibliotek komunikujących się z serwerem. W starszych bibliotekach ograniczenia takiego nie było dzięki emulacji, która jednak zmniejszała wydajność. Przyjrzyjmy się, jak możemy pobrać zawartość tabeli produkty:
<?php
try
{
$pdo = new PDO('mysql:host=localhost;dbname=produkty', 'root', 'root');
$pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo -> query('SELECT id, nazwa, opis FROM produkty');
echo '<ul>';
foreach($stmt as $row)
{
echo '<li>'.$row['nazwa'].': '.$row['opis'].'</li>';
}
$stmt -> closeCursor();
echo '</ul>';
}
catch(PDOException $e)
{
echo 'Połączenie nie mogło zostać utworzone: ' . $e->getMessage();
}
?>
Metoda query() zwraca obiekt zbioru wyników odpowiadający wykonanemu zapytaniu (zauważ, że w tym przypadku nie kończymy go średnikiem!). Jedną z technik uzyskania kolejnych rekordów jest przepuszczenie tego obiektu przez pętlę foreach. Kolejne rekordy zostaną zapisane do tablicy asocjacyjnej $row, z której możemy pobrać wyniki. Po zakończeniu pobierania niezbędne jest zamknięcie zbioru wyników poleceniem closeCursor() - inaczej nie będziemy w stanie wysłać następnego zapytania.
Zauważ, że zaraz po połączeniu się z bazą danych korzystamy z metody setAttribute(). Pozwala ona skonfigurować niektóre aspekty pracy z biblioteką PDO - w tym przypadku żądamy, aby ewentualne błędy w zapytaniach raportowane były jako wyjątki.
Powyższy przykład można zapisać także w inny sposób:
<?php
try
{
$pdo = new PDO('mysql:host=localhost;dbname=produkty', 'root', 'root');
$pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo -> query('SELECT id, nazwa, opis FROM produkty');
echo '<ul>';
while($row = $stmt -> fetch())
{
echo '<li>'.$row['nazwa'].': '.$row['opis'].'</li>';
}
$stmt -> closeCursor();
echo '</ul>';
}
catch(PDOException $e)
{
echo 'Połączenie nie mogło zostać utworzone: ' . $e->getMessage();
}
?>
W tym wypadku wykorzystaliśmy pętlę while i jawnie zażądaliśmy zwrócenia rekordu metodą fetch(). Jest ona, wbrew pozorom bardzo użyteczna - można ją wywołać wszędzie, np. w instrukcji if (sytuacja, gdy zawsze pobieramy jeden rekord), a także ustawić tryb pobierania.
Uwaga! PHP Data Objects ma czasem problemy z działaniem z MySQL 4.1. Aby uniknąć problemów na tej wersji (sporo firm hostingowych wciąż ją oferuje), musisz pamiętać o tym, aby po zamknięciu zbioru wyników metodą closeCursor() dodatkowo ręcznie skasować obiekt $stmt:
unset($stmt);
Inaczej próba przypisania do niej nowego zbioru wyników spowoduje wygenerowanie przez MySQL komunikatu General Error 2050.
[edytuj] Aktualizacja danych
Zapytania typu INSERT czy UPDATE służące do modyfikacji zawartości bazy lub inne, niezwracające zbioru wyników, wysyła się za pomocą metody exec(). Wynikiem jej działania jest liczba określająca ilość zmodyfikowanych rekordów. W poniższym przykładzie zakodujemy dodawanie pewnego konkretnego produktu do naszej listy produktów:
<?php
try
{
$pdo = new PDO('mysql:host=localhost;dbname=produkty;port=3305', 'root', 'root');
$pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$ilosc = $pdo -> exec('INSERT INTO `produkty` (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES(
\'Miotacz ognia na dezodorant\',
\'Rewelacyjny miotacz ognia dla kazdej domowej gospodyni!
Nie martw sie o paliwo - wystarczy zwykly dezodorant!\',
\'54\',
\'40.99\',
\'5\')');
if($ilosc > 0)
{
echo 'Dodano: '.$ilosc.' rekordow';
}
else
{
echo 'Wystąpił błąd podczas dodawania rekordów!';
}
}
catch(PDOException $e)
{
echo 'Wystąpił błąd biblioteki PDO: ' . $e->getMessage();
}
?>
[edytuj] Podpinanie
Rzadko kiedy zdarza się, aby wszystkie informacje potrzebne do zmodyfikowania bazy były na sztywno zakodowane w zapytaniu tak, jak to zrobiliśmy w powyższym przykładzie. W codziennej praktyce modyfikujemy dane za pomocą formularzy, ikonek podpowiadających, co trzeba zmienić i jak. Rozwiązanie jest pozornie banalne: składamy zapytanie z predefiniowanych części, którymi opakowujemy dane z formularza, a później wysyłamy ten miks do bazy. Ilustruje to kolejny przykład, który udostępnia prosty formularz do dodawania nowych produktów:
<?php
try
{
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
$pdo = new PDO('mysql:host=localhost;dbname=produkty;port=3305', 'root', 'root');
$ilosc = $pdo -> exec('INSERT INTO `produkty` (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES(
\''.$_POST['nazwa'].'\',
\''.$_POST['opis'].'\',
\''.$_POST['ilosc'].'\',
\''.$_POST['cena'].'\',
\''.$_POST['jakosc'].'\')');
if($ilosc > 0)
{
echo 'Dodano: '.$ilosc.' rekordow';
}
else
{
echo 'Wystąpił błąd podczas dodawania rekordów!';
}
}
else
{
echo '
<form method="post" action="pdo_5.php">
<p>Nazwa: <input type="text" name="nazwa"/></p>
<p>Opis: <input type="text" name="opis"/></p>
<p>Ilosc: <input type="text" name="ilosc"/></p>
<p>Cena: <input type="text" name="cena"/></p>
<p>Jakosc: <select name="jakosc">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</select></p>
<p><input type="submit" value="Dodaj"/></p>
</form>
';
}
}
catch(PDOException $e)
{
echo 'Wystapil blad biblioteki PDO: ' . $e->getMessage();
}
?>
Kiedy został nadesłany formularz (metoda POST), nawiązywane jest połączenie z bazą. W szkielet zapytania wstawiamy wprowadzone przez użytkownika dane, wykorzystując zwyczajny operator łączenia ciągów. Później metoda exec() umieszcza nam nowy rekord w bazie. Na pierwszy rzut oka wszystko wygląda wspaniale - mamy formularz, redaktorzy mogą dodawać produkty, a internauci je oglądać. Lecz pewnego dnia jeden z redaktorów zgłasza problem: nie może wpisać do opisu produktu apostrofy, gdyż skrypt generuje wtedy jakieś tajemnicze błędy. Co jest grane? Testujemy kopię skryptu na lokalnym komputerze i działa, ale na właściwym serwerze WWW już nie. Przyczyną problemu jest złamanie dwóch podstawowych zasad obsługi formularzy:
- Nigdy nie ufaj danym zewnętrznym
- Nigdy nie ufaj magic quotes
Efekt jest taki, że stworzyliśmy aplikację podatną na włamania typu SQL Injection, które polegają na wykorzystaniu dziur w kontroli danych z formularzy. Zauważ, jak bezbronny jest nasz formularz: baza danych wymaga, aby ilość była liczbą. Gdzie to sprawdzamy w skrypcie? Nigdzie. Gdy jakiś inteligent wpisze nam zamiast ilości "miecio jest niepoważny", skrypt beztrosko umieści to w zapytaniu nie patrząc na sens tego, co robi. Co więcej, zwróć uwagę na pewną rzecz: w języku SQL znak apostrofu jest czymś więcej, niż tylko znakiem - kończy on lub zaczyna sekwencję ciągu tekstowego. Dlatego wprowadzając jakikolwiek tekst, który ma zawierać apostrofy, musimy poddać je zabiegowi escapingu, czyli mówiąc po polsku - poprzedzić znakiem backslash, aby MySQL wiedział, że są one integralną częścią tekstu i nie kończą wprowadzanej sekwencji. Kiedy PHP był jeszcze niewielkim projektem, ktoś wpadł na pomysł wspomożenia programistów i wymyślił tzw. magic quotes. Opcja ta, jeżeli jest włączona, powoduje, że we wszystkich danych z tablic $_GET, $_POST oraz $_COOKIE apostrofy są automatycznie poprzedzane backslashem, dzięki czemu nie trzeba się tym zajmować samodzielnie. Brzmi ciekawie? Niezupełnie! Zwróćmy uwagę, że nie tylko baza danych może służyć do przechowywania informacji. Niektóre z nich ktoś zechce umieścić w pliku i wtedy z kolei musi się sam tych niepotrzebnych backslashów pozbywać. Kolejną kontrowersyjną rzeczą dotyczącą magic quotes jest fakt, że nie wszystkie serwery miały tę opcję włączoną, tak samo nie wszyscy programiści wiedzieli, że coś takiego w ogóle istnieje. Czy widziałeś w sieci serwisy, gdzie apostrofy w artykułach poprzedzane były setkami backslashów? To właśnie efekt tego - programista miał u siebie w domu wyłączoną opcję magic quotes, więc ręcznie dodawał sobie backslashe przy danych umieszczanych w zapytaniach SQL. Później wrzucił skrypt na serwer, gdzie magic quotes dla odmiany było włączone, przez co backslashe doklejane były dwa razy - jeden z nich faktycznie escape'ował apostrofy, ale drugi był uznawany przez MySQL za integralną część tekstu. Z drugiej strony, jeśli ktoś miał w domu serwer lokalny z włączonymi magicznymi apostrofami, a później wrzucił swoja stronę WWW na serwer bez nich, stawał się łatwym celem dla hackerów, którzy bez trudu mogą włamać się atakiem SQL Injection. Atak ten polega na tym, że skoro apostrof nie jest escape'owany, to jego wprowadzenie tak naprawdę powoduje, że dalsza część ciągu jest uznawana za fragment zapytania! Możemy więc sobie zupełnie legalnie dopisać własne warunki. Wyobraźmy sobie teraz, że ktoś manipuluje w ten sposób zapytaniami związanymi z bezpieczeństwem za pomocą formularza logowania. To nie fikcja: do wyobraźni powinien przemówić ten film (j. ang).
Ostatecznie sami twórcy PHP doszli do wniosku, że magic quotes jest rozwiązaniem bezsensownym. W tworzonym właśnie PHP 6 tej opcji już nie ma i należy samodzielnie escape'ować wszystkie dane. Jednak póki co pracujemy na PHP 5.1 - choć w tym podręczniku podczas instalacji zalecaliśmy wyłączenie magic quotes, nie mamy pewności, że serwer docelowy dla naszych stron WWW posiada identyczne ustawienia. Jeśli korzystamy z PDO i mechanizmu podpinania, problem nas nie dotyczy, ponieważ biblioteka automatycznie dostosuje się wtedy do ustawień, lecz w każdym innym przypadku powinniśmy zastosować specjalny filtr, który zniweluje nam efekt niewłaściwych ustawień i nada danym pożądaną przez nasz skrypt postać.
<?php
if(version_compare(phpversion(), '6.0.0-dev', '<'))
{
// Dla PHP 5 i wcześniejszych wyłączmy magic quotes
function removeSlashes(&$value){
if(is_array($value))
{
return array_map('removeSlashes', $value);
}
else
{
return stripslashes($value);
}
} // end rmGpc();
set_magic_quotes_runtime(0);
if(get_magic_quotes_gpc())
{
$_POST = array_map('removeSlashes', $_POST);
$_GET = array_map('removeSlashes', $_GET);
$_COOKIE = array_map('removeSlashes', $_COOKIE);
}
}
?>
Przejdźmy teraz do właściwego tematu niniejszej sekcji. Skoro magic quotes jest wyłączone, rozsądek podpowiada, że dane musimy sami escape'ować. W starych rozszerzeniach do komunikacji z bazą danych służyły do tego specjalne funkcje udostępniane przez sterownik, przez które musieliśmy przepuścić wszystkie dane - nadal jednak konieczne było samodzielne spajanie tego z zapytaniem. PDO promuje filozofię przeniesienia tego zadania na bazę danych, co udostępniają najnowsze biblioteki komunikacji z serwerami DB. W języku polskim proces ten doczekał się niezbyt szczęśliwej nazwy bindowanie od angielskiego określenia data binding, jednak w tym podręczniku będziemy konsekwentnie stosować termin podpinanie, naszym zdaniem znacznie lepiej oddający jego charakter.
Podpinanie polega na przeniesieniu spajania danych z zapytaniem z języka programowania na serwer DB. Do bazy wysyłamy tutaj tak naprawdę szkielet zapytania ze specjalnymi wstawkami, do których później podpinamy interesujące nas dane za pomocą specjalnej metody, gdzie możemy dodatkowo określić ich typ (tekst, liczba itd.). Zobaczmy, jak wygląda podany na początku przykład przepisany z wykorzystaniem podpinania:
<?php
try
{
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
$pdo = new PDO('mysql:host=localhost;dbname=produkty;port=3305', 'root', 'root');
$pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo -> prepare('INSERT INTO `produkty` (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES(
:nazwa,
:opis,
:ilosc,
:cena,
:jakosc)'); // 1
$stmt -> bindValue(':nazwa', $_POST['nazwa'], PDO::PARAM_STR); // 2
$stmt -> bindValue(':opis', $_POST['opis'], PDO::PARAM_STR);
$stmt -> bindValue(':ilosc', $_POST['ilosc'], PDO::PARAM_INT);
$stmt -> bindValue(':cena', (float)$_POST['cena'], PDO::PARAM_STR);
$stmt -> bindValue(':jakosc', $_POST['jakosc'], PDO::PARAM_INT);
$ilosc = $stmt -> execute(); // 3
if($ilosc > 0)
{
echo 'Dodano: '.$ilosc.' rekordow';
}
else
{
echo 'Wystapil blad podczas dodawania rekordow!';
}
}
else
{
echo '
<form method="post" action="pdo_6.php">
<p>Nazwa: <input type="text" name="nazwa"/></p>
<p>Opis: <input type="text" name="opis"/></p>
<p>Ilosc: <input type="text" name="ilosc"/></p>
<p>Cena: <input type="text" name="cena"/></p>
<p>Jakosc: <select name="jakosc">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</select></p>
<p><input type="submit" value="Dodaj"/></p>
</form>
';
}
}
catch(PDOException $e)
{
echo 'Wystapil blad biblioteki PDO: ' . $e->getMessage();
}
?>
Opis:
- Na początek wysyłamy do bazy danych szkielet zapytania, wykorzystując metodę prepare(). Zamiast danych, umieszczamy w ich miejscu wstawki, np. :nazwa, :opis. Jako rezultat otrzymujemy obiekt klasy PDOStatement, który wykorzystamy do podpięcia danych.
- Tutaj podpinamy dane z formularza pod konkretne wstawki metodą bindValue() obiektu PDOStatement. Określamy także ich typ: stała PDO::PARAM_STR określa podpinanie danych tekstowych, PDO::PARAM_INT - liczb całkowitych.
- Właściwe wykonanie zapytania metodą execute().
Podpinanie jest odporne na ataki SQL Injection. MySQL ma jasno określone, co jest danymi, a co zapytaniem i ściśle się tego trzyma. Ponadto jest także wydajniejsze, niż samodzielne spinanie wszystkiego po stronie PHP.
Szczególnie ciekawa właściwość podpinania polega na możliwości podpięcia kilku zestawów danych do tego samego szkieletu zapytania, dzięki czemu wydajność wzrasta jeszcze bardziej. Zademonstruje to poniższy przykład, w którym rozszerzyliśmy nasz formularz tak, aby naraz można nim było wprowadzać kilka produktów. Gdy zostanie on wysłany, połączymy się z MySQL'em, przekazując szkielet naszego zapytania. Następnie będziemy podpinali do niego dane kolejnych produktów i wykonywali.
<?php
try
{
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
$pdo = new PDO('mysql:host=localhost;dbname=produkty;port=3305', 'root', 'root');
$pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo -> prepare('INSERT INTO `produkty` (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES(
:nazwa,
:opis,
:ilosc,
:cena,
:jakosc)'); // 1
$ilosc = 0;
foreach($_POST['produkty'] as $produkt)
{
if(strlen($produkt['nazwa']) > 0)
{
$stmt -> bindValue(':nazwa', $produkt['nazwa'], PDO::PARAM_STR); // 2
$stmt -> bindValue(':opis', $produkt['opis'], PDO::PARAM_STR);
$stmt -> bindValue(':ilosc', $produkt['ilosc'], PDO::PARAM_INT);
$stmt -> bindValue(':cena', (float)$produkt['cena'], PDO::PARAM_STR);
$stmt -> bindValue(':jakosc', $produkt['jakosc'], PDO::PARAM_INT);
$ilosc += $stmt -> execute(); // 3
}
}
if($ilosc > 0)
{
echo 'Dodano: '.$ilosc.' rekordow';
}
else
{
echo 'Wystapil blad podczas dodawania rekordow!';
}
}
else
{
echo '<form method="post" action="pdo_7.php">';
for($i = 1; $i <= 4; $i++)
{
echo '<hr/>
<p>Nazwa: <input type="text" name="produkty['.$i.'][nazwa]"/></p>
<p>Opis: <input type="text" name="produkty['.$i.'][opis]"/></p>
<p>Ilosc: <input type="text" name="produkty['.$i.'][ilosc]"/></p>
<p>Cena: <input type="text" name="produkty['.$i.'][cena]"/></p>
<p>Jakosc: <select name="produkty['.$i.'][jakosc]">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</select></p>';
}
echo '<p><input type="submit" value="Dodaj"/></p></form>';
}
}
catch(PDOException $e)
{
echo 'Wystapil blad biblioteki PDO: ' . $e->getMessage();
}
?>
Opis:
- Szkielet zapytania wysyłany tylko raz.
- Zestawy danych ładowane są w pętli.
- W pętli wykonujemy też metodę execute().
Podpinanie nie ogranicza się tylko do zapytań typu INSERT. Z powodzeniem można stosować je także przy SELECT. Napiszemy teraz skrypt wyświetlający listę produktów oraz umożliwiający nam zobaczenie szczegółów każdego z nich. Dlatego do drugiego zapytania, pobierającego szczegółowe informacje, musimy podpiąć ID produktu, który chcemy obejrzeć.
<?php
try
{
$pdo = new PDO('mysql:host=localhost;dbname=produkty;port=3305', 'root', 'root');
$pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo -> query('SELECT id, nazwa FROM produkty ORDER BY id');
echo '<ul>';
while($row = $stmt -> fetch())
{
echo '<li><a href="pdo_8.php?id='.$row['id'].'">'.$row['nazwa'].'</a></li>';
}
$stmt -> closeCursor();
echo '</ul>';
if(isset($_GET['id'])) // 1
{
$stmt = $pdo -> prepare('SELECT `nazwa`, `opis`, `ilosc`, `cena`, `jakosc` FROM `produkty` WHERE `id` = :id'); // 2
$stmt -> bindValue(':id', $_GET['id'], PDO::PARAM_INT);
$stmt -> execute(); // 3
if($details = $stmt -> fetch()) // 4
{
echo '<hr/>
<p><b>Nazwa:</b> '.$details['nazwa'].'</p>
<p><b>Opis:</b> '.$details['opis'].'</p>
<p><b>Ilosc:</b> '.$details['ilosc'].'</p>
<p><b>Cena:</b> '.$details['cena'].'</p>
<p><b>Jakosc:</b> '.$details['jakosc'].'</p>';
}
else
{
echo '<hr/><p>Przepraszamy, podany rekord nie istnieje!</p>';
}
$stmt -> closeCursor();
}
}
catch(PDOException $e)
{
echo 'Wystapil blad biblioteki PDO: ' . $e->getMessage();
}
?>
Opis:
- Oczywiście wyświetlanie szczegółów przeprowadzamy tylko, jeśli podaliśmy ID.
- Przygotowujemy szkielet zapytania SELECT.
- Wykonujemy zapytanie metodą execute(). Zauważmy, że obiektem $stmt dysponujemy już od momentu wywołania metody prepare(), dlatego execute() nam już nic tu nie zwraca.
- Dalej postępujemy już tradycyjnie, po prostu pobierając kolejne rekordy (w tym wypadku tylko jeden) i zamykając kursor.
Jedyną wadą podpinania jest wydłużenie kodu PHP. Jeśli dotychczas wysyłaliśmy zapytanie DELETE zwyczajnie wykonując metodę exec(), teraz musimy to rozpisać na kilka linijek. Jednak jeszcze w tym rozdziale poznamy nakładkę na PDO zwaną Open Power Driver, dzięki której kod z powrotem stanie się krótki i czytelny.
Uwaga! Biblioteka PDO działa nieco inaczej na wersjach MySQL 5.0 i 4.1 także w przypadku podpinania. Wersja 5.0 jest bardziej elastyczna, jeśli chodzi o konwersję typów i nic jej nie zaszkodzi, kiedy spróbujemy wstawić do pola TINYINT(1) wartość oznaczoną w skrypcie jako PDO::PARAM_BOOL. Na MySQL 4.1 takie zapytanie nie zostanie wykonane, a ponadto serwer DB nie wygeneruje żadnego ostrzeżenia czy komunikatu.
Ćwiczenie: Zaprogramować formularz do edycji danych produktów z wykorzystaniem podpinania. Skrypt musi wczytywać do formularza dane edytowanego produktu oraz po jego wysłaniu, zmodyfikować wskazany rekord zapytaniem UPDATE. Pamiętaj: wraz z wysłanym formularzem musisz przesłać także ID rekordu, który modyfikujesz!
[edytuj] Obsługa relacji
Potrafimy już pobierać wyniki pojedynczego zapytania, potrafimy też wewnątrz jednego zapytania tworzyć relacje. Przejdźmy się jednak do naszej bazy danych księgarni i załóżmy, że chcemy wyświetlić listę kategorii oraz znajdujące się w każdej z nich książki. Sporo początkujących programistów podchodziło do tego zadania z marszu: wysyłali zapytanie żądające pobrania listy kategorii, a następnie w pętli kolejne, które dla aktualnej kategorii pobierało książki. Od razu przestrzegamy przed takim sposobem myślenia! Łamie on podstawową zasadę pracy z bazami danych mówiącą, że generalnie im mniej zapytań, tym lepiej. Ilość wysyłanych zapytań musi być względnie stała i poważnym błędem jest dopuszczenie do sytuacji, gdy zależy ona wprost proporcjonalnie od ilości pobieranych danych. PHP Data Objects niejako wymusza rezygnację z tej techniki, ponieważ wspominaliśmy, że nie można wysłać innego zapytania, kiedy nie skończyliśmy pobierać wyników jednego i nie zamknęliśmy jego kursora. Jak więc zatem poradzić sobie z tym zadaniem? Jest to bardzo proste - nasz skrypt będzie bez względu na ilość kategorii wykonywać dwa zapytania, których wynik będzie ładowany do tablicy. Dopiero z niej będzie wyświetlany kod HTML.
<?php
try
{
$pdo = new PDO('mysql:host=localhost;dbname=produkty;port=3305', 'root', 'root');
$pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo -> query('SELECT id, nazwa FROM kategorie ORDER BY id');
$wynik = array();
while($row = $stmt -> fetch())
{
$wynik[$row['id']] = array( // 1
'nazwa' => $row['nazwa'],
'ksiazki' => array() // 2
);
}
$stmt -> closeCursor();
$stmt = $pdo -> query('SELECT nazwa, wydawnictwo, kategoria_id
FROM ksiazki ORDER BY kategoria_id, id'); // 3
while($row = $stmt -> fetch())
{
$wynik[$row['kategoria_id']]['ksiazki'][] = array( // 4
'nazwa' => $row['nazwa'],
'wydawnictwo' => $row['wydawnictwo']
);
}
$stmt -> closeCursor();
// 5
foreach($wynik as &$kategoria)
{
echo '<h3>'.$kategoria['nazwa'].'</h3>';
foreach($kategoria['ksiazki'] as &$ksiazka)
{
echo '<p><i>'.$ksiazka['nazwa'].'</i>
(Wyd. '.$ksiazka['wydawnictwo'].')</p>';
}
}
}
catch(PDOException $e)
{
echo 'Wystapil blad biblioteki PDO: ' . $e->getMessage();
}
?>
Sztuczka jest tu bardzo prosta - wykorzysujemy ID kategorii jako indeks tablicy (1). Ładujemy do niej nazwę kategorii oraz tworzymy pustą tablicę ksiazki (2) - tutaj będą trafiały książki należące do tej kategorii. Następnie wysyłamy zapytanie służące do pobrania wszystkich książek (3). Zauważmy, że sortujemy je najpierw według ID kategorii, a ponadto tenże ID pobieramy. Wykorzystany zostaje jako klucz dostępu, dzięki czemu jesteśmy w stanie ulokować daną książkę w odpowiedniej kategorii (4). Na końcu dwoma zagnieżdżonymi pętlami wyświetlamy wszystko.
Sposób opiera się na wcześniejszym zbuforowaniu danych. W praktyce programiści bardzo często tworzą takie bufory, ponieważ pozwalają one na dodatkową obróbkę pobranych danych, a w przypadku korzystania z systemu szablonów są nawet niezbędne.
Ćwiczenie: Zmodyfikować powyższy skrypt tak, aby przy nazwie książki wymienieni byli również wszyscy jej autorzy, tłumacze, itd. Skrypt może wysyłać najwyżej cztery zapytania.
[edytuj] Zaawansowane techniki bazodanowe
W bardziej rozbudowanych projektach zachodzi potrzeba stosowania bardziej rozbudowanych narzędzi, niż zwyczajnych sterowników bazodanowych. Potrzebne są tam biblioteki oferujące mechanizmy cache'owania czy debugowania. Co więcej, bezpośrednie operowanie na bazie przy pomocy języka SQL, szczególnie przy wielu prostych operacjach jest wyjątkowo niewygodne. Tutaj z pomocą przychodzą nam systemy ORM (Object-Relational Mapping). Mówiąc ogólnie, odwzorowują one strukturę bazy danych po stronie skryptu w postaci obiektów. W ten sposób, utworzenie nowego wiersza np. w tabeli z użytkownikami sprowadza się tam do utworzenia nowego obiektu klasy User i przypisania do jego pól żądanych wartości.
Jednym z kilku dostępnych systemów ORM dla PHP5 jest Doctrine. W jego skład wchodzi szereg narzędzi:
- Parser autorskiego wariantu języka SQL, DQL-a, zarówno w postaci tekstowej, jak i obiektowej.
- Generator struktury bazy danych.
- Generator modeli.
- System cache.
- i wiele innych.
Jest on szczególnie przydatny w grupowej pracy nad projektem, gdzie pojawia się problem przenoszenia zmian poczynionych w strukturze bazy przez jednego programistę do pozostałych uczestników. W przypadku Doctrine, cała baza opisana jest w formie pliku tekstowego z wykorzystaniem prostego i czytelnego formatu YAML, który może być łatwo umieszczony w systemie kontroli wersji. Programista może na jego podstawie wygenerować strukturę bezpośrednio w bazie danych oraz pliki modeli, które pozwalają na manipulację zawartością bazy poprzez obiekty PHP.
[edytuj] Zakończenie
Nareszcie umiemy komunikować się z bazami danych z poziomu PHP, wykorzystując do tego celu bibliotekę PHP Data Objects. Nasze skrypty mogą dzięki temu w pełni czerpać z oferowanych przez bazy danych możliwości. Jednak temat baz danych nie został jeszcze zamknięty. Ponieważ PDO jest relatywnie nową biblioteką, wiele skryptów wciąż korzysta ze starych technik wywodzących się jeszcze z prehistorycznych czasów PHP 2.0/FI - dlatego też następny z rozdziałów poświęcony zostanie krótkiemu powrotowi do przeszłości.
[edytuj] Jak to się robiło kiedyś?
PHP Data Objects jest bardzo młodą biblioteką i mimo swoich zalet, wciąż tysiące skryptów napisanych wcześniej korzystają ze starych oraz niewygodnych funkcji komunikacji z bazami danych. Dlatego podręcznik ten zawiera także im poświęcony rozdział.
[edytuj] Pobieranie wyników
Ten zestaw funkcji w ogóle nie korzysta z dobrodziejstw programowania obiektowego - kiedy powstawał, w PHP po prostu jeszcze takowego nie było! Ponieważ znamy już się nieco na pracy z bazami danych, zaczniemy od razu od pobrania listy naszych produktów:
<?php
mysql_connect('localhost:3305', 'root', 'root'); // 1
mysql_select_db('produkty'); // 2
$r = mysql_query('SELECT `id`, `nazwa`, `ilosc` FROM `produkty` ORDER BY `ilosc`'); // 3
echo '<ul>';
while($row = mysql_fetch_assoc($r)) // 4
{
echo '<li>'.$row['id'].' - '.$row['nazwa'].' - '.$row['ilosc'].'</li>';
}
echo '</ul>';
mysql_close(); // 5
?>
Opis:
- Funkcja mysql_connect(), która przyjmuje parametry: serwer, nazwa użytkownika, hasło, powoduje nawiązanie połączenia z bazą danych.
- Funkcja mysql_select_db() wybiera bazę danych, na której będziemy pracować.
- Funkcja mysql_query() wysyła zapytanie do bazy. W zależności od jego rodzaju generuje:
- Zbiór wyników - dla zapytań SELECT.
- true - dla zapytań typu INSERT jeśli wykonanie zapytania się powiodło
- false - w przypadku jakiegokolwiek błędu w zapytaniu.
- mysql_fetch_assoc() pobiera kolejny rekord ze zbioru wyników $r jako tablicę asocjacyjną. Istnieją jeszcze mysql_fetch_num() (numeryczne indeksy tablicy) oraz mysql_fetch_array() (połączenie obu tych sposobów).
- mysql_close() zamyka połączenie z bazą.
Zauważ, że nie ma tutaj w ogóle czegoś takiego, jak zamykanie kursora. Jeśli korzystasz z tych funkcji, jest ono niepotrzebne, ponieważ to rozszerzenie tak naprawdę oszukuje. Wszystkie rekordy w są pobierane automatycznie przez mysql_query() i zapisywane do specjalnego bufora, skąd odczytuje je mysql_fetch_assoc(). Analogiczna metoda PDOStatement::fetch() pobierała dane bezpośrednio z serwera DB, umożliwiając ich natychmiastowe przetwarzanie. Obie techniki mają swoje plusy i minusy. PDO dzięki temu jest znacznie wydajniejsze, szczególnie przy większej liczbie rekordów, lecz nie można w nim sprawdzić, ile wyników ostatecznie dało nam zapytanie, dopóki ich wszystkich nie pobierzemy.
[edytuj] Obsługa błędów
Spróbujmy dodać do naszej listy produktów sortowanie:
<?php
mysql_connect('localhost:3305', 'root', 'root');
mysql_select_db('produkty');
$r = mysql_query('SELECT `id`, `nazwa`, `ilosc` FROM `produkty` ORDER BY `ilosc` DECS');
echo '<ul>';
while($row = mysql_fetch_assoc($r))
{
echo '<li>'.$row['id'].' - '.$row['nazwa'].' - '.$row['ilosc'].'</li>';
}
echo '</ul>';
mysql_close();
?>
Po uruchomieniu skryptu dostajemy dziwny komunikat:
Warning: mysql_fetch_assoc(): supplied argument is not a valid MySQL result resource in D:\Serwer\www\mysql\skrypt.php on line 9
Zobaczmy: mamy literówkę w zapytaniu; napisaliśmy DECS zamiast DESC, jednak mysql_query() w ogóle nie zgłosił żadnego komunikatu! Jedynym sygnałem, że coś jest nie tak, było zwrócenie wartości false zamiast zbioru wyników, co po wstawieniu do funkcji mysql_fetch_assoc() zaowocowało komunikatem o podaniu niewłaściwego parametru. Czy więc jest tu w ogóle jakaś obsługa błędów? Oczywiście, tyle że zakłada ona, że programista lubi monotonię i pisanie w kółko:
mysql_query('zapytanie') or die('Blad MySQL: '.mysql_error().'<br/>');
}
W praktyce bardziej opłacało się tu napisać własny wariant mysql_query(), który automatyzuje tę czynność i samodzielnie zgłasza nam błędy, jak trzeba.
[edytuj] Wstawianie danych
Do wykonywania zapytań INSERT czy UPDATE także używana jest funkcja mysql_query(), lecz tym razem będzie ona za każdym razem zwracała wartość true. Aby sprawdzić, ile rekordów zostało zmodyfikowanych, musimy wywołać dodatkowo mysql_affected_rows(). Oto przepisany przykład z poprzedniego rozdziału dodający nowe produkty do bazy:
<?php
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
mysql_connect('localhost:3305', 'root', 'root');
mysql_select_db('produkty');
mysql_query('INSERT INTO `produkty` (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES(
\''.mysql_real_escape_string($_POST['nazwa']).'\',
\''.mysql_real_escape_string($_POST['opis']).'\',
\''.mysql_real_escape_string($_POST['ilosc']).'\',
\''.mysql_real_escape_string($_POST['cena']).'\',
\''.mysql_real_escape_string($_POST['jakosc']).'\')');
$ilosc = mysql_affected_rows();
if($ilosc > 0)
{
echo 'Dodano: '.$ilosc.' rekordow';
}
else
{
echo 'Wystąpił błąd podczas dodawania rekordów!';
}
mysql_close();
}
else
{
?>
<form method="post" action="mysql_4.php">
<p>Nazwa: <input type="text" name="nazwa"/></p>
<p>Opis: <input type="text" name="opis"/></p>
<p>Ilosc: <input type="text" name="ilosc"/></p>
<p>Cena: <input type="text" name="cena"/></p>
<p>Jakosc: <select name="jakosc">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</select></p>
<p><input type="submit" value="Dodaj"/></p>
</form>
<?php
}
?>
Zauważ, jak musimy tutaj umieszczać dane w zapytaniu. Nie tylko wymaga to zabawy operatorem łączenia ciągów, ale też konieczność wywoływania funkcji mysql_real_escape_string() do escape'owania danych i zapobiegania atakom SQL Injection. Oczywiście, gdy magic quotes było włączone, funkcji tej nie powinno się używać.
[edytuj] Informacje dodatkowe
Ze względu na charakter pobierania rekordów przez to rozszerzenie, umożliwia ono policzenie ilości zwróconych wyników jeszcze przed rozpoczęciem ich pobierania. Aby to wykonać, należy skorzystać z funkcji mysql_num_rows() z podanym jako parametr zbiorem wyników.
<?php
mysql_connect('localhost:3305', 'root', 'root');
mysql_select_db('produkty');
$r = mysql_query('SELECT `id`, `nazwa`, `ilosc` FROM `produkty` ORDER BY `ilosc`');
echo '<p>Pobrano '.mysql_num_rows($r).' wyników</p>';
echo '<ul>';
while($row = mysql_fetch_assoc($r))
{
echo '<li>'.$row['id'].' - '.$row['nazwa'].' - '.$row['ilosc'].'</li>';
}
echo '</ul>';
mysql_close();
?>
Jeśli dodaliśmy nowy rekord, możemy pobrać jego ID funkcją mysql_insert_id() wykonaną zaraz po funkcji mysql_query() z zapytaniem INSERT.
[edytuj] Zakończenie
Jak wspomnieliśmy, rozszerzenie to ma charakter historyczny. Twórcy PHP stopniowo ograniczają wsparcie dla niego; nie ma w nim np. żadnej implementacji mechanizmu podpinania, choć biblioteki klienckie MySQL jak najbardziej na to zezwalają. Jest ono także niewygodne w użyciu oraz na dłuższą metę mało efektywne. W codziennej praktyce chyba żaden szanujący się programista nie stosował żadnej z tych funkcji bezpośrednio, lecz korzystał z dodatkowej, napisanej w PHP nakładki automatyzującej wszystkie nużące czynności i zapewniającej wsparcie programowania obiektowego. Teraz, gdy do dyspozycji jest biblioteka PDO, sens korzystania z tych funkcji jest bardzo wątpliwy.
[edytuj] phpMyAdmin
Dotychczas wszystkie nasze bazy danych i tabele tworzyliśmy ręcznie z wiersza poleceń. Na dłuższą metę taka praca jest bardzo niewygodna i nieefektywna i nikt nie administruje w ten sposób bazami danych. Do tego celu wykorzystywane są specjalne, zaawansowane narzędzia, przestawiające całą strukturę w postaci graficznej oraz udostępniające narzędzia do np. importu czy eksportu zawartości. Jeśli tworzysz dynamiczne strony WWW, prędzej czy później spotkasz się z aplikacją phpMyAdmin, napisanym w PHP darmowym i niezwykle rozbudowanym menedżerem bazy danych MySQL wchodzącym w skład podstawowego wyposażenia niemal każdej szanującej się firmy hostingowej czy serwera WWW. Dzięki niemu możesz w parę minut skopiować stworzoną przez Ciebie na własnym komputerze bazę danych na właściwy serwer, a mnóstwo programistów wykorzystuje go do projektowania baz dla swych projektów WWW. W tym rozdziale nauczysz się podstaw pracy z phpMyAdminem.
[edytuj] Instalacja
Przebieg instalacji phpMyAdmina jest bardzo prosty:
- Wejdź na stronę [2] i pobierz z działu DOWNLOADS najnowszą dostępną wersję (w chwili pisania tego rozdziału: 2.8.2.1).
- phpMyAdmina nie będziemy instalować wraz z naszymi projektami, lecz do katalogu htdocs serwera Apache. Warto wprowadzić taki podział na swoim komputerze, aby oddzielić narzędzia od tworzonych przez nas stron. Rozpakuj zatem ściągnięte archiwum do katalogu D:/Serwer/Apache2/htdocs lub /usr/local/apache2/htdocs/ w zależności od systemu operacyjnego.
- Pojawi się tam katalog phpMyAdmin lub nieco dłuższy. Skróć go do zwykłego pma. W ten sposób aplikacja będzie dostępna z przeglądarki po wpisaniu adresu http://localhost/pma/ - dzięki temu szybciej się go wpisuje.
- Otwórz plik config.inc.php. Jest to skrypt konfiguracyjny phpMyAdmina i możesz tu zmieniać wszystkie ustawienia.
- Odnajdź dyrektywę $cfg['PmaAbsoluteUri'] i wprowadź do niej pełen adres URL do aplikacji, np. http://localhost/pma.
- Pora na ustawienie połączenia z bazą. phpMyAdmin obsługuje zarówno automatyczną konfigurację, gdzie wszystkie wartości podane są w bazie, jak i ręczne logowanie z użyciem formularza WWW (rozwiązanie spotykane u firm hostingowych). My pracujemy u siebie w domu, dlatego wszystkie wartości wypełnimy w pliku konfiguracyjnym. Zmodyfikuj następujące dyrektywy:
- $cfg['Servers'][$i]['host'] - wpisz nazwę hosta, na jakim pracuje MySQL (przeważnie localhost).
- $cfg['Servers'][$i]['port'] - domyślnie MySQL pracuje na porcie 3306, ale jeśli masz inny, ustaw go tutaj.
- $cfg['Servers'][$i]['user'] - nazwa użytkownika MySQL (u nas: root), na którym będziemy pracować. Niepodanie tej wartości włączy konieczność ręcznego logowania się do phpMyAdmina.
- $cfg['Servers'][$i]['password'] - hasło podanego użytkownika.
- Zapisz skrypt i spróbuj uruchomić w przeglądarce http://localhost/pma. phpMyAdmin powinien automatycznie ustawić się do pracy w języku polskim (jeśli nie, otwórz jeszcze raz config.inc.php i zmień wartość dyrektywy $cfg['DefaultLang'] na pl-iso-8859-2). Jeśli otrzymasz komunikat błędu o niemożności nawiązania połączenia, musisz sprawdzić jego parametry w pliku config.inc.php.
[edytuj] Rzut okiem
Po uruchomieniu ujrzysz ekran startowy phpMyAdmina. Podczas pracy okno przeglądarki podzielone jest na dwie części: po lewej znajduje się wąski i ciemny pasek. Domyślnie znajduje się w nim lista rozwijana, z której wybieramy interesującą nas bazę danych. Gdy ją wybierzemy, pod spodem wyświetli się również lista znajdujących się w niej tabel. Szersza i jaśniejsza część ekranu to część operacyjna - tu wykonujemy wszystkie operacje.
Ekran startowy zawiera różne informacje o stanie połączenia oraz daje dostęp do niektórych opcji. Po prawej stronie możemy zmienić motyw graficzny oraz język interfejsu, natomiast pośrodku ekranu znajdują się rzeczy bardziej powiązane z samym MySQL-em. Pod napisem Utwórz nową bazę danych widzimy dwa obiekty formularza. W pierwszy z nich wpisujemy nazwę, w drugi kodowanie i po kliknięciu na przycisk "Utwórz" stworzona zostanie nowa baza danych o takich parametrach. Poniżej mamy dostęp do różnych ekranów administracyjnych, np. listy procesów serwera czy edytora uprawnień.
Przejdźmy za pomocą części nawigacyjnej do naszej bazy produkty. Zarówno w części nawigacyjnej, jak i na ekranie roboczym pojawi nam się spis tabel, które już utworzyliśmy. W części roboczej dodatkowo widoczna jest belka nawigacyjna z następującymi elementami:
- Struktura - pokazuje listę tabel, czyli to, co właśnie oglądamy.
- SQL - ekran do tworzenia i wykonywania zapytań SQL.
- Szukaj - wyszukiwarka danych
- Zapytanie przez przykład - graficzny edytor zapytań dla nieznających języka SQL (wzorowany na edytorach dostępnych np. w Microsoft Access).
- Eksport - narzędzia eksportu bazy danych do pliku SQL lub innych.
- Import - narzędzia do importu bazy danych z pliku SQL. W starszych wersjach phpMyAdmina ekran ten był częścią ekranu SQL.
- Operacje - narzędzia administracyjne: zmiana nazwy bazy danych, kopiowanie, zmiana kodowania.
- Uprawnienia - pokazuje użytkowników uprawnionych do dostępu do wybranej bazy.
- Usuń - usuwa bazę danych.
Na właściwym serwerze WWW ilość zakładek może być ograniczona ze względu na brak uprawnień.
Pod listą tabel znajduje się niewielki formularz inicjujący edytor nowej tabeli. Niebawem z niego skorzystamy, lecz najpierw zapoznamy się jeszcze z widokiem pojedynczej tabeli. Możemy do niego przejść, wybierając nazwę z panelu nawigacyjnego lub klikając na drugą z ikonek w ekranie roboczym przy interesującej nas tabeli. Ujrzymy wtedy ekran podobny do tego przedstawionego na ilustracji 3.
Widok tabeli składa się ze szczegółowej listy wszystkich pól pokazującej informacje m.in. o ich typie czy o wartości domyślnej. Poniżej znajduje się prosty formularz umożliwiający dodanie nowych pól do tabeli, a jeszcze niżej informacje dodatkowe: spis indeksów, rozmiar danych w tabeli i statystyka rekordów. Zauważmy, że zmianie uległa belka nawigacyjna. Zniknęła opcja Zapytanie przez przykład, za to doszły dwie nowe:
- Przeglądaj - wyświetla zawartość tabeli. Jeżeli trzymasz w tabeli dużo rekordów, nie musisz się przejmować - wyniki są porcjowane.
- Wyczyść - czyści tabelę zapytaniem TRUNCATE (po uprzednim potwierdzeniu).
Dodatkowo niektóre zakładki zmieniają swoje działanie, np. opcja Eksport umożliwia teraz eksportowanie tylko aktualnej tabeli.
[edytuj] Tworzenie tabeli
Aby rozpocząć tworzenie nowej tabeli, przełączmy się na widok bazy danych. Pod listą tabel widoczny jest niewielki formularz zatytułowany "Utwórz nową tabelę". Wpisujemy w nim nazwę nowej tabeli oraz ilość pól, które zamierzamy do niego dodać. Jeśli się pomylisz, nie przejmuj się. Edytor będzie można łatwo powiększyć jeszcze w trakcie edycji, a ponadto można dodawać nowe pola do tabeli już po jej utworzeniu. Po kliknięciu na "Wykonaj" ukaże się rozbudowany edytor umożliwiający dokładne zdefiniowanie struktury. Oto omówienie poszczególnych kolumn:
- Nazwa - nazwa pola.
- Typ - z listy wybieramy typ danych, jakie mają być w nim przechowywane.
- Długość/wartości - dla pól VARCHAR czy CHAR podajemy tu maksymalną dozwoloną długość ciągu tekstowego. Dla liczb nie trzeba w sumie nic wpisywać, phpMyAdmin zaproponuje wtedy domyślne wartości, które usatysfakcjonują każdego. Dla pól ENUM oraz SET wpisujemy tu listę dozwolonych wartości ujętych w apostrofy i odseparowanych przecinkami.
- Metoda porównywania napisów - tylko dla pól VARCHAR, TEXT itp. - wybieramy tutaj, według jakiego kodowania mają być porównywane znajdujące się tu dane. Przykładowo, jeżeli zamierzasz stworzyć witrynę wykorzystującą kodowanie Unicode, musisz odnaleźć tu zbiór np. utf8_polish_ci.
- Atrybuty - dodatkowe atrybuty, np. dla liczb można wybrać atrybut UNSIGNED, co spowoduje, że będzie można przechowywać tu tylko liczby dodatnie, ale za to w dwukrotnie większym dozwolonym zakresie (przestrzeń zwolniona po wywaleniu części ujemnej).
- Null - czy pole może przyjmować wartości NULL.
- Domyślne - domyślna wartość tego pola w nowych rekordach.
- Dodatkowo - dla pola ID możemy tu wybrać atrybut AUTO_INCREMENT.
Kolejne cztery pola wyboru pozwalają zdefiniować rodzaj indeksu. Od lewej strony mamy:
- PRIMARY KEY - ustawić dla pola ID.
- INDEX - normalny indeks.
- UNIQUE - pole z unikalnymi wartościami (nie mogą się powtarzać w dwóch rekordach)
- --- - brak indeksu.
Pole pod ikonką "T" umożliwia stworzenie indeksu FULLTEXT ułatwiającego przeszukiwanie zawartości tekstów. Można go utworzyć tylko w tabelach MyISAM, a ponadto aby mieć z niego jakiś pożytek, trzeba umieć pisać odpowiednie zapytania wykorzystujące tzw. "fulltext searching".
Uwaga: zaznaczenie dla kilku pól pozycji INDEX nie spowoduje utworzenia kilku indeksów, tylko jeden indeks łączony! Dlatego jeśli zamierzasz stworzyć więcej indeksów, musisz to zrobić z pomocą dodatkowego edytora już po utworzeniu tabeli. Włączamy go w widoku tabeli w pozycji "Indeksy". Widoczny jest tam formularz zatytułowany "Utwórz indeks dla X kolumn". Po wybraniu liczby kolumn w indeksie, przejdziemy do szczegółowego widoku, gdzie możemy wybrać:
- Nazwę indeksu
- Jego rodzaj
- Określić pola mające wejść w jego skład.
Dalszą część formularza można zignorować. Pamiętajmy też o określeniu globalnego kodowania dla całej tabeli oraz wybraniu typu (domyślny w MySQL-u to mający większe możliwości InnoDB, ale na co dzień korzysta się głównie z wydajniejszego MyISAM).
[edytuj] Modyfikacja tabeli
Istnieje możliwość modyfikacji struktury tabeli już po jej utworzeniu. Typowe operacje to:
- Dodawanie nowych pól: pod listą pól w widoku struktury tabeli znajduje się niewielki formularz, w którym określamy, ile kolumn chcemy dodać oraz w którym miejscu. Po kliknięciu na "Wykonaj" zostaniemy przeniesieni do identycznego edytora, jak w przypadku tworzenia tabeli.
- Modyfikacja już istniejących pól. Klikamy na ikonę ołówka przy interesującym nas polu lub zaznaczamy grupę pól i klikamy na ołówek pod spisem. Modyfikacja odbywa się w identycznym edytorze, jak w przypadku tworzenia tabeli.
- Usuwanie pól - za pomocą ikonki krzyżyka. Wcześniej musimy potwierdzić naszą chęć.
[edytuj] Zarządzanie rekordami
W zakładce Przeglądaj możemy obejrzeć zawartość aktualnej tabeli. Na ekranie ukaże się lista wszystkich rekordów wraz z wartościami wszystkich pól. Jest ona porcjowana: naraz pokazywane jest tylko 30, a do następnych stron przełączamy się za pomocą strzałek. Ikonki przy każdym rekordzie umożliwiają edycję danych lub jego usunięcie. Analogiczna przeglądarka ukaże nam się, gdy za pomocą zakładki SQL wykonamy zapytanie SELECT lub inne generujące jakąś listę wyników.
Zarówno edycja, jak i dodawanie nowego rekordu odbywa się w specjalnym edytorze pokazanym na screenie. Formularz składa się z pięciu kolumn:
- Pole - nazwa pola
- Typ - informacja o typie możliwych do przechowania danych
- Funkcja - z tej listy możemy wybrać funkcję, przez jaką zostanie przepuszczona wartość wpisana w polu "Wartość". Uwaga: niektóre funkcje nie wymagają podawania żadnego dodatkowego parametru w tamtym polu (np. UNIX_TIMESTAMP() będący odpowiednikiem time() w PHP).
- Null - jeśli pole zezwala, możemy tutaj zaznaczyć, że wstawiamy wartość NULL.
- Wartość - dokładna wartość, jaką chcemy wstawić w wybrane pole.
Formularz dodawania umożliwia tworzenie do dwóch rekordów naraz. Uważaj, gdyż w przypadku tabel o dużej liczbie pól, phpMyAdmin wstawia co kilkanaście pasek z przyciskiem Wykonaj, jednak nie korzystaj z niego, lecz z przycisku znajdującego się pod całym formularzem. Inaczej zaakceptujesz tylko część wprowadzonych wartości, co może doprowadzić do nieprzewidywalnych zachowań.
Jeśli chcemy dodawać rekordy seryjnie, możemy z listy pod formularzem przy napisie "a następnie" wybrać "dodaj nowy rekord" zamiast "wróć". Spowoduje to, że po dodaniu rekordów z powrotem zostaniemy przeniesieni do formularza dodawania.
[edytuj] Import i eksport zawartości
phpMyAdmin jest szczególnie lubiany przez programistów, gdyż najczęściej to za jego pomocą bazy danych przenoszone są z lokalnego komputera na właściwy serwer WWW. Służy do tego zakładka Eksport. Aby prawidłowo wyeksportować zawartość bazy danych, musimy wpierw spędzić chwilkę na konfiguracji:
- W ramce "Eksport" wybieramy interesujące nas tabele. Jeżeli eksportujemy całą bazę, możemy kliknąć na "Zaznacz wszystkie".
- Wybieramy format. Domyślnie phpMyAdmin zaproponuje eksport do pliku SQL, który jest niczym innym, jak listą zapytań CREATE TABLE oraz INSERT, które po uruchomieniu odtworzą dokładną kopię naszej bazy.
- W ramce "Opcje eksportu" zaznaczamy, co chcemy eksportować: samą strukturę, same dane, czy obie rzeczy naraz.
- Dodatkowo, phpMyAdmin umożliwia zachowanie kompatybilności ze starszymi wersjami bazy danych, a nawet innych serwerów DB! Jeśli stworzyłeś na MySQL 5.0 bazę danych, ale na serwerze jest MySQL 4.0, skorzystaj z listy "Kompatybilność eksportu SQL", a aplikacja wygeneruje zapytania dla wybranej przez Ciebie wersji serwera. Podobnie możesz postąpić, jeśli chcesz przenieść swoją bazę np. na PostgreSQL (dodajmy, że ten serwer DB także posiada swój "webowy" menedżer zwany oczywiście phpPgAdmin).
- Jeśli nie mamy ochoty, aby naszym monitorem zawładnęły zapytania SQL, zaznaczamy jeszcze opcję "Zapisz jako plik", co spowoduje, że generowany wynik będziemy mogli od razu ściągnąć na nasz komputer, zamiast wyświetlać jego zawartość w przeglądarce.
- Klikamy przycisk "Wykonaj".
Importowanie bazy danych odbywa się za pomocą zakładki Import. Wskazujemy w niej plik SQL na naszym dysku ze strukturą bazy, wybieramy kodowanie i klikamy "Wykonaj". Pamiętaj, że PHP ma limit wykonywania ograniczony do 30 sekund. Jeśli twój plik SQL ma naprawdę potężne rozmiary, będziesz mógł kontynuować później jego wgrywanie od wybranego zapytania (ramka "Import częściowy"). W starszych wersjach phpMyAdmina import odbywał się za pomocą zakładki SQL, ale według niemal identycznej procedury.
[edytuj] Zakończenie
Od tej pory wszystkie bazy danych i tabele będziemy tworzyć już z użyciem phpMyAdmina. Dzięki temu narzędziu praca z bazami jest naprawdę przyjemna, i co ważniejsze, wygodna. phpMyAdmin stał się tak popularny, że na polu menedżerów dla bazy MySQL w zasadzie nie ma żadnej konkurencji, a twórcy menedżerów dla innych serwerów DB bardzo mocno się na nim wzorują (podobna nawigacja, układ menusów, formularze itd.):
- phpPgAdmin - menedżer dla baz PostgreSQL.
- SQLiteManager - menedżer dla baz SQLite.
W następnym rozdziale sprawdzimy nasze umiejętności w praktyce, tworząc system newsów oparty o MySQL.
[edytuj] System newsów
Spróbujmy teraz zaimplementować zdobytą przez nas wiedzę w praktyce. Napiszemy prosty system newsów z podziałem na kategorie i możliwością dodawania komentarzy. Podręcznik ten pokaże, jak zacząć, natomiast twoim zadaniem będzie dopisanie (z pomocą wskazówek) wszystkich dodatków niezbędnych do tego, aby system był w pełni funkcjonalny. Nowością w porównaniu do księgi gości będzie to, iż nie będziemy już umieszczać kodu HTML bezpośrednio we właściwym pliku, lecz umieścimy go jako zbiór funkcji w osobnym. Dzięki temu poprawi się czytelność kodu, a ty poznasz zalety separacji tych dwóch elementów jeszcze przed przejściem do omawiania systemów szablonów.
[edytuj] Projekt bazy danych
System newsów oparty będzie o bazę danych MySQL, w której ulokujemy trzy tabelki. Będą one połączone ze sobą relacją jeden do wielu:
- Istnieje grupa kategorii
- Każda kategoria może zawierać dowolną liczbę newsów, ale pojedynczy news może należeć tylko do jednej z nich.
- Każdy news może zawierać dowolną ilość komentarzy.
Układ tabelek zostanie tak zoptymalizowany, aby jak najrzadziej zmuszać bazę do wykonywania funkcji COUNT() w celu zliczenia ilości wpisów.
Zacznijmy od tabeli kategorii:
CREATE TABLE `categories` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(40) NOT NULL,
`description` varchar(255) NOT NULL,
`news_num` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Oprócz typowych pól informacyjnych: title oraz description (tytuł i opis), mamy też pole news_num przechowujące aktualną liczbę newsów wewnątrz kategorii. Musimy pamiętać, aby nasz system poprawnie zmniejszał i zwiększał jego zawartość w trakcie edycji i wprowadzania nowych elementów do bazy.
Następne w kolejności są newsy:
CREATE TABLE `news` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(128) NOT NULL,
`body` text NOT NULL,
`date` int(11) NOT NULL,
`author` varchar(30) NOT NULL,
`category_id` int(11) NOT NULL,
`comment_num` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `category_id` (`category_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Tabela zaczyna się od pól informacyjnych. Z punktu widzenia projektanta bazy najistotniejsze są jednak dwa ostatnie. category_id przechowuje ID kategorii, do której należy news; w tym miejscu tworzymy naszą relację. comment_num działa na podobnej zasadzie, jak w kategoriach. Dzięki temu nie trzeba będzie wykonywać skomplikowanych zapytań przy pobieraniu listy newsów, aby pokazać tam jednocześnie ilość komentarzy.
Na samym końcu zapoznamy się z tabelą komentarzy:
CREATE TABLE `comments` (
`id` int(11) NOT NULL auto_increment,
`news_id` int(11) NOT NULL,
`author` varchar(30) NOT NULL,
`date` int(11) NOT NULL,
`body` text NOT NULL,
PRIMARY KEY (`id`),
KEY `news_id` (`news_id`,`date`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Zwróć uwagę, w jaki sposób założony jest podwójny indeks na pola news_id oraz date. Gdyby utworzyć tutaj dwa osobne indeksy, w zasadzie nic byśmy nie uzyskali. Popatrzmy na to tak: nigdy nie wyświetlamy wszystkich komentarzy, jakie mamy w bazie. Zawsze są one powiązane z jakimś konkretnym newsem, dlatego tak istotne jest odzwierciedlenie tego w strukturze indeksów. Przy osobnych indeksach pole date zostanie posortowane globalnie, a wybierając komentarze tylko dla pojedynczego newsa, system DB i tak będzie musiał przeskanować całą tabelę, aby je względem tejże daty wybrać.
Od strony bazy danych to tyle. Przejdźmy do kodowania.
[edytuj] Funkcje podstawowe
Napiszemy teraz plik functions.php. Umieścimy w nim różne podstawowe funkcje. W tej chwili jest ich stosunkowo niewiele: łączenie się z bazą, kontrola długości wprowadzonego tekstu, prymitywne zabezpieczenie przed floodem. Plik ten przyda Ci się jednak podczas samodzielnej rozbudowy; prawdopodobnie rozrośnie się wtedy znacznie. Jego zawartość wygląda następująco:
<?php
function initSystem()
{
global $sql;
$sql = new pdo('host=localhost;port=3305;dbname=artykuly', 'root', 'root');
$sql -> exec('SET NAMES `utf8`');
ob_start();
} // end initSystem();
function doneSystem()
{
ob_end_flush();
} // end doneSystem();
function commentsAllowed()
{
if(isset($_COOKIE['a84skljf']))
{
return false;
}
setcookie('a84skljf', 1, time() + 3600);
return true;
} // end commentsAllowed();
function validTextField($text, $min, $max)
{
if(strlen($text) > $min && strlen($text) < $max)
{
return true;
}
return false;
} // end validTextField();
?>
Opis poszczególnych funkcji:
- initSystem() - inicjacja systemu; nawiązuje połączenie z bazą danych i włącza buforowanie wyjścia.
- doneSystem() - aktualnie tylko kończy buforowanie wyjścia. Być może znajdziesz dla niej jakieś ciekawe dodatkowe zastosowania.
- commentsAllowed() - funkcja zwraca true, jeżeli internauta ma prawo dodawać komentarze i false, jeżeli już takowy niedawno dodał.
- validTextField() - prosta funkcja do kontroli danych. Zwraca true, jeżeli długość tekstu mieści się w podanym zakresie.
[edytuj] DAO
[edytuj] Kod HTML
[edytuj] Składamy system w całość
[edytuj] Ćwiczenia
[edytuj] Zakończenie
[edytuj] Bazy danych - co dalej?
Ostatnie rozdziały nauczyły nas nie tylko podstaw zarządzania i tworzenia baz danych, ale także odwoływania się do nich z poziomu PHP. Nie powinieneś jednak poprzestać tylko i wyłącznie na zawartych w niniejszym podręczniku informacjach. Ilu programistów, tyle możliwych organizacji danych na stronie WWW, a to musi znaleźć swe odzwierciedlenie w projekcie bazy danych. Zagadnieniu temu można poświęcić niejedną książkę, dlatego ćwicz i eksperymentuj, ile się da.
Generalnie, przymierzając się do zaprojektowania bazy danych dla jakiejś witryny, możemy skupić się albo na jak najlepszym dopasowaniu jej do wymagań, albo na elastyczności. Pierwsze podejście zazwyczaj nie wymaga tworzenia skomplikowanej architektury, a znajdujące się tam dane można względnie szybko odczytywać. Niestety, jakakolwiek zmiana lub dodanie nowych opcji pociąga za sobą konieczność modyfikacji bazy danych przez programistę, stąd też jest to wysoce niewydajne przy pisaniu pakietów wielokrotnego użytku. Odwrotna filozofia zakłada tworzenie bardziej uniwersalnych tabel - nie odpowiadają one dokładnie strukturze danych, które mają przechowywać, ale dzięki obecności dodatkowych pól kontrolnych, bardzo łatwo dopasowują się do zmian. Przykładem może być tutaj system CMS skoncentrowany na prezentowaniu w sieci informacji tekstowej w postaci artykułów, recenzji oraz opisów. W gruncie rzeczy te trzy rzeczy wymagają tego samego: pola na treść, tytułu, jakichś linków dodatkowych. Dlaczego więc nie utworzyć zbiorczej tabeli z dodatkowym polem typ? Przyjmujemy konwencję, że jeżeli zawiera ono wartość 0, mamy do czynienia z artykułem, gdy 1 - z recenzją itd. Od strony PHP możemy teraz niezwykle łatwo napisać jednolity panel do zarządzania nimi. Jeżeli w przyszłości klient zażyczy sobie dodania kolejnego rodzaju tekstu, sprowadzi się to do przydzielenia mu kolejnego numeru i dodania do formularzy edycyjnych.
Nie wszystkie informacje da się tak prosto przechowywać w bazie danych i trzeba mieć tego świadomość. Do odwzorowania za pomocą SQL-owych tabel hierarchicznego drzewa, do którego zawartości jest szybki dostęp, musimy zastosować pewne sztuczki oraz dodatkowe algorytmy. Innymi słowy, aby uzyskać to, co chcemy, czasem musimy ruszyć głową i wymyślić jakąś ciekawą koncepcję. W jej realizacji przydatne będą bardziej zaawansowane możliwości języka SQL takie, jak klauzule JOIN, unie, widoki czy wyzwalacze (triggery). Ich dokładny opis znajduje się w dokumentacji MySQL-a pod adresem http://dev.mysql.com/doc. W sieci znajdziemy także wiele artykułów wyjaśniających ich praktyczne zastosowanie.
Równie istotnym czynnikiem jest wydajność. Niektóre algorytmy korzystania z bazy danych oraz możliwości języka SQL są bardzo czasożerne i ich stosowanie na większych bazach mija się z celem. Nie wolno Ci myśleć kategoriami, że skoro działa, to jest dobrze. W środowisku testowym bazy liczą zazwyczaj po kilka-kilkanaście rekordów i nijak nie można z taką zawartością sprawdzić, czy w rzeczywistości witryna nie padnie klientowi przy tysiącu rekordów z powodu przeciążenia. Poprawianie niezoptymalizowanych, nieprzemyślanych baz jest trudnym procesem i na pewno narazi klienta na dodatkowe koszty. Analogiczne rozumowanie można przeprowadzić dla języka PHP.
Na zakończenie rozdziału o bazach danych, jeszcze raz podkreślamy: poznaliśmy dopiero podstawy pracy z tym narzędziem. Jego szersze omówienie przekracza możliwości tego podręcznika, dlatego nie poprzestawaj na zawartych tu informacjach. Ćwicz, eksperymentuj, nie bój się sięgać po Google i dodatkowe źródła. W następnej części podręcznika poznamy systemy szablonów.
[edytuj] Czym jest system szablonów?
Nastaje nowa era w dziejach. Koniec z umieszczaniem kodu HTML bezpośrednio w skryptach PHP. W tej części podręcznika zapoznasz się z systemami szablonów, które nie tylko pozwolą na odseparowanie tych dwóch elementów od siebie, ale również ułatwią wykonanie wielu dodatkowych zadań.
[edytuj] Idea systemu szablonów
Do tej pory wszystkie tworzone przez nas skrypty generowały zakodowany "na sztywno" w nich samych kod HTML. W przypadku większości zaprezentowanych tutaj przykładów nie stanowiło to zbyt dużego problemu, ale w większych projektach powstaje z tego powodu potężny zamęt. Gdyby przyszło nam wymieniać szatę graficzną, prawdopodobieństwo uszkodzenia jakiegoś algorytmu jest w takiej sytuacji spore.
System szablonów (ang. template engine albo templating engine) eliminuje tę niedogodność. Kod HTML jest przechowywany w osobnych, specjalnych plikach zwanych szablonami, w których odpowiednimi znacznikami oznaczone są miejsca, gdzie należy wstawić wyniki działania skryptu. Skrypt przetwarza dane, przekazuje je systemowi szablonów, a następnie wydaje polecenie przetworzenia żądanego szablonu. Parser ładuje szablon do pamięci, odnajduje w nim znaczniki, zamienia na dane ze skryptu, a rezultat wysyła do przeglądarki, gdzie internauta ogląda końcowy wynik. Wiele systemów szablonów nie poprzestaje na prostym osadzaniu danych - twórcy szablonów dostają do rąk zaawansowane narzędzia, niejednokrotnie o możliwościach normalnych języków programowania, a dodatkowe możliwości ułatwiają tworzenie np. list.
System szablonów to normalna biblioteka napisana w PHP. Niemal zawsze posiada ona zorientowane obiektowo API. Istnieje wiele gotowych oraz porządnych systemów szablonów. Część z przytoczonych poniżej zalet oraz wad jest domeną tylko niektórych z nich. W tym podręczniku omawiać będziemy tylko dwa systemy szablonów: Smarty oraz Open Power Template, na nich też poznamy praktyczne działanie tej kategorii skryptów.
[edytuj] Zalety
Zalety systemów szablonów:
- Porządek w kodzie - HTML oddzielony od PHP.
- Automatyzacja - wiele systemów szablonów udostępnia specjalne znaczniki, które w prosty sposób pozwalają zrealizować skomplikowane zadania.
- Inna filozofia pracy - niektóre rzeczy łatwiej jest zrobić za pomocą systemu szablonów (np. obsługa formularzy).
- System cache - zaawansowane systemy szablonów potrafią przechowywać w plikach cache generowane wyniki, co zmniejsza obciążenie serwera i umożliwia obsłużenie większego ruchu.
- Inna organizacja pracy aplikacji WWW - dzięki systemom szablonów, nietrudno jest zbudować skrypt, który najpierw przetworzy wszystkie niezbędne dane, a dopiero na samym końcu zajmie się generowaniem wyniku HTML. Przy okazji umożliwia to większą dynamiczność aplikacji. Zauważmy bowiem, że przy wymieszanym kodzie HTML i PHP to, co już wysłaliśmy, jest wysłane i w razie jakichś problemów musimy zaakceptować ten smutny fakt. W przypadku systemu szablonów wystarczy jedynie zmienić szablon.
- Webmaster nie musi znać PHP - w wielu firmach grafik oraz programista to dwaj zupełnie różni ludzie i nie jest wcale powiedziane, iż muszą oni znać nawzajem swoje profesje. Dla webmastera tworzącego kod HTML zawiłości PHP mogą być trudne do przeskoczenia. Warstwa abstrakcji w postaci systemu szablonów separuje go od tego, oddając w jego ręce łatwy do zrozumienia zestaw narzędzi.
[edytuj] Krytyka
Część programistów PHP (i nie tylko) krytykuje systemy szablonów. Najważniejsze z przedstawianych przez nich argumenty to:
- Najlepszym systemem szablonów jest sam PHP - część systemów szablonów, o dziwo, korzysta z tego argumentu w całkiem praktyczny sposób. Zamiast samodzielnie zajmować się obsługą pętli, zwyczajnie kompilują one szablony do postaci kodu PHP, a później wykonują prosty include.
- Mitem jest, że system szablonów ułatwia pracę webmasterom. Zamiast jednego języka programowania (PHP) dostajemy inny, tyle że o innej składni. - jest to smutna prawda dotycząca części systemów szablonów. Udostępniane przez nie instrukcje są jedynie kalką tradycyjnych pętli oraz konstrukcji z klasycznych języków programowania. W takim przypadku faktycznie - stosowanie systemów szablonów nie ma zbyt wielkiego sensu. Pamiętajmy jednak, że są pozytywne wyjątki mające szereg instrukcji ukrywających programowanie przed webmasterem.
- Systemy szablonów zmniejszają wydajność - dotyczy to tylko części systemów szablonów. Jak wspomnieliśmy wyżej, najwydajniejsze kompilują szablony do postaci kodu PHP, a przy kolejnych wejściach wywołują jedynie prosty include, uzyskując wydajność bardzo zbliżoną do zwykłego mieszania kodu HTML i PHP. Ponadto dużą rolę odgrywa tu odpowiednie z nich korzystanie. Nawet najlepszy parser, jeśli jest nieumiejętnie stosowany, może skutecznie "zarżnąć" serwer już przy małym obciążeniu.
[edytuj] Systemy, jakie poznamy
W podręczniku tym omówimy dwa systemy szablonów: Smarty oraz Open Power Template. Z pozoru opierają się na identycznym projekcie, ale w rzeczywistości prezentują nieco inne podejście do problemu szablonów. Zacznijmy od ich krótkiej charakterystyki.
[edytuj] Smarty
Smarty (TM) to jeden z najstarszych oraz bez wątpienia najpopularniejszy z dostępnych systemów szablonów, jego początki sięgają 2000 roku. Od tamtej pory przeszedł długą drogę, wzbogacony o nowe możliwości. Popularność oraz długa obecność sprawia dodatkowo, iż biblioteka jest bardzo stabilna i posiada bardzo małą liczbę błędów. Pomimo ogromnych możliwości, Smarty wygrywa wydajnościowo z wieloma mniej rozbudowanymi systemami szablonów. Dodatkowymi jego atutami są duża liczba materiałów pomocniczych (jego omówienie można znaleźć niemal w każdej bardziej zaawansowanej książce traktującej o PHP) oraz elastyczna architektura - łatwo rozszerzyć jego możliwości za pomocą rozszerzeń.
Smarty posiada obiektową architekturę, ale ze względu na konieczność zachowania kompatybilności, jest skryptem pisanym i zoptymalizowanym głównie pod kątem należącego już do przeszłości PHP4. Wielu programistów zarzuca mu także zbytnie upodobnienie się do języka programowania oraz spychanie instrukcji automatyzujących złożone zadania na dalszy plan. Zastrzeżenia wysuwane są także pod adresem stylu nazewnictwa metod oraz samej składni szablonów, jednak jest to już jedynie kwestia gustu.
Witryna Smarty: smarty.net
[edytuj] Open Power Template
Open Power Template to polski system szablonów zdobywający popularność głównie w naszym kraju. Jego początki datowane są na koniec 2004 roku, a obecny kształt przybrał na początku roku 2006. Jest on napisany oraz zoptymalizowany pod kątem PHP5 i nie działa na starszych wersjach. Głównym celem przyświecającym jego powstaniu było poprawienie i usunięcie wad systemu Smarty, stąd też pewna liczba podobieństw występujących między tymi systemami (najprostsze szablony mogą być bez żadnych zmian przetwarzane przez oba). W Open Power Template instrukcje typowe dla języków programowania mają jednak charakter drugoplanowy, a znacznie większy nacisk położony został na bardziej wysokopoziomowe instrukcje rugujące programowanie z szablonów. Wydajność tego systemu jest minimalnie wyższa niż w Smarty.
Open Power Template nie jest jednak tak popularny jak Smarty, co rzutuje na jakość dostępnych materiałów. Dokumentacja przechodzi okres poważnych zmian i natrafić można w niej na nieścisłości. Bardzo mało jest tekstów objaśniających wszystko krok po kroku, trudniej także uzyskać pomoc w przypadku napotkania problemów.
Open Power Template jest częścią większego projektu: Open Power Board. W jego ramach opracowywany jest także dodatek do OPT zwany Open Power Forms zajmujący się obsługą formularzy, zaawansowany system kontroli danych oraz wsparcie dla AJAX.
Witryna Open Power Template: opt.openpb.net
[edytuj] Smarty
Nasze praktyczne zmagania z systemami szablonów rozpoczniemy od biblioteki Smarty.
[edytuj] Instalacja
W przeciwieństwie do dotąd objaśnianych zestawów funkcji oraz rozszerzeń, Smarty napisany jest w całości w PHP, dlatego musimy samodzielnie przeprowadzić proces jego instalacji. Nie jest on jednak trudny. W praktyce sprowadza się on jedynie do skopiowania gdzieś plików i dołączenia do skryptu jednego z nich. Dokładna procedura opisana jest poniżej:
- Wchodzimy na stronę http://www.smarty.net/ i pobieramy stamtąd najnowszą dostępną wersję (w chwili pisania tego tekstu - 2.6.16).
- Zakładamy, że nasze skrypty są w katalogu kursphp. Tworzymy w nim nowy podkatalog, np. smarty .
- Otwieramy ściągnięte archiwum i kopiujemy do naszego nowostworzonego katalogu zawartość folderu libs.
- Aby załadować bibliotekę do naszej aplikacji, dołączamy plik Smarty.class.php.
Zanim zaczniemy, musimy jeszcze utworzyć dwa dodatkowe katalogi:
- /templates - tu trzymać będziemy nasze szablony. PHP musi mieć uprawnienia do odczytu.
- /templates_c - aby zwiększyć wydajność, Smarty wpierw kompiluje każdy szablon do postaci kodu PHP, a dopiero później go wykonuje. Raz skompilowany kod jest przechowywany na HDD w tym właśnie katalogu. Programista nie powinien tam nic grzebać - po prostu należy przydzielić dla PHP prawa do zapisu i nic więcej.
[edytuj] Pierwszy skrypt
Szablon to nic innego, jak plik z kodem HTML, który zawiera dodatkowe znaczniki określające, gdzie mają pojawić się dane ze skryptu. Nasz pierwszy szablon (szablon1.tpl) wygląda następująco:
<html>
<head>
<title>Smarty: pierwszy skrypt</title>
</head>
<body>
<p>Hello world! Dzisiaj jest {$data}!</p>
</body>
</html>
{$data} - ten fragment oznacza, że w tym miejscu ma pojawić się zawartość zmiennej $data. (Możliwe jest jednak zmiana systemu oznaczenia wprowadzania zmiennych Smarty. Są to dwa pola z klasy smarty z pliku libs/Smarty.class.php. var $left_delimiter, oraz var $right_delimiter) Jednak uważaj - to, że w szablonie stosujemy taką zmienną nie znaczy wcale, że jeżeli stworzymy sobie w naszym skrypcie analogiczną, to ją nam w tym miejscu wyświetli. Nie ta bajka. Zmienne wewnątrz szablonów i zmienne PHP są dwiema zupełnie osobnymi rzeczami. Zmienne szablonowe należy utworzyć odpowiednią funkcją i przypisać im jakąś wartość ze skryptu. Popatrzmy więc, jak się to robi:
<?php
require('./smarty/Smarty.class.php'); // 1
$tpl = new Smarty; // 2
$tpl -> template_dir = './templates/';
$tpl -> compile_dir = './templates_c/';
$tpl -> assign('data', date('d.m.Y')); // 3
$tpl -> display('szablon1.tpl'); //4
?>
Opis skryptu (szablon1.php):
- Na początku ładujemy bibliotekę.
- Smarty ma budowę obiektową. Aby rozpocząć zabawę z szablonami, musimy utworzyć obiekt klasy Smarty i go skonfigurować. Najprostsza konfiguracja polega na określeniu ścieżek do katalogów z szablonami oraz ich skompilowanymi wersjami, ale dyrektyw jest znacznie więcej.
- Tutaj ustawiamy, jaką wartość ma mieć szablonowa zmienna $data.
- Kiedy wszystkie dane przenieśliśmy już do parsera, możemy nakazać mu przetworzenie konkretnego szablonu.
Po uruchomieniu powyższego skryptu zobaczysz, że na ekranie przeglądarki pojawił się napis "Hello world! Dzisiaj jest (tu aktualna data)!"
[edytuj] Więcej o zmiennych
Smarty to coś więcej, niż zwykłe umieszczanie danych w kodzie HTML. W zasadzie po stronie szablonów mamy do dyspozycji całkiem rozbudowany język programowania obsługujący m.in. tablice i obiekty z PHP, a także rozmaite formy manipulacji danymi. Załóżmy, że mamy jakąś listę wiadomości, jednak z oczywistych przyczyn na stronie głównej pragniemy wyświetlić jedynie nagłówki i kilkanaście początkowych wyrazów. Możemy cały mechanizm przetwarzania zrealizować po stronie PHP, lecz z tym różnie bywa, zwłaszcza gdy kod PHP do pobierania listy wykorzystywany jest jeszcze w paru innych miejscach, gdzie taka właściwość jest niewskazana. To jednak nie problem, ponieważ możemy odpowiednie przycinanie dodać bezpośrednio w szablonie, który tego wymaga.
<?php
require('./smarty/Smarty.class.php');
$tpl = new Smarty;
$tpl -> template_dir = './templates/';
$tpl -> compile_dir = './templates_c/';
$tpl -> assign('wiadomosc', array(
'tytul' => 'Premier podaje się do dymisji!',
'data' => date('d.m.Y'),
'autor' => 'Jan Nowak',
'tresc' => 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
Cras nec diam. In hac habitasse platea dictumst. Donec id leo. Ut
feugiat augue at metus. In hac habitasse platea dictumst. Donec
pulvinar sollicitudin tellus. Quisque mattis faucibus nulla. Praesent
in mauris. Maecenas erat nisi, laoreet in, porta nec, varius ut, turpis.
Suspendisse pretium nibh at tellus placerat venenatis. Vestibulum
ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia
Curae; Etiam felis arcu, ringilla a, commodo quis, blandit id, est.
Fusce nec sapien nec libero dignissim volutpat.'
));
$tpl -> display('szablon2.tpl');
?>
Od strony skryptu za wiele nowego nie ma, poza faktem przypisywania do zmiennej szablonowej całej tablicy z danymi. Zwróćmy uwagę na długość oryginalnej wiadomości.
<html>
<head>
<title>SmartyNews!</title>
</head>
<body>
<h3>{$wiadomosc.tytul}</h3>
<p>Napisał {$wiadomosc.autor} dnia {$wiadomosc.data}</p>
<p>{$wiadomosc.tresc|truncate:200:"..."}</p>
</body>
</html>
W tym przykładzie odwołujemy się do poszczególnych wartości w tablicy za pomocą kropki, po której podajemy nazwę indeksu. Smarty oferuje także alternatywną składnię, zbliżoną bardziej do PHP: $wiadomosc[tytul]. W ostatniej zmiennej, pojawiają się jeszcze dodatkowe, tajemne znaki. Jest to tzw. modyfikator i służy, jak nazwa wskazuje, do końcowej obróbki danych, najczęściej związanej bezpośrednio z procesem wyświetlania. Tutaj nakazujemy przyciąć długość wiadomości do ok. 200 znaków z zaokrągleniem do pełnych słów. Jeżeli wiadomość faktycznie była dłuższa, niż nakazujemy, na jej końcu mają być wstawione trzy kropki.
[edytuj] Sekcje
W systemie Smarty sekcja jest jednym z rodzajów pętli. Najczęściej wykorzystuje się ją do tworzenia wszelkiego rodzaju list. Pokażemy teraz, jak wykorzystać omawianą bibliotekę do wygenerowania listy newsów, a następnie jak połączyć system szablonów oraz bazy danych, w których wykorzystujemy relację jeden do wielu.
Zacznijmy od listy. Tak wygląda szablon HTML listy newsów:
<html>
<head>
<title>SmartyNews!</title>
</head>
<body>
{section name=i loop=$newsy}
<h3>{$newsy[i].tytul}</h3>
<p>Dnia {$newsy[i].data}</p>
<p>{$newsy[i].tresc|truncate:200:"..."}</p>
{/section}
</body>
</html>
Zwróćmy uwagę na dwa znaczniki: otwierający {section name=i loop=$newsy} oraz zamykający {/section}. Cały kod HTML między nimi jest powtarzany w kółko i służy za szablon pojedynczego newsa. W znaczniku otwierającym sekcję podajemy dwa parametry. Pierwszy z nich, name, określa nazwę sekcji. Identycznie będzie też nazywać się jej iterator wskazujący, na którym elemencie aktualnie jesteśmy. Drugi parametr to loop. Podajemy w nim nazwę zmiennej, w której mieści się tablica z danymi (możemy też podać cyfrę, np. loop=10 będzie oznaczało, że pętla wykona się 10 razy). Aby odwołać się np. do tytułu newsa w obrębie sekcji, stosujemy składnię {$newsy[i].tytul}, czyli najpierw nazwa zmiennej, w nawiasie kwadratowym podajemy iterator i po kropce dopiero nazwę zmiennej wewnętrznej, której wartość jest unikalna dla każdego elementu listy.
Tablica z danymi musi być w odpowiedni sposób wygenerowana po stronie PHP. Zobaczymy teraz skrypt, który się tym zajmuje.
<?php
require('./smarty/Smarty.class.php');
$tpl = new Smarty;
$tpl -> template_dir = './templates/';
$tpl -> compile_dir = './templates_c/';
$newsy = array();
for($i = 0; $i < 5; $i++)
{
$newsy[] = array(
'tytul' => 'Tytuł wiadomości',
'data' => date('d.m.Y'),
'tresc' => 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
Cras nec diam. In hac habitasse platea dictumst. Donec id leo. Ut
feugiat augue at metus. In hac habitasse platea dictumst. Donec
pulvinar sollicitudin tellus. Quisque mattis faucibus nulla. Praesent
in mauris. Maecenas erat nisi, laoreet in, porta nec, varius ut, turpis.
Suspendisse pretium nibh at tellus placerat venenatis. Vestibulum
ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia
Curae; Etiam felis arcu, ringilla a, commodo quis, blandit id, est.
Fusce nec sapien nec libero dignissim volutpat.'
);
}
$tpl -> assign('newsy', $newsy);
$tpl -> display('szablon3.tpl');
?>
Widzimy tutaj, że tablica $newsy zawiera mniejsze tablice asocjacyjne przypisujące konkretnym zmiennym wewnętrznym odpowiednie wartości. Następnie przekazujemy ją do parsera metodą assign(). Jako ćwiczenie spróbuj utworzyć taką tablicę, pobierając jej zawartość z bazy danych.
Sekcje można zagnieżdżać, dzięki czemu możliwe jest wyświetlanie danych w określonym porządku, np. kategorii oraz przypisanych do każdej z nich produktów. Wróćmy się do naszej bazy danych biblioteki z rozdziału o PHP Data Objects. Spróbujemy wyświetlić jej zawartość na ekranie, korzystając z pakietu Smarty. Jest to nieco trudniejsze, niż w wypadku zwykłych, płaskich list, ale również wykonalne. Rozpocznijmy od szablonu:
<html>
<head>
<title>Biblioteka</title>
</head>
<body>
<ul>
{section name=i loop=$kategorie}
<li>{$kategorie[i].nazwa} <ul>
{section name=j loop=$ksiazki[i]}
<li>{$ksiazki[i][j].nazwa}</li>
{/section}
</ul></li>
{/section}
</ul>
</body>
</html>
Zwróć uwagę, że w podrzędnej sekcji dotyczącej książek, musimy "podpiąć się" pod sekcję nadrzędną, aby Smarty wiedział, w jaki sposób powiązane są ich elementy. Odwołując się do rekordów książek, musimy podać w nawiasach kwadratowych najpierw iterator kategorii, a później książek.
Od strony PHP musimy przygotować dwie listy: po jednej dla kategorii i książek, z tym że druga musi uwzględniać istnienie dwóch indeksów identyfikujących rekordy. Przypomnij sobie, w jaki sposób wiązaliśmy te elementy w rozdziale poświęconym PDO - wykorzystaliśmy tam po prostu ID kategorii jako element wiążący, jednak w tym przypadku jest to niemożliwe. Gdybyśmy bowiem skasowali jakiś rekord, powstałaby dziura w numeracji, w którą sekcja z pewnością by się zaplątała, wyświetlając w tym miejscu pusty rekord. Musimy zatem użyć innej techniki. Zaproponujemy teraz nieco inne rozwiązanie tego problemu. Dane dla kategorii będziemy pobierać tak, jak poprzednio, natomiast dane książek posortujemy w pierwszej kolejności według nazw kategorii, a dopiero później według ich własnych tytułów. Zauważmy, że dzięki temu mamy zagwarantowane, że książki znajdujące się w tej samej kategorii znajdą się na liście wyników obok siebie. Teraz wystarczy podczas pobierania utrzymywać dwie zmienne: $i jako iterator oraz $kid - ID ostatniej znanej kategorii. Zanim wprowadzimy dane rekordu do tablicy, sprawdzamy prosty warunek: jeżeli $kid jest różne od identyfikatora kategorii, do której należy dana książka, to znaczy, że przeszliśmy już do książek z następnej kategorii i musimy w tym celu zwiększyć $i o 1 tak, aby dane te trafiły do kolejnego rekordu. Dodatkowo zapamiętujemy ID nowej kategorii. Oto ilustracja tego algorytmu w kodzie PHP:
<?php
require('./smarty/Smarty.class.php');
$tpl = new Smarty;
$tpl -> template_dir = './templates/';
$tpl -> compile_dir = './templates_c/';
$pdo = new PDO('mysql:host=localhost;dbname=podrecznik_php', 'root', 'root');
$kategorie = array();
$ksiazki = array();
$stmt = $pdo -> query('SELECT id, nazwa, il_ksiazek FROM kategorie ORDER BY nazwa');
while($row = $stmt -> fetch())
{
$kategorie[] = $row;
}
$stmt -> closeCursor();
unset($stmt);
$stmt = $pdo -> query('SELECT x.id, x.nazwa, k.id AS `kategoria_id` FROM ksiazki x, kategorie k
WHERE k.id = x.kategoria_id ORDER BY k.nazwa, x.nazwa');
$i = -1;
$kid = 0;
while($row = $stmt -> fetch())
{
if($row['kategoria_id'] != $kid)
{
$i++;
$kid = $row['kategoria_id'];
}
$ksiazki[$i][] = $row;
}
$stmt -> closeCursor();
$tpl -> assign('kategorie', $kategorie);
$tpl -> assign('ksiazki', $ksiazki);
$tpl -> display('szablon4.tpl');
?>
Sekcje w systemie Smarty mają duże możliwości i poznanie ich wszystkich wykracza poza ramy tego podręcznika. Więcej przykładów ich wykorzystania można znaleźć w dokumentacji biblioteki.
[edytuj] Instrukcje warunkowe
Smarty posiada także instrukcję warunkową if, dzięki której można testować różne warunki i wyświetlać fragmenty kodu HTML warunkowo. Wróćmy do naszej listy newsów. Dodamy do każdego jej elementu nową zmienną: news_dnia. Jeżeli będzie ona ustawiona na 1, oznacza to, że mamy do czynienia z newsem dnia i wypadałoby to jakoś specjalnie zaznaczyć. Aby nie komplikować kodu, przyjmiemy w przykładzie, że newsem dnia jest pierwszy z elementów:
<?php
require('./smarty/Smarty.class.php');
$tpl = new Smarty;
$tpl -> template_dir = './templates/';
$tpl -> compile_dir = './templates_c/';
$newsy = array();
for($i = 0; $i < 5; $i++)
{
$newsy[] = array(
'tytul' => 'Tytuł wiadomości',
'data' => date('d.m.Y'),
'tresc' => 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
Cras nec diam. In hac habitasse platea dictumst. Donec id leo. Ut
feugiat augue at metus. In hac habitasse platea dictumst. Donec
pulvinar sollicitudin tellus. Quisque mattis faucibus nulla. Praesent
in mauris. Maecenas erat nisi, laoreet in, porta nec, varius ut, turpis.
Suspendisse pretium nibh at tellus placerat venenatis. Vestibulum
ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia
Curae; Etiam felis arcu, ringilla a, commodo quis, blandit id, est.
Fusce nec sapien nec libero dignissim volutpat.',
'news_dnia' => ($i == 0 ? 1 : 0)
);
}
$tpl -> assign('newsy', $newsy);
$tpl -> display('szablon5.tpl');
?>
I czas na szablon:
<html>
<head>
<title>SmartyNews!</title>
</head>
<body>
{section name=i loop=$newsy}
<h3>{$newsy[i].tytul}</h3>
{if $newsy[i].news_dnia eq 1}
<p><strong>News dnia!</strong></p>
{/if}
<p>Dnia {$newsy[i].data}</p>
<p>{$newsy[i].tresc|truncate:200:"..."}</p>
{/section}
</body>
</html>
Instrukcję warunkową tworzą znaczniki {if warunek} ... {/if}. Za warunek uznawane jest dowolne poprawne wyrażenie - jeżeli idzie Ci układanie takowych po stronie PHP, również w Smartym nie będziesz mieć z nimi problemów. Jedynie trzeba przyzwyczaić się do nowych operatorów. Biblioteka preferuje użycie tekstowych zamienników, aczkolwiek symboliczny zapis z PHP także jest dozwolony. Oto lista najważniejszych operatorów:
| Smarty | PHP |
|---|---|
| eq | == |
| neq, ne | != |
| gt | > |
| lt | < |
| ge, gte | >= |
| le, lte | <= |
| not | ! |
[edytuj] Dostęp do zmiennych sesyjnych
Z poziomu Smarty możemy mieć dostęp do zmiennych sesyjnych, które ustawiliśmy sobie z poziomu skryptów PHP. Dostęp do tych zmiennych odbywa się przez odwołanie do tablicy $smarty.session, np. $smarty.session.login_name.
Jak można to praktycznie wykorzystać, to już zupełnie inna sprawa. Przykładem może być tu proces logowania i nadawania uprawnień tj. podczas logowania tworzymy w zmiennej sesyjnej obraz uprawnień nadanych użytkownikowi, a potem już z poziomu szablonu, bez konieczności przekazywania za każdym razem przez metodę assign(), możemy sterować tym co pokazujemy użytkownikowi:
<?php // to jest power-user ustawiamy to $_SESSION['prawa_zapisu'] = 1; ?>
a potem w szablonie
{if $smarty.session.prawa_zapisu}
kod tylko dla power-usera
{/if}
W analogiczny sposób można odwoływać się do innych rodzajów danych wejściowych. Wartości pól formularza zapisane są w $smarty.post, z adresu URL w $smarty.get. Istnieje też możliwość dostania się do ciastek: $smarty.cookie. Znowu jednak sprawy bezpieczeństwa ... pod żadnym pozorem nie wolno na produkcyjnych, publicznych serwerach używać odwołań do typu $smarty.post czy $smarty.get ponieważ to zaproszenie do ataków XSS.
[edytuj] Zakończenie
Biblioteka Smarty ma dużo większe możliwości, niż są w stanie pomieścić założenia tego podręcznika. W sieci znaleźć można dużo artykułów na temat tego systemu szablonów, także w języku polskim. Oprócz przetwarzania szablonów, Smarty posiada również zaawansowany moduł cache'owania wyników, szczególnie przydatny na stronach z dużym ruchem.
Przejdziemy teraz do omówienia alternatywnej biblioteki Open Power Template.
[edytuj] Open Power Template
Zapoznamy się teraz z polskim systemem szablonów Open Power Template reprezentującym nieco inne podejście do pracy z szablonami, niż Smarty oraz napisanym specjalnie dla PHP5.
[edytuj] Instalacja
Najnowszą wersję można ściągnąć ze strony projektu: [3] w postaci spakowanego archiwum. Zawiera ono kilka katalogów:
- /configurator - narzędzie do konfigurowania OPT (tylko w wersji 1.0.x)
- /docs - angielska dokumentacja w formacie PDF (tylko w wersji 1.0.x)
- /examples - przykładowe skrypty demonstrujące różne możliwości OPT
- /lib - źródła biblioteki. Ten katalog interesuje nas najbardziej i to jego zawartość należy dołączać do naszych projektów korzystających z OPT.
- /toolset - pakiet narzędziowy do OPT od wersji 1.1.x
- /unitTest - zestaw testów sprawdzających poprawność działania kompilatora oraz całej biblioteki. Aby go uruchomić, wymagany jest skrypt PHPUnit 1.3
Instalacja OPT jest bardzo prosta. Kopiujemy zawartość katalogu /lib do drzewa katalogowego naszego projektu. W kodzie naszych skryptów musimy ustawić stałą OPT_DIR pokazującą położenie plików biblioteki, a następnie dołączyć plik opt.class.php. Ścieżka podana w stałej powinna być zakończona ukośnikiem.
<?php
define('OPT_DIR', '../includes/opt/');
require(OPT_DIR.'opt.class.php');
?>
Musimy także stworzyć katalogi, w których będziemy przechowywać szablony:
- /templates - pliki źródłowe
- /templates_c - katalog na użytek OPT, w którym będzie on umieszczać sobie skompilowane wersje. PHP musi posiadać prawa zapisu w tym katalogu.
Jeżeli zamierzamy w obrębie folderu /templates tworzyć podkatalogi, musimy także stworzyć je w /templates_c.
[edytuj] Pierwszy skrypt
Zanim rozpoczniemy, konieczne będzie pewne wyjaśnienie. OPT używa nieco innej terminologii, niż Smarty. To, co w poprzednim systemie zwaliśmy zmienną szablonową, tutaj będzie blokiem (zmienne też istnieją, lecz rozpoczynają się znakiem małpy i są tworzone przez szablon, a nie przez programistę). Podobnych różnic jest nieco więcej i będziemy je stopniowo wyjaśniać.
Napiszemy teraz pierwszy skrypt korzystający z OPT do przetwarzania szablonów:
<?php
define('OPT_DIR', '../lib/'); // 1
require(OPT_DIR.'opt.class.php'); // 2
try
{
$tpl = new optClass; // 3
$tpl -> root = './templates/'; // 4
$tpl -> compile = './templates_c/';
$tpl -> gzipCompression = 1;
$tpl -> httpHeaders(OPT_HTML); // 5
$tpl -> assign('current_date', date('d.m.Y')); // 6
$tpl -> parse('szablon1.tpl'); // 7
}
catch(optException $exception) // 8
{
optErrorHandler($exception);
}
?>
Jak widać, skrypt startowy jest znacznie bardziej rozbudowany, aniżeli ten ze Smarty'ego. Wynika to ze specyfiki nowego systemu. Oto opis działania:
- Ustawiamy stałą OPT_DIR ze ścieżką do plików biblioteki.
- Ładujemy główny plik biblioteki.
- Tworzymy obiekt klasy optClass.
- Tutaj konfigurujemy OPT. Podobnie, jak w Smarty, może to się odbywać poprzez bezpośrednie ustawianie odpowiednich dyrektyw. root zawiera ścieżkę do katalogu ze źródłami szablonów, natomiast compile - ścieżkę do katalogu dla skompilowanych wersji. gzipCompression włącza automatyczną kompresję generowanego kodu HTML, o ile przeglądarka takową obsługuje. Pozwala to przyspieszyć ładowanie strony, jednak na serwerze musi być zainstalowane rozszerzenie zlib.
- OPT posiada także prosty menedżer nagłówków HTTP. W tym przypadku ustawiamy, że generujemy dokument HTML (nagłówek text/html). Do dyspozycji mamy również stałe OPT_XHTML - informacja o wysyłaniu dokumentu XHTML, o ile przeglądarka obsługuje odpowiedni nagłówek oraz o ile użytkownik ustawił sobie odpowiedni priorytet. W przeciwnym razie wysyłany jest nagłówek text/html. OPT_FORCE_XHTML zawsze wymusza wysłanie nagłówków XHTML-a, o ile tylko przeglądarka je rozpoznaje.
- Analogicznie, jak w Smarty, przypisujemy dane do bloków metodą assign().
- Wysyłamy żądanie parsowania szablonu szablon1.tpl. Wygenerowany kod zostanie automatycznie wysłany do przeglądarki.
- W OPT wszystkie błędy obsługiwane są przez mechanizm wyjątków, które musimy przechwycić. Możemy sami napisać sobie ich obsługę, lub skorzystać z gotowej, zawartej w funkcji optErrorHandler().
Nasz szablon (szablon1.tpl) wygląda następująco:
<html>
<head>
<title>Mój pierwszy skrypt w OPT</title>
</head>
<body>
<p>Hello world! Dzisiaj jest {$current_date}!</p>
</body>
</html>
Zauważ, że w OPT bloki osadzamy identycznie, jak w Smarty, domyślnie otaczając je nawiasami klamrowymi oraz poprzedzając nazwę znakiem dolara. Po wykonaniu skryptu ujrzymy w przeglądarce stronę zatytułowaną "Mój pierwszy skrypt w OPT" oraz posiadającą zawartość, np.:
Hello world! Dzisiaj jest 28.12.2006!
Zwykłe osadzanie bloków to nie jedyna operacja, jaką można przeprowadzić wewnątrz nawiasów klamrowych. Ponieważ OPT mimo wszystko z tradycją programistyczną nie zrywa, faktycznie można umieszczać w nich niemal dowolny rodzaj wyrażeń. Poniżej zaprezentujemy skrypt, który będzie wyświetlał statystyki odwiedzin pewnego serwisu. Dla uproszczenia pominiemy na razie wszystkie rzeczy związane z bazą danych i ilości użytkowników wpiszemy do skryptu "na sztywno".
<?php
define('OPT_DIR', '../lib/'); // 1
require(OPT_DIR.'opt.class.php'); // 2
function getActiveUsers()
{
// funkcja zwraca liczbe aktywnych uzytkownikow
return 367;
}
function getInactiveUsers()
{
// funkcja zwraca liczbe nieaktywnych uzytkownikow
return 62;
}
try
{
$tpl = new optClass;
$tpl -> root = './templates/';
$tpl -> compile = './templates_c/';
$tpl -> gzipCompression = 1;
$tpl -> httpHeaders(OPT_HTML);
$tpl -> assign('active_users', getActiveUsers());
$tpl -> assign('inactive_users', getInactiveUsers());
$tpl -> parse('statystyka.tpl');
}
catch(optException $exception)
{
optErrorHandler($exception);
}
?>
W skrypcie definiujemy dwa bloki: active_users oraz inactive_users, zatem te informacje może wyświetlić szablon. Załóżmy teraz, że szef chce widzieć także łączną liczbę użytkowników, ponieważ ma trudności z (szybkim) sumowaniem liczb większych, niż 10. Niektóre aplikacje mogą być tak złożone, że odnalezienie tych dwóch linijek w setkach kilobajtów kodu może być ciężkie - w końcu najpierw trzeba poprawić szablon, a potem zmodyfikować skrypt, by przesyłał także i trzeci blok. Jeśli zadania są podzielone między webmastera i programistę, trzeba nawet zaangażować cały zespół do poszukiwań. Jednak jak wspomnieliśmy, wykorzystamy tutaj coś więcej, niż tylko zwykłe osadzanie bloków, po prostu wykonując niezbędne sumowanie po stronie szablonu (dodajmy, że podobną rzecz można zrealizować też w Smarty):
<html>
<head>
<title>Statystyki serwisu: użytkownicy</title>
</head>
<body>
<p>Aktywnych użytkowników: {$active_users}</p>
<p>Nieaktywnych użytkowników: {$inactive_users}</p>
<p>Łącznie użytkowników: {$active_users + $inactive_users}</p>
</body>
</html>
Po wykonaniu skryptu otrzymamy następujący rezultat:
Aktywnych użytkowników: 367
Nieaktywnych użytkowników: 62
Łącznie użytkowników: 429
Choć OPT także przenosi do szablonów cały język programowania, należy traktować to jednak z dystansem jako pewien dodatek, kiedy inne możliwości zawodzą. W rzeczywistości przy pracach nad rozmaitymi serwisami wykorzystuje się jedynie część potencjału biblioteki, zostawiając całą logikę i przetwarzanie danych skryptom, a w szablonie umieszczając to, co szablon powinien zawierać, czyli opis, jak osadzić wygenerowane dane w kodzie HTML.
OPT pozwala na proste osadzenie wewnątrz szablonów list, a od wersji 1.1.0 także hierarchicznych struktur (drzew) oraz wstawek systemu dzielenia wyników na strony. Ponadto, jeśli tworzymy aplikację z wielojęzycznym interfejsem, możemy ją łatwo zintegrować z OPT, dzięki czemu nie będzie konieczne ręczne przenoszenie wszystkich komunikatów językowych ze skryptu do szablonów.
Ćwiczenie 1: Napisz skrypt, który najpierw wyświetla formularz, w którym pyta się o imię, nazwisko, stanowisko oraz adres e-mail osoby odwiedzającej, a po jego wysłaniu wyświetla wpisane dane w postaci ładnej wizytówki. Do wyświetlania kodu HTML użyj wyłącznie systemu szablonów Open Power Template.
Ćwiczenie 2: Rozszerz skrypt wizytówki o sprawdzanie poprawności wpisanych danych. Jeżeli są błędne, wyświetl odpowiedni komunikat.
Ćwiczenie 3: Usprawnij system raportowania błędów w skrypcie wizytówek. Jeżeli użytkownik źle wypełnił formularz, powinien zostać on przeładowany wraz z wpisaną przez niego zawartością oraz odpowiednimi komunikatami wyświetlonymi pod błędnie wypełnionymi polami.
| Porada W ćwiczeniach wykorzystaj fakt, że możesz bez żadnych konsekwencji tworzyć w szablonach bloki, którym nie przypisujesz w skrypcie żadnej wartości. |
[edytuj] Tworzenie list
OPT również posiada znacznik określany mianem sekcji i również używa się go do tworzenia wszelkiego rodzaju list, lecz w tym systemie szablonów jego użycie wymaga znacznie mniej pracy. Spróbujmy napisać tutaj jeszcze raz skrypt do wyświetlania listy newsów z poprzedniego rozdziału, aby się o tym przekonać. Rozpoczniemy od kodu PHP, który generalnie nie ulega żadnym modyfikacjom - tu także musimy wygenerować identyczną tablicę z danymi i przekazać ją do parsera w identyczny sposób z użyciem metody assign().
<?php
define('OPT_DIR', '../lib/');
require(OPT_DIR.'opt.class.php');
try
{
$tpl = new optClass;
$tpl -> root = './templates/';
$tpl -> compile = './templates_c/';
$tpl -> gzipCompression = 1;
$tpl -> httpHeaders(OPT_HTML);
$newsy = array();
for($i = 0; $i < 5; $i++)
{
$newsy[] = array(
'tytul' => 'Tytuł wiadomości',
'data' => date('d.m.Y'),
'tresc' => 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
Cras nec diam. In hac habitasse platea dictumst. Donec id leo. Ut
feugiat augue at metus. In hac habitasse platea dictumst. Donec
pulvinar sollicitudin tellus. Quisque mattis faucibus nulla. Praesent
in mauris. Maecenas erat nisi, laoreet in, porta nec, varius ut, turpis.
Suspendisse pretium nibh at tellus placerat venenatis. Vestibulum
ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia
Curae; Etiam felis arcu, ringilla a, commodo quis, blandit id, est.
Fusce nec sapien nec libero dignissim volutpat.'
);
}
$tpl -> assign('newsy', $newsy);
$tpl -> parse('szablon3.tpl');
}
catch(optException $exception)
{
optErrorHandler($exception);
}
?>
A tak będzie prezentować się szablon HTML pozwalający tę właśnie listę wyświetlić:
<html>
<head>
<title>OPT News!</title>
</head>
<body>
{section=newsy}
<h3>{$newsy.tytul}</h3>
<p>Dnia {$newsy.data}</p>
<p>{$newsy.tresc}</p>
{/section}
</body>
</html>
OPT nie wymaga podawania żadnych iteratorów itd. - to, jak dokładnie wszystko jest obsługiwane od strony technicznej, jest już wewnętrzną sprawą biblioteki. Naszym zadaniem jest jedynie określenie nazwy sekcji równoznacznej w tym wypadku z nazwą bloku, w którym mieści się tablica z danymi. Do poszczególnych bloków sekcji odwołujemy się, podając najpierw nazwę samej sekcji, a później po kropce nazwę informacji, którą chcemy w danym miejscu wyświetlić.
Analogicznie sprawa ma się z zagnieżdżaniem sekcji. Tutaj również przepiszemy na OPT skrypt obsługi biblioteki z poprzedniego rozdziału. O dziwo, także i teraz generowanie tablic z danymi odbywa się dokładnie tak samo.
<?php
define('OPT_DIR', '../lib/');
require(OPT_DIR.'opt.class.php');
try
{
$tpl = new optClass;
$tpl -> root = './templates/';
$tpl -> compile = './templates_c/';
$tpl -> gzipCompression = 1;
$tpl -> xmlsyntaxMode = 1;
$tpl -> httpHeaders(OPT_HTML);
$pdo = new PDO('mysql:host=localhost;dbname=podrecznik_php', 'root', 'root');
$kategorie = array();
$ksiazki = array();
$stmt = $pdo -> query('SELECT id, nazwa, il_ksiazek FROM kategorie ORDER BY nazwa');
while($row = $stmt -> fetch())
{
$kategorie[] = $row;
}
$stmt -> closeCursor();
unset($stmt);
$stmt = $pdo -> query('SELECT x.id, x.nazwa, k.id AS `kategoria_id` FROM ksiazki x, kategorie k
WHERE k.id = x.kategoria_id ORDER BY k.nazwa, x.nazwa');
$i = -1;
$kid = 0;
while($row = $stmt -> fetch())
{
if($row['kategoria_id'] != $kid)
{
$i++;
$kid = $row['kategoria_id'];
}
$ksiazki[$i][] = $row;
}
$stmt -> closeCursor();
$tpl -> assign('kategorie', $kategorie);
$tpl -> assign('ksiazki', $ksiazki);
$tpl -> parse('szablon4.tpl');
}
catch(optException $exception)
{
optErrorHandler($exception);
}
?>
Szablon:
<html>
<head>
<title>Biblioteka</title>
</head>
<body>
<ul>
<opt:section name="kategorie"> {* 1 *}
<li>{$kategorie.nazwa} <opt:show name="ksiazki"><ul> {* 2 *}
<opt:section> {* 3 *}
<li>{$ksiazki.nazwa}</li> {* 4 *}
</opt:section>
</ul></opt:show>
</li>
</opt:section>
</ul>
</body>
</html>
Chwila, a cóż to się porobiło ze składnią? Nic - to jest dalej jeden i ten sam OPT. Rzuć na chwilę okiem na skrypt PHP, gdzie podczas konfigurowania pojawiła się dyrektywa xmlsyntaxMode. Dzięki niej włączamy tzw. tryb kompatybilności z XML, który udostępnia alternatywną składnię znaczników, stojącą w zgodzie z językiem XML. Naturalnie OPT nie sprawdza tak ściśle, czy użycie poszczególnych wzorców pasuje do reguł języka XML i nie ma żadnego problemu, aby rozpocząć sekcję znacznikiem XML-owym, a skończyć tradycyjnym, ograniczonym nawiasami klamrowymi. Dodajmy, że styl parametrów nazwa="wartość" dostępny jest zawsze i można go używać zamiennie z listami parametrów rozpoczynających się od znaku równości.
Przejdźmy teraz do samego szablonu:
- Tutaj rozpoczynamy sekcję kategorii.
- Instrukcja show jest rozwinięciem sekcji i tu też podajemy jej nazwę. Sprawdza ona, czy do sekcji przypisane są jakieś elementy, co pozwala na niewyświetlenie znaczników <ul> i </ul>, kiedy nie są one z racji braku takowych potrzebne.
- Nazwa sekcji została już podana w instrukcji show i tu już nie trzeba jej podawać.
- Oto odwołanie się do bloków wchodzących w skład opisu pojedynczej książki. Podajemy nazwę interesującej nas sekcji, a później blok wewnątrz niej. Alternatywnie moglibyśmy napisać $kategorie.ksiazki.nazwa, ale dla OPT nie robi to absolutnie żadnej różnicy.
Zwróćmy uwagę na fakt, że OPT z góry "wie", jakie są zależności pomiędzy danymi, dzięki czemu w tym systemie szablonów nie trzeba martwić się o iteratory. Znacznie poprawia to przenośność kodu. Fragment szablonu wyświetlający listę książek można wyciąć i przenieść w inne miejsce, używając go np. do wyświetlenia wszystkich książek, bez podziału na kategorie, a nie wymagałoby to absolutnie żadnych zmian. Powstaje jednak pytanie, czy w ten sposób nie ograniczamy naszych możliwości? W końcu teoretycznie ręczne określanie wszystkich parametrów pozostawia w naszych rękach większą kontrolę. Odpowiedź brzmi: nie. Dłuższa praktyka pokazuje, że paradoksalnie nasze możliwości w ten sposób rosną. OPT zezwala na przekazywanie danych do sekcji na cztery różne sposoby (część z nich jest dostępna po włączeniu odpowiednich dyrektyw konfiguracyjnych). Dzięki wprowadzeniu takiej separacji, kod tej samej sekcji bez żadnych zmian może w jednym miejscu pracować w oparciu o jeden format danych, a w innym o drugi. Biblioteka sama zajmuje się technicznymi aspektami ich obsługi.
[edytuj] Rendering drzew
[edytuj] Interfejs wielojęzyczny
[edytuj] Zakończenie
Na OPT kończymy poznawanie możliwości oferowanych przez systemy szablonów. W następnym rozdziale zatytułowanym PHP/Sztuczki dowiemy się, jak korzystać z systemów szablonów do osiągania różnych rezultatów. Podamy tam konkretne wskazówki, jak za pomocą szablonów tworzyć dynamiczne formularze oraz jak składać design strony z kilku szablonów. W studium przypadku przerobimy napisany wcześniej system newsów z wykorzystaniem obu poznanych systemów szablonów. PHP/Sztuczki PHP/Studium przypadku/System newsów na Smarty PHP/Studium przypadku/System newsów na OPT
[edytuj] Klasy i obiekty
Podstawy programowania obiektowego poznaliśmy już w rozdziale "Wstęp do programowania obiektowego", ponieważ potrzebowaliśmy ich do prawidłowego korzystania z PDO oraz systemów szablonów. Teraz nauczymy się o nim wszystkiego, co tylko jest możliwe i pokażemy, jak prawidłowo wykorzystywać je w codziennej praktyce programistycznej.
Przypomnijmy, czego nauczyliśmy się do tej pory:
- Klasa jest elementem opisowym. Informuje PHP, jakie dane mogą przechowywać obiekty jej rodzaju oraz jakie operacje mogą wykonywać.
- Obiekt jest fizycznym, namacalnym bytem, przechowującym konkretne informacje i mogącym wykonywać na nich różne operacje. Każdy obiekt pochodzi od jakiejś klasy, która te operacje dokładnie definiuje.
- Elementami do przechowywania danych w obiektach są pola.
- Metody, lub inaczej funkcje wewnętrzne, opisują możliwe operacje do wykonania na polach obiektu.
- Obiekt konkretnej klasy tworzymy poleceniem new konkretnaKlasa;, np. $obiekt = new konkretnaKlasa;
- Do pól oraz metod obiektu odwołujemy się "operatorem" ->, np. $obiekt -> pole, $obiekt -> metoda().
- W PHP5 obiekt nie jest równoznaczny ze zmienną. Zmienna obiektowa przechowuje jedynie odnośnik do konkretnego obiektu, stąd wyrażenie $obiekt2 = $obiekt1; nie utworzy nam kopii obiektu!
- W PHP4 obiekt był jednocześnie zmienną obiektową.
[edytuj] Tworzenie klas
Do tej pory nastawieni byliśmy na korzystanie z gotowych obiektów, natomiast tworzenie własnych było potraktowane powierzchownie. Teraz skupimy się właśnie na tym elemencie.
Klasę deklarujemy słowem kluczowym class, po którym podajemy jej unikalną nazwę. W nawiasach klamrowych umieszczamy informacje o dozwolonych polach klasy oraz metodach, jakie będzie ona posiadać.
<?php
class osoba
{
public $imie;
public $nazwisko;
public function ustawPersonalia($imie, $nazwisko)
{
$this -> imie = $imie;
$this -> nazwisko = $nazwisko;
} // end ustawPersonalia();
public function personalia()
{
return $this -> imie.' '.$this -> nazwisko;
} // end personalia();
}
$osoba = new osoba; // 4
$osoba -> ustawPersonalia('Adam', 'Kowalski');
echo $osoba -> personalia();
?>
Możemy teraz stworzyć różne obiekty klasy osoba. Metodą ustawPersonalia() ustawiamy im imiona i nazwiska, natomiast personalia() pozwala pobrać sformatowane personalia przypisane do danego obiektu. Oczywiście w tym wypadku istnieje możliwość odwołania się np. do imienia bezpośrednio, np. $osoba -> imie. Zauważmy, że tworzenie metod nie różni się szczególnie od pisania własnych funkcji. Różnice to słowo kluczowe public na początku oraz specjalny wskaźnik $this pozwalający na odwołanie się do obiektu, na którym uruchomiliśmy metodę.
[edytuj] Kontrola dostępu (Hermetyzacja)
Zajmiemy się teraz wyjaśnieniem, co to tajemnicze słowo public oznacza. Jedną z zalet programowania obiektowego jest tzw. hermetyzacja, czyli ukrywanie przed programistą implementacji klas. Wiedza o tym, w jaki sposób obiekty działają, nie jest mu do niczego potrzebna i nawet nie jest wskazane, by ingerował on w wewnętrzne dane obiektu. Dzięki kontroli dostępu można zablokować mu dostęp do niektórych pól lub metod tak, że będą mogły zmieniać ich wartość jedynie inne metody.
Jeśli pole/metodę poprzedzimy słowem kluczowym public, PHP zapewni do niej swobodny dostęp, tj. będą się mogły odwoływać doń zarówno metody danej klasy, jak i programista korzystający z danego obiektu (np. $obiekt -> metoda()). Możemy także użyć słowa kluczowego private, które ograniczy dostęp wyłącznie do innych metod klasy (czyli poprzez $this->metoda(), napisanie $obiekt->metoda() będzie niemożliwe). Przepiszmy powyższy przykład tak, aby programista nie widział pól imie oraz nazwisko.
<?php
class osoba
{
private $imie;
private $nazwisko;
public function ustawPersonalia($imie, $nazwisko)
{
$this -> imie = $imie;
$this -> nazwisko = $nazwisko;
} // end ustawPersonalia();
public function personalia()
{
return $this -> imie.' '.$this -> nazwisko;
} // end personalia();
}
$osoba = new osoba; // 4
$osoba -> ustawPersonalia('Adam', 'Kowalski');
echo $osoba -> personalia();
?>
Działa on dalej, ponieważ nie odwołujemy się do prywatnych pól spoza obiektu. Zobaczmy, co się stanie przy próbie nielegalnego dostępu:
<?php
class osoba
{
private $imie;
private $nazwisko;
public function ustawPersonalia($imie, $nazwisko)
{
$this -> imie = $imie;
$this -> nazwisko = $nazwisko;
} // end ustawPersonalia();
public function personalia()
{
return $this -> imie.' '.$this -> nazwisko;
} // end personalia();
}
$osoba = new osoba; // 4
$osoba -> ustawPersonalia('Adam', 'Kowalski');
echo $osoba -> imie;
?>
Ten skrypt już się nie wykona. Na ekranie ujrzymy komunikat:
Fatal error: Cannot access private property osoba::$imie
Jako prywatne możemy także oznaczyć niektóre metody, zaznaczając w ten sposób, że służą one wyłącznie do prywatnego użytku danej klasy i programista nie powinien ich samodzielnie wywoływać.
[edytuj] Praktyczne zastosowanie OOP
Do tej pory operowaliśmy głównie na różnych klasach typu osoba, które nie mają zbyt wiele wspólnego z pisaniem prawdziwych aplikacji. Dlatego pragniemy już teraz wyjaśnić, w jaki sposób można wykorzystać OOP w praktyce. Z kilkoma takimi przykładami mieliśmy już do czynienia. Konkretniej mamy na myśli bibliotekę do komunikacji z bazą danych - PHP Data Objects, a także systemy szablonów Smarty i Open Power Template. W przypadku PDO każdy obiekt reprezentował tam pojedyncze połączenie z bazą danych z określonym na nim zestawem operacji. Mamy też obiekty zbioru wyników zwracane przez metody PDO::prepare() oraz PDO::query() reprezentujące wyniki danego zapytania. W obu systemach szablonów utrzymywaliśmy obiekty $tpl, dzięki którym mogliśmy manipulować szablonami. Zauważmy, że zebranie wszystkich danych oraz metod parsera w klasę ułatwia przepływ danych oraz ukrywanie tych właściwości, których programista nie powinien widzieć.
Ogólna zasada jest taka, że klasami czynimy poszczególne elementy funkcjonalne składające się na aplikację WWW:
- Połączenie z bazą danych
- System szablonów
- System zarządzania konfiguracją
- Poszczególne akcje, jakie można wykonać na danej podstronie.
- Sesje
- I wiele innych...
Rodzi się tu kolejne pytanie: na co tworzyć całą klasę, by później utworzyć jeden jej obiekt? Przecież identyczny efekt osiągniemy, wykorzystując tablice i funkcje. Odpowiedź leży w tym, że poznaliśmy dopiero ułamek możliwości programowania obiektowego. Między klasami można utworzyć znacznie bardziej złożone interakcje, a dzięki temu, że taki model programowania jest bardziej zbliżony do naturalnego sposobu myślenia, prościej projektuje się też kompletną aplikację. Popatrz na to w ten sposób: czemu mamy ograniczać się do tworzenia systemu konfiguracji dostosowanego do jednej konkretnej sytuacji? A jeśli w jakimś miejscu potrzebny by nam był podsystem, który czerpie dane z innego źródła? Zamiast tworzyć dwie osobne klasy robiące to samo, zaprojektujmy bardziej ogólną, która za parametr pobierać będzie obiekt reprezentujący jakieś źródło danych. Oto przykład:
<?php
class config
{
private $dataSources = array();
private $data = array();
private $modified = false;
public function setDatasource($ds)
{
$name = get_class_name($ds);
$this -> dataSources[$name] = $ds;
$this -> data[$name] = $ds -> getData();
} // end setDatasource();
public function get($id)
{
foreach($this -> data as &$values)
{
if(isset($values[$id]))
{
return $values[$id];
}
}
return NULL;
} // end get();
public function set($id, $value)
{
foreach($this -> data as &$values)
{
if(isset($values[$id]))
{
$values[$id] = $value;
$this -> modified = true;
}
}
} // end set();
public function save()
{
if($this -> modified)
{
foreach($this -> dataSources as $name => $ds)
{
$ds -> saveData($this -> data[$name]);
}
}
} // end save();
}
class fileResource
{
private $fn;
public function setFilename($fn)
{
$this -> fn = $fn;
} // end setFilename();
public function getData()
{
return parse_ini_file($this -> fn);
} // end save();
public function saveData($data)
{
$code = '';
foreach($data as $id => $value)
{
$code .= $id.' = "'.$value."\"\r\n";
}
file_put_contents($this -> fn, $code);
} // end saveData();
}
class databaseResource
{
public function getData()
{
global $sql;
$result = array();
$stmt = $sql -> query('SELECT name, value FROM `config` ORDER BY `name`');
while($row = $stmt -> fetch())
{
$result[$row['name']] = $row['value'];
}
$stmt -> closeCursor();
return $result;
} // end save();
public function saveData($data)
{
global $sql;
$stmt = $sql -> prepare('UPDATE `config` SET `value` = :value WHERE `name` = :name');
foreach($data as $name => $value)
{
$stmt -> bindValue(':name', $name, PDO::PARAM_STR);
$stmt -> bindValue(':value', $value, PDO::PARAM_STR);
$stmt -> execute;
}
} // end saveData();
}
// uzycie
$pdo = new PDO('mysql:host=localhost;dbname=strona', 'root', 'root');
// tworzymy pusta klase konfiguracyjna
$cfg = new config;
$file = new fileResource;
$file -> setFilename('../config/konfiguracja.php');
// teraz mamy dostepna konfiguracje wczytana z pliku
$cfg -> setDatasource($file);
echo $cfg -> get('page_title').'<br/>';
// teraz mamy takze dostepna konfiguracje wczytana z bazy!
$cfg -> setDatasource(new databaseResource);
echo $cfg -> get('page_timezone').'<br/>';
$cfg -> save(); // jesli zaszly zmiany w konfiguracji, mozemy je zapisac
?>
Do uruchomienia przykładu potrzebny jest nam jeszcze plik konfiguracyjny:
; <?php die(); ?> na wypadek, gdyby ktos z przegladarki sprobowal go zalaczyc page_title = "Tytuł strony" page_address = "http://localhost/"
A także tabelę w bazie danych:
CREATE TABLE `config` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(40) NOT NULL,
`value` VARCHAR(255),
PRIMARY KEY(`id`),
UNIQUE(`name`)
);
INSERT INTO `config` (`name`, `value`) VALUES('page_timezone', 'ETC/GMT+2');
Uruchom przykład i sprawdź, czy działa, jak należy. Powinny pokazać się wartości dwóch dyrektyw konfiguracyjnych, jednej wczytanej z pliku, a drugiej z bazy.
Przyjrzyjmy się teraz dokładniej sposobowi funkcjonowania naszego mechanizmu konfiguracyjnego. Mamy klasę config, poprzez którą możemy odczytywać i modyfikować dyrektywy konfiguracyjne. Jednak sama w sobie jest ona bezużyteczna, ponieważ nie potrafi znikąd pobrać stosownych danych, ani ich z powrotem zapisać. Jej funkcjonalność określamy, dodając do niej jeden lub więcej obiektów klas fileResource, databaseResource czy jeszcze innych, jakie sami napiszemy. Obiekt jest reprezentowany przez zmienną, dlatego w bardzo prosty sposób można go przekazywać jako parametr funkcji, składować w tablicach i później przepuszczać pętlą foreach. Jeżeli do klasy config dodamy 10 różnych obiektów źródeł danych konfiguracyjnych, wtedy klasa główna będzie skanować 10 źródeł w poszukiwaniu stosownej dyrektywy, jakiej żądamy. Jeśli dodamy pięć - wtedy pięciu. Co więcej, nie jesteśmy w żaden sposób ograniczeni przez projektanta systemu. Jeśli mielibyśmy jakieś nietypowe życzenie (np. udostępnienie jakichś ważnych danych z innego obiektu poprzez system konfiguracji), musimy jedynie stworzyć sobie nową klasę posiadającą metody getData() oraz saveData(), a jej obiekt dodać do konfiguracji. Zauważ, jak bardzo jest to zgodne z intuicją. Jeśli jakiś obiekt ma mieć większe możliwości, dołączamy do niego, jak klocki, mniejsze obiekty, które pozwalają mu na wykonanie dodatkowych czynności. Kod jest niezwykle przenośny i można bez trudu zaadaptować go do nowych zastosowań.
[edytuj] Zakończenie
Mamy już solidne podstawy programowania obiektowego, a także pokazaliśmy, w jaki sposób wykorzystuje się jego własności podczas tworzenia oskryptowania stron internetowych, pisząc modularny i łatwy w rozbudowie system konfiguracji. W następnym rozdziale pokażemy, jak sterować tworzeniem i niszczeniem obiektów.
[edytuj] Konstruktory i destruktory
Metody klas nie muszą być wywoływane wyłącznie przez programistę tworzącego dany skrypt. Istnieje pewna grupa metod, które są wywoływane automatycznie przez interpreter w momencie zajścia jakiegoś zdarzenia - metody takie nazywamy magicznymi, a w PHP możemy poznać je po tym, że ich nazwy rozpoczynają się od dwóch podkreśleń: __.
Pierwszymi magicznymi metodami, jakie poznamy, będą konstruktor i destruktor, wywoływane odpowiednio w momencie tworzenia oraz niszczenia obiektu.
[edytuj] Konstruktor
Konstruktor jest metodą o nazwie __construct(), która może pobierać parametry, lecz nie wolno jej zwracać wartości. Jej zadaniem jest zainicjowanie nowoutworzonego obiektu. Wracając do przykładu z klasami reprezentującymi osoby, można je przepisać następująco:
<?php
class osoba
{
public $imie;
public $nazwisko;
public function __construct($imie, $nazwisko)
{
$this -> ustawPersonalia($imie, $nazwisko);
} // end __construct();
public function ustawPersonalia($imie, $nazwisko)
{
$this -> imie = $imie;
$this -> nazwisko = $nazwisko;
} // end ustawPersonalia();
public function personalia()
{
return $this -> imie.' '.$this -> nazwisko;
} // end personalia();
}
$osoba = new osoba('Adam', 'Kowalski');
echo $osoba -> personalia();
?>
Teraz personalia osoby możemy ustawić zarówno stosowną metodą, jak i w momencie tworzenia obiektu, ponieważ stworzyliśmy konstruktor, który pobiera imię i nazwisko i kieruje je do metody ustawPersonalia(). Zauważmy, że w takim wypadku przekazujemy te parametry tuż przy nazwie klasy tak, jakby to była funkcja. Jeśli konstruktor nie pobiera żadnych parametrów, możemy (acz nie musimy) pominąć nawiasy:
// wywołanie konstruktora, który nie pobiera parametrów $obiekt = new klasa; $obiekt = new klasa();
Ćwiczenie: W poprzednim rozdziale podaliśmy zbudowany na OOP system konfiguracji. Jedną z klas wchodzących do zestawu była fileResource. Dodaj do niej konstruktor, który pobiera nazwę z plikiem konfiguracyjnym tak, aby można go było podać już w momencie tworzenia obiektu. Przerób kod korzystający z tego zestawu klas, aby korzystał z konstruktora.
[edytuj] Destruktor
Destruktor zaś nazywa się __destruct(). Nie może on ani pobierać parametrów, ani zwracać jakichkolwiek wartości. Wywoływany jest w momencie niszczenia obiektu (czyli sytuacji, gdy nie ma już żadnych odwołań do obiektu). Tłumacząc praktycznie:
<?php
class MojaKlasa
{
public $zmienna;
public function __construct($zmienna)
{
$this -> zmienna = $zmienna;
}
public function __destruct()
{
echo $this -> zmienna;
}
}
$klasa = new MojaKlasa('tekst');
$klasa -> zmienna = 'innytekst';
?>
Powyższy przykład wyświetli napis innytekst. Jak widać, destruktor jest wywoływany jako ostatni. Ta właściwość przydaje się np. przy zamykaniu połączeń z bazą danych.
[edytuj] Zakończenie
[edytuj] Dziedziczenie klas
Pisząc obiektowy kod, prędzej (lub później) zajdzie potrzeba pisania klas wg tego samego planu - tym samym powtarzania kilka razy tego samego kodu. Spójrzmy na poniższy przykład:
<?php
class Lilia
{
public $czy_podlany = false;
public function nazwa()
{
echo 'Lilia';
}
public function podlej()
{
$this -> czy_podlany = true;
echo 'Kwiat zostal podlany';
}
}
class Roza
{
public $czy_podlany = false;
public function nazwa()
{
echo 'Roza';
}
public function podlej()
{
$this -> czy_podlany = true;
echo 'Kwiat zostal podlany';
}
}
?>
Jak widać, bardzo duża część kodu została napisana dwa razy. Żeby uniknąć takich sytuacji, programiści dostali mechanizm dziedziczenia.
[edytuj] Czym jest dziedziczenie?
Dziedziczenie oznacza przejmowanie pól i metod przez klasę dziedziczącą. Przykładowo, jeśli rodzic (klasa, której pola i metody dziedziczymy) ma metodę Nazwa(), klasa potomna (dziedzicząca) też będzie posiadać tę metodę, mimo że przeglądając jej kod w edytorze nie znajdziemy jej deklaracji.
[edytuj] Implementacja dziedziczenia
Aby zaznaczyć, że dana klasa jest podklasą innej, stotsujemy słowo kluczowe extends. Umieszczamy je po nazwie klasy, a przed klamrą otwierającą ciało klasy. Po słowie extends wpisujemy nazwę klasy, z której chcemy dziedziczyć. Wracając do naszych kwiatków:
<?php
class Kwiat
{
public $czy_podlany = false;
public function podlej()
{
$this -> czy_podlany = true;
echo 'Kwiat zostal podlany';
}
}
class Lilia extends Kwiat
{
public function nazwa()
{
echo 'Lilia';
}
}
// Teraz swobodnie tworzymy obiekt klasy Lilia
$lilia = new Lilia();
// Uzywamy metod z klasy Lilia
$lilia -> nazwa();
// Jak i klasy Kwiat
$lilia -> podlej();
?>
Jak widać, $lilia posiada pola i metody zarówno klasy Kwiat, jak i Lilia.
[edytuj] Unieważnianie
Załóżmy jednak, że mamy nieco inną sytuację - zarówno podklasa, jak i rodzic posiadają metodę o tej samej nazwie:
<?php
class A
{
public function nazwij()
{
echo 'Jestem klasa A';
}
}
class B extends A
{
public function nazwij()
{
echo 'Jestem klasa B';
}
}
// Tworzymy obiekty
$a = new A();
$b = new B();
// I wywolujemy nazwij()
$a -> nazwij();
$b -> nazwij();
?>
Otrzymamy 'Jestem klasa AJestem klasa B'. Jak widać, klasa dziedziczy metody pod warunkiem, że nie posiada swoich własnych metod o takiej nazwie. Podobnie sprawa się ma z polami:
<?php
class A
{
public $nazwa = 'A';
public function nazwij()
{
echo 'Jestem klasa ' . $this -> nazwa;
}
}
class B extends A
{
public $nazwa = 'B';
}
// Tworzymy obiekty
$a = new A();
$b = new B();
// I wywolujemy nazwij()
$a -> nazwij();
$b -> nazwij();
?>
Efekt będzie taki sam, jak w poprzednim przykładzie.
Czasem możemy jednak nie chcieć unieważniania metod. Wtedy potrzebujemy użyć słowa kluczowego final. Oznacza ono, że klasa, bądź metoda, jest zadeklarowana jako wersja ostateczna, i nie może zostać nadpisana. Jakakolwiek próba unieważnienia metody finalnej (bądź dziedziczenia klasy finalnej) kończy się komunikatem o błędzie.
[edytuj] Kontrola nad dostępem a dziedziczenie
Przypomnijmy sobie słowa kluczowe private i public. Pamiętamy ich znaczenie, prawda? A jak się zachowają one przy dziedziczeniu metod i pól? Public zapewne nic nie zmieni - w końcu oznacza 'dostęp dla każdego'. A private? Private oznacza całkowity zakaz wstępu. Nie wchodzić z zewnątrz, nie dziedziczyć. Zatem brakuje nam rozwiązania pośredniego: brak dostępu z zewnątrz, ale zezwolenie na dziedziczenie. Na szczęście developerzy PHP przewidzieli taką sytuację - wprowadzili słowo kluczowe protected. Oznacza dokładnie tyle, co 'można dziedziczyć, ale nie można użyć spoza klasy'.
<?php
class Parent
{
public function a()
{
echo 'A';
}
protected function b()
{
echo 'B';
}
}
class Child extends Parent
{
public function c()
{
$this -> b();
}
}
// Tworzymy obiekty
$class = new Parent();
echo $class -> a(); // Zadziala (zwroci: A)
echo $class -> b(); // Wyrzuci blad - brak dostepu
$child = new Child();
echo $child -> a; // Zadziala (zwroci: A)
echo $child -> b; // Blad - patrz wyzej
echo $child -> c; // Zadziala (zwroci: B)
?>
[edytuj] Klasy i metody abstrakcyjne
Znów wróćmy do naszej klasy kwiat. Załóżmy, że potrzebujemy w każdym obiekcie kwiatu metody nazwa(). Ale klasa Kwiat nie posiada takiej metody. Jeśli więc nie chcemy pozwolić na tworzenie obiektów klasy Kwiat, ale chcemy, by inne klasy z niej dziedziczyły, możemy przerobić klasę Kwiat na klasę abstrakcyjną:
<?php
abstract class Kwiat
{
public $czy_podlany = false;
abstract public function nazwa();
public function podlej()
{
$this -> czy_podlany = true;
echo 'Kwiat zostal podlany';
}
}
?>
Jak widać, aby utworzyć abstrakcyjną klasę, przed słowem kluczowym class trzeba umieścić inne słowo kluczowe - abstract. Teraz próba stworzenia obiektu z klasy Kwiat zakończy się komunikatem błędu. Ale słowo abstract może odnosić się też do metod. Cóż to oznacza? Oznacza to, że klasa dziedzicząca musi zawierać taką samą metodę (z takimi samymi parametrami). Nie wpisujemy natomiast ciała metody - ono jest niezależne w każdej podklasie.
[edytuj] Wielodziedziczenie
[edytuj] Zakończenie
PHP/Interfejsy PHP/Wyjątki PHP/Iteratory PHP 5 dodało nowe metody do klas. Metody __toString, __call, __get i __set są obecnie testowane, dają wiele możliwości, lecz jeszcze nie wiadomo czy tak naprawdę się przyjmą. Istotną zmianą jest również sposób przekazywania obiektów. Obecnie są one zawsze przekazywane przez referencję. Stąd wersje 4 i 5 nie są do końca kompatybilne.
__toString() metoda bezparametrowa, bardzo przydatna, jeżeli chcemy mieć łatwy dostęp do danych zawartych w obiekcie.
<?php
class TestToString
{
private $nazwa = "";
public function __construct($nazwa)
{
$this->nazwa = $nazwa;
}
public function __toString()
{
return $this->nazwa;
}
}
$obiekt = new TestToString("Obiekt nr 1");
echo $obiekt;
?>
__construct() konstruktor, metoda wywoływana podczas tworzenia obiektu. Parametry podane w tej metodzie będą wymagane podczas tworzenia obiektu – (w PHP 3 i 4 konstruktorem była metoda nazywająca się tak samo jak nazwa klasy)
__destruct() zdarzenie destruktora, to zdarzenie wykonuje się przed zniszczeniem obiektu
__call() metoda dwuparametrowa, pierwszy parametr to nazwa metody a drugi to jej parametry, metoda wykonuje się w czasie, gdy operujemy na metodzie nieznajdującej się w obiekcie.
__get() metoda jednoparametrowa, jej parametr to nazwa pola, którego nie ma w obiekcie, a była próba odczytania jej wartości
__set() metoda dwuparametrowa, gdzie pierwszy parametr to nazwa pola a drugi to wartość przypisana, metoda ta wykonywana jest w momencie, gdy chcemy przypisać wartość do pola, które nie zostało zawarte w obiekcie.
__clone() metoda wykonywana podczas klonowania obiektu (tzw. konstruktor kopiujący), daje nam możliwość zmodyfikowania sklonowanego obiektu, metodę warto użyć, jeżeli potrzebujemy kopii obiektu i w kopii od razu chcemy przeprowadzić jakąś niewielką zmianę.
__unset() metoda uruchamiana podczas gdy używamy unset() na właściwości obiektu.
__isset() metoda uruchamiana podczas gdy używamy isset() na właściwości obiektu. PHP/Studium przypadku/Hackowanie PDO
[edytuj] Dlaczego nie piszemy ciurkiem?
Istotnym mankamentem większości skryptów oraz aplikacji tworzonych przez początkujących jest wyraźny brak elastyczności. Aby dodać jakąkolwiek nową podstronę, praktycznie wszystko trzeba budować niemal od zera. Odpowiada za to nieprzykładanie wagi do projektowania. Programista po prostu siada i zaczyna pisać. Czy w ten sposób można napisać aplikację godną uwagi? Nic bardziej mylnego! W rzeczywistości, im więcej logicznych części składowych wyróżnimy oraz oddzielimy od siebie, tym łatwiej będzie nam wprowadzać nawet poważne zmiany, a także znacznie szybciej tworzyć kod. Przecież po to twórcy PHP oddali nam do dyspozycji całe programowanie obiektowe, funkcje i wiele innych udogodnień. Przyjrzyjmy się prostemu przypadkowi.
Na stronie mamy skrypt wyświetlający listę wiadomości i dzielący je na strony. Jest on napisany ciurkiem, bez żadnego podziału na funkcje oraz obiekty, a kod HTML jest wymieszany z PHP. Problemy pojawiają się praktycznie przy każdej próbie modyfikacji:
- Zmiana wyglądu
- Zmiana struktury bazy
- Zmiana wynikająca z konieczności aktualizacji PHP
- Zbudowanie nowej podstrony na bazie już istniejącej
Dwa środkowe punkty, o ile zajdzie taka sytuacja, będzie trzeba zastosować do wszystkich plików, co w przypadku dużego serwisu jest zadaniem bardzo czasochłonnym i nieefektywnym. Programista musi spędzić całe dnie, aby poprawić każdy z elementów z osobna. Można sobie jedynie wyobrażać, co by się stało, gdyby o czymś zapomniał. Gdyby poświęcił na samym początku choćby kilka dni na zaprojektowanie rozszerzalnego szkieletu strony, znacznie skróciłby sobie pracę. Oto, jakie modyfikacje mógłby tu wprowadzić.
Pierwszą rzeczą jest oddzielenie kodu HTML od PHP za pomocą szablonów. W ten sposób unikniemy ewentualnych uszkodzeń algorytmów przy zmianie wyglądu. Wszystko odbędzie się w trzymanych osobno szablonach. Następnym krokiem jest wprowadzenie jednolitego oraz wieloznacznego nazewnictwa, aby przyszykować grunt na programowanie metodą kopiuj-wklej, skądinąd całkiem pożyteczną.
Programista może zapytać się, jak w praktyce wygląda wyświetlanie na stronie typowej listy. Czy można tu wyróżnić jakieś stałe elementy warte wyszczególnienia? Utworzenie listy polega na wysłaniu zapytania, pobraniu wyników przy jednoczesnej ich obróbce, wstawieniu do systemu szablonów i przetworzeniu jednego z nich. Z pomocą programowania obiektowego stwórzmy zatem dla każdego "zasobu", jaki mamy na stronie (artykuły, newsy, pliki itd.) osobną klasę z tymi samymi metodami, np. pobierz(), dodaj(). Wywołanie każdej z nich spowoduje wykonanie określonej operacji dla takiego zasobu, jaki ma zaprogramowany klasa przechowywana w naszym obiekcie. Do osobnej klasy da się także przenieść sam algorytm dzielenia na strony, konfigurowany przy wywoływaniu odpowiedniej komendy. Po wprowadzeniu wszystkich tych uproszczeń nasz kod skróci się do prostego
$zasob = new news;
$stronicowanie = new stronicowanie(30, $news -> ilosc(), DOKUMENT_PHP);
$tpl -> assign('lista', $zasob -> pobierz($stronicowanie));
$tpl -> parse(DOKUMENT.'_index.tpl');
Zasoby są uniwersalne. Metodę pobierz() można wywoływać także w kodzie panelu administracyjnego. Teraz, jeżeli chcemy dodać do witryny np. listę przydatnych linków, napisać do niej odpowiednią klasę zasobu i zdublować powyższy kod, zmieniając w nim jedynie nazwę klasy tworzonego obiektu oraz stałe. Dzięki "fizycznemu" oddzieleniu wszystkich elementów oraz wprowadzeniu stałych, trwa to zaledwie parę minut. Równie prosto możemy utworzyć do tego sekcję panelu administracyjnego. W ten oto sposób odpowiednia architektura przyspiesza pracę programisty oraz zwiększa jego wydajność.
Dobra architektura przydaje się szczególnie przy pracach nad wieloma projektami. W rzeczywistości wcale nie trzeba zaczynać nad każdym z nich pracy od zera. Mając wystarczająco elastyczny kod możemy skopiować całość starego projektu, pozmieniać w nim wygląd, dodać żądane opcje, usunąć błędy i na tym koniec. Jest to zdecydowanie mniej czasochłonne, niż pisanie po raz kolejny mechanizmu autoryzacji, logowania, wyświetlania tekstów.
W tym rozdziale zajmiemy się technikami tworzenia dobrych oraz elastycznych aplikacji WWW w języku PHP. Wiele rozwiązań jest już wymyślonych i tylko czeka na zastosowanie. Jednak pamiętaj, żeby nie przesadzać z kolei w drugą stronę. Każda z technik ma swoje zalety, ale ma i też pewne wady. Im więcej chcemy ich zastosować, tym dłużej będziemy pracowali nad samym silnikiem, a końcowy efekt wcale nie musi być zadowalający. Paradoksalnie, implementowanie wzorców projektowych, MVC i innych technik wszędzie, gdzie to jest możliwe, prowadzi także do niepotrzebnego skomplikowania kodu, nie przekładając się ani trochę na wzrost wydajności pracy, a powodując wyłącznie obciążenie interpretera. Umiejętność wyczucia, kiedy potrzebujemy elastycznego, a kiedy po prostu działąjącego rozwiązania, przychodzi z doświadczeniem.
Wspominamy o zagrożeniach, ponieważ część programistów zafascynowana światem technik projektowych zaczyna implementować je dosłownie wszędzie, bez patrzenia, czy rzeczywiście każda z nich jest im potrzebna. Tak jak w przypadku innych rzeczy, są one dobre, ale tylko do pewnej granicy, której przekraczanie wiąże się z ryzykiem.
Następny rozdział zapozna Cię z pojęciem wzorca projektowego oraz kilkoma podstawowymi technikami projektowania. PHP/Wzorce projektowe
[edytuj] Problem konfiguracji
Wraz ze wzrostem wielkości naszej aplikacji zachodzi konieczność stworzenia centralnego systemu zarządzania danymi konfiguracyjnymi. Powinniśmy przenieść do niego możliwie jak najwięcej informacji, aby ułatwić sobie ewentualną modyfikację całego serwisu, jeśli zajdzie taka potrzeba. Wyróżniamy dwa podstawowe elementy wymagające nieco innego podejścia do problemu.
[edytuj] Konfiguracja bibliotek
Niektóre biblioteki aplikacji oraz ustawienia połączenia z bazą danych muszą być przechowywane w plikach tekstowych na serwerze. Musimy zadbać o to, aby nikt niepowołany nie mógł ich odczytać z użyciem przeglądarki. Nie trzeba pisać własnych parserów pliku konfiguracyjnego, ponieważ język PHP posiada odpowiednie narzędzia.
Pierwszym sposobem jest stworzenie zwyczajnego pliku PHP, np. config.php zawierającego deklaracje kilku zmiennych albo stałych z naszymi ustawieniami:
<?php
define('DB_DSN', 'mysql:host=localhost;dbname=baza');
define('DB_USER', 'uzytkownik');
define('DB_PASSWORD', 'haslo');
?>
Rozwiązanie to ma bardzo prostą zaletę: jeżeli potencjalny włamywacz wpisze adres do tego pliku w przeglądarce, ujrzy jedynie pustą stronę. Jednak ewentualna modyfikacja i zarządzanie taką konfiguracją może nastręczyć problemy. Jeżeli popełnimy jakiś błąd i wygenerujemy zły kod PHP, nie będziemy mieli żadnej możliwości przechwycenia komunikatu. Ujrzymy po prostu Parse error.
PHP posiada także funkcję parse_ini_file() przetwarzającą podany plik za pomocą tego samego parsera, który przetwarza php.ini. Zyskujemy zatem dostęp do podziału na sekcje oraz komentarzy. Jednak tu musimy już zadbać o to, aby nikt konfiguracji nie odczytał. Po pierwsze, nadal zapisujemy plik pod nazwą config.php (ważne jest końcowe rozszerzenie). Następnie na początku pliku umieszczamy prostą komendę:
; <?php die() ?> - zatrzymaj, jeśli ktoś uruchomił plik parserem PHP dsn = "mysql:host=localhost;dbname=baza" uzytkownik = "uzytkownik" haslo = "haslo"
Kiedy przetworzymy plik parserem INI, otrzymamy tablicę z dyrektywami konfiguracyjnymi. Interpreter PHP natomiast zatrzyma się na poleceniu die() i tak napastnik też nie ujrzy dalszej części pliku. Można też użyć bardzo podobnej metody:
; <?php /* - całość to komentarz, więc parser PHP nic nie wyświetli dsn = "mysql:host=localhost;dbname=baza" uzytkownik = "uzytkownik" haslo = "haslo" ; */ ?>
Polega ona na wzięciu wszystkiego w komentarz. Parser PHP opuści te dane i wyświetli pustą stronę, natomiat parser INI nie przejmie się komentarzami /* i */ i dane będą dla niego dostępne.
Tę drugą metodę wykorzystuje prosta w użyciu klasa ConfigMagik, która potrafi nie tylko odczytywać pliki INI, ale też zapisywać do nich dane.
Funkcja parse_ini_file() jest wykorzystywana m.in. przez bibliotekę Open Power Template opisywaną we wcześniejszej części książki. Dzięki niej całą konfigurację możemy przechować w tablicy i udostępniać odpowiednie dyrektywy przy pomocy odpowiednich metod lub funkcji:
<?php
class config
{
private $__data;
public function __construct()
{
// Parametr "true" włącza przetwarzanie sekcji
$this -> __data = parse_ini_file(DIR_CONFIG.'config.php', true);
} // end __construct();
public function __get($name)
{
if(!isset($this -> __data[$name]))
{
throw new Exception('Odwołanie do nieistniejącej dyrektywy: '.$name);
}
return $this -> __data[$name];
} // end __get();
}
try
{
$config = new config();
echo 'DSN '.$config -> dsn;
echo 'User '.$config -> uzyktownik; // literówka, zwróci wyjątek
}
catch(Exception $e)
{
die('Błąd: '.$e -> getMessage());
}
?>
Nasz prosty system pomaga nam także przy debugowaniu, gdyż w przypadku odwołania do niezdefiniowanej dyrektywy zwraca wyjątek. Prawdziwą potęgę takiego rozwiązania poznamy po omówieniu następnej części rozdziału.
[edytuj] Ustawienia serwisu
Konfiguracja serwisu zawiera takie informacje, jak format daty, ustawienia strefy czasowej czy szaty graficznej. Może być przechowywana w trzech miejscach:
- W plikach INI, jak w przypadku ustawień technicznych.
- W plikach XML.
- W bazie danych.
Pliki XML oraz bazy danych dają nam możliwość przechowania wraz z aktualną wartością także dodatkowych informacji, jak wartość domyślna czy nawet opis pomocy. Odczyt jest jednak zbyt wolny, aby można było z niego korzystać przy każdym wejściu na stronę. Dlatego też musimy zadbać, aby cache'ować zbierane dane np. w zserializowanych tablicach zapisanych w zwyczajnym pliku.
Odpowiedź na pytanie, gdzie się tym zajmować, jest bajecznie prosta: w konstruktorze naszej klasy config. To jest właśnie potęga systemu konfigurującego opartego na klasie. Konfiguracja może być porozrzucana nawet po dziesięciu miejscach, lecz system zadba o jej zgrabne połączenie. W ten sposób programista nie musi zastanawiać się gdzie dana dyrektywa się znajduje. Do wszystkiego odwołuje się jednolitym interfejsem, który automatycznie decyduje, skąd pobrać dane. PHP/Przenośność
[edytuj] Przygotowania
Skrypt, który tu napiszemy nie będzie dobrze działał po skopiowaniu i wklejeniu, mam raczej zamiar pokazać rozwiązania rozmaitych problemów.
Na początku należy stworzyć w bazie danych tabelę `users`, oraz wrzucić trochę przykładowych danych:
CREATE TABLE `users` ( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` TEXT NOT NULL, `password` TEXT NOT NULL, `regdate` DATE NOT NULL, `email` TEXT NOT NULL, `ranga` INT NOT NULL ); INSERT INTO `users` ( `id` , `name` , `password` , `regdate` , `email` , `ranga` ) VALUES ( NULL , 'admin', MD5('super tajne'), '2007-10-05', 'mail@poczta.pl', 1 ); INSERT INTO `users` ( `id` , `name` , `password` , `regdate` , `email` , `ranga` ) VALUES ( NULL , 'user', MD5('tajne'), '2007-10-07', 'innymail@poczta.pl', 2 ); CREATE TABLE `ranks` ( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` TEXT NOT NULL, `hash` TEXT NOT NULL ); INSERT INTO `ranks` (`name`, `hash`) VALUES("Administratorzy", "admins"); INSERT INTO `ranks` (`name`, `hash`) VALUES("Użytkownicy", "users")
A teraz opiszę pokrótce co znaczy które pole: Tabela users: `name` - Nazwa użytkownika, `password` - Hasło, `regdate` - Data rejestracji, `email` - Email, `ranga` - id rangi z tabeli ranks, Tabela ranks: `name` - Czytelna dla ludzi nazwa rangi, `hash` - Nazwa rangi czytelna dla PHP.
[edytuj] Formularz logowania
Teraz należy stworzyć formularz (w (X)HTML), na przykład:
<form action="zaloguj.php" method="post"> <label for="login">Login: </label><input type="text" name="login"/> <label for="password">Hasło: </label><input type="text" name="password"/> <input type="submit" value="zaloguj"/> </form>
A teraz obsługa, czyli plik zaloguj.php:
<?php interface storage { public function login($name, $pass);//1 } class mysqlStorage implements storage{//2 public function login($name, $pass){//3 global $pdo; $pdo=new PDO('mysql:host=localhost;dbname=nazwabazy', 'root', 'root'); $stmt=$pdo->prepare('SELECT name, ranga FROM `users` WHERE name=:name AND password=:password'); $stmt->bindValue(':name', $name); $stmt->bindValue(':password', $pass); $stmt->execute(); $user=$stmt->fetch(); if($user){ return $user; } return false; }// login(); }//mysqlStorage class user{//4 public $name; public $rank; private $storage; public function __construct($storage){ $this->storage=$storage; } public function zaloguj(){ if($_SERVER['REQUEST_METHOD']=='POST'){ $user=$this->storage->login($_POST['login'], $_POST['password']); if(!$user){die('Nieprawidłowa nazwa użytkownika lub hasło!');} $this->name=$user['name']; $this->rank=$user['ranga']; $_SESSION['zalogowany']=true; $_SESSION['name']=$this->name; $_SESSION['rank']=$this->rank; } } } $user=new user(new mysqlStorage); $user->zaloguj() ?>
- Tworzymy interfejs, który należy zaimplementować aby stworzyć sterownik przechowywania danych
- Ta klasa to sterownik przechowywania danych w bazie danych MySQL
- Funkcja pobiera dane ze źródła przechowywania(czyli w tym wypadku bazy danych), i zwraca je, jeśli nie ma użytkownika to zwraca false.
- Teraz najważniejsza klasa ;). Służy ona do logowania użytkownika, w konstruktorze przyjmuje instancję klasy przechowującej dane.
Dlaczego stworzyliśmy interfejs storage? Aby było można szybko i łatwo zmienić sposób przechowywania danych.
[edytuj] Rejestracja
Teraz napiszemy moduł rejestracji. Do interfejsu dopisz metodę która rejestruje użytkownika:
<?php public function rejestracja($name, $password, $email, $regdate); ?>
Teraz implementacja w klasie mysqlStorage:
<?php public function rejestracja($name, $password, $email, $regdate){ $stmt=$pdo->prepare('INSERT INTO `users` VALUES(NULL, :name, :password, :email, :regdate, 2)'); $stmt->bindValue(':name', $name); $stmt->bindValue(':password', md5($password)); $stmt->bindValue(':email', $email); $stmt->bindValue(':regdate', $regdate); $stmt->execute(); } ?>
Chyba jasne? Ta dwójka na końcu zapytania to numer rangi. Dalej - klasa user:
<?php public function register(){ $this->storage->rejestracja($_POST['name'], $_POST['password'], $_POST['email'], date('d-m-Y')); } ?>
Formularza pokazywał nie będę.
[edytuj] Ćwiczenia
- Napisz panel administracyjny z następującymi funkcjami:
- Usuwanie użytkownika
- Zmiana rangi
[edytuj] SQL Injection
Atak typu SQL Injection polega na takiej zmianie jednego lub kilku parametrów zapytania (query) wysyłanego do bazy danych typu SQL, że polecenie to staje się niezgodne z zamierzeniem autora skryptu.
Załóżmy, że mamy stronę, która wyświetla dane klientów naszej firmy. Użytkownik po wejściu na stronę listaklientow.php otrzymuje listę klientów wyświetlająca wszystkich klientów firmy, którzy w bazie danych w kolumnie wyswietl mają wartość 1. Strona daneklienta.php, do której prowadzą łącza ze strony listaklientow.php wyświetla natomiast dane klienta dostarczone za pomocą zmiennej klient, a więc np. daneklienta.php?klient=Kowalski wyświetla dane dowolnego (ważne dla dalszej części przykładu) klienta o ID Kowalski. Co jednak, kiedy dane jakiegoś klienta nie powinny być oglądane przez niepowołane osoby? Administrator strony ustawia co prawda wartość 1 w kolumnie wyswietl bazy danych tylko dla określonych klientów, ale ktoś wpada na pomysł zrobienia czegoś takiego: daneklienta.php?klient=Tajny, gdzie klient o ID Tajny to klient, do którego profilu link nie jest wyświetlany przez plik listaklientow.php, jednak skrypt wyświetla dane klienta o dowolnym ID! Wtedy już mamy problem. Baza danych dostaje rozkaz wyświetlenia informacji dla ID Tajny, a skrypt jej tego nie zabrania. Właśnie wtedy mamy do czynienia z prostym SQL Injection.
Groźniejszym typem SQL Injection jest wypadek, w którym wspomniany ktoś (potencjalny haker) wyśle w zmiennej klient instrukcję:
'; TRUNCATE TABLE klienci
I problem gotowy. Cała tabela klienci jest czyszczona, ponieważ zapytanie SQL wyglądało w sposób:
mysql_query("SELECT zamowienia FROM klienci WHERE id='". $_GET['id'] ."'");
Jak widać, apostrof (') jest zamykany i wydawane jest nowe, niebezpieczne polecenie SQL.
[edytuj] Zabezpieczanie się przed SQL Injection
Zabezpieczyć się przed atakiem typu SQL Injection można bardzo łatwo - wystarczy przefiltrować cudzysłowy oraz apostrofy z parametru wysyłanego do zapytania SQL. Wiele opcji hostingowych ma domyślnie włączoną funkcję magic_quotes_gpc() (dla serwerów Apache), która dodaje znak \ przed każdym potencjalnie niebezpiecznym znakiem parametru. Można też dokonać tego korzystając z funkcji mysql_escape_string(), która robi to samo, co wspomniane magic_quotes_gpc(), np.
$id = mysql_escape_string($_GET['id']);
I problem z głowy ;).
[edytuj] Dodatkowe informacje
Wartym odnotowana jest jeszcze przypadek, w którym mamy do czynienia z serwerami z włączonym magic_quotes_gpc(). Jeśli nie chcemy/nie możemy go wyłączyć, możemy skorzystać z funkcji stripslashes(), która działa odwrotnie do funkcji mysql_escape_string():
$id = stripslashes($_GET['id']);
PHP Injection polega na dopisywaniu przez złośliwych użytkowników fragmentów kodu do przesyłanych zmiennych m.in. za pośrednictwem formularzy znajdujących się na stronach WWW.
Przed PHP Injection można zabezpieczyć się filtrowaniem przekazywanych parametrów - wygląda to dokładnie tak samo, jak w przypadku SQL Injection, jednak atak nie ma bezpośredniego (lub nie ma w ogóle) wpływu na bazę danych.
< PHP
Prosimy nie traktować tej strony jako darmowej reklamy dla tych czy innych edytorów. Opisy niespełniające zasady neutralnego punktu widzenia są usuwane!
Choć kod PHP można tworzyć już w zwykłym notatniku, o wiele lepiej jest zaopatrzyć się w specjalny edytor wyposażony w wiele dodatkowych opcji, m.in. konwersję między systemami kodowań czy podświetlanie składni. Oto alfabetyczna lista edytorów PHP, zarówno tych darmowych, jak i komercyjnych. Jeżeli znasz jakiś warty umieszczenia, sporządź odpowiedni opis i dołącz tu. Prosimy jedynie pamiętać o zachowaniu zasady neutralnego punktu widzenia.
[edytuj] Wieloplatformowe
Edytory dostępne dla systemów rodziny Windows, dla MacOS, systemów unikso-podobnych i czasem jeszcze innych.
[edytuj] Eclipse
free software
Aplikacja rozwijana przez IBM. Za pomocą pluginów można ją rozszerzać o dowolne funkcjonalności. Eclipse jest aplikacją wieloplatformową napisaną w Javie. Dla PHP cały czas rozwijany jest specjalny plugin: PHPEclipse. Ma wbudowany parser PHP. Umożliwia m.in. debugowanie kodu, auto uzupełnianie i wiele innych. Przy pomocy Eclipse można także projektować bazy danych. W tym celu należy zainstalować plugin Azzurri Clay. Umożliwia on tworzenie projektów baz danych (PostgreSQL, MySQL). Narzędzie pracuje w trybie WYSIWYG.
Do Eclipse'a zostało napisanych bardzo dużo pluginów, pozwalających m.in. na obsługę AJAX, (X)HTML, CSS i innych. Program udostępniany na warunkach Eclipse Public License.
Więcej informacji na oficjalnej stronie programu i stronach domowych pluginów: PHPEclipse i Azzurri Clay
[edytuj] Eclipse PHP Development Tool
free software
Narzędzie firmowane przez Zend - twórców języka PHP. Podświetla kod, zaznacza błędy w czasie rzeczywistym, wyświetla podpowiedzi (także dla własnych klas i funkcji, opisanych przy pomocy składni PHP Documentatora), umożliwia zaawansowane debugowanie (zatrzymywanie skryptu, sprawdzanie wartości zmiennych). Publikowane na Eclipse Public License.
Więcej informacji na stronie domowej programu.
[edytuj] NetBeans IDE
free software
NetBeans IDE to wieloplatformowe, zintegrowane środowisko programistyczne napisane w Javie. Jest rozwijane przez firmę Sun Microsystems (niedługo Oracle) i obecnie posiada obsługę m.in. Ajax, C/C++, baz danych, Java, Ruby, XML, PHP i wiele innych. Wspiera także większość popularnych systemów kontroli wersji a ich instalacja odbywa się jednym kliknięciem. NetBeans jest porównywany często do Eclipse, choć jest mniej możliwości konfiguracji, co dla niektórych może być zaletą, gdyż przez to IDE jest mniej złożone oferując jednocześnie całą potrzebną funkcjonalność. Dla developerów PHP istnieje specjalnie przygotowana wersja, którą można pobrać ze strony http://www.netbeans.org/features/php/index.html. Aktualna wersja środowiska to 6.7. Planowana na jesień wersja 6.8 ma pełne wsparcie PHP 5.3.0, jeszcze lepszą obsługę testów Selenium i PHPUnit oraz integrację z frameworkiem Symfony. Obsługa PHP w Netbeans jest ciągle ulepszana o czym zawsze można przeczytać na NetBeans for PHP Weblog
[edytuj] Vim
free software
Edytor ten jest bardzo znany w środowisku użytkowników Linuksa. Pracuje w trybie tekstowym i obsługuje się go wyłącznie za pomocą klawiatury, ale posiada za to rewelacyjne możliwości kolorowania składni oraz personalizacji. Osoby korzystające z graficznych interfejsów użytkownika mogą używać gVima (wykorzystującego GTK), posiadającego prosty interfejs okienkowy oraz dającego się już obsługiwać myszką. Program wydawany na licencji Vima zgodnej z GNU GPL.
Dosępne wersje dla systemów: unikso-podobnych, MS-DOS i MS-Windows, AmigaOS, OS/2, MacOS, MorphOS i kilku innych.
Więcej informacji na oficjalnej stronie programu.
[edytuj] Zend Studio
komercyjny
Bardzo dobry edytor PHP dla zaawansowanych programistów sieciowych. Ma wiele funkcji ułatwiających pracę na dużymi projektami m.in.: auto uzupełnianie kodu, debugger dla skryptów PHP. Wbudowany w program serwer daje możliwość analizowania skryptów na swoim komputerze. Program przeznaczony jest dla dobrze znających język PHP programistów sieciowych.
Więcej informacji na oficjalnej stronie programu.
[edytuj] Unikso-podobne
Edytory pod systemy Linuksowe, GNU, *BSD i wszelkie inne systemy unikso-podobne.
[edytuj] Bluefish
free software
Autorzy Bluefisha chwalą się, że ich program używa 40-45% mniej zasobów od konkurencji. Pozwala otworzyć naraz ponad 500 dokumentów. Zapewnia kolorowanie składni m.in. dla HTML, PHP, Java, JavaScript, SQL, XML, Python, Perl, CSS. Edytor jest na licencji GNU GPL. Istnieją wersje dla Linuksa, FreeBSD, MacOS-X, OpenBSD, Solaris.
Więcej informacji na oficjalnej stronie programu.
[edytuj] Kate
free software
Kde Advanced Text Editor jest wygodnym edytorem tekstowym pozwalającym na wygodną pracę z dziesiątkami plików jednocześnie. Posiada obsługę plików przez ftp, zarządzanie projektami, kolorowanie składni częściej używanych języków, wybór stron kodowych, makra, obsługę wyrażeń regularnych i wiele innych udogodnień. Jest on dostępny w większości dystrybucji. Jest sztandarowym edytorem środowiska graficznego KDE. Kate jest edytorem opublikowanym na licencji GNU LGPL.
Więcej informacji na stronie oficjalnej edytora.
[edytuj] Quanta Plus
free software
Zaawansowane środowisko programistyczne działające w systemach unikso-podobnych (środowisko KDE). Posiada wiele funkcji przydatnych przy pisaniu dużych aplikacji takich jak zarządzanie projektami, zakładki w kodzie, wbudowany debugger, przeglądarkę dokumentacji PHP, auto uzupełnianie i wiele innych. Program udostępniany na warunkach GNU GPL.
Więcej informacji na oficjalnej stronie programu.
[edytuj] Windows
Edytory przeznaczone dla systemów Windows.
[edytuj] Crimson Editor
freeware (free software?)
Zaawansowany i wygodny w obsłudze edytor, umożliwia m. in.: obsługę FTP, zaznaczanie kolumn, definiowanie własnych makr, podpinanie zewnętrznych programów (np. kompilatorów), zarządzanie projektami. Zauważone błędy: słaba obsługa dużych plików (> 0.5 MB), słaba obsługa otwierania plików w otoczeniu sieciowym w Windows 2000, "gryzie się" z niektórymi wersjami pgAdmina. Zapewnia kolorowanie składni m.in. dla HTML-a, PHP, Javy, JavaScriptu, SQL-a, XML-a, Pythona, Perla, CSS, ASP, Fortrana, LaTeX-a i wielu innych. Wersja darmowa (freeware, choć z dopiskiem Now it is open source zostały opublikowane źródła niewydanej oficjalnie wersji 3.71) do wszelakich zastosowań. Sporym mankamentem jest istnienie wersji tylko dla Windows.
Więcej informacji na oficjalnej stronie programu.
[edytuj] Delphi for PHP
shareware
Środowisko Delphi jest potężnym RAD-em, bardzo przyjemnym w użytkowaniu. Wersja dla PHP została wydana 22 lipca 2007. Możliwe pobranie 30-dniowego triala.
Więcej informacji na oficjalnej stronie programu, polskim cenniku.
[edytuj] Dev-PHP IDE
free software
Dev-PHP ma ciekawą funkcję podświetlania składni języka, w którym aktualnie piszemy. Dev-PHP potrafi podświetlać składnię w HTML, JS, CSS, PHP, MySQL oraz XML. Bez żadnych innych edytorów, program ten świetnie nadaje się do pisania kompletnych stron WWW. Dev-PHP posiada także funkcję numerowanie linii, podpowiedzi (Dev-PHP podpowiada, jakie parametry trzeba przekazać do poszczególnych funkcji). Dev-PHP oferuje także parsowanie skryptu PHP przez wskazany interpreter. Program ten również wspiera pisanie interfejsów graficznych z wykorzystaniem PHP-GTK. Program dostępny na licencji GNU GPL, niestety tylko na systemy Windows.
Więcej informacji na oficjalnej stronie programu.
[edytuj] Dreamweaver
shareware
Znany na całym świecie, potężny program firmy Adobe, umożliwiający pisanie rozbudowanych stron HTML, a także łatwe tworzenie aplikacji internetowych w popularnych językach programowania skryptowego, m.in. PHP i ASP. Posiada takie udogodnienia jak: kolorowanie składni, numerowanie wierszy, auto uzupełnianie kodu. Program dostępny jest w angielskiej wersji językowej i jest płatny. Istnieją wersje pod Windows i MacOS.
30-dniowy trial można pobrać ze strony producenta.
[edytuj] E-Net
freeware
Darmowy edytor stron internetowych wspomagający tworzenie stron z zastosowaniem HTML, CSS, JavaScript, PHP. Instaluje zintegrowany z programem serwer WWW Apache oraz parser języka PHP, co w znaczący sposób przyspiesza i ułatwia pisanie dokumentów internetowych. Dla ułatwienia i przyspieszenia generowania kodu istnieje system podpowiedzi dla HTML, CSS, JavaScript i PHP.
Więcej informacji na oficjalnej stronie programu.
[edytuj] HateML Pro
freeware
Godny uwagi pretendent do ulubionego narzędzia zawodowego webmastera. Oprócz standardowych funkcji edytorów tego typu (kolorowanie, wstawianie tagów itp.), wyróżnia się kilkoma przemyślanymi rozwiązaniami (np. podgląd bazy danych na serwerze MySQL - bardzo pomocne przy edycji skryptów PHP, podgląd i edycja atrybutów i wartości dla każdego tagu w aktualnych dokumencie – włącznie ze zdarzeniami itp., otwieranie plików, na których aktualnie znajduje się kursor - świetna funkcja w przypadku dołączonych w skrypcie innych plików, biblioteka plików dołączonych w bieżącym dokumencie i wiele innych).
Więcej informacji na oficjalnej stronie programu.
[edytuj] Kicia
freeware
Polski edytor, nadaje się zarówno dla webmasterów, jak i dla programistów. Pozwala na edycję programów i dokumentów w językach: HTML, PHP, Python, Assembler, Perl, Pascal, C++, Java. Można w nim również tworzyć skrypty VBScript i JavaScript, lub korzystać z gotowej biblioteki skryptów i programów. Istnieje także możliwość edycji plików ini oraz zwykłych tekstów w formacie txt. Program posiada gotowe fragmenty skryptów i składni, dzięki czemu nawet początkujący programiści i webmasterzy utworzą swój własny serwis lub program. W edytorze zawarte zostały kursy oraz linki do kursów. Program edytuje również kaskadowe arkusze stylów CSS.
Program nie jest rozwijany, można go pobrać z działu download serwisu internetowego pisma Komputer ŚWIAT.
[edytuj] Notatnik SP
freeware
Uniwersalny edytor (najprawdopodobniej w zamierzeniu programistyczny) z kolorowaniem składni dla wielu języków programowania, nada się również doskonale do PHP. Niestety nie można w jednym oknie programu pracować z wieloma plikami. Innym problemem jest to, że program rozpoczyna numerowanie składni od wiersza nr 0, co może utrudnić wyszukiwanie błędów na podstawie komunikatów parsera. Posiada rozległe opcje "przerabiania" tekstu (usuwanie pustych wierszy, zamiana na małe litery itp). Niestety nie da się zapisać pliku o kodowaniu UTF-8. Duże pliki (np. binarne) otwiera szybko, nie "zawieszając" się.
Strona producenta jest nieaktualna, program można ściągnąć ze strony programypc.pl.
[edytuj] Notepad ++
free software
Darmowy, uniwersalny edytor o otwartym kodzie, oferujący podświetlanie składni niemal 40 języków programowania. W oknie programu można uruchomić wiele (dziesiątki czy nawet setki przy odpowiedniej ilości RAM) osobnych plików, a także pracować w trybie sklonowanego widoku. Obsługuje kodowania UTF-8, UCS-2, ANSI (windows-1250), niestety brak wsparcia dla Latin2 (iso-8859-2). Na stronie programu dostępnych jest też ponad 20 pluginów oraz spolszczenie. Program udostępniany na licencji GNU GPL.
Pliki instalacyjne przygotowywane są dla Windows, istnieje możliwość uruchomienia także na Linuksie za pomocą Wine'a.
Więcej informacji na oficjalnej stronie programu.
[edytuj] Pajączek
shareware
Pajączek to rozbudowany polski edytor WWW napisany przez firmę Cream Software. Program jest płatny, ale najnowsza wersja NxG umożliwia bezproblemową współpracę z PHP - posiada m.in. dołączony manual PHP. Zaletą aplikacji jest kilka predefiniowanych ustawień okienek dialogowych – czy wolimy mieć wszystkie pokazane, czy np. wszystkie ukryte, mając więcej miejsca na kod.
Więcej informacji na oficjalnej stronie programu.
[edytuj] PHP Designer
shareware
PHP Designer jest zaawansowanym edytorem przystosowanym zarówno do edycji, debugowania, analizowania i publikowania skryptów PHP. Oprócz kolorowania składni umożliwia auto uzupełnianie kodu, jest przystosowany do edycji wielu innych technologii wykorzystywanych na stronach WWW. Edytor wyposażony jest również w możliwość wstawiania gotowych struktur kontrolnych, serwerowych zmiennych PHP wraz z opisem i możliwość dołączenia Manuala PHP.
Więcej informacji na oficjalnej stronie programu.
[edytuj] PSPad
freeware
PSPad to czeski darmowy edytor przeznaczony do programowania w różnych językach, m.in. PHP. Dostępny jest także w polskiej wersji językowej. Posiada kolorowanie składni, dobrą obsługę tabulacji oraz przyjemny w użyciu mechanizm konwersji między różnymi kodowaniami (wspierane m.in. ISO-8859-2, Windows-1250 i Unicode). Wady to istnienie kilku kombinacji klawiszy identycznych, jak te do wstawiania niektórych polskich znaków. Należy je samemu zlokalizować i usunąć z konfiguracji. Dodatkowo, jeżeli z programu chcą na tym samym komputerze korzystać dwie osoby, każda musi zainstalować swą własną kopię, ponieważ PSPad nie przewiduje możliwości personalizacji ustawień.
Więcej informacji na oficjalnej stronie edytora.
[edytuj] WebSite Pro
freeware
Jest to polski, prosty edytor stron posiadający kolorowanie składni m.in. php. Doskonale nadaje się do szybkiej edycji bądź pisania prostych skryptów.
Autor porzucił tworzenie tego edytora i na oficjalnej stronie programu dostępny jest już jedynie klucz potrzebny do korzystania z niego, natomiast ściągnąć go można ze strony dobreprogramy.pl.
[edytuj] kED 2
freeware
Jest to polski w pełni darmowy edytor przeznaczony do XHTML, CSS, PHP i innych. Koloruje składnię dokumentów, konwertuje strony kodowe (Windows-1250, ISO-8859-2, UTF-8, UTF-16), ułatwia wstawianie tabel, grafik, odsyłaczy, umożliwia współpracę z walidatorem Tidy oraz lokalnym serwerem WWW; zawiera spis znaczników wraz z ich atrybutami, a ponadto pełną listę właściwości CSS2 i listę kilkudziesięciu funkcji PHP. Wersja instalacyjna (ma domyślnie włączone automatyczne zapisywanie konfiguracji, zawiera walidator Tidy oraz umożliwia skojarzenie z kEDem plików htm, html, php, css, xml, js i vbs). Autor porzucił tworzenie tego edytora ze względu na brak wolnego czasu i środków. Na oficjalnej stronie programu od niedawna została usunięta możliwość download-u lecz można go pobrać z tego mirroru.
[edytuj] MacOS
Edytory przeznaczone dla systemów Apple'a.
[edytuj] Bluefish
free software
Autorzy Bluefisha chwalą się, że ich program używa 40-45% mniej zasobów od konkurencji. Pozwala otworzyć naraz ponad 500 dokumentów. Zapewnia kolorowanie składni m.in. dla HTML, PHP, Java, JavaScript, SQL, XML, Python, Perl, CSS. Edytor jest na licencji GNU GPL. Istnieją wersje dla Linuksa, FreeBSD, MacOS-X, OpenBSD, Solaris.
Więcej informacji na oficjalnej stronie programu.
[edytuj] Dreamweaver
shareware
Znany na całym świecie, potężny program firmy Adobe, umożliwiający pisanie rozbudowanych stron HTML, a także łatwe tworzenie aplikacji internetowych w popularnych językach programowania skryptowego, m.in. PHP i ASP. Posiada takie udogodnienia jak: kolorowanie składni, numerowanie wierszy, auto uzupełnianie kodu. Program dostępny jest w angielskiej wersji językowej i jest płatny. Istnieją wersje pod Windows i MacOS.
30-dniowy trial można pobrać ze strony producenta.
[edytuj] Autorzy
Choć Wikibooks może edytować każdy, istnieje pewna grupa ludzi aktywnie opiekujących się tym podręcznikiem przez cały proces jego powstawania. Nie tylko stworzyli wiele rozdziałów, ale także dbali o jednolitość całości oraz poprawność. W grupie tej znajduje się aktualnie jedna osoba:
Jeżeli chcesz pomóc, po prostu włącz się do akcji. Dotychczasowi członkowie grupy udzielą wszystkich wskazówek na temat edycji.
[edytuj] GNU Free Documentation License
Version 1.2, November 2002
Copyright (C) 2000,2001,2002 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[edytuj] 0. PREAMBLE
The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others.
This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software.
We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.
[edytuj] 1. APPLICABILITY AND DEFINITIONS
This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law.
A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language.
A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them.
The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none.
The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.
A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque".
Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only.
The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text.
A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the Document means that it remains a section "Entitled XYZ" according to this definition.
The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.
[edytuj] 2. VERBATIM COPYING
You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3.
You may also lend copies, under the same conditions stated above, and you may publicly display copies.
[edytuj] 3. COPYING IN QUANTITY
If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.
If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages.
If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public.
It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.
[edytuj] 4. MODIFICATIONS
You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version:
- A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission.
- B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement.
- C. State on the Title page the name of the publisher of the Modified Version, as the publisher.
- D. Preserve all the copyright notices of the Document.
- E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices.
- F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below.
- G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice.
- H. Include an unaltered copy of this License.
- I. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence.
- J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission.
- K. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein.
- L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles.
- M. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version.
- N. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section.
- O. Preserve any Warranty Disclaimers.
If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles.
You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties--for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard.
You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one.
The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version.
[edytuj] 5. COMBINING DOCUMENTS
You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.
The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.
In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled "History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all sections Entitled "Endorsements."
[edytuj] 6. COLLECTIONS OF DOCUMENTS
You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects.
You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.
[edytuj] 7. AGGREGATION WITH INDEPENDENT WORKS
A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document.
If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.
[edytuj] 8. TRANSLATION
Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail.
If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.
[edytuj] 9. TERMINATION
You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense or distribute the Document is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
[edytuj] 10. FUTURE REVISIONS OF THIS LICENSE
The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See http://www.gnu.org/copyleft/.
Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation.
[edytuj] How to use this License for your documents
To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page:
Copyright (c) YEAR YOUR NAME.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.2
or any later version published by the Free Software Foundation;
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
A copy of the license is included in the section entitled "GNU
Free Documentation License".
If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the "with...Texts." line with this:
with the Invariant Sections being LIST THEIR TITLES, with the
Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation.
If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.

