348
PDF wygenerowany przy użyciu narzędzi open source mwlib. Zobacz http://code.pediapress.com/ aby uzyskać więcej informacji. PDF generated at: Mon, 18 Nov 2013 13:25:24 UTC PHP

PHP - EITCA · 2014-06-20 · w lipcu 2004 roku. Nowości sprawiły, że PHP zaczął konkurować z innymi rozwiązaniami server-side jak równy z równym. Pojawił się całkowicie

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

PDF wygenerowany przy użyciu narzędzi open source mwlib. Zobacz http://code.pediapress.com/ aby uzyskać więcej informacji.PDF generated at: Mon, 18 Nov 2013 13:25:24 UTC

PHP

TreśćArtykułyWprowadzenie 1

O podręczniku 1Czym jest PHP 2Możliwości 5Jak się uczyć? 6

Instalacja 9

Opis instalacji 9Apache HTTP Server 11MySQL 5 14PHP 17

Podstawy języka 21

Pierwszy skrypt 21Zmienne i tablice 24Formularze 35Struktury kontrolne 40Instrukcja if 41Instrukcja switch 45Instrukcja for 48Instrukcja while 51Instrukcja do while 52Instrukcja foreach 53Funkcje 56Inne elementy składni 65Każdy popełnia błędy 76Korzystanie z dokumentacji 82Studium przypadku: Księga gości 85Ćwiczenia 91

Rozmaitości 94

Przetwarzanie tekstu 94Podstawy wyrażeń regularnych 102Obsługa ciastek 106

Sesje 112Wysyłanie e-maili 117Internacjonalizacja 119System plików 127Data i czas 133

Programowanie obiektowe 138

Czym jest programowanie obiektowe? 138Klasy i obiekty 141Konstruktory i destruktory 148Dziedziczenie 154Interfejsy 166Wyjątki 173Elementy statyczne 181Metody magiczne 191Iteratory 202Automatyczne ładowanie 207Ćwiczenia 214

Zaawansowane programowanie 215

Bazy danych 216

Wstęp do baz danych 216Projekt bazy danych 218Zarządzanie rekordami 222Pobieranie rekordów 227Relacje i indeksy 236Biblioteka PDO 243Jak to się robiło kiedyś? 258phpMyAdmin 262Studium przypadku: System newsów 269Bazy danych - co dalej? 272

Systemy szablonów 274

Czym jest system szablonów? 274Prosty edukacyjny system szablonów 278Open Power Template 285Smarty 300

Frameworki 310

Czym jest framework? 310

Bezpieczeństwo 312

Dobre praktyki 313

Inne 314

Edytory PHP 314Autorzy 321Dla twórców podręcznika 321

Archiwum - zawiera fragmenty rozdziałów, które były kiedyś zaczęte,lecz zostały odłożone na później lub są już niepotrzebne z różnychprzyczyn (np. zawarta w nich treść jest przekazywana w zupełnie innysposób). 325

SQL Injection 325Hashowanie 326PHP Injection 328Bazy danych i sesje 329Smarty 331

PrzypisyŹródła i autorzy artykułu 341Źródła, licencje i autorzy grafik 343

Licencje artykułuLicencja 344

1

Wprowadzenie

O podręczniku

O podręcznikuPodręcznik ten nie jest "jeszcze jednym kursem PHP". To zbiorowe opracowanie, które nie tylko uczy, jakprogramować w języku PHP, lecz również pokazuje, jak robić to dobrze. Staramy się demonstrować najnowszemożliwości projektu, stawiając na aktualność. Podręcznik może być czytany przez wszystkich: zarówno przezosoby, 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 izwyczajne wypisanie listy konstrukcji oraz funkcji nie wystarczy, aby powiedzieć o sobie "jestem doświadczonymprogramistą". 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 konkretnezadanie.Od czytelników tego podręcznika wymagamy pewnej znajomości języka HTML i orientowania się w tworzeniustron WWW. Związane jest to z omawianym tu materiałem ukierunkowanym w głównej mierze na pisaniedynamicznych witryn internetowych. Jeżeli czujesz, że brakuje Ci wiedzy, zacznij swoją przygodę od poznaniajęzyka HTML. Znajomość programowania i algorytmów nie jest konieczna, aczkolwiek pozwoli na szybszeprzyswajanie 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łamywaczamiPodręcznik poprawiać i uzupełniać może każdy. Jeśli tylko masz czas i nie widzisz żadnych przeszkód w liberalnejlicencji GNU FDL, pomóż w jego rozwoju: dopisz rozdział, zaktualizuj zawartość, popraw błędy (dalszeinformacje). Każda pomoc jest ważna, gdyż tworzą to ludzie dla ludzi. Pamiętaj jednak, aby nie używać żadnychmateriałów objętych prawem autorskim, a wszelkie większe edycje konsultować w dyskusji z innymi.

Czym jest PHP 2

Czym jest PHP

Czym jest PHP?PHP to skryptowy język programowania - programy w nim napisane nie są kompilowane do postaci kodumaszynowego 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 wnim 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 żadnychograniczeń. 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 takiejwolności PHP rozwija się bardzo dynamicznie, a w Internecie można znaleźć setki modyfikacji oraz dodatkowychmodułów.Mimo otwartości kodu, nad rozwojem oficjalnej wersji projektu czuwa firma Zend Company z Izraela założonaprzez twórców PHP. Zapewnia ona dodatkowe narzędzia i opiekę prawną, a także wyznacza kierunki rozwojuprojektu.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 donapisania rozbudowanej aplikacji. PHP znaleźć można wszędzie: od prywatnych stron domowych przez gryinternetowe aż do potężnych witryn korporacji lub portali. Opanowanie języka jest proste także dlatego, iż Internetpeł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 przezkonkurencyjne języki skryptowe dla stron WWW: ASP, JSP. Staramy się, aby podręcznik opisywał możliwienajnowszą wersję, jednak z powodu dużej ilości materiału niekiedy mogą pojawiać się fragmenty odnoszące się dostarszych wydań. W podręczniku nie opisujemy PHP 4.x, praktycznie nierozwijanego, ale wciąż popularnego naserwerach oraz wśród starszych programistów.PHP jest wykonywany po stronie serwera. Oznacza to, że PHP nie jest interpretowany (przetwarzany) przez programdo oglądania Internetu, lecz przez specjalny program na serwerze! Co innego HTML, JavaScript w wersjiwykonywanej po stronie klienta. Jak działa interpretator PHP? To proste - przerabia instrukcje PHP na HTML iJavaScript w wersji do interpretacji po stronie klienta, które trafiają na Twój komputer i wiesz co się dzieje. A przyokazji - Wikibooks i jej siostrzany projekt - Wikipedia - są napisane w PHP.

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-sidepracujące po stronie przeglądarki użytkownika (np. JavaScript w wersji wykonywanej po stronie klienta). Abywykorzystywać go na własnej stronie, musisz upewnić się, że twój serwer WWW 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 HTTPzwanego także serwerem WWW. Jeśli stwierdzi on, na podstawie rozszerzenia pliku, że dany dokument zawiera kodPHP, kieruje do 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 przetworzenia. Utworzony kod HTMLjest 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 PHPkod HTML. Oto przykład. Jeśli mamy plik PHP o następującej treści:

Czym jest PHP 3

<html>

<body>

<?php

echo 'Podaj login';

?>

</body>

</html>

To internauta zobaczy jedynie dokument o takiej treści:

<html>

<body>

Podaj login

</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żymiilościami danych: forami dyskusyjnymi, systemami zarządzania treścią, sklepami internetowymi. Generują oneodpowiedni kod HTML dla przeglądarki, a w momencie kiedy internauta przegląda stronę, PHP już zakończył nadnią swą pracę. Jest to bardzo istotne, ponieważ wszelkie dalsze reakcje na poczynania użytkownika należy albopozostawić 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 ramachprojektu Wikibooks. PHP zarządza tutaj pobieraniem treści żądanej strony z bazy, sformatowaniem jej, a wprzypadku kliknięcia na opcję "Edytuj" - dodaniem nowej wersji tekstu. JavaScript pracujący po stronie przeglądarkiużytkownika ułatwia edycję tekstu, obsługując przyciski automatycznie wstawiające niektóre rodzaje formatowaniaoraz znaki narodowe. Jest to dobry przykład współpracowania tych dwóch języków w naprawdę dynamicznejaplikacji internetowej.

Historia projektuPierwsza wersja PHP, rozpowszechniana pod nazwą PHP/FI ("Personal Home Page/Forms Interpreter"), zostałastworzona przez Rasmusa Lerdorfa w roku 1994 jako zestaw skryptów Perla służący do monitorowania internautówodwiedzają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 czerwca1995 roku autor udostępnił publicznie kod źródłowy (PHP Tools 1.0). Już kilka miesięcy później projektprzekształcił się w zalążek znanego obecnie języka programowania, gdy został połączony z innym narzędziemRasmusa 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. Cociekawe, wersja ta spędziła większość "życia" na beta testach. Oficjalne wydanie było tylko jedno i ukazało się wlistopadzie 1997 roku.W 1997 roku projektem zainteresowali się dwaj izraelscy programiści: Zeev Suraski i Andi Gutmans. Odkryli oni, żePHP/FI ma zbyt małe możliwości na potrzeby aplikacji eCommerce, którą tworzyli na uniwersytecie. Zdecydowaliwtedy, że przepiszą kod PHP całkowicie od nowa, korzystając z pomocy już istniejącej społeczności PHP. Wczerwcu 1998 roku ogłosili PHP 3.0 jako następcę PHP/FI, którego dalszy rozwój został wtedy zatrzymany. Był towielki 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.

Czym jest PHP 4

Krótko po wydaniu PHP 3, w zimie 1998 Zeev Suraski oraz Andi Gutmans jeszcze raz zabrali się za przepisywaniekodu źródłowego PHP, korzystając z doświadczeń nabytych przy pracach nad poprzednią wersją. Za główne celeobrali poprawienie modułowości oraz wydajności złożonych aplikacji. Choć dotychczasowa wersja potrafiła sobie znimi 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ługopóźniej zaczęto budować PHP 4. Jego nazwa to kompromisowe połączenie imion twórców projektu. Nowa, oparta oniego wersja PHP, ukazała się w maju 2000 roku. Tak jak poprzednio, był to potężny krok naprzód. Programiścimieli do dyspozycji teraz wiele nowych narzędzi, konstrukcji językowych oraz bezpieczniejszy systemwejścia/wyjścia. Od strony administracyjnej pojawiło się oficjalne wsparcie dla wielu nowych serwerów. Przezcztery lata od chwili wydania ukazały się trzy kolejne edycje tej wersji oznaczone numerami: 4.1, 4.2 oraz 4.3. Wkażdej z nich odczuwalne było zwiększenie bezpieczeństwa, szybkości działania oraz możliwości. W 2004 rokuobsługiwały one łącznie 20% wszystkich domen sieciowych. Również daleko po premierze PHP 5, "czwórka" byłabardzo 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ą nacelu dodanie do tego języka modelu obiektowego z prawdziwego zdarzenia. W lutym 2003 ukazała się pierwszawersja 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 zaczął konkurować z innymi rozwiązaniami server-side jak równy zró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ą sposobureprezentacji 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. Obecniepublicznie dostępne są codzienne snapshoty rozwojowego repozytorium kodu źródłowego, które można ściągnąć iprzetestować. Głównym celem jest dalsze dążenie do ujednolicenia projektu, wprowadzenia kolejnych możliwościwymaganych 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 najstarszychskryptów ponownie spowoduje problemy z kompatybilnością.Na podstawie Wikipedii

Możliwości 5

Możliwości

Możliwości

Budowa PHPPHP ma budowę modułową. Jądro projektu zapewnia obsługę wszystkich elementów języka oraz dostęp dopodstawowego zestawu funkcji. Aby dodać więcej możliwości, instaluje się dodatkowe moduły. Część z nich macharakter oficjalny i jest dołączona do każdej dystrybucji, pozostałe zgrupowane są w tzw. repozytorium PECL inależy je samodzielnie pobrać.PHP, dzięki otwartości kodu źródłowego, pracuje bez problemu na niemal każdym istniejącym obecnie 32-bitowymsystemie operacyjnym (z Uniksem oraz Windowsem na czele) oraz pozwala na łatwą integrację z większościąserwerów WWW, np. Apache, IIS, OmniHTTPD.

Co PHP może zrobić?PHP 5.1 oferuje swoim programistom wiele możliwości:1.1. Komunikacja z wieloma popularnymi bazami danych poprzez jednolity interfejs.2.2. Obsługa wielu popularnych protokołów sieciowych, m.in. SSL, IMAP, SMTP, IRC.3.3. Profesjonalne wsparcie standardu XML.4.4. Tworzenie obrazków w wielu popularnych formatach graficznych.5.5. Wiele funkcji obróbki tekstu.6.6. Wyrażenia regularne.7.7. Bardzo elastyczne tablice o mieszanych kluczach.8.8. Wsparcie dla usług sieciowych (SOAP, XML-RPC).9.9. Zaawansowany model programowania obiektowego.10.10. Możliwość zintegrowania z platformą .NET.11.11. Obsługa obiektów COM w Windows.12.12. Funkcje kryptograficzne.13.13. Funkcje konwersji kalendarza.14.14. Funkcje konwersji kodowań.15.15. Algorytmy kompresji: GZip oraz BZip2.PHP jest nie tylko językiem internetowym. Można go także używać do pisania aplikacji na konsolę systemuoperacyjnego, 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, posiadam.in. repozytorium PEAR.

Wady PHPPoczątkowe lata rozwoju miały jednak w sobie coś z pospolitego ruszenia. Z tamtego okresu do dziś przetrwałotrochę niekonsekwencji oraz nieścisłości, które sprawiają pewne kłopoty programistom. Twórcy powoli je usuwają,ale potrwa to jeszcze wiele lat.1.1. Ujednolicenie nazewnictwa - większość starszych modułów korzysta z różnych standardów nazywania

poszczególnych funkcji, co utrudnia ich zapamiętywanie.2. 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 dobardziej czytelnej dla nich postaci, oszczędzając wiele czasu. PHP za każdym uruchomieniem wykonuje całą pracę

Możliwości 6

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 zachowaniekompatybilności wstecznej z olbrzymią bazą już stworzonego kodu PHP, który musi działać na nowych wersjach,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 [1]

oraz www.php.net [2].

Popularność PHPPHP jest, mimo swoich wad, niezwykle popularny. Wykonać w nim można wszystkie typy witryn internetowych: odstron domowych przez gry internetowe, aż do komercyjnych portali oraz witryn wielkich korporacji. Polskaspołeczność PHP jest bardzo aktywna i napisała do tego języka wiele poradników co w połączeniu z łatwymdostę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.

Przypisy[1] http:/ / www. zend. com[2] http:/ / www. php. net

Jak się uczyć?

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. Podręcznik ma na celu wprowadzić w temat, zainteresować nim i pokazać ogółzagadnień, 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 przezkaż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ęzykiemprogramowania i pewnych spraw nie da się ominąć. Teoria bardzo dobrze uzupełnia praktykę, dlatego dokształcajsię także pod tym względem. Pozwoli Ci to zrozumieć, dlaczego dany problem rozwiązywany jest tak, a nie inaczejlub dlaczego w ogóle się nim zajmujemy.Cała wiedza o PHP zgromadzona jest w obszernej dokumentacji (www.php.net/docs.php [1]), zawierającej m.in. opiswszystkich modułów. Należy nauczyć się sprawnego poruszania po niej i orientowania się, gdzie co leży. Temuzagadnieniu 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żejze środowiskiem PHP, adres [www.php.net] powinien nawet znaleźć się wśród najczęściej przeglądanych przezCiebie witryn. Stanie w miejscu, kiedy projekt ten posuwa się naprzód, jest kiepską alternatywą i bez orientowaniasię w zachodzących zmianach szybko odkryjesz, że twoje skrypty z tajemniczych powodów nie chcą pracować nanowych wersjach, co przyprawia o złość klienta.

Jak się uczyć? 7

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 jakdział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ć pewnekonwencje, jakich będziemy się trzymali w kodzie. Istotne jest, aby planowane przedsięwzięcie umieć zrealizować wpraktyce. Choć projekty często tworzymy, aby nauczyć się czegoś nowego, poprzeczka nie powinna być windowanazbyt wysoko, gdyż wtedy nigdy go nie ukończymy. O wiele lepsze jest stopniowe poznawanie coraz bardziejzłożonych elementów.W książce tej duży nacisk kładziony jest na ponowne wykorzystywanie raz napisanego kodu. Uważamy, że nie masensu raz po raz na nowo odkrywać koła i tworzyć wszystkiego od nowa. Dobry programista po pewnym czasieutworzy sobie zbiór bibliotek z najczęściej używanymi funkcjami, które będzie przenosić między kolejnymiprojektami, oszczędzając sobie zbędnej pracy. Praktyki te także powinny być w naszym planie uwzględnione. Innesprawy, 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 oraz w jakim stopniu,•• ogólna budowa całej aplikacji:

•• jaką drogę pokonują dane podczas tworzenia strony,•• w jaki sposób dane są przetwarzane,•• jak dane są 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, generatorydiagramów itd. Po głębszym poznaniu języka PHP można zaznajomić się również z diagramami UMLwykorzystywanymi w zawodowych zespołach programistycznych.

Fora dyskusyjneNiestety, nie każdy problem będziemy w stanie rozwiązać samodzielnie. Wtedy z pomocą przyjdą nam foradyskusyjne, których dużo jest w polskim Internecie. Zanim zadasz jakieś pytanie, użyj wyszukiwarki, często bowiemodpowiedź została już wcześniej udzielona. Pamiętaj, że często aby uzyskać odpowiedź, musisz zastosować się doreguł forum. Poświęć przynajmniej kilka minut na zapoznanie się z regulaminem oraz zaleceniami. Ich przeczytanieto chwila, a może zaoszczędzić ci przykrości przy kontakcie z moderatorem. Zamieszczamy ten krótki poradnik,gdyż dużo początkujących programistów nie zwraca na te niby oczywiste rzeczy uwagi, co skutkuje długimoczekiwaniem na pomoc lub konfliktem z moderatorem na samym wstępie.Zanim napiszesz post, upewnij się, że:•• Umieszczasz go na odpowiednim forum i w odpowiednim dziale. 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 pewnozostaną źle odebrane przez internautów.

•• Użyłeś wyszukiwarki i nie znalazłeś odpowiedzi na swoje pytanie. Z pomocą wyszukiwarki często znajdzieszodpowiedź znacznie szybciej, niż gdybyś miał oczekiwać na odpowiedź na forum.

Jak się uczyć? 8

•• Problem nie jest wyjaśniony w jakimś artykule w serwisie. Wiele serwisów posiada bazę artykułów i porad, atakże zbiór FAQ (Najczęściej Zadawane Pytania), 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. które powinieneś usunąć,• 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 i interpunkcyjnym - błędy nie tylko utrudniają

zrozumienie twojej wypowiedzi, ale także są oznaką ignorancji. Jeżeli nie jesteś pewny, pisz posty w edytorzetekstu ze sprawdzaniem pisowni.

Oczywiście istotna jest także sprawa tytułu tematu. Najlepiej kiedy streszcza on istotę problemu, np. "Problem zpołą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 niewymaga 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 dorozpaczy. 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.

Przypisy[1] http:/ / www. php. net/ docs. php

9

Instalacja

Opis instalacji

Opis instalacjiPHP jest dostępny na niemal każdym popularnym systemie operacyjnym, lecz do efektywnego korzystania z niegopotrzebne jest dodatkowe oprogramowanie.

Serwer WWWPHP 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 Serverrozwijany przez Apache Foundation (httpd.apache.org [1]). 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 jestwiele wersji na różne systemy operacyjne.PHP komunikuje się z serwerem WWW na trzy sposoby:1. 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ż dlakażdego żądania musi zostać uruchomiona nowa, niezależna kopia interpretera, jednak oferuje administratorowizmiany wielu ustawień dotyczących bezpieczeństwa wykonywanych skryptów.

2.2. Moduł serwera - do najpopularniejszych serwerów można podłączyć PHP jako moduł. Znacząco zwiększawydajność, lecz utrudnia zachowanie odpowiedniego poziomu bezpieczeństwa na serwerze.

3.3. 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łaboudokumentowane oraz wymaga skorzystania z nieoficjalnych dodatków, przez co stanowi wyzwanie nawet dladoświadczonych administratorów.

Baza danychPHP sprawdza się najlepiej przy przetwarzaniu danych, lecz do ich przechowywania używana jest najczęściejniezależna aplikacja, np. relacyjna baza danych. Dane w bazie przechowywane są jako rekordy w tabelach ookreś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 MySQLprodukowana przez szwedzką firmę MySQL AB (www.mysql.com [2]). Także i ona współpracuje z każdympopularnym systemem operacyjnym, a przy tym nietrudno znaleźć pomoc w przypadku ewentualnych kłopotów.

Opis instalacji 10

Biblioteki dodatkoweJeżeli pracujesz na systemie Unix/Linux i chciałbyś zainstalować PHP ze źródeł, musisz upewnić się, że posiadaszzainstalowane wszystkie dodatkowe biblioteki. Zazwyczaj powinny być one już dostępne np. jako pakiety, ale nawszelki wypadek podamy adresy, pod którymi można je zdobyć:• Zlib (www.gzip.org/zlib [3]),• Libxml2 (www.xmlsoft.org [4]),•• Libmysql (dostarczana razem z MySQL).Instalując PHP w systemach Windows, wszystkie biblioteki dostarczane są razem z pakietem instalacyjnym.

PoradaJeżeli któryś z adresów będzie nieaktywny, pamiętaj o wykorzystaniu wyszukiwarki internetowej do odnalezienia potrzebnego Ci pliku.

Proces instalacjiInstalacja PHP zostanie przeprowadzona w kilku etapach:1.1. instalacja Apache,2.2. instalacja MySQL,3.3. instalacja PHP,4.4. integracja PHP z serwerem WWW,5.5. 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. Wpodręczniku nie będziemy opisywać, jak je z nich zainstalować, ponieważ opis rozrósłby się do niewyobrażalnychrozmiarów. Jeżeli jesteś zainteresowany tym sposobem instalacji, poszukaj odpowiedniego poradnika wdokumentacji twojej dystrybucji.

Alternatywny sposób instalacjiMoż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 iznacznie ułatwia instalacje i zarządzanie poszczególnymi komponentami serwera.W zeszycie ćwiczeń podręcznika Informatyki dla gimnazjum znajduje się dział Serwer WWW wraz z bazą danych iPHP opisujący w skrócie instalację i uruchomienie pakietu XAMPP.

Przypisy[1] http:/ / httpd. apache. org/[2] http:/ / www. mysql. com[3] http:/ / www. gzip. org/ zlib/[4] http:/ / www. xmlsoft. org/

Apache HTTP Server 11

Apache HTTP Server

Instalacja Apache HTTP ServerAby wygodnie testować skrypty PHP i generowane przez nie witryny internetowe, należy postawić na własnymkomputerze prywatny, testowy serwer WWW.Najpopularniejszym wykorzystywanym w Internecie serwerem WWW jest program o otwartych źródłach ApacheHTTP Server rozwijany przez Apache Foundation. Zarówno dokumentację, jak i pakiety instalacyjne można znaleźćpod adresem httpd.apache.org [1].Moduł ten opisuje, jak zainstalować Apache na różnych systemach operacyjnych. Możemy również skorzystać zpakietu XAMPP, który zainstaluje Apache wraz z potrzebnymi nam serwerem MySQL oraz interpreterem PHP.

Instalacja w systemach Unix/Linux

Kompilacja ze źródeł

Wydajemy polecenia w konsoli: su [enter], hasło administratoraPrzechodzimy do katalogu z kodem źródłowym

./configure

make

make install

Pakiety

Serwer Apache oraz inne niezbędne programy można także zainstalować z pakietów dołączonych do Twojejdystrybucji Linuksa, jednak z powodu ich różnorodności nie będziemy w tym podręczniku omawiać szczegółowoinstalacji na każdym z nich. Podajemy jedynie ogólne wskazówki, jak przystosować pakiet do uruchamianiaprzykł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 pakietautomatycznie 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żytkowniksystemu miał na swoim koncie katalog www.1. Otwórz plik httpd.conf w katalogu /etc/apache lub, jeśli nie ma w nim za dużo treści, plik apache2.conf w tym

samym katalogu2. Znajdź dyrektywę UserDir i ustaw ją na /home/*/www3. 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, a w nim katalogi dla użytkowników,będziesz mieć do nich dostęp poprzez adres http://localhost/~nazwa_uzytkownika/ (np. folder /home/test/www orazdostęp przez localhost/~test)Możesz teraz uruchomić serwer Apache poprzez skrypt startowy demona znajdujący się wraz z innymi (np. wkatalogu /etc/rc.d/ albo /etc/init.d/ w zależności od dystrybucji). Pamiętaj, że jest to na razie tylko serwer, do któregowciąż musimy podpiąć PHP. Zajmiemy się tym w następnym rozdziale.

Apache HTTP Server 12

Instalacja w systemach WindowsPobieramy ze strony httpd.apache.org [1] najnowszą wersję instalacyjną (MSI Installer) dla systemu Windows.Uruchamiamy instalator i po trzech kliknięciach "Next" pojawia się formularz, w którym wpisujemy następującedane:

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ługiwanyz 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 jako miejsce, do którego ma być zainstalowany serwer,wybieramy 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ż katalogD:\Serwer\Apache2\htdocs, z którego domyślnie odczytywane są strony, nie jest najlepszym rozwiązaniemumieszczać tam wszystkich naszych dokumentów. Pozostawimy go tylko w celu późniejszego zainstalowania w nimróżnych menedżerów serwera (np. do zarządzania bazą danych) obsługiwanych z poziomu przeglądarki. Dla naszychwł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ę # Includeconf/extra/httpd-userdir.conf (tzn. usunąć znak #), jak również linijkę "# LoadModule userdir_modulemodules/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.

Apache HTTP Server 13

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 "www", utwórz w nim katalognp. test, zapisz w nim plik index.html, edytując go np. notatnikiem wpisz w jego treści "Hello World" , następnieuruchom w przeglądarce: http:/ / localhost/ ~test/ ''. Jeżeli wyświetlił się napis, wszystko jest OK. W systemach2000/XP serwer Apache może pracować jako usługa systemu operacyjnego. Zamiast okienka dosowego, dostępnabędzie w zasobniku systemowym ikonka programu "Apache Monitor", pozwalająca na włączanie, wyłączanie orazrestartowanie zainstalowanych usług Apache. Jeśli taka ikonka nie wyświetla się w zasobniku systemowym, należyuruchomić "D:\Serwer\bin\ApacheMonitor.exe". Aby mieć do niej dostęp na stałe, można dodać skrót do tegoprogramu 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 wcałych zestawach instalacyjnych (Krasnal, WebServ itd.) - np. często wykorzystywany mod_rewrite należy samemuwłączyć po instalacji Apache'a, poprzez edycję pliku httpd.conf (znajdujemy linijkę #LoadModule rewrite_modulemodules/mod_rewrite.so i usuwamy # na początku.)

Instalacja w systemie Mac OS XStandardowo Mac OS X ma zainstalowany serwer Apache - możesz go włączyć w panelu preferencji Sharing jakoPersonal Web Sharing.Jeśli chcesz nowszą wersję Apache, możesz zainstalować pakiet Apache2 za pomocą programów Fink [2] orazFinkCommander [3].Apache można również skompilować samemu. Potrzebna będzie instalacja dodatku Developer Tools z płyty systemuMac OS X, dalej należy postępować jak w przypadku systemów Unix/Linux.

Przypisy[1] https:/ / httpd. apache. org/[2] http:/ / fink. sourceforge. net/[3] http:/ / finkcommander. sourceforge. net

MySQL 5 14

MySQL 5

Instalacja MySQL 5.0Aby testować strony na własnym komputerze, oprócz programu Apache będzie nam jeszcze potrzebna instalacjaserwera MySQL (bazy danych).

Instalacja w systemach Unix/Linux

Kompilacja ze źródeł

$ ./configure --prefix=/usr/local/mysql

$ make

# make install

Pakiety

Także serwer baz danych MySQL można zainstalować z pakietów. Przeważnie jest on już wtedy w pełni gotowy dopracy 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ł samskonfigurować i uruchomić serwer.

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", musimysprawdzić 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ówmy-[small|medium|large|huge].cnf. Przykładowo dla prostej bazy danych na zwykłym domowym PC do ćwiczeń wzupeł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 prawado 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 napartycji systemowej. Wielu użytkowników ma katalog /home domyślnie przez dystrybucję lub z własnego wyboruumieszczony na innej partycji lub nawet innym dysku. Warto wtedy utworzyć w /home katalog na bazy, któryprzeż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.

MySQL 5 15

# 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 - inaczejbę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 startowymserwera. 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. Serwermusi wiedzieć, z jakim katalogiem będzie pracował. W innych dystrybucjach (np. Debian) skrypty startoweumieszczane są w katalogu /etc/init.d/. Przy okazji zajmiemy sie automatycznym uruchamianiem serwera naprzyszł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 wpodrę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 opisanypodczas wykonywania - prawie, że wizard. Pozostawi jedynie konto roota dla bazy, wyrzuci konta anonimowe i bazętest wraz z zawartymi w niej tabelami.

Instalacja w systemach WindowsPoniższy sposób instalacji dotyczy systemów Windows 2000 oraz Windows XP. W następnym akapicie są opisaneróżnice dla przypadku Windows 98.1. Wejdź na stronę [1].2.2. Pobierz plik oznaczony jako "Windows (x86)" (lub x64, jeśli Twój komputer ma architekturę 64-bitową).3. Uruchom plik ściągnięty plik MSI lub, jeśli ściągnąłeś wersję ZIP, rozpakuj archiwum i uruchom plik Setup.exe.4. Wybierz rodzaj instalacji (Custom).5.5. Wybierz katalog, do którego chcesz zainstalować serwer.6.6. Zaakceptuj proponowane przez instalator komponenty pakietu.7.7. Poczekaj, aż wszystkie pliki zostaną skopiowane.8. Instalator zaproponuje Ci rejestrację w witrynie mysql.com. Możesz pominąć ten krok wybierając Skip Sign-up,

chyba że posiadasz już konto lub chcesz takie utworzyć.9.9. Zaznacz opcję "Configure the MySQL Server now" i kliknij "Finish". Uruchomione zostanie narzędzie

konfiguracji serwera.10.10. Wybierz "Reconfigure Instance" i kliknij "Next".

MySQL 5 16

11.11. Wybierz "Standard configuration" i kliknij "Next".12.12. W kolejnym kliknij na "Next" bez zaznaczania czegokolwiek.13.13. Zaznacz opcję "Modify Security Settings" i wpisz we wszystkie pola hasło "root".14.14. Kliknij "Execute", aby zapisać zmiany.MySQL został zainstalowany i jest dostępny jako usługa systemu Windows. Możesz go zatrzymywać lubrestartować poprzez Panel Sterowania > Narzędzia administracyjne > Usługi.Poniższy sposób instalacji dotyczy systemu Windows 98.

1.1. Wykonaj kolejno kroki od 1. do 9. z opisu instalacji w Windows 2000/XP (powyżej).2.2. Kliknij "Next", wybierz "Standard configuration" i ponownie kliknij "Next".3.3. W kolejnym kliknij na "Next" bez zaznaczania czegokolwiek.4.4. 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 -pshutdown".W razie problemów z "Call to undefined function mysqli_connect()" skopiuj libmysql.dll z folderu "php5" do"Apache2\bin"

Do zrobienia:instalacja na systemie Windows Me - jest tam inna procedura Konfiguracja w systemie Windows 7 - często pojawia się Error:0 Czasamipodczas konfiguracji w Windows XP jest Error:0

Aby dokonać w przyszłości aktualizacji bazy danych MySQL do nowszej wersji, zatrzymaj serwer MySQL, zrób nawszelki wypadek kopię zapasową katalogu data, a następnie powtórz wszystkie powyższe kroki, instalując nowąwersję na starą.

Instalacja w systemie Mac OS XWystarczy pobrać gotowy pakiet (.dmg) ze strony domowej MySQL. Zamieszczony jest na nim standardowyinstalator. 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 innychsystemach uniksowych.

Przypisy[1] http:/ / dev. mysql. com/ downloads/ mysql/

PHP 17

PHP

Instalacja PHP 5.2Na samym końcu instalujemy główny program, czyli interpreter PHP oraz podłączamy go do już zainstalowanegoserwera Apache.

Instalacja w systemach Unix/Linux

Pakiety

Niemal wszystkie dystrybucje systemu Linux mają w swoich zasobach pakiety PHP. Jeżeli nie czujesz się na siłachinstalować projektu ze źródeł, możesz skorzystać z tego rozwiązania i postępować odpowiednio według instrukcjisystemu pakietów używanego w twojej dystrybucji. Pakiety mają jednak kilka wad, a najważniejszą z nich jestkonieczność polegania na intuicji autora pakietów. Interpreter nie zawsze jest skonfigurowany tak, jak byśmychcieli, 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 wersjeinterpretera. Przypominamy, że podręcznik ten pisany jest pod wersje PHP 5.1.x/5.2.x, więc jeśli twój pakietzainstaluje 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• display_errors = On - wyświetlanie 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 wersjePHP 4, aktualnie zdecydowanie odradza się korzystanie z nich nie tylko ze względów bezpieczeństwa, ale też zpowodu planów ich wycofania w PHP 6. Dlatego pozostawiamy je wyłączone i tak też będziemy pisać naszeskrypty.Upewnij się ponadto, że PHP ładuje moduły php_pdo.so, php_pdo_mysql.so, php_mysql.so, php_zlib.so orazphp_gd2.so.

Kompilacja ze źródeł

Jeśli zdecydowałeś się na kompilację ze źródeł, zacznij od ich pobrania - źródła projektu dostępne są na stroniewww.php.net [1]. 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 zmodyfikujnazwę swojego pliku. Po rozpakowaniu powinien pojawić się katalog php-5.2.0. Przechodzimy do niego:

cd php-5.2.0

Teraz rozpoczynamy konfigurowanie naszej kompilacji poprzez podanie odpowiednich dyrektyw do skryptuconfigure.

./configure --with-apxs2=/usr/local/apache2/bin/apxs --with-config-file-path=/etc/apache

--with-zlib --with-mysql=/usr/local/mysql

PHP 18

--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 - ustawić ścieżkę do katalogu kont, w których zamierzamy docelowo używać PHP, czyli /home/*/www

(ta sama, co w Apache)Otwieramy plik konfiguracji serwera Apache: /etc/apache/httpd.conf. Sprawdzamy, czy program "apxs" dodał linijkę

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 koniecrestartujemy/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 ntzainstalowanej wersji PHP (wersja, konfiguracja, moduły itd.). Jeżeli zamiast tego pokazany zostanie wpisany wyżejkod, oznacza to, że instalacja nie przebiegła poprawnie i na którymś etapie został popełniony błąd.

PHP 19

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 dlaprzyszłych wydań.

Instalacja w systemach WindowsInstalacja PHP jako jedyna nie polega na klikaniu dalej. Taka możliwość oczywiście istnieje, lecz wtedy nie możnaskonfigurować interpretera do pracy jako moduł serwera.By zacząć działać, musimy ściagnąć najnowszą wersję PHP ze strony www.php.net [1] w wersji dla systemuWindows. W chwili powstawania tego tekstu najnowszą wersją było PHP 5.1.1. Ściągnięty plik (tzw. binarki,przykładowo wyglądające: 'php-5.1.1-Win32.zip' ) rozpakowujemy do przykładowego katalogu D:/Serwer/php5/.Następnie kopiujemy plik php.ini-recommended do php.ini i zabieramy się za edytowanie php.ini:1. 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.2. W dyrektywie doc_root wprowadź ścieżkę do katalogu "D:/Serwer/www" utworzonego przy okazji instalowania

serwera Apache. Tu będziemy trzymać nasze projekty.3. W dyrektywie extension_dir wprowadzamy ścieżkę do katalogu D:/Serwer/php5/ext, aby PHP mógł

zlokalizować dodatkowe moduły.4. Przechodzimy do sekcji ; Dynamic Extensions ; gdzie ustawimy, jakie dodatkowe moduły mają być ładowane

przy starcie PHP. Wskazane jest usunąć średnik (co odblokowuje moduł) sprzed następujących linii:

extension=php_gd2.dll

extension=php_mysql.dll

extension=php_mysqli.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. Ostatni moduł to nowa biblioteka PHP Data Objects służąca komunikacji zbazami danych, skonfigurowana do działania z bazą danych MySQL. Nie należy dopisywać linii z plikami bibliotekw php.ini, lecz jedynie odblokowywać istniejące! W pliku .ini są uwzględnione wszystkie pliki bibliotek istniejącefizycznie w zastosowanym przez nas pakiecie instalacyjnym PHP. Dopisanie nieistniejącego pliku spowoduje błąd.Ostatnim krokiem jest podłączenie PHP do serwera Apache. Jeżeli zamierzamy zrobić to jako CGI, na koniec plikukonfiguracyjnego 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 dodać jeszcze jedną linijkę (pod poprzednio dodanym kodem):

PHPIniDir "D:/Serwer/php5"

albo, jeśli serwer Apache zgłosi błąd podczas startu, wypróbować składnię:

PHPIniDir="D:/Serwer/php5"

PHP 20

aby wskazać lokalizację pliku php.ini, ponieważ domyślna jego lokalizacja w serwerze Apache to C:\Windows. Jeśliwięc tego nie zrobimy, nasze moduły nie zostaną załadowane. Teraz restartujemy serwer i zabieramy się zasprawdzenie, czy wszystko przebiegło poprawnie. Umieść w katalogu (wskazanym w pliku konfiguracyjnymserwera - opis znajdziesz w rozdziale Instalacja Apache) D:/Serwer/Apache2/htdocs/ plik phpinfo.php z poniższąlinijką:

<?php phpinfo(); ?>

W przeglądarce wpisz http:/ / localhost/ phpinfo. php - powinien pokazać Ci się bardzo długi raport na tematzainstalowanej wersji PHP (wersja, konfiguracja, moduły itd.). Jeżeli zamiast tego ujrzysz wpisany wyżej kod,oznacza to, że coś zostało zrobione źle na którymś z etapów podpinania PHP do serwera.

Instalacja w systemie Mac OS XMożna zainstalować gotowy pakiet PHP5 za pomocą Fink [2] i FinkCommander [3].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 jakdla Unix/Linux.

Przypisy[1] http:/ / www. php. net/ downloads. php[2] http:/ / fink. sf. net[3] http:/ / finkcommander. sf. net

21

Podstawy języka

Pierwszy skrypt

Pierwszy skryptW tym rozdziale napiszemy pierwszy skrypt PHP.

Skrypt PHPJęzyk PHP umożliwia zagnieżdżanie skryptów wykonywanych po stronie serwera.

ĆwiczenieUtwórz taki plik i zapisz z rozszerzeniem PHP:

Witaj Świecie <?php echo 2*2; ?>

Następnie uruchom go w przeglądarce www, z poziomu serwera (np. http://localhost/skrypt.php).

Wybierz w przeglądarce opcję podglądu źródła i porównaj źródło otrzymane przez przeglądarkę z oryginalnym plikiem.

Przeglądarka otrzymuje tylko już przetworzony kod, w tym przypadku <? echo 2*2; ?> zostało zamienionena 4. O szczegółach tej instrukcji - w dalszej części podręcznika.Interpreter PHP rozpoznaje kod do przetworzenia po znakach <?php i ?>. Każdy kod między nimi jest programemPHP.Na początku będziesz musiał poznać instrukcję echo, która wysyła tekst do przeglądarki:

<?php

echo 42;

?>

Funkcja ta została omówiona tutaj, ponieważ jej znajomość przydaje się do nauki zmiennych i wyrażeń; bardziejszczegółowo zostanie to omówione w kolejnych rozdziałach.

Skrypt PHP wewnątrz dokumentu HTMLJak wspomnieliśmy wcześniej, skrypty PHP możemy mieszać ze zwykłym kodem HTML. Kod naszych algorytmówzamykany jest wewnątrz specjalnych wstawek wyłapywanych przez interpreter oraz zmienianych później nawygenerowany kod. Tak też zrobimy w naszym pierwszym skrypcie, który tradycyjnie wyświetli na ekranieprzeglą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!';

Pierwszy skrypt 22

?>

</body>

</html>

W powyższym przykładzie widzimy skrypt PHP osadzony za pomocą znaczników <?php oraz ?> w zwyczajnymkodzie HTML, który w następnych przykładach będziemy już pomijać, aby nie marnować miejsca. Wewnątrz mamyjedną linijkę:

echo 'Hello world!';

Nakazuje ona wyświetlenie tekstu "Hello world!" w przeglądarce. Tekst do wyświetlenia ograniczyliśmyapostrofami. Średnik na końcu informuje o zakończeniu komendy. Możemy rozbić to na kilka linijek, ale dla PHPnie 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. Innymisą 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 wjednej linijce. Jest tak dlatego, ponieważ nowa linia oznaczana jest specjalnym znacznikiem HTML, którego tam nieumieściliśmy. 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 utrudniajakiekolwiek modyfikacje wyglądu. W dalszych rozdziałach tego podręcznika nauczymy się korzystać z szablonów, które pomogą namcałkowicie oddzielić jedno od drugiego, lecz na razie wszystko będzie wymieszane.

Pierwszy skrypt 23

KomentarzeKiedy twoje skrypty staną się bardziej rozbudowane, w kodzie przyda się pewna organizacja. Pomogą tu zpewnoś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 prawachautorskich. Komentarz jest całkowicie ignorowany przez interpreter PHP i nie wpływa na wynik jego działania.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 ([email protected])

* 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żyw danym fragmencie poprawić albo jak on funkcjonuje. W procesie usuwania błędów (debugowania) komentarzymoż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 praktykprogramistycznych i nigdy nie należy o nich zapominać.

Pierwszy skrypt 24

Znaczniki wstawek PHPOficjalnymi znacznikami rozpoczynającymi i kończącymi kod PHP są <?php ... ?>, jednak wciąż możnaspotkać starszą formę <? ... ?>. Nie zalecamy jej stosowania, ponieważ wiele serwerów ma ją wyłączoną iTwoje skrypty bez przeróbek nie będą na nich działać.Wartą odnotowania rzeczą jest możliwość pominięcia końcowego ?> jeśli chcemy, aby kod PHP ciągnął się dokońca pliku. Taka też konwencja zostanie przyjęta w dalszych rozdziałach. Jest ona stosowana przez wiele skryptów,gdyż pozwala uniknąć przypadkowego pozostawienia np. spacji czy pustej linii na końcu pliku, co niekiedy możebyć źródłem poważnych problemów, o czym przekonamy się z dalszych rozdziałów. Nasz skrypt będzie zatemprezentować się następująco:

<?php

echo 'To jest tekst 1<br/>';

echo 'To jest tekst 2<br/>';

echo 'A to jest tekst 3<br/>';

Uruchom go i przekonaj się, że to rzeczywiście działa!

Zmienne i tablice

Zmienne i tabliceSamo wyświetlanie tekstu jest niezbyt ciekawe. Wszystkie programy komputerowe operują na danych, zatem wjęzykach programowania muszą istnieć mechanizmy do przechowywania informacji w pamięci. Do tego służązmienne i tablice, którymi zajmiemy się właśnie teraz.

DanePHP, jak każdy inny język programowania, operuje na danych. Niektóre z nich są zapisane na sztywno w skrypcie.Każda rzecz, która reprezentuje jakąkolwiek informację, zwana jest wyrażeniem. Oto prosty przykład:

8

To jest wyrażenie reprezentujące liczbę całkowitą.

6.454

To jest wyrażenie reprezentujące liczbę zmiennoprzecinkową będącą komputerowym, skończonym przybliżeniem(nie wartością dokładną) liczby rzeczywistej.

0x6F44

To jest wyrażenie reprezentujące liczbę zapisaną w systemie szesnastkowym.

07647

To jest wyrażenie reprezentujące liczbę zapisaną w systemie ósemkowym.

'To jest tekst bez znaków specjalnych'

"To też jest tekst, ale \t-\t ze znakami specjalnymi (tabulatorami)"

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

Zmienne i tablice 25

jest już normalny skrypt.Cudzysłów posiada więcej takich kodów formatujących:

"\n - tak robimy zejście do nowej linijki w systemach uniksowych (np. Linux, Mac OS X)"

"\r - tak kiedyś się robiło w Mac OS Classic"

"\r\n - a tak wciąż się robi w systemach 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 złożone wyrażenie (reprezentuje ono sumę dwóch mniejszych wyrażeń):

5 + 7

Oto wyrażenie będące połączeniem dwóch mniejszych wyrażeń tekstowych.

'Tekst A '.'Tekst B'

Znak kropki oraz plusa 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 ściśle określonakolejność 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 gokonwertować. 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.

FunkcjeInformatyka wiele zawdzięcza matematyce. W programowaniu występuje wiele pojęć zaczerpniętych bezpośredniood królowej nauk. Jednym z nich jest funkcja, do której możemy wprowadzać parametry, a w zamian otrzymujemyjakiś wynik. Poniżej będzie pierwszy "naprawdę" dynamiczny skrypt, jaki stworzymy. Skorzystamy w nim z dwóchfunkcji, aby wyświetlić aktualny czas:

<?php

echo 'Dzisiaj mamy: '.date('d.m.Y').'<br/>';

echo 'Od 1.1.1970 minęło: '.time().' sekund';

Zmienne i tablice 26

Jak widać, składnia funkcji jest następująca: nazwaFunkcji(parametry). Jeśli funkcja nie posiadaparametrów, nawiasy są puste. Jeżeli jest ich więcej, niż jeden, oddzielamy je od siebie przecinkiem. Zaś samparametr jest niczym innym, jak... pewnym wyrażeniem. Wynik działania funkcji również jest wyrażeniem, dlategomożemy ją wpleść w nasz tekst za pomocą operatora kropki.Powyższy kod zachowa się następująco:1.1. Wykonana zostanie funkcja date(), przyjmując za argument tekst 'd.m.Y', a jej wynikiem będzie aktualna data.2. Następnie powyżej opisany wynik zostanie połączony (operator .) z sąsiednimi tekstami, przez co powstanie

wyrażenie, np. "Dzisiaj mamy: 01.01.2010". Echo spowoduje wstawienie tego wyrażenia do kodu HTML stronywynikowej.

3.3. Następna linijka wywoła się analogicznie, wywołanie funkcji zakończy się zwróceniem wyniku, który zostaniepołączony z sąsiednimi wyrażeniami w jeden tekst.

Uwaga!Funkcja time() wyświetla czas uniksowy, dlatego nie można używać tej funkcji bezpośrednio do określenia ile czasu minęło odkonkretnej daty.

Uwaga!echo oraz podobna instrukcja print nie są funkcjami ani wyrażeniami! Są to instrukcje języka PHP - mają tę cechę, że nie musimystosować przy nich nawiasów. print może zachowywać się jak wyrażenie, które zawsze wartościowane jest przez 1.

ZmienneInnym pojęciem matematycznym jest zmienna, zawierająca pewną informację, przeważnie uzyskaną w trakciewykonywania skryptu. Można traktować ją jako pojemnik, do którego będziemy mogli w trakcie wykonywaniaskryptu wstawić dowolną informację, zapamiętując ją w ten sposób. Umożliwia to przechowywanie i przetwarzaniedanych w potrzebnym nam celu.Każdej zmiennej przypisujemy 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 (ew. podkreślenia). Dalsza część nazwy może jużzawierać cyfry. Stosując wielkie litery trzeba uważać, ponieważ dla interpretera są one rozróżnialne od małych, coma istotne znaczenie. $zmienna i $Zmienna to dwie różne zmienne. Przykłady poprawnych nazw zmiennych:

$a, $b, $foo, $_50, $_Foo, $moja_zmienna, $mojaZmienna3

Przykłady nieprawidłowych nazw:

$5a, $'a', $

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 skorzystać z operatora =. Po lewej stronie umieszczamy naszą zmienną,a po prawej dowolne wyrażenie określające wartość, która zostanie zapisana w zmiennej. Oto, jak wygląda to wpraktyce:

<?php

// inicjujemy zmienna $czas aktualnym czasem w sekundach od 1.1.1970

$czas = time();

$czas2 = $czas / 60;

echo 'Od 1.1.1970 minęło '.$czas.' sekund<br/>';

echo 'Od 1.1.1970 minęło '.$czas2.' minut<br/>';

Zmienne i tablice 27

echo 'Od 1.1.1970 minęło '.($czas / 3600).' godzin';

W powyższym przykładzie stworzyliśmy zmienne $czas i $czas2, zapisując w pierwszej z nich liczbę sekundzwróconą przez wywołanie funkcji time(). Następnie wykorzystaliśmy ją w obliczeniach w kolejnej linijce.Poznaliśmy w ten sposób jedno z zastosowań zmiennych. Zachowaliśmy w nich wynik działania jednej sekcjiprogramu, aby potem używać go wielokrotnie gdzie indziej, bez konieczności każdorazowego odwoływania się dofunkcji i zbędnego liczenia tego samego.PHP, w przeciwieństwie do innych języków programowania, ma bardzo liberalne reguły stosowania zmiennych. Nietrzeba ich nigdzie uprzednio deklarować, a interpreter sam nam dopasuje rodzaj informacji do naszych potrzeb(ustali tzw. typ zmiennej). Dana zmienna jest tworzona podczas pierwszego jej wykorzystania w skrypcie. Jest sporosytuacji, w których zachowanie to jest pożądane, lecz może też utrudnić pracę. Aby mieć świadomość zagrożenia,wyobraź sobie taką sytuację: programista pisząc szybko, może popełnić literówkę. Jeżeli zostanie ona popełnionapodczas wpisywania nazwy, PHP utworzy zmienną zawierającą tę literówkę. W teorii nie jest to żadnym błędem,jednak możemy się domyślać, że zapis obliczeń do innej zmiennej spowoduje błędne działanie programu, aprogramista będzie musiał spędzić dużo czasu na odnalezienie przyczyny. Słowo "teoria" nie znalazło się tuprzypadkowo. 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.initakie beztroskie podejście do zmiennych nie jest tolerowane. Tutaj PHP wymaga już, aby podczas pierwszego użyciazmiennej została jej przypisana jakaś wartość, ponieważ inaczej otrzymamy powiadomienie (ang. notice) o próbieodwołania się do nieistniejącej zmiennej. Popatrzmy sobie na ten przykład:

<?php

echo $a * 16 + 5;

Zmienna $a nie została w tym kodzie nigdzie początkowo 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świetlaniepowiadomień. Zrestartuj serwer i uruchom powyższy skrypt. Wynikiem powinno być "5". PHP bez pytaniapodstawił do $a wartość neutralną 0. Przywróć teraz poprzedni poziom (E_ALL | E_STRICT) i ponownie uruchomten 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 zajdziesytuacja, ż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 etapieprzetworzenia jakiejś informacji, umieszczana jest ona w nowej zmiennej. Takie podejście jest nieprawidłowe. Otoprzykłady "złych" skryptów:

<?php

$tekst = 'To jest jakiś tekst';

$tekstMaly = strtolower($tekst);

Zmienne i tablice 28

$tekstBezpieczny = addslashes($tekstMaly);

echo $tekstBezpieczny;

Przykład 2:

<?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 sposób rozwiązania tego problemu - bez użycia zmiennych (już trochę mniej czytelny):

<?php

echo addslashes(strtolower('To jest jakiś tekst'));

W drugim "złym" skrypcie w ogóle niepotrzebnie tworzymy zmienną - format daty możemy wpisać bezpośrednio dofunkcji.

<?php

echo date('d.m.Y');

Jednak nie zawsze jest to lepsza wersja. Jeżeli nasz skrypt bardzo często będzie formatować różne daty, a mybędziemy chcieli mieć możliwość zmieniania tych formatów w przyszłości, użycie zmiennych bardzo ułatwiłobysprawę - zmieniamy format daty w jednej zmiennej, zamiast w kilku czy kilkudziesięciu wywołaniach date().Nauczenie się, kiedy warto użyć zmiennych, a kiedy nie, to kwestia praktyki. W niniejszym podręczniku będziemyzwracali 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.

TypyDo tej pory miałeś okazję zauważyć, że istnieje w PHP 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 jeszczejedną, lecz stworzoną na jej własne potrzeby do zaznaczania niektórych rzeczy (powiemy o niej później).

Wielkości skalarne

Pierwszym typem skalarnym jest liczba całkowita. Jej angielskim określeniem jest integer, używany bywa skrót 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)

Zmienne i tablice 29

$a = 0x1A; // zapis szesnastkowy (odpowiednik dziesiętnego 26)

Możemy także korzystać z wartości ułamkowych zwanych liczbami zmiennoprzecinkowymi (ang. floating pointnumbers albo skrótowo float), które są przybliżeniem liczb rzeczywistych (ważne - nigdy nie zawierają dokładnejwartości, prawie zawsze jest to odrobinę różniąca się liczba, dlatego mówi się o "przybliżeniu"). Przy ichzapisywaniu 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;

Kolejnym typem jest typ logiczny (boolean), przyjmujący jedynie wartości FALSE i TRUE. Jest on używany przezwiele funkcji do zwracania rezultatu, czy operacja się powiodła. Wyrażenia porównawcze (czy równy, czy większyitd.) także generują wartości logiczne.Ostatnim z typów skalarnych jest ciąg tekstowy (ang. string). Zdążyliśmy już wspomnieć nieco o nim, m.in. oistnieniu dwóch składni zapisywania ciągów. Ta oparta na apostrofach dopuszcza mniejszy zestaw kodówformatują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, zamiast znaku

nowej linii';

Uruchom powyższy skrypt, aby zobaczyć jaki tekst zostanie wyświetlony. Spróbuj usunąć backslash sprzedapostrofu (w tekście: \') i zobacz, co się stanie. Skrypt się nie uruchomi, ponieważ wystąpił błąd składni. PHPnapotka w sumie trzy apostrofy, a więc między drugim i trzecim będzie nierozpoznany dla parsera tekst, natomiasttrzeci będzie niedomknięty.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: znak cudzysłowu \" backslash \\ znak nowej

linii \n i inne: \t \r \$";

Cudzysłowy zezwalają na "proste" umieszczanie wewnątrz tekstu wartości zmiennych, co zilustrujemy wprymitywnym przykładzie:

<?php

$czas = time();

echo "Aktualny czas w sekundach: $czas sek.";

Wartym zapamiętania jest fakt, że wstawianie zmiennych w ten sposób jest kilka razy wolniejsze, niż łączenie ich zciągiem operatorem kropki.

<?php

// tutaj można użyć cudzysłowu jak i apostrofu

echo "Aktualny czas w sekundach: ".time()." sek.";

Zmienne i tablice 30

Niektórzy początkujący programiści niezbyt rozumieją ideę tej możliwości - próbują wykorzystywać ciągi dowprowadzania wartości zmiennych jako parametrów do funkcji:

<?php

$formatDaty = 'd.m.Y';

echo date("$formatDaty");

Powinno się unikać takiej konstrukcji, i choć PHP ją akceptuje, nie jest to prawidłowe użycie tej struktury języka. Cowięcej, przy złożonych typach powoduje zniekształcenie danych. Jeżeli spotkasz kogoś piszącego w ten sposób,poinformuj go o tym. Do tego wyświetlanie danych zawartych w cudzysłowiach przebiega wolniej niż wapostrofach.

Inne typy

Typami złożonymi są w PHP tablice oraz obiekty. Tablice poznamy jeszcze w tym rozdziale, natomiast obiektamioraz samym programowaniem obiektowym zajmiemy się w dalszej części podręcznika.Istnieją jeszcze dwa typy specjalne: resource oraz NULL. Pierwszy z nich reprezentuje wszelkiego rodzajupołą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ć. Najlepiejnadaje się do tego polecenie unset():

<?php

$zmienna = 4856;

unset($zmienna);

echo $zmienna;

Zmiennej już nie ma, dlatego polecenie echo pokaże nam powiadomienie o nieistniejącej zmiennej.

Konwersja typów

PHP potrafi sam rozpoznać typ informacji przypisanej do zmiennej oraz automatycznie konwertować go wzależ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 nich (pomijając wiodące białe znaki) znaków jest cyfrą. W przeciwnymprzypadku 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

Zmienne i tablice 31

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

Więcej o operatorachDotychczas poznałeś już kilka operatorów, np. + czy =. Jak zdążyłeś już zauważyć, po obu stronach takiegooperatora stoją wyrażenia, na których wykonuje on operacje i zwraca wynik. Sam jest zatem wyrażeniem. Operatorymają 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.

++ Postinkrementacja (zwiększenie) $zmienna++ Reprezentuje wartość zmiennej, a następnie zwiększa ją o 1.

++ Preinkrementacja (zwiększenie) ++$zmienna Zwiększa wartość zmiennej o 1, a następnie reprezentuje ją.

-- Postdekrementacja (zmniejszenie) $zmienna-- Reprezentuje wartość zmiennej, a następnie zmniejsza ją o 1.

-- Predekrementacja (zmniejszenie) --$zmienna Zmniejsza wartość zmiennej o 1, a następnie reprezentuje ją.

Zwróć uwagę na cztery ostatnie pozycje. Wyszczególnione zostały tam tzw. operatory jednoargumentowe, czylitakie, 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ł podanypodwójnie, ponieważ w zależności od tego, czy postawimy go przed, czy po zmiennej, otrzymamy nieco innerezultaty. 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/>';

Zmienne i tablice 32

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, adopiero potem zwiększamy ją o jeden (dlatego zmiana widoczna jest dopiero w drugim stanie). ++$zmiennanajpierw powiększa, potem zwraca, w efekcie czego otrzymujemy w obu stanach liczbę "6". Identyczna zasadaobowiązuje operator --.Zajmijmy się teraz przypisywaniem danych do zmiennej. Wiemy już, że operator przypisania po lewej stroniewymaga zmiennej, po prawej wyrażenia, którego wartość trzeba w niej umieścić. Skoro operator to też jestwyrażenie, to jaką wartość ono reprezentuje? Okazuje się, że tę, która jest przypisywana. Możemy wobec tegozastosować sprytną sztuczkę, o której wbrew pozorom wie niezbyt wielu programistów. Zainicjujmy pięć zmiennychnaraz 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ęć zmiennychnaraz.Poznany już operator przypisania nie jest jedynym, jaki istnieje w PHP. Aby ułatwić modyfikację wartościzmiennych 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żebyć zerem.

%= $zmienna %=wyrazenie

$zmienna = $zmienna %wyrażenie

Umieszcza w zmiennej resztę z dzielenia tej zmiennej przez wyrażenie, któreoczywiś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 operacjachna 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/>';

Zmienne i tablice 33

$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ę zoperatorami służącymi do porównywania danych oraz podstawami operatorów logicznych. Będą nam one potrzebneprzy omawianiu instrukcji warunkowych i pętli.

TabliceZaznajomimy się teraz z tablicami, pierwszym złożonym typem danych. Cofnijmy się do czasów szkołypodstawowej/gimnazjum na lekcję matematyki i przypomnijmy nasz pierwszy kontakt z pojęciem funkcji. Była tammowa, ż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ż wszystkie są możliwymi wynikami funkcji. Gdyby to zapisać jako zupełnie osobne zmienne, mielibyśmyproblem z późniejszym dostaniem się do nich. Popatrz na to w ten sposób: użytkownik wprowadza argument, a mymamy dla niego odnaleźć wartość spośród tych podanych. Skąd PHP może wiedzieć, że akurat ta grupa zmiennychjest ze sobą w jakiś sposób powiązania, a tym bardziej znać regułę wybierania "tej właściwej"? Na razie nie jest to wogó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ę zapomocą indeksów.

Wiemy już, że tablica jest zmienną, która grupuje sobie mniejsze zmienne opisywane przez ich indeksy (wartości xw naszej tabelce funkcji). Najważniejsze jest jednak to, że przy odwoływaniu się do nich, potrzebny nam indeksmożemy określić zwyczajnym wyrażeniem! To oznacza, że zadanie wymienione akapit wyżej staje się realne. Zanimje 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. Należy pamiętać, że PHP (tak jak wiele popularnych języków programowania) zaczyna numerację zmiennych (indeksów) w tablicy od 0. Dlatego też możemy śmiało pominąć zapis 0 =>. Zapis ten

Zmienne i tablice 34

przyda się nam jeżeli nie chcemy zacząć numeracji od 0, ale np. od 1, piszemy wtedy array( 1=> 5.... Terazspró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śmyodczytać. Możemy teraz pokusić się o napisanie skryptu losującego elementy:

<?php

$tablica = array(0 => 5, 3, 8, 7, 9, 24, 15, 2, 19);

$liczba_losowa = rand(0, 8);

echo 'Pod numerem '.$liczba_losowa.' kryje się wartość

'.$tablica[$liczba_losowa];

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 czynieniaz 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, żekażdy element tablicy zachowuje się, jak zwykła zmienna, dlatego także możesz przypisywać do niego dowolnewartoś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;

var_dump($tablica);

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 formiekaż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(), przy czym var_dump() generuje gotowy kodHTML, otoczony znacznikami <pre> </pre> i sformatowany, podczas gdy print_r() zwraca tekstsformatowany, ale bez znaczników HTML.Zobacz teraz, jak zachowuje się $tablica, kiedy próbujemy ją wywołać samodzielnie:

Zmienne i tablice 35

<?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 przedwprowadzaniem danych do funkcji jako funkcja("$zmienna");. Gdyby w zmiennej była tablica, do funkcjizamiast niej dotarłby napis Array i całość przestałaby działać. Osobną kwestią jest wydajność takiego zmuszania dokonwersji 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.

Przypisy[1][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.

Formularze

FormularzeW tym rozdziale zajmiemy się podstawami komunikacji skryptu PHP z przeglądarką.

Protokół HTTP

Działanie protokołu HTTP.

Podstawą funkcjonowania stronWWW jest protokół HTTP, któregoużywa przeglądarka i serwer. Zasadajego działania jest bardzo prosta. Gdychcemy obejrzeć dokument podpodanym adresem URL, wysyłamy doserwera tzw. żądanie HTTPzawierające lokalizację zasobu orazgarść informacji o nas samych. Serwerodnajduje lub generuje (np. przy pomocy PHP) odpowiedni dokument i odsyła wszystko jako odpowiedź HTTP.Odpowiedź zawsze zawiera pewną ilość nagłówków informacyjnych oraz opcjonalną treść, w której przesyłany jestdokument. Przeglądarka odbiera wszystko i rozpoczyna działanie. Protokół jest stosowany zarówno do pobieraniakodu HTML strony, jak i znajdujących się na niej obrazków, ściągania plików i innych danych multimedialnych.Rodzaj pobieranej zawartości jest określany przez nagłówki.

Skrypty PHP zawsze pracują po stronie serwera, generując odpowiedzi HTTP na przychodzące do niego żądania.Przeważnie koncentrujemy się jedynie na budowaniu treści, ponieważ interpreter potrafi samodzielnie skonstruowaćpodstawowy zestaw odpowiednich nagłówków, który co najwyżej uzupełniamy. Bardzo często do wygenerowaniastrony potrzebne są dodatkowe informacje, które najczęściej przechowywane są w bazie danych, a rzadziej - wplikach. Skrypty pobierają z nich dane, poddają je obróbce i osadzają w kodzie HTML, który jest odsyłany doklienta.

Formularze 36

Uwaga!Gdy przeglądarka zaczyna wyświetlać dokument, skrypt PHP już dawno skończył swoją pracę. Pamiętaj, że działanie Twoich skryptównie może wykroczyć poza ramy protokołu HTTP, w szczególności reagować na zdarzenia w przeglądarce (np. kliknięcie myszką), animodyfikować fizycznie wysłanej treści. Do takich celów wykorzystywane są inne języki i technologie: JavaScript, AJAX.

Istotną częścią protokołu HTTP są rodzaje żądań (zwane "metodami") informujące o tym, co próbujemy zrobić. Dwapodstawowe to:1. Żądania GET - zwyczajne pobieranie dokumentu z serwera.2. Żądania POST - wysłanie pewnych danych na serwer.Istnieją jeszcze inne metody, które są coraz powszechniej stosowane w większych aplikacjach WWW, jednak naniektórych serwerach są one z nieznanych powodów poblokowane. Protokołem HTTP zajmiemy się dokładniej wdalszej części podręcznika, tymczasem na razie będą nas interesować te dwie metody. Pierwsza z nich jestwykorzystywana podczas zwykłego pobierania z serwera dokumentu, natomiast druga - przy formularzach.

Ogólnie o danych wejściowychW żądaniu HTTP przenoszonych jest wiele informacji o tym, co użytkownik chce obejrzeć oraz o nim samym.Wielu danych dostarcza także sam serwer HTTP. Wszystkie one są podstawą dla aplikacji PHP do wygenerowaniaodpowiedzi. Mechanizm ich odbierania ewoluował stopniowo. Pierwsze wersje języka rejestrowały wszystkienadesłane parametry, dane formularzy itd. jako zmienne, lecz było to wyjątkowo niebezpieczne. Dodając noweparametry można było rejestrować nowe zmienne, które mogły nadpisać zmienne używane w kodzie aplikacjipowodując jego błędne działanie (np. zyskanie praw administratora strony przez atakującego). Programista nie mógłtakże policzyć, ile danych właściwie trafiało do skryptu i gdzie są one zawarte.Wszystko zmieniło się wraz z pojawieniem się PHP4. Obecnie wszystkie pola są rejestrowane w specjalnych,tworzonych przez skrypt tablicach asocjacyjnych posegregowane według miejsca, z którego nadeszły. PHP potrafiodbierać informacje:•• Charakterystyczne dla metody GET (adresy URL)•• Charakterystyczne dla metody POST (zawartość formularzy)•• z serwera•• z ciasteczek•• z sesji (emulowanych przez PHP)Tutaj zajmiemy się trzema pierwszymi pozycjami. Ciastka oraz sesje zostaną omówione w dalszych rozdziałachpodręcznika.

Adresy URLDo adresu URL można dołączać po znaku zapytania dodatkowe zmienne, np.www.example.com/strona.php?zmienna=wartosc&inna_zmienna=wartosc. Są oneprzechowywane w tablicy $_GET. Ten sposób przekazywania danych wykorzystujemy tylko, gdy skrypt niewykonuje operacji mających efekty uboczne (np. może być wyszukiwanie, ale już nie dodawanie lub usuwanierekordów) lub do przesyłania danych kontrolnych. W przeciwnym wypadku roboty indeksujące stronę i proxyładujące strony z wyprzedzeniem mogą niechcący wykonywać operacje na serwerze. Ponadto cache przeglądarki idostawców internetowych może spowodować zignorowanie zapytań.Przyjrzymy się zawartości tablicy:

<?php

var_dump($_GET);

Formularze 37

Wywołując skrypt normalnie: http:/ / localhost/ ~programowanie_php/ nazwaskryptu. php otrzymamy następującyrezultat:

array(0) { }

Oznacza to, że nie otrzymaliśmy tą drogą żadnych danych. Dodajmy teraz do adresu ciąg ?parametr=wartosc. Poodś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(sizeof($_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 sizeof()pojawiająca się w kodzie zwraca ilość elementów w tablicy. Sprawdzamy w ten sposób, czy użytkownik podał to, cotrzeba. Kontrola nadchodzących danych jest niezwykle istotna i nigdy nie wolno jej zlekceważyć. Pominięcie tegoaspektu zazwyczaj kończy się dla skryptu tragicznie, bo jeżeli coś jest do zepsucia, na pewno znajdzie się ktoś, ktotego 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 [1]. Dla lepszego efektustwó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 XHTML</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>

Formularze 38

</form>

</body>

</html>

Choć tworzenie formularzy teoretycznie pasuje do następnej sekcji, tak naprawdę w tym momencie zwyczajnieoszukujemy. Wypełnij ten formularz i wyślij go, a zobaczysz, że przeglądarka dokleiła do pliku podanego wznaczniku FORM parametry utworzone na podstawie pól! Tak więc nasz skrypt nawet nie ma możliwościstwierdzenia, skąd dane do niego przyszły! Poznajmy zatem metodę POST...

FormularzeObsługa formularzy z prawdziwego zdarzenia, którymi można przesyłać setki informacji, odbywa się dosyćpodobnie, jak w przypadku użycia w formularzu metody "get" do przesyłania adresów z parametrami. Różnica jesttaka, że wszystko wysyła się wyłącznie z formularza, który posiada parametr "method" ustawiony na "post" oraz żekorzysta się z tablicy $_POST wewnątrz samego skryptu. Przeróbmy nasze ostatnie dzieło tak, aby pracowało wten 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ż transmisjaodbywa się niejako innym kanałem.

Nazwy tablic są trochę nieprecyzyjne. To, że wysłaliśmy żądanie metodą POST wcale nie oznacza, że tablica $_GET będzie wtedyzawsze pusta. Możliwe jest jednocześne wysłanie danych formularzem wraz ze zmiennymi w adresie URL, co sprawi, że obie tablice będąposiadać jakąś zawartość.

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"/>

Formularze 39

- skrypt otrzymuje $_POST['nazwa'] z wartością wpisaną w pole formularza.

<input type="hidden" name="nazwa" value="wartosc"/>

- skrypt otrzymuje $_POST['nazwa'] z wartością wpisaną w danym znaczniku. Użyteczne do przesyłaniaformularzem 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.

SerwerNa 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 przypadkudalsze informacje są niepewne. Serwerowi potrzebny jest wyłącznie ten adres, aby móc gdzieś wysłać rezultat, apola $_SERVER['HTTP_X_FORWARDED_FOR'] lub $_SERVER['HTTP_CLIENT_IP'] są bardzoniepewne - 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 skrypcieforów dyskusyjnych phpBB i jeżeli naprawdę zależy Ci na nim, pozostań przy REMOTE_ADDR, a resztę traktujwyłą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ńmyzatem przy takiej postaci, a przetwarzaniem nagłówków przeglądarek zajmiemy się kiedy indziej.

Formularze 40

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ą ludzietrafiający do nas z wyszukiwarek. Zauważ - szukane frazy zazwyczaj dołączane są właśnie do adresów URL i w tensposó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ępnychrozdziałów w miarę potrzeby.

Przypisy[1] http:/ / localhost/ ~programowanie_php/ nazwaskryptu. php?imie=Adam& nazwisko=Kowalski

Struktury kontrolne

Struktury kontrolneProgramy wykonujące szereg zawsze tych samych instrukcji nie pozwalają rozwinąć skrzydeł. Jeżeli aplikacja mabyć w pełni interaktywna, musi umieć reagować na poczynania użytkownika lub sytuacje kryzysowe na podstawiezadanych warunków. Tu do akcji wkraczają instrukcje kontrolne. Są one podstawowym budulcem wielu algorytmówi ich poznanie jest niezbędne. Za to my zyskamy pełną kontrolę nad wszystkim, mogąc efektywnie reagować nawszystkie zdarzenia.Jedną z instrukcji kontrolnych - if - widziałeś już w poprzednim rozdziale. Zauważyłeś też pewnie, iż nie była onazakończona średnikiem, a zamiast tego pojawiały się przy niej nawiasy klamrowe tworzące tzw. blok kodu. Jest tocią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, żewiększość zaawansowanych programistów nie umie z niej korzystać, a ponadto wymaga ona od nas pewnejdodatkowej wiedzy. Oto lista wszystkich instrukcji:1.1. Instrukcja if2.2. Instrukcja switch3.3. Instrukcja for4.4. Instrukcja while5.5. Instrukcja do while6.6. Instrukcja foreach

Instrukcja if 41

Instrukcja if

Instrukcja ifZ tą instrukcją zetknęliśmy się już przy okazji omawiania formularzy. Przypomnijmy 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, orazopcjonalne 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łasię z plikiem itd. Poniżej napiszemy skrypt, który będzie potrafił rozwiązywać równanie kwadratowe

. Przypomnijmy, że ilość jego rozwiązań rzeczywistych zależy od wartości tzw.współczynnika Δ ( ). Jeżeli obliczony wynik (Δ) jest dodatni, istnieją dwa rozwiązania. Jeżeliujemny - 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'];

Instrukcja if 42

// 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>';

}

elseif($delta < 0)

{

// 6

echo 'Delta ujemna. Brak rozwiązań w zbiorze liczb rzeczywistych!';

}

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. Numeryodpowiednich fragmentów zaznaczone są w kodzie komentarzami jednolinijkowymi.1. 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 tejzmiennej nie ma. Musimy wtedy podstawić za nią neutralną wartość 0, ponieważ inaczej skrypt będzie namzgłaszać powiadomienia.

2. Tutaj zaczynamy właściwy algorytm. Najpierw sprawdzamy, czy rzeczywiście mamy do czynienia z równaniemkwadratowym. Parametr a musi być różny od zera. W razie problemów instrukcją die() zatrzymujemy skryptw tym miejscu.

3. Liczymy współczynnik Δ. Funkcja pow(liczba, potega) podnosi podaną liczbę do odpowiedniej potęgi idziała szybciej, niż ręczne mnożenie wartości.

4.4. Pierwszy wariant - kiedy Δ jest dodatnia...5. 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 pierwiastekz podanej liczby. Zwróć uwagę na użycie nawiasów do zasugerowania właściwej kolejności działań.

6.6. Gdy Δ jest ujemna, równanie nie ma rozwiązania.7.7. 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. Jejformalna składnia jest następująca:

<?php

if(wyrazenie)

{

// blok kodu

}

Instrukcja if 43

elseif(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, przyczym ilość elseif 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• elseif - 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 wartowiedzieć, 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 przydatnychoperatoró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

//Zakładamy, że do skryptu wysłano formularz z polami liczba1 i liczba2

if($_POST['liczba1'] == 1 && $_POST['liczba2'] == 2)

{

Instrukcja if 44

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

//Zakładamy, że do skryptu wysłano formularz z polami liczba1 i liczba2

if($_POST['liczba1'] == 1 || $_POST['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śnieniadomagają 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. Dlategoskrypt 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 tenprzydaje się przy niektórych funkcjach zwracających różne typy wartości w zależności od powodzenia operacji.

Uwaga!Operatory = i == są w PHP bardzo podobne, dlatego czasem przy ich wpisywaniu zdarzają się pomyłki. Jeżeli twoja instrukcjawarunkowa zachowuje się tak, jakby jej warunek był zawsze prawdziwy, upewnij się, że wstawiłeś tam właściwy operator!

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ść pustą (np. NULL, 0 albo pusty ciąg

tekstowy).• 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ą.

Instrukcja switch 45

Instrukcja switch

Instrukcja switchInstrukcja switch zwana jest także instrukcją wyboru. Jej działanie jest podobne do szczególnego przypadkupoznanej ostatnio instrukcji warunkowej. Pokażemy to na przykładzie.Nietrudno znaleźć witryny internetowe, które w jednym pliku grupują kilka różnych zadań wykonywanych wzależności od parametru, np. index.php?act=dodaj, index.php?act=usun itd. Możemy tozaprogramować 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, ale również czytelność.Przypuśćmy, że z jakiegoś powodu musimy zmienić miejsce, z którego pobieramy informację o akcji. Trzeba tozrobić w czterech miejscach, a tych może być w teorii jeszcze więcej. Znacznie łatwiejszą w użyciu, a przy tymwydajniejszą alternatywą jest instrukcja switch. Działa ona w ten sposób, że wybiera spośród dostępnego zbioruokreśloną wartość na podstawie wartości pewnego wyrażenia i wykonuje zdefiniowany dla niej kod. Przepiszmy razjeszcze powyższy przykład:

<?php

if(!isset($_GET['act']))

{

$_GET['act'] = 'index';

}

switch($_GET['act'])

{

case 'dodaj':

echo 'Dodawanie danych';

break;

case 'edytuj':

echo 'Edycja danych';

break;

Instrukcja switch 46

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ówklamrowych 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żeklauzula default: pisana na samym końcu opisująca domyślne zachowanie. Jej już nie musimy dodawać, jeżelitego nie potrzebujemy, niemniej często się ona przydaje. Zwróć uwagę na komendę break; stojącą na końcukaż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 prostegopowodu: 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

if(!isset($_GET['act']))

{

$_GET['act'] = 'index';

}

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 danychWywołując skrypt jako nazwapliku.php?act=dod, zobaczymy: Jak nie damy komendy "break", to pokażenam się też... Dodawanie danychPHP wykonał zarówno stan "dod", jak i następujący po nim "dodaj", gdyż w tym pierwszym brakowało komendybreak.Kod stanu może zawierać inne struktury kontrolne:

<?php

if(!isset($_GET['act']))

Instrukcja switch 47

{

$_GET['act'] = 'index';

}

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żyprzetworzyć, 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 PHPzaletą jest, że "wie", jakie są wszystkie stany. Instrukcja warunkowa if jest bardziej ogólna i tam interpretersprawdza po prostu po kolei wszystkie warunki, aż natrafi na pasujący, nie zagłębiając się w jakieś większezależności między danymi. Switcha nie należy używać, kiedy mamy tylko dwa stany, gdyż takie zagranieprzypominałoby wytoczenie haubicy do zabicia komara. Instrukcja ta nie sprawdza się także przy wszystkichbardziej ogólnych warunkach rodzaju "mniejszy, większy".

Instrukcja for 48

Instrukcja for

Instrukcja for

PętleWszystkie 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 zliczamyilość wywołań pętli i na podstawie tego określamy, czy trzeba ją przerwać, czy nie. W for definiujemy trzywyraż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 od10, pętla ma się powtarzać. Przy każdym cyklu należy zwiększyć wartość $i o 1.

Uwaga!Uważaj na warunek końca pętli! Jeżeli niepoprawnie go zdefiniujesz, pętla może nie wykonać się wcale albo też powtarzać się wnieskończoność. Drugi przypadek nie jest aż taki groźny, ponieważ PHP automatycznie przerywa wykonywanie skryptu, jeżeli trwa onoponad 30 sekund.

Proste wyświetlanie tablicPę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 wierszezapisane 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++)

Instrukcja for 49

{

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 stosujemyjeszcze 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, dlategooddzielamy 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 niuansietechnicznym. Pierwsza z konstrukcji pobiera ilość elementów tablicy na samym początku. Jeżeli któryś cykl pętlidoda 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 wilości wykonanych cykli. Sposób ten jest jednak mniej wydajny od pierwszego.

Break i ContinuePrzy okazji omawiania instrukcji switch poznaliśmy komendę break. Ma ona bardzo duże zastosowanie przypętlach, które potrafi przerywać. Istnieje także kolejne polecenie: continue. Przerywa ono jedynie aktualny cykl pętlii powoduje rozpoczęcie następnego.Mamy prosty ciąg tekstowy:

Komenda; Komenda; Komenda; Komenda. To już pomijamy.

Wiemy o nim trzy rzeczy:1.1. Spacje ignorujemy2.2. Kropka oznacza koniec wprowadzania komend3.3. Średnik separuje komendyNaszym zadaniem jest wprowadzenie komend do tablicy, aby można je było łatwiej przetwarzać. Skrypt tenbę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" utworzonympierwszym 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ągutekstowym. Wskazuje na aktualnie przetwarzany znak:

for($i = 0; $i < strlen($tekst); $i++)

{

Implementujemy możliwość pierwszą. Spacje ignorujemy, dlatego przy ich napotkaniu przerywamy aktualny cyklpętli komendą continue i przechodzimy do następnego:

Instrukcja for 50

if($tekst[$i] == ' ')

{

continue;

}

Zauważ, jak odwołujemy się do określonego znaku wewnątrz ciągu: $tekst[$i]. Numer znaku (począwszy odzera) podajemy jako indeks w nawiasach kwadratowych, identycznie jak w tablicach.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 izainicjować 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ą var_dump():

}

echo '<pre>';

var_dump($tablica);

echo '</pre>';

Zapytajmy się, jak przerwać pętlę, jeżeli jesteśmy aktualnie w instrukcji switch? Wywołanie break i continuebę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 instrukcjiwyboru (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])

Instrukcja for 51

{

case ' ':

continue 2;

case '.':

break 2;

case ';':

$t++;

$tablica[$t] = '';

break;

default:

$tablica[$t] .= $tekst[$i];

}

}

echo '<pre>';

var_dump($tablica);

echo '</pre>';

Przy stanach spacji oraz kropki wywołujemy komendy continue oraz break z parametrem 2, aby podkreślić, żedotyczą one pętli for, a nie instrukcji switch. break w kodzie obsługi średnika nie ma parametru, więc odnosi się doinstrukcji switch.

Instrukcja while

Instrukcja whileKolejną pętlą jest while, będąca znacznie prostszą odmianą poznanej ostatnio pętli for. Wymagany jest tu jedyniewarunek 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ż wpierwszym 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ęciapewnego stanu. Sztandarowym przykładem jest czytanie pliku, gdzie takim specyficznym stanem, w którym musimyprzerwać naszą pracę, jest jego koniec. Zastosowanie pętli while będzie tu o wiele lepsze, niż obliczanie napodstawie 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

Instrukcja while 52

}

fclose($f); // 4

Opis działania:1. Otwieramy plik do odczytu. Uchwyt do niego zapisujemy w zmiennej $f. W ten sposób poznaliśmy nowy typ

danych: Resource, czyli zasób.2.2. Dopóki nie osiągniemy końca pliku...3.3. Pobieraj z niego kolejne 16-znakowe bloki.4.4. 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 zrozdział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 dopobierania rekordów.

Instrukcja do while

Instrukcja do whileW przeciwieństwie od normalnego while, tutaj warunek sprawdzany jest na końcu, tak więc pętla zostanie wykonanaprzynajmniej 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 imoż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órymzainstalowałeś PHP. Następnie wydaj następujące polecenie: php -f /scieżka/do/skrypt.phpJako wartość parametru -f musisz podać pełną ścieżkę do skryptu, który chcesz uruchomić.

Instrukcja do while 53

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.

Instrukcja foreach

Instrukcja foreachOstatnią pętlą jest foreach. Ma ona specyficzne zastosowanie, ponieważ służy wyłącznie do przeglądania zawartościtypów złożonych: tablic oraz obiektów. Kod wewnątrz niej jest powtarzany dla każdego z elementów tablicy, a onsam jest na ten czas przenoszony do tworzonej przez pętlę zmiennej. Wróćmy do naszego przykładu z pętlą forodczytują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 wzmiennej $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)

Instrukcja foreach 54

{

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, doczego 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ątrzpę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>';

var_dump($plik);

echo '</pre>';

Wewnątrz pętli próbujemy przypisać wartość do zmiennej $linia. Owszem, udaje nam się to, ale nowa treść nietrafia w ogóle do tablicy i systemowe wyświetlenie jej zawartości ukazuje brak jakiejkolwiek reakcji. Czy zatemmożliwe jest dokonywanie przypisań wewnątrz foreach? Oczywiście. Są dwie sztuczki. Pierwsza polega nawykorzystaniu 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>';

Instrukcja foreach 55

var_dump($plik);

echo '</pre>';

Rozwiązanie to jest nieco trikowe, ale działa. Możemy jednak zastosować coś innego. PHP posiada pewien elementzwany referencją. Ogólnie rzecz biorąc jest to odnośnik do zmiennej, który zachowuje się tak, jak ona. Modyfikacjareferencji powoduje także modyfikację oryginalnego elementu. Począwszy od PHP 5, referencje można używać wpę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>';

var_dump($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 elementyskładni.

PoradaJeżeli elementy twojej tablicy są bardzo duże i liczą np. po kilka kilobajtów, użyj referencji do zwiększenia wydajności. Normalniemusiałyby być one w całości kopiowane, co tylko pochłaniałoby zbędnie moc. Referencja natomiast utworzy do nich zwyczajny odnośniki czasochłonne kopiowanie nie będzie miało miejsca.

Funkcje 56

Funkcje

Funkcje

Z Wikipedii: Funkcja jest definiowana jako relacja pomiędzy elementami zbioru X (dziedziny) i elementami zbioru Y(przeciwdziedziny), o tej własności, że każdy element zbioru X jest w relacji z dokładnie jednym elementem zbioru Y.

Funkcje są pojęciem znanym z matematyki. Przeniesione na grunt informatyki, zachowują się podobnie i mająpodobne zastosowanie: reprezentują jakieś przekształcenie, jakie można wykonać na danych. Dane wejściowe, czylipatrząc na definicję - element zbioru X - wymieniamy jako argumenty funkcji, a po jej wykonaniu otrzymujemywynik, czyli element zbioru Y. W matematyce jedną z najbardziej znanych funkcji jest sinus, który dla każdejwartości kąta "produkuje" stosunek długości odpowiednich boków w trójkącie prostokątnym zawierających ten kąt.Sinus może być jak najbardziej poprawną funkcją w PHP (i rzeczywiście, język ten udostępnia programiście funkcjęsin()), ale ponieważ programy wykonują dużo więcej różnorodnych operacji, możemy spotkać również bardziejpraktyczne funkcje z punktu widzenia generowania stron WWW, np. strip_tags(), która z podanego tekstuusuwa znaczniki HTML. Oprócz tego, programista może samodzielnie tworzyć własne funkcje i omówienie tegozagadnienia jest głównym celem rozdziału, który właśnie czytasz.W programowaniu będziemy przede wszystkim chcieli, aby zdefiniować sobie zestaw funkcji, w którym zamkniemyczęsto wykonywane operacje. Może to być np. obsługa błędów - zamiast kopiować i wklejać odpowiedzialny za niąkod w różne miejsca skryptu, opakowujemy go w funkcję, do której niezbędne dane podajemy jako argumenty i poprostu wywołujemy ją. Dobry podział skryptu na funkcje ma jeszcze jedną zaletę - jeśli w jakimś kawałku koduznajdziemy błąd, wystarczy poprawić go tylko w jednej funkcji, a zmiana będzie widoczna wszędzie.Znajomość funkcji to jeden z fundamentów programowania, dlatego w tym rozdziale niezbędna jest szczególnauwaga.

Tworzenie własnych funkcjiKażda funkcja musi posiadać pewną unikalną nazwę, która pozwoli odróżnić ją od innych. Musimy także określić,jakie argumenty przyjmuje i co właściwie robi. Odpowiada za to następująca konstrukcja:

function nazwaFunkcji(argumenty)

{

// 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 argument: $tekst. Zauważmy, że nazwę tę piszemy ze znakiem

Funkcje 57

dolara. Gdybyśmy chcieli podać więcej argumentów, oddzielamy je od siebie przecinkami. Jeżeli funkcja nie będzieużywać żadnego argumentu, za nazwą pozostawiamy puste nawiasy. Abyśmy mogli z argumentów skorzystać,muszą one mieć swoje nazwy, gdyż wewnątrz funkcji stają się zwykłymi zmiennymi.Kod funkcji jest dowolnym poprawnym kodem PHP i można w nim umieścić dowolną rzecz, z tworzeniem kolejnejfunkcji włącznie. Jednak zauważmy, że nasza pierwsza funkcja nie zwraca wartości. Zamiast tego wynik wysyła odrazu 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.

Uwaga!Słowo kluczowe return możemy umieścić w dowolnym miejscu funkcji, lecz musimy mieć świadomość, że zwrócenie przez funkcjęwartości równoznaczne jest z jej zakończeniem! Kod umieszczony za return nie wykona się, a jeśli umieścimy to w pętli, zwróceniewartości spowoduje jej automatyczne przerwanie wraz z całą funkcją.

Zwróćmy uwagę na fakt, iż przy deklarowaniu argumentów nie podajemy żądanego typu danych. O to musimyzadbać 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 - zarazwyjaś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;

}

}

Funkcje 58

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:1.1. Oto deklaracja funkcji wyświetlania katalogów. Znak równości oraz wartość po drugim argumencie oznacza, że

jest on opcjonalny. Jeżeli go nie podamy przy wywołaniu, przyjmie on wartość domyślną. Opcjonalnychargumentów może być więcej, z tym że podajemy je zawsze na końcu. W naszej funkcji opcjonalny argumentokreśla, czy funkcja ma zwracać wszystko (domyślny stan: 0), czy jedynie pliki (stan 1).

2.2. Otwieramy katalog o podanej ścieżce3.3. Pętla pobierająca kolejne elementy katalogu, dopóki istnieją.4. 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 takimkontekście ją wywołujemy. Jeżeli rzeczywiście mamy plik, dodajemy go do tablicy wynikowej jako kolejnyelement.

5.5. Niepodanie indeksu oznacza: "utwórz nowy element o indeksie MAX+1".6. 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. Mamytu kolejno: czy element jest katalogiem, czy nie ma on nazwy "." i ".." oraz czy funkcja ma od programistyzezwolenie na pobieranie katalogów.

7.7. Zamykamy katalog8.8. Zwracamy tablicę jako wynik9.9. A oto mała niespodzianka. Począwszy od PHP 5 można definiować typy argumentó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ć tutajnp. liczbę, PHP zgłosiłby błąd.

10.10. Wywołanie funkcji z jednym argumentem i przekierowanie wyniku do funkcji wyświetlającej listę.11.11. Ponowne wywołanie, lecz tym razem żądamy wyłącznie plików.W ten sposób poznaliśmy już niemal wszystko, co dotyczy definiowania argumentów. Pozostała jeszcze jedna rzecz,a mianowicie pobieranie ich zupełnie nieokreślonej liczby. Uruchom taki oto skrypt:

<?php

function funkcja($a)

{

Funkcje 59

echo $a;

}

funkcja(1, 2, 3, 4, 5);

Nasza funkcja pobiera tylko jeden argument, lecz my podajemy mu pięć. Mogłoby się wydawać, że spowodujemytym samym błąd, jednak tak się nie stanie. PHP nadmiarowych argumentów nie ignoruje. Choć niezadeklarowaliśmy żadnego z nich podczas tworzenia funkcji, istnieje pewien sposób, aby je wydostać. Jest tofunkcja func_get_args() zwracająca tablicę z wartościami wszystkich argumentów, które przekazaliśmy dofunkcji.

function funkcja()

{

$argumenty = func_get_args();

echo '<ul>';

foreach($argumenty 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 argumentu. Obie te funkcje operująbezpośrednio na funkcji, dlatego PHP nakłada kilka ograniczeń na ich stosowanie. Najlepiej jest wywołać je nasamym początku tworzonej funkcji, aby uniknąć kłopotów.

Widzialność zmiennychNapiszmy 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. Pouruchomieniu okazuje się, że tylko bezpośrednie wyświetlenie podało nam prawidłowy wynik. echo wewnątrzfunkcji nie pokazało żadnej wartości. Dlaczego? Zmienna przecież istnieje. I owszem, lecz tylko w tej częściskryptu, w której została utworzona. PHP ma zaimplementowaną tzw. widzialność zmiennych - dla każdej funkcjitworzony jest osobny stos, niezależny od drugiego. Jeżeli więc utworzymy zmienną bezpośrednio w skrypcie, niebędzie ona istnieć w żadnej z naszych funkcji, gdyż te mają własne stosy. Ma to wyeliminować konfliktynazewnictwa.

Funkcje 60

Istnieje jednak sposób na powiedzenie PHP, że używana w funkcji zmienna jest już stworzona w stosie głównymskryptu. 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 stosugłównego. Działa ono nawet wtedy, jeśli zmienna o danej nazwie nie istnieje, dlatego korzystanie z funkcjiużywających global musi być bardzo uważne.

Uwaga!Używanie global w swoich funkcjach jest obecnie uznawane za bardzo złą praktykę programistyczną z powodu wielu problemów ztestowaniem i przewidywalnością działania tak napisanych programów.

StaticInną przydatną rzeczą jest przenoszenie niektórych zmiennych między wywołaniami tej samej funkcji. Dzięki temunie musimy zapamiętywać ich wartości w globalnych tablicach, narażając się na konflikty nazewnictwa. Aby tegodokonać, wystarczy zadeklarować wybrane zmienne jako static, a ich wartość zostanie zapamiętana do następnegowywołania.

<?php

function koloruj()

{

static $i = 0;

$i++;

if($i % 2 == 0)

{

return '#ffffff';

}

return '#cccccc';

} // end koloruj();

echo '<table width="30%">';

for($x = 0; $x < 10; $x++)

{

echo '<tr><td bgcolor="'.koloruj().'">'.$x.'</td></tr>';

}

echo '</table>';

Funkcje 61

Powyższy przykład koloruje naprzemiennie wiersze w tablicy. Sztuczka ta nie wymaga finezji: po prostuzwię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 $imiędzy kolejnymi wywołaniami przy pomocy słowa kluczowego static. Gdybyś usunął tę linijkę, funkcja cały czaszwracałaby ten sam kolor, gdyż zmienna tworzona byłaby w kółko od nowa z domyślną wartością 0.

RekurencjaRekurencja w programowaniu oznacza odwoływanie się funkcji do samej siebie. Jest użyteczna, w niektórychsytuacjach wręcz niezbędna, lecz pochłania znacznie więcej zasobów, dlatego należy korzystać z niej ostrożnie.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łujesamą siebie (1), z doklejoną do dotychczasowej ścieżki nazwą tego katalogu. W ten sposób możemy pobrać całedrzewo, niemniej w przypadku rozbudowanych struktur może trwać to nawet kilkanaście sekund!Sprawdźmy następujący skrypt:

<?php

function wypisz($tekst, $ile)

{

echo $ile.': '.$tekst.'<br/>';

if($ile > 0)

{

wypisz($tekst, $ile - 1);

}

} // end wypisz();

wypisz('Witaj', 30);

Funkcje 62

Demonstruje on pewną właściwość rekurencji - możemy nią zastąpić pętle, odpalając naszą funkcję rekurencyjnieokreśloną liczbę razy. Jako licznik służy nam wartość argumentu $ile. Gdy jest ona większa od zera, funkcjawywołuje samą siebie, zmniejszając go o 1, aż dojdziemy do zera. Nie ma w tym nic dziwnego. Takie rozumieniefunkcji jest podstawą tzw. programowania funkcyjnego charakterystycznego dla takich języków programowania, jakOcaml czy Erlang. Zamiast pętli, tworzymy funkcje wywoływane rekurencyjnie.Brzmi to interesująco, lecz w PHP natrafia na bardzo ważną przeszkodę. Zmieńmy powyższy kod tak, aby wypisałnasz tekst 200 razy i wykonajmy go. Niespodzianka! Po dojściu do mniej więcej połowy otrzymaliśmy błąd:

Fatal error: Maximum function nesting level of '100' reached, aborting!

Gdy parser wywołuje nową funkcję, musi zapamiętać gdzieś wartości wszystkich zmiennych oraz ogólnie cały standotychczasowej. Odkłada go na tzw. stos i po zakończeniu wewnętrznej funkcji, pobiera go stamtąd z powrotem.Stos ten ma jednak ograniczoną głębokość (w PHP wynoszącą 100), dlatego nie możemy w sposób zagnieżdżonywywoływać funkcji w nieskończoność. Doprowadziłoby to bowiem do szybkiego wyczerpania się pamięci.Wiemy, że każdą pętlę da się zapisać w postaci rekurencyjnej, ale zależność ta działa też w drugą stronę. Każdąrekurencję da się zapisać przy pomocy zwykłych pętli oraz instrukcji warunkowych, choć w pewnych przypadkachmoże to być zadanie bardzo trudne. Oto prosta implementacja rekurencyjna funkcji silnia, która w matematycezdefiniowana jest następująco:

<?php

function silnia($n)

{

if($n > 0)

{

return $n * silnia($n - 1);

}

return 1;

} // end silnia();

echo silnia(6);

Jej wersja iteracyjna, czyli zapisana przy pomocy pętli, jest w PHP dużo wydajniejsza:

<?php

function silnia($n)

{

$wynik = 1;

while($n > 0)

{

$wynik *= $n--;

}

return $wynik;

} // end silnia();

echo silnia(6);

Funkcje 63

Użyteczne funkcjePHP dysponuje kilkoma funkcjami do zarządzania funkcjami. Brzmi to może dość śmiesznie, lecz w praktyce bywabardzo przydatne.Na początek zastanówmy się, kiedy PHP sprawdza, że funkcja nie istnieje. Okazuje się, że nie dzieje się to wmomencie kompilacji, lecz wykonywania skryptu. Ma to swoje uzasadnienie przy konstruowaniu modułowychskryptów (zajmiemy się nimi w następnym rozdziale). Pierwszy plik PHP odwołuje się do funkcji zdefiniowanych wdrugim, lecz ten z kolei ładowany jest później. Gdyby z powodu nieistnienia jednej z nich skrypt byłby przerywanyw momencie kompilacji, skrypt nie miałby żadnych szans na działanie. Ponadto nie dałoby się pracować zeskryptami 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 podanaprzez nas funkcja istnieje. Narzędziem tym można sondować zarówno nasze własne, jak i definiowane przezrozszerzenia 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 funkcjiextension_loaded(), która ma tę przewagę, że działa także z rozszerzeniami obiektowymi, w którychzwykłych funkcji nie ma:

<?php

if(extension_loaded('imap'))

{

echo 'IMAP dostępny';

}

else

{

echo 'IMAP niedostępny';

}

Jak dobrze korzystać z funkcji?Funkcje to jedno z podstawowych narzędzi programisty. Omówiliśmy techniczne aspekty ich działania w językuPHP, lecz nie poruszaliśmy dotąd tematu, jak je tworzyć, aby faktycznie były dla nas użyteczne. Istnieje kilka zasad,których przestrzeganie daje nam pewność, że nie natkniemy się gdzieś na problemy z utrzymaniem projektu.1. Funkcje powinny realizować jedno, konkretne zadanie. Unikamy tworzenia funkcji w stylu mydło i powidło. Jeśli

nie wiemy bądź nie rozumiemy, co dana funkcja tak naprawdę pozwoli nam osiągnąć, przerywamy pisanie kodu iwracamy nad kartkę papieru. Oczywiście nic nie stoi na przeszkodzie, by zadanie było bardzo złożone (np.obsługa formatowania BBCode); dopóki jest to jedno zadanie, jesteśmy w domu.

Funkcje 64

2.2. Jeśli kod danej funkcji staje się bardzo długi, możemy rozważyć jej rozbicie na podproblemy, które zapiszemy wmniejszych funkcjach. Powinniśmy to zrobić zwłaszcza wtedy, gdy dany problem pojawia się wielokrotnie wróżnych postaciach. Próbujemy wtedy wyciągnąć wspólny mianownik, a różnice obsłużyć poprzez konfiguracjęargumentami.

3.3. Funkcje nie powinny być zbyt długie.Oczywiście zalecenia te stanowią punkt odniesienia, a nie prawo, za którego nieprzestrzeganie czeka nas lincz.Wielu początkujących programistów zadaje pytania, kiedy pisać tak, a kiedy inaczej. Odpowiedź jest bardzo prosta:nie ma jednej, uniwersalnej reguły, która mówi, że w przypadku danej funkcji mamy ją rozbić, a w przypadku innej -nie (zauważmy, że reguła taka musiałaby być albo bardzo błyskotliwa, albo obejmować nieskończoną liczbęprzypadków). Kluczem jest zwyczajne myślenie. Dobry programista myśli podczas pisania kodu i każdy jego krokma uzasadnienie. Punkt odniesienia jest pomocą, wokół którego się obraca, ale jeśli widać, że rozbijanie jakiegośskomplikowanego i długiego algorytmu, który stanowi jedną całość, będzie niepotrzebną komplikacją, nikt poważnynie będzie tego robił. Oczywiście zdarzają się pomyłki; nie wszystkie uzasadnienia okazują się poprawne, ale dużeprojekty ulepszane są cały czas. Jeśli coś się nie sprawdziło, jest po prostu przerabiane. Sztuka polega na tym, żejeśli już się pomylimy, pomyłka nie powinna być poważna.Aby zrozumieć inny aspekt tworzenia dobrej funkcji, porozmawiajmy nieco o tym, co one robią. Być możeniektórzy uważni czytelnicy już zauważyli, że w dotychczasowych przykładach pojawiały się zarówno funkcje, którenp. wypisywały coś na ekran, oraz takie, które wyłącznie obrabiały zewnętrzne argumenty i zwracały wynik.Okazuje się, że taki podział ma bardzo duże znaczenie praktyczne i teoretyczne, zwłaszcza przy dowodzeniupoprawności programu. Każda operacja języka programowania może mieć tzw. skutki uboczne. Jest to dowolnyefekt jej działania, który zmienia stan programu. Przykładowo operacja $a + $b nie ma skutków ubocznych.Możemy ją wywołać 1000 razy, ale dopóki nie zaczniemy czegoś robić z wynikiem, nie zmieni to ani o jotędziałania programu. Z drugiej strony operacja $a = 5 ma już skutek uboczny - od tego momentu zmienna $a majuż inną wartość, co potencjalnie może wpłynąć na działanie dalszej części kodu. Pomimo swojej nazwy, skutkiuboczne często są właśnie spodziewanymi rezultatami jakiejś operacji. Nie należy ich rozumieć dosłownie, leczwłaśnie jako taką zmianę stanu programu, która może wpłynąć na dalszy kod.Także i funkcje możemy sklasyfikować względem tego czy mają one jakieś skutki uboczne czy nie. Spoglądając nanapisany wyżej przykład funkcji silnia() łatwo stwierdzimy, że nie ma ona żadnych skutków ubocznych,ponieważ jedyne, co robi, to przetwarza podany jej argument i zwraca wynik, nie zajmując się tym, jak będzie onwykorzystany. Poza tym nie wypisujemy w niej nic na ekran, ani nie korzystamy z żadnych innych zewnętrznychźródeł danych. Przykład pobierający zawartość katalogu ma już skutek uboczny; w wyniku jej wykonania doprzeglądarki wysyłany jest tekst z listą katalogów. Wywołując ją dwukrotnie, użytkownik dostanie taką listę dwarazy.Jeśli chcemy napisać dobrą funkcję, po prostu musimy znać wszystkie jej skutki uboczne i wiedzieć, czy faktyczniesą one dla nas pożądane czy nie. W tym drugim przypadku powinniśmy funkcję przepisać tak, aby ich nie zawierała.Rozpatrzmy funkcję dodającą jakiś kod HTML do podanego tekstu:

<?php

function kolorujTekst($tekst)

{

echo '<font color="red">'.$tekst.'</font>';

}

Jej skutkiem ubocznym jest wypisanie tekstu bezpośrednio na ekran. Oprócz tego mamy też drugą funkcję:

<?php

function pogrubTekst($tekst)

{

Funkcje 65

echo '<strong>'.$tekst.'</strong>';

}

Jeśli będziemy chcieli tworzyć złożenie w stylu kolorujTekst(pogrubTekst('tekst')), które wyświetlipogrubiony i pokolorowany tekst, taki skutek uboczny jest nie do przyjęcia. Zamiast echo powinniśmy użyć returntak, aby funkcja zwracała wynik, dzięki czemu jej użytkownik może zdecydować w miejscu wywołania, co taknaprawdę chce z nim zrobić. Przecież takiego kodu HTML nie musimy wcale wysyłać przeglądarki, lecz np. zapisaćdo pliku. Nietrudno zauważyć, że dopiero wyeliminowanie skutku ubocznego zwiększyło nasze możliwościwykorzystania naszych funkcji do tego celu.

Inne elementy składni

Inne elementy składni

Include i requireTworzenie dynamicznych stron byłoby bardzo kłopotliwe, gdybyśmy musieli pracowicie kopiować wszystkieutworzone przez nas funkcje do każdego pliku PHP z osobna. Na szczęście PHP udostępnia mechanizmy nadołączanie jednego skryptu do drugiego. Służą do tego instrukcje include, require, include_once orazrequire_once.Rozpatrzmy taką sytuację: mamy dwa pliki z funkcjami definiującymi wygląd treści: normalny.php orazopcjonalny.php. Stworzone w nich są identyczne funkcje różniące się jedynie zawartością. We właściwymskrypcie 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:

Inne elementy składni 66

<?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, dlatego zapis np. $zmienna =

require('skrypt.php'); jest nieprawidłowy. Różnica między nim, a include polega na sposobie obsługi błędów. Instrukcja require 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. Pozwala to uniknąć omyłkowego, kilkukrotnego ładowania tych samych funkcji, co oczywiście prowadziłoby do

Inne elementy składni 67

błędu. Powinniśmy używać ich tylko wtedy, kiedy liczymy się z taką możliwością.Budowanie kompletnej strony z mniejszych plików jest bardzo pożyteczne. Generalnie nie zaleca się pisaniawszystkiego 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. Dodatkowo tworzymy katalog actions, w którymbędzie zawarty kod różnych podstron takich, jak rejestracja czy lista artykułów. Oba katalogi powinny byćumieszczone poza katalogiem publicznym dostępnym z przeglądarki. Tam umieścimy tylko jeden plik,index.php, który będzie zarządzać uruchamianiem poszczególnych akcji. Oto i on:

<?php

require('../includes/config.php');

require('../includes/dispatch.php');

require('../includes/session.php');

require('../includes/authorize.php');

require('../includes/templates.php');

require('../includes/functions.php');

require('../includes/layout.php');

initSystem();

dispatchAction(isset($_GET['act']) ? $_GET['act'] : 'index');

Funkcję dispatchAction() umieszczamy w jednym z plików w katalogu /includes jako funkcję silnikanaszej strony (np. w dispatch.php). Jej zadaniem jest odpalenie wybranej akcji, a może ona wyglądaćnastępująco:

<?php

function dispatchAction($action)

{

if(!ctype_alpha($action))

{

displayError('Nazwa akcji zawiera nieprawidłowe znaki.');

}

if(!file_exists('../actions/'.$action.'.php'))

{

displayError('Podana akcja nie istnieje.');

}

require_once('../actions/'.$action.'.php');

$action .= 'Action';

$action();

} // end dispatchAction();

Nazwa akcji jest tłumaczona na ścieżkę do pliku w katalogu /actions. Wklejając jakiekolwiek dane zewnętrzne do ścieżek używanych przez system musimy zwracać szczególną uwagę na bezpieczeństwo. Gdyby jakiś wścibski użytkownik wywołał naszą stronę jako index.php?act=../includes/dispatch, albo jeszcze jakiś inny plik, moglibyśmy mieć poważne problemy. Dlatego najpierw sprawdzamy funkcją ctype_alpha() czy podana nazwa akcji składa się wyłącznie z liter. Teraz mamy pewność, że działalność użytkownika będzie ograniczona

Inne elementy składni 68

wyłącznie do plików PHP w katalogu /actions. Oczywiście musimy jeszcze funkcją file_exists()upewnić się, że odpowiedni plik istnieje i dopiero wtedy możemy go bezpiecznie załadować.Zwróćmy uwagę, co dzieje się później. Zakładamy, że plik z akcją będzie zawierać funkcję o nazwienazwaakcjiAction(). Dlatego do nazwy akcji doklejamy słowo Action i wywołujemy funkcję, wczytując jejnazwę ze zmiennej (podświetlona linia 14). Jest to jedna z ciekawych właściwości PHP i warto o niej pamiętać,aczkolwiek w przypadku przyjmowania danych z zewnątrz także zwracamy uwagę na bezpieczeństwo. W naszymprzypadku załatwiają je już mechanizmy przeznaczone dla require_once.

Uwaga!Nigdy nie używaj danych przysłanych z przeglądarki do dynamicznego wczytywania plików, wywoływania funkcji itd. bez upewnieniasię, że są one bezpieczne. Przez tak banalne błędy padło już wiele witryn, należących nawet do ważnych publicznych instytucji.Niefiltrowanie danych to zaproszenie hackera do ataku na naszą stronę.

W ramach ćwiczenia spróbuj uzupełnić czymś pozostałe pliki i napisać akcję index, która wyświetli jakiś tekstpowitalny.

StałeSpójrzmy raz jeszcze na przykład z plikiem index.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 niewygodnezwłaszcza, gdy trzeba je będzie z jakiegoś powodu zmienić.Zdefiniujmy zatem wszystkie używane ścieżki w pliku index.php za pomocą zmiennych. Już na pierwszy rzutoka widać, iż rozwiązanie to nie jest najlepsze, bowiem każdą zmienną ze ścieżką musimy przenosić do funkcji zużyciem global, a ponadto istnieje ryzyko, że w którymś miejscu omyłkowo ją nadpiszemy. Remedium na te kłopotysą stałe. Są to aliasy na pewne wartości, których po utworzeniu nie można już modyfikować. Najogólniejwykorzystuje się 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:1.1. Stałe nie mają znaku dolara ($) przed nazwą2.2. Stałe mogą być definiowane oraz używane wszędzie bez zważania na zasady dotyczące zakresu ich dostępności3.3. Stałe nie mogą być ponownie definiowane lub "oddefiniowane" po tym jak raz zostały zdefiniowane4.4. Stałe mogą zawierać tylko wartości skalarneOprócz przechowywania ścieżek do katalogów, stałe mają zastosowanie podczas pisania bibliotekprogramistycznych. 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 czytelnychnazwach. Możemy to pokazać na podstawie funkcji error_reporting() pozwalającej ustawić poziomraportowania błędów przez PHP. Da się ją wywoływać w ten sposób:

Inne elementy składni 69

error_reporting(1 | 2 | 4 | 8);

Jednak w takiej postaci chyba żaden programista nie potrafiłby odczytać, jaki właściwie poziom raportowaniabłędów został ustawiony. Zamiast cyfr, można użyć odpowiadające im stałe zdefiniowane przez 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 wsposó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 oznacza która wartość logiczna. Wykorzystajmy więcfakt, iż komputer zapisuje wszystko w postaci zerojedynkowej i prześlijmy wszystkie parametry jako jedną liczbę odługości pięciu bitów (od 0 do 32). W stałych zdefiniujemy nazwy poszczególnych elementów przypisując imkolejne potęgi dwójki, czyli kolejne bity liczby. Następnie za pomocą operatora alternatywy bitowej zbudujemy znich odpowiednią kombinację:

Inne elementy składni 70

<?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ślanymianem ustawiania flag (każda stała to jedna flaga) i jest powszechnie wykorzystywany w programowaniu zewzglę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ładamykilka potęg dwójki operatorem alternatywy bitowej, zapalamy tym samym poszczególne bity:

Inne elementy składni 71

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 operatorzekoniunkcji bitowej używanym do testowania, dany bit rezultatu jest zapalany jedynie w wypadku jego obecnościnaraz w obu podanych liczbach:

$rezultat & SILNIK

REZULTAT: 11001

SILNIK (1): 00001

-------------------

00001

Otrzymujemy liczbę większą od zera, czyli prawdę logiczną. Pytając się o flagę SWIATLA, zostanie wykonane takiedziałanie:

$rezultat & SWIATLA

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 jestcała tajemnica tego sposobu.

Ścieżki oraz include_pathZastanówmy się, skąd PHP wie gdzie szukać naszych plików. Podczas uruchamiania skryptu PHP ustawia mukatalog roboczy na folder, w którym znajduje się wykonywany plik i według niego domyślnie rozpoznaje wszystkieścieżki względne, czyli takie, które zaczynają się od pojedynczej kropki (katalog bieżący) lub podwójnej (katalognadrzędny). Załadowanie kolejnego skryptu poprzez require nie ma wpływu na tę ścieżkę! Wracając do naszegoprzykładu z silnikiem strony oznacza to, że gdybyśmy chcieli w pliku ../includes/dispatch.phpzaładować jakiś inny plik z tego samego katalogu, powinniśmy podać ścieżkę ../includes/innyPlik.php(względem index.php) zamiast ./innyPlik.php. Jest to jeden z powodów, dla którego nie jest zalecanetworzenie akcji bezpośrednio w katalogu publicznym, np. rejestracja.php, logowanie.php, leczstworzenie pojedynczego punktu wejścia i wybór akcji poprzez odpowiedni algorytm napisany w PHP. Oprócz tego,ma to również pewien wpływ na bezpieczeństwo i niezawodność, a także umożliwia łatwe zastosowaniemechanizmu mod_rewrite oferowanego np. przez serwer Apache do stworzenia przyjaznych adresów URL.Pozostaje wciąż pytanie, co się dzieje, jeśli ścieżka nie zaczyna się od pojedynczej lub podwójnej kropki. Gdy jest tościeżka bezwzględna, czyli zaczynająca się od ukośnika w systemach uniksowych, a od litery dysku w systemachWindows, PHP od razu ładuje plik z podanej lokalizacji i nie wykonuje żadnych innych przeszukań. Ostatniamożliwość to podana od razu nazwa pliku lub katalogu:

require('plik.php');

Inne elementy składni 72

PHP wykorzystuje wtedy mechanizm include_path. Jest to zwyczajna lista ścieżek oddzielonych średnikami, podktórymi należy szukać danego pliku. PHP sprawdza je po kolei, dopóki nie trafi. Jej domyślna wartość jestzdefiniowana w pliku php.ini i domyślnie będzie mieć wartość w stylu:

.;/sciezka/do/katalogu/publicznego;/sciezka/do/PEAR

W ten sposób PHP na początku sprawdzi zawsze katalog bieżący, później katalog publiczny, a później repozytoriumklas PEAR, którym nie będziemy się teraz zajmować. Listę ścieżek można modyfikować z poziomu skryptu PHPprzy pomocy funkcji set_include_path() oraz get_include_path(). Utwórz dwa pliki o nazwiewczytany.php. Jeden z nich umieść w katalogu publicznym, drugi w katalogu nadrzędnym. Ich kod niech będzienastępujący:

<?php

echo __FILE__;

__FILE__ jest specjalną stałą, która zawsze zawiera ścieżkę bezwzględną pliku, w którym się ona znajduje. Dziękiniej będziemy wiedzieć, który z plików się faktycznie załadował. Dodajmy także plik index.php, który będzieładować jeden z nich:

<?php

require('wczytany.php');

Uruchommy ten skrypt i zobaczmy, jaka ścieżka została wypisana na ekranie. Powinien zostać załadowany plikleżący w katalogu publicznym, ponieważ include_path nakazuje w pierwszej kolejności przeszukać właśnie jego.Teraz zmodyfikujmy include_path:

<?php

set_include_path('..'.PATH_SEPARATOR.'.');

require('wczytany.php');

Tym razem załadowany został plik z katalogu nadrzędnego, ponieważ po modyfikacji PHP w pierwszej kolejnościszuka właśnie tam. Usuńmy teraz nadrzędny wczytany.php. PHP załadował plik z katalogu publicznego, gdyż wnadrzędnym nie znalazł tego, czego szukał. Ten eksperyment powinien pokazać istotę działania include_path. Użytyw kodzie identyfikator PATH_SEPARATOR to kolejna stała zawierająca aktualny separator ścieżek.Oczywiście pojawia się pytanie, który sposób jest bardziej wydajny. Include_path generuje dodatkowy narzut wrazze wzrostem liczby ścieżek, które musi sprawdzać, dlatego gdy znamy strukturę naszej strony, lepiej posługiwać sięścieżkami bezwzględnymi bądź względnymi:

<?php

// wczytuj zawsze z bieżącego katalogu

require('./wczytany.php');

Include_path przydaje się w sytuacjach, gdy potrzebna nam większa elastyczność lub chcemy skorzystać z bibliotekPHP udostępnianych na serwerze dla ogółu (np. wspomniane już repozytorium PEAR, które zazwyczaj umieszczanejest w katalogu instalacyjnym PHP). Dzięki temu nasz skrypt nie musi wiedzieć, gdzie fizycznie znajdują sięodpowiednie pliki na serwerze, lecz po prostu je wczytuje, a interpreter zajmuje się resztą.Będąc przy temacie ścieżek warto zwrócić uwagę na różnice między systemami operacyjnymi. PHP jest językiemniezależnym od platformy, dlatego skrypty napisane pod Windowsem powinny działać także na serwerze zzainstalowanym Linuksem, ale gdy zaczynamy bawić się plikami, mogą wystąpić problemy. Poniżej zostały zebranewszystkie najważniejsze różnice, o których trzeba pamiętać, pisząc własne skrypty. Powinny zapamiętać je wszczególności osoby pracujące pod systemem Windows, który bardzo rzadko instaluje się na serwerach WWW.

Inne elementy składni 73

Własność Windows Linux i inne

Wielkość liter w plikach Nie ma znaczenia Ma znaczenie

Separator katalogów \ /

Korzeń systemu plików Litera dysku /

Podział na dyski Widoczny dla użytkownika Niewidoczny dla użytkownika

Mechanizm uprawnień Tylko po skonfigurowaniu Zawsze

Najwięcej problemów generuje sprawa wielkości liter. Jeśli widzisz, że skrypt działający pod Windowsem nie możeznaleźć plików na serwerze Linuksowym, w pierwszej kolejności sprawdź czy jeśli np. zaczynasz w wywołaniachnazwy plików dużą literą, faktycznie tak się zaczynają na dysku. Warto korzystać także z separatora katalogów wstylu uniksowym, czyli ukośnika w prawo. Jest on rozpoznawany także przez Windows, natomiast ukośnik w lewoprzez systemy uniksowe - nie. PHP przechowuje odpowiedni ukośnik charakterystyczny dla systemu, na którympracuje, w stałej DIRECTORY_SEPARATOR.

ReferencjeReferencja 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, żemodyfikując jedną z tych zmiennych, zmieniała się także druga. Jest tak dlatego, że obie te zmienne reprezentują wrzeczywistoś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.Modyfikując jedną zmienną, zmiana automatycznie będzie widoczna w drugiej.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 problemzniknął, ponieważ możemy poinformować PHP, że zmienna wartości ma być referencją. Dzięki temu możliwebędzie modyfikowanie przez nią zawartości tablicy:

<?php

$tablica = array(0 => 1, 2, 3, 4, 5, 6);

foreach($tablica as $id => &$element)

{

Inne elementy składni 74

$element = rand(1, 6);

}

var_dump($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 var_dump() pokazałaby nam dalej pierwotną zawartość tablicy. Tak się jednak nie dzieje, zatemsposób okazuje się skuteczny.Dzięki referencjom funkcje mogą pozornie zwracać więcej wartości - poprzez referencyjne parametry. Napiszemysobie teraz funkcję do kolorowania tekstów dłuższych, niż 5 znaków. W zależności od długości funkcja zwracawartość 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/>';

}

Inne elementy składni 75

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)

{

// treść

}

// proba 1

funkcja('test');

// proba 2

$zmienna = 'test';

funkcja($zmienna);

Pierwszy zapis spowoduje błąd, gdyż nie przekazaliśmy wartości w postaci zmiennej.W naszych skryptach powinniśmy unikać stosowania referencji. Prawidłowe ich zastosowanie to sytuacja, w którejpotrzebny jest nam alias na zmienną, czyli de facto kilka zmiennych prowadzących do tej samej wartości. Bardzo złąpraktyką jest wykorzystywanie referencji do optymalizacji skryptu. Nie wykonujemy wtedy kopiowania, ale PHPimplementuje specjalny algorytm, który opóźnia wykonanie kopii do momentu, gdy jest ona naprawdę potrzebna.Jeżeli zrobimy przypisanie $a = $b i nie wykonamy żadnej operacji, która uzasadniałaby utworzenie kopii, PHPnie będzie tracić czasu na jej robienie!Należy wystrzegać się także wywoływania funkcji ze zmuszaniem do przekazywania referencji, gdy funkcja sobietego wyraźnie nie życzy: funkcja(&zmienna) - zapis taki był poprawny jeszcze w PHP 4, lecz w PHP 5 zostałwycofany i generuje ostrzeżenie.

EvalNiektó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. PHPoferuje 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ędziewyświetlenie się w przeglądarce wartości zmiennej. Nie nadużywaj tej własności zbyt często. Wykonywanietworzonego przez nas kodu PHP jest ciekawą opcją, która jednak prowadzi do dość dużego spadku wydajności,trudnych do wykrycia błędów, a nawet problemów z bezpieczeństwem. PHP jest tak zaprojektowany, że praktycznienigdy nie potrzeba w nim używać eval. Oto dwa przykłady: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:

Inne elementy składni 76

$funkcja($a, $b, $c);

Pokazane to zostało w przykładach powyżej.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. Wprzypadku 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óbzarówno swojej aplikacji, jak i serwerowi.

Każdy popełnia błędy

Każdy popełnia błędyBłą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ę nazagadnieniu 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ą.

Komunikaty błędów PHPPrzetworzenie 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 3

Każdy popełnia błędy 77

Jest to błąd składni powiązany z fazą kompilacji. PHP nie może skompilować skryptu, ponieważ w podanym pliku wlinii 3 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 3. Rzeczywiście, znajduje się on na swym miejscu tak, jak byćpowinien, ale skoro według interpretera nie powinno go tu być, a nic więcej w tej linii nie mamy, zerknijmy wyżej:if(isset($zmienna) - okazuje się, że nie zamknęliśmy jednego z nawiasów. PHP oczekiwał znaku ), leczzamiast tego przeszedł do kolejnej linijki, dostał { i zgłosił błąd. Po dodaniu brakującego nawiasu skrypt zaczynapoprawnie 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 5Owo tajemnicze T_STRING jest tzw. tokenem. Kompilatory mają tendencję do ułatwiania pracy zarówno sobie, jak iprogramiś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ś miejscumożna było podać dowolną zmienną, używamy tokenu i kompilator już wie, co można tam umieszczać. Informacje obłędnym użyciu tokenu PHP wyświetla w sposób jawny, jak widać na powyższym przykładzie. Komunikatinformuje 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 to jest funkcja? Kompilator poprostu 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.

<?php

$zmienna = 'wartosc';

if(isset($zmienna))

{

echo $zmienna;

inicjujAplikacje();

}

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 functioninicjujAplikacje() in /home/uzytkownik/www/kurs/aplikacja.php on line 5Jest to błąd wykonywania skryptu. PHP poinformował nas w ten sposób, że dotarł do wywołania funkcjiinicjujAplikacje(), lecz taka nie istnieje. Pozostało nam jedynie odkryć przyczynę tego stanu - może niedołączyliśmy jakiejś ważnej biblioteki? Zwróć uwagę, że PHP sprawdza istnienie funkcji w momenciewykonywania skryptu, a nie kompilacji. Gdybyśmy nie zainicjowali zmiennej $zmienna, kod wewnątrz instrukcjiif nie zostałby wykonany i problem pozornie by nie wystąpił. To oczywiście tylko złudzenie. On tam jest, lecz PHPnie wykonał tego kawałka kodu i nie wykrył problemu. Spróbuj wykonać taki eksperyment. Praktyka ta ma swojeuzasadnienie - przypomnijmy sobie instrukcję eval z końca poprzedniego rozdziału. Tam nazwa funkcji byłaustalana w momencie wykonywania, gdyż tylko wtedy mają prawo istnieć jakiekolwiek zmienne.

Każdy popełnia błędy 78

Komunikaty Fatal error pojawiają się zawsze wtedy, gdy w trakcie wykonywania wystąpi problemuniemożliwiający dalszą pracę interpreterowi. Przykładem jest podanie do instrukcji require nazwy nieistniejącegopliku. 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 Parseerror, 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 dosortowania tablic, wywołuje pojawienie się komunikatu o błędzie typu Warning: Warning: sort() expects parameter1 to be array, string given in /home/uzytkownik/www/kurs/aplikacja.php on line 3Mó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 isł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 wszelkiwypadek wyświetli w tym miejscu komunikat: Notice: Undefined variable: zmienna in/home/uzytkownik/www/kurs/aplikacja.php on line 2Podobny komunikat pojawi się, gdy spróbujemy odczytać wartość nieistniejącego elementu tablicy. Do dobrychzwyczajów należy takie pisanie skryptów, aby nie generowały one tego typu komunikatów z kilku powodów:1.1. 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ń.2.2. 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.3.3. 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ą, a także inicjuj jeprzed pierwszym użyciem domyślną wartością.

Operator @Jeżeli dowolne wyrażenie PHP poprzedzimy znakiem @, interpreter nie wyświetli żadnego komunikatu, jeżeliwykryje w nim błąd. Nie oznacza to naturalnie, że błąd ten nagle znika. On tam jest, lecz my nie jesteśmy o tympowiadamiani. Dlatego należy bardzo rozważnie postępować z jego użyciem. @ przydaje się głównie przy funkcjachdo komunikacji z zewnętrznymi źródłami danych, np. plikami. Jeżeli PHP wykryje np. brak żądanego pliku, nietylko 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żelitego naprawdę potrzebujemy. Poniżej pokazany przykład ma za zadanie pokazać datę modyfikacji pliku lub naszwłasny komunikat, jeżeli on nie istnieje. Na początek wariant najprostszy:

Każdy popełnia błędy 79

<?php

echo filemtime('plik.txt');

Kiedy plik.txt nie będzie istnieć, interpreter wygeneruje stosowny komunikat, którego my w takiej formieoglą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 dyskustanowi duży narzut czasowy dla oprogramowania, dlatego powinniśmy się starać wykonywać jak najmniej takichoperacji. Wiemy z dokumentacji, że samo filemtime() zwraca nam false, jeśli plik nie istnieje, przeszkadzanam 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 zostaniewykonana!

Uwaga!Operator @ zatrzymuje także komunikaty Parse error. Jeżeli twój skrypt nie działa z niewiadomych przyczyn, w pierwszej kolejnościusuń go, aby sprawdzić, czy przypadkiem nie maskuje on komunikatu błędu. Dopiero potem zajmij się diagnozą.

PoradaZanim użyjesz operatora @, trzy razy się zastanów, czy naprawdę jest Ci on w tym miejscu do czegoś konkretnego potrzebny. Jegoprzetwarzanie powoduje znaczny spadek wydajności i jedynie pozornie rozwiązuje problem. Do dobrych praktyk programistycznychnależy unikanie jego stosowania.

Każdy popełnia błędy 80

Poziom raportowania błędówPoziom 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 instalacjiwedług tego podręcznika, zaleciliśmy użycie najwyższego możliwego poziomu E_ALL | E_STRICT, który zgłaszadosłownie wszystko. Dzięki temu możemy w porę reagować na każdy problem i mieć pewność, że pozainstalowaniu aplikacji na innym serwerze nie zostaniemy zasypani toną komunikatów. Jednak takie ustawienienależy stosować tylko w fazie tworzenia/testowania naszych skryptów. Gdy już umieścimy je na stronieinternetowej, należy wyłączyć wyświetlanie wszystkich błędów, ponieważ takie komunikaty często ujawniają wieleinformacji 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 poziomraportowania, 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;

Techniki debugowaniaInny 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 się właściwie stało. Jedynym sposobem jest poumieszczanie w kodzie na chwilę własnych informacji, któredokładnie powiadomią nas, które fragmenty są wykonywane w jakiej kolejności i z jakimi danymi. Metoda wywodzisię w prostej linii z języka C/C++, gdzie w kodzie umieszcza się tzw. asercje. Jeżeli kompilujemy kod programu wtrybie debug (wykrywanie błędów), w oznaczanych przez nas miejscach dodawane są odpowiednie instrukcjesprawdzające poprawność danych i raportujące ewentualne problemy. Kiedy kompilujemy program w wersji"oficjalnej", preprocesor nie umieszcza już tych instrukcji w kodzie. Oprócz tego do wielu języków istnieją specjalnedebugery, które pozwalają śledzić wykonywanie krok po kroku lub zatrzymać na wybranej linii, co przydaje się przyanalizie złożonych algorytmów.PHP żadnego preprocesora nie ma (można nawet powiedzieć, że sam nim jest dla języka HTML), dlatego samimusimy 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żenam 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 tylkowyświetla komunikat, ale także zatrzymuje skrypt. Do wyświetlania zawartości tablic lub obiektów możemyzastosować funkcje var_dump() albo print_r(). Po usunięciu usterki wszystkie poczynione przez nas wstawkiusuwamy z kodu.PHP posiada specjalną funkcję obsługi asercji: assert(). Generuje ona komunikat błędu, jeżeli podane jejwyrażenie ma wartość false:

Każdy popełnia błędy 81

<?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 wrepozytorium PECL tj. http:/ / pecl. php. net/ package/ Xdebug a nawet jako paczka w niektórych dystrybucjachLinuksa). Rozwiązania takie są dosyć ciekawe, ponieważ pokazują znacznie więcej użytecznych dla programistyinformacji. 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ękiobecnoś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ładpakietu Eclipse).

Pakiety testoweW praktyce okazuje się, że ręczne sprawdzanie aplikacji po każdej wprowadzonej zmianie bywa uciążliwe i jestnarażone na ryzyko przeoczenia jakiegoś istotnego elementu. Dlatego w większych projektach używa się na co dzieńsystemów automatycznego testowania. Zagadnieniom testowania poświęcono wiele książek. Sprowadzają się onenajczęściej do napisania dodatkowego zestawu testów, który wywołuje różne funkcje w określony sposób i sprawdzaczy wynik ich działania zgadza się ze spodziewanym. Po wprowadzeniu większych zmian wystarczy jedynieuruchomić system testujący, którego wynikiem będzie raport, ile testów zostało pomyślnie zaliczonych i gdziepojawiły się błędy.Możemy wyróżnić dwa rodzaje testów:1.1. Testy funkcjonalne - sprawdzają funkcjonalność gotowej aplikacji, np. wypełniając formularz określonymi

danymi i weryfikując odpowiedź programu.2.2. Testy jednostkowe - koncentrują się na pojedynczych elementach kodu aplikacji (np. funkcjach w kodzie)

sprawdzając, jak reagują one na określone argumenty. Założenie polega na tym, że jeśli funkcje działająpoprawnie i gdzieś wystąpił błąd, najprawdopodobniej leży on w sposobie poskładania programu z funkcji, a niew samych funkcjach.

Do każdego z nich istnieją odpowiednie systemy testowania. Testy funkcjonalne mogą być realizowane przypomocy Selenium, zaś do testów jednostkowych w PHP powszechnie stosowany jest skrypt PHPUnit. Nie będziemyich teraz poznawać, ponieważ wciąż brakuje nam sporo wiedzy o samym PHP, aby zrozumieć, jak są one napisane ijak takie testy układać. Wrócimy do nich w dalszej części podręcznika.

Korzystanie z dokumentacji 82

Korzystanie z dokumentacji

Korzystanie z dokumentacjiNajważniejszym miejscem dla każdego programisty PHP jest dokumentacja projektu. Jest ona o tyle ważna, żeinformacja o nowościach w składni w pierwszej kolejności pojawia się właśnie tutaj. Źródła w rodzaju artykułówniekoniecznie są aktualizowane wraz z nowymi wersjami i nie można się na nich w pełni opierać. Dokumentacjaprzydaje 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 [1]. Istnieje także polska, leczjest 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 bardzouporzą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 miejscachumieszczone są uwagi, ostrzeżenia oraz porady. Na dole każdej strony znajduje się sekcja z komentarzamiprogramistów. Naprawdę warto od niej zaczynać studiowanie dokumentacji, ponieważ często znajdują się tam owiele ciekawsze przykłady użycia lub naprawdę przydatne sztuczki i algorytmy. Ilość komentarzy donajpopularniejszych funkcji jest ogromna i obejmuje niemal wszystko: własne implementacje, przykłady ikomentarze odnośnie użycia, linki do artykułów oraz inne.

Instalacja PHPInformacje dotyczące instalowania PHP znajdują się w rozdziale II. Opisane zostały tam przykłady oraz dodatkowewiadomości dla wszystkich platform i serwerów. Nie wystarczą one jednak do skonfigurowania PHP do własnychpotrzeb, 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 sobieodszukać 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 zinstalowaniem jakiegokolwiek rozszerzenia, znajduje się na poświęconej mu stronie dokumentacji w sekcji"Installation".

Składnia językaAby dowiedzieć się, jak przedstawia się składnia języka, należy zajrzeć do rozdziału "Language reference". Jest totypowy spis dostępnych komend, instrukcji oraz elementów wyrażeń ułożony tematycznie. Najbardziej przydatnybędzie dla programistów mających już pewną wprawę w tworzeniu aplikacji oraz skryptów, gdyż opisy nie są takprzystępne, jak w kursach i książkach. Ich zadaniem jest dostarczenie wyczerpującej wiedzy o danym elemencie iwypisanie wszystkich możliwych zagadnień z nim związanych.Rozdział poświęcony programowaniu obiektowemu jest zdublowany. W PHP 5 ten element został napisanycałkowicie od nowa, tracąc część kompatybilności ze starymi wersjami i stąd konieczność podania dwóch osobnychopisów w zależności od posiadanej wersji. Do części dla PHP 4 polecamy już sięgać jedynie z powodówhistorycznych.

Korzystanie z dokumentacji 83

"Security" oraz "Features"W tych dwóch rozdziałach znajdują się informacje praktyczne pokazujące niektóre aspekty charakterystyczne dlaPHP. Tutaj możesz dowiedzieć się o:1.1. Ładowaniu plików na serwer2.2. Bezpieczeństwie skryptów3.3. Kilku niezbyt lubianych przez programistów opcjach: Register globals oraz Magic quotes.4.4. Metodach raportowania błędów5.5. Obsłudze ciastek i sesji6.6. Pisaniu skryptów dla tzw. trybu bezpiecznego wykorzystywanego na produkcyjnych serwerach.

Spis funkcjiJest to jeden z najważniejszych rozdziałów dla programisty. Zawiera spis wszystkich funkcji pogrupowanychwedług modułów. Na początku każdego podrozdziału znajduje się skrótowy opis rozszerzenia, wymagania orazsposó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ść jegowyłączenia.Następnie na stronie znajduje się kilka prostych przykładów i lista stałych definiowanych przez dany moduł. Nakońcu wyszczególniony jest alfabetyczny spis wszystkich funkcji modułu wraz z lakonicznie zaznaczonymdział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ącakonwencja:• 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 kwadratoweoznaczają parametry opcjonalne. Z podanego powyżej przykładu wynika, że możemy podać:1.1. nazwę2.2. nazwę i wartość3.3. nazwę, wartość i czas wygaśnięcia4.4. nazwę, wartość, czas wygaśnięcia i ścieżkę5.5. nazwę, wartość, czas wygaśnięcia, ścieżkę i domenę6.6. nazwę, wartość, czas wygaśnięcia, ścieżkę, domenę oraz nakaz wysłania ciastka połączeniem szyfrowanym

Korzystanie z dokumentacji 84

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 comożna znaleźć:1. Arrays - wszystko, co związane z tablicami2. ctype - sprawdzanie zawartości ciągów tekstowych3. Exif - wyciąganie dodatkowych informacji z plików JPG i TIFF4. Filesystem - operacje na plikach5. Function handling - zarządzanie funkcjami6. HTTP - kilka funkcji do protokołu HTTP (np. wysyłanie ciastek)7. Image functions - biblioteka GD do generowania obrazków8. Math - funkcje matematyczne9. Misc. - funkcje niepasujące nigdzie indziej (np. kolorowanie składni)10. MySQL - najstarszy zbiór funkcji do komunikacji z bazą MySQL11. mysqli - "Improved MySQL", komunikacja z nowymi wersjami bazy MySQL.12. Network - funkcje sieciowe13. Output Control - buforowanie wyjścia skryptu14. PDO - biblioteka PDO do komunikacji z różnymi bazami danych, którą niebawem poznamy.15. spl - "Standard PHP Library", obiektowe nakładki na wiele podstawowych operacji.16. Strings - operacje na ciągach tekstowych17. Variables handling - rozpoznawanie typów zmiennych itd.Zachęcamy do przejrzenia tych rozdziałów i zorientowania się w ich strukturze. Dobra orientacja w dokumentacjiPHP sprawia, że nie trzeba nawet przykładać dużej wagi do "wkuwania" funkcji na pamięć.

Przypisy[1] http:/ / www. php. net/ manual/ en/ index. php

Studium przypadku: Księga gości 85

Studium przypadku: Księga gości

Studium przypadku: Księga gościDotychczasowe rozdziały miały w sobie więcej teorii i nie ukazywały prawdziwej istoty tworzenia skryptów dlastron WWW. Najlepszym rodzajem nauki jest praktyczne stworzenie skryptu z prawdziwego zdarzenia i pokazaniew ten sposób, jak poszczególne elementy języka ze sobą się łączą. W tym rozdziale stworzymy samodzielniefunkcjonalną księgę w PHP. Wykorzystamy w niej pętle, tablice, instrukcje warunkowe, formularze oraz dołączanieplików zewnętrznych. Ponadto poznamy kilka interesujących funkcji.

PlanKsię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 "|". Abywyeliminować sytuację, gdy ktoś wpisze np. w treść taki znak, albo (co gorsza) naciśnie enter, każda kolumnazostanie 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ściw 7-bitowych protokołach. Do zapisu są tu używane wyłącznie litery, cyfry, ukośnik oraz znak równości, zatem niema 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 dwiefunkcje zajmujące się wyłącznie tym. Księga będzie miała dostęp do danych jedynie za ich pomocą. Rozwiązanie tojest bardzo użyteczne. Kiedy poznasz już bazy danych, z pewnością zapragniesz przesiąść się właśnie na nie. Niebędziesz musiał przepisywać całej księgi. Po prostu napiszesz do tych dwóch funkcji nowy kod. Dzięki takiemurozwiązaniu źródła skryptu będą uporządkowane i czytelne, a ponadto bardzo łatwe do modyfikacji.

dane.phpPlik dane.php zawierać będzie dwie funkcje: dodajWpis() oraz pobierzWpisy() zajmujące się wyłącznieoperowaniem 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ść albozmienić mu nazwę. Pierwszy etap obrabiania danych to ucięcie białych znaków (spacji, tabulatorów itp.) z początkui końca każdego ciągu. Nie są nam one do niczego potrzebne, a jedynie utrudniają nam sprawę.

Studium przypadku: Księga gości 86

// 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 jegopoczątku jest na pewno dodany identyfikator protokołu. Zwróć uwagę na użyty operator: !==. Wspomniana funkcjazwraca 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 jakopozycję 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");

Studium przypadku: Księga gości 87

fclose($f);

return true;

} // end dodajWpis();

Ostatni akord to zapisanie wpisu w pliku. Na początek otwieramy go w trybie dopisywania (parametr a), następniebudujemy tablicę z danymi wpisu. Parametry przybyłe z formularza kodujemy w Base64, środkowa kolumna to czasdodania wpisu w sekundach od 1.1.1970. Dzięki takiemu jego zapisowi, będziemy go mogli później dowolnieformatować 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 &lt;). W ten sposób nikt nie rozwali nam księgizł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 donowej 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(). Zniej przechodzi do nl2br(), a z niej do base64_encode(). Alternatywne kursy preferują w tym miejscu czytelną, alemniej 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 trafiado zmiennej, co ma szczególnie negatywny wpływ na wydajność przy dużych danych. Ponadto zapis ten często niejest opatrzony stosownym komentarzem i wyrabia złe nawyki tworzenia mnóstwa niepotrzebnych zmiennychtymczasowych, o czym już wspominaliśmy. Trzeba zapamiętać, że nawet podawany przez nas wariant konstrukcjiksię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 jednaknajnowsze wpisy są na końcu, a my chcemy je wyświetlić w kolejności odwrotnej: najnowsze na górze. Dlatego odrazu 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żemyksiędze do wyświetlenia.

foreach($wpisy as $wpis)

{

$wpis = explode('|', trim($wpis));

Studium przypadku: Księga gości 88

$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 jeprzez funkcję trim(), bowiem file() pozostawia w tablicach znaki zejścia do nowej linii. Dopiero po ich usunięciumożemy funkcją explode() rozbić ciąg z powrotem na tablicę (czyli wykonać operację odwrotną do implode()). Wpisprzepuszczamy przez base64_decode(), aby zdekodować informacje, formatujemy datę i pakujemy to do tablicy$rezultat. Później zwracamy ją.

ksiega.phpW pliku tym znajdzie się główna część funkcjonalna księgi. To jego powinniśmy uruchamiać z poziomuprzeglą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, czyteż wystarczy wyświetlić aktualną zawartość księgi. PHP zapisuje informację o użytej metodzie wysyłania żądania wspecjalnej 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'],

Studium przypadku: Księga gości 89

$_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 funkcjidodajWpis() poszczególne pola formularza i ifem sprawdzamy, jaki jest rezultat, aby móc wygenerować stosownykomunikat.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 zelementó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>

Studium przypadku: Księga gości 90

<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ę dodawaniawpisó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ę tylkopodczas 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.

Co dalej?Napisana tutaj księga nie jest wyrafinowanym szczytem techniki. Jej głównym zadaniem było pokazaniepodstawowych mechanizmów tworzenia dynamicznych stron WWW w praktyce. Wraz z powiększaniem się twojejwiedzy 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 danychdającymi nieporównywalnie większe możliwości, niż pliki tekstowe. Na bazach danych oparta jest obecniezdecydowana większość istniejących aplikacji internetowych. Dzięki temu, że odseparowaliśmy pobieranie danychod ich wyświetlania, przepisanie księgi na MySQL będzie jedynie zabiegiem kosmetycznym. Zdecydowaniepolecamy korzystanie z takiego rozwiązania, gdyż daje programiście sporą elastyczność. Nie zawsze bowiem zdarzasię, że organizacja naszych danych będzie dokładnie taka, jakiej życzyłby sobie klient. Programując wszystko "nasztywno" 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.

Studium przypadku: Księga gości 91

Na tym kończymy poznawanie podstaw języka PHP. Przyszła pora na dokładniejsze poznanie kilku użytecznychfunkcji przydatnych podczas tworzenia własnych skryptów.

Ćwiczenia

ĆwiczeniaĆwiczenia utrwalające materiał z pierwszego rozdziału.Odpowiedzi

Zasada działania PHP1.1. PHP jest językiem skryptowym. Co to oznacza?2.2. Wskaż czynności, które można wykonać przy pomocy PHP. Jeżeli czegoś nie da się zrobić w PHP, jakiej

technologii użyjesz?•• Licznik odwiedzin•• Otwarcie pliku na dysku internauty•• Rozwijane menu•• Otwarcie pliku na serwerze•• Rejestrowanie informacji o osobach przychodzących•• System newsów•• Aktualizacja fragmentu załadowanej strony WWW•• Zareagowanie na kliknięcie myszką•• Pobranie adresu MAC karty sieciowej internauty•• Wyszukiwarka treści

3.3. Co powinien generować skrypt PHP uruchamiany na serwerze WWW?

Podstawy językaSzybkie pytania:1. Do czego służy komenda echo?2. Dlaczego tekst, który chcemy wyświetlić przy pomocy echo nie musi być otoczony nawiasami, a przyprintf musi?

3.3. Objaśnij różnicę w działaniu cudzysłowów i apostrof.4.4. Do czego służą zmienne? Jakimi atrybutami można je opisać?5.5. Wskaż niepoprawne nazwy zmiennych:

•• $_

•• $zmienna

•• $Napis383

•• $dZi33cI_N30

•• $15newsow

•• $gżegżółka

•• $_i

6.6. Opisz na przykładzie, dlaczego powinno się inicjować zmienne przed pierwszym użyciem.Poniżej pokazane są dwie sytuacje, w których stosujemy zmienne tymczasowe, lecz w jednej z nich są oneniepotrzebne. Wskaż "błędną" sytuację oraz objaśnij, dlaczego w jednym przypadku zmienna tymczasowa jestpożądana, a w drugim nie?

Ćwiczenia 92

Przykład 1:

<?php

$tresc = $_POST['tresc'];

$tresc = htmlspecialchars($tresc);

$tresc = nl2br($tresc);

zapisz($tresc);

Przykład 2:

<?php

$wynik = skomplikowaneObliczenia();

if($wynik > 4)

{

przetwarzajDalej($wynik * 536 - 832 / ($wynik / 2));

}

else

{

buforuj($wynik);

}

Pętle i instrukcje warunkoweSzybkie pytania:1. Jaka musi być wartość wyrażenia w pętli while, aby jej treść wykonała się ponownie?2. Jaka musi być wartość wyrażenia w pętli do... while, aby jej treść wykonała się ponownie?3. Co najmniej ile razy wykona się kod w pętli do... while, a ile w while?4. W jaki sposób wykonać określony kod, gdy żądany przez nas warunek nie jest spełniony?5. Zamień podane wyrażenia logiczne na ich odwrotności (tj. odwrotność ma dawać true, gdy wyrażenie jest

fałszywe i na odwrót) bez użycia operatora negacji !•• $a == $b

• ($a > 8) && ($a < 20)

• ($a == 7) || ($b < 20)

6.6. Opisz słownie, co opisują wyrażenia oraz ich odwrotności z poprzedniego ćwiczenia.

FunkcjePoniżej pokazany jest fragment pewnego skryptu. Podana funkcja ma dodawać formatowanie do podanego wargumencie ciągu tekstowego, lecz nie działa zgodnie z zamierzeniami autora. Znajdź błąd i zaproponuj jegorozwiązanie. Jeżeli nie znasz niektórych z funkcji, znajdź je w dokumentacji.

<?php

function formatuj($tekst)

{

echo '<strong>'.$tekst.'</strong>';

} // end formatuj();

// ...

$dane = file('dane.txt');

Ćwiczenia 93

echo '<ul>';

foreach($dane as $linia)

{

$wiersz = explode('|', trim($linia));

echo '<li>'.formatuj($wiersz[0]).': '.$wiersz[1].'</li>';

}

echo '</ul>';

Ćwiczenia praktyczneĆwiczenie 1

Matematyczną operację x! (czyt. x silnia) definiujemy następująco: , , -innymi słowy jest to mnożenie wyniku przez kolejne liczby naturalne aż do n. Zaimplementuj silnię w PHP w dwóchwariantach: rekurencyjnym oraz przy użyciu pętli for. Spróbuj przy pomocy każdej z nich policzyć silnię z 50 oraz105. Co zaobserwowałeś?Ćwiczenie 2

Na wielu stronach internetowych wykorzystuje się podział listy wyników na strony, aby ograniczyć ilośćjednocześnie wyświetlanych informacji i przyspieszyć ładowanie strony. Wbrew pozorom, Twoja wiedza jest już wpełni wystarczająca, aby stworzyć taki system. Twoim zadaniem jest napisanie funkcji o następującychwłaściwościach:•• Funkcja przyjmuje za argumenty:

•• łączną ilość elementów,•• ilość elementów na jedną stronę,•• numer aktualnej strony.

•• Zwraca tablicę zawierającą:•• kod HTML z listą dostępnych stron,•• numer pierwszego elementu wyświetlonego na aktualnej stronie,•• ilość elementów wyświetlanych na aktualnej stronie (pamiętaj, że na ostatniej stronie nie musi znajdować się

pełna liczba elementów).•• Funkcja musi wygenerować kod HTML z odnośnikami do wszystkich dostępnych stron. Aktualna strona musi

być wyróżniona, np. pogrubieniem.•• Funkcja musi obliczyć, który element powinien wyświetlać się jako pierwszy na aktualnej stronie.Napisz skrypt testujący napisaną funkcję, który pobiera numer strony z adresu URL. Uwzględnij sytuację, gdy wadresie URL nie ma podanego numeru. Sprawdź, czy funkcja poprawnie zachowuje się, gdy próbujesz podać np.ujemną albo nieistniejącą stronę. Powinieneś zostać wtedy przerzucony do strony pierwszej. Dodaj do skryptu pętlęfor, którą zasymulujesz wyświetlane elementy poprzez zwykłe wyświetlanie liczb naturalnych.Ćwiczenie 3

Dodaj napisany system stronicowania do księgi gości z poprzedniego rozdziału.

94

Rozmaitości

Przetwarzanie tekstu

Przetwarzanie tekstuNajwiększa ilość wykonywanych w PHP operacji dotyczy danych tekstowych oraz ich obróbki. Poznamy tutajkilkanaście funkcji, które będą nam pomocne. W następnym rozdziale zaznajomimy się natomiast z podstawamiwyrażeń regularnych pozwalających na znacznie bardziej zaawansowane manipulacje oraz dokładniejszą kontrolępoprawności.

Wyszukiwanie ciągówPodstawową funkcją wyszukiwania jednego ciągu w drugim jest strpos() posiadająca także wariant stripos(), wktórym nie gra roli wielkość liter. Użyliśmy jej już raz podczas pisania księgi gości do sprawdzenia, czywprowadzony 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ępniepatrzymy na rezultat. strpos() powinien zwrócić nam pozycję, od której zaczyna się tekst, którego szukamy, przyczym znaki liczone są od zera. Jeżeli nic nie zostanie znalezione, dostaniemy wartość false. Z tego powodupowinniśmy zawsze wykorzystywać te funkcje z operatorami === oraz !== sprawdzającymi nie tylko wartość, ale ityp zwracanych danych. Oto interesująca sztuczka, która w jednej linijce pozwoli nam na sprawdzenie, czy ciągzostał znaleziony i EWENTUALNE pobranie pozycji jego wystąpienia:

Przetwarzanie tekstu 95

<?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. Wten 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, musimyposłużyć się funkcjami strrpos() i strripos().

Modyfikacja ciągówZajmiemy się teraz dosyć rozległą kwestią modyfikacji oraz obróbki tekstu. Jest ona niezwykle ważna, jeżelizamierzasz pisać aplikacje typu forum dyskusyjnego czy księgi gości. Z wiadomych przyczyn nigdy nie powinniśmyzezwolić internaucie na wypisywanie wszystkiego, co mu się podoba i musimy poddać jego tekst elektronicznejobróbce. Za jej pomocą można również ułatwiać formatowanie tekstu. Przykładowo, treść źródłowa tegopodręcznika zapisana jest w specjalnym zestawie znaczników dostosowanym do specyfiki Wikibooks. Odpowiedniabiblioteka zajmuje się jego przetwarzaniem na język HTML. Tak złożonymi kwestiami nie będziemy się tu jednakzajmować. Wspomniane zostaną przede wszystkim te funkcje dostępne w PHP. Bardziej zaawansowane algorytmymusisz już zaprogramować samodzielnie.Nagłówki na stronach internetowych przybierają różne style, w zależności od pełnionych funkcji. Część z nichpisana 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órewykonają to za nich. Choć obecnie zadanie sformatowania tytułów można z powodzeniem zrealizować już zwykorzystaniem CSS-a, PHP posiada szereg funkcji do konwersji dużych liter na małe. Pamiętajmy wszak, że PHPto 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>';

?>

Przetwarzanie tekstu 96

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, aucwords() - początek każdego wyrazu. Doświadczenie podpowiada nam, że otrzymywane dane nie zawsze są takklarowne. Jeżeli potrzebne nam automatyczne kapitalizowanie początków zdań, musimy sami napisać odpowiednialgorytm.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 (opcjonalnie) ilość znaków dopobrania. Domyślnie funkcja kończy wycinanie na końcu ciągu. W przykładzie połączymy ją z funkcją strpos() dowycięcia nazwiska z personaliów pewnego człowieka.

<?php

$nazwa = 'Janusz Kowalski';

if(($id = strpos($nazwa, ' ')) !== false)

{

$nazwisko = substr($nazwa, $id + 1); // +1, by zacząć

wycinanie od jednego miejsca za znalezioną spacją

}

else

{

$nazwisko = '';

}

echo $nazwisko;

?>

W personaliach odnajdujemy spację, która posłuży nam jako marker. strpos() zwróci nam jej pozycję. Przekazujemyją do substr() jako punkt startowy wycinania i pobieramy ilość znaków równą długości personaliów minus pozycjispacji. W ten sposób w nasze ręce wpadnie nazwisko i będziemy mogli zrobić z nim, co tylko chcemy. Alternatywnysposób rozwiązania tego problemu polega na rozbiciu tego ciągu na tablicę poznaną już funkcją explode(). Przydajesię 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 naobrazki. Jeżeli nie interesują nas żadne dodatkowe fajerwerki, napisanie konwertera jest bajecznie proste. PHPposiada funkcję str_replace() zamieniającą jeden fragment ciągu na drugi (jej wariant, str_ireplace() nie rozróżniawielkoś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>';

Przetwarzanie tekstu 97

// Drugi, kilka naraz

echo '<p>'.str_replace(array(

':)',

':]',

':('

), array(

'<img src="smile.gif"/>',

'<img src="eye.gif"/>',

'<img src="sad.gif"/>'

), $post).'</p>';

?>

Pierwszym parametrem jest szukany ciąg, drugim - ciąg docelowy, a trzecim - ciąg, na którym operujemy. Możemyzdefiniować 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.

Miejsce podawania danych, na których funkcja ma operować, nie jest ujednolicone. Część funkcji wymaga jego podania na końcu, a częśćna początku. Jest to pozostałość po pionierskich czasach rozwoju PHP tworzonego wtedy po części metodą pospolitego ruszenia, orazówczesnych inspiracjach językiem C.

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ę wewpisie jako statyczny tekst. Okazuje się, że nie jest to jedyne dostępne nam rozwiązanie. Możemy znacznikiwyrzucić 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ą. Przywycinaniu 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 niekonieczniemusi wycinać białe znaki - wystarczy, że podamy nasz własny zestaw jako drugi parametr, a możemy oszczędzićsobie dużo pracy.

Przetwarzanie tekstu 98

ASCIIKiedy powstawały komputery, powstała konieczność stworzenia uniwersalnego standardu kodowania znakówalfabetu za pomocą kodów liczbowych rozumianych przez maszyny. Umożliwiłoby to pisanie dokumentów, które dasię przenosić między maszynami bez konieczności ich konwersji. Tak narodził się siedmiobitowy standard ASCIIzawierający 128 kodów. Jego cechą szczególną było istnienie tzw. kodów sterujących (od 0 do 32), dzięki którymmożna było nawet sterować pracą niektórych urządzeń. W latach późniejszych wykorzystano także ósmy bit (kodyod 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 ipobierają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!</p>');

}

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

Przetwarzanie tekstu 99

}

?>

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:1.1. Dotarły do nas dane z formularza.2. Konwersja ze znaku na kod. Sprawdzamy, czy pole dane zawiera dokładnie jeden znak. Jeśli tak, wyświetlamy

wynik.3. 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.4.4. 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ć zodpowiedniego 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(), czykażdy jego znak zawiera się w zestawie $zestaw. Jeśli nie, przerywamy pracę i zwracamy false. Oto przykładoweuż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żeniamiregularnymi, z których podstawami zaznajomi Cię następny rozdział.

Przetwarzanie tekstu 100

Formatowanie tekstuKorzenie PHP znajdują się w języku C, więc nic dziwnego, że dziedziczy on po nim funkcję printf() służącą doprezentacji 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 umieszczana ich miejscu dane z kolejnych parametrów. W powyższym przykładzie zastosowaliśmy kod %x, który spowodujewyś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. Wartonadmienić 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.

KodowanieTeraz nieco o przechowywaniu haseł użytkowników przez PHP. Generalnie nigdy nie składuje się ich w postacijawnej oraz nie koduje się algorytmem dwukierunkowym (czyli dającym teoretyczną możliwość ichrozszyfrowania). Powszechna praktyka poleca stosowanie tzw. funkcji haszujących generujących unikalne, równejdł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 bardzooczywisty. 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ą tensam hasz i zwiemy to kolizją. W dobrym algorytmie, aby doszło do takiej sytuacji, muszą to być naprawdę różne

Przetwarzanie tekstu 101

ciągi. Ponadto zmiana już pojedynczego znaku wewnątrz haszowanego ciągu musi powodować utworzenie zupełnieinnego wyniku. Siła algorytmu zależy od tego, jak dużo czasu potrzeba na wykrycie kolizji metodą brute-force, czylisprawdzenia wszystkiego na wszystkim. Algorytm MD5 został już złamany na tyle, że zwyczajny komputer PC jestw stanie znaleźć kolizję już w ciągu zaledwie ośmiu godzin. Dla SHA1 ilość niezbędnych do sprawdzeniakombinacji 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łemzaszyfrowanym 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 byciazalogowanym między poszczególnymi żądaniami. Zajmiemy się tym w dalszych rozdziałach.

Uwaga!Bardzo wielu początkujących programistów próbuje "poprawić" siłę funkcji haszujących, dokonując ich złożenia, np. sha1(sha1($tekst))lub wymyślnych sklejeń fragmentów różnych funkcji. Przestrzegamy przed taką amatorską kryptografią. Funkcje haszująceopracowują najlepsi matematycy świata i szansa, że eksperymentując na chybił trafił stworzysz coś lepszego, jest praktycznie zerowa. Wrzeczywistości, skutki będą najczęściej odwrotne od zamierzonych. Przykładowo, złożenie dwóch identycznych funkcji haszujących magorszą lub co najmniej taką samą odporność na kolizje, jak pojedyncza funkcja. Również argument o "tajności" algorytmu można włożyćmiędzy bajki - era tajnej kryptografii skończyła się w połowie lat 70. i praktycznie wszystkie powszechnie stosowane algorytmykryptograficzne obecnie są jawne. Istnieją inne, znacznie skuteczniejsze techniki obrony przez próbami łamania kluczy, jak np. użyciedodatkowego, jawnego i unikalnego dla każdego konta ciągu zwanego solą.

Podstawy wyrażeń regularnych 102

Podstawy wyrażeń regularnych

Podstawy wyrażeń regularnychW poprzednim rozdziale poznaliśmy proste techniki wyszukiwania i manipulacji danymi tekstowymi. Ponadtodowiedzieliśmy się o funkcjach z serii ctype pozwalających na prostą kontrolę zawartości ciągu pod kątemwystępujących w nim znaków. Jak sam zapewne zdążyłeś zauważyć, narzędzia te nie posiadają jednak żadnychzaawansowanych 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ł.

Istota wyrażeń regularnychMechanizm wyrażeń regularnych (ang. regular expressions, czasem skracane do regexp) jest tak naprawdę parserempewnego 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 odpowiednimifunkcjami 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 nasinformacji z ciągu nie stanowi kłopotu. Wystarczy, że znamy wzorzec go opisujący, a system wyrażeń zwróci namdodatkowo tablicę uzyskanych z jego wnętrza danych, których potrzebujemy. Wyrażenia regularne dają nam takżedostęp do znacznie bogatszego w możliwości kuzyna funkcji str_replace() z poprzedniego rozdziału. O ile tamtafunkcja bezmyślnie zamieniała wszystkie napotkane wystąpienia jakiegoś fragmentu na inny, dzięki wyrażeniomregularnym możemy zdefiniować naprawdę wymyślne mechanizmy zamiany uwzględniające wiele dodatkowychczynników.Jak widać, wyrażenia regularne to potężne narzędzie, jednak przez to też skomplikowane. Niemniej każdy szanującysię programista powinien znać przynajmniej jego podstawy, ponieważ praktyka zawodowa pokazuje, iżwykorzystywane są one bardzo często.

Uwaga!W PHP zaimplementowane są dwa mechanizmy wyrażeń: wyrażenia kompatybilne z POSIX (nazwy funkcji zaczynają się od prefiksuereg_) oraz wyrażenia regularne Perla (nazwy funkcji zaczynają się od preg_). W całym podręczniku będziemy używali jedynie tychdrugich - nie tylko posiadają większe możliwości, ale również działają znacznie szybciej, co ma niebagatelne znaczenie w przypadkudużej ilości danych. W sieci wciąż spotkać można artykuły demonstrujące wyrażenia POSIX, jednak my odradzamy ich stosowanie.

Pierwszy przykładNasze pierwsze spotkanie praktyczne z wyrażeniami regularnymi rozpoczniemy od prostego sprawdzenia, czywypełnione pole formularza zawiera dokładnie jedną cyfrę. Do porównywania wzorca z ciągiem służy funkcjapreg_match(), która zwraca ilość wystąpień ciągu według podanego wzorca.

<?php

if($_SERVER['REQUEST_METHOD'] == 'POST')

{

if(preg_match('/^[0-9]$/D', $_POST['cyfra']))

{

echo '<p>Wpisałeś cyfrę '.$_POST['cyfra'].'</p>';

}

else

{

Podstawy wyrażeń regularnych 103

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 wymuszainterpretację $ 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 danymmiejscu. Ostatecznie wzorzec ten opisuje wszystkie ciągi składające się z DOKŁADNIE jednego znaku będącegocyfrą z przedziału 0 do 9.Istnieje jeszcze jeden sposób powiadomienia parsera, ile znaków chcemy tam widzieć. Jest nim użyciekwantyfikatoró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)

Klasy znakówNauczymy 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żnazmienić na dowolną inną długość za pomocą omówionych wyżej kwantyfikatorów. Zatrzymajmy się jednakdokł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 wformularzu tekst "9a" - o dziwo także i on zostanie przyjęty, mimo że na drugiej pozycji mamy literę! Co jest nietak? Nic - wyrażenie działa prawidłowo. Parser po prostu osiągnął jego koniec przy sprawdzeniu pierwszego znakucią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")

Podstawy wyrażeń regularnych 104

• 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-Z0-9\.\-_]+ - 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ętneoraz 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?

Podstawy wyrażeń regularnych 105

GrupyPoszczegó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, abynp. 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 jejwymagań. 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 Dec2006, 16:34", zamiast w łatwych do przetwarzania sekundach od 1.1970. Do rozbicia ciągu na poszczególnefragmenty 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 datyzawarliśmy w grupach, a do samej funkcji preg_match() podaliśmy trzeci parametr. Do podanej tam zmiennejzostanie przypisana tablica z treścią pasującego ciągu na indeksie 0 oraz wartościami wszystkich użytych grup nakolejnych indeksach. Teraz możemy już łatwo przekonwertować funkcją mktime() naszą datę na format uniksowy.

Podstawy wyrażeń regularnych 106

Ta sekcja jest zalążkiem. Jeśli możesz, rozbuduj ją [1].

Przypisy[1] http:/ / pl. wikibooks. org/ w/ index. php?title=PHP/ Podstawy_wyra%C5%BCe%C5%84_regularnych& action=edit

Obsługa ciastek

Obsługa ciastek

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ącychprzeglądarce na zidentyfikowanie dostarczanych treści. Nagłówki precyzują typ MIME danych, np. text/html dladokumentu HTML, kodowanie znaków, zachowanie się serwerów proxy, ustawienia cache'owania danych itd. PHPposiada funkcję header() pozwalającą skryptowi wysłać własne nagłówki. Muszą być one jednak zdefiniowaneprzed wysłaniem jakiegokolwiek kodu HTML (są to przecież NAGŁÓWKI). W przeciwnym wypadku dostaniemyniemiły komunikat Cannot add header information. Programiści często wykorzystują nagłówki do zdefiniowaniatypu 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ójdysk twardy. Nie będzie on jednak ujawniać katalogu na serwerze, gdzie on się znajduje. Cała komunikacjaprowadzona 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'].'"');

Obsługa ciastek 107

readfile('./pdf/'.$_GET['plik']);

}

else

{

// 4

header('HTTP/1.1 404 Not Found');

exit('Nie znaleziono pliku

'.htmlspecialchars($_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.1.1. Na początku dokonujemy sprawdzenia, czy ktoś w ogóle zainteresował się podaniem nazwy dokumentu do

pobrania.2. 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ściamię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() odrzuciniebezpieczny fragment ../. Mamy więc pewność, że internauta będzie ściągać TYLKO i WYŁĄCZNIE to, cochcemy, aby ściągał.

3. Jeśli stwierdzimy, że plik istnieje, powiadamiamy nagłówkami, że oto nadejdzie dokument PDF jako załącznik oodpowiedniej nazwie. Funkcją readfile() wysyłamy jego zawartość.

4.4. 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ł donaszego 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

{

Obsługa ciastek 108

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łamytym razem nagłówek Location. Informuje on przeglądarkę, że treści nie będzie i powinna raczej skontaktować się zpodanym 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ą.

Ciastka w PHPChyba każdy internauta słyszał o ciastkach i wyolbrzymianych "zagrożeniach" z nimi związanych. W rzeczywistościsą to zwyczajne informacje umieszczane przez witryny WWW w przeglądarkach po to, aby był do nich dostępmiędzy wywołaniami kolejnych podstron w obrębie witryny. Jedyny problem może pojawić się z tzw. ciastkamipublicznymi, które może odczytać każda strona w Internecie. Jednak poza tym jest to bardzo pożyteczne narzędziewykorzystywane 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ływieprzestają istnieć. Do wysyłania ciastek służy w PHP funkcja setcookie(), a do pobierania wartości tych ustawionychprzez 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ącciastko 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

Obsługa ciastek 109

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żnauproś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);

?>

Funkcje buforowania wyjściaIstnieją sytuacje, kiedy musimy wysłać jakiś kod HTML przed wysłaniem nagłówków HTTP, jednak tradycyjnemetody nie pozwalają na to. W PHP można ten problem omijać, używając funkcji buforowania wyjścia. Ogólnierzecz 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 ikodu 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>';

}

Obsługa ciastek 110

ob_end_flush();

?>

Zwróć uwagę, że przed stworzeniem ciastka skrypt wysyła już kod HTML. Dlatego wszystkie instrukcje zawarliśmymiędzy funkcjami ob_start() i ob_end_flush(). Pierwsza inicjuje buforowanie wyjścia, a druga kończy je, wysyłającjego zawartość do przeglądarki.Buforowanie wyjścia może też posłużyć do celów algorytmicznych, kiedy musimy przechwycić wysyłany kod, abygo 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 skryptcenzorski. Przechwytuje on tekst i cenzuruje go, chyba że internauta zna sposób aktywujący prawdziwą treść. Jest tocoś 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);

}

?>

Obsługa ciastek 111

Buforowanie wyjścia jest też podstawą tzw. kompresji GZip. Jest to kompresowanie treści strony przed wysłaniemtak, aby zajmowała mniejszą objętość, przez co użytkownik szybciej ją pobierze. Kompresję wspierają wszystkienowoczesne przeglądarki (np. Opera, Firefox).Aby uruchomić kompresję GZip, twoja wersja PHP musi mieć doinstalowaną bibliotekę zlib. Wtedy możeszrozpocząć 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 zbuforowanejtreści. ob_gzhandler jest jednym z predefiniowanych uchwytów, zajmującym się właśnie kompresją GZip. Druga zfunkcji 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 otym pamiętać.

Kompresja GZip może być także włączona "firmowo" w pliku php.ini dyrektywą zlib.output_compression. Nie powinieneś wtedywywoływać powyższych funkcji. Aby upewnić się, czy ustawienia interpretera na to pozwalają, musisz sprawdzić stan podanej dyrektywyfunkcją ini_get()

Ciastka a bezpieczeństwoCiastka 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ć tobezpiecznie. Ciastko jest zwyczajnym nagłówkiem HTTP i naprawdę nie stanowi dużego problemu przechwyceniejego treści. Dlatego pamiętaj, aby nigdy nie przesyłać nim loginów, haseł zalogowanego użytkownika, ani żadnychinnych danych potencjalnie pomocnych przy autoryzacji. Jest to całkowicie zła droga, gdyż systemy autoryzacjipisze się z wykorzystaniem sesji. Sesja jest pewnym rekordem z informacjami identyfikacji danego internautyprzechowywanymi w bazie albo w pliku tekstowym na serwerze. Posiada długi, alfanumeryczny identyfikator i to onprzesył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 posiadawłasny. Omówimy go w następnym rozdziale.

Sesje 112

Sesje

SesjeProtokół HTTP jest protokołem bezstanowym. Oznacza to, że serwer WWW rozpatruje każde żądanie niezależnie odinnych, nie szukając żadnych powiązań w stylu wysyłania ich przez tego samego internautę. Utrudnia to teoretycznietworzenie wszelkich systemów autoryzacji, które wymagają śledzenia poczynań użytkownika na naszej stronie iprzenoszenia jego danych autoryzacyjnych między kolejnymi żądaniami, czyli krótko mówiąc - wymagają obecnościsystemu 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 PHPSESSIDdoklejanego automatycznie do adresów URL. Na jego podstawie odczytywany jest później odpowiedni plik zdanymi sesji zapisany gdzieś na serwerze. Pod koniec przetwarzania żądania wszystkie wprowadzone przez skryptzmiany są z powrotem zapisywane do wspomnianego pliku tak, aby były widoczne przy wejściu na kolejnąpodstronę. I tak to się toczy.

Wprowadzenie do sesjiCzas na trochę praktyki. Aby zainicjować mechanizm sesji, wystarczy wywołać funkcję session_start(), najlepiej napoczątku naszej aplikacji. Od tego momentu do naszej dyspozycji zostaje oddana superglobalna tablica $_SESSION -wszystkie zapisane do niej dane są przesyłane między kolejnymi żądaniami. Popatrzmy na pierwszy, bardzo prostyprzykł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:1.1. Inicjalizujemy sesje2. 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.3.3. Zmieniamy dane sesji4.4. Odczytujemy dane sesjiPo odświeżeniu strony zauważymy, że licznik wskazuje już "2", po kolejnym - "3". Oznacza to, że PHP zapisujezmienną $licznik i przesyła ją między naszymi żądaniami. Różnica pomiędzy sesjami, a ciastkami jest taka, że danete w ogóle nie opuszczają serwera WWW, są przez to (w teorii) bezpieczniejsze.

Sesje 113

Prosta autoryzacja użytkownikówNapiszemy teraz prosty skrypt do autoryzacji użytkowników bazujący na sesjach. Jak wspomnieliśmy w poprzednimrozdziale, nie powinno się przesyłać pomiędzy żądaniami haseł oraz loginów użytkowników, nieważne czy wformie ciastek, czy sesji. Alternatywą jest ich ID. Jak przekonasz się w następnych rozdziałach, bazy danych, gdziewiększość aplikacji WWW trzyma swoje informacje, wcale nie identyfikują rekordów jakimiś abstrakcyjnymirzeczami typu login lub tytuł. Operują na zwyczajnych, automatycznie nadawanych i unikalnych liczbach zwanychidentyfikatorami (w skrócie pisze się "id"). Dzięki temu można szybko je do siebie porównać, co ma szczególneznaczenie 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 IDrównym 0 nie istnieje.Zatem, jak taki system logowania działa? Kiedy sesja jest już załadowana, skrypt sprawdza zapisany w niej IDużytkownika. Jeżeli jest on większy od zera, ktoś jest już zalogowany i wystarczy tylko pobrać skądś dane jegoprofilu. W przypadku ID równego 0 mamy do czynienia z kimś anonimowym. Tu, w zależności od sytuacji możemymu wyświetlać ogólnodostępne treści albo formularz logowania. Pisanie skryptu zaczniemy od stworzenia sobienamiastki bazy danych. Będzie nią tablica $uzytkownicy przechowująca loginy oraz hasła użytkowników. Ponadtonapiszemy funkcję czyIstnieje() znajdującą ID użytkownika o podanym loginie i haśle lub false, kiedy takowy nieistnieje. 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 wypadkugościa oznaczymy jako osobę anonimową (niezalogowaną):

Sesje 114

// 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ć:

Sesje 115

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

Bezpieczeństwo sesjiSytuacja z mechanizmem sesji standardowo dostępnym w PHP jest o tyle śmieszna, że jego autorzy właśnie namzostawili swobodę działania odnośnie tego, jak go zabezpieczyć przed kradzieżą. Normalnie użyty jest on bowiemdziurawy 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 SessionFixation. 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 gowygenerowaliśmy, a nie jakiś algorytm losująco-mieszający. Teraz popatrz: podszywasz się pod pracownikajakiegoś serwisu internetowego i każesz nieświadomemu użytkownikowi odwiedzić jakiśtam adres, najlepiejwymagający zalogowania. Do adresu URL doczepiasz ciąg ?PHPSESSID=abcdef. "abcdef" jest wymyślonym przeznas identyfikatorem. Kiedy użytkownik posłusznie się zaloguje, jest zdany na naszą łaskę. Mamy ID jego sesji imoż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 zainicjowaniumechanizmu 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 wszystkowygeneruje 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óryw ten sposób zostanie ujawniony przed światem. Znajomy może teraz wędrować sobie po serwisie wykorzystującsesję ciamajdy. session_regenerate_id() tutaj nie zadziałała, bo przecież ta sesja już istnieje. Ataki tego rodzajuokreś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ć jeprzy kolejnych wizytach z danymi dostarczonymi przez serwer. Jeżeli wystąpi niezgodność, ktoś próbuje użyćcudzej sesji.

Sesje 116

<?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 lepszymrozwiązaniem byłoby utworzenie sesji na nowo w przypadku niezgodności, niż obwieszczanie całemu światunaszego "odkrycia"). Pamiętaj jednak, że najsłabszym ogniwem jest zawsze człowiek. Jeżeli hacker zdobędzie twojehasło, zabezpieczenia na nic się nie zdadzą, chyba że akurat masz stały adres IP i tak sobie wszystko napisałeś, że natwoje konto można się tylko z niego logować. Tylko kto stosuje tak rygorystyczne i nieporęczne środkibezpieczeństwa?

PodsumowanieSesje PHP są bardzo dobrym rozwiązaniem, jednak paradoksalnie wiele profesjonalnych aplikacji pisze własnesystemy sesji całkowicie od zera. Powodów jest kilka:1.1. Integracja ze strukturą kodu reszty aplikacji2.2. 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 mimotego taki sam.

3.3. Bezpieczeństwo - kiedy piszesz wszystko od zera, możesz wstawić dodatkowe zabezpieczenia tam, gdzienormalnie nie sięgniesz.

4.4. Elastyczność - dlaczego dane sesji muszą być trzymane akurat w pliku? Baza danych jest przecież równie dobra,jeśli nie lepsza.

5.5. Specyfika zastosowania - wiele systemów sesji służy tylko celom autoryzacji. Stąd też pisze się je od zera pod tojedno 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 Zzaprogramować. Na razie jedynie sygnalizujemy taką możliwość jako alternatywę dla systemu sesji wbudowanegow PHP. Który z nich wybierzesz, to zależy tylko od Ciebie.

Wysyłanie e-maili 117

Wysyłanie e-maili

Wysyłanie e-mailiAby wysyłać e-maile z PHP, interpreter musi być skonfigurowany do pracy z demonem pocztowym. Odpowiedniedyrektywy znajdują się w pliku php.ini w sekcji [mail function]. Należy tam podać odpowiednie parametry wzależ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.

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.

Biblioteka pear::mailDla osób które lubią używać gotowych bibliotek można polecić moduł mail z pear. Szczegóły znajdziesz podadresem: http:/ / pear. php. net/ package/ Mail . Biblioteka jest dosyć prosta i posiada w swojej dokumentacji jasneprzykłady.Wysyłanie załączników jest równie proste - zainteresuj się klasą pear::Mail_Mime.

PrzykładyNastępujący przykład spowoduje wysłanie wiadomości e-mail na adres [email protected] o temacie "Witaj" itreści "Oto test funkcji mail":

<?php

if(mail('[email protected]', '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: [email protected]".PHP_EOL."Reply-To:

[email protected]".PHP_EOL."Content-type: text/plain; charset=iso-8859-2";

if(mail('[email protected]', 'Witaj', 'Oto test funkcji mail',

$naglowki))

{

echo 'Wiadomość została wysłana';

}

Wysyłanie e-maili 118

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 najlepiejpodawać wszystkie nagłówki w jednym ciągu i zejścia zaznaczać przy użyciu właśnie tych kodów. Inaczej może tospowodować wysłanie niepoprawnej wiadomości, gdyż niektóre edytory mają tendencję do zniekształcania tychzejść.

Uwaga!Nie umieszczaj danych od użytkownika w nagłówkach bez uprzedniego usunięcia z nich przejść do nowej linii. W przeciwnym wypadkuużytkownik będzie miał możliwość podrzucenia nowych nagłówków, a nawet dodania załączników do wiadomości i twój skrypt zostaniewykorzystany przez spamboty do rozsyłania spamu.

PoradaNagłówki używające polskich liter muszą być potraktowane specjalnym kodowaniem, np. Quoted-Printable. W przeciwnym wypadkuserwery i czytniki poczty nie rozpoznają kodowania nagłówków i mogą zdeformować polskie znaki lub wręcz uznać je za błąd transferu.

Z poziomu PHP można także wysyłać e-maile w formacie HTML. W tym celu należy dodać do wiadomościodpowiednie nagłówki:

<?php

// Naglowki mozna sformatowac tez w ten sposob.

$naglowki = "Reply-to: [email protected] <[email protected]>".PHP_EOL;

$naglowki .= "From: [email protected] <[email protected]>".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('[email protected]', 'Witaj', $wiadomosc, $naglowki))

{

echo 'Wiadomość została wysłana';

}

PoradaNiektórzy programiści piszą własne implementacje funkcji mail(), samodzielnie łącząc się z serwerem i obsługując protokół SMTP.Pozwala to na uniezależnienie się od ustawień połączenia w pliku php.ini, lecz jest skomplikowane i nie będziemy się tym zagadnieniemtu zajmować. Warto jest poznać możliwości biblioteki PHPmailer.

Wysyłanie e-maili 119

Dokumentacja• Funkcje pocztowe w php [1]

• Opis działania funkcji mail() [2]

Przypisy[1] http:/ / pl. php. net/ manual/ en/ ref. mail. php[2] http:/ / pl. php. net/ manual/ en/ function. mail. php

Internacjonalizacja

InternacjonalizacjaW tym rozdziale przyjrzymy się zagadnieniom związanym z dostosowywaniem naszych witryn do konkretnychjęzyków i ustawień regionalnych. PHP posiada odpowiedni zestaw narzędzi, który czyni to zadanie nietrudnym wwykonaniu. Lecz uważaj! Nieuważne skonfigurowanie mechanizmu może być przyczyną błędów!

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 interpreterwiedział, że Ż jest literą polskiego alfabetu, która również podlega zamianie, a drugi nie? Odpowiedź kryje się wustawieniach regionalnych tych maszyn, znanych z terminologii linuksowej pod nazwą locale. PHP domyślniewykonuje 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 funkcjisetlocale(). Za pierwszy parametr podajemy flagi określające, które aspekty regionalizacji chcemy zmodyfikować, awszystkie kolejne to identyfikatory możliwych ustawień. PHP będzie próbował każdego z nich po kolei, aż trafi nataki, który jest zainstalowany w systemie. Dzięki temu jedną funkcją można przystosować skrypt do pracy zarównoz 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.

Internacjonalizacja 120

Uwaga!Funkcja setlocale() wpływa także na formatowanie liczb zmiennoprzecinkowych, co może być zgubne w skutkach podczas pracy zbazami danych! Dokładne omówienie tego problemu znajdziesz w dalszych rozdziałach.

Polska dataPisząc aplikację przeznaczoną na rynek międzynarodowy, powinniśmy pomyśleć także o formatowaniu dat. Poznanajuż 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 musimysamodzielnie napisać sobie funkcję, która nam to przeformatuje. Umożliwiając dodawanie obsługi nowych językówdo 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 tomodyfikacja 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';

Internacjonalizacja 121

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.

Internacjonalizacja 122

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 [1].

Wielojęzyczny interfejsKolejnym krokiem na drodze umożliwienia obcokrajowcom przeglądania naszych witryn jest stworzenie kilkuwersji językowych. Realizuje się to, tworząc nakładki językowe, w których poszczególnym komunikatom interfejsuprzypisane 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 tekstw zależności od wybranego języka. Ponieważ nie znamy jeszcze programowania obiektowego, będziemy musielinapisać 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))

Internacjonalizacja 123

{

$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 oddzielniegrupy. W ten sposób strona z newsami może załadować sobie tylko treści komunikatów dla newsów, z pominięciemnp. 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 plikjezyki/pl/global.php:

komunikat = "Komunikat"

hello_world = "Witaj swiecie!"

Korzysta z niego poniższy przykład:

<?php

require('./i18n.php');

Internacjonalizacja 124

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. Abybył 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 torozwiązanie najlepsze, ale na tym etapie wiedzy powinno nam wystarczyć. Kiedy poznamy programowanieobiektowe, będziesz mógł przepisać ten kod z jego wykorzystaniem, dzięki czemu zyska on na elegancji ibezpieczeń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 tenmoduł nie będą działać.

Automatyczna detekcja językaPrzeglądarka internetowa to cenne źródło informacji o internaucie. Przesyła ona m.in. informację o języku, w jakimona pracuje. Możemy przyjąć, że jest to rodzimy język użytkownika i na podstawie tego automatycznie ustawić muodpowiednią 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

Internacjonalizacja 125

{

$uzyty = 'en';

}

echo 'Wybrany język to '.$zainstalowane[$uzyty];

?>

Analiza powyższego skryptu:1.1. Tablica z nazwami zainstalowanych języków - tylko do celów kontrolnych.2. 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 wogóle potrzebna i możemy ją usunąć. Stąd w drugim explode pojawia się odwołanie $jezyki[0] wskazujące poprostu na pierwszą, istotną część przekazu.

3.3. Pętlą przelatujemy całą tablicę. Jeśli zauważymy, że dany język jest zainstalowany, ustawiamy go i przerywamypętlę.

4. Jeżeli zmienna $uzyty ma w tym miejscu dalej domyślną wartość null, oznacza to, że żaden z językówuż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 miejscusprawdzał 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ędzieobsługiwany przez naszą witrynę.

Internacjonalizacja plikami .moTakie wykonanie skryptu jest niewygodne. Najbardziej popularnym sposobem jest budowanie tego na plikach .mo.Utwórz plik lang.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

?>

1.1. Przeglądarki wysyłają informacje o języku. Jeśli użytkownik ma ustawiony w przeglądarce język polski, zobaczypolską wersję strony, jeśli nie - angielską.

2.2. Przygotowuje domenę tekstową. Zmień "domena" na coś innego.3.3. Ustawia domenę na kodowanie UTF-8. Zmień "domena" na coś innego.4.4. Wybiera domenę. Zmień "domena" na coś innego.Ten plik dołączamy do każdych plików, które wymagają internacjonalizacji funkcją<pre>include("./lang.php");</pre> Teraz należy zamienić wszystkie teksty tak jak tu:

Internacjonalizacja 126

<?php

echo "Polska wiadomość";

// =

echo _("Polska wiadomość");

Pobieramy edytor plików .po, np. Poedit [2] i tworzymy pliki .mo (obsługa poniżej). Ważna jest także budowastruktury katalogów.

|

|-lang.php |-en_US -|LC_MESSAGES -|domena.mo

|-locale -|-pl_PL -|LC_MESSAGES -|domena.mo

|

(katalog zawiera plik lang.php i folder locale. W nim znajdują się

podfoldery z kodami języków (en_US i pl_PL). W każdym podfolderze

języków znajduje się folder LC_MESSAGES, którego zawartością w obu

podfolderach języków jest plik domena.mo z przetłumaczonymi tekstami.)

Obsługa PoEditObsługa jest bardzo prosta - wybieramy Plik > Nowy katalog, uzupełniamy dane. Zakładka ścieżki jest bardzoważ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. 2dodajemy ścieżkę D:\Serwer\www\gettext\gettext oraz jeśli potrzebne jej podkatalogi.

TestowanieAby 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ą.

ZakończenieInternacjonalizacja jest bardzo ważnym zagadnieniem. Jeśli naprawdę myślimy o ekspansji na rynki zagraniczne, nielekceważmy jej. Czas zainwestowany w uczynienie serwisu bardziej przyjaznym obcokrajowcom w przyszłościzaprocentuje ich dobrymi wrażeniami z pobytu u nas.

Przypisy[1] http:/ / www. php. net/ strftime[2] http:/ / www. poedit. net/ download. php

System plików 127

System plików

System plikówPrzez 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ć, jestwydajność. Wszelkie odwołania do systemu plików są dosyć powolne i często stanowią nawet wąskie gardło wszybkoś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.

Odczyt danychZanim 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ęzykaC:

<?php

// 1.1 - Tworzymy odwołanie do pliku

$_Uchwyt = fopen('plik.txt', 'r');

// 1.2 - Wykonujemy kod dopóki skrypt nie napotka końca pliku

while(!feof($_Uchwyt))

{

// 1.2.1 - Czytamy z pliku jeden kilobajt (1024 B)

echo fread($_Uchwyt, 1024);

}

// 1.3 - Zamykamy plik

fclose($_Uchwyt);

?>

Każdy dostęp do pliku musi rozpocząć się od jego otwarcia. Zadaniem tym zajmuje się funkcja fopen(). Parametr rnakazuje 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, czyosiągnęliśmy koniec pliku. Po zakończonej pracy nasze połączenie z plikiem trzeba zamknąć. Odpowiada za tofclose().

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ć funkcjifilesize(), aby podała nam rozmiar pliku:

<?php

// 1.1 - Otwieramy

$_Uchwyt = fopen('plik.txt', 'r');

// 1.2 - Czytamy całą zawartość pliku

echo fread($_Uchwyt, filesize('plik.txt'));

System plików 128

// 1.3 - Zamykamy

fclose($_Uchwyt);

?>

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 tutajoperator @, aby zagłuszyć funkcję fopen() i sprawdzić zwracany wynik. Powinna ona zwrócić nam połączenie zplikiem, tj. wartość typu Resource. Zobaczmy:

<?php

// 1.1 - Otwieramy plik, alarmujemy w wypadku błędu

$_Uchwyt = @fopen('inny_plik.txt', 'r') or die('Wystąpił błąd.');

// 1.2 - Czytamy plik

echo fread($_Uchwyt, filesize('inny_plik.txt'));

// 1.3 - Zamykamy

fclose($_Uchwyt);

?>

Uwaga!Jeśli skrypt odczytujący zacznie się dziwnie zachowywać, pierwszym krokiem powinno być tymczasowe usunięcie operatora @ - inaczejnigdy nie dowiesz się, co jest przyczyną problemów!

PoradaW celu uproszczenia twojego kodu możesz napisać sobie własny wariant funkcji fopen() posiadający twoją obsługę błędów. Uprości tozarządzanie kodem projektu.

Od PHP 4.3.0 nie trzeba już rozpisywać się, aby wczytać zawartość pojedynczego pliku. Cała czynność jestzautomatyzowana 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

// 1.1 - Otwieramy, czytamy i zamykamy

$_TrescPliku = @file_get_contents('plik.txt') or die('Wystąpił

błąd.');

// 1.2 - Zwracamy treść pliku

echo $_TrescPliku;

?>

Pisząc księgę gości, poznaliśmy funkcję file(), która zwracaną zawartość rozbijała od razu na tablicę poszczególnychlinijek. Dzięki tej właściwości wyświetlimy plik jako listę wypunktowaną HTML bez większych trudności:

<?php

// 1.1 - Otwieramy plik i czytamy go do tablicy

$_TrescPliku = @file('plik.txt') or die('Wystąpił błąd.');

// 1.2 - Otwieramy znacznik listy

echo '<ul>';

System plików 129

// 1.3 - Dla każdej linii pliku...

foreach($_TrescPliku as $_Linia)

{

// 1.3.1 - ...tworzymy nowy element listy...

echo '<li>'.$_Linia.'</li>';

}

// 1.4 - ...i zamykamy listę.

echo '</ul>';

?>

Pamiętaj, że file() nie gubi znaków końca linii - te są nadal zapisane w poszczególnych linijkach. Dlatego gdybędziesz chciał z powrotem połączyć wszystko w całość, powinieneś napisać

implode("", $_TrescPliku);

zamiast np.

implode("\n", $_TrescPliku);

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 wykorzystaniemfile_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

// 1.1 - Inicjacja zmiennej

$_Plik='plik.txt';

// 1.2 - Kontynuujemy jeśli plik istnieje

if (file_exists($_Plik))

{

// 1.2.1 - Czytamy zawartość

$zawartosc=file($_Plik);

// 1.2.2 - Sprawdzamy czy plik jest pusty

if (count($zawartosc)==0)

{

die("Plik: $_Plik jest pusty!");

}

}

else

System plików 130

{

// 1.2.3 - Jeśli plik nie istnieje wyświetlamy komunikat

die("Plik: $_Plik nie istnieje!");

}

// Tutaj dalsze operacje na pliku

?>

Metoda na "pana nauczyciela" polega na pełnym obsłużeniu wszystkich możliwości jakie mogą wystąpić podczasczytania pliku oraz posłużenia się funkcjami typu file_exists(). Wadą takiego rozwiązania jest jednak to, że pisząckod możemy zakopać się w blokach if 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!

Zapis danychZapis danych wygląda analogicznie do odczytu. Różne jest tylko miejsce docelowe danych. Sposób pierwszy polegana otwarciu pliku funkcją fopen() i skorzystaniu z fwrite() do dodania nowej zawartości. Plik otwieramy zparametrem w (nadpisujemy starą zawartość) lub a (dopisujemy coś do pliku). W przypadku operowania danymibinarnymi, dodajemy jeszcze literę b.

<?php

// 1.1 - Otwieranie

$_Plik = fopen('./plik.txt', 'w');

// 1.2 - Zapisywanie łańcucha

fwrite($_Plik, 'To jest nowa zawartość pliku');

// 1.3 - Zamykanie

fclose($_Plik);

?>

Po uruchomieniu tego skryptu w plik.txt powinna pojawić nam się nowa zawartość. W przypadku pracy na systemieLinux/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 dopliku bajtów i możemy wykorzystać to do kontroli, czy operacja dopisywania faktycznie się udała. Funkcja pobieradwa parametry: nazwę pliku oraz tekst do wpisania i "firmowo" nie zniekształca danych binarnych.

<?php

// 1.1 - Jeśli zapis się powiódł wyświetl komunikat o powodzeniu

if(file_put_contents('./plik.txt', 'To jest nowa zawartość

pliku') != 0)

{

echo 'Udało się zapisać nową zawartość do pliku.';

}

?>

System plików 131

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, czegopotrzebujemy.

<?php

// 1.1 - Jeśli zapis się powiódł wyświetl komunikat o powodzeniu

if(file_put_contents('./plik.txt', 'Dopisana treść', FILE_APPEND)

!= 0)

{

echo 'Udało się dodać zawartość do pliku.';

}

?>

Ten skrypt będzie już dopisywać dane do pliku, zamiast je nadpisywać.

Informacje o plikachW 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 parametrnazwę 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 mamyplik A.txt i na jego podstawie generujemy B.txt zawsze, kiedy ulegnie on zmianie (taki kompilator). Musimy zatemnapisać 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.';

}

System plików 132

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 zbytwolno, gdyż przeciążyliśmy go dużą ilością odwołań do dysku twardego. Jeżeli uruchomimy go na witrynie z dużymruchem, 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ędzieistniał 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';

}

?>

System plików 133

Zauważmy, w tym przypadku mamy tylko dwa odwołania do dysku, a jeśli plik A.txt nie będzie istnieć, to nawetjedno! Zamiast wykonywania za każdym razem setek nowych funkcji, wykorzystujemy maksymalnie te dane, którejuż mamy. To jest właściwa filozofia przy pracy z plikami.

ZakończeniePlikom poświęciliśmy naprawdę bardzo duży rozdział. Jednak mało która aplikacja PHP wykorzystuje je jakogłówne źródło danych dla internauty. Znacznie poważniejszym i mającym większe możliwości narzędziem są bazydanych. Zagadnienie to jest omówione w następnej części podręcznika. Czy jednak pliki należy w takim raziewyrzucić? Nie, ze względu na wydajność. Wbrew pozorom, odczyt rekordów z bazy zazwyczaj jest wolniejszy, niż zpliku i w przypadku elementarnych ustawień aplikacji, które nie wymagają złożonego sortowania oraz stosowaniarozbudowanych relacji (np. konfiguracja, dane systemowe), można pokusić się o zastąpienie ich plikami.

Data i czas

Data i czasPrzeglądając wiele dynamicznych stron internetowych, można napotkać multum różnych przykładów użycia daty iczasu. W księgach gości i forach dyskusyjnych zapisywana jest godzina wysłania wiadomości. Większość licznikówdziennych opiera się na tych funkcjach. W niektórych serwisach można zmieniać strefy czasowe, a wszystkienajskuteczniejsze zabezpieczenia przed spamem są bazowane na limitach czasowych. Ten rozdział będziepoświęcony właśnie funkcjom służącym do zadań związanych z operowaniem obecnym czasem.

Formatowanie datyPodstawową funkcją do zwracania obecnej daty jest date(), która przyjmuje jako parametr wartość tekstową. I tutajpojawia się pierwsza zagadka, wielu na początku zastanawiałoby się czemu akurat wartość tekstowa - tekst nie mawiele wspólnego z datą.I tutaj kłania się PHP, pomagając wielu programistom. Łańcuch ten, mówi jak należy przeformatować datę - nietrzeba pisać własnych funkcji. W tym parametrze funkcji można umieszczać własne znaczniki, mówiące jakieinformacje należy wyświetlić:

<?php

echo date('d-m-Y');

?>

Powyższy kod zwróci wartość podobną do 17-04-2004. Spróbuj usunąć myślniki pomiędzy literami. Co zauważyłeś?

W pierwszym parametrze funkcji date(), w celu prawidłowego sformatowania daty, można używać znaków specjalnych.

Pamiętaj też, iż nie należy sugerować się nazwą funkcji. Funkcja date() umożliwia również wyświetlenie godziny,strefy czasowej, a nawet informacji, czy czas jest letni:

<?php

echo date('H:i:s'); // Godzina, np. 09:39:21

echo date('e / Z'); // Różnica dla strefy czasowej i identyfikator dla

niej (np. Europe/Berlin / 3600)

if (date('I')=="1"){ echo "Czas letni"; }else{ echo "Czas zimowy"; }

// date('I') zwraca 1 gdy czas jest letni - można to wykorzystać

Data i czas 134

?>

Problemy z formatowaniemPrzyjmijmy, że na końcu daty chcesz dodać wyraz "rok". Wiele osób korzystając z możliwości bezpośredniegoformatowania daty zrobiłoby tak:

echo date('d-m-Y rok');

Wynik tego kodu byłby podobny do przykładu poniżej:

06-03-2010 Sat, 06 Mar 2010 21:17:44 +01002010k

To co widzimy, jest zupełnie inny wynik, niż był oczekiwany. Dlaczego? Duża część liter w pierwszym parametrzefunkcji date() jest używana do wypisywania danych. PHP widząc fragment:

rok

Zamienił litery na dane i uzyskano:

Sat, 06 Mar 2010 21:17:44 +01002010k

Jak to ominąć? Umieszczając tekst poza funkcją, np.:

echo date('d-m-Y ')."rok";

Jest jeszcze jedna metoda nie zapisana w dokumentacji tego języka, ale poprawnie działająca. Polega ona napoprzedzeniu liter backslashem. Trzeba pamiętać, że jeżeli format daty zostałby podany pomiędzy podwójnymicudzysłowami, to przykładową literę "r" należałoby poprzedzić podwójnym ukośnikiem ( zrobiłby się wówczas znich tylko jeden ). W przeciwnym wypadku zrobiłaby się z niej tabulacja, a nie litera.

echo date('d-m-Y \r\o\k');

Ten sposób jest bardziej przenośny oraz można go umieścić w jednej stałej i odwoływać się do niego w innychskryptach. W poprzednim sposobie nie ma takiego komfortu.

Lista znacznikówd - Dzień miesiąca, 2 cyfry z wiodącymi zeramiD - Tekstowy opis angielskiej nazwy dnia, trzy literyj - Dzień miesiąca bez zer wiodącychl (mała litera 'L') - Pełen angielski opis dnia tygodniaN - Liczbowa forma dnia tygodnia, zgodna z normą ISO-8601 (dodana w PHP 5.1.0)S - Angielski przyrostek porządkowy dla dnia miesiąca, 2 literyw - Liczbowa forma dnia tygodniaz - Dzień roku (Zaczynając od 0)W - Numer tygodnia w roku, zgodny z normą ISO-8601, Tygodnie rozpoczynają Poniedziałki (dostępne od PHP4.1.0)F - Pełen angielski opis, dnia miesiąca, taki jak January czy Marchm - Liczbowa forma miesiąca, z zerami wiodącymi

Data i czas 135

M - Krótki, angielski opis miesiąca, trzy literyn - Liczbowa forma miesiąca, bez zer wiodącycht - Ilość dni w danym miesiącuL - Informacja o tym, czy rok jest przestępnymo - Numer roku, zgodny z normą ISO-8601. Zwraca to taką samą wartość jak Y, z takim wyjątkiem, że numertygodnia ISO (W) należy do poprzedniego lub następnego roku, niż rok użyty w tym miejscu. (dodane w PHP 5.1.0)Y - Pełna liczbowa forma roku, 4 cyfryy - Dwie cyfry reprezentujące roka - Pora dnia - dwie małe litery (przed/po południu) (ang. Ante/Post meridiem)A - Pora dnia - dwie duże litery (przed/po południu) (ang. Ante/Post meridiem)g - Godzina, w formacie 12-godzinnym, bez zer wiodącychG - Godzina, w formacie 24-godzinnym, bez zer wiodącychh - Godzina, w formacie 12-godzinnym, z zerami wiodącymiH - Godzina, w formacie 24-godzinnym, z zerami wiodącymii - Minuty z zerami wiodącymis - Sekundy, z zerami wiodącymie - Identyfikator strefy czasowej (dodano w PHP 5.1.0)I (duże i) - Informacja o tym, czy czas jest letniO - Różnica z czasem Greenwich (GMT) w godzinachP - Różnica z czasem Greenwich (GMT) z dwukropkiem pomiędzy godzinami i minutami (dodano w PHP 5.1.3)T - Skrót dla strefy czasowejZ - Różnica dla strefy czasowej w sekundach. Wyrównanie to jest zawsze ujemne dla stref położonych na zachód odpołudnika 0, oraz dodatnie dla tych leżących na wschód od niego.c - Data w standardzie ISO 8601 (dodana w PHP 5)r - Data sformatowana zgodnie z RFC 2822U - Sekundy liczone od ery UNIX-a (1 stycznia 1970 00:00:00 czasu Greenwich - GMT)

Zmiana datyWyświetlanie daty i czasu nie ogranicza się do wyświetlania informacji obecnych. Drugi parametr funkcji date()przyjmuje wartość czasu w sekundach od 1 stycznia 1970. Jeśli zostanie on podany, funkcja wyświetli czas, którybył zapisany w tym parametrze. Jest on opcjonalny, tak więc:

date('r');

jest tym samym co:

date('r',time());

Przy następnych etapach będzie konieczne poznanie funkcji time, zwracającej czas liczony od tzw. "Ery UNIX-a"(01.01.1970 r.)Teraz zastanówmy się - jak wyświetlić datę, która będzie za 1 dzień, tydzień, 21 dni, rok? Tutaj należy sobieprzypomnieć jednostki i wykorzystać matematykę:

Data i czas 136

1 minuta to 60 sekund

60

60 minut to 1 godzina

60*60

24 godziny to 1 doba

60*60*24

7 dni to 1 tydzień

60*60*24*7

365 dni to 1 rok (bez uwzględnienia przestępności)

60*60*24*365

Teraz ładnie trzeba to przełożyć na PHP. Sprawa załatwiona:

<?php

// Aby nie liczyć kilka razy tego samego, użyjemy już wcześniej

poznanych stałych

define('MINUTA',60);

define('GODZINA',60*60);

define('DOBA',60*60*24);

define('TYDZIEN',60*60*24*7);

define('ROK',60*60*24*365);

// Zwracamy dane

echo date('d-m-Y H:i:s',time()+MINUTA); // Za minutę

echo "<br>";

echo date('d-m-Y H:i:s',time()+GODZINA); // Za godzinę

echo "<br>";

echo date('d-m-Y H:i:s',time()+3*ROK+17*DOBA+6*GODZINA+39*MINUTA); //

Za 3 lata, 17 dni, 6 godzin i 39 minut

?>

Jednak przed nami staje jeszcze problem optymalizacji, oraz dobra naszej klawiatury - pisanie np.17*ROK+4*TYDZIEN+17*DOBA+4*GODZINA+7*MINUTA+42 jest bardzo męczące. Tutaj znów przychodzi zpomocą PHP, dzięki funkcji strtotime(). Funkcja ta zamienia większość angielskich tekstowych opisów daty i czasuna znacznik czasu:

<?php

echo date('d-m-Y H:i:s',strtotime("now"));

echo date('d-m-Y H:i:s',strtotime("+1 day"));

echo date('d-m-Y H:i:s',strtotime("+1 week 3 days 11 hours"));

echo date('d-m-Y H:i:s',strtotime("next Monday"));

echo date('d-m-Y H:i:s',strtotime("24 January 2003"));

?>

Dzięki temu nie jest wymagane użycie żadnych skomplikowanych operacji matematycznych - PHP zrobi wszystkoza ciebie.

Data i czas 137

Sprawdzanie datyCzasem w formularzu wymagane jest sprawdzenie, czy podana data jest prawdziwa. Tutaj bardzo dobrze sprawdzisię funkcja checkdate(). Przyjmuje ona za argumenty miesiąc, dzień i na końcu rok daty do sprawdzenia.

var_dump(checkdate(7, 16, 2006));

var_dump(checkdate(2, 30, 2003));

Różne formaty datyZauważ, że checkdate() nie przyjmuje tekstu jako parametrów. Czy to oznacza, że gdy mamy datę np. "9.21.2004",nie można jej sprawdzić? Tutaj pasuje idealnie wcześniej poznana funkcja explode().

$_Dane = explode(".","9.21.2004");

var_dump(checkdate($_Dane[0], $_Dane[1], $_Dane[2]));

ZakończeniePoznałeś już większość funkcji związanych z czasem. Naprawdę jest ich więcej, ale aby ich używać należy wejść wgłębia programowania obiektowego, którego podstaw będziesz się uczył już w następnym rozdziale.

138

Programowanie obiektowe

Czym jest programowanie obiektowe?

Czym jest programowanie obiektowe?Dotychczas pisane przez nas skrypty miały charakter strukturalny. Programowanie strukturalne jest proste doopanowania i wystarczające dla podstawowych skryptów, jednak wraz z ich rozrastaniem się panowanie nad corazwiększą liczbą funkcji staje się coraz trudniejsze. W tym rozdziale nauczysz się nowej techniki programowaniazwanej programowaniem obiektowym, która jest wykorzystywana w niemal wszystkich bardziej zaawansowanychaplikacjach. W założeniu jest ona dość intuicyjna i opiera się na naturalnym postrzeganiu świata przez człowieka,lecz wielu programistów miewa z nią na początku kłopoty. Zalecamy dobre przyłożenie się do tego rozdziału,ponieważ znajomość prezentowanego materiału jest kluczowa do zrozumienia dalszej części podręcznika.

Filozofia programowania obiektowegoProgramowanie zorientowane obiektowo oparte jest na zupełnie innej filozofii, niż dotychczasowe skrypty, jakiepisaliśmy. Podstawowymi bytami, którymi operujemy, są obiekty oraz klasy, które całkiem nieźle odzwierciedlająrzeczywistość wokół nas. Próba wyrażenia wycieczki do sklepu po zakupy przy pomocy funkcji będzie wyglądaćnienaturalnie i zupełnie nieintuicyjnie, tymczasem bardzo łatwo możemy ją opisać obiektami. Idziemy do sklepu ibierzemy jeden z dostępnych obiektów klasy koszyk. Wiemy, że we wszystkich koszykach możemy umieszczaćtowary. Wędrując wśród półek, do koszyka wrzucamy obiekt klasy chleb, która jest szczególnym przypadkiemtowaru. Nasz chlebek znajduje się w koszyku, ale na półce są jeszcze inne takie obiekty, które mogą zostać wzięteprzez innych klientów. Bierzemy też masło i jakiś napój (wszystko to obiekty odpowiednich klas) i z tym wszystkimwędrujemy do obiektu kasa, któremu przekazujemy nasz koszyk. W kasie dowiadujemy się, ile pieniędzy musimyprzekazać, aby pobrane obiekty stały się nasze. Po zapłaceniu odbieramy nasze obiekty i bierzemy je do domu, czyliobiektu klasy dom.W tej krótkiej historyjce zawarliśmy wszystkie podstawowe cechy programowania obiektowego. W kodzie naszegoprogramu tworzymy klasy, które opisują pewien rodzaj przedmiotu, definiując jego właściwości oraz zachowania. Wsklepie mieliśmy do czynienia z klasą koszyk, o której wiedzieliśmy że:•• Każdy koszyk ma określoną pojemność (właściwość)•• W koszykach mogą znajdować się towary (właściwość - lista towarów, które są w koszyku).•• Do koszyków możemy wrzucać nowe towary (zachowanie).Wszystkie koszyki w sklepie to obiekty tej klasy. Mają one wszystkie identyczny zestaw właściwości orazidentycznie się zachowują, lecz każdy koszyk jest na swój sposób wyjątkowy. Wzięliśmy obiekt koszyk opojemności 10 litrów i aktualnie zawiera on chleb, masło i napój. Klient, który przeszedł obok nas, niesie swójwłasny koszyk o pojemności 15 litrów, w którym niesie warzywa, mąkę i mięso. Są to dwa różne koszyki, jednakzachowujące się tak samo.

Czym jest programowanie obiektowe? 139

Dla kasjera każda wędlina jest towarem. Dla klienta nie każdy towarjest wędliną.

W historyjce o wizycie w sklepie mówimy też otowarach, czyli obiektach klasy towar. Powiedzieliśmytakże, że "chleb jest towarem". Taki związek nosinazwę dziedziczenia i również występuje wprogramowaniu obiektowym. Stwierdzenie to oznacza,że chleb posiada wszystkie właściwości i zachowaniatowaru (cena, możliwość zmiany ceny przezpracownika), a jednocześnie dodaje nowe, specyficznedla siebie, np. wagę czy możliwość pokrojeniabochenka na pół, co oczywiście wpłynie też na cenę.Każda klasa może dziedziczyć po dowolnej innejklasie, przejmując jej właściwości i zachowania. Dziękitemu w kasie można wszystko podliczyć. Kasaprzetwarza towary, zatem interesuje ją jedynie cena bezwzględu na to, czy dany towar jest chlebem czynapojem. Gdyby w sklepie była krajalnica, obsługiwałaby ona wyłącznie różne rodzaje chleba - nie możemypołożyć na niej napoju, ponieważ tu samo bycie towarem już nie wystarcza; obiekt, który kładziemy, musi być conajmniej chlebem.

Jest też jeszcze jeden aspekt dziedziczenia - klasa dziedzicząca może modyfikować zachowania klasy bazowej.Przypuśćmy, że nasze towary są wyposażone w zachowanie oblicz cenę. W podstawowej wersji podaje ono poprostu wartość właściwości cena, jednak niektóre rodzaje towarów mogą wymagać bardziej złożonychprzeliczników. Tak będzie w przypadku warzyw, gdzie cena jest uzależniona od wagi kupowanego towaru. W klasiewarzywa możemy zaznaczyć: "w zwykłym towarze po prostu braliśmy podaną cenę, jednak tutaj będziemyuwzględniać wagę". Gdy warzywa trafią do kasy, kasjer oczywiście wykona obliczanie ceny. W tym miejscumożliwe są dwa wyniki:• Ponieważ kasjer przetwarza towary, wybierze zawsze zachowanie charakterystyczne dla towaru nawet, gdy

będzie kasować warzywa, które powinny być traktowane inaczej.•• Kasjer przetwarza towary, ale wie, że niektóre towary mogą mieć specyficzne zasady wyceniania i stosuje się do

nich.W tym drugim przypadku mamy do czynienia z tzw. polimorfizmem, czyli z sytuacją, gdy wciąż pamiętamy oprawdziwej naturze obiektu, nawet gdy traktujemy go bardziej ogólnie. W PHP wszystkie zachowania sąpolimorficzne, dlatego w tym języku obowiązywać Cię będzie wyłącznie druga możliwość. Brak polimorfizmu jestze względów wydajnościowych stosowany w niektórych językach kompilowanych, jak C++ i wspominamy o nimjedynie przy okazji.Podsumujmy to, czego zdążyliśmy się dowiedzieć. Wbrew pozorom nowych pojęć nie ma wcale aż tak dużo:• Klasy - definiują pewien rodzaj obiektów o określonych właściwościach i zachowaniach• Obiekty - rzeczywiste byty, na których pracujemy.• Właściwości - pewne informacje charakteryzujące obiekt. W dalszej części będziemy je nazywać "polami" klasy

bądź obiektu.• Zachowania - definiują, co obiekty danej klasy mogą robić. W dalszej części będziemy je nazywać "metodami".• Dziedziczenie - pozwala wyrażać zależności "X jest Y-kiem".• Polimorfizm - pamiętanie o prawdziwej naturze obiektów nawet, gdy rozpatrujemy je z punktu widzenia

ogólniejszej klasy.Wiemy już, że programowanie obiektowe świetnie opisuje otaczającą nas rzeczywistość, dlatego teraz zastanowimysię, jak za jego pomocą opisać środowisko programu komputerowego, a w szczególności skryptu strony

Czym jest programowanie obiektowe? 140

internetowej.

Programowanie obiektowe w aplikacjachPrzejście z poziomu analizy świata rzeczywistego do abstrakcyjnego środowiska programu komputerowego sprawiapoczątkującym programistom wiele trudności. Na pierwszy rzut oka ciężko powiedzieć, co w programie powinnobyć klasą, ile i jakich obiektów tworzyć oraz jak rozdzielić funkcje programu między klasy, by wszystko miało ręce inogi? Najlepiej przyglądać się rzeczywistym skryptom. Techniki programowania obiektowego są rozbudowanądziedziną wiedzy, o której można napisać kilka podręczników takich, jak ten. Nabranie wprawy z pewnością zajmietrochę czasu, dlatego nie zrażaj się początkowymi niepowodzeniami.W aplikacjach WWW podstawowym zastosowaniem programowania obiektowego jest kontrola nad akcją, którąmusimy dla użytkownika wykonać. Przypomnijmy sobie naszą księgę gości. Mieliśmy tam różne akcje w stylu"dodaj wpis" czy "przeglądaj wpisy", które były umieszczone w osobnych plikach oraz wybierane instrukcją**switch**. W kodzie obiektowym stworzymy sobie specjalną klasę bazową Moduł. Stwierdzamy w niej, żemoduły mają akcje oraz definiujemy metody pozwalające wywołać akcję o podanej nazwie. Następnie tworzymysobie specjalizowane klasy reprezentujące poszczególne moduły naszej strony: News, Księga gości itd. Każda klasazawierać będzie kilka akcji związanych z danym modułem reprezentowanych przez metody. Przykładowo, w klasieKsięga gości umieścimy metody wyświetl oraz dodaj wpis.Tworzymy także kolejną klasę Nadzorca, która będzie potrafiła tworzyć obiekty określonych modułów i żądać odnich wykonania określonych akcji. W obrębie jednego żądania może zostać wykonanych kilka akcji z różnychmodułów, dlatego nadzorca musi umieć także przechowywać obiekty tych, które już załadował. Działanie skryptumożemy opisać wtedy następująco:

Przypuśćmy, że chcemy wyświetlić listę wpisów w księdze gości. Dlatego tworzymy obiekt nadzorcy iprzekazujemy do niego nazwę odpowiedniego modułu i akcji, po czym każemy mu rozpocząć pracę. Nadzorcazaładuje odpowiednią klasę, utworzy jej obiekt i nakaże mu wykonać określoną akcję, dzięki czemu wprzeglądarce ujrzymy to, co chcemy zobaczyć.

Zauważmy, że od naszych klas tworzyliśmy jedynie po jednym obiekcie. To nie przypadek i nie pomyłka. Bardzoczęsto zdarza się, że tworzymy klasę tylko po to, by mieć dokładnie jeden jej obiekt (ba, czasami nawet wymuszamyodpowiednim kodem niemożność utworzenia większej liczby obiektów). Na początku wydaje się to dziwne, alezwróćmy uwagę na kilka rzeczy:1.1. Jeśli tworzymy dużą aplikację, chcielibyśmy, aby była ona w miarę spójna. Skoro już utworzyliśmy kilka klas,

warto wszystkie elementy aplikacji przedstawić w takiej postaci nawet, jeśli oznacza to tworzenie tylkopojedynczych ich obiektów.

2.2. Obiekty posiadają pola, które są niczym innym, jak kolejnym rodzajem zmiennych. Jest to wygodny sposóborganizowania danych w naszej aplikacji, bez bardzo brzydkiego tworzenia zmiennych globalnych, instrukcji**global** itd. W dodatku obiekty posiadają bardzo precyzyjne mechanizmy określające, kto i kiedy może dostaćsię do wybranych danych, co poprawia niezawodność.

3.3. Mając klasę, możemy skorzystać z dziedziczenia. Przecież nasza aplikacja WWW może mieć kilku różnychnadzorców do różnych zastosowań. Zależność "X jest Y-kiem" jest bardzo przydatna, a nie uzyskamy jej takłatwo przy pomocy zwykłych funkcji.

Kolejne zastosowanie to interakcja z bazami danych, do której przejdziemy tuż po rozdziale poświęconymprogramowaniu obiektowemu. W bazach danych istnieją dziesiątki elementów o różnorodnej strukturze: artykuły,wiadomości, profile użytkowników. Istnieją specjalne biblioteki zwane ORM (ang. Object-Relational mapper), któreprzenoszą te elementy do aplikacji właśnie w postaci obiektów.

Czym jest programowanie obiektowe? 141

Słowo końcowePrzedstawione tu informacje mają póki co bardziej teoretyczny charakter, który miał zaznajomić Cię z całą ideą.Począwszy od następnego rozdziału zaczniemy już pisać obiektowy kod w PHP, jednak często będziemy wracać dotego, co zostało tutaj powiedziane. Jeszcze raz przypominamy: informacje, które tu poznasz, są niezbędne dorozumienia dalszej części podręcznika. Mogą wydawać się skomplikowane, ale kiedy już załapiesz, o co w tymwszystkim chodzi, Twoje życie naprawdę stanie się o wiele prostsze.

Klasy i obiekty

Klasy i obiektyW tym rozdziale nauczymy się, jak tworzyć klasy i obiekty w PHP.

Uwaga!Począwszy od tego miejsca będziemy stosować angielskie nazewnictwo w programowaniu obiektowym, gdyż jest ono podstawą wieluuznanych i powszechnie stosowanych konwencji. Angielski jest podstawowym językiem informatyki i każdy programista powinien znaćgo w przynajmniej podstawowym stopniu.

Tworzenie klasW językach programowania klasy traktowane są zawsze jako rodzaj typów danych. W PHP deklarujemy je słowemkluczowym class, po którym podajemy jej unikatową nazwę. Zasady jej tworzenia są podobne, jak w przypadkunazw zmiennych, tj. nie mogą one zaczynać się od cyfry. Następnie w nawiasach klamrowych umieszczamyinformacje o dozwolonych polach oraz metodach, jakie klasa będzie posiadać:

<?php

class Person

{

public $name;

public $surname;

public function setFullName($name, $surname)

{

$this->name = $name;

$this->surname = $surname;

} // end setFullName();

public function getFullName()

{

return $this->name.' '.$this->surname;

} // end getFullName();

}

Zwyczajowo pola deklaruje się na początku klasy, natomiast później - metody, które okazują się być bardzo podobne do funkcji. Podobieństwo jest jak najbardziej uzasadnione. Właściwie funkcje także reprezentują pewne zachowanie, więc nic nie stoi na przeszkodzie, aby wykorzystać tę funkcjonalność do zdefiniowania zachowań obiektów. Zasady działania metod są bardzo podobne - pobierają one argumenty i mogą zwracać wartość. Jedyna istotna różnica to obecność specjalnego wskaźnika $this, który wskazuje zawsze na obiekt, na którym daną metodę wywołujemy.

Klasy i obiekty 142

Dzięki niemu możemy dostać się do wartości przechowywanych w polach obiektu oraz wywoływać inne metody.Służy do tego specjalny operator ->. Zauważ, że odwołując się do pól, pomijamy znak dolara. Nie przejmuj sięsłowem kluczowym public. Jego znaczenie poznamy za chwilę.Pola klasy zachowują się, jak zwykłe zmienne. Mogą być częścią wyrażeń, możemy do nich przypisywać wartości iwykonywać wszystkie inne operacje, które są prawidłowe dla zmiennych.Stwórzmy teraz kilka obiektów klasy Person reprezentujących różne osoby. W przeciwieństwie do większościjęzyków kompilowanych, a podobnie jak w Javie, obiekty nie są jednym z rodzajów wartości, ale specjalnym bytem.Wartość obiektowa to jedynie referencja do istniejącego już obiektu. Gdy wykonujemy przypisanie lubprzekazujemy obiekt jako argument funkcji/metody, kopiujemy jedynie referencję, a nie obiekt. Wbrew pozoromtakie zachowanie jest bardzo praktyczne. W PHP4, gdzie obiekt był jednocześnie wartością, korzystanie zprogramowania obiektowego było przez to bardzo problematyczne i prowadziło do wielu błędów.Obiekty tworzymy operatorem new, po którym podajemy nazwę klasy. Zwraca on referencję do obiektu, którąmożemy zapisać w zmiennej:

<?php

// Dolaczamy plik z nasza klasa

require('./Person.php');

$janusz = new Person;

$janusz->setFullName('Janusz', 'Kowalski');

$adam = new Person;

$adam->setFullName('Adam', 'Nowak');

echo 'Witaj, jestem '.$janusz->getFullName().'<br/>';

echo 'A ja jestem '.$adam->getFullName().'<br/>';

Możemy też odwołać się bezpośrednio do odpowiednich pól: $adam->name.Wywołanie metod jest niezwykle proste. Wystarczy wziąć obiekt i po operatorze -> wywołać metodę dokładnie wtaki sam sposób, jak to robiliśmy z funkcjami. Metody zawsze działają w imieniu tego obiektu, na którym zostaływywołane, a to prowadzi nas do jednej z kluczowych reguł projektowania obiektowego:

Metody definiują zachowanie obiektów, dlatego powinny operować przede wszystkim na obiekcie, który je wywołał.

Do klasy Person nie będziemy dodawać metody w stylu polaczSieZBazaDanych(), ponieważ nie ma onażadnego związku z osobami. Co niby miałoby oznaczać wywołanie $adam->polaczSieZBazaDanych()?Adam jest osobą, a komunikacja z bazami danych to zadanie dla zupełnie innej klasy specjalizującej się w tymkonkretnym zagadnieniu.

Zmienne obiektowe są referencjamiWspomnieliśmy, że w PHP5 obiekty nie są rodzajem wartości, lecz oddzielnym bytem, do którego skrypt posiadajedynie referencje. Spójrzmy, czym to skutkuje w praktyce. Rozpatrzmy prosty skrypt:

<?php

function modify($value)

{

$value += 5;

} // end modify();

Klasy i obiekty 143

$number = 6;

modify($number);

echo $number;

Skrypt ten wyświetli nam wartość "6". Zmienna, na której operuje funkcja modify() jest jedynie kopią zmiennejglobalnej $number, dlatego jej modyfikacja nie wpływa na wartość oryginału. Inaczej jest w przypadku obiektów:

<?php

require('./Person.php');

function modify($object)

{

$object->surname = 'Nowak';

} // end modify();

$janusz = new Person;

$janusz->setFullName('Janusz', 'Kowalski');

modify($janusz);

echo $janusz->getFullName();

Tym razem skrypt wyświetlił nam wartość Janusz Nowak, co oznacza, że zmiana stanu obiektu wprowadzonaprzez funkcję jest widoczna globalnie. Zachowanie jest jak najbardziej prawidłowe. Funkcja modify() operujenie na kopii, ale na referencji do obiektu. Obie referencje: globalna $janusz oraz lokalna $object sąoddzielne, ale wskazują na dokładnie ten sam obiekt. Dlatego wykonanie operacji poprzez jedną z nich sprawi, żedruga także zauważy zmiany.

Zapamiętaj, przekazanie zmiennej obiektowej przez argument lub przypisanie jej wartości operatorem = kopiuje jedynie referencję doobiektu, a nie obiekt.

Spróbuj w ramach ćwiczenia wykonać taką samą sztuczkę z operatorem przypisania.

Kontrola dostępu (hermetyzacja)W przeciwieństwie do funkcji i programowania strukturalnego, klasy posiadają precyzyjne mechanizmy kontrolidostępu do swojego wnętrza. Proces ukrywania części funkcjonalności przed programistą nosi nazwę hermetyzacji ipomaga zwiększyć niezawodność oprogramowania. Tworząc klasę, będziemy zawsze starali się określić tzw.publiczny interfejs, z którego może korzystać programista, odwołując się do tworzonych obiektów, jednocześnieukrywając wszystkie wewnętrzne aspekty działania klasy. Interpreter będzie pilnować, aby użytkownicy niewywołali żadnej "wewnętrznej" metody, co mogłoby doprowadzić do błędów w działaniu lub użycia jej niezgodnie zprzeznaczeniem.Poznaliśmy już jeden z modyfikatorów dostępu, public, który podaliśmy przed każdą metodą oraz polem. Jest onwyjątkowy, ponieważ w przypadku metod można go pominąć, zostawiając samo function nazwa(), a wprzypadku pól zastąpić synonimem var. My jednak będziemy stosować konwencję, w której zawsze jawnieokreślamy widzialność elementu.Pozostałe modyfikatory dostępu to protected oraz private. Pierwszy z nich dotyczy dziedziczenia, dlategozajmiemy się nim później, a tymczasem przyjrzymy się drugiemu z nich. Mówi on, że dane pole lub metoda jestprywatnym elementem klasy. W praktyce oznacza to, że odwołać się do niego możemy wyłącznie z poziomu innejmetody w naszej klasie, a z zewnątrz jest on niedostępny. W naszych przykładach będziemy stosować konwencję, wktórej prywatne elementy będą posiadać nazwy zaczynające się od podkreślenia.

Klasy i obiekty 144

<?php

class SomeClass

{

private $_field;

public function getField()

{

// Poprawne odwołanie

return $this->_field;

} // end getField();

} // end SomeClass;

$someObject = new SomeClass;

// dostęp jest możliwy poprzez metodę

echo $someObject->getField();

// błąd, próba dostępu do pola prywatnego!

echo $someObject->_field;

Powyższy skrypt się nie wykona. W ostatniej linijce PHP zgłosi nam błąd, jakim jest próba dostania się doprywatnego elementu klasy spoza obiektu. Jednocześnie działa odwołanie umieszczone wewnątrz metodygetField(). Taki rodzaj metody zwie się po angielsku getter, a oprócz niego mamy też setter, który pozwalaustawić wartość określonemu polu. Jest to jedna z konwencji stosowanych w hermetyzacji, w myśl której klasa niepowinna, poza szczególnymi wyjątkami, zawierać publicznych pól. Jeżeli użytkownik ma mieć dostęp do jakiegośpola, musi to robić za pośrednictwem dodatkowych metod, czyli getterów i setterów. Zauważmy, że takie podejściepozwala nam tworzyć pola dostępne publicznie tylko do odczytu bez możliwości ich modyfikacji. Wystarczyzadeklarować je jako prywatne i stworzyć publiczny getter, bez settera. Przepiszmy zatem naszą klasę Personzgodnie z regułami hermetyzacji, ograniczając nieco dostęp:

<?php

class Person

{

private $_name = null;

private $_surname = null;

public function setName($name)

{

if($this->_name === null)

{

$this->_name = $name;

return true;

}

return false;

} // end setName();

public function setSurname($surname)

Klasy i obiekty 145

{

if($this->_surname === null)

{

$this->_surname = $surname;

return true;

}

return false;

} // end setSurname();

public function getName()

{

return $this->_name;

} // end getName();

public function getSurname()

{

return $this->_surname;

} // end getSurname();

public function getFullName()

{

return $this->_name.' '.$this->_surname;

} // end getFullName();

} // end Person;

Teraz pola $_name oraz $_surname są prywatne, a ich wartość można odczytać wyłącznie poprzez getterygetName() oraz getSurname(). Dostępne są także analogiczne settery setName() oraz setSurname(),jednak zauważmy, że w praktyce można je wywołać tylko raz. Oznacza to, że gdy raz ustawimy danemu obiektowinazwisko, nie jesteśmy w stanie go już później zmienić, gdyż metoda będzie zawsze zwracała wartość false,odmawiając modyfikacji. Jest to typowy przykład kontroli dostępu i ma on szczególne znaczenie w dużychaplikacjach liczących sobie setki klas, gdzie umożliwia to wymuszenie stosowania się do określonych konwencji ipomaga zapobiegać bałaganowi w kodzie.

Praktyczne zastosowanieSpróbujmy teraz napisać kod, który będzie przydatny w aplikacji WWW. Stworzymy prosty, obiektowy irozszerzalny mechanizm konfiguracji, na przykładzie którego pokażemy kilka technik projektowania obiektowego.Będzie on składać się z dwóch klas:1. Config - zarządza konfiguracją i udostępnia ją skryptowi.2. ConfigLoader - rodzaj ładowarki, czyli klasy potrafiącej załadować skądś konfigurację.Zacznijmy od napisania klasy głównej. Programista będzie mógł dodawać do niej różne ładowarki, a ona będzieudostępniała wczytaną konfigurację pozostałej części skryptu. Opcje konfiguracyjne będą wczytywane leniwie, tj.gdy zajdzie taka potrzeba. Domyślnie ładowarka będzie jedynie kolejkowana w tablicy $_awaitingLoaders.Dopiero przy próbie odczytu nieistniejącej opcji, skrypt będzie prosić kolejne ładowarki o wczytanie swojej częścikonfiguracji, dopóki nie trafi na taką, która wczyta to, czego potrzebujemy.

<?php

class Config

Klasy i obiekty 146

{

private $_config = array();

private $_awaitingLoaders = array();

public function get($option)

{

// Jeśli podana opcja istnieje, zwróć jej wartość

if(isset($this->_config[$option]))

{

return $this->_config[$option];

}

// Opcja nie istnieje, sprawdzamy, czy któraś z oczekujących

ładowarek ją ma.

foreach($this->_awaitingLoaders as $id => $loader)

{

$this->_config = array_merge($this->_config, $loader->load());

unset($this->_awaitingLoaders[$id]);

if(isset($this->_config[$option]))

{

return $this->_config[$option];

}

}

return null;

} // end get();

public function addLoader(ConfigLoader $loader)

{

$this->_awaitingLoaders[] = $loader;

} // end addLoader();

} // end Config;

Pojawił się tu nowy element składni: public function addLoader(ConfigLoader $loader) - przednazwą zmiennej pojawiła się nazwa obiektu. Jest to jedno z kolejnych udoskonaleń programowania obiektowego wPHP, czyli określanie typów argumentów. Działa ono zarówno w metodach, jak i funkcjach i mówi, że danyargument może przyjąć wyłącznie obiekt klasy ConfigLoader. Próba podania dowolnego innego rodzaju wartościzakończy się błędem. W PHP określanie typów argumentów ograniczone jest wyłącznie do klasy, a od PHP 5.1również do tablic (typ Array). Nie można wymuszać typów skalarnych (np. liczby całkowite) ani zasobów. Wnaszym przypadku daje to nam pewność, że programista nie będzie próbował nakarmić naszego systemukonfiguracji jakimiś dziwnymi danymi, które mogłyby doprowadzić do błędu.Nasz system konfiguracji stosuje leniwe ładowanie opcji, lecz zwróćmy uwagę, że jego użytkownik wcale nie musio tym wiedzieć. Dla niego korzystanie z tej klasy ogranicza się do pamiętania, że musi zdefiniować przynajmniejjedną ładowarkę oraz że dostęp do opcji możliwy jest do odczytu poprzez metodę get(). Sposób wczytywaniaopcji nie jest już przedmiotem jego zainteresowań, dlatego wewnętrzne aspekty działania klasy oraz faktycznysposób reprezentacji danych został przed nim ukryty, by nie mógł przy nim majstrować.Napiszmy teraz ładowarkę, która będzie wczytywać opcje z pliku INI:

Klasy i obiekty 147

<?php

class ConfigLoader

{

private $_fileName = '';

public function setFilename($filename)

{

$this->_fileName = $filename;

} // end setFilename();

public function load()

{

if(file_exists($this->_fileName))

{

return parse_ini_file($this->_fileName);

}

// jeśli pliku nie ma, zwróć pustą tablicę

return array();

} // end load();

} // end ConfigLoader;

Tu również zastosowaliśmy hermetyzację. Można, a nawet trzeba ustawić nazwę pliku, który chcemy odczytać,jednak nie musi być ona później dostępna, dlatego pominęliśmy całkowicie getter. Metoda load() wywoływanaprzez naszą klasę Config musi zwrócić tablicę z opcjami wczytanymi z pliku.To wszystko, spójrzmy teraz, jak wykorzystać nasz system w praktyce:

<?php

require('./Config.php');

require('./ConfigLoader.php');

$config = new Config;

// utworz ladowarki wczytujace rozne fragmenty konfiguracji

$basicConfig = new ConfigLoader;

$basicConfig->setFilename('./config/basic.ini.php');

$securityConfig = new ConfigLoader;

$securityConfig->setFilename('./config/security.ini.php');

$layoutConfig = new ConfigLoader;

$layoutConfig->setFilename('./config/layout.ini.php');

$config->addLoader($basicConfig);

$config->addLoader($securityConfig);

$config->addLoader($layoutConfig);

Klasy i obiekty 148

// zaladujmy pare opcji

echo $config->get('website_name');

echo $config->get('session_time');

Naszemu systemowi brakuje wciąż parę rzeczy. Przykładowo, można by pokusić się o zrobienie różnych rodzajówładowarek: z plików, z bazy danych itd., lecz do tego potrzebna jest nam znajomość dziedziczenia. Ponadtoprzydałoby się, aby system potrafił odpowiednio raportować błędy. Póki co, jeśli pomylimy się w nazwie plikukonfiguracji, dowiemy się o tym dopiero po wnikliwym śledztwie, ponieważ jedynym śladem w ładowarce jestzwrócenie pustej tablicy. Odpowiednie mechanizmy poznamy w dalszej części podręcznika.Stworzony system konfiguracji wykorzystuje jeszcze jedną technikę programistyczną o nazwie kompozycja. Jest toalternatywny do dziedziczenia sposób rozszerzania funkcjonalności obiektów, który jednak nie wymaga żadnejdodatkowej składni. Obrazowo mówiąc, polega on na tym, że jeden obiekt przechowuje referencje do innychobiektów, które potrafi wykorzystywać lub których funkcjonalność potrafi udostępnić na zewnątrz. Wprzeciwieństwie do dziedziczenia, kompozycja ma charakter dynamiczny. Możemy napisać algorytm, który w locieskomponuje nam gotowy obiekt, niczym z klocków lego, np. na podstawie konfiguracji lub opisu w bazie danych.Kompozycja jest bardzo często stosowana w praktyce obok dziedziczenia.

ZakończenieMamy już solidne podstawy programowania obiektowego, a także pokazaliśmy, w jaki sposób wykorzystuje się jegowłasności podczas tworzenia oskryptowania stron internetowych, pisząc modularny i łatwy w rozbudowie systemkonfiguracji. W następnym rozdziale pokażemy, jak sterować tworzeniem i niszczeniem obiektów.

Konstruktory i destruktory

Konstruktory i destruktoryMetody klas nie muszą być wywoływane wyłącznie przez programistę tworzącego dany skrypt. Istnieje pewna grupametod, które są wywoływane automatycznie przez interpreter w momencie zajścia jakiegoś zdarzenia - metody takienazywamy 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 wmomencie tworzenia oraz niszczenia obiektu.

KonstruktorKonstruktor jest metodą o nazwie __construct(), która może pobierać parametry, lecz nie wolno jej zwracaćwartości. Jej zadaniem jest wykonanie pewnych akcji tuż po utworzeniu obiektu tak, aby można było od razu zacząćz nim pracę. Spójrzmy na nasz przykład z osobami, który analizowaliśmy ostatnio. Tuż po utworzeniu pola$_name oraz $_surname miały wartość pustą i należało ręcznie przypisać im wartość, a do tego czasu obiektPerson znajdował się w stanie, który możemy uznać za błędny. Może się zdarzyć, że wskutek pomyłki ktoś zapomnizainicjować odpowiednio obiekt po utworzeniu i przekaże go do dalszego przetwarzania. Gdy błąd się ujawni,moglibyśmy stracić dużo czasu na znalezienie błędu, a nawet potencjalnie zagrozić bezpieczeństwu aplikacji. Dziękikonstruktorom mamy pewność, że nasz obiekt zawsze będzie poprawnie inicjowany. W naszym przypadku chcemy,aby tworzona osoba od razu posiadała imię i nazwisko.

<?php

class Person

Konstruktory i destruktory 149

{

private $_name = null;

private $_surname = null;

public function __construct($name, $surname)

{

$this->_name = $name;

$this->_surname = $surname;

} // end __construct();

public function getName()

{

return $this->_name;

} // end getName();

public function getSurname()

{

return $this->_surname;

} // end getSurname();

public function getFullName()

{

return $this->_name.' '.$this->_surname;

} // end getFullName();

} // end Person;

Settery są już nam niepotrzebne. Jak pamiętamy, mogły być one wywołane tylko raz, a skoro teraz imię i nazwiskojest przypisywane przez konstruktor, nie trzeba dodatkowych metod, które i tak nie zadziałają.Popatrzmy teraz, jak przekazywać argumenty do konstruktora. Robimy to tuż po nazwie klasy przy operatorze new.Gdy klasa nie posiada konstruktora lub konstruktor nie pobiera argumentów, nawiasy wyjątkowo można w tymwypadku pominąć tak, jak to dotąd robiliśmy. Jednak po dokonanych zmianach musimy już napisać:

$janusz = new Person('Janusz', 'Kowalski');

Uwaga!W PHP4 metoda konstruktora musiała nazywać się identycznie, jak klasa, tj. jeśli mieliśmy klasę Person, jej konstruktorem była metoda onazwie Person. Dla zachowania kompatybilności, PHP5 wciąż akceptuje takie konstruktory, lecz dopiero w drugiej kolejności. Wsparciedla nich zostanie usunięte w PHP6.

Ćwiczenie: W poprzednim rozdziale podaliśmy zbudowany na OOP system konfiguracji. Jedną z klas wchodzącychdo zestawu była ConfigLoader. Dodaj do niej konstruktor, który pobiera nazwę z plikiem konfiguracyjnym tak, abymożna go było podać już w momencie tworzenia obiektu. Przerób przykładowy kod tak, aby wykorzystywałmożliwości konstruktora.

Konstruktory i destruktory 150

DestruktorDotąd nie zajmowaliśmy się zagadnieniem niszczenia obiektów. Jest ono dość specyficzne, ponieważ PHP nie tylkonie narzuca obowiązku niszczenia niepotrzebnych obiektów, ale wręcz programiści często to ignorują. Większośćskryptów PHP działa na zasadzie "uruchom się, wygeneruj odpowiedź, zakończ pracę", więc nie ma sensuniepotrzebnie komplikować skrypt i bawić się w zarządzanie cyklem życia obiektów, kiedy i tak wszystkie przestanąistnieć podczas kończenia pracy.Podobnie jak w większości dynamicznych języków, obiekt przestaje istnieć w momencie usunięcia wszystkichprowadzących do niego referencji. W PHP mamy możliwość zaprogramowania operacji, która ma się wtedywykonać, dzięki destruktorom. Przykładowo, gdy nasz obiekt reprezentuje otwarty plik, w destruktorze możemy goautomatycznie zamknąć. Destruktor jest metodą o nazwie __destruct(), która nie może ani pobierać żadnychargumentów, ani też zwracać wartości. Poniższy przykład ilustruje działanie destruktorów:

<?php

class Destructable

{

public function __construct()

{

echo 'Obiekt klasy Destructable został stworzony.<br/>';

} // end __construct();

public function __destruct()

{

echo 'Obiekt klasy Destructable został zniszczony.<br/>';

} // end __destruct();

} // end Destructable;

$firstObject = new Destructable;

$secondObject = new Destructable;

unset($firstObject);

echo 'Kończymy pracę...<br/>';

Wynikiem jego działania jest:

Obiekt klasy Destructable został stworzony.

Obiekt klasy Destructable został stworzony.

Obiekt klasy Destructable został zniszczony.

Kończymy pracę...

Obiekt klasy Destructable został zniszczony.

Pierwsza z informacji o zniszczeniu obiektu powstała w wyniku jawnego wywołania unset($firstObject),które zniszczyło jedyną istniejącą w skrypcie referencję do niego. Drugi napis wygenerowała sekwencja kończeniapracy skryptu, podczas której kolejno niszczone są wszystkie obiekty.Destruktory są wyjątkowymi metodami z powodu kilku dodatkowych ograniczeń, które ich dotyczą. Jeżeli wykonują się w momencie kończenia pracy skryptu, nie możemy z ich poziomu wysłać nagłówków HTTP, ponieważ te zostały już wysłane do przeglądarki. Ponadto niektóre serwery (np. Apache) zmieniają wtedy katalog roboczy, przez co wszystkie dotychczasowe ścieżki względne przestają wtedy działać. Rozwiązaniem jest wcześniejsze pozyskanie ścieżek bezwzględnych funkcją realpath() (uwaga: jest to operacja dyskowa i nie nadużywaj jej) i

Konstruktory i destruktory 151

zapamiętanie ich do czasu zniszczenia obiektu.

PoradaStaraj się, aby kod Twoich destruktorów był krótki i prosty. W szczególności unikaj wykonywania w nich skomplikowanych operacji.Niebawem poznamy mechanizm raportowania błędów, wyjątki i wnętrze destruktorów jest jedynym miejscem, w którym występująograniczenia w jego stosowaniu.

Więcej o niszczeniu obiektówPodczas automatycznego niszczenia obiektów na podstawie licznika referencji pojawia się poważny problem.Przypuśćmy, że mamy obiekt A, który w jednym z pól przechowuje referencję do obiektu B. Jednocześnie B wswoim polu posiada referencję do obiektu A. W ogólnodostępnych zmiennych posiadamy jedną referencję do A,którą kasujemy, przez co oba obiekty stają się nieosiągalne dla naszego skryptu, lecz mimo to nie można ich usunąć,ponieważ liczniki wciąż wskazują, że istnieje do nich po jednej referencji. Problem ten nosi nazwę wykrywaniacyklicznych referencji. Odśmiecacze pamięci większości języków (np. Java) potrafią poprawnie rozpoznawać takiesytuacje i mimo wszystko usunąć niedostępne obiekty, lecz PHP aż do wersji 5.3.0 pozbawiony był takiejmożliwości. Większość skryptów wykonuje się krótko, dlatego zazwyczaj nikomu to nie przeszkadzało, jednak przyskomplikowanych, obiektowych strukturach danych, które w połowie działania trzeba było usuwać, aby zwolnićtrochę pamięci dla reszty skryptu, programista musiał się nieźle nagimnastykować.Sprawdźmy działanie poniższego skryptu:

<?php

class CircularReference

{

private $_secondary;

private $_name;

public function __construct($name)

{

$this->_name = $name;

} // end __construct();

public function setSecondary(CircularReference $object)

{

$object->_secondary = $this;

$this->_secondary = $object;

} // end setSecondary();

public function __destruct()

{

echo 'Obiekt '.$this->_name.' znika.<br/>';

} // end __destruct();

} // end CircularReference;

// Tworzymy pierwszy obiekt i zapamietujemy referencje w $a

$a = new CircularReference('A');

// Dodajemy drugi obiekt, lecz nie zostawiamy sobie referencji

Konstruktory i destruktory 152

// Powstaje nam cykl: A ma dostep do B, B ma dostep do A.

$a->setSecondary(new CircularReference('B'));

// Usun jedyna posiadana referencje

unset($a);

echo 'Koniec pracy skryptu.<br/>';

Gdy uruchomimy ten skrypt, jego wynikiem działania powinno być:

Koniec pracy skryptu.

Obiekt A znika.

Obiekt B znika.

Czyli mimo, iż nie mamy do obiektu dostępu, nie jest on niszczony natychmiast. W PHP 5.3 automatycznewykrywanie cykli może być włączone w pliku php.ini lub poprzez wywołanie funkcji gc_enable(), lecznajprawdopodobniej także i wtedy nasze wyjście będzie wyglądało tak, jak powyżej. Odśmiecacz po prostu czeka, ażuzbiera się wystarczająca liczba referencji i dopiero wtedy przegląda pamięć w poszukiwaniu obiektów. Możemy towymusić, przerabiając lekko nasz skrypt. Aby PHP nie zawalił nas komunikatami o niszczonych obiektach, dodajmyflagę sygnalizującą, czy obiekt ma nas informować o swoim zniszczeniu, a następnie utwórzmy dodatkową pętlę,która będzie w kółko tworzyć obiekty z cyklami:

<?php

// Uwaga: tylko PHP 5.3.

class CircularReference

{

private $_secondary;

private $_name;

private $_noMsg;

public function __construct($name, $noMsg = false)

{

$this->_name = $name;

$this->_noMsg = $noMsg;

} // end __construct();

public function setSecondary(CircularReference $object)

{

$object->_secondary = $this;

$this->_secondary = $object;

} // end setSecondary();

public function __destruct()

{

if(!$this->_noMsg)

{

echo 'Obiekt '.$this->_name.' znika.<br/>';

}

Konstruktory i destruktory 153

} // end __destruct();

} // end CircularReference;

gc_enable();

// Tworzymy pierwszy obiekt i zapamietujemy referencje w $a

$a = new CircularReference('A');

// Dodajemy drugi obiekt, lecz nie zostawiamy sobie referencji

// Powstaje nam cykl: A ma dostep do B, B ma dostep do A.

$a->setSecondary(new CircularReference('B'));

// Usun jedyna posiadana referencje

unset($a);

// Tworz duzo obiektow z cyklami

for($i = 0; $i < 10000; $i++)

{

$a = new CircularReference('A', true);

$a->setSecondary(new CircularReference('B', true));

}

echo 'Koniec pracy skryptu.<br/>';

Tym razem odśmiecacz pamięci zareagował, co poznajemy po zmienionym wyniku:

Obiekt A znika.

Obiekt B znika.

Koniec pracy skryptu.

Spróbuj zmniejszyć ilość iteracji do 1000. Czy wtedy też włącza się odśmiecacz?Oczywiście czasami nie chcemy czekać, aż PHP zorientuje się, że powinien wyczyścić pamięć. Odnalezienie cyklimożemy wymusić, wywołując funkcję gc_collect_cycles(). Przerób powyższy skrypt, usuwając pętlę izastępując ją wywołaniem tej funkcji. Zauważysz, że nasze obiekty ponownie zostały prawidłowo usunięte przedkońcem pracy.

Uwaga!Obecnie (styczeń 2010) odśmiecacz pamięci PHP najprawdopodobniej zawiera błąd powodujący wyciek pamięci, której nie da się wżaden sposób zwolnić, dlatego odradzane jest częste włączanie go. Błąd występuje w PHP 5.3.0 oraz 5.3.1.

ZakończenieUmiemy już zarządzać tworzeniem oraz niszczeniem obiektu, a także wiemy, jak wykorzystać konstruktory dowymuszenia poprawnej inicjacji tworzonego obiektu. Ponadto poznaliśmy nieco zasady zarządzania pamięcią wPHP, które wprawdzie nie przydają się aż tak często, lecz na pewno warto je znać. W następnym rozdziale zajmiemysię dziedziczeniem.

Dziedziczenie 154

Dziedziczenie

Dziedziczenie klasW naszych rozważaniach z początku rozdziału mówiliśmy o dziedziczeniu, które wyrażało nam, że "X jest Y-kiem".Dziedziczenie pozwala nam rozszerzyć już istniejącą klasę poprzez przejęcie jej pól oraz metod i dołożenie nowych.Jednak współdzielenie kodu to nie wszystko. Obiekty klasy dziedziczącej są jednocześnie obiektami wszystkich klasdziedziczonych, co oznacza, że możemy ich użyć tam, gdzie program spodziewa się dostać ogólniejszy obiekt.

Implementacja dziedziczeniaPrzypuśćmy, że chcemy zbudować formularz WWW. W jego skład wchodzą różne elementy, lecz podstawowazasada ich działania jest identyczna. Każdemu możemy ustawić nazwę i przypisać wartość. Skorzystamy zdziedziczenia, aby utworzyć klasę bazową Element zawierającą ogólny interfejs, a następnie stworzymy jejspecjalizacje reprezentujące pole tekstowe, listę rozwijaną itd.

<?php

class FormElement

{

private $_name;

private $_value;

public function setName($name)

{

$this->_name = $name;

} // end setName();

public function getName()

{

return $this->_name;

} // end getName();

public function setValue($value)

{

$this->_value = $value;

} // end setValue();

public function getValue()

{

return $this->_value;

} // end getValue();

} // end FormElement;

class FormInput extends FormElement

{

public function display()

{

Dziedziczenie 155

echo '<input type="text" name="'.$this->getName().'" value="'.htmlspecialchars($this->getValue()).'"

/>';

} // end display();

} // end FormInput;

class FormTextarea extends FormElement

{

private $_width = 60;

private $_height = 15;

public function setDimensions($width, $height)

{

if($width < 1 || $height < 1)

{

return false;

}

$this->_width = $width;

$this->_height = $height;

return true;

} // end setDimensions();

public function display()

{

echo '<textarea name="'.$this->getName().'" rows="'.$this->_height.'"

cols="'.$this->_width.'">'.htmlspecialchars($this->getValue()).'</textarea>';

} // end display();

} // end FormTextarea;

Aby wykonać dziedziczenie, po nazwie klasy dopisujemy słowo kluczowe extends i podajemy nazwę klasy, którąchcemy rozszerzyć. Dzięki temu zarówno FormInput, jak i FormTextarea mogą korzystać z metod getName()oraz getValue() mimo, iż nigdzie ich nie deklarowaliśmy. Ponadto dołożyły one własne metody umożliwiającewyświetlanie danego pola w określony sposób, a FormTextarea dołożył dodatkowo możliwość ustawianiarozmiarów pola tekstowego. Poniżej pokazany jest przykład wykorzystania naszych klas:

<?php

require('./FormElements.php');

$fields = array();

$field = new FormInput;

$field->setName('nickname');

$field->setValue('Wpisz tu swoją nazwę');

$fields[] = $field;

$field = new FormTextarea;

$field->setName('comment');

Dziedziczenie 156

$field->setDimensions(60, 5);

$fields[] = $field;

// Wyswietl formularz

foreach($fields as $field)

{

$field->display();

}

Kontrola dostępu w dziedziczeniuDo tej pory poznaliśmy dwa modyfikatory dostępu: public oraz private. W naszym poprzednim przykładziezauważyliśmy, że w metodzie display() odwoływaliśmy się do nazwy elementu poprzez getName(), zamiastodczytywać ją bezpośrednio z pola $_name. Wynika to z tego, że klasa pochodna (tj. FormInput, FormTextareaitd.) nie ma dostępu do prywatnych pól i metod klasy bazowej. Modyfikator private ogranicza nam dostęp doobiektów aktualnej i tylko aktualnej klasy, bez żadnych wyjątków. My tymczasem potrzebujemy czegoś pośredniego- zablokować dostęp z zewnątrz, a jednocześnie umożliwić go klasom potomnym. Dlatego wykorzystamy trzecimodyfikator, protected. Dzięki niemu możemy rozbudować naszą klasę FormElement o kilka przydatnych rzeczy.Chronione elementy klasy są często wykorzystywane do tworzenia metod pomocniczych, które może wykorzystaćprogramista rozszerzający klasę. U nas przydałaby się jakaś szybka możliwość tworzenia listy atrybutów dlaznaczników formularzy. Wpisywanie ich na sztywno jest kiepskim pomysłem, ponieważ nie zawsze wszystkieatrybuty są potrzebne. Dlatego dodamy metodę, która przyjmie listę atrybutów jako tablicę i wyświetli tylko teniepuste, a następnie wykorzystamy ją w klasie potomnej.

<?php

class FormElement

{

protected $_name = null;

protected $_value = null;

public function setName($name)

{

$this->_name = $name;

} // end setName();

public function getName()

{

return $this->_name;

} // end getName();

public function setValue($value)

{

$this->_value = $value;

} // end setValue();

public function getValue()

Dziedziczenie 157

{

return $this->_value;

} // end getName();

protected function _buildAttributes(array $attributes)

{

$code = '';

foreach($attributes as $name => $value)

{

if($value !== null)

{

$code .= ' '.$name.'="'.htmlspecialchars($value).'"';

}

}

return $code;

} // end _buildAttributes();

} // end FormElement;

class FormInput extends FormElement

{

public function display()

{

echo '<input'.$this->_buildAttributes(array(

'type' => 'text',

'name' => $this->getName(),

'value' => $this->getValue()

)).' />';

} // end display();

} // end FormInput;

W ramach ćwiczenia analogicznie zmodyfikuj klasę FormTextarea.Stwórz obiekt klasy FormInput i ustaw mu nazwę, ale nie ustawiaj wartości. Wyświetl go i sprawdź źródło.Możemy zauważyć, że metoda pomocnicza _buildAttributes() pominęła nam atrybut value, gdyż jegowartość była pusta i nie było sensu go wyświetlać. Ponadto tym razem możemy już z poziomu naszych klaspotomnych odwoływać się bezpośrednio do $_name oraz $_value, ponieważ zadeklarowaliśmy je jako polachronione.

Uwaga!Oznaczenie pola jako private wcale nie gwarantuje nam, że nie da się go z zewnątrz odczytać, ani zmodyfikować. Dzięki mechanizmomrefleksji, począwszy od PHP 5.3 możliwy jest pełen dostęp do wszystkich właściwości każdego obiektu, także tych prywatnych ichronionych. Dlatego modyfikatory dostępu należy traktować jedynie jako użyteczną pomoc w wykrywaniu pomyłek.

Dziedziczenie 158

Unieważnianie metodPHP pozwala na tworzenie w klasach potomnych pól oraz metod, które już istnieją w klasie bazowej. Działanie tonazywa się unieważnianiem, ponieważ nowa wersja metody zastępuje (unieważnia) dotychczasową i nie wymagażadnej dodatkowej składni. Po prostu tworzymy nową metodę o identycznej nazwie i argumentach, jak istniejąca.

<?php

class FormCheckboxList extends FormElement

{

private $_options = array();

protected $_value = array();

public function setOptions(array $options)

{

$this->_options = $options;

foreach($options as $name => $description)

{

$this->_value[$name] = false;

}

} // end setOptions();

public function setValue($value)

{

if(!is_array($value))

{

return false;

}

foreach($value as $name => $checked)

{

if(isset($this->_options[$name]))

{

$this->_value[$name] = (boolean)$checked;

}

}

return true;

} // end setValue();

public function display()

{

echo '<ul>';

foreach($this->_options as $name => $description)

{

echo '<li><input'.$this->_buildAttributes(array(

'type' => 'checkbox',

'name' => $this->_name.'['.$name.']',

'checked' => ($this->_value[$name] ? 'checked' : null)

)).' /> '.$description.'</li>';

}

Dziedziczenie 159

echo '</ul>';

} // end display();

} // end FormCheckboxList;

Nasz nowy rodzaj elementów formularzy, FormCheckboxList, wyświetla listę checkbox-ów. Wartość elementu niejest już pojedynczą liczbą czy tekstem, ale zbiorem wartości, który musi być odpowiednio przetworzony. Dlategostworzyliśmy nową wersję metody setValue(), która upewnia się, że dostała tablicę, a później odpowiedniopakuje jej zawartość do wewnętrznych struktur obiektu. Zmieniliśmy także deklarację pola $_value tak, abypoczątkowo zawsze zawierało tablicę.Przejrzystość wymaga, aby klasa FormElement dostała pustą metodę display(). Chcemy dzięki temupowiedzieć, że elementy formularzy mogą się wyświetlać, lecz nie precyzujemy jak. Dokładne zasady wyświetlaniaustala sobie każdy rodzaj elementu z osobna. Utwórz pustą, publiczną metodę display() w klasieFormElement.Przyjrzymy się teraz, jak podczas unieważniania zachowują się modyfikatory dostępu oraz argumenty.

Nowa wersja metody powinna przyjmować takie same argumenty z takimi samymi typami. Można zmieniać wartości domyślneopcjonalnych argumentów oraz dodawać nowe opcjonalne argumenty.

Stosowanie się do tej zasady zapewni nam maksymalną zgodność ze standardami PHP. Poniższy przykład ilustrujepoprawne konstrukcje:

<?php

class Foo

{

public function metoda1($foo)

{

echo $foo;

} // end metoda1();

public function metoda2($foo, $bar = '')

{

echo $foo;

} // end metoda2();

public function metoda3($foo)

{

echo $foo;

} // end metoda3();

} // end Foo;

class Bar extends Foo

{

protected $_bar = array();

public function metoda1($foo)

{

echo 'Nowe wywołanie';

} // end metoda1();

Dziedziczenie 160

public function metoda2($foo, $bar = 'Nowa wartość domyślna')

{

echo 'Nowe wywołanie';

} // end metoda2();

public function metoda3($foo, $bar = 'Nowy argument opcjonalny')

{

echo 'Nowe wywołanie';

} // end metoda3();

} // end Bar;

Zobaczmy teraz, co się stanie, gdy złamiemy jedną z tych reguł. Sprawmy, by nowy argument w metodziemetoda3() był wymagany (tj. usuwamy domyślną wartość):

public function metoda3($foo, $bar)

Jeżeli nie grzebaliśmy nic przy ustawieniach raportowania błędów, które zostały zaproponowane na początkupodręcznika, dostaniemy niemiły komunikat:Strict Standards: Declaration of Bar::metoda3() should be compatible with that of Foo::metoda3() in /sciezka/do/pliku.php on line 35

Błędy Strict Standards to upomnienia, że naruszyliśmy jakąś regułę. Mimo iż skrypt pozornie działa dalej,nigdy nie należy ich ignorować, ponieważ ich łamanie może spowodować, że nasz skrypt przestanie działać naprzyszłych wersjach PHP lub że jego wykonywanie może prowadzić do błędów w innym miejscu. Zauważmy:obiekty Bar są jednocześnie obiektami klasy Foo. Jeżeli w pewnym miejscu skrypt oczekuje obiektów Foo, maprawo zakładać, że metoda3() posiada jeden wymagany argument i tak też ją wywoła. Tymczasem my beztroskowprowadzamy mu obiekt klasy Bar, który teoretycznie powinien tu zadziałać, lecz powoduje nam niespodziewanybłąd, gdyż tutaj metoda3() ma już dwa wymagane argumenty. To jest cena naszej ignorancji.Modyfikatory dostępu mogą być zmieniane w ograniczonym zakresie. Możemy osłabiać dostęp (np. zastąpić metodęprywatną chronioną oraz chronioną przez publiczną), ale nie możemy czynić go bardziej restrykcyjnym. Jeśli wklasie bazowej metoda zadeklarowana jest jako chroniona, próba zastąpienia jej metodą prywatną skończy sięnatychmiastowym błędem krytycznym.Unieważniając metody, mamy dostęp do jeszcze jednej przydatnej możliwości. Przypuśćmy, że chcemy jedynierozszerzyć istniejącą funkcjonalność, a nie całkowicie ją zastępować. Na szczęście PHP pozwala na wywołaniepierwotnej wersji metody:

public function metoda($argument)

{

echo 'Trochę dodatkowych operacji';

parent::metoda($argument);

} // end metoda();

Dziedziczenie 161

Konstruktory i destruktory w dziedziczeniuKonstruktory oraz destruktory zachowują się prawie identycznie, jak zwykłe metody. Gdy klasa potomna nie będziezawierać konstruktora albo destruktora, PHP po prostu odziedziczy go po klasie bazowej. Jednak jeśli zdecydujemysię go unieważnić, nie wykona się on, dopóki tego nie powiemy explicite poprzez znaną już nam konstrukcjęparent::metoda(). Jedyna różnica polega na tym, że nie ma ograniczeń co do zmiany ilości argumentów wkonstruktorach klas potomnych. Jest tak, ponieważ konstruktor jest wywoływany automatycznie tylko podczastworzenia obiektu konkretnej klasy, do której należy.Dodajmy konstruktor do klasy FormElement tak, aby można było natychmiast ustawić nazwę:

<?php

class FormElement

{

protected $_name;

protected $_value;

public function __construct($name)

{

$this->_name = $name;

} // end __construct();

// dalsza część klasy

} // end FormElement;

To wystarczy, aby przy tworzeniu wszystkich elementów trzeba było określić ich nazwę. Klasy potomne odziedzicząten konstruktor i wykorzystają go u siebie. Jednak gdy zdecydujemy się wprowadzić nowy konstruktor, musimyjawnie powiedzieć, że chcemy wywołać także stary:

<?php

class FormCheckboxList extends FormElement

{

private $_options = array();

protected $_value = array();

public function __construct($name, array $options)

{

parent::__construct($name);

$this->setOptions($options);

} // end __construct();

// dalsza część klasy

} // end FormCheckboxList;

Jak widać w konstruktorze dodaliśmy nowy argument wymagany.Aby uczynić klasę bardziej elastyczną, możemy dodać wartosć domyślną do argumentu konstruktora:

<?php

Dziedziczenie 162

class FormElement

{

protected $_name;

protected $_value;

public function __construct($name = null)

{

$this->_name = $name;

} // end __construct();

// dalsza część klasy

} // end FormElement;

Dzięki temu określenie nazwy elementu bezpośrednio podczas tworzenia obiektu jest możliwe, ale nie jest jużwymagane. Analogicznie możesz zmodyfikować klasę FormCheckboxList

Klasy abstrakcyjneW naszej rzeczywistości nie wszystkie pojęcia muszą mieć praktyczne zastosowanie. Niektóre pozwalają po prostuwygodnie odnosić się do grupy wielu różnych rzeczy. W historyjce z początku rozdziału mówiliśmy o towarach:chlebie, napojach, warzywach itd., ale nigdzie nie pojawił się towar, który był tylko towarem i niczym więcej.Dlatego powiemy, że towar jest pojęciem abstrakcyjnym. Nie może występować samodzielnie, ale może pomagaćokreślać inne pojęcia. W świecie programowania obiektowego towar byłby tzw. klasą abstrakcyjną. Jest to specjalnyrodzaj klasy stworzony specjalnie po to, aby po nim dziedziczyć. Tworzenie obiektów klas abstrakcyjnych jestzabronione, za to można tworzyć obiekty ich klas potomnych.Klasę abstrakcyjną deklaruje się, dodając przed słowem class dodatkowy modyfikator abstract. Ponownieprzyjrzyjmy się naszemu systemowi wyświetlania formularzy. Takim abstrakcyjnym bytem jest tam FormElement.Sam w sobie nie nadaje się do niczego, ponieważ nie ma zdefiniowanego wyświetlania, za to stanowi podbudowędla innych klas. Przepiszmy tę klasę po raz kolejny:

<?php

abstract class FormElement

{

protected $_name = null;

protected $_value = null;

public function __construct($name)

{

$this->_name = $name;

} // end __construct();

public function setName($name)

{

$this->_name = $name;

} // end setName();

public function getName()

{

Dziedziczenie 163

return $this->_name;

} // end getName();

public function setValue($value)

{

$this->_value = $value;

} // end setName();

public function getValue()

{

return $this->_value;

} // end getName();

abstract public function display();

protected function _buildAttributes(array $attributes)

{

$code = '';

foreach($attributes as $name => $value)

{

if($value !== null)

{

$code .= ' '.$name.'="'.htmlspecialchars($value).'"';

}

}

return $code;

} // end _buildAttributes();

} // end FormElement;

Teraz próba napisania new FormElement('nazwa') zakończy się błędem wykonania skryptu.W podanym kodzie możemy zauważyć jeszcze jedną rzecz, a mianowicie metodę abstrakcyjną. Analogicznie jak wprzypadku klasy, jest to metoda, która jest przeznaczona do tego, aby ją unieważnić, dlatego też nie może zawieraćżadnej treści. Po nawiasach zamykających listę argumentów możemy postawić już jedynie średnik. Idealnie nadajesię to dla naszej metody display(). Chcemy powiedzieć, że elementy mogą być wyświetlane, ale szczegółypozostawiamy do zaimplementowania klasom potomnym.

Jeżeli klasa zawiera metody abstrakcyjne, musi być także zadeklarowana jako abstrakcyjna.

Definicja ta mówi nam jeszcze jedno. Przypuśćmy, że stworzyliśmy klasę dziedziczącą po FormElement, która niezaimplementowała metody display(). Dopóki jej nie dodamy, nowa klasa także musi być zadeklarowana jakoabstrakcyjna!Do koncepcji abstrakcji będziemy często wracać w następnych rozdziałach.

Dziedziczenie 164

Słowo "final"Słowo abstract wymusza na programiście konieczność dziedziczenia po klasie oraz unieważnienia metody. W PHPistnieje także jego odwrotność, czyli słowo final, która uniemożliwia takie działanie. Jedyna różnica polega na tym,że klasa posiadająca metody finalne, nie musi być deklarowana jako finalna.Metody finalne należy stosować tam, gdzie obecność jakiegoś algorytmu w takiej postaci, w jakiej go napisaliśmy,jest krytyczna dla działania całości, a próba jego unieważnienia mogłaby się dla skryptu skończyć tragicznie.

Obiekt potomka jest obiektem klasy bazowejPrzyjrzyjmy się teraz praktycznej obserwacji dotyczącej dziedziczenia. Do tej pory używaliśmy go wyłącznie jakowygodny sposób na współdzielenie kodu, jednak mechanizm ten sięga o wiele głębiej. Napiszmy klasę zarządcy dlanaszego formularza:

<?php

class FormBuilder

{

protected $_elements = array();

final public function addElement(FormElement $element)

{

$this->_elements[] = $element;

return $element;

} // end addElement();

public function display()

{

foreach($this->_elements as $element)

{

$element->display();

}

} // end display();

} // end FormBuilder;

Metoda addElement() została zadeklarowana jako finalna, ponieważ nie przewidujemy możliwości zmianysposobu dodawania nowych elementów do formularza. Programista może rozszerzyć tę klasę, by np. udoskonalićwyświetlanie, ale proces dodawania nowych elementów ma zostawić w spokoju. Zwróćmy także uwagę naargument, który musi być obiektem klasy FormElement. Spójrzmy teraz, jak używać nowej klasy:

<?php

require('./FormElements.php');

require('./FormBuilder.php');

$form = new FormBuilder;

$form->addElement(new FormInput('name'));

$form->addElement(new FormInput('email'));

$form->addElement(new FormTextarea('comment'))->setDimensions(70, 15);

Dziedziczenie 165

$form->display();

Ukazuje on praktyczne znaczenie dziedziczenia. Choć tworzymy obiekty klasy FormInput, możemy je przekazaćjako argument FormElement, ponieważ dziedziczenie mówi nam jasno: FormInput używa interfejsu zFormElement, posiada wszystkie jego zachowania oraz właściwości, dlatego obiekt FormInput jest jednocześnieobiektem FormElement. Przy okazji spójrzmy na następującą linijkę:

$form->addElement(new FormTextarea('comment'))->setDimensions(70, 15);

Jest to ilustracja, że referencja do obiektu wcale nie musi być przechowywana w zmiennej. Jeżeli jakaś funkcja lubmetoda zwraca obiekt, możemy tuż po niej napisać -> i dostać się do jego elementów bez potrzeby stosowaniazmiennej tymczasowej. Zastosowaliśmy tu pewną sztuczkę: metoda addElement() po prostu zwraca elementprzekazany jako argument po to, aby nie trzeba było przepisać obiektu do zmiennej, gdy chcemy ustawić jeszczejakieś dodatkowe właściwości. Możemy to rozbudować jeszcze dalej i sprawić, że metody takie, jak setValue()czy setDimensions() będą po prostu zwracać $this, dzięki czemu taki łańcuszek wywołań metod możnaciągnąć w nieskończoność. Technika ta nosi nazwę fluent interface i spotkamy się z nią jeszcze w dalszej częścipodręcznika.

ĆwiczenieAby przećwiczyć poznane wiadomości, wróćmy do przykładu z systemem konfiguracji. Nadaje się on idealnie dodalszej rozbudowy poprzez dziedziczenie. Zauważmy, że konfiguracji nie musimy ładować wyłącznie z plików INI.Zamiast tego, możemy utworzyć abstrakcyjną klasę ConfigLoader definiującą interfejs ładowania konfiguracji,która następnie będzie rozszerzana przez rozmaite specjalizacje, np. IniFileConfigLoader, PhpConfigLoader itd.Twoim zadaniem jest odpowiednia przebudowa tamtego kodu. Zdefiniuj interfejs klasy abstrakcyjnejConfigLoader. Określ, które metody muszą być abstrakcyjne, a które finalne oraz gdzie zastosować słowoprotected. Dodaj niezbędne konstruktory i destruktory oraz interfejs fluent interface do głównej klasy. WIniFileConfigLoader dodaj możliwość wyłączenia sprawdzania, czy plik konfiguracyjny istnieje. Przetestuj swojemodyfikacje na podanym przykładzie:

<?php

$config = new Config;

$config->addLoader(new

IniFileConfigLoader('./basic.ini'))->setFileExistsCheck(true);

$config->addLoader(new PhpConfigLoader('./extra.php'));

echo $config->get('basic_option');

echo $config->get('php_option');

ZakończeniePo poznaniu dziedziczenia potrafimy już całkiem sporo wyrazić przy pomocy obiektów. Dziedziczenie ma jednakpewne ograniczenie: nie można dziedziczyć po dwóch klasach naraz:

<?php

class Klasa extends A, B

{

// ...

Dziedziczenie 166

} // end Klasa;

W następnym rozdziale poznamy mechanizm pozwalający częściowo poradzić sobie z tym problemem, czyliinterfejsy.

Interfejsy

InterfejsyDziedziczenie jest jednym z najważniejszych mechanizmów programowania obiektowego, gdyż pozwala rozszerzaćistniejące klasy oraz traktować je bardziej ogólnie. Podobnie jak większość języków programowania, PHP niezezwala jednak na tzw. dziedziczenie wielokrotne, czyli możliwość rozszerzenia więcej niż jednej klasy naraz:

<?php

class MultipleInheritance extends FirstClass, SecondClass

{

} // end MultipleInheritance;

Przy tradycyjnej konstrukcji systemu obiektowego w języku dziedziczenie wielokrotne wprowadza wiele patologii, ajego poprawne używanie wymaga od programisty ścisłej dyscypliny i szczegółowej wiedzy o hierarchii klas.Zamiast niego, PHP wykorzystuje zaczerpniętą z Javy ideę interfejsów.

Interfejs jest zbiorem nazw operacji (metod), których istnienia wymagamy od implementującej go klasy.

Z definicji wynika, że interfejs możemy traktować, jak coś w rodzaju klasy czysto abstrakcyjnej. Może on zawieraćwyłącznie stałe klasowe, z którymi zapoznamy się w dalszej części podręcznika, oraz abstrakcyjne prototypy metodpozbawione implementacji. Tę dostarcza dopiero klasa, dlatego mówimy, że implementuje ona interfejs. Interfejsównie dotyczą ograniczenia dziedziczenia. Programista może jednocześnie rozszerzać inną klasę oraz obokimplementować dowolną liczbę interfejsów.

Uwaga!Od tej pory słowo "interfejs" może oznaczać albo mechanizm interfejsów dostępny w języku, albo zbiór publicznych metod oraz pól wklasie, które są przeznaczone dla używającego jej programisty. Z kontekstu będzie wynikać, o które znaczenie chodzi.

Przykładowe użycieRozpatrzmy prosty system uprawnień. Główna klasa stanowi interfejs dostępu, który potrafi odpowiadać na pytania,czy użytkownik powinien mieć dostęp do wskazanego zasobu. Oprócz tego chcemy mieć zestaw innych klas, którepotrafiłyby generować listę uprawnień dla naszego systemu. Nie chcemy ograniczać możliwości ich dziedziczenia,dlatego zamiast tego utworzymy interfejs opisujący, czego system uprawnień wymaga od twórcy generatorauprawnień, aby mógł on poprawnie działać.

<?php

interface AclGeneratorInterface

{

public function generate();

public function isAllowed($resource);

Interfejsy 167

} // end AclGenerator;

class AclSystem

{

private $_permissions = array();

public function loadGenerator(AclGeneratorInterface $generator)

{

foreach($generator->generate() as $resource => $access)

{

if(!isset($this->_permissions[$resource]))

{

if($access == 2)

{

// Tutaj dostęp będzie generowany dynamicznie

$this->_permissions[$resource] = $generator;

}

elseif($access == 0 || $access == 1)

{

$this->_permissions[$resource] = $access;

}

}

}

} // end loadGenerator();

public function isAllowed($resource)

{

if(!isset($this->_permissions[$resource]))

{

return false;

}

if(is_object($this->_permissions[$resource]) &&

$this->_permissions[$resource] instanceof AclGeneratorInterface)

{

return $this->_permissions[$resource]->isAllowed($resource);

}

return (bool)$this->_permissions[$resource];

} // end isAllowed();

} // end AclSystem;

Aby utworzyć interfejs, po słowie kluczowym interface wpisujemy jego unikalną nazwę, a następnie w nawiasach klamrowych wymieniamy listę wszystkich prototypów metod, których ma on dostarczać. Wszystkie metody interfejsu muszą być z założenia publiczne, dlatego nie można tu używać innych modyfikatorów dostępu, jednak powszechną konwencją jest dopisywanie słowa kluczowego public dla celów czytelności. Powyższy skrypt nie przedstawia póki co żadnej klasy, która by implementowała AclGeneratorInterface, jednak mamy pokazany kod odpowiedzialny za jego wykorzystanie. Jak widać w linii 13, interfejsy mogą być stosowane do typowania argumentów, identycznie jak klasy. W naszym przykładzie chcemy mieć pewność, że wszystkie obiekty, które przekażemy jako argument do loadGenerator() posiadały metody generate() oraz isAllowed() bez

Interfejsy 168

względu na ich położenie w hierarchii klas.W linii 38 widoczna jest jeszcze jedna konstrukcja, czyli specjalny operator instanceof. Pozwala on testować czydany obiekt jest instancją wybranej klasy lub implementuje określony interfejs.Zobaczmy teraz, jak zaimplementować interfejs w klasie tak, by PHP o tym wiedział. Napiszemy przykładowygenerator, który wczyta uprawnienia z pliku.

<?php

class FileGenerator implements AclGeneratorInterface

{

private $_role = '';

public function __construct($role)

{

$this->_role = (string)$role;

} // end __construct();

public function generate()

{

$acl = parse_ini_file('./access/'.$this->_role.'.ini');

foreach($acl as &$permission)

{

if($permission != 0 && $permission != 1)

{

$permission = 0;

}

}

return $acl;

} // end generate();

public function isAllowed($resource)

{

return false;

} // end isAllowed();

} // end FileGenerator;

Aby zaimplementować interfejs, wystarczy po nazwie klasy podać słowo kluczowe implements i wymienić listęinterfejsów, oddzielając je przecinkami. Musimy pamiętać o następujących ograniczeniach:1.1. Implementowane metody muszą mieć dokładnie taki sam nagłówek, jak w interfejsie.2.2. Klasa nie może implementować dwóch (lub więcej) interfejsów, które mają metodę o tej samej nazwie.3.3. Jeżeli klasa odziedziczyła jakąś metodę, której wymaga implementowany interfejs, jej nagłówek musi być

zgodny z zawartością interfejsu.Poniżej pokazany jest przykład błędnego nazewnictwa metod:

<?php

interface GooInterface

{

public function bar($arg1, $arg2);

Interfejsy 169

} // end GooInterface;

class Foo

{

public function foo()

{

echo 'foo';

} // end foo();

public function bar($joe = 'joe')

{

echo 'bar';

} // end bar();

} // end Foo;

class Bar extends Foo implements GooInterface

{

public function joe()

{

echo 'joe';

} // end joe();

} // end GooInterface;

Po uruchomieniu tego skryptu zobaczymy:

Fatal error: Declaration of Foo::bar() must be compatible with that of GooInterface::bar() in /test.php on line 22

Mówi on, że odziedziczona metoda bar() jest niezgodna z interfejsem, który właśnie próbujemyzaimplementować.

Dziedziczenie interfejsówInterfejsy można również dziedziczyć dokładnie tak samo, jak klasy, przy pomocy słowa kluczowego extends. Tutajjednak wielokrotne dziedziczenie jest jak najbardziej dozwolone pod warunkiem, że nie ma konfliktów metod.Dziedziczenie interfejsów jest rzadko spotykane w rzeczywistych skryptach, lecz warto wiedzieć, że ono istnieje.Poniżej przedstawiony jest przykładowy skrypt pokazujący sposób wykorzystania i działanie:

<?php

interface Foo

{

public function foo();

} // end Foo;

interface Bar

{

public function bar();

} // end Bar;

interface Joe extends Foo, Bar

{

Interfejsy 170

public function joe();

} // end Joe;

class Abc implements Joe

{

public function foo()

{

echo 'foo';

} // end foo();

public function bar()

{

echo 'bar';

} // end foo();

public function joe()

{

echo 'joe';

} // end foo();

} // end Abc;

$class = new Abc;

if($class instanceof Foo)

{

echo 'Ten obiekt implementuje interfejs Foo<br/>';

}

if($class instanceof Bar)

{

echo 'Ten obiekt implementuje interfejs Bar<br/>';

}

if($class instanceof Joe)

{

echo 'Ten obiekt implementuje interfejs Joe<br/>';

}

Uwaga!Dziedziczenie klas i interfejsów jest od siebie niezależne. Interfejs nie może dziedziczyć po klasie, a klasa po interfejsie, która może gojedynie implementować.

Interfejsy 171

Interfejsy wbudowaneJęzyk PHP posiada kilkanaście specjalnych interfejsów rozpoznawanych przez interpreter i przeznaczonych dododatkowych zastosowań. Wchodzą one w skład tzw. Standard PHP Library, czyli nowej standardowej bibliotekiPHP zbudowanej w całości w oparciu o programowanie obiektowe. Będziemy się z nią zapoznawać po kawałkurównież w dalszej części podręcznika.W poprzednich rozdziałach poznaliśmy funkcję sizeof() (znaną też jako count()), która zwraca ilośćelementów w tablicy. Może ona współpracować także z obiektami, zwracając ilość publicznych pól:

<?php

class Counter

{

public $foo;

protected $bar;

}

$counter = new Counter;

echo sizeof($counter);

Wiedza o ilości pól jest rzadko potrzebna w praktyce, jednak nic nie stoi na przeszkodzie, aby to przeprogramować.Tutaj przyda nam się pierwszy specjalny interfejs o nazwie Countable. Dostarcza on metodę count(), którąPHP wywołuje, gdy chce uzyskać informacje o ilości elementów.Pierwszym specjalnym interfejsem, jaki poznamy, jest Countable, który dostarcza metodę count(). Informuje oninterpreter, że klasa, która go implementuje, jest zbiorem elementów, które można policzyć (identycznie, jakelementy w tablicy). Rozbudujmy klasę Config z pierwszego rozdziału tak, aby można było uzyskać informacje oilości aktualnie załadowanych opcji. Zakładamy, że wykonałeś ćwiczenie z poprzedniego rozdziału dotyczące jejrozbudowy:

<?php

class Config implements Countable

{

private $_config = array();

private $_awaitingLoaders = array();

public function count()

{

return sizeof($this->_config);

} // end count();

// pozostała część klasy

} // end Config;

Teraz możemy prosto dowiedzieć się, ile opcji aktualnie znajduje się w konfiguracji:

<?php

require('./Config.php');

$config = new Config;

echo 'Ilość elementów w konfiguracji: '.sizeof($config);

Obiekty można jeszcze bardziej upodobnić do tablic dzięki interfejsowi ArrayAccess. Dostarcza on czterech metod:

Interfejsy 172

• offsetGet($key) - wywoływana przy próbie odczytu: $obiekt['klucz']• offsetSet($key, $value) - wywoływana przy próbie zapisu: $obiekt['klucz'] = 5• offsetExists($key) - wywoływana przy próbie sprawdzenia, czy element o podanym kluczu istnieje:isset($obiekt['klucz'])

• offsetUnset($key) - wywoływana przy próbie usunięcia elementu o podanym kluczu:unset($obiekt['klucz'])

Aby przećwiczyć interfejsy w praktyce, cofnijmy się do przykładu z systemem formularzy z poprzedniego rozdziału.Do klasy abstrakcyjnej FormElement dodamy specjalne chronione pole $_attributes będące tablicąprzechowującą dodatkowe atrybuty dla elementu formularza (np. klasa CSS, ID itd.). Klasa musi implementowaćinterfejsy ArrayAccess oraz Countable, które pozwolą na proste zarządzanie atrybutami tak, jakby obiekt elementubył tablicą. Zaimplementuj wszystkie wymagane metody tak, aby odpowiednio modyfikowały pole$_attributes. Zmodyfikuj klasy odpowiednich elementów tak, aby uwzględniały dodane atrybuty wgenerowanym kodzie HTML. Poniżej znajduje się przykładowy plik testowy:

<?php

require('./FormElements.php');

require('./FormBuilder.php');

$form = new FormBuilder;

$element = $form->addElement(new FormInput('name'));

$element['class'] = 'name';

$element['id'] = 'f_name';

if(isset($element['id']))

{

unset($element['id']);

}

$form->display();

Interfejsów specjalnych jest jeszcze więcej. Wśród nich specjalną grupę stanowią tzw. iteratory. Poświęcimy im całyosobny rozdział.

Kiedy stosować?Nie ma jednej, uniwersalnej reguły mówiącej, kiedy należy stosować interfejsy, a kiedy dziedziczenie klas.Zasadniczo gdy chcemy dostarczyć klasie użytkownika pewną, gotową część implementacji, jesteśmy skazani nadziedziczenie, gdyż PHP nie udostępnia żadnych innych mechanizmów jej wstrzykiwania. Jednak gdy pragniemyjedynie zdefiniować listę zachowań, których oczekujemy, bez wnikania w szczegóły ich działania, interfejsy są owiele lepszym pomysłem, gdyż mogą być implementowane niezależnie oraz nie zamykają drogi do dziedziczenia.To do nas, jako projektantów architektury aplikacji, należy odpowiedź na jakiej funkcjonalności najbardziej namzależy i odpowiednio wybrać dostępne środki. Polecamy analizować obiektowo napisane skrypty i biblioteki, abyzapoznać się z ich budową. Naśladowanie dobrych wzorców to jedna z najlepszych szkół.Musimy mieć świadomość, że interfejsy w połączeniu z dziedziczeniem nie gwarantują nam pełnej swobodywielokrotnego wykorzystania kodu. Dzięki interfejsom możemy swobodnie przenosić listę wymaganych zachowań,lecz nie da się przenieść implementacji metod bez użycia dziedziczenia, które ma ograniczenia. Istnieje szansa, żeprzyszłe wersje PHP będą oferować jeszcze jeden mechanizm do obejścia tego problemu.

Interfejsy 173

ZakończeniePoznaliśmy już prawie wszystkie główne mechanizmy obiektowe, które dostarcza nam PHP. Interfejsy znaczącoposzerzyły nasze możliwości wyrażania zależności między klasami. Kolejny rozdział poświęcony będzieprofesjonalnym mechanizmom raportowania błędów przy pomocy wyjątków. Od strony technicznej nie są oneczęścią programowania obiektowego, lecz w PHP silnie na nim bazują i dlatego ich omówienie znajduje się właśnietutaj.

Wyjątki

WyjątkiBłędy nie muszą wynikać wyłącznie z nieuwagi programisty. Może je powodować użytkownik poprzez dziwnedziałania, niewłaściwą konfigurację lub nawet problemy systemowe. Obsługa błędów jest to jeden z istotniejszychelementów współczesnych aplikacji. Początkujący programiści często ją bagatelizują, kompletnie nie przejmując siętym, że przy złych ustawieniach na ekranie przeglądarki pojawia się 500 ostrzeżeń PHP, albo załatwiając sprawęnajprostszą komendą die(). Wykorzystują to hakerzy, dla których ścieżki dostępu, nazwy plików i numery liniiwyświetlane przy komunikatach PHP to znakomite źródło informacji o serwerze, skrypcie oraz umiejętnościachprogramisty.Ignorowanie zagrożenia wynika także z tego, że PHP przez długi czas nie miał zadowalających mechanizmówobsługi błędów, a opracowane wtedy prymitywne rozwiązania funkcjonują do dnia dzisiejszego. Zastanówmy sięzatem, czego będziemy wymagać od systemu obsługi błędów:1. Priorytet - nie wszystkie błędy są krytyczne dla pracy skryptu. Gdy nie uda nam się połączyć z bazą danych,

najprawdopodobniej nie będziemy w stanie nic wyświetlić, dlatego informujemy internautę, że tym razem musiobejść się smakiem. Jednak brak pliku językowego nie jest już aż tak krytyczny. Prawdopodobnie tłumacze niezakończyli jeszcze swej pracy, dlatego tymczasowo możemy obejść problem, wczytując komunikaty wdomyślnym języku, które już są gotowe.

2. Miejsce wystąpienia - sposób obsługi błędu często zależy też od miejsca jego wystąpienia. Brak plikukonfiguracyjnego to poważna sprawa, lecz inne funkcje brak potrzebnych im plików mogą interpretować inaczej.Jako programiści musimy mieć możliwość decydowania, co zrobić z błędnymi sytuacjami w konkretnym miejscuskryptu.

3. Przerwanie pracy - gdy wystąpi błąd, dalsze wykonywanie funkcji zazwyczaj przestaje mieć sens, jednak niezawsze chcemy przy tym przerwać cały skrypt. System obsługi błędów musi mieć możliwość przerwania tychpartii wykonywanego kodu, dla których wykryty problem jest krytyczny, nie zakłócając przy tym reszty skryptu.

Te trzy właściwości posiadają wyjątki. W PHP każdy wyjątek jest specjalnym obiektem klasy Exception lub jejpochodnych. Wyjątki można rzucać oraz obsługiwać. Skrypt rzuca wyjątek podczas wystąpienia sytuacji, którąuznajemy za błędną. Powoduje on przerwanie wykonywania bieżącego fragmentu kodu. Kod zawarty jest wspecjalnych blokach informujących, jak wyjątki danego rodzaju obsłużyć. Interpreter przerywa wszystkie aktualniewykonywane funkcje, dopóki nie trafi na blok, który wie, jak obsłużyć rzucony wyjątek i jemu przekazujesterowanie. W kodzie obsługi wyjątku możemy sprawdzić, co to jest za błąd oraz np. wyświetlić internaucieinformacje o problemie.

Wyjątki 174

Wyjątki w praktycePrzyjrzyjmy się teraz, jak suchy opis prezentuje się w praktyce. Poniżej prezentujemy zmodyfikowaną wersję klasyFileConfigLoader z naszego systemu konfiguracji omówionego w poprzednich rozdziałach. Dotychczas brak plikuobsługiwaliśmy, po prostu zwracając pustą tablicę, jednak logiczniejszym jest rzucenie wtedy wyjątku z informacjąo problemie. W przykładzie zakładamy, że wykonałeś ćwiczenie z rozdziału Dziedziczenie.

<?php

class FileConfigLoader extends FileLoader

{

private $_fileName = '';

public function setFilename($filename)

{

$this->_fileName = $filename;

} // end setFilename();

public function load()

{

if(file_exists($this->_filename))

{

return parse_ini_file($this->_filename);

}

// jeśli pliku nie ma, rzuć wyjątek

throw new Exception('Cannot read the configuration file:

'.$this->_filename);

} // end load();

} // end FileConfigLoader;

Wyjątki rzucamy przy pomocy słowa kluczowego throw, którego argumentem jest wyrażenie dające obiekt klasyException lub pochodnych. Może on być wczytany ze zmiennej lub zwrócony przez inną metodę, lecz najczęściejspotykaną konstrukcją jest podana w przykładzie throw new, która w locie tworzy potrzebny obiekt. Konstruktorklasy Exception domyślnie pobiera w argumencie komunikat błędu, który możemy dobrać według upodobań.Na razie tak rzucony wyjątek spowoduje co najwyżej wyświetlenie komunikatu Fatal error z informacją, że PHP nieznalazł żadnego bloku potrafiącego go przechwycić i obsłużyć. Przechwytywaniem wyjątków zajmuje się bloktry... catch:

<?php

require('./Config.php');

require('./ConfigLoader.php');

$config = new Config;

try

{

$config->addLoader(new FileConfigLoader('./config/basic.ini.php'));

// nieistniejący plik

Wyjątki 175

$config->addLoader(new

FileConfigLoader('./config/unexisting.ini.php'));

// spróbujmy odczytać nieistniejącą opcję. Jak pamiętamy, spowoduje

to

// próbę wczytania wszystkich plików oraz odkrycie, że jeden z nich

nie

// istnieje. Rzucony zostanie wyjątek.

echo $config->get('unexisting_option');

// to się już nie wykona

echo $config->get('website_name');

}

catch(Exception $exception)

{

echo 'Wystąpił błąd w linii '.$exception->getLine().':

'.$exception->getMessage();

}

Jak pamiętamy, nasz system konfiguracji obsługuje leniwe ładowanie, czyli pliki odczytywane są w momencieodwołania do nieznanej opcji. Przy tej okazji obiekt stworzony dla pliku unexisting.ini.php odkryje, że takiplik nie istnieje i rzuci wyjątek, przerywając kolejne poziomy wykonywania skryptu, dopóki nie natrafi na blok try,który przechwytuje wszystkie wyjątki, jakie wystąpiły w podanym kodzie. Następnie porównuje je z blokiem catch,gdzie precyzujemy, jakie klasy wyjątków chcemy obsługiwać oraz do jakiej zmiennej należy zapisać przechwyconywyjątek. Jeśli dany blok nie może odnaleźć dopasowania, interpreter wraca do przerywania pracy kolejnych funkcji imetod, dopóki nie natrafi na następny try...catch. Kiedy PHP zlokalizuje już kod wiedzący, jak obsłużyć danywyjątek, przeskakuje do odpowiadającego mu bloku catch i wykonuje go.W naszym przypadku przepływ sterowania będzie wyglądać następująco:1. Próbujemy odczytać opcję unexisting_option.2.2. Opcja jest niedostępna. Próbujemy załadować pliki.3. Ładujemy plik basic.ini.php.4.4. Opcja się nie pojawiła, więc przechodzimy dalej.5. Ładujemy plik unexisting.ini.php6.6. Taki plik nie istnieje. Rzucamy wyjątek.7. PHP przerywa wykonywanie metody load() w klasie FileConfigLoader.8. PHP przerywa wykonywanie metody get() w klasie Config, która odpowiada za pobranie wartości

nieistniejącej opcji.9. PHP przerywa wykonywanie dalszego kodu w bloku try.10. PHP znajduje blok catch i odkrywa, że potrafi on obsługiwać wyjątki klasy Exception.11. PHP zapisuje obiekt wyjątku do zmiennej $exception i rozpoczyna wykonywanie kodu należącego do

bloku catch.12.12. Wyświetlamy komunikat i linię, w której wystąpił błąd.13. Kontynuujemy pracę skryptu od pierwszej linijki po bloku try ... catch.

Wyjątki 176

Zagnieżdżanie bloków tryBloki try... catch można zagnieżdżać. PHP próbuje dopasować rzucony wyjątek do najniższego z bloków, którypotrafi go obsłużyć i tam przekierowuje działanie skryptu. Zazwyczaj aplikacja posiada jeden główny blok, któregozadaniem jest przechwycenie wszystkich wyjątków i potraktowanie ich jako błędy krytyczne. Wszędzie, gdziechcemy potraktować je łagodniej, stosujemy dodatkowe bloki, w których określamy inny sposób obsługi.Poniżej pokazany jest przykład zagnieżdżania. Za pomocą argumentu URL where można ustawić, w którymmiejscu skrypt ma rzucić wyjątek:

<?php

if(!isset($_GET['where']))

{

$_GET['where'] = 0;

}

try

{

if($_GET['where'] == 0)

{

throw new Exception('Błąd 0');

}

echo 'Dalsza część bloku...<br/>';

try

{

if($_GET['where'] == 1)

{

throw new Exception('Błąd 1');

}

echo 'Dalsza część bloku podrzędnego...<br/>';

}

catch(Exception $exception)

{

echo 'Problem: '.$exception->getMessage().'<br/>';

}

echo 'Dalsza część skryptu...<br/>';

}

catch(Exception $exception)

{

echo 'Błąd krytyczny: '.$exception->getMessage().'<br/>';

}

Wywołaj skrypt z argumentem ?where=0 oraz ?where=1. Pierwsze wywołanie rzuci wyjątek Błąd 0, któryzostanie przechwycony przez główny blok, który leży najbliżej, i potraktowane jak błąd krytyczny. Wyjątek zdrugiego wywołania znajduje się w podrzędnym bloku, dlatego to właśnie on zostanie użyty do jego obsługi.Zauważmy, że po wykonaniu klauzuli catch skrypt kontynuuje działanie od końca bloku - w drugim przypadkuwciąż wyświetli nam się napis "Dalsza część skryptu". Instrukcje echo umieszczone tuż po throw nie wykonają sięnigdy.

Wyjątki 177

Pokażemy teraz przykład praktycznego zastosowania, w którym wykorzystamy klasę SplFileObject z bibliotekiStandard PHP Library. Udostępnia ona obiektowy interfejs do operacji na plikach, a ewentualne błędy raportuje jakowyjątki RuntimeException. Naszym zadaniem jest wczytanie konfiguracji strony WWW oraz tekstu powitalnego:1.1. Brak konfiguracji jest krytyczny - bez niej nie jesteśmy w stanie wyświetlić strony.2.2. Brak tekstu powitalnego możemy potraktować łagodniej - być może osoba odpowiedzialna za treść merytoryczną

jeszcze go nie dostarczyła, dlatego zamiast niego wystarczy wyświetlić tekst domyślny.

<?php

try

{

$config = new Config;

// Ładowanie konfiguracji strony

$manualLoader = new ManualConfigLoader();

$configFile = new SplFileObject('./config/website.conf');

foreach($configFile as $line)

{

$option = explode('=', $line);

$manualLoader->addOption(trim($option[0]), trim($option[1]));

}

$config->addLoader($manualLoader);

// Ładowanie tekstu powitalnego.

$text = '';

try

{

$introFile = new SplFileObject($config->get('intro_file'));

foreach($introFile as $line)

{

$text .= $line;

}

}

catch(RuntimeException $exception)

{

$text = 'Brak zdefiniowanego tekstu powitalnego.';

}

// Wyświetl tekst powitalny

echo $text;

}

catch(RuntimeException $exception)

{

echo 'Błąd krytyczny: '.$exception->getMessage();

}

Aby przykład zadziałał, należy dołączyć do niego stworzony w poprzednich rozdziałach system konfiguracji oraz napisać klasę ManualConfigLoader. Jest to kolejny system ładowania, który umożliwia ręczne definiowanie opcji konfiguracyjnych z poziomu skryptu poprzez metodę addOption($nazwa, $wartosc). Pozostawiamy to

Wyjątki 178

jako ćwiczenie.Obiekty klasy SplFileObject reprezentują pojedynczy plik tekstowy, który jest otwierany w konstruktorze.Wszystkie błędy sygnalizowane są rzuceniem wyjątku. Najprostszy odczyt z pliku polega na umieszczeniu obiektuw pętli foreach, dzięki czemu w zmiennej $line pojawi się treść kolejnych linijek. Przykład pokazuje także, jakwykorzystać try ... catch jako instrukcję sterowania przepływem wykonania skryptu. Przyjrzyjmy się, jak ładowanyjest tekst powitalny. Przed wejściem do sekcji krytycznej tworzymy pustą zmienną $text. W bloku try próbujemyją wypełnić na podstawie treści z pliku. Jeśli proces ten zostanie z jakiegoś powodu przerwany, PHP rzuci wyjątek,który sprawi, że do zmiennej zostanie zapisana wartość domyślna. W ten sposób tuż po wykonaniu bloku mamypewność, że w zmiennej $text zawsze coś się znajdzie - albo wczytane z pliku, albo uzupełnione domyślnątreścią. Wyjątek potrzebny jest nam jedynie do przerwania wykonywania aktualnego kodu i przeskoczenia do catch.Nie zajmujemy się jego zawartością oraz informacjami.

Klasa ExceptionKlasa Exception jest podstawą wszystkich wyjątków, jakie można rzucać w PHP. Udostępnia ona zbiórpodstawowych metod do zarządzania informacjami o wyjątku i często jest rozszerzana poprzez dziedziczenie.Programiści często nie dodają żadnej nowej funkcjonalności w klasach pochodnych. Chodzi o to, aby mócprzechwytywać tylko pewien rodzaj wyjątków pochodzących z określonego źródła:

<?php

class CustomException extends Exception { }

try

{

// kod ...

}

catch(CustomException $exception)

{

// Przechwytujemy jedynie wyjątki CustomException, a

// innymi się nie zajmujemy.

}

Konstruktor klasy Exception przyjmuje do trzech argumentów:1. Komunikat $message2. Kod błędu $code3. Poprzedni wyjątek $previousPonadto mamy do dyspozycji szereg metod służących do pobierania informacji o wyjątku, gdy już goprzechwycimy:• getMessage() - zwraca komunikat błędu.• getCode() - zwraca kod błędu.• getPrevious() - zwraca poprzedni wyjątek.• getFile() - zwraca nazwę pliku, w którym wyjątek został rzucony.• getLine() - zwraca numer linii, w której wyjątek został rzucony.• getTrace() - zwraca tablicę zawierającą ślad stosu, czyli informacje o wszystkich funkcjach i metodach

wywołanych w momencie rzucenia wyjątku.• getTraceAsString() - zwraca ślad stosu, ale jako tekst.

Wyjątki 179

Uwaga!Metoda getPrevious() oraz argument $previous w konstruktorze zostały dodane w PHP 5.3.0.

Biblioteka SPL dostarcza zbiór gotowych klas wyjątków dla najczęstszych problemów. Programista powinien jestosować wszędzie tam, gdzie to jest potrzebne, zachowując ich znaczenie.• RuntimeException - reprezentuje błędy, które mogą być wykryte jedynie w trakcie wykonywania (brak

pliku, błąd połączenia itd.).• OutOfRangeException - reprezentuje błędy przekroczenia przez wartość ustalonego zakresu.• RangeException - podanie niewłaściwego zakresu wartości.• OutOfBoundsException - brak wartości o podanym kluczu.• DomainException - próba użycia niewłaściwych danych (np. spodziewamy się listy opcji konfiguracyjnych, a

dostaliśmy zawartość menu).• LengthException - wartość ma niewłaściwą długość.• InvalidArgumentException - argument ma niewłaściwą wartość (np. spodziewaliśmy się liczby, a

dostaliśmy tablicę).• OverflowException - próba dodania nowej wartości do przepełnionego pojemnika na dane (np. gdy

ustawiliśmy odgórny limit na 100 elementów).• UnderflowException - przeciwieństwo poprzedniego, czyli próba pobrania wartości z pustego.• LogicException - niewłaściwe/fałszywe wyrażenie logiczne.Dobrą praktyką jest pisanie tzw. kodu idiotoodpornego, który nie sypie się przy podaniu niewłaściwych danych, leczrozsądnie to sygnalizuje, dzięki czemu reszta aplikacji ma szansę odpowiednio zareagować. Nietrudno zauważyć, żepowyższe klasy wyjątków dotyczą najbardziej podstawowych problemów, jakie mogą wystąpić w naszym skrypcie,dlatego powinny być wykorzystane przy sprawdzaniu warunków początkowych i brzegowych.

Uwaga!PHP nie jest w stanie sprawdzić czy daną klasę wyjątków używamy zgodnie z jej znaczeniem. Jest to zadanie dla programisty piszącego itestującego skrypt.

Problem sprzątaniaPrzerywanie pracy w połowie skomplikowanego procesu nie zawsze jest dobrym pomysłem. Metoda mogłaprzydzielić sobie sporo zasobów (np. otwarte pliki), a rzucając wyjątek uniemożliwiamy jej ich zwolnienie. Może todoprowadzić do kolejnych problemów w dalszej części skryptu lub nawet uszkodzenia danych. Problem ten znanyjest pod nazwą problemu sprzątania, czyli jak zwolnić przydzielone zasoby po rzuceniu wyjątku. Niektóre językioferują dla niego wbudowane wsparcie poprzez dodatkową klauzulę finally w bloku try. Wykonuje się ona zawsze,niezależnie od tego czy pojawi się wyjątek czy nie, a jej zadaniem jest posprzątanie po całym procesie. Brakuje jejjednak w PHP, dlatego programiści muszą radzić sobie inaczej. Często spotykanym rozwiązaniem jest dodanie"fikcyjnego" bloku try ... catch, który przechwytuje wyjątek tylko po to, by posprzątać, po czym rzuca goponownie:

public function complexMethod()

{

try

{

echo 'Skomplikowane obliczenia';

// domyślne sprzątanie.

$this->_cleanObject();

}

Wyjątki 180

catch(Exception $exception)

{

$this->_cleanObject();

throw $exception;

}

} // end complexMethod();

Podświetlona linijka ukazuje dopiero co przechwycony wyjątek, który rzucamy ponownie, by zajął się nimnadrzędny blok try, ponieważ my chcieliśmy tylko posprzątać.

PoradaZawsze przed napisaniem danego kawałka kodu zastanów się nad produkowanymi przez niego efektami ubocznymi (np. modyfikacjadanych itd.) oraz przemyśl czy w przypadku wystąpienia wyjątku nie musimy przypadkiem posprzątać po nieukończonym procesie.Nieuwzględnianie tego jest przyczyną wielu trudnych do wykrycia problemów w bardziej złożonych skryptach.

Wyjątki w PHPW PHP wyjątki pojawiły się stosunkowo późno. O ile we własnym kodzie nie ma problemu z ich stosowaniem, towiększość dostępnych wbudowanych rozszerzeń nie potrafi raportować błędów przy ich pomocy. Obsługę wyjątkówposiada jedynie kilka najnowszych, obiektowych modułów:•• Standard PHP Library•• Biblioteka PHAR•• Biblioteka PDONiestety i to nie jest regułą. Twórcy PHP nie są konsekwentni i wciąż zdarza im się wprowadzać nowe rozszerzeniaz autorskimi mechanizmami obsługi błędów (np. biblioteka Intl wprowadzona w PHP 5.3.0). Aby pisać w PHP,trzeba do tego po prostu przywyknąć.Język posiada jednak możliwość tłumaczenia wielu standardowych błędów na wyjątki. Sztuczka polega na napisaniuspecjalnej funkcji obsługi błędów i zainstalowaniu jej w skrypcie:

<?php

function ExceptionErrorHandler($errno, $errstr, $errfile, $errline)

{

if(in_array($errno, array(E_WARNING, E_RECOVERABLE_ERROR)))

{

throw new ErrorException($errstr, 0, $errno, $errfile, $errline);

}

return false;

}

set_error_handler('ExceptionErrorHandler');

Powyższy kod sprawi, że ostrzeżenia (Warning) oraz przechwytywalne błędy krytyczne (Catchable fatal error)zostaną przekonwertowane na wyjątki.

Uwaga!Funkcja obsługi błędów przechwytuje wszystkie możliwe błędy nawet, gdy raportowanie niektórych z nich jest wyłączone.

Wyjątki 181

ZakończenieWyjątki to elegancki mechanizm obsługi błędów, który świetnie współpracuje z programowaniem obiektowym. Wnastępnym rozdziale powrócimy do niego z powrotem, aby omówić elementy statyczne klas, z których możnakorzystać bez konieczności posiadania obiektów.

Elementy statyczne

Elementy statycznePoznane do tej pory mechanizmy programowania obiektowego bazowały w całości na obiektach, czylirzeczywistych, niezależnych bytach reprezentujących odpowiednie klasy. Jednak w niektórych sytuacjach tworzenieobiektu tylko po to, by móc wykonać jakąś metodę jest przerostem formy nad treścią. Dlatego PHP, podobnie jakwiększość innych języków obiektowych, oferuje możliwość tworzenia tzw. elementów statycznych klasy. Dodziałania nie potrzebują one jej obiektów, ale ponieważ są powiązane z klasą, wciąż mogą wykorzystywać np.dziedziczenie. Możemy to traktować jako rozszerzenie zwykłych funkcji i zmiennych globalnych o niektórewłaściwości obiektów.

Tworzenie statycznych pól oraz metodAby utworzyć statyczne pole lub metodę, dodajemy do jego deklaracji słowo kluczowe static. Następnie możemywykorzystać operator zakresu :: poprzedzony nazwą klasy, by dostać się do nich. Nie potrzebujemy przy tymżadnego obiektu tej klasy. W poniższym przykładzie używamy elementów statycznych do kontrolowania, ileobiektów klasy zostało już utworzonych.

<?php

class TypicalClass

{

private $_value;

static private $_objectCount = 0;

public function __construct($value)

{

$this->_value = $value;

self::$_objectCount++;

} // end __construct();

public function getValue()

{

return $this->_value;

} // end getValue();

static public function getObjectCount()

{

return self::$_objectCount;

} // end getObjectCount();

} // end TypicalClass;

Elementy statyczne 182

$object1 = new TypicalClass('foo');

$object2 = new TypicalClass('bar');

$object3 = new TypicalClass('joe');

echo 'Do tej pory utworzona została następująca ilość ';

echo 'obiektów TypicalClass: '.TypicalClass::getObjectCount();

self jest odpowiednikiem $this dla metod statycznych i wskazuje na aktualną klasę. Oprócz tego mamy teżparent, z którego korzystaliśmy już przy omawianiu dziedziczenia. Jak łatwo się domyślić, wskazuje on na klasębazową do naszej. Zauważmy, że w przeciwieństwie do ->, po operatorze zakresu musimy podać znak dolara,odwołując się do statycznego pola klasy.Metody statyczne posiadają pewne ograniczenie w stosunku do ich zwykłych odpowiedników. Nie są wywoływanew kontekście obiektu, dlatego nie można w nich korzystać ze zmiennej specjalnej $this. Pomimo tego, PHPdopuszcza ich wywoływanie na dwa sposoby:

// sposob 1

SomeClass::staticMethod();

// sposob 2

$object = new SomeClass;

$object->staticMethod();

Okazuje się, że nic nie przeszkadza, aby metodę statyczną wywołać jak zwykłą, lecz nie będzie to miało żadnegoznaczenia. Zalecamy, aby unikać takiego mieszania, gdyż wprowadza ono nieład w kodzie i utrudnia jego analizęinnym osobom. Działanie w drugą stronę, tj. wywoływanie metod niestatycznych jako statyczne powodujewygenerowanie komunikatu E_STRICT.

Uwaga!W przeciwieństwie do metod, statycznych pól klasy nie można wywoływać w kontekście obiektu.

Statyczne elementy w dziedziczeniuElementy statyczne także podlegają dziedziczeniu. Z poziomu klasy B rozszerzającej A możemy dostać się dowszystkich statycznych metod i pól zadeklarowanych w tej drugiej, a także nadpisać je. Modyfikatory dostępu takie,jak protected i private pozwalają ograniczyć możliwość stosowania wyłącznie do innych metod klasy aktualnejoraz klas pochodnych. Przydaje się to, gdy potrzebujemy zamknąć jakąś często wykonywaną operację w postacifunkcji, lecz jednocześnie nie chcemy udostępniać jej użytkownikom naszej klasy.W poniższym przykładzie wykorzystamy tzw. wzorzec projektowy, czyli ogólny przepis na osiągnięcie pewnegoefektu w programowaniu obiektowym. Wzorcom projektowym przyjrzymy się bliżej w dalszej części podręcznika,dlatego nie będziemy ich tutaj dokładnie objaśniać. Jednym ze wzorców jest fabryka. Czasami zwykły konstruktororaz operator new jest zbyt mało elastyczny, aby móc utworzyć obiekt. Przykładowo, chcielibyśmy, aby był on jużod razu prawidłowo skonfigurowany do pracy lub aby na podstawie dostarczonych danych utworzony był obiektjednej z klas rozszerzających wybieranej dynamicznie. Najogólniej ujmując, rozwiązanie polega na utworzeniustatycznej metody factory(), która tworzy nowy obiekt, konfiguruje go i udostępnia skryptowi. Naszauproszczona fabryka będzie musiała uwzględniać to, że klasa może być dziedziczona i wtedy programista musi miećmożliwość poprawienia jej, aby tworzyć także obiekty nowego rodzaju.

<?php

Elementy statyczne 183

class BaseItem

{

static protected $_instances = 0;

private function __construct()

{

echo 'Tworzymy obiekt podstawowy<br/>';

} // end __construct();

public function doSomeConfig()

{

echo 'Konfigurujemy obiekt '.((get_class($this) == 'BaseItem') ?

'podstawowy' : 'specjalny').'<br/>';

} // end doSomeConfig();

public function work()

{

echo 'Ja działam: '.get_class($this).'<br/>';

} // end work();

public static function factory()

{

$object = new BaseItem;

$object->doSomeConfig();

self::$_instances++;

return $object;

} // end factory();

} // end BaseItem;

class SpecialItem extends BaseItem

{

private function __construct()

{

echo 'Tworzymy obiekt specjalny<br/>';

} // end __construct();

public static function factory()

{

$object = new SpecialItem;

$object->doSomeConfig();

parent::$_instances++;

return $object;

} // end factory();

} // end SpecialItem;

Elementy statyczne 184

$baseObject = BaseItem::factory();

$specialObject = SpecialItem::factory();

$baseObject->work();

$specialObject->work();

Zwróćmy uwagę, że obie klasy posiadają prywatny konstruktor. Oznacza to, że programista może utworzyć ichobiekty jedynie za pośrednictwem naszej fabryki, która jest częścią klasy i dzięki temu może wywołać konstruktor(ograniczenia widoczności działają na podst. porównywania klas, a nie obiektów). W klasie BaseItemzadeklarowany został chroniony licznik obiektów współdzielony także przez wszystkie klasy rozszerzające. Jednakrozszerzając ją, nie możemy zostawić fabryki niezmienionej. W metodzie factory() mamy jasno powiedziane,jakiej klasy obiekt tworzymy, przez co wywołanie SpecialItem::factory() dalej tworzyłoby nam wrzeczywistości obiekty BaseItem. Musimy nadpisać wspomnianą metodę, zmieniając tę jedną linijkę i pozostawiającresztę kodu niezmienioną. Przy okazji widać także, że do statycznych elementów klasy bazowej można odwoływaćsię poprzez parent.Jako ćwiczenie sprawdź następujące rzeczy:1. Czy zastąpienie parent przez self także zadziała i czy nie zmieni wyniku? Dodaj statyczną metodę count(),

która zwróci wartość licznika i użyj jej, aby się o tym przekonać.2. Zakomentuj metodę factory() w klasie SpecialItem i spróbuj wyjaśnić zachowanie skryptu pamiętając o

tym, że konstruktory obu klas są prywatne.Wnikliwi czytelnicy powinni dostrzec w kodzie wywołanie funkcji get_class(). Zwraca ona nazwę klasyobiektu podanego w argumencie. W szczególności, jeśli za argument podamy $this, możemy dowiedzieć sięwszystkiego o obiekcie, który wywołał aktualną metodę. Spróbuj pomyśleć, jak metoda może wykorzystać tęfunkcję do dowiedzenia się, czy została wywołana na obiekcie klasy bazowej czy pochodnej?

Stałe klasowe

Stałe klasowe zostały wprowadzone w PHP 5.1.0.

PHP 5.1.0 wprowadził koncepcję stałych klasowych. Zachowują się one dokładnie tak samo, jak poznane już zwykłestałe, lecz są powiązane z konkretną klasą, a dostęp do nich odbywa się za pośrednictwem operatora ::poprzedzonego nazwą klasy. Stałą deklarujemy przy pomocy słowa kluczowego const, po którym podajemy jejnazwę oraz żądaną wartość. Stałe klasowe są stosowane do nazywania różnych specjalnych wartości, które sąprzeznaczone do wykorzystywania z obiektami naszej klasy. Ponieważ ich częścią jest nazwa klasy, nie musimyobawiać się o konflikt nazw oraz tworzyć bardzo długich, złożonych identyfikatorów. Przykład zastosowania podanyjest poniżej:

<?php

class File

{

const READ = 1;

const WRITE = 2;

private $_ptr;

private $_fileName;

private $_mode;

Elementy statyczne 185

public function __construct($fileName, $mode = self::READ)

{

$this->_fileName = $fileName;

$this->_mode = $mode;

if(!file_exists($fileName))

{

throw new FileException('Podany plik nie istnieje:

'.$fileName);

}

switch($mode)

{

case self::READ:

$this->_ptr = fopen($fileName, 'r');

break;

case self::WRITE:

$this->_ptr = fopen($fileName, 'w');

break;

case self::READ | self::WRITE:

$this->_ptr = fopen($fileName, 'rw');

}

} // end __construct();

// pozostałe metody

} // end File;

try

{

$file = new File('./plik.txt', File::READ);

// inne działania

}

catch(FileException $exception)

{

die('Nie można otworzyć pliku');

}

W przykładzie tworzymy klasę File, która ma reprezentować otwarty plik. Chcielibyśmy w przejrzysty sposóbreprezentować tryby otwarcia, dlatego utworzyliśmy dla nich kilka stałych klasowych wykorzystywanych jakobinarne flagi. Aby powyższy przykład zadziałał, należy we własnym zakresie dopisać klasę wyjątku FileException.Stałe klasowe mogą być też deklarowane w interfejsach. Wtedy można się do nich odwoływać zarówno zapośrednictwem nazwy interfejsu, jak i nazw klas go implementujących.

Elementy statyczne 186

Późne wiązanie statyczne

Późne wiązanie statyczne zostało wprowadzone w PHP 5.3.0.

Wróćmy do naszej fabryki. Ma ona pewien drobny mankament. Jeśli programista chce utworzyć więcej klaspochodnych, musi dla każdej z nich od zera napisać fabrykę. Pół biedy, gdy jest ona prosta, ale wyobraźmy sobie, żeskłada się z ponad 100 linii kodu. Jaką mamy gwarancję, że programista nie popsuje czegoś? Co zrobić, gdybędziemy chcieli dodać funkcjonalność do klasy bazowej, która będzie musiała być uwzględniona przez wszystkiefabryki potomne? Nie zawsze musimy mieć wpływ na to, co napisze użytkownik naszej klasy.Problem polega na tym, że metoda factory() jest monolitem, który nie rozróżnia samego utworzenia obiektu odjego konfiguracji. Dlatego wprowadźmy dodatkową, chronioną metodę _concreteFactory(), której zadaniemjest wyłącznie utworzenie obiektu i przekazanie go ogólnej fabryce, która dokona reszty. Programista musi jedyniezmodyfikować _concreteFactory(), pozostawiając konfigurację w naszych rękach.

<?php

class BaseItem

{

static protected $_instances = 0;

private function __construct()

{

echo 'Tworzymy obiekt podstawowy<br/>';

} // end __construct();

public function doSomeConfig()

{

echo 'Konfigurujemy obiekt '.((get_class($this) == 'BaseItem') ?

'podstawowy' : 'specjalny').'<br/>';

} // end doSomeConfig();

public function work()

{

echo 'Ja działam: '.get_class($this).'<br/>';

} // end work();

public static function factory()

{

$object = self::_concreteFactory();

$object->doSomeConfig();

self::$_instances++;

return $object;

} // end factory();

protected static function _concreteFactory()

{

Elementy statyczne 187

return new BaseItem;

} // end _concreteFactory();

} // end BaseItem;

class SpecialItem extends BaseItem

{

private function __construct()

{

echo 'Tworzymy obiekt specjalny<br/>';

} // end __construct();

protected static function _concreteFactory()

{

return new SpecialItem;

} // end _concreteFactory();

} // end SpecialItem;

$baseObject = BaseItem::factory();

$specialObject = SpecialItem::factory();

$baseObject->work();

$specialObject->work();

Ups, okazuje się, że skrypt nie do końca działa tak, jak chcemy. Ci, którzy odpowiedzieli na pytania z podrozdziału"Statyczne elementy w dziedziczeniu", powinni już znać wyjaśnienie tego, co się stało. Rezultatem działania skryptujest:

Tworzymy obiekt podstawowy

Konfigurujemy obiekt podstawowy

Tworzymy obiekt podstawowy

Konfigurujemy obiekt podstawowy

Ja działam: BaseItem

Ja działam: BaseItem

Innymi słowy, wywołanie SpecialItem::factory() całkowicie zignorowało istnienie nadpisanej metodySpecialItem::_concreteFactory() i ponownie wywołało jej odpowiednik w klasie bazowej. Okazuje się,że PHP wiąże wywołanie z konkretną metodą w kodzie już w fazie kompilacji. Kompilując metodę factory(), odrazu powiązał ją na sztywno z _concreteFactory() w tej samej klasie, bez oglądania się na dziedziczenie. Wpierwszym rozdziale o programowaniu obiektowym wspominaliśmy o polimorfiźmie oraz o tym, że w PHPwszystkie metody są polimorficzne z definicji i nie trzeba się tym przejmować. Doprecyzujmy teraz: wszystkieniestatyczne metody są polimorficzne, a w przypadku statycznych, musimy skorzystać z tzw. późnego wiązaniastatycznego (ang. late static binding) wprowadzonego w PHP 5.3.0. Chcemy poinformować PHP, że odwołanie do_concreteFactory() może prowadzić do różnych metod w zależności od tego czy wywołamyBaseItem::factory() czy SpecialItem::factory(). Dokonujemy tego poprzez zastąpienie słowakluczowego self przez static:

<?php

class BaseItem

Elementy statyczne 188

{

static protected $_instances = 0;

private function __construct()

{

echo 'Tworzymy obiekt podstawowy<br/>';

} // end __construct();

public function doSomeConfig()

{

echo 'Konfigurujemy obiekt '.((get_class($this) == 'BaseItem') ?

'podstawowy' : 'specjalny').'<br/>';

} // end doSomeConfig();

public function work()

{

echo 'Ja działam: '.get_class($this).'<br/>';

} // end work();

public static function factory()

{

$object = static::_concreteFactory();

$object->doSomeConfig();

self::$_instances++;

return $object;

} // end factory();

protected static function _concreteFactory()

{

return new BaseItem;

} // end _concreteFactory();

} // end BaseItem;

class SpecialItem extends BaseItem

{

private function __construct()

{

echo 'Tworzymy obiekt specjalny<br/>';

} // end __construct();

protected static function _concreteFactory()

{

return new SpecialItem;

} // end _concreteFactory();

} // end SpecialItem;

Elementy statyczne 189

$baseObject = BaseItem::factory();

$specialObject = SpecialItem::factory();

$baseObject->work();

$specialObject->work();

Teraz nasz kod działa tak, jak tego chcemy. Późnego wiązania należy używać wtedy, gdy przewidujemy możliwośćrozszerzania klasy z metodami statycznymi, i to w taki sposób, by robić z tego dziedziczenia prawdziwy użytek.

ZastosowanieProgramiści najczęściej wykorzystują elementy statyczne do tworzenia dodatkowych mechanizmów związanych zinicjacją obiektów oraz ogólnych operacji powiązanych z klasą, które:•• nie wymagają obecności obiektu,•• lub których wyniki muszą być dostępne dla wszystkich obiektów.Innym zastosowaniem jest pozostawienie klasy wyłącznie w charakterze pojemnika chroniącego dostęp do danych itraktowanie metod statycznych jako bardziej rozbudowanych funkcji. Pokażemy teraz jedno z rozwiązańprogramistycznych stosowanych w wielu skryptach, które bazuje w całości na elementach statycznych klas.W rozdziale o funkcjach poznaliśmy słowo kluczowe global przenoszące zmienną z globalnej do lokalnejprzestrzeni funkcji. W aplikacjach obiektowych korzystanie z global jest traktowane jako zła praktyka, która możeprowadzić do nieprzewidzianych zachowań. Wszystko powinno być udostępniane za pośrednictwem obiektowychinterfejsów, które pilnują, aby w danym miejscu programista miał dostęp wyłącznie do określonych usług. Wbrewpozorom, ma to sens, ponieważ ogranicza samowolę i wymusza stosowanie się do wytycznych twórcy systemu,zmniejszając ryzyko popełnienia błędu. Powagę sytuacji podkreśla fakt, że toczone były dyskusje czy nie usunąćglobal z PHP 6.0.Pomimo tego, czasami potrzebny jest taki publicznie dostępny rejestr najważniejszych obiektów, z którychmoglibyśmy korzystać. Skoro tak, to trzeba go napisać:

<?php

class Registry

{

private static $_objects = array();

public static function set($name, $value)

{

if(!is_object($value))

{

throw new RuntimeException('Trying to assign a non-object

value to '.$name.' in the registry.');

}

self::$_objects[$name] = $value;

} // end set();

public static function get($name)

{

if(!isset(self::$_objects[$name]))

{

Elementy statyczne 190

throw new OutOfBoundsException($name.' is not a valid registry

key.');

}

return self::$_objects[$name];

} // end get();

} // end Registry;

$config = new Config;

$config->addLoader(new FileConfigLoader('./config.ini.php'));

Registry::set('config', $config);

// gdzies indziej

$config = Registry::get('config');

Dzięki takiemu rejestrowi mogliśmy dodać raportowanie błędów przy pomocy wyjątków oraz nałożyć różneograniczenia. Nasza klasa Registry może przechowywać wyłącznie obiekty. Tablice oraz wartości skalarne nie sądozwolone.

ZakończenieElementy statyczne to potężne narzędzie, jednak należy z niego korzystać rozważnie. Ponieważ zachowują się onebardziej jak funkcje i zwykłe zmienne, znacznie trudniej je testować. Zawsze dwa razy zastanów się, czy danametoda lub pole naprawdę musi być statyczne, zanim dopiszesz do jego prototypu słowo static.

Metody magiczne 191

Metody magiczne

Metody magiczneW każdej klasie możemy utworzyć szereg metod, które będą traktowane w specjalny sposób przez PHP.Zwyczajowo nazywa się je metodami magicznymi, gdyż wywołuje je interpreter w odpowiedzi na różne zdarzenia, anie programista. Można je poznać po tym, że ich nazwy zaczynają się od dwóch podkreśleń, co oznacza, że dwietakie metody powinniśmy już kojarzyć. Są to __construct() oraz __destruct() wywoływaneautomatycznie przy tworzeniu i niszczeniu obiektu. To jednak tylko czubek góry lodowej.

Dostęp do pól obiektuGdy próbujemy odwołać się do nieistniejącego pola klasy, PHP zazwyczaj generuje komunikat E_NOTICE. Dziękimetodom __get() oraz __set() możemy zaprogramować własną akcję i wykorzystać to do swoich celów.

Metoda __get() przyjmuje dokładnie jeden argument będący nazwą pola, które próbujemy odczytać i powinna zwrócić wartość, którabędzie zaprezentowana użytkownikowi jako wartość tego pola.

Metoda __set() przyjmuje dokładnie dwa argumenty: nazwę pola oraz wartość, którą użytkownik próbuje do niego przypisać.

Przyjrzyjmy się naszemu stale ulepszanemu systemowi konfiguracji. Odwoływanie się do opcji konfiguracyjnychprzez $config->get('nazwa') jest odrobinę niewygodne. Dlatego zasymulujemy, że poszczególne opcje sądostępne jako pola klasy. Poniżej pokazany jest fragment klasy Config z nową metodą:

<?php

class Config implements Countable

{

private $_config = array();

private $_awaitingLoaders = array();

public function __get($name)

{

return $this->get($name);

} // end __get();

// pozostała część klasy

} // end Config;

$config = new Config;

$config->addLoader(new FileConfigLoader('./config.ini.php'));

echo $config->websiteTitle;

Metoda __get wywoływana jest w podświetlonej linijce, gdy próbujemy dostać się do nieistniejącego pola. PHPpobiera jej wynik i zwraca skryptowi jako jego wartość. Jednocześnie ponieważ nie chcemy, aby ktoś modyfikowałkonfigurację, nie udostępniamy metody __set() wywoływanej przy próbie przypisania nieistniejącemu polujakiejś wartości.

Metody magiczne 192

Wywoływanie metodMagiczne metody potrafią także przechwytywać wywołania metod. Zasada działania jest analogiczna - jeśli PHPstwierdzi, że nie istnieje metoda o podanej nazwie, ale w klasie zdefiniowana jest operacja __call(), zrzuca całąrobotę na nią. __call() dostaje dwa argumenty: nazwę metody oraz tablicę argumentów, z którymi programistapróbował ją wywołać. Wykorzystajmy to do stworzenia mechanizmu zdarzeń. Zasada działania jest bardzo prosta.Jak pamiętamy, klasy posiadają pewne zachowania reprezentowane poprzez metody. Chcielibyśmy mieć możliwośćrejestrowania dodatkowych czynności do wykonania, gdy zajdzie określone zdarzenie. Możemy dodatkoweczynności zapisać w postaci specjalnej klasy, której obiekt "wstrzykniemy" do właściwego obiektu. Znajdujący siętam kod sam zadba o to, aby czynności zostały wykonane.Opis ten brzmi nieco abstrakcyjnie, ale spójrzmy na przykładowe zastosowanie. Mamy klasę User, która m.in.potrafi dodawać nowych użytkowników do naszej aplikacji. Chcemy, aby dodanie użytkownika z poziomu paneluadministracyjnego zostało odnotowane w logach. Nic prostszego - piszemy klasę, w której implementujemy metodęonAdd(), która będzie zapisywać informację do logów, a utworzony od niej obiekt dodamy do obiektu User już wtrakcie działania skryptu, gdy zorientujemy się, że wyświetlamy panel administracyjny. Nie musimy modyfikowaćkodu oryginalnej klasy. Co więcej, gdybyśmy chcieli wykonać przy tej okazji jeszcze inne czynności, wystarczy żenapiszemy jeszcze więcej klas zdarzeń i podepniemy je pod User.Najpierw zapoznajmy się z interfejsem EventDispatcher. Musi go implementować każda klasa, która będzie chciałaobsługiwać zdarzenia. Przy okazji napiszemy też korzystającą z niego klasę User:

<?php

interface EventDispatcher

{

public function addEventHandler(EventHandler $handler);

}

class User implements EventDispatcher

{

private $_handlers = array();

public function addEventHandler(EventHandler $handler)

{

$this->_handlers[] = $handler;

} // end addEventHandler();

public function addUser($login, $password)

{

foreach($this->_handlers as $handler)

{

$handler->onAdd($login, $password);

}

echo 'Użytkownik '.$login.' został dodany<br/>';

} // end addUser();

} // end User;

Teraz pora na klasę EventHandler tworzącą podstawę dla wszystkich klas obsługi zdarzeń, jakie będziemy chcieli stworzyć. Zauważmy, że nie zawsze będziemy chcieli implementować wszystkie możliwe zdarzenia. Klasa User na razie obsługuje jedynie onAdd(), ale przecież w rzeczywistej aplikacji może tez posiadać zdarzenia onEdit(), onDelete() i inne. Po co tworzyć puste implementacje, kiedy można wszystko elegancko przechwycić poprzez

Metody magiczne 193

__call() i odpowiednio przetworzyć przed użyciem? Ponadto, przed wywołaniem zdarzenia warto sprawdzić,czy spełnione są wszystkie warunki. EventHandler będzie zawierać dodatkową metodę _checkConditions(),która musi zwrócić true, aby wyrazić zgodę na wykonanie zdarzenia.

<?php

class EventHandler

{

protected function _checkConditions()

{

return true;

} // end _checkConditions();

public function __call($name, $arguments)

{

if($this->_checkConditions())

{

$name = '_'.$name.'Event';

if(method_exists($this, $name))

{

$this->$name($arguments);

}

}

} // end __call();

} // end EventHandler;

Zwróćmy uwagę na linijkę 17 - oto odpowiedź, dlaczego nazwa pola klasy nie jest poprzedzona znakiem dolara. Wtym miejscu PHP odczytuje nazwę metody do wywołania wprost ze zmiennej, dzięki czemu możemy dynamiczniedecydować, co wykonać! Oczywiście warto wcześniej upewnić się, że w ogóle posiadamy odpowiednią metodę doobsługi zdarzenia i temu służy funkcja method_exists(). Zauważmy, że jeśli nie chcemy obsługiwać zdarzenia,nie grozi to nam teraz żadnymi konsekwencjami w postaci Fatal error. Wszystko przechwyci __call() i jeślistwierdzi, że nie potrafi zająć się zdarzeniem, po prostu je zignoruje.Poniżej pokazujemy przykładowe zastosowanie. Stworzymy sobie dwie klasy do obsługi zdarzeń. Pierwsza będzierejestrować fakt dodania nowego użytkownika, a druga pomoże nam w organizacji konkursu, gdzie pięciu losowowybranych nowo zarejestrowanych użytkowników wygrywa nagrodę.

<?php

class LoggingEvent extends EventHandler

{

protected function _onAddEvent($arguments)

{

$f = fopen('./logs/user_log.log', 'a');

fwrite($f, date('r').': '.$arguments[0].' zostal

zarejestrowany.');

fclose($f);

} // end _onAddEvent();

} // end LoggingEvent;

class CompetitionEvent extends EventHandler

Metody magiczne 194

{

protected $_winners = 0;

protected function _checkConditions()

{

// sprawdzmy, czy w ogole mozna jeszcze wygrywac.

$this->_winners = trim(file_get_contents('./winner_count.txt'));

return ($this->_winners < 5);

} // end _checkConditions();

protected function _onAddEvent($arguments)

{

// Losuj zwyciezce z prawdopodobienstwem 0,2%

if(rand(0, 1000) < 2)

{

echo $arguments[0].' wygrał!<br/>';

file_put_contents('./winner_count.txt', ++$this->_winners);

}

} // end _onAddEvent();

} // end CompetitionEvent;

$user = new User;

// Tworzymy normalnie, bez zadnych bajerow.

$user->addUser('wacek', 'foo');

// Teraz bedziemy rejestrowac fakt utworzenia w logach.

$user->addEventHandler(new LoggingEvent);

$user->addUser('franek', 'bar');

// A ten uzytkownik wezmie udzial w konkursie.

$user->addEventHandler(new CompetitionEvent);

$user->addUser('jacek', 'joe');

Oto potęga programowania obiektowego - przy odrobinie pomysłowości możemy rozszerzać istniejące klasy bezzmiany ani jednej linijki ich kodu. Wszystko odbywa się w pełni dynamicznie, a pomogła nam w tym magicznametoda __call(). W ramach ćwiczenia przyjrzyj się dokładnie kodowi do losowania zwycięzców. W pewnejsytuacji nie zadziała on prawidłowo. Czy potrafisz powiedzieć kiedy i dlaczego?

Od PHP 5.3.0 dostępna jest także analogiczna metoda __callStatic() do obsługi metod statycznych.

Metody magiczne 195

Klonowanie obiektówJak pamiętamy, operator przypisania kopiuje jedynie referencję do obiektu. Są jednak sytuacje, gdy chcielibyśmyfizycznie skopiować cały obiekt. Najprostsze wyjście to wykorzystanie operatora clone:

$kopia = clone $obiekt;

Jednak nasz obiekt może mieć otwarte różne zasoby (np. pliki) lub przechowywać referencje do innych obiektów.Standardowe klonowanie po prostu skopiuje same referencje, dlatego musimy mieć możliwość "poprawienia"sklonowanej kopii tak, by była ona w pełni samodzielna wtedy, gdy tego potrzebujemy. Tu do akcji wkraczamagiczna metoda __clone(). Gdy znajduje się ona w klasie, PHP wywołuje ją tuż po sklonowaniu obiektu, dziękiczemu ma ona możliwość wprowadzenia poprawek.Aby zademonstrować działanie wspomnianej funkcji, napiszemy implementację struktury danych zwanej kolejką.Struktury danych są jednym z filarów algorytmiki. Służą do przechowywania dowolnie dużej liczby informacjiograniczonej wyłącznie rozmiarami pamięci, oferując dostęp do nich w pewien ściśle określony sposób. Działaniekolejki jest podobne do kolejek w sklepie - klienci przychodzą z jednej strony, a z drugiej są obsługiwani. Winformatyce klientów zastępują dane dodawane z jednej strony, a zdejmowane z drugiej. Będziemy potrzebowaćdwóch klas: pierwszej do reprezentowania pojedynczego elementu kolejki oraz drugiej do zarządzania całą strukturą.Nas szczególnie będzie interesować proces klonowania. Klonując kolejkę, musimy także sklonować wszystkieobiekty reprezentujące jej elementy, dlatego musimy skorzystać z magicznej metody __clone().Zaczynamy od napisania klasy pojedynczego elementu kolejki. Będzie on przechowywać informację oraz referencjędo następnego elementu w kolejce. Następny element może mieć namiary na kolejny i tak dalej, aż do końca -tworzy się w ten sposób łańcuch elementów prowadzących od pierwszego do ostatniego czekającego elementu.

<?php

class QueueElement

{

private $_data;

private $_next = null;

public function __construct($data)

{

$this->_data = $data;

} // end __construct();

public function getData()

{

return $this->_data;

} // end getData();

public function setNext(QueueElement $next)

{

$this->_next = $next;

} // end setNext();

public function getNext()

{

Metody magiczne 196

return $this->_next;

} // end getNext();

} // end QueueElement;

Nasza klasa składa się praktycznie wyłącznie z getterów i setterów. Nie ma tutaj jeszcze operacji klonowania,ponieważ na tym poziomie nie mamy jeszcze nic do zrobienia. Dopiero główna klasa kolejki zawierać będzie całesterowanie. Znajdować się tu będą namiary na pierwszy i ostatni element kolejki tak, aby można było się do nichszybko dostać podczas odczytu lub zapisu.

<?php

class Queue

{

private $_first = null;

private $_last = null;

public function enqueue($data)

{

// Nic nie ma w kolejce.

if($this->_first === null)

{

$this->_first = $this->_last = new QueueElement($data);

}

else

// Coś jest w kolejce, doklejamy się do ostatniego.

{

$element = new QueueElement($data);

$this->_last->setNext($element);

$this->_last = $element;

}

} // end enqueue();

public function dequeue()

{

if($this->_first === null)

{

throw new UnderflowException('Nie można pobrać elementu z

pustej kolejki.');

}

$dequeued = $this->_first;

$this->_first = $this->_first->getNext();

if($this->_first === null)

{

$this->_last = null;

}

return $dequeued->getData();

Metody magiczne 197

} // end dequeue();

public function __clone()

{

// Zalozmy, ze sklonowana kolejka jest pusta

$top = $this->_first;

$this->_first = $this->_last = null;

// Przejedźmy się po dotychczasowej kolejce i skopiujmy

// wszystkie jej elementy

while($top != null)

{

$this->enqueue($top->getData());

$top = $top->getNext();

}

} // end __clone();

} // end Queue;

Przykładowe działanie:

<?php

$queue = new Queue;

$queue->enqueue(5);

$queue->enqueue(4);

$queue->enqueue(3);

$cloned = clone $queue;

$cloned->enqueue(2);

try

{

while(true)

{

echo $queue->dequeue().'<br/>';

}

}

catch(UnderflowException $exception)

{

// pusto

}

W przykładzie dodajemy do kolejki trzy elementy, a następnie ją klonujemy i dorzucamy do kopii jeszcze jedenelement. Na końcu wyświetlamy elementy oryginału. Gdyby podczas klonowania pozostawić niezmienioneelementy, wyświetliłyby nam się cztery liczby. Elementy kolejki są obiektami, zatem w skrypcie dostępne są jedyniereferencje do nich. Operacja enqueue() sprawiłaby, że czwarta liczba zostałaby doczepiona do wspólnegoelementu, a co gorsza - oryginał wcale by o tym nie wiedział! Mogłoby to doprowadzić do poważnych błędów wdziałaniu obu list. Zakomentuj operację __clone() i prześledź wszystko samodzielnie.

Metody magiczne 198

Przy implementowaniu własnej wersji __clone() musimy pamiętać, że operuje ona na już sklonowanymobiekcie, a nie na oryginale. Innymi słowy, PHP najpierw wykonuje dokładną kopię całego obiektu, a dopieropóźniej wykonuje na niej __clone(), by pozwolić jej pozmieniać te rzeczy, które powinny być zmodyfikowane.

PoradaNie musisz implementować struktur danych samodzielnie. Oprócz tablic, począwszy od wersji PHP 5.3.0 w bibliotece standardowej SPLzawarte są implementacje najważniejszych struktur: kolejek, stosów, list dwukierunkowych, stert oraz kolejek priorytetowych.

Serializacja obiektówPisząc w PHP nie musimy przejmować się, jak interpreter reprezentuje tablice i obiekty w pamięci komputera.Jednak zdarza się, że chcielibyśmy zapisać je w całości do pliku lub przesłać komuś przez sieć. Za przetłumaczeniezłożonego typu do postaci tekstowej i złożenie z niej z powrotem oryginału po drugiej stronie odpowiada tzw.serializacja. Spróbujmy zobaczyć, jak ten proces przebiega:

<?php

$array = array(

'foo' => 'foo',

'bar' => 'bar',

'joe' => 'joe'

);

// Sprawdźmy, jak wygląda tablica normalnie

var_dump($array);

// Zserializujmy ją

$serialized = serialize($array);

echo $serialized.'<br/>';

// A teraz przywracamy oryginał

var_dump(unserialize($serialized));

Wynikiem działania skryptu powinno być:

array

'foo' => string 'foo' (length=3)

'bar' => string 'bar' (length=3)

'joe' => string 'joe' (length=3)

a:3:{s:3:"foo";s:3:"foo";s:3:"bar";s:3:"bar";s:3:"joe";s:3:"joe";}

array

'foo' => string 'foo' (length=3)

'bar' => string 'bar' (length=3)

'joe' => string 'joe' (length=3)

Funkcja serialize() przekonwertowała tablicę na specjalnie sformatowany ciąg tekstowy, w którym zawarte sąinformacje o wszystkich jej elementach. Gdy wprowadzimy go do funkcji unserialize(), PHP z powrotemodtworzy naszą tablicę. Serializować można także obiekty, a konwersji podlegają wszystkie pola. Nie zawsze jest tozjawisko pożądane. Jeśli nasz obiekt zawiera uchwyt do pliku, zostanie on zgubiony podczas przesyłania, dlategodobrze by było, gdyby zamiast niego w zserializowanym tekście znalazła się po prostu ścieżka do niego. Podczasdeserializacji musielibyśmy otworzyć go ponownie.

Metody magiczne 199

Niestandardowe reguły serializacji takie, jak powyżej opisana, mogą zostać zaprogramowane dzięki magicznymmetodom __sleep() oraz __wakeup(), które umieszczamy w naszej klasie. Nie pobierają one żadnychargumentów. Pierwsza z nich powinna zwrócić tablicę z nazwami pól, które mają podlegać serializacji, zaś w drugiejmożemy umieścić cokolwiek. Zobaczmy zatem, jak przesłać tekstem obiekt reprezentujący połączenie z plikiem:

<?php

class File

{

private $_handle;

private $_name;

public function __construct($filename)

{

$this->_name = $filename;

$this->_handle = fopen($filename, 'r');

} // end __construct();

public function __destruct()

{

fclose($this->_handle);

} // end __destruct();

public function getFilename()

{

return $this->_name;

} // end getFilename();

public function read($bytes)

{

return fread($this->_handle, (int)$bytes);

} // end read();

public function __sleep()

{

return array('_name');

} // end __sleep();

public function __wakeup()

{

echo 'Odtwarzam połączenie z plikiem...<br/>';

$this->_handle = fopen($this->_name, 'r');

} // end __wakeup();

} // end File;

W metodzie magicznej __sleep() mówimy: serializacja pola $_handle nie ma sensu, wystarczy nam nazwapliku na odtworzenie obiektu. Odtworzenie w __wakeup() polega po prostu na ponownym otwarciu pliku.Możemy teraz przetestować całość:

Metody magiczne 200

<?php

require('./File.php');

$file = new File('plik.txt');

echo $file->read(100).'<br/>';

$file2 = unserialize(serialize($file));

echo $file2->read(100).'<br/>';

Pierwszy, oryginalny obiekt tworzymy w tradycyjny sposób i odczytujemy z pliku 100 bajtów. Następnie całośćserializujemy i odserializowujemy, by spróbować coś odczytać poprzez "nowy" obiekt. Po uruchomieniuzauważymy, że oba odczyty się udały. PHP wywołał __wakeup() podczas deserializacji i ponownie otworzyłplik, a świadczy o tym komunikat "Odtwarzam połączenie z plikiem...".Jest kilka zastosowań dla serializacji. Może posłużyć jako szybki cache złożonych struktur. Zamiast mozolniebudować dużą kolekcję obiektów przy każdym wejściu na stronę, możemy zbudować ją raz, zserializować i zapisaćdo pliku, a przy kolejnych żądaniach HTTP ładować z powrotem. Jednak musimy tutaj uważać. Prostota serializacjiwielu programistom zupełnie przesłania kwestię bezpieczeństwa. Przede wszystkim nie wolno zserializowanychdanych pokazywać użytkownikom naszej witryny w sposób jawny (nawet w ciastkach!). Spróbuj wyświetlić sobiezserializowaną postać obiektu klasy File. Zauważysz, że zapisana jest tam informacja o tym, do jakiej klasy obiektnależy. Jeśli będziesz bezmyślnie odserializowywać wszystko, co przyjdzie do Ciebie z przeglądarki, sprytny hackermoże tak spreparować ciąg, by powstał jakiś kluczowy dla aplikacji obiekt i wykorzystać go do swoich niecnychcelów. Oto przykład: mamy klasę Mailer służącą do wysyłania e-maili i wpadliśmy na pomysł, by dane ozalogowanych użytkownikach trzymać w ciastkach jako zserializowane tablice. Agresor jednak modyfikuje takieciastko tak, aby po odczytaniu przez skrypt tworzyło obiekt klasy Mailer i rozesłało spam w Twoim imieniu.Musisz przyznać, że nie jest to ciekawa perspektywa. Dlatego trzy razy pomyśl, zanim zserializowane daneudostępnisz komukolwiek poza serwerem.

Rozszerzony dostęp do pól obiektówRozdział ten zaczęliśmy od omówienia metod __get() i __set(), pisząc prosty system konfiguracyjny.Możemy odczytywać i zapisywać nowe wartości do różnych opcji, ale nie mamy jak sprawdzić czy konkretna opcjaistnieje. Uzupełnimy teraz to niedopatrzenie dzięki dwóm kolejnym metodom magicznym: __isset() oraz__unset(). Pierwsza z nich wywoływana jest, gdy na danym polu zadziałamy komendą isset():

if(isset($config->property))

{

...

}

Metoda __unset() wywoływana jest podczas wykonywania komendy unset(), czyli próby usunięcia pola zobiektu:

unset($config->property);

Obie pobierają jeden argument będący nazwą pola. Dodajmy zatem do naszej klasy Config metodę __isset().

public function __isset($name)

{

return isset($this->_config[$name]);

} // end __isset();

Metody magiczne 201

Jak widać, jest to bardzo proste. Jedyne, co robimy, to każemy sprawdzić czy opcja istnieje w naszej tablicy zopcjami.

Konwersja do ciągu tekstowegoW PHP każdy obiekt można przekonwertować na ciąg tekstowy, a dzięki magicznym metodom możemyoprogramować tę operację. Na tapetę weźmiemy naszą klasę File. Nie chcemy ciągle pisać skomplikowanychodwołań w stylu $file->getFilename() za każdym razem, gdy próbujemy wyświetlić nazwę pliku. Dlategododamy metodę __toString(), która pozwoli nam to nieco uprościć. Dodaj poniższy kod do ciała wspomnianejklasy:

public function __toString()

{

return $this->_name;

} // end __toString();

Od metody tej oczekujemy, że wygeneruje tekstową reprezentację obiektu. W naszym przypadku ma to być poprostu nazwa pliku. Dzięki temu możemy teraz wyświetlić ją dużo prościej:

<?php

require('./File.php');

$file = new File('plik.txt');

echo 'Otworzyłem plik '.$file.'<br/>';

ZakończenieMetody magiczne są dobrym mechanizmem do reagowania na różne zdarzenia, które wykonujemy na obiektach, aktóre nie są związane z wywoływaniem metod. Jednak pamiętaj, że wszystko wymaga umiaru. Nie stosuj ich dododawania obiektom kompletnie nieintuicyjnych zachowań, gdyż inni programiści będą mieć później sporeproblemy ze zrozumieniem Twojego kodu. Metody magiczne nie są też dobrze wspierane przez zaawansowaneedytory z dynamicznym podpowiadaniem składni. Jeśli jakaś metoda jest normalnie zaprogramowana w klasie jakometoda, edytor pokaże ją na podglądzie. Jeśli będzie ona wybierana przez skomplikowany algorytm ukryty w__call(), część programistów nawet nie zauważy jej istnienia, a ich praca nad kodem będzie żmudniejsza.

Iteratory 202

Iteratory

IteratoryNa samym początku naszej nauki poznaliśmy pętlę foreach, która przechodzi po wszystkich elementach tablicy.Okazuje się, że możemy ją stosować także do iterowania po obiektach, a co więcej - możemy kontrolować, cowłaściwie nasz obiekt będzie wtedy generować! Obiekty z taką spersonalizowaną obsługą pętli foreach nazwiemyiteratorami, a omówienie zagadnienia rozpoczniemy od pokazania paru przykładów ze znanej już nam bibliotekiStandard PHP Library, która posiada całkiem pokaźną kolekcję domyślnych iteratorów.

Iteratory z biblioteki SPLPrzed zabraniem się za programowanie obiektowe omawialiśmy m.in. funkcje dostępu do plików. Korzystanie z nichbyło średnio wygodne. Przykładowo, aby odczytać zawartość katalogu, musieliśmy manipulować dużą ilościąfunkcji, a na dokładkę otrzymywaliśmy jedynie nazwę, nie wiedząc nawet czy mamy do czynienia z plikiem czy zkatalogiem. Iteratory doskonale nadają się do operacji na systemie plików. W SPL-u mamy klasęDirectoryIterator, która pozwoli nam pobrać zawartość katalogu:

<?php

try

{

$dir = new DirectoryIterator('./katalog/');

foreach($dir as $file)

{

// Pomiń pozycje "." oraz ".."

if($file->isDot())

{

continue;

}

if($file->isDir())

{

echo 'Katalog '.$file.'<br/>';

}

else

{

echo 'Plik '.$file.'<br/>';

}

}

}

catch(UnexpectedValueException $exception)

{

echo 'Błąd: '.$exception->getMessage();

}

Teraz iteracja po katalogach jest wyjątkowo prosta, a na dodatek zyskujemy czytelne, obiektowe API. Gdybykatalog nie istniał, zamiast dziwacznych komunikatów dostaniemy normalny wyjątek, który możemy przechwycić ioprogramować według naszych potrzeb.

Iteratory 203

Zastanówmy się teraz, jak wyświetlić katalogi wraz z zawartością ich podkatalogów, czyli innymi słowy - kompletnedrzewo systemu plików. Jest to możliwe dzięki dwóm kolejnym iteratorom: RecursiveDirectoryIteratororaz RecursiveIteratorIterator. Powinny być one używane razem - pierwszy bowiem sam z siebie dopodkatalogów nie wejdzie, ale udostępnia metody getChildren() i hasChildren(), które sąwykorzystywane przez drugi. RecursiveIteratorIterator służy do rekurencyjnego przechodzenia podowolnej strukturze, która implementuje powyższe dwie metody (także naszej własnej). My jednak wróćmy donaszych katalogów:

<?php

$dirIterator = new RecursiveIteratorIterator(new

RecursiveDirectoryIterator('./s3/'),

RecursiveIteratorIterator::SELF_FIRST);

foreach($dirIterator as $file)

{

if($file->isDir())

{

echo str_repeat('---', $dirIterator->getDepth()).' Katalog

'.$file.'<br/>';

}

else

{

echo str_repeat('---', $dirIterator->getDepth()).' Plik

'.$file.'<br/>';

}

}

Konstruktor klasy RecursiveIteratorIterator pobiera jako pierwszy argument obiekt iteratora, po którymnależy przejść rekurencyjnie. W naszym przypadku jest to iterator do przechodzenia po katalogach. W drugim,opcjonalnym parametrze możemy ustawić sposób przechodzenia przy pomocy stałych klasowych:1. RecursiveIteratorIterator::LEAVES_ONLY - wyświetli jedynie liście (pliki). Wartość domyślna.2. RecursiveIteratorIterator::SELF_FIRST - najpierw zwróć katalog, a później to, co się w nim

znajduje.3. RecursiveIteratorIterator::CHILD_FIRST - najpierw zwróć zawartość katalogu, a później sam

katalog.Iterator produkuje nam obiekty poszczególnych plików, lecz nie ma w nich informacji o ich głębokości w strukturzekatalogowej. Tę informację uzyskujemy bezpośrednio z iteratora poprzez metodę getDepth(), dzięki czemuwiemy, ile pauz należy wyświetlić przed nazwą elementu, aby go odpowiednio wciąć.Powyższy przykład pokazał jeszcze jedną ciekawą właściwość iteratorów SPL, a mianowicie możliwość ichkomponowania w czasie wykonywania. Zauważmy, że nie mamy jednego iteratora do rekurencyjnego przechodzeniapo katalogach, ale mamy dwa mniejsze - jeden oferujący rekurencję, drugi - wędrówki po systemie plików. Dopierołącząc je ze sobą uzyskujemy to, co chcemy. Gdy przejdziemy do późniejszych rozdziałów podręcznika zauważymy,że takie postępowanie ma swoją fachową nazwę dekorator. Oto inne zastosowanie kompozycji iteratorów. Mamytablicę z sześcioma elementami, ale chcemy wyświetlić jedynie elementy od 2 do 4, przy czym nie znamy ichindeksów (pętla for odpada). Wykorzystajmy zatem iterator tablicowy ArrayIterator i połączmy go zLimitIterator, który doda odpowiednie limity:

<?php

$array = new ArrayIterator(

Iteratory 204

array('jabłko', 'banan', 'gruszka', 'wisienka', 'czereśnia',

'truskawka')

);

echo '<ul>';

foreach(new LimitIterator($array, 2, 1) as $item)

{

echo '<li>'.$item.'</li>';

}

echo '</ul>';

Chociaż tablice współpracują z foreach, ale nie współpracują z innymi iteratorami, dlatego w tym przypadkumusimy ją opakować w specjalny obiekt klasy ArrayIterator. Domyślnie pokazałby on nam wszystkie owoce,ale my chcemy dostać jedynie "gruszkę" oraz "wisienkę". Tu do akcji wkracza LimitIterator, któremumówimy, że chcemy dostać dwa elementy (drugi argument) począwszy od drugiego (trzeci argument - liczone odzera!). Uruchom ten przykład i spróbuj pobawić się ustawieniami, aby zobaczyć, co się stanie.

Interfejs IteratorAggregateGdy wiemy już, ile można osiągnąć dzięki iteratorom, pora nauczyć się samodzielnie je tworzyć. Sprowadza się todo zaimplementowania w naszej klasie jednego z dwóch specjalnych interfejsów. Zaczniemy od omówieniaprostszego IteratorAggregate, który wymaga dodania dokładnie jednej metody: getIterator(). Jejzadaniem jest... utworzenie iteratora, który zajmie się procesem iteracji w imieniu naszej klasy. Przypomnijmy sobienaszą klasę Config, która opcje konfiguracyjne przechowuje w tablicy. Dla celów debugowych chcielibyśmy miećmożliwość prostego wyświetlenia wszystkich opcji. Przypomnijmy sobie strukturę klasy:

<?php

class Config implements Countable

{

private $_config = array();

private $_awaitingLoaders = array();

// metody klasy

} // end Config;

Opcje przechowywane są w polu $_config, dlatego to po nim powinniśmy iterować. Dodajmy do klasy interfejsIteratorAggregate oraz zaimplementujmy niezbędną metodę:

<?php

class Config implements Countable, IteratorAggregate

{

private $_config = array();

private $_awaitingLoaders = array();

public function getIterator()

{

return new ArrayIterator($this->_config);

} // end getIterator();

Iteratory 205

// metody klasy

} // end Config;

Ponieważ lista opcji jest tablicą, w naszym imieniu iterować będzie po niej ArrayIterator, który tworzymy wmomencie wywołania getIterator(). Możemy teraz wyświetlić wszystkie opcje:

<?php

require('./Config.php');

$config = new Config;

$config->addLoader(new FileConfigLoader('./config.ini.php'));

echo '<ul>';

foreach($config as $name => $value)

{

echo '<li>'.$name.': '.$value.'</li>';

}

echo '</ul>';

Interfejs IteratorInterfejs IteratorAggregate jest przydatny w najprostszych sytuacjach, gdy mamy pasujący iterator, alepojawia się problem, skąd taki wziąć gdy żaden nam nie pasuje? Pełną kontrolę nad przebiegiem procesu iteracjizapewnia dopiero interfejs Iterator, który wymaga dodania do naszej klasy pięciu metod:1. reset() - ustawia kursor na początku kolekcji.2. valid() - sprawdza czy kursor znajduje się we właściwym położeniu (np. czy nie wyszedł poza rozmiar

tablicy).3. next() - przesuwa kursor na kolejną pozycję.4. key() - zwraca klucz aktualnej pozycji.5. current() - zwraca wartość aktualnej pozycji.Aby lepiej zrozumieć, jak PHP z nich korzysta, spróbujmy przetłumaczyć sobie wywołanie pętli foreach naodpowiadające jej wywołanie pętli while:

<?php

// Tworzymy dowolny iterator

$iterator = new SomeIterator;

// Foreach

foreach($iterator as $name => $value)

{

...

}

// Równoważny mu while

$iterator->reset();

while($iterator->valid())

{

$name = $iterator->key();

$value = $iterator->value();

Iteratory 206

...

$iterator->next();

}

Wyposażeni w taką informację spróbujmy napisać iterator zliczający. Będzie on odliczać od podanej liczby zadanąilość razy. Jako wartość będzie zwracana aktualna liczba, a jako klucz - numer iteracji.

<?php

class CountingIterator implements Iterator

{

private $_start;

private $_end;

private $_key;

private $_value;

public function __construct($start, $offset)

{

$this->_start = $start;

$this->_offset = $offset;

} // end __construct();

public function reset()

{

$this->_key = 0;

$this->_value = $this->_start;

} // end reset();

public function valid()

{

return $this->_key < $this->_offset;

} // end valid();

public function next()

{

$this->_key++;

$this->_value++;

} // end next();

public function key()

{

return $this->_key;

} // end key();

public function current()

{

return $this->_value;

Iteratory 207

} // end current();

} // end CountingIterator;

echo '<ul>';

foreach(new CountingIterator(5, 10) as $idx => $value)

{

echo '<li>'.$idx.'. '.$value.'</li>';

}

echo '</ul>';

Powyższy przykład wyświetli dziesięć kolejnych liczb, począwszy od wartości 5. Zwróćmy uwagę, że w ten sposóbzmieniliśmy foreach w pętlę for. Oczywiście w prawdziwych aplikacjach WWW nie będzie to mieć zbyt wielkiegosensu, ale dobrze pokazuje istotę procesu iteracji. W ramach ćwiczenia spróbuj zaimplementować interfejsIterator w miejsce IteratorAggregate w klasie Config. Będziesz musiał iterować po tablicy asocjacyjnej,dlatego wszystkie metody interfejsu będą zwykłymi nakładkami na analogiczne funkcje obsługi tablic: reset(),next(), key() oraz current(). Jeśli nie pamiętasz ich działania, posłuż się dokumentacją i spróbuj rozwiązaćproblem, jak w metodzie valid() sprawdzić, że dotarliśmy do końca tablicy.

ZakończenieIteratory są bardzo wygodnym elementem PHP pozwalającym ukryć przed programistą wiele zbędnych szczegółówtechnicznych. Jednak i tutaj należy korzystać z nich z umiarem i nie stosować, gdy nie ma ku temu żadnegologicznego uzasadnienia.

Automatyczne ładowanie

Automatyczne ładowanieWraz z rozbudową projektu pojawia się w nim coraz więcej plików i bibliotek, które musimy dołączać. W PHP 4 iwcześniejszych wersjach początek większych skryptów prezentował się mniej więcej następująco:

<?php

define('DIR_INC', './includes/');

require(DIR_INC.'config.php');

require(DIR_INC.'base.php');

require(DIR_INC.'validation.php');

require(DIR_INC.'forms.php');

require(DIR_INC.'database.php');

require(DIR_INC.'i18n.php');

require(DIR_INC.'controller.php');

require(DIR_INC.'helpers.php');

// itd.

Oczywiście nie trzeba wielkiej filozofii, by zauważyć, że zarządzanie taką listą jest niezwykle trudne, a przy tym niewydajne. Nie wszystkie podstrony muszą przecież korzystać z całej funkcjonalności oferowanej przez silnik strony, w związku z czym ładowanie wszystkiego to niepotrzebna strata czasu. Remedium na te bolączki był

Automatyczne ładowanie 208

wprowadzony w PHP 5.0.0 mechanizm automatycznego ładowania klas. Jego działanie jest bardzo proste. Wmomencie gdy próbujemy utworzyć obiekt nieznanej klasy, PHP wywołuje specjalną, napisaną przez nas funkcję,której zadaniem jest odnalezienie i załadowanie pliku zawierającego tę klasę. Dopiero gdy nie zostanie onznaleziony, generowany jest błąd.Automatyczne ładowanie ułatwia życie programiście. Nie musimy już zastanawiać się czy dana klasa zostałazaładowana czy jeszcze nie, a gdyby nie - szukać miejsca, w którym można ją bezpiecznie załadować. Z naszejperspektywy kod wygląda tak, jakby PHP miał już "z definicji" załadowane wszystkie możliwe klasy. Co więcej,ponieważ ładujemy tylko to, czego faktycznie potrzebujemy, możemy często zyskać na wydajności, mimo iżmusimy przy okazji wykonać operację tłumaczenia nazwy klasy na ścieżkę do pliku.

Automatyczne ładowanie - PHP 5.0Pierwotny mechanizm automatycznego ładowania wprowadzony w PHP 5.0 był prosty, lecz dość prymitywny.Wszystko, co musieliśmy zrobić, to utworzyć magiczną funkcję o nazwie __autoload(), która jako argumentpobierała nazwę klasy do załadowania. Utwórzmy najpierw dwa pliki testowe:Klasa1.php:

<?php

class Klasa1

{

public function __construct()

{

echo 'Tworzę obiekt klasy Klasa1<br/>';

} // end __construct();

} // end Klasa1;

Klasa2.php:

<?php

class Klasa2

{

public function __construct()

{

echo 'Tworzę obiekt klasy Klasa2<br/>';

} // end __construct();

} // end Klasa2;

Nazwa pliku jednoznacznie mówi nam, jaka klasa się w nim znajduje, zatem pliki mogą zawierać tylko po jednejklasie bądź interfejsie. Spróbujmy teraz napisać skrypt, który będzie potrafił ładować je automatycznie:

<?php

function __autoload($className)

{

echo 'Ładuję klasę '.$className.'<br/>';

require('./'.$className.'.php');

} // end __autoload();

// Możemy od razu korzystać z klas

$obiekt1 = new Klasa1;

$obiekt2 = new Klasa2;

Automatyczne ładowanie 209

$obiekt3 = new Klasa1;

Wynikiem działania naszego skryptu powinno być:

Ładuję klasę Klasa1

Tworzę obiekt klasy Klasa1

Ładuję klasę Klasa2

Tworzę obiekt klasy Klasa2

Tworzę obiekt klasy Klasa1

W linijce 9 od razu próbujemy utworzyć obiekt klasy Klasa1 bez jej uprzedniego ładowania. PHP szybko odkrywa,że nie ma jej jeszcze w swojej pamięci, dlatego wywołuje funkcję __autoload(), prosząc ją o jej odnalezienie.Funkcja wyświetla na ekranie informację, że coś ładuje, a następnie dokleja do nazwy klasy rozszerzenie i ścieżkę, acałość przesyła do instrukcji require. Gdy dwie linijki niżej próbujemy ponownie utworzyć obiekt tej samej klasy,PHP wie już, jak ona działa i nie musi nic doładowywać, tylko od razu przystępuje do tworzenia obiektu.

Automatyczne ładowanie - SPLFunkcja __autoload() ma jedną, ale bardzo poważną wadę. Wyobraźmy sobie sytuację, w której korzystamy zdwóch bibliotek rozwijanych przez niezależne ekipy programistów. Każda biblioteka posiada swój własnymechanizm ładowania klas, a my także w naszym projekcie chcemy mieć jeszcze jeden. Występuje konflikt,ponieważ w pamięci nie może być trzech funkcji __autoload(), lecz co najwyżej jedna. Pojawia się konfliktnazw między bibliotekami.Problem szybko dostrzegli również twórcy PHP i już w wersji 5.1 w znanym już nam pakiecie Standard PHPLibrary pojawił się nowy mechanizm automatycznego ładowania, który umożliwia równoczesną pracę kilkuładowarek. Jeśli zdecydujemy się na jego wykorzystanie, PHP automatycznie wyłączy funkcję __autoload().Wykorzystajmy te same przykładowe klasy, zmieniając jedynie korzystający z nich skrypt:

<?php

function myClassLoader($className)

{

echo 'Ładuję klasę '.$className.'<br/>';

require('./'.$className.'.php');

// Zwróć "true", informując, że klasa została znaleziona.

return true;

} // end myClassLoader();

spl_autoload_register('myClassLoader');

// Możemy od razu korzystać z klas

$obiekt1 = new Klasa1;

$obiekt2 = new Klasa2;

$obiekt3 = new Klasa1;

Kod powinien dać dokładnie taki sam wynik, jak poprzednio, jednak w jego budowie jest zasadnicza różnica. Naszafunkcja ładująca może mieć teraz dowolną nazwę, jednak przez to musimy poinformować PHP, że ma jąwykorzystywać. Służy do tego funkcja spl_autoload_register(), do której podajemy nazwę funkcji jakoargument.

Automatyczne ładowanie 210

Jak wspomnieliśmy, w projekcie może być użyte kilka ładowarek. Razem tworzą one stos. Zauważmy, że naszafunkcja zwraca wartość true. To informacja dla PHP, że plik został znaleziony. Jeśli nasza funkcja zwróci co innego,PHP wywoła kolejny autoloader i tak aż do skutku lub wyczerpania zapasu. Założenie jest takie, że funkcja ładującapowinna po nazwie klasy rozpoznać czy potrafi ją załadować i jeśli nie, natychmiast zakończyć działanie, byprzekazać sterowanie innej. Możemy dodać do naszych klas unikalne prefiksy, np. MojProjekt i funkcjąstrpos() sprawdzać czy są one obecne:

<?php

function myClassLoader($className)

{

if(strpos($className, 'MojProjekt') !== 0)

{

// Te klase obsluguje jakas inna ladowarka - nie ma prefiksu!

return false;

}

echo 'Ładuję klasę '.$className.'<br/>';

require('./'.$className.'.php');

// Zwróć "true", informując, że klasa została znaleziona.

return true;

} // end myClassLoader();

Teraz zadanie dla Ciebie. Dodaj do klas oraz ich plików prefiks MojProjekt, utwórz analogiczne klasy z prefiksemInnyProjekt i napisz dla nich drugą ładowarkę. Zarejestruj ją obok pierwszej, użyj nowych klas i obserwuj działanieskryptu.Jeżeli jakaś ładowarka nagle nam przeszkadza, możemy ją w każdej chwili usunąć funkcjąspl_autoload_unregister().

PoradaZalecane jest obecnie używanie mechanizmu automatycznego ładowania z pakietu SPL.

Ładowarki obiektoweŁadowarki SPL nie muszą być funkcjami, ale także metodami klas. Umożliwia to budowę całkiem przyjemnego iłatwego w konfiguracji interfejsu wielokrotnego użytku do ładowania klas. Zamienimy teraz nasze ładowarki zpoprzedniego przykładu na takie właśnie klasy, w których będziemy mogli ustawić ścieżki dostępu orazrozpoznawane prefiksy.

<?php

class Loader

{

private $_prefix;

private $_directory;

private $_valid = false;

public function __construct($prefix, $directory)

{

$this->_prefix = (string)$prefix;

if(!is_dir($directory))

Automatyczne ładowanie 211

{

throw new RuntimeException($directory.' is not a valid

directory path.');

}

$this->_directory = $directory;

$this->_valid = true;

} // end __construct();

public function autoload($className)

{

if(strpos($className, $this->_prefix) !== 0)

{

return false;

}

require($this->_directory.$className.'.php');

return true;

} // end autoload();

public function register()

{

if($this->_valid)

{

spl_autoload_register(array($this, 'autoload'));

$this->_valid = false; // aby się nie dało drugi raz

zarejestrować

}

} // end register();

} // end Loader;

$firstLoader = new Loader('MojProjekt', './MojProjekt/');

$secondLoader = new Loader('InnyProjekt', './InnyProjekt/');

$firstLoader->register();

$secondLoader->register();

// Tutaj reszta skryptu

Cały proces rejestracji ukryty jest teraz wewnątrz klasy ładowarki. Argumenty takie, jak prefiks czy katalog,przechowywane są w obiekcie, który dzięki metodzie register() potrafi się sam zarejestrować jako ładowarkaSPL, wywołując w linii 33 funkcję spl_autoload_register(). Rejestrowanie metod realizujemy poprzezprzekazanie jako argument tablicy z obiektem oraz nazwą metody.Nasza ładowarka ma charakter uniwersalny - za pomocą tego samego kodu zainicjowanego różnymi parametramimożemy obsługiwać teraz dwie różne biblioteki znajdujące się w różnych katalogach. Spróbuj przy jej pomocyzaładować klasy z poprzedniego rozdziału.

Automatyczne ładowanie 212

Standard nazewnictwa klasW dużych projektach niezbędne jest przestrzeganie pewnych konwencji, aby nie pogubić się we własnym kodzie.Jedna z nich może dotyczyć sposobu nadawania klasom nazwy. Także i w tym podręczniku stosowaliśmy jednolitąkonwencję cechującą się używaniem angielskich słów oraz rozpoczynaniem każdego z nich dużą literą, np.FormInput czy ConfigLoader. Na nasze potrzeby wystarczała ona dotąd w zupełności, ale zauważmy, że klasaConfigLoader może być użyta w wielu różnych bibliotekach. Gdybyśmy chcieli użyć ich jednocześnie, wystąpiłbykonflikt nazw. Co więcej, nie jest to jedyna konwencja i inni programiści mogą swoje klasy nazywać inaczej.Utrudnia to tworzenie przenośnych ładowarek, a także wprowadza zamieszanie w kodzie, gdy część klas nazywa sięnp. Foo_Bar_Joe, drugą część FooBarJoe, a trzecią jeszcze inaczej.Problem dostrzegli niedawno programiści pracujący przy największych projektach PHP i powołali stowarzyszenie,którego celem było rozwiązanie tej sytuacji i wypracowanie jednolitego standardu nazewnictwa. Po przyjęciu, miałon być stopniowo wdrożony we wszystkich projektach. Zapoznamy się teraz z jego założeniami i od tego momentubędziemy go konsekwentnie stosować w dalszej części podręcznika.

Uwaga!Proponowany standard uwzględnia także tzw. przestrzenie nazw, które rozwiązują problem kolizji nazw i pojawiły się w 2009 roku wraz zwydaniem PHP 5.3.0. Zostaną one omówione w dalszej części podręcznika.

Zgodnie z założeniami nowego standardu, nazwy klas składają się z co najmniej dwóch członów oddzielonych odsiebie znakiem _ lub \ (separator przestrzeni nazw, które poznamy już wkrótce). Nazwa każdego członu pisana jest wkonwencji FooBar. Pierwszy człon powinien zawsze określać tzw. dostawcę, czyli np. nazwę projektu lub grupyprogramistycznej odpowiedzialnej za jego rozwój. Przypuśćmy, że tworzymy projekt o nazwie "Ala". Wtedy naszeklasy powinny dostać nazwy:1.1. Ala_Klasa12.2. Ala_Klasa23.3. Ala_Klasa34.4. Ala_JakisElement_Klasa15.5. Ala_JakisElement_Klasa26.6. itd.Podkreślenia pełnią bardzo ważną rolę, ponieważ podczas ładowania są zamieniane na separator katalogów wścieżce do pliku. Oznacza to, że klasa Ala_Klasa1 będzie znajdować się w pliku Ala/Klasa1.php, aAla_JakisElement_Klasa1 - w Ala/JakisElement/Klasa1.php. Jak widać, możemy w ten sposób naszepliki grupować w podkatalogi. Ładowarka po nazwie dostawcy może rozpoznać czy dana klasa należy do niej i wjakim katalogu powinna szukać plików.Pełen tekst standardu można znaleźć na liście dyskusyjnej Grupy Standaryzacyjnej PHP [1] (j. angielski).

Uniwersalne ładowarkiZauważmy, że standard uwzględnia sytuację, w której korzystamy z kilku różnych bibliotek. Jeśli ich konwencjanazewnictwa jest identyczna, szybko dochodzimy do wniosku, że można by stworzyć jedną, uniwersalną ładowarkępodobną do tej, którą napisaliśmy wyżej, ale uwzględniającą potrzeby wszystkich projektów. Takie ładowarkifaktycznie powstają i najczęściej wchodzą w skład różnych projektów.Oprócz tego, także sama grupa standaryzacyjna PHP rozpoczęła opracowywanie domyślnej ładowarki, która wprzyszłości ma szansę stać się częścią pakietu SPL, a co za tym idzie - być obecna w każdym interpreterze PHP.Obecnie dostępna jej jest implementacja w PHP [2], a także rozszerzenie w języku C do samodzielnej kompilacjijako moduł PHP. Jej użycie jest bardzo proste:

Automatyczne ładowanie 213

<?php

$classLoader = new SplClassLoader('Ala', '/ścieżka/do/projektu/Ala');

$classLoader->register();

ZakończenieAutomatyczne ładowanie klas jest powszechnie spotykane w niemal wszystkich nowoczesnych projektach ibibliotekach programistycznych napisanych w PHP. Rozwiązuje problem zależności między poszczególnymiklasami, zapanowania nad ich ładowaniem, a dzięki dodatkowej standaryzacji może być także remedium nakonflikty nazw. Pamiętaj, że automatyczne ładowanie działa wyłącznie dla klas oraz interfejsów. Nie da się przyjego pomocy ładować zwyczajnych funkcji, tak więc decydując się na jego użycie, najlepiej będzie w całości przejśćna programowanie obiektowe i zrezygnować z ich stosowania.To był już ostatni temat związany bezpośrednio z programowaniem obiektowym w języku PHP. W rozdziale tympoznaliśmy po kolei wszystkie elementy tego paradygmatu programowania dostępne w języku, od podstawowychpojęć takich, jak klasy, poprzez dziedziczenie aż do iteratorów i automatycznego ładowania. Jest to duża dawkamateriału, i to niezbyt łatwego. O programowaniu obiektowym powstają grube książki, a jego pełną potęgę możnapoznać dopiero dzięki praktyce. Nie zrażaj się, jeśli zapomniałeś niektórych rzeczy lub wciąż nie do końcarozumiesz, jak wykorzystać je w praktyce. Niemal cała dalsza część podręcznika będzie bazować na poznanych tuinformacjach, dlatego będzie jeszcze sporo okazji do przećwiczenia wszystkiego. Nie obawiaj się wracania do tychrozdziałów; z pewnością Ci się jeszcze przydadzą. Tymczasem przed nami zestaw ćwiczeń, które już teraz pomogąCi utrwalić zdobyte informacje.

Przypisy[1] http:/ / groups. google. com/ group/ php-standards/ web/ psr-0-final-proposal?pli=1[2] http:/ / gist. github. com/ 221634

Ćwiczenia 214

Ćwiczenia

ĆwiczeniaĆwiczenia utrwalające materiał z rozdziału o programowaniu obiektowym.Odpowiedzi

Podstawy1.1. Czym różni się klasa od obiektu?2.2. Spójrz na Twój komputer i wyobraź sobie, że masz go zamodelować w Twoim programie przy pomocy

programowania obiektowego. Co będzie klasą, a co obiektem i dlaczego?3.3. Wymień elementy wchodzące w skład klasy i powiedz krótko, do czego służą.4.4. Co oznacza, że zmienna obiektowa jest referencją do obiektu?5. Do czego służy słowo kluczowe public przy deklarowaniu elementów klasy? Jakie znasz jeszcze inne podobne

do niego?6.6. W jaki sposób możemy utworzyć obiekt jakiejś klasy?7.7. W jaki sposób możemy oprogramować proces tworzenia nowego obiektu lub jego niszczenia?8.8. Kiedy obiekt jest niszczony przez PHP?

Dziedziczenie1.1. Podaj praktyczny przykład zastosowania dziedziczenia.2. Mamy klasę Foo rozszerzającą Bar oraz funkcję, która przyjmuje jeden argument. Opisz, jak zachowa się PHP,

gdy za argument podamy obiekt klasy Foo, a funkcja będzie wymagać:• Obiektu klasy Foo• Obiektu klasy Bar• Obiektu klasy Foo, a my wprowadzamy obiekt klasy Bar.

3. Do czego służy słowo kluczowe protected?4.4. Podczas dziedziczenia możemy unieważnić jedną z dotychczasowych metod. Przypuśćmy, że w klasie pochodnej

unieważniliśmy jedną z metod, lecz zmieniliśmy listę argumentów. Jak zachowa się PHP?5.5. Do czego służą klasy abstrakcyjne?6.6. Jak zabezpieczyć jakąś metodę przed unieważnieniem?

215

Zaawansowane programowanie

216

Bazy danych

Wstęp do baz danych

Wstęp do baz danychAplikacje bazodanowe to specjalistyczne aplikacje, których głównym celem jest przechowywanie złożonychinformacji, 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 sposobywe 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ękitemu programista nie musi się martwić pisaniem kodu odpowiedzialnego np. za sortowanie wyników - całą pracęwykonali już za niego twórcy bazy danych.

Przegląd terminologiiPrzy pracy z bazami danych będziemy stosowali pewną terminologię. Pora, by się z nią zapoznać. Program, któryzarządza bazami danych jest nazywany serwerem baz danych. W tym podręczniku będziemy pisali w skrócie serwerDB (DB od angielskiego słowa database oznaczającego po prostu bazę danych). Dane są w nim ułożone whierarchiczny 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

Wstęp do baz danych 217

• 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ższymprzykładzie istnieje relacja między produktami, a kategoriami, do których są przypisane (pole category). Wyróżniasię 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. Jednaksiąż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 jestrzadko wykorzystywana.

Relacje można odzwierciedlać w strukturze bazy, a także pobierać dane z ich wykorzystaniem (np. pobrać produktyposortowane 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 wspecjalnym języku SQL (Structured Query Language). Jego podstawy poznamy w niniejszym podręczniku, lecz pobardziej zaawansowane jego możliwości będziesz musiał sięgnąć do innych źródeł. Terminem ANSI SQL określamynazwę standardu definiującego język SQL. Różne serwery DB implementują jego założenia lepiej lub gorzej, ale wprzypadku korzystania ze złożonych możliwości kompatybilność między nimi nie jest zadowalająca.

Przegląd serwerów DBOto krótki przegląd niektórych najczęściej spotykanych serwerów DB:• MySQL - najpopularniejszy serwer DB do zastosowań WWW stworzony przez szwedzką firmę MySQL AB, a

obecnie rozwijany przez potentata branży bazodanowej, Oracle Corporation. Można go używać bez żadnychopłat. MySQL słynie ze swej olbrzymiej wydajności, a najnowsza wersja 5, z której będziemy korzystać,obsługuje już prawie cały standard ANSI SQL. Początkowo PHP posiadał wbudowaną obsługę tego serwera, leczw 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.

• SQLite - ten serwer DB jest dość specyficzny, ponieważ w rzeczywistości jest to biblioteka wbudowywana waplikacje, które go wykorzystują (np. w interpreter PHP). Stąd też do korzystania z niego nie potrzeba żadnychdodatkowych programów. SQLite jest wbudowany domyślnie w PHP, odkąd zmienił się sposób licencjonowaniaMySQL-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

Wstęp do baz danych 218

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. W tym podręcznikuskupimy się właśnie na niej.

Spis treściTa część podręcznika rozpocznie się od nauki podstaw języka SQL. Następnie nauczysz się podstaw programowaniaobiektowego, którego znajomość jest niezbędna, aby korzystać z biblioteki PDO. Dowiemy się także, jak wprzeszłości komunikowało się z bazami danych, jak stworzyć system sesji oparty o bazy danych oraz napiszemyprzykładową aplikację: system newsów.1. Podstawy języka SQL

1.1. Projekt bazy danych2.2. Zarządzanie rekordami3.3. Pobieranie rekordów4.4. Relacje i indeksy

2.2. Biblioteka PDO3.3. Bazy danych i sesje4.4. Jak to się robiło kiedyś?5.5. phpMyAdmin6.6. Studium przypadku: System newsów7.7. Bazy danych - co dalej?

Projekt bazy danych

Projekt bazy danychOdstawimy teraz na chwilę PHP i nauczymy się pracować z serwerem baz danych MySQL. Poznamy równieżpodstawy języka SQL do komunikacji z nim.

Wiersz poleceń serwera DBAby 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. SerwerDB 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 katalogz instalacją MySQLa, a następnie na znajdujący się w nim folder bin. Tutaj należy uruchomić program mysql.exez 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

Projekt bazy danych 219

Podstawą bezpieczeństwa w bazie danych MySQL są użytkownicy, którzy mają jasno określone przywileje dostępudo baz. Najważniejszym z nich jest root, służący na serwerach produkcyjnych jedynie do administracji. Jednak wdomowych warunkach można go wykorzystać także do projektowania własnych baz oraz testowania skryptów. Wnaszym przypadku podaliśmy podczas instalacji, że hasło tego użytkownika to także root. Podaj je, gdy zostaniesz oto 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 wierszupoleceń obowiązują następujące reguły:1. Zapytania kończymy średnikiem lub sekwencją \g. Jeżeli zabraknie tego elementu, ENTER zwyczajnie będzie

Cię przenosił do nowej linijki!2. Pomoc włączamy sekwencją \h3. Opuszczamy wiersz poleceń sekwencją \q

Tworzenie bazy danychStandardowo po instalacji MySQL posiada stworzone dwie domyślne bazy: test oraz mysql. Pierwsza jest pusta, adruga zawiera ustawienia serwera i lepiej nic tam nie grzebać bez dokładnej znajomości jej budowy. My jednak nieskorzystamy 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 OKoznacza, że zostało ono zaakceptowane i poprawnie wykonane. Dalej mogą wystąpić informacje diagnostyczne(ilość dokonanych zmian, czas wykonywania lub lista wyników).

Tworzenie tabeliJak powiedzieliśmy, właściwe dane przechowywane są w tabelach. Ich tworzenie polega na definiowaniuszczegółowej struktury rekordów. Utworzymy teraz tabelę produkty przechowującą informacje o różnychproduktach:

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

) ENGINE = 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ą wnastępującej kolejności:1.1. Nazwa pola2.2. Typ pola3.3. Czy pole może być puste?4.4. Wartość domyślna

Projekt bazy danych 220

5.5. Parametry dodatkowe6.6. Klucze i indeksyPrzyjrzyjmy się zatem poszczególnym polom:1. 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ą relacjioraz 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.

2. 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

3. opis - opis produktu z dodatkiem wazeliny. Parametry to:• TEXT - typ tekstowy (maksymalna długość: 64 kB)• NOT NULL - pole nie może być puste

4. 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.

5. 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

6. 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 terazprzejmować. Wystarczy na razie wiedzieć, że domyślnym jest obecnie InnoDB, ale do większości zastosowań używasię wydajniejszego MyISAM.Zauważyłeś już pewnie, że w zapytaniu wykorzystaliśmy kilka różnych typów liczbowych. Oto dokładniejszaspecyfikacja ważniejszych typów:1. TINYINT - liczba jednobajtowa. Wartości od -128 do 127.2. TINYINT UNSIGNED - liczba jednobajtowa bez znaku. Wartości od 0 do 255. Słowo UNSIGNED po kolejnych

nazwach typów liczbowych robi to samo, co w tym przypadku.3. SMALLINT - liczba dwubajtowa. Wartości od -32768 do 32767, a bez znaku od 0 do 65535.4. MEDIUMINT - liczba trzybajtowa. Wartości od -8388608 do 8388607, a bez znaku od 0 do 16777215.5. INT - liczba czterobajtowa. Wartości od -2147483648 do 2147483647, a bez znaku od 0 do 2147483647.6. BIGINT - liczba ośmiobajtowa. Wartości od -9223372036854775808 do 9223372036854775807, a bez znaku od

0 do 18446744073709551615.7. FLOAT - liczba zmiennoprzecinkowa czterobajtowa (tak, jak w PHP).8. 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).9. 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.

Projekt bazy danych 221

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

11. BLOB - typ do przechowywania danych binarnych, np. plików. Maksymalna wielkość to także 64 kB.12. BOOL - typ logiczny, równoważnik zapisu TINYINT(1).13. 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 wnawiasach nie mają nic wspólnego z wielkością danych, jakie można w nich trzymać. Mówią one bazie danychMySQL, ż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, kiedybędziemy na niej operować, jak na tekście.

Specyfika języka SQLNapisaliśmy już kilka zapytań i widać z nich, że SQL w wielu miejscach przypomina zwyczajny mówiony angielski.Pora poznać kilka jego cech:1. 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.2. 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łówkluczowych 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.

ĆwiczeniaW ramach treningu utwórz następujące tabele:1. Tabela klientów sklepu z polami id, imie, nazwisko, wiek, miejscowosc, ulica, numer_domu, numer_mieszkania,

telefon.2. Tabela dostawców sklepu z polami id, nazwa, dzien_tyg_dostawy, naleznosc3. 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ę.

Zarządzanie rekordami 222

Zarządzanie rekordami

Zarządzanie rekordamiWiemy już, jak utworzyć bazę danych oraz strukturę tabel. Niewątpliwie bez tych operacji nie można zacząć, leczpóźniej podstawowymi operacjami stają się te, dzięki którym możemy zarządzać danymi. Właśnie w tym odcinkudowiemy się, jak dodawać, modyfikować oraz usuwać niepotrzebne rekordy. Na początek proste polecenie, którepozwoli 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ępnychdwóch odcinkach. Teraz posłuży nam on jedynie do sprawdzania, czy wszystko przebiega poprawnie.

Dodawanie rekordówAby 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 pierwszymzapytaniu musimy bezwzględnie podać wartości wszystkich pól nowego rekordu, jakie mamy zdefiniowane wstrukturze 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 naszejtabeli 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,

Zarządzanie rekordami 223

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 oddzielonyprzecinkiem.Pamiętaj, że wartości tekstowe musimy zawsze podawać w apostrofach. Liczby można podawać zarówno z nimi, jaki bez, lecz podczas programowania apostrofami obejmuje się najczęściej wszystkie wstawiane ze skryptu wartości.

Modyfikowanie rekordówCzasem zachodzi konieczność zmodyfikowania niektórych informacji. Tu pomocne będzie zapytanie UPDATE. Maono 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 zmianyzostały wprowadzone we wszystkich rekordach! Bardzo rzadko jest to pożądana rzecz, ponieważ o wiele częściejchcemy zmodyfikować pewną, konkretną grupę. Do jej uwzględnienia użyjemy nowej klauzuli: WHERE wyrażenieumieszczanej po liście pól do podmiany. Wyrażenie jest dowolnym poprawnym wyrażeniem języka SQL, a układasię je podobnie, jak te w PHP. Szczegółowe informacje o budowaniu wyrażeń poznamy w następnym odcinku, terazograniczymy 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. Wieleosó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 tylezaawansowanym 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.

Zarządzanie rekordami 224

Usuwanie rekordówNiepotrzebne 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 nanormalnym serwerze. Jednak wtedy nie powinno się wykorzystywać polecenia DELETE FROM `produkty`. Do tegocelu 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 ikliknięciu "Delete", a uruchomieniem jego formatowania.Wykonując wiele zapytań DELETE oraz INSERT zauważysz, że pozostają Ci luki w numeracji. Spróbuj dodać terazjakiś rekord. Jeżeli wykonałeś wcześniej zapytanie kasujące ten o ID 3, MySQL pozostawi tam lukę, nadającnowemu rekordowi następny w kolejności ID - 4. Prawdopodobnie z przyczyn estetycznych niektórym nowymprogramistom to zachowanie przeszkadza, lecz jest to zupełnie błędne podejście do problemu. Jak wspomnieliśmybowiem, MySQL jest relacyjną bazą danych, w której rekordy z jednej tabeli mogą być połączone odpowiednimirelacjami 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 ztej usuniętej, gdyż nowy rekord zajął pustą lukę w numeracji. Działanie takie stwarza poważne zagrożeniesynchronizacji 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 itwojej bazie.

Zamiana rekordówBardzo przydatną operacją jest automatyczne podmienianie rekordów. Polega ono na tym, że jeżeli wstawiamyrekord A do tabeli, w której znajduje się już rekord B o podobnym kluczu, jest on automatycznie nadpisywany przezsystem. MySQL posiada dwa zapytania, które są pomocne przy podmienianiu rekordów, jednak zanim się do nichdobierzemy, musimy utworzyć sobie nową tabelę.Aby MySQL mógł zdecydować, czy rekord należy nadpisać, musi wiedzieć, które pola tabeli przechowują wartościunikalne, 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 polenaraz, a operacja REPLACE przy rekordach mających z definicji nie tylko unikalne, ale też nadawane automatycznieID, zbyt dużego sensu nie ma. Dlatego istnieje indeks UNIQUE, który może być nałożony na dowolnie dużo pól itakże sprawi, że zyskają one tę właściwość. Aby pokazać to w praktyce, utwórzmy tabelę dla elektronicznegosł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;

Zarządzanie rekordami 225

Mamy tu trzy pola: id, haslo oraz znaczenie. ID służy jedynie celom administracyjnym (wyszukiwanie względemliczby jest zawsze szybsze, niż względem tekstu). Dla użytkownika naszego słownika najważniejsze będzie jednakpole haslo, które także powinno mieć wartości unikalne. Indeks UNIQUE tworzymy tak, jak na przykładzie: poliś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 przezMySQL ma dla nas duże znaczenie. Kiedy będzie w nim bowiem już dużo słów, ze zwykłej pomyłki możemyspróbować dodać po raz drugi to samo. Tradycyjne polecenie INSERT zareagowałoby błędem powiadamiającym, żepró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 ipokażą 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świetlmyzawartość 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:1.1. Spróbuj dodać nowy rekord.2.2. Jeśli nie powiedzie się z powodu duplikacji unikalnej wartości, usuń stary rekord powodujący kolizję.3.3. 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żerozszerzenie 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. Dodajmyponownie 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 jednakzajdzie 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ższymprzykładzie, aby wprowadzić nową definicję hasła na miejsce starej.

Zarządzanie rekordami 226

Dzięki automatycznej możliwości nadpisywania rekordów z unikalnymi kluczami, nasza baza jest wygodniejsza.Jeżeli jednak tworzona aplikacja ma pracować z różnymi systemami DB, musimy sprawdzić, czy obsługują onepodaną operację i w razie potrzeby napisać odpowiedni emulator.

Błędy w zapytaniachTakże w języku SQL można popełnić błędy, które uniemożliwią wykonanie zapytania. Każdy serwer DB raportujeje 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 wokolicy ciągu cena` = '2.45' WHERE `id` = '1'. Niepisana zasada mówi, że najczęściej błąd popełniliśmybezpośrednio przed nim. Zobaczmy więc, co znajduje się w zapytaniu przed odwołaniem do pola "cena". Okazujesię, ż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 teatrybuty nadają polu właściwość unikalności, czyli nie mogą istnieć dwa rekordy o takich samych wartościach wtym 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 nicsię teraz nie stanie, ponieważ rekord o ID 3 niedawno skasowaliśmy. W takim wypadku wykonaj je jeszcze raz, abaza 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.

Pobieranie rekordów 227

Pobieranie rekordów

Pobieranie rekordówOsobnego omówienia wymaga operacja pobierania rekordów z bazy danych, która jest esencją pracy z tego typuaplikacjami. W poprzednim rozdziale poznaliśmy dla potrzeb testowych zapytanie SELECT * FROM tabela, leczbył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 wieluciekawych rzeczy. Teraz przyjrzymy się im dokładniej.

Filozofia zapytania SELECTZapytanie SELECT to w zasadzie zbiór klauzul, które możemy dodawać i odejmować, działających jak filtry dladanych. 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 zaczniemypisanie skryptów łączących się z bazą, aby pobrać wyniki z rekordów. Kolejne wiersze obrazują wszystkie pasującerekordy wyników.Z doświadczenia możemy powiedzieć, że pozostawianie bazie danych MySQL spraw nazewnictwa pól na liściewyników nie należy do najszczęśliwszych i w wielu przypadkach zachodzi potrzeba jego ręcznego określenianazwy. 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órychzamierzamy korzystać. W tym odcinku poprzestaniemy na pojedynczej tabeli i dopiero później powiemy sobie, jakmożna obsłużyć więcej. Spróbujmy dowiedzieć się zatem czegoś o stworzonej ostatnio tabeli produkty:

Pobieranie rekordów 228

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 niejpobrane pola id oraz nazwa wszystkich rekordów w tabeli produkty. Gdybyśmy chcieli pobrać wartości wszystkichpó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ólręcznie! Miej to na uwadze podczas projektowania twoich zapytań.

Klauzula WHEREDo 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ć nazapytanie warunek, który nie dopuści do listy wyników tych rekordów, gdzie ilość postów jest mniejsza niż podanawartość. Wszystko to należy do kompetencji klauzuli WHERE warunek, która pojawia się również przy zapytaniachUPDATE 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:

Pobieranie rekordów 229

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 ANDwyraż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:

pole IN(wartosci)

Prawda, jeśli wartość pola znajduje się na liście wartości podanej w nawiasach. Spróbujemy pobrać nim rekordy oID 1, 2 oraz 4.

SELECT id, nazwa FROM produkty WHERE id IN(1,2,4);

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, aleomijające rekordy o podanych ID.

SELECT id, nazwa FROM produkty WHERE id NOT IN(1,2,4);

pole BETWEEN mniejszy AND wiekszy

Wartość pola znajduje się w przedziale od mniejszy do wiekszy. Spróbujemy pobrać nim produkty, których ilość wmagazynie waha się od 0 do 100:

SELECT id, nazwa FROM produkty WHERE ilosc BETWEEN 0 AND 100;

pole IS NULL

Podczas tworzenia tabel powiedzieliśmy sobie nieco o polach z dozwolonymi wartościami pustymi (null). Zapomocą 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 rekordytakowe posiadają:

SELECT id, nazwa FROM produkty WHERE ilosc IS NULL;

Pobieranie rekordów 230

Klauzula ORDER BYTa klauzula dodaje możliwość sortowania wyników według określonego kryterium. Tradycyjnie podajemy ją poWHERE. 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 donajwiększego, ale możemy ją odwrócić, dodając po wyrażeniu słowo DESC. Spróbujmy posegregować naszerekordy 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, żewtedy 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 |

Pobieranie rekordów 231

| 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ślipołączymy wszystko z klauzulą WHERE, poczujemy prawdziwą potęgę baz danych. Posortujmy według ceny tylkote 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)

Klauzula LIMITNa stronach internetowych często prezentowane są olbrzymie ilości informacji. Aby wyświetlenie ich spisu nieprzeciążało łącza, listę wyników dzieli się na strony (inaczej: porcjuje) tak, że naraz wyświetla się jedynie niewielkajej część, a do reszty możemy się dostać poprzez odpowiednie linki nawigacyjne. Oczywiste jest, że wybieranie tegomałego kawałka danych powinno zachodzić po stronie bazy danych, a nie PHP. Tak jest w istocie, dzięki klauzuliLIMIT. 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ąć pobieranieoraz 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ć punktrozpoczęcia, ilość rekordów poprzedzamy "numerem startowym":

SELECT id, nazwa FROM produkty LIMIT 2, 3;

+----+-------------------------+

| id | nazwa |

+----+-------------------------+

| 3 | Zszywacze |

| 4 | Karteczki samoprzylepne |

| 5 | Strugaczki |

Pobieranie rekordów 232

+----+-------------------------+

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 wrzeczywistości rozpoczęcie od rekordu trzeciego. W poprzednim przykładzie start od pierwszego rekordumoglibyś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 lubnawet innych sposobów do wykonywania porcjowania danych. Aby zapewnić pewną kompatybilność, MySQLudostępnia niektóre z nich jako alternatywę. Oto powyższy przykład zapisany z wykorzystaniem składni bazyPostgreSQL, 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ęzykaSQL między nimi. Dla większej ilości baz danych może być konieczne zupełne zrezygnowanie z klauzuli LIMIT narzecz odpowiednio konstruowanych warunków WHERE oraz dodatkowych pól w tabelach.

Funkcje grupująceJę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 nichróżne istotne informacje. Wiąże się z tym kilka ograniczeń użycia, lecz póki co nie będą nas one dotyczyć, gdyż niepotrafimy 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 zewzglę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. Wnaszej tabeli jedynie ilosc zezwala na jej obecność. Wstawmy więc ją do rekordu o ID 5, aby mieć na czymeksperymentować:

Pobieranie rekordów 233

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 pobraniagatunkó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ścitowaru w magazynie. Służy do tego funkcja grupująca AVG(). Hipoteza jest następująca: jeśli funkcja ta jestwrażliwa na obecność NULL, powinna dawać różne wyniki w zależności od tego, czy w rekordzie towaruniewystępującego w magazynie oznaczymy ilość przez NULL, czy przez 0. Sprawdźmy to. Oto ciąg wykonywanychprzez 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 |

Pobieranie rekordów 234

+------------+

1 row in set (0.00 sec)

Oto opis poczynionych kroków:1. Ustawiamy w rekordzie 5 pole ilosc na NULL.2.2. Obliczamy średnią ilość towarów w magazynie. Wynik: 90,8(3)3. Ustawiamy w rekordzie 5 pole ilosc na 0.4.4. Obliczamy średnią ilość towaru w magazynie. Wynik: 77,86Jak widać, AVG() dało nam różne wyniki w zależności od tego, co mieliśmy w polu ilosc, potwierdzając tym samymnaszą hipotezę. Zjawisko to bardzo łatwo wytłumaczyć. Wartość 0 traktowana jest jak normalna ilość. Zgodnie zewzorem na średnią arytmetyczną, sumujemy sześć wartości, lecz dzielimy już przez siedem, z uwzględnieniemnaszego zera. Wartość NULL wyraźnie mówi bazie danych MySQL: nie licz mnie, ja nie istnieję. Bądźmy świadomitych 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.

Ciekawe sztuczkiNa sam koniec pragniemy pokazać pewną ciekawą sztuczkę, która ukaże potęgę języka SQL i być może zachęciwielu 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 dasię 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 |

Pobieranie rekordów 235

+----+

| 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ś, żenigdzie nie ma tam wzmianki o konieczności wymieniania pól. Jest wręcz przeciwnie - czarno na białym było tamnapisane "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 dotematu, który ma nam wystarczyć na początek przy programowaniu w PHP. Zanim jednak pokażemy, jakwykorzystać potęgę baz danych w tym języku, czeka nas jeszcze jeden rozdział poświęcony relacjom oraz indeksom.

Relacje i indeksy 236

Relacje i indeksy

Relacje i indeksyTen rozdział jest już ostatnim na naszej drodze poznawania bazy danych MySQL. Po zapoznaniu się z relacjami orazistotą działania indeksów, powrócimy do PHP, by nauczyć się wykorzystywać tę moc w naszych skryptach.

IndeksyPodczas 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ściamirekordów, z jakimi mamy do czynienienia (MySQL pracuje stabilnie nawet przy dziesiątkach milionów rekordów wtabeli), 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ć wnaszej stworzonej wcześniej bazie wyszukiwanie względem ceny, MySQL musi za każdym razem na nowoprzetrząsać od początku do końca wszystko, co tam się znajduje. Kiedy produktów będzie parę tysięcy, może totrwać znacznie dłużej, niż teraz. Tutaj do akcji wkraczają indeksy. Indeks nałożony na pole A jest kopią zawartościtego pola, tyle że posortowaną i odpowiednio ułożoną. Podczas robienia wszelkiego rodzaju poszukiwań względemznajdują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órnagranica nieustalona). Powiedzmy, że chcemy wiedzieć, jakie rekordy mają wartości z zakresu od 700 do 900. Jako żesą one posortowane, MySQL może od razu strzelić sobie w środek tego zbioru i sprawdzić:1.1. Jeżeli wartość środkowego elementu jest mniejsza od podanego zakresu, przeszukujemy tylko późniejsze

rekordy.2.2. Jeżeli wartość jest większa od podanego zakresu - tylko wcześniejsze.3.3. 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 zsamej definicji. Także sortowanie jest prostsze - wystarczy przejechać się po indeksie; nawet nie trzeba żadnejfunkcji 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 naktórych nie. Ba! Po ich utworzeniu będziemy się nawet mogli przekonać, czy MySQL je wykorzystuje. Wystarczyzapytanie SELECT poprzedzić słowem EXPLAIN, a otrzymamy pełną informację diagnostyczną o jegowykonywaniu. 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 zbytwiele 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 dlapola 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 |

Relacje i indeksy 237

| 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 jakichfaktycznie 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 niegopól (indeks może składać się z więcej, niż jednego pola, podobnie też w tabeli może być kilka indeksów).

Relacje jeden do wieluDotychczas każda tworzona tabela funkcjonowała niezależnie od innych, lecz takie podejście nie wykorzystujenawet połowy potęgi baz danych. Prawdziwa moc tkwi w tym, że między tabelami mogą istnieć powiązania, czylirelacje, 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ścidostępnych tam pozycji. W prawdziwej bazie danych biblioteki oczywiście dodalibyśmy jeszcze tutaj różneozdabiacze 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 tensposó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,

Relacje i indeksy 238

`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:1. 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.2. 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, doktó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ć poprzedzonakropką 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ę zksiążką.

Relacje i indeksy 239

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 bardzodużych zapytaniach, zajmujących niejednokrotnie wiele linijek. Jednak niosą one w sobie coś więcej, niż tylkopoprawiają 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 najednym rekordzie z podanej tabeli, do którego odnoszą się warunki podane w WHERE. Ale są sytuacje, kiedy narazmusimy pobrać dane z dwóch rekordów w jednej tabeli - aliasy są w tym wypadku konieczne, aby odróżnić jedenrekord od drugiego: FROM ksiazki ks1, ksiazki ks2. Naturalnie może się zdarzyć, że oba aliasy wskażą nam ten samrekord, 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 jerelacją (tak, możliwe jest tworzenie relacji między rekordami tej samej tabeli!). Na koniec najbardziej wymownyprzykład korzystania z relacji jeden do wielu. Radzimy przyjrzeć mu się uważnie, ponieważ podobne zapytaniapojawiają 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)

Relacje i indeksy 240

Relacje wiele do wieluKolejny 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ć. Bynajmniejnie 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ówinam 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 |

Relacje i indeksy 241

| 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ę inazwisko 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.

OptymalizacjaIm większe zapytanie z większą ilością skomplikowanych instrukcji, tym wolniej jest wykonywane. Dlatego dobrastruktura bazy danych nie tylko musi być elastyczna, ale też zoptymalizowana w taki sposób, aby jak najwięcejdanych dało się pobrać prostymi zapytaniami, korzystającymi z elementarnych funkcji. Brak tej cechy jestmankamentem baz projektowanych nie tylko przez początkujących, ale i wielu zaawansowanych programistów, conegatywnie 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

Relacje i indeksy 242

nich komentarzy. Pobranie takich informacji jednym zapytaniem wymaga sprytnego zastosowania funkcji grupującejCOUNT(), której użycie w relacjach jest obwarowane paroma ograniczeniami. Jednak to nie wszystko. Kiedy tabelezepniemy "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ększejiloś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 bardzoprosty sposób. Dlaczego bowiem zliczać ilość komentarzy przy każdym wyświetlaniu strony? Przecież liczba ta niezmienia się tak znowu często. Dorzućmy do tabeli newsy pole il_komentarzy. Przy dodawaniu newsa inicjujemy jewartością 0. Przy dodawaniu komentarza zwiększamy jego wartość o 1, przy usuwaniu - zmniejszamy. W ten sposóbuzyskaliś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 typuoptymalizacja spotykana jest w wielu zaawansowanych aplikacjach, np. systemie forów dyskusyjnych InvisionPower 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ędzietam, gdzie dane będą sortowane lub wybierane według różnych kryteriów. Indeksy zawierają informacje ułożone wpewnym porządku, dlatego po ich wstawieniu w takie miejsca MySQL wykorzysta je do przyspieszenia całegoprocesu.Optymalizację można przeprowadzać także z poziomu języka programowania, za pomocą którego komunikujemysię 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 tojednak optymalizacja typowo programowa, dlatego zajmiemy się nią później.

ZakończenieNa tym kończymy naszą przygodę z poznawaniem języka SQL. Jeżeli pragniesz poznać go lepiej, bardzo przydatnaokaż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óworaz własne poszukiwania z użyciem Google. Każdy sposób jest dobry, a znajomości SQL-a nigdy za wiele.• Dokumentacja MySQL 5.0 [1]

Przypisy[1] http:/ / dev. mysql. com/ doc/ refman/ 5. 0/ en/

Biblioteka PDO 243

Biblioteka PDO

Biblioteka PDOJeszcze rok temu programiści pragnący komunikować się z bazą danych poprzez PHP musieli zmagać się z wielomaproblemami. Każdy serwer DB udostępniał inne API do komunikacji, które zostały na nasze nieszczęście wiernieodtworzone 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 zbazami danych, po raz pierwszy napisany wyłącznie w OOP. Jego najważniejszą zaletą jest to, że możemy za jegopomocą łączyć się zarówno z bazą danych MySQL, jak i z bazą danych PostgreSQL (o innych systemach DB niewspominając). Wersji beta PDO można było używać już w PHP 5.0, natomiast stabilna wersja pojawiła się wraz zPHP 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.

Nawiązywanie połączeniaAby nie być gołosłownym, przejdźmy od słów do czynów. Połączymy się z naszą bazą utworzoną podczaswcześ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 parametrport(przykład poniżej) Inne serwery DB mogą wymagać innych parametrów połączeń; po szczegóły odsyłamy dodokumentacji PHP.

<?php

$mysql_host = 'localhost'; //lub jakiś adres: np

sql.nazwa_bazy.nazwa.pl

$port = '3307'; //domyślnie jest to port 3306

$username = 'login';

$password = 'hasło';

$database = 'nazwa_bazy'; //'produkty'

Biblioteka PDO 244

try{

$pdo = new

PDO('mysql:host='.$mysql_host.';dbname='.$database.';port='.$port,

$username, $password );

echo 'Połączenie nawiązane!';

}catch(PDOException $e){

echo 'Połączenie nie mogło zostać utworzone.<br />';

}

?>

•• 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łędziewygenerowany przez PHP ujawni nazwę użytkownika i nazwę bazy!).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;encoding=utf8', 'root',

'root');

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 dlaprzykładu. W ten sposób można przekazać jeszcze inne zapytania już zaraz po połączeniu.

Pobieranie danychPobieranie danych w sterownikach baz danych realizuje się w następujący sposób: najpierw wysyłamy zapytanie iuzyskujemy od serwera zbiór wyników. Przelatując po nim pętlą, otrzymujemy kolejne rekordy w postaci tablic. Wbibliotece PDO zbiorem wyniku jest obiekt klasy PDOStatement. Naraz możemy mieć otwarty tylko jeden zbiórwyników. Zabezpiecza to przed próbami tworzenia zapytań rekurencyjnych oraz wynika ze specyfiki pracy bibliotekkomunikujących się z serwerem. W starszych bibliotekach ograniczenia takiego nie było dzięki emulacji, którajednak zmniejszała wydajność. Przyjrzyjmy się, jak możemy pobrać zawartość tabeli produkty:

<?php

try

{

$pdo = new PDO('mysql:host=localhost;dbname=produkty', 'root',

Biblioteka PDO 245

'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 tymprzypadku nie kończymy go średnikiem!). Jedną z technik uzyskania kolejnych rekordów jest przepuszczenie tegoobiektu 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() - inaczejnie 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 zapytaniachraportowane 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();

Biblioteka PDO 246

}

?>

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 zawszepobieramy 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 komunikatuGeneral Error 2050.

Aktualizacja danychZapytania typu INSERT czy UPDATE służące do modyfikacji zawartości bazy lub inne, niezwracające zbioruwynikó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 donaszej 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!';

Biblioteka PDO 247

}

}

catch(PDOException $e)

{

echo 'Wystąpił błąd biblioteki PDO: ' . $e->getMessage();

}

?>

PodpinanieRzadko kiedy zdarza się, aby wszystkie informacje potrzebne do zmodyfikowania bazy były na sztywnozakodowane w zapytaniu tak, jak to zrobiliśmy w powyższym przykładzie. W codziennej praktyce modyfikujemydane 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 tenmiks 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 'Pomyślnie dodano: '.$ilosc.' rekordów';

}

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>

Biblioteka PDO 248

<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 'Wystapił blad biblioteki PDO: ' . $e->getMessage();

}

?>

Kiedy został nadesłany formularz (metoda POST), nawiązywane jest połączenie z bazą. W szkielet zapytaniawstawiamy wprowadzone przez użytkownika dane, wykorzystując zwyczajny operator łączenia ciągów. Późniejmetoda exec() umieszcza nam nowy rekord w bazie. Na pierwszy rzut oka wszystko wygląda wspaniale - mamyformularz, redaktorzy mogą dodawać produkty, a internauci je oglądać. Lecz pewnego dnia jeden z redaktorówzgł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

Biblioteka PDO 249

wrzucił skrypt na serwer, gdzie magic quotes dla odmiany było włączone, przez co backslashe doklejane były dwarazy - 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óźniejwrzucił 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 jegowprowadzenie tak naprawdę powoduje, że dalsza część ciągu jest uznawana za fragment zapytania! Możemy więcsobie zupełnie legalnie dopisać własne warunki. Wyobraźmy sobie teraz, że ktoś manipuluje w ten sposóbzapytaniami związanymi z bezpieczeństwem za pomocą formularza logowania. To nie fikcja: do wyobraźnipowinien przemówić ten film (j. ang) [1].Ostatecznie sami twórcy PHP doszli do wniosku, że magic quotes jest rozwiązaniem bezsensownym. W tworzonymwłaśnie PHP 6 tej opcji już nie ma i należy samodzielnie escape'ować wszystkie dane. Jednak póki co pracujemy naPHP 5.1 - choć w tym podręczniku podczas instalacji zalecaliśmy wyłączenie magic quotes, nie mamy pewności, żeserwer docelowy dla naszych stron WWW posiada identyczne ustawienia. Jeśli korzystamy z PDO i mechanizmupodpinania, problem nas nie dotyczy, ponieważ biblioteka automatycznie dostosuje się wtedy do ustawień, lecz wkażdym innym przypadku powinniśmy zastosować specjalny filtr, który zniweluje nam efekt niewłaściwychustawień 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

Biblioteka PDO 250

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ęcznikubę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 bazywysyłamy tutaj tak naprawdę szkielet zapytania ze specjalnymi wstawkami, do których później podpinamyinteresują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

Biblioteka PDO 251

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:1. 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.

2. 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.

3. 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 tegosamego 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 zostanieon wysłany, połączymy się z MySQL'em, przekazując szkielet naszego zapytania. Następnie będziemy podpinali doniego dane kolejnych produktów i wykonywali.

<?php

Biblioteka PDO 252

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!';

}

}

Biblioteka PDO 253

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:1.1. Szkielet zapytania wysyłany tylko raz.2.2. Zestawy danych ładowane są w pętli.3. W pętli wykonujemy też metodę execute().Podpinanie nie ogranicza się tylko do zapytań typu INSERT. Z powodzeniem można stosować je także przySELECT. Napiszemy teraz skrypt wyświetlający listę produktów oraz umożliwiający nam zobaczenie szczegółówkażdego z nich. Dlatego do drugiego zapytania, pobierającego szczegółowe informacje, musimy podpiąć IDproduktu, 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');

Biblioteka PDO 254

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:1.1. Oczywiście wyświetlanie szczegółów przeprowadzamy tylko, jeśli podaliśmy ID.2. Przygotowujemy szkielet zapytania SELECT.3. 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.4.4. Dalej postępujemy już tradycyjnie, po prostu pobierając kolejne rekordy (w tym wypadku tylko jeden) i

zamykając kursor.

Biblioteka PDO 255

Jedyną wadą podpinania jest wydłużenie kodu PHP. Jeśli dotychczas wysyłaliśmy zapytanie DELETE zwyczajniewykonując metodę exec(), teraz musimy to rozpisać na kilka linijek. Jednak jeszcze w tym rozdziale poznamynakł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. Wersja5.0 jest bardziej elastyczna, jeśli chodzi o konwersję typów i nic jej nie zaszkodzi, kiedy spróbujemy wstawić dopola TINYINT(1) wartość oznaczoną w skrypcie jako PDO::PARAM_BOOL. Na MySQL 4.1 takie zapytanie niezostanie wykonane, a ponadto serwer DB nie wygeneruje żadnego ostrzeżenia czy komunikatu.Ćwiczenie: Zaprogramować formularz do edycji danych produktów z wykorzystaniem podpinania. Skrypt musiwczytywać do formularza dane edytowanego produktu oraz po jego wysłaniu, zmodyfikować wskazany rekordzapytaniem UPDATE. Pamiętaj: wraz z wysłanym formularzem musisz przesłać także ID rekordu, którymodyfikujesz!

Obsługa relacjiPotrafimy 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 orazznajdują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 kategoriipobierało książki. Od razu przestrzegamy przed takim sposobem myślenia! Łamie on podstawową zasadę pracy zbazami danych mówiącą, że generalnie im mniej zapytań, tym lepiej. Ilość wysyłanych zapytań musi być względniestała i poważnym błędem jest dopuszczenie do sytuacji, gdy zależy ona wprost proporcjonalnie od ilościpobieranych danych. PHP Data Objects (PDO) 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 niezamknęliśmy jego kursora. Jak więc zatem poradzić sobie z tym zadaniem? Jest to bardzo proste - nasz skrypt będziebez względu na ilość kategorii wykonywać dwa zapytania, których wynik będzie ładowany do tablicy. Dopiero zniej 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();

Biblioteka PDO 256

$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ępniewysyłamy zapytanie służące do pobrania wszystkich książek (3). Zauważmy, że sortujemy je najpierw według IDkategorii, a ponadto tenże ID pobieramy. Wykorzystany zostaje jako klucz dostępu, dzięki czemu jesteśmy w stanieulokować daną książkę w odpowiedniej kategorii (4). Na końcu dwoma zagnieżdżonymi pętlami wyświetlamywszystko.Sposób opiera się na wcześniejszym zbuforowaniu danych. W praktyce programiści bardzo często tworzą takiebufory, ponieważ pozwalają one na dodatkową obróbkę pobranych danych, a w przypadku korzystania z systemuszablonów są nawet niezbędne.Ćwiczenie: Zmodyfikować powyższy skrypt tak, aby przy nazwie książki wymienieni byli również wszyscy jejautorzy, tłumacze, itd. Skrypt może wysyłać najwyżej cztery zapytania.

Biblioteka PDO 257

Zaawansowane techniki bazodanoweW bardziej rozbudowanych projektach zachodzi potrzeba stosowania bardziej rozbudowanych narzędzi, niżzwyczajnych sterowników bazodanowych. Potrzebne są tam biblioteki oferujące mechanizmy cache'owania czydebugowania. Co więcej, bezpośrednie operowanie na bazie przy pomocy języka SQL, szczególnie przy wieluprostych operacjach jest wyjątkowo niewygodne. Tutaj z pomocą przychodzą nam systemy ORM (Object-RelationalMapping). Mówiąc ogólnie, odwzorowują one strukturę bazy danych po stronie skryptu w postaci obiektów. W tensposób, utworzenie nowego wiersza np. w tabeli z użytkownikami sprowadza się tam do utworzenia nowego obiektuklasy User i przypisania do jego pól żądanych wartości.Jednym z kilku dostępnych systemów ORM dla PHP5 jest Doctrine [2]. W jego skład wchodzi szereg narzędzi:1.1. Parser autorskiego wariantu języka SQL, DQL-a, zarówno w postaci tekstowej, jak i obiektowej.2.2. Generator struktury bazy danych.3.3. Generator modeli.4.4. System cache.5.5. i wiele innych.Jest on szczególnie przydatny w grupowej pracy nad projektem, gdzie pojawia się problem przenoszenia zmianpoczynionych 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órymoż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 poprzezobiekty PHP.

ZakończenieNareszcie umiemy komunikować się z bazami danych z poziomu PHP, wykorzystując do tego celu bibliotekę PHPData 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ą, wieleskryptó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.

Przypisy[1] http:/ / www. youtube. com/ watch?v=jMQ2wdOmMIA[2] http:/ / www. doctrine-project. org

Jak to się robiło kiedyś? 258

Jak to się robiło kiedyś?

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śniejkorzystają ze starych oraz niewygodnych funkcji komunikacji z bazami danych. Dlatego podręcznik ten zawieratakże im poświęcony rozdział.

Pobieranie wynikówTen zestaw funkcji w ogóle nie korzysta z dobrodziejstw programowania obiektowego - kiedy powstawał, w PHP poprostu jeszcze takowego nie było! Ponieważ znamy już się nieco na pracy z bazami danych, zaczniemy od razu odpobrania 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:1. Funkcja mysql_connect(), która przyjmuje parametry: serwer, nazwa użytkownika, hasło, powoduje nawiązanie

połączenia z bazą danych.2. Funkcja mysql_select_db() wybiera bazę danych, na której będziemy pracować.3. 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.

4. mysql_fetch_assoc() pobiera kolejny rekord ze zbioru wyników $r jako tablicę asocjacyjną. Istnieją jeszczemysql_fetch_num() (numeryczne indeksy tablicy) oraz mysql_fetch_array() (połączenie obu tych sposobów).

5. 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

Jak to się robiło kiedyś? 259

metoda PDOStatement::fetch() pobierała dane bezpośrednio z serwera DB, umożliwiając ich natychmiastoweprzetwarzanie. Obie techniki mają swoje plusy i minusy. PDO dzięki temu jest znacznie wydajniejsze, szczególnieprzy 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.

Obsługa błędówSpró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 avalid MySQL result resource in D:\Serwer\www\mysql\skrypt.php on line 9Zobaczmy: mamy literówkę w zapytaniu; napisaliśmy DECS zamiast DESC, jednak mysql_query() w ogóle niezgłosił żadnego komunikatu! Jedynym sygnałem, że coś jest nie tak, było zwrócenie wartości false zamiast zbioruwyników, co po wstawieniu do funkcji mysql_fetch_assoc() zaowocowało komunikatem o podaniu niewłaściwegoparametru. Czy więc jest tu w ogóle jakaś obsługa błędów? Oczywiście, tyle że zakłada ona, że programista lubimonotonię 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ść isamodzielnie zgłasza nam błędy, jak trzeba.

Jak to się robiło kiedyś? 260

Wstawianie danychDo wykonywania zapytań INSERT czy UPDATE także używana jest funkcja mysql_query(), lecz tym razem będzieona za każdym razem zwracała wartość true. Aby sprawdzić, ile rekordów zostało zmodyfikowanych, musimywywołać dodatkowo mysql_affected_rows(). Oto przepisany przykład z poprzedniego rozdziału dodający noweprodukty 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>

Jak to się robiło kiedyś? 261

<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 zapobieganiaatakom SQL Injection. Oczywiście, gdy magic quotes było włączone, funkcji tej nie powinno się używać.

Informacje dodatkoweZe względu na charakter pobierania rekordów przez to rozszerzenie, umożliwia ono policzenie ilości zwróconychwyników jeszcze przed rozpoczęciem ich pobierania. Aby to wykonać, należy skorzystać z funkcjimysql_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 funkcjimysql_query() z zapytaniem INSERT.

Jak to się robiło kiedyś? 262

ZakończenieJak wspomnieliśmy, rozszerzenie to ma charakter historyczny. Twórcy PHP stopniowo ograniczają wsparcie dlaniego; nie ma w nim np. żadnej implementacji mechanizmu podpinania, choć biblioteki klienckie MySQL jaknajbardziej na to zezwalają. Jest ono także niewygodne w użyciu oraz na dłuższą metę mało efektywne. Wcodziennej praktyce chyba żaden szanujący się programista nie stosował żadnej z tych funkcji bezpośrednio, leczkorzystał z dodatkowej, napisanej w PHP nakładki automatyzującej wszystkie nużące czynności i zapewniającejwsparcie programowania obiektowego. Teraz, gdy do dyspozycji jest biblioteka PDO, sens korzystania z tychfunkcji jest bardzo wątpliwy.

phpMyAdmin

phpMyAdminDotychczas wszystkie nasze bazy danych i tabele tworzyliśmy ręcznie z wiersza poleceń. Na dłuższą metę taka pracajest bardzo niewygodna i nieefektywna i nikt nie administruje w ten sposób bazami danych. Do tego celuwykorzystywane są specjalne, zaawansowane narzędzia, przestawiające całą strukturę w postaci graficznej orazudostępniające narzędzia do np. importu czy eksportu zawartości. Jeśli tworzysz dynamiczne strony WWW, prędzejczy później spotkasz się z aplikacją phpMyAdmin, napisanym w PHP darmowym i niezwykle rozbudowanymmenedż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 nawłasnym komputerze bazę danych na właściwy serwer, a mnóstwo programistów wykorzystuje go do projektowaniabaz dla swych projektów WWW. W tym rozdziale nauczysz się podstaw pracy z phpMyAdminem.

InstalacjaPrzebieg instalacji phpMyAdmina jest bardzo prosty:1. Wejdź na stronę [1] i pobierz z działu DOWNLOADS najnowszą dostępną wersję (w chwili pisania tego

rozdziału: 2.8.2.1).2. 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 (w zależności od systemu operacyjnego) :D:/Serwer/Apache2/htdocs , /usr/local/apache2/htdocs/ lub /etc ( dla Linuksa Ubuntu)

3. Pojawi się tam katalog phpMyAdmin lub nieco dłuższy. Skróć go do zwykłego pma. W ten sposób aplikacjabędzie dostępna z przeglądarki po wpisaniu adresu http://localhost/pma/ - dzięki temu szybciej się go wpisuje.

4. Otwórz (lub utwórz w folderze głównym plik) config.inc.php. Jest to skrypt konfiguracyjny phpMyAdmina imożesz tu zmieniać wszystkie ustawienia.

5. Odnajdź dyrektywę $cfg['PmaAbsoluteUri'] i wprowadź do niej pełen adres URL do aplikacji, np.http://localhost/pma.

6.6. Pora na ustawienie połączenia z bazą. phpMyAdmin obsługuje zarówno automatyczną konfigurację, gdziewszystkie wartości podane są w bazie, jak i ręczne logowanie z użyciem formularza WWW (rozwiązaniespotykane u firm hostingowych). My pracujemy u siebie w domu, dlatego wszystkie wartości wypełnimy w plikukonfiguracyjnym. 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.

phpMyAdmin 263

• $cfg['Servers'][$i]['password'] - hasło podanego użytkownika.7. 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.

UruchomienieW przeglądarce otwórz :

http:/ / localhost/ pma

lub :

http:/ / localhost/ phpmyadmin/

jeśli nie skróciłeś katalogu.Zaloguj się i program jest już gotowy do pracy.

Rzut okiem

Ekran startowy phpMyAdmina

Po uruchomieniu ujrzysz ekranstartowy phpMyAdmina. Podczaspracy okno przeglądarki podzielonejest na dwie części: po lewej znajdujesię wąski i ciemny pasek. Domyślnieznajduje się w nim lista rozwijana, zktórej wybieramy interesującą nas bazędanych. Gdy ją wybierzemy, podspodem wyświetli się również listaznajdujących się w niej tabel. Szersza ijaśniejsza część ekranu to częśćoperacyjna - tu wykonujemy wszystkieoperacje.Ekran startowy zawiera różneinformacje o stanie połączenia orazdaje dostęp do niektórych opcji. Poprawej 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 obiektyformularza. 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

phpMyAdmin 264

Podgląd bazy danych

Struktura tabeli

dostęp do różnych ekranówadministracyjnych, np. listy procesówserwera czy edytora uprawnień.

Przejdźmy za pomocą częścinawigacyjnej do naszej bazy produkty.Zarówno w części nawigacyjnej, jak ina ekranie roboczym pojawi nam sięspis tabel, które już utworzyliśmy. Wczęści roboczej dodatkowo widocznajest belka nawigacyjna znastępującymi elementami:

• Struktura - pokazuje listę tabel,czyli to, co właśnie oglądamy.

• SQL - ekran do tworzenia iwykonywania zapytań SQL.

• Szukaj - wyszukiwarka danych• Zapytanie przez przykład -

graficzny edytor zapytań dlanieznających języka SQL(wzorowany na edytorachdostępnych np. w MicrosoftAccess).

• Eksport - narzędzia eksportu bazydanych do pliku SQL lub innych.

• Import - narzędzia do importu bazydanych z pliku SQL. W starszychwersjach phpMyAdmina ekran tenbył 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, lecznajpierw zapoznamy się jeszcze z widokiem pojedynczej tabeli. Możemy do niego przejść, wybierając nazwę zpanelu nawigacyjnego lub klikając na drugą z ikonek w ekranie roboczym przy interesującej nas tabeli. Ujrzymywtedy 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ścidomyślnej. Poniżej znajduje się prosty formularz umożliwiający dodanie nowych pól do tabeli, a jeszcze niżejinformacje dodatkowe: spis indeksów, rozmiar danych w tabeli i statystyka rekordów. Zauważmy, że zmianie uległabelka nawigacyjna. Zniknęła opcja Zapytanie przez przykład, za to doszły dwie nowe:

phpMyAdmin 265

• 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 tylkoaktualnej tabeli.

Tworzenie tabeli

Edytor struktury tabeli

Aby rozpocząć tworzenie nowej tabeli,przełączmy się na widok bazy danych.Pod listą tabel widoczny jest niewielkiformularz zatytułowany "Utwórz nowątabelę". Wpisujemy w nim nazwęnowej tabeli oraz ilość pól, którezamierzamy do niego dodać. Jeśli siępomylisz, nie przejmuj się. Edytorbędzie można łatwo powiększyćjeszcze w trakcie edycji, a ponadtomożna dodawać nowe pola do tabelijuż po jej utworzeniu. Po kliknięciu na"Wykonaj" ukaże się rozbudowanyedytor umożliwiający dokładnezdefiniowanie struktury. Otoomó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 wapostrofy i odseparowanych przecinkami.

• Metoda porównywania napisów - tylko dla pól VARCHAR, TEXT itp. - wybieramy tutaj, według jakiegokodowania 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żnaprzechowywać 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".

phpMyAdmin 266

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 formularzzatytułowany "Utwórz indeks dla X kolumn". Po wybraniu liczby kolumn w indeksie, przejdziemy doszczegół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 orazwybraniu typu (domyślny w MySQL-u to mający większe możliwości InnoDB, ale na co dzień korzysta się główniez wydajniejszego MyISAM).

Modyfikacja tabeliIstnieje 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" zostaniemyprzeniesieni 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 przypadkutworzenia tabeli.

•• Usuwanie pól - za pomocą ikonki krzyżyka. Wcześniej musimy potwierdzić naszą chęć.

Zarządzanie rekordami

Lista zawartości tabeli

W zakładce Przeglądaj możemyobejrzeć zawartość aktualnej tabeli. Naekranie ukaże się lista wszystkichrekordów wraz z wartościamiwszystkich pól. Jest ona porcjowana:naraz pokazywane jest tylko 30, a donastępnych stron przełączamy się zapomocą strzałek. Ikonki przy każdymrekordzie umożliwiają edycję danychlub jego usunięcie. Analogicznaprzeglądarka ukaże nam się, gdy zapomocą zakładki SQL wykonamyzapytanie SELECT lub inne generującejakąś listę wyników.

Zarówno edycja, jak i dodawanienowego rekordu odbywa się wspecjalnym edytorze pokazanym na screenie. Formularz składa się z pięciu kolumn:• Pole - nazwa pola• Typ - informacja o typie możliwych do przechowania danych

phpMyAdmin 267

Dodawanie nowego rekordu

• Funkcja - z tej listy możemywybrać funkcję, przez jaką zostanieprzepuszczona wartość wpisana wpolu "Wartość". Uwaga: niektórefunkcje nie wymagają podawaniażadnego dodatkowego parametru wtamtym polu (np.UNIX_TIMESTAMP() będącyodpowiednikiem time() w PHP).

• Null - jeśli pole zezwala, możemytutaj zaznaczyć, że wstawiamywartość NULL.

• Wartość - dokładna wartość, jakąchcemy wstawić w wybrane pole.

Formularz dodawania umożliwiatworzenie 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 przyciskiemWykonaj, jednak nie korzystaj z niego, lecz z przycisku znajdującego się pod całym formularzem. Inaczejzaakceptujesz 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ć "dodajnowy rekord" zamiast "wróć". Spowoduje to, że po dodaniu rekordów z powrotem zostaniemy przeniesieni doformularza dodawania.

Import i eksport zawartości

Ekran eksportu zawartości

phpMyAdmin jest szczególnie lubianyprzez programistów, gdyż najczęściejto za jego pomocą bazy danychprzenoszone są z lokalnego komputerana właściwy serwer WWW. Służy dotego zakładka Eksport. Abyprawidłowo wyeksportować zawartośćbazy danych, musimy wpierw spędzićchwilkę na konfiguracji:

1.1. W ramce "Eksport" wybieramyinteresujące nas tabele. Jeżelieksportujemy całą bazę, możemykliknąć na "Zaznacz wszystkie".

2. Wybieramy format. DomyślniephpMyAdmin zaproponuje eksportdo pliku SQL, który jest niczym innym, jak listą zapytań CREATE TABLE oraz INSERT, które po uruchomieniuodtworzą dokładną kopię naszej bazy.

3.3. W ramce "Opcje eksportu" zaznaczamy, co chcemy eksportować: samą strukturę, same dane, czy obie rzeczynaraz.

4.4. Dodatkowo, phpMyAdmin umożliwia zachowanie kompatybilności ze starszymi wersjami bazy danych, a nawetinnych serwerów DB! Jeśli stworzyłeś na MySQL 5.0 bazę danych, ale na serwerze jest MySQL 4.0, skorzystaj z

phpMyAdmin 268

listy "Kompatybilność eksportu SQL", a aplikacja wygeneruje zapytania dla wybranej przez Ciebie wersjiserwera. Podobnie możesz postąpić, jeśli chcesz przenieść swoją bazę np. na PostgreSQL (dodajmy, że ten serwerDB także posiada swój "webowy" menedżer zwany oczywiście phpPgAdmin).

5.5. Jeśli nie mamy ochoty, aby naszym monitorem zawładnęły zapytania SQL, zaznaczamy jeszcze opcję "Zapiszjako plik", co spowoduje, że generowany wynik będziemy mogli od razu ściągnąć na nasz komputer, zamiastwyświetlać jego zawartość w przeglądarce.

6.6. Klikamy przycisk "Wykonaj".Importowanie bazy danych odbywa się za pomocą zakładki Import. Wskazujemy w niej plik SQL na naszym dyskuze strukturą bazy, wybieramy kodowanie i klikamy "Wykonaj". Pamiętaj, że PHP ma limit wykonywaniaograniczony do 30 sekund. Jeśli twój plik SQL ma naprawdę potężne rozmiary, będziesz mógł kontynuować późniejjego wgrywanie od wybranego zapytania (ramka "Import częściowy"). W starszych wersjach phpMyAdmina importodbywał się za pomocą zakładki SQL, ale według niemal identycznej procedury.

ZakończenieOd tej pory wszystkie bazy danych i tabele będziemy tworzyć już z użyciem phpMyAdmina. Dzięki temu narzędziupraca z bazami jest naprawdę przyjemna, i co ważniejsze, wygodna. phpMyAdmin stał się tak popularny, że na polumenedżerów dla bazy MySQL w zasadzie nie ma żadnej konkurencji, a twórcy menedżerów dla innych serwerówDB 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.

Przypisy[1] http:/ / www. phpmyadmin. net

Studium przypadku: System newsów 269

Studium przypadku: System newsów

System newsówSpróbujmy teraz zaimplementować zdobytą przez nas wiedzę w praktyce. Napiszemy prosty system newsów zpodziałem na kategorie i możliwością dodawania komentarzy. Podręcznik ten pokaże, jak zacząć, natomiast twoimzadaniem będzie dopisanie (z pomocą wskazówek) wszystkich dodatków niezbędnych do tego, aby system był wpełni funkcjonalny. Nowością w porównaniu do księgi gości będzie to, iż nie będziemy już umieszczać kodu HTMLbezpoś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 omawianiasystemów szablonów.

Projekt bazy danychSystem 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:1.1. Istnieje grupa kategorii2.2. Każda kategoria może zawierać dowolną liczbę newsów, ale pojedynczy news może należeć tylko do jednej z

nich.3.3. 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_numprzechowujące aktualną liczbę newsów wewnątrz kategorii. Musimy pamiętać, aby nasz system poprawniezmniejszał 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;

Studium przypadku: System newsów 270

Tabela zaczyna się od pól informacyjnych. Z punktu widzenia projektanta bazy najistotniejsze są jednak dwaostatnie. 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 dwaosobne indeksy, w zasadzie nic byśmy nie uzyskali. Popatrzmy na to tak: nigdy nie wyświetlamy wszystkichkomentarzy, jakie mamy w bazie. Zawsze są one powiązane z jakimś konkretnym newsem, dlatego tak istotne jestodzwierciedlenie tego w strukturze indeksów. Przy osobnych indeksach pole date zostanie posortowane globalnie, awybierając komentarze tylko dla pojedynczego newsa, system DB i tak będzie musiał przeskanować całą tabelę, abyje względem tejże daty wybrać.Od strony bazy danych to tyle. Przejdźmy do kodowania.

Funkcje podstawoweNapiszemy teraz plik functions.php. Umieścimy w nim różne podstawowe funkcje. W tej chwili jest ich stosunkowoniewiele: łą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. Jegozawartość 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();

Studium przypadku: System newsów 271

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:1. initSystem() - inicjacja systemu; nawiązuje połączenie z bazą danych i włącza buforowanie wyjścia.2. doneSystem() - aktualnie tylko kończy buforowanie wyjścia. Być może znajdziesz dla niej jakieś ciekawe

dodatkowe zastosowania.3. commentsAllowed() - funkcja zwraca true, jeżeli internauta ma prawo dodawać komentarze i false, jeżeli już

takowy niedawno dodał.4. validTextField() - prosta funkcja do kontroli danych. Zwraca true, jeżeli długość tekstu mieści się w podanym

zakresie.

Bazy danych - co dalej? 272

Bazy danych - co dalej?

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ę donich z poziomu PHP. Nie powinieneś jednak poprzestać tylko i wyłącznie na zawartych w niniejszym podręcznikuinformacjach. Ilu programistów, tyle możliwych organizacji danych na stronie WWW, a to musi znaleźć sweodzwierciedlenie w projekcie bazy danych. Zagadnieniu temu można poświęcić niejedną książkę, dlatego ćwicz ieksperymentuj, ile się da.Generalnie, przymierzając się do zaprojektowania bazy danych dla jakiejś witryny, możemy skupić się albo na jaknajlepszym dopasowaniu jej do wymagań, albo na elastyczności. Pierwsze podejście zazwyczaj nie wymagatworzenia 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 przezprogramistę, stąd też jest to wysoce niewydajne przy pisaniu pakietów wielokrotnego użytku. Odwrotna filozofiazakł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 postaciartykułó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? Przyjmujemykonwencję, że jeżeli zawiera ono wartość 0, mamy do czynienia z artykułem, gdy 1 - z recenzją itd. Od strony PHPmożemy teraz niezwykle łatwo napisać jednolity panel do zarządzania nimi. Jeżeli w przyszłości klient zażyczysobie dodania kolejnego rodzaju tekstu, sprowadzi się to do przydzielenia mu kolejnego numeru i dodania doformularzy edycyjnych.Nie wszystkie informacje da się tak prosto przechowywać w bazie danych i trzeba mieć tego świadomość. Doodwzorowania 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, czasemmusimy ruszyć głową i wymyślić jakąś ciekawą koncepcję. W jej realizacji przydatne będą bardziej zaawansowanemożliwości języka SQL takie, jak klauzule JOIN, unie, widoki czy wyzwalacze (triggery). Ich dokładny opisznajduje się w dokumentacji MySQL-a pod adresem http:/ / dev. mysql. com/ doc [1]. W sieci znajdziemy takżewiele 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ęzykaSQL 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 nijaknie można z taką zawartością sprawdzić, czy w rzeczywistości witryna nie padnie klientowi przy tysiącu rekordów zpowodu przeciążenia. Poprawianie niezoptymalizowanych, nieprzemyślanych baz jest trudnym procesem i na pewnonarazi 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 tymnarzędziem. Jego szersze omówienie przekracza możliwości tego podręcznika, dlatego nie poprzestawaj nazawartych tu informacjach. Ćwicz, eksperymentuj, nie bój się sięgać po Google i dodatkowe źródła. W następnejczęści podręcznika poznamy systemy szablonów.

Bazy danych - co dalej? 273

Przypisy[1] http:/ / dev. mysql. com/ doc/

274

Systemy szablonów

Czym jest system szablonów?

Czym jest system szablonów?W omówionych do tej pory zagadnieniach i przykładach milcząco zakładaliśmy, że nasz kod PHP bezpośredniogenerował HTML dla przeglądarki. Pobierając dane z bazy natychmiast obudowywaliśmy je znacznikami iposyłaliśmy w świat. Jest to cecha charakteryzująca skrypty początkujących programistów PHP, która jednak rodzidużo problemów z przenośnością i elastycznością tworzonego kodu. W następnych rozdziałach poznamy tzw.systemy szablonów, czyli biblioteki umożliwiające ich rozwiązanie.

Idea systemu szablonówOd poznania programowania obiektowego powoli przestajemy patrzeć na aplikację WWW jak na monolitycznyblok, w którym wszystkie elementy są ściśle i nierozerwalnie ze sobą związane. Przekonaliśmy się już, że o wielelepszym podejściem jest podział aplikacji na warstwy zajmujące się różnymi zadaniami i komunikujące międzysobą. Przyjrzyjmy się zatem procesowi generowania kodu HTML. Opisując go możemy powiedzieć, że skrypt musinajpierw pobrać skądś dane, a następnie opakować je czymś, co umożliwi ich wyświetlenie w przeglądarce wzamierzony sposób. Wyraźnie widać tutaj dwa etapy, które - jak się okazuje - świetnie nadają się do rozdzielenia nadwie warstwy.

Warstwa logiki biznesowej to warstwa aplikacji odpowiadająca za przetwarzanie, pobieranie i obróbkę danych. W jej skład wchodząwszystkie operacje do komunikacji z plikami, bazami danych oraz algorytmy do przeprowadzania wszelkich obliczeń na danych.

Warstwa prezentacji to warstwa aplikacji odpowiadająca za wyświetlenie (prezentację) danych użytkownikowi. To tutaj decydujemy, wjakim formacie wyświetlić dane, które z nich wyświetlić oraz jak je wyświetlić.

Czym jest system szablonów? 275

Zasada działania systemów szablonów.

System szablonów (ang. template engine) to biblioteka, którapozwala oddzielić warstwę prezentacji od logiki biznesowej.Podstawowym pojęciem jest tutaj szablon, czyli prosty pliktekstowy ze szkieletem wynikowego kodu HTML oraz zestaweminstrukcji mówiących, co, gdzie i jak wyświetlić. Aplikacja WWWnajpierw pobiera wszystkie dane, przetwarza je i umieszcza wsystemie szablonów wraz z informacją o tym, jakiego szablonuużyć, jednak bez zwracania uwagi na szczegóły procesuwyświetlania. System szablonów wczytuje szablon i wykonuje go,osadzając w nim dane ze skryptu. Wynikiem jest gotowy kod, któryjest posyłany do przeglądarki.

Systemy szablonów wykorzystywane są w zdecydowanejwiększości dużych projektów programistycznych nawet, jeśli nie sąjawnie nazwane w ten sposób. Separacja pozwala na bardziejniezależy rozwój obu warstwy aplikacji oraz większąniezawodność; zajmując się algorytmami przetwarzania nie plącząnam się wokół wstawki kodu HTML, a pracując nad warstwąprezentacji nie ma obawy, że spowodujemy błąd w mechanizmachpobierania z bazy. Migracja na nową szatę graficzną sprowadza siędo napisania nowych szablonów, a co więcej - możemyprzechowywać kilka zestawów szablonów i dać użytkownikowiwybór, którego z nich chce użyć.Nie odpowiedzieliśmy na jeszcze jedno cisnące się na usta pytanie -czym właściwie jest szablon? Jest to pewien plik tekstowy, rodzajskryptu zawierający statyczny kod wysyłany do przeglądarki oraz reguły mówiące, jakie dane gdzie umieścić.Oczywiście do wyrażania tych reguł potrzebny jest jakiś dodatkowy meta-język i ze względu na niego możemywyróżnić dwa główne rodzaje systemów szablonów:1. PHP jako język szablonów - reguły osadzania danych opisywane są przy pomocy zwykłych wstawek kodu PHP.2. Dedykowany język szablonów - system szablonów wprowadza specjalny język do zapisu reguł oraz dostarcza

parser do jego przetwarzania.Każde z rozwiązań ma swoje wady i zalety, które zaraz omówimy.

PHP jako język szablonówPrzykładowy szablon napisany w PHP może wyglądać następująco:

<html>

<head>

<title><?=$title?></title>

</head>

<body>

<?if($user == 'Adam'){?>

Hello, <strong><?=$user?></strong>

<?}else{?>

Hello, <?=$user?>

<?}?>

</body>

Czym jest system szablonów? 276

</html>

Zamiast korzystania z konstrukcji <?php echo $zmienna;?>, użyliśmy tu krótszego odpowiednika dającegoten sam efekt, a mianowicie <?=$zmienna?>. Jednak aby takie rozwiązanie działało, musimy w pliku php.iniserwera ustawić short_open_tag na "on".Korzystamy tutaj ze znanych nam już pętli, instrukcji warunkowych i zmiennych, co umożliwia bardzo szybkiewgryzienie się i wykorzystanie doświadczeń, które już znamy. Z drugiej strony czysty PHP poza pętlami iwarunkami nie oferuje praktycznie nic rzeczywiście przydatnego do pisania dużej liczby szablonów i co więcej,bywa dość niewygodny. Dlatego systemy tego typu udostępniają dodatkowo pokaźny zbiór tzw. helperów, np. wpostaci funkcji lub metod statycznych jakiejś klasy, które zamykają skomplikowane fragmenty kodu w prostymwywołaniu. Przykładowo, zamiast pisać skomplikowany kod do sklejania adresu URL z danych, możemy miećfunkcję link(), do której podamy jedynie argumenty i tytuł odnośnika, a od razu otrzymamy gotowy znacznik<a>.Zalety:1.1. Prostota użycia - możemy wykorzystać doświadczenia, które już znamy.2.2. Potężna siła wyrazu - PHP to pełnoprawny język programowania, dlatego w szablonach możemy posługiwać się

wszystkimi elementami składni do osiągnięcia żądanego celu.Wady:1.1. Bez helperów praktycznie niezdatny do użycia na dłuższą metę. Ponadto same helpery są czasem wyjątkowo

skomplikowane, szczególnie gdy próbujemy dostosować je do własnych potrzeb.2.2. Bardzo rozwlekła składnia i brak rozumienia struktury dokumentu HTML. Bez ścisłej dyscypliny bardzo łatwo

stworzyć mocno nieczytelny kod.3.3. Bierzemy język PHP ze wszystkimi jego niekonsekwencjami, których nie mamy szansy zmienić i poprawić.

Dedykowany język szablonówPod nazwą dedykowany język szablonów kryje się cała rodzina różnorodnych języków używanych przez różnesystemy szablonów. Poniżej możemy zobaczyć przykład szablonu utworzonego w takim hipotetycznym języku:

<html>

<head>

<title>{TITLE}</title>

</head>

<body>

{IF:USER == 'Adam'}

Hello, <strong>{USER}</strong>

{ELSE}

Hello, {USER}

{/IF}

</body>

</html>

Cel przyświecający ich powstawaniu jest jeden: PHP jest zbyt nieczytelny, zbyt rozwlekły i ma zbyt dużo niekonsekwencji, by można było w nim łatwo tworzyć szablony, dlatego spróbujemy stworzyć coś lepszego, pozbawionego określonych wad. PHP powstał dlatego, że ktoś stwierdził, iż C czy C++ nie nadaje się na dłuższą metę do wygodnego tworzenia stron WWW. C i C++ powstały, gdyż uznano, że wygodniej jest pisać programy w ten sposób, niż w czystym assemblerze. Przykłady można mnożyć i analogiczna sytuacja ma miejsce tutaj. Czy się to udaje, zależy już od pomysłowości oraz umiejętności programisty tworzącego określony system. Teoretycznie

Czym jest system szablonów? 277

tworząc własny język jesteśmy ograniczeni jedynie przez prawa fizyki i matematyki oraz przez własną wyobraźnię.W praktyce wiele z powstających języków ogranicza się głównie do udostępnienia wąskiego podzbioru PHP z<?php ?> zamienionym na klamerki, a w wielu popełniono równie poważne błędy projektowe, jak w PHP. Z tegowłaśnie powodu nie cieszą się one uznaniem części programistów patrzących przez pryzmat tych nieudanych prób.Zalety:1.1. Dobrze zaprojektowany dedykowany język szablonów może być dużo efektywniejszy w użyciu, niż PHP.2.2. Możliwość pozbycia się wad i niedoróbek PHP, a także wielu detali technicznych, ukrywając je za prostymi

konstrukcjami języka.Wady:1.1. W praktyce rzadko kiedy to się udaje: brak umiejętności, pomysłowości, doświadczenia.2.2. Nawet jeśli konstrukcje języka są proste w użyciu, trzeba się ich najpierw nauczyć, zwłaszcza jeżeli odbiegają

pod względem zasady działania od znanych już nam wzorców.

KontrowersjeSystemy szablonów to jedne z najbardziej kontrowersyjnych rodzajów bibliotek. W Internecie oraz w publikacjachmożna bardzo łatwo spotkać mnóstwo wzajemnie wykluczających się poglądów. Poniżej przedstawiony jestprzegląd najważniejszych kontrowersji, argumentów krytyków oraz punktów widzenia, a także komentarz, jakbędziemy traktować je w tym podręczniku.

Problem: PHP to system szablonów.Wielu programistów nie stosuje rozróżnienia na systemy szablonów oraz języki szablonów, nazywając np. PHPsystemem szablonów, albo degradując pojęcie systemu szablonów do języka. Powiedzieliśmy na początku, żesystem szablonów jest biblioteką, czyli zawiera jakieś klasy, funkcje, interfejsy, które umożliwiają przekazywaniedanych z warstwy logiki do szablonów. PHP sam w sobie nic takiego nie posiada - dostarcza on pętli, instrukcjiwarunkowych, jakichś funkcji do przetwarzania danych, czyli ewidentnych narzędzi do pisania reguł i skryptów.Dopiero gdy dodamy do niego odpowiednie klasy i kod pozwalający uruchomić jakiś szablon z określonymi danymi,możemy mówić o systemie szablonów, natomiast bez tego jest to tylko język. W drugim kierunku sytuacja jestanalogiczna. Język szablonów to jedynie sposób zapisu reguł osadzania danych, który bez odpowiedniego API nicnie znaczy.W tym podręczniku będziemy konsekwentnie trzymać się zasady, że system szablonów to biblioteka, a językszablonów to język.

Problem: zawężanie pojęcia systemów szablonów do systemów z dedykowanym językiem.Niektórzy programiści mówią, że nie używają systemów szablonów, lecz PHP, ponieważ nie widzą sensu w uczeniusię kolejnego języka. Przypomnijmy sobie, co powiedzieliśmy w poprzednim problemie - PHP to język szablonów, anie system. Różnice w działaniu systemów szablonów korzystającego z PHP oraz ze swojego własnego językasprowadzają się jedynie do tego, że w tym drugim przypadku trzeba szablony przetworzyć we własnym zakresie.Poza tym ich zasada funkcjonowania, zakres obowiązków i zadań jest dokładnie taki sam. Co więcej, PHP jestjęzykiem takim, jak każdy inny. To, że akurat biblioteka jest też w nim napisana, nie wyróżnia go w żaden konkretnysposób - ostatecznie kompilator np. dla języka C pozostaje kompilatorem bez względu na to, w czym go napisano.W tym podręczniku przyjmujemy zasadę: jeśli wygląda jak kaczka i kwacze jak kaczka, to jest to kaczka.

Problem: systemy szablonów to dodatkowa warstwa abstrakcji i dodatkowy narzut.Odpowiedź na to pytanie wiąże się bezpośrednio z dwoma powyższymi podpunktami. System szablonów, a język szablonów to dwie różne rzeczy i nie należy ich mylić. Jeśli chcemy oddzielić warstwę logiki od warstwy prezentacji w aplikacji WWW, nie ma innego sposobu, jak użyć szablonów. Do przetwarzania szablonów zawsze potrzebny jest nam jakiś interfejs bez względu na to czy piszemy je w PHP czy w innym języku. Ponadto nie wolno na aplikację patrzeć nigdy jedynie przez pryzmat wydajności i narzutów tego czy innego elementu. Aplikacja w pierwszej

Czym jest system szablonów? 278

kolejności ma spełniać określone wymagania i udostępniać określoną funkcjonalność. Jeżeli jednym z wymagań jestelastyczna budowa i podział na warstwy, wtedy ten dodatkowy narzut jest narzutem, który musimy ponieść, abyzrealizować cel.W tym podręczniku przekonamy się, że każdy system szablonów to pewna warstwa abstrakcji bez względu na to, zjakiego języka korzysta oraz że są one powszechnie stosowane nawet, jeśli nie nazywają się systemami szablonów.

Problem: przetwarzanie dedykowanego języka szablonów jest niewydajne.Argument ten jest słuszny jedynie w przypadku systemów szablonów, które od początku do końca zajmują sięsamodzielnie przetwarzaniem takiego języka. Sęk w tym, że takie systemy można policzyć na palcach jednej ręki.Prawie wszystkie istniejące obecnie systemy szablonów, które oferują swój własny język, korzystają z pewnejsztuczki: zamiast wykonywać szablon od zera, kompilują go do postaci... kodu PHP i wykonują. Co więcej,kompilacja wykonywana jest tylko wtedy, gdy zmieniony zostanie oryginalny szablon źródłowy; w przeciwnymwypadku używana jest od razu zapisana na dysku skompilowana wersja, dokładnie tak samo jak w systemachkorzystających z PHP. Różnice w wydajności sprowadzają się tylko do tego, kto taki szablon PHP potrafi napisaćlepiej: programista czy komputer, a to już zależy od konkretnej implementacji.

ZakończenieDowiedzieliśmy się już, czym są systemy szablonów i dlaczego są one takie ważne. Pora przejść od teorii dopraktyki. Na początku zilustrujemy to, co zostało tu powiedziane, próbując napisać prosty, edukacyjny systemszablonów. Pomoże nam to zrozumieć zasadę działania i kryjące się za tym mechanizmy. Następnie pokażemywybrane trzy systemy szablonów oraz sposób ich użycia.

Prosty edukacyjny system szablonów

Prosty edukacyjny system szablonów

Uwaga!Do uruchomienia przykładów zawartych w tym rozdziale wymagane jest PHP w wersji co najmniej 5.3.0!

Celem tego rozdziału jest zbudowanie prostego systemu szablonów, aby zapoznać się w praktyce z zasadamidziałania tego typu bibliotek. Nasz system opierać się będzie na języku PHP, gdyż budowa odpowiedniegokompilatora to temat, którym można by zapełnić całą książkę i który dość mocno odbiega od właściwegozagadnienia.

ProjektZanim zaczniemy cokolwiek pisać, zastanówmy się, co system szablonów musi robić. W poprzednim rozdzialepowiedzieliśmy, że jest to warstwa pośrednia między resztą aplikacji, a przeglądarką: musi odebrać dane z warstwylogiki biznesowej oraz na podstawie odpowiedniego szablonu wygenerować kod, który zostanie posłany doprzeglądarki. Stąd znamy już pierwszą funkcjonalność: możliwość przekazania danych ze skryptu do szablonu.Szablon jest plikiem, który musi być zlokalizowany gdzieś na dysku. Aplikacja WWW, przekazując dane, musitakże wybrać określony szablon. Potrzebna jest nam zatem jakaś prosta konfiguracja, w której programista będziemógł poinformować, gdzie na dysku zlokalizowane są szablony oraz ustawić ewentualne inne opcje. Na końcu wartopomyśleć o udostępnieniu jakichś helperów, które pozwolą twórcy szablonu skupić się na merytorycznej treściszablonu, zamiast na technicznych szczegółach każdego możliwego zadania.Nasz projekt napiszemy w oparciu o programowanie obiektowe. Poniżej przedstawiona jest lista klas:1. Wikibooks\Tpl\Engine - główna klasa zarządzająca konfiguracją.

Prosty edukacyjny system szablonów 279

2. Wikibooks\Tpl\View - klasa reprezentująca szablon z przypisanymi do niego danymi i umożliwiająca jegowykonanie, który na nasze potrzeby nazwiemy sobie widokiem.

3. Wikibooks\Tpl\Helper - klasa z helperami.Stosujemy się tutaj do standardu nazewnictwa i lokowania klas w plikach omówionego w rozdziale Automatyczneładowanie.

Klasa Wikibooks\Tpl\EnginePierwsza z klas nie będzie zbyt rozbudowana - jedynie, co będzie robić, to przechowywać konfigurację oraz służyćza fabrykę widoków.

<?php

namespace Wikibooks\Tpl;

class Engine

{

private $_templateDir;

private $_extension;

public function __construct($templateDir, $extension = 'php')

{

$this->setTemplateDir($templateDir);

$this->setExtension($extension);

} // end __construct();

public function setTemplateDir($dir)

{

if(!is_dir($dir))

{

throw new RuntimeException('Podany katalog szablonów '.$dir.'

jest niedostępny.');

}

if(strlen($dir) > 0 && $dir[strlen($dir) - 1] != '/')

{

$dir .= '/';

}

$this->_templateDir = $dir;

} // end setTemplateDir();

public function getTemplateDir()

{

return $this->_templateDir;

} // end getTemplateDir();

public function setExtension($extension)

{

if(!ctype_alnum($extension))

{

Prosty edukacyjny system szablonów 280

throw new DomainException('Nazwa rozszerzenia może zawierać

wyłącznie litery i cyfry.');

}

$this->_extension = $extension;

} // end setExtension();

public function getExtension()

{

return $this->_extension;

} // end getTemplateDir();

public function createView($viewName)

{

return new View($viewName,

$this->_templateDir.$viewName.'.'.$this->_extension);

} // end createView();

} // end Engine;

W tej klasie mamy cztery metody do zarządzania konfiguracją. setTemplateDir() pozwala ustawićprogramiście katalog, w którym przechowywane są szablony, zaś setExtension() pozwala wybrać domyślnerozszerzenie plików. Towarzyszą im odpowiednie gettery. Zgodnie z regułami programowania obiektowego nieupychamy całej funkcjonalności w jednej klasie, lecz rozdzielamy zadania między kilka mniejszych. Dlatego w tymmiejscu jedyna rzecz, która ma coś więcej wspólnego z szablonami, to metoda createView() służąca za fabrykęwidoków. W imieniu konkretnego widoku buduje mu odpowiednią ścieżkę do pliku, bazując na swojej konfiguracji.

Klasa Wikibooks\Tpl\ViewKlasa View reprezentować będzie pojedynczy szablon oraz przypisane do niego dane z warstwy logiki. Jeśli chcemyskomponować wyjście HTML z kilku mniejszych szablonów, np. aby mieć wspólny szablon z nagłówkiem i stopkąstrony, powinniśmy utworzyć dla każdego z nich odpowiednie widoki.

<?php

namespace Wikibooks\Tpl;

class View

{

private $_template;

private $_path;

private $_data = array();

public function __construct($template, $path)

{

$this->_template = $template;

if(!file_exists($path))

{

throw new RuntimeException('Określony szablon

'.$template.' nie istnieje.');

}

Prosty edukacyjny system szablonów 281

$this->_path = $path;

} // end __construct();

public function getTemplate()

{

return $this->_template;

} // end getTemplate();

public function __set($name, $value)

{

$this->_data[$name] = $value;

} // end __set();

public function __get($name)

{

return $this->_data;

} // end __get();

public function render()

{

extract($this->_data);

require($this->_path);

} // end render();

} // end View;

Każdy widok przechowuje informacje o szablonie, jaki reprezentuje, a także o danych, jakie już zdążyliśmy do niegoprzypisać. Służy do tego tablica $_data, a obsługiwana jest poprzez metody magiczne __set() i __get().Gdy chcemy wyświetlić szablon, korzystamy z metody render(). Pojawia się w niej wywołanie funkcjiextract(). Rozpakowuje ona zawartość tablicy jako zmienne, czyli np. z elementu foo powstanie nam zmienna$foo. Ułatwi nam to odwoływanie się do danych skryptu w szablonie. Ostatnia rzecz to operacja require iwykonanie szablonu. Jak widać, nie ma tu nic skomplikowanego.

Klasa Wikibooks\Tpl\HelpersNa koniec pozostało nam napisać kilka helperów. Będą one zapakowane jako metody statyczne w klasie Helpers:

<?php

namespace Wikibooks\Tpl;

class Helpers

{

static public function linkTo($url, $title)

{

return '<a href="'.htmlspecialchars($url).'">'.$title.'</a>';

} // end linkTo();

static public function pluralPl($number, $singular, $plural1,

$plural2)

{

Prosty edukacyjny system szablonów 282

if($number == 1)

{

return $singular;

}

elseif($number > 1 && $number < 5)

{

return $plural1;

}

return $plural2;

} // end pluralPl();

} // end Helpers;

Omówienie:1. linkTo() - generuje odnośnik.2. pluralPl() - pozwala w prosty sposób utworzyć liczbę mnogą (aby móc wyświetlać np. "1 produkt"/"2

produkty").Oczywiście jest to tylko zalążek - duże projekty korzystają z dziesiątek różnych helperów, np. do generowaniaformularzy, nawigacji i wielu innych elementów. Zauważmy, że nawet te, które podaliśmy, można by rozbudować.Przykładowo, do linkTo() moglibyśmy dorobić metodę, która pozwoli na automatyczne dodanie odpowiedniejklasy CSS wszystkim odnośnikom tworzonym za jej pomocą. Pozostawiamy to jako ćwiczenie.

UżycieJak widać, nasz prosty system szablonów nie był zbyt trudny do napisania. Rzeczywiście, podstawowe mechanizmystojące za tego typu skryptami nie są zbyt wyszukane i bazują na jedynie kilku elementach języka. Oczywiście jeślichcielibyśmy dodać więcej funkcjonalności, stopień zaawansowania kodu znacznie by się skomplikował, ale nie jestto celem tego rozdziału.Przyjrzyjmy się teraz, jak korzystać z naszego systemu. Na początku napiszemy szablon, który umieścimy wkatalogu /templates:

<?php use \Wikibooks\Tpl\Helpers as Helpers; ?>

<html>

<head>

<title>Mój pierwszy szablon</title>

</head>

<body>

<h1>Witaj,</h1>

<p>Witaj, <?php echo $name; ?></p>

<p>Aktualnie na stronie <?php echo Helpers::pluralPl($userNum, 'jest 1 użytkownik', <br />

'są '.$userNum.' użytkownicy', 'jest '.$userNum.' użytkowników'); ?>.</p>

<?php if(is_array($products) && sizeof($products) > 0){ ?>

<p>Co chciałbyś dziś kupić?</p>

<ul>

<?php foreach($products as $product){ ?>

<li><?php echo $product['nazwa']; ?></li>

<?php } ?>

</ul>

Prosty edukacyjny system szablonów 283

<p><?php echo Helpers::linkTo('zamowienie.php', 'Złóż zamówienie!'); ?></p>

<?php }else{ ?>

<p>Niestety nie mamy żadnych produktów do zaoferowania.</p>

<?php } ?>

</body>

</html>

W pierwszej linijce musieliśmy umieścić import klasy helperów z przestrzeni nazw, aby skrócić jej nazwę. Poza tymw pliku mamy niemal wyłącznie kod HTML z paroma wstawkami PHP. Widzimy, że wszystkie dane ze skryptu:$name, $userNum oraz $products zostały rozpakowane do postaci zmiennych. Helpery umożliwiły namskrócenie niektórych typowych operacji. Do wyświetlenia listy produktów użyliśmy pętli foreach obudowanejwarunkiem sprawdzającym czy nie jest ona pusta.A oto i skrypt korzystający z tego szablonu. Dane pobieramy z tabeli produkty, którą poznaliśmy w rozdziale obazach danych. Jeśli jej nie masz, skocz tam i zainstaluj ją. Zakładamy także, że w pliku SplClassLoader.phpznajduje się automatyczna ładowarka klas SPL omówiona w rozdziale Automatyczne ładowanie.

<?php

require('./SplClassLoader.php');

$loader = new SplClassLoader('Wikibooks', './');

$loader->register();

header('Content-type: text/html;charset=utf-8');

try

{

// Inicjujemy, co trzeba

$pdo = new PDO('mysql:host=localhost;dbname=test', 'root',

'root');

$pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$tpl = new \Wikibooks\Tpl\Engine('./templates/');

// Warstwa logiki aplikacji - pobieranie danych

$productList = array();

$stmt = $pdo->query('SELECT * FROM produkty');

while($row = $stmt->fetch(PDO::FETCH_ASSOC))

{

$productList[] = $row;

}

$stmt->closeCursor();

// Przekazujemy dane do warstwy prezentacji

$view = $tpl->createView('szablon');

$view->products = $productList;

$view->name = 'Adam Kowalski';

$view->userNum = rand(0, 10);

// Wyświetlamy wszystko

$view->render();

Prosty edukacyjny system szablonów 284

}

catch(Exception $exception)

{

die('Wystąpił błąd: '.$exception->getMessage());

}

W naszym skrypcie znalazły się tylko instrukcje dotyczące przetwarzania danych. Jest on o wiele czytelniejszy niżgdyby ten sam kod HTML próbować umieścić tutaj. Możemy skupić się na samym procesie przetwarzania, bezzajmowania się wyświetlaniem. Jedyne co robimy, to kierujemy wszystko, co wygenerowaliśmy, do systemuszablonów.

Czy warto tworzyć własny system?Przedstawiony powyżej system szablonów jest bardzo prymitywny i z pewnością nie nadaje się do żadnychpoważniejszych zastosowań. Programiści często zadają pytania czy lepiej użyć gotowej biblioteki czy też napisaćwłasną. Za argument często podawana jest "zbyt duża funkcjonalność" gotowych rozwiązań, która budzi pewneprzerażenie. Zwłaszcza początkujący mają tendencję do twierdzenia, że jest im ona niepotrzebna. Tymczasemrzeczywistość ma się zupełnie inaczej. Patrząc na artykuły w sieci oraz nawet na ten rozdział łatwo dojść downiosku, że jedyne, co nam potrzeba do szczęścia, to if i foreach, jednak nic bardziej mylnego! Już na typowyminternetowym blogu pojawia się wiele elementów, których wyrażanie za pomocą tych dwóch konstrukcji jest conajmniej męczące. Mamy stronicowanie, wszelkiego rodzaju elementy nawigacyjne, a ilość zależności "jeśli X,wyświetl Y" jest bardzo duża. Bazując jedynie na najprymitywniejszych elementach tracimy mnóstwo czasu nawymyślanie koła od zera, podczas gdy tzw. "gotowe" systemy szablonów oferują od razu gotowe, sprawdzonerozwiązania. Twierdzenie, że wystarczy mi podstawowa funkcjonalność to mit, na którym przejechało się już wieleosób. Nawet jeśli nie wykorzystamy wszystkiego, o wiele lepiej mieć w zanadrzu odpowiednie narzędzia, niżzorientować się, że klient oczekuje od nas czegoś, a my nie możemy mu tego dać, ponieważ nasze biblioteki są zbytprymitywne. Jeśli chodzi o wydajność, w przypadku systemów szablonów nie do końca sprawdza się zasada większyznaczy wolniejszy. Jak wspomnieliśmy, wiele systemów szablonów to w rzeczywistości typowe API doprzekazywania danych ze skryptu oraz kompilator, który jest ładowany tylko wtedy, gdy jest potrzebny. Dużafunkcjonalność jest uzyskiwana praktycznie zerowym kosztem, a główna różnica wydajnościowa między takimprostym systemem, a gotowym pakietem będzie wynikać z różnych czasów ładowania samej biblioteki przezinterpreter.System szablonów warto napisać samodzielnie jedynie w dwóch sytuacjach:1.1. W celach edukacyjnych tak, jak to zrobiliśmy powyżej.2.2. Gdy faktycznie mamy ciekawy pomysł na system szablonów i chcemy go realizować.Przy czym zadanie drugie jest znacznie trudniejsze zwłaszcza, gdy będziemy chcieli napisać własny kompilator. Wwiększości przypadków skończy się to jedynie na udostępnieniu małego podzbioru PHP w postaci pętli i prostychwarunków, który przy pierwszym poważniejszym zadaniu będzie bardziej ograniczał, niż pomagał. Najlepszesystemy szablonów powstawały latami rozwijane przez doświadczonych programistów i szansa, że zrobimy cośchoć w połowie tak funkcjonalnego "z marszu", jest minimalna.

ZakończeniePoznaliśmy już, jak systemy szablonów działają w praktyce i jak są zbudowane, a także dowiedzieliśmy się,dlaczego w rzeczywistych projektach nie warto jest tworzyć ich samodzielnie. Pora zatem poznać kilka gotowychsystemów.

Open Power Template 285

Open Power Template

Open Power TemplateOmówiony w poprzednim rozdziale Savant to doskonały przykład biblioteki wykorzystującej PHP jako językszablonów. W Open Power Template sytuacja odwraca się o 180 stopni. Ten system udostępnia swój własny,XML-owy język, którego cechą szczególną jest deklaratywne podejście do tworzenia szablonów. Zamiast skupiaćsię na szczegółach implementacyjnych, twórca szablonu powinien jedynie powiedzieć, co chce osiągnąć i zlecićkompilatorowi wygenerowanie odpowiedniego kodu, który spełni jego życzenia. Pomaga w tym rozumieniestruktury HTML szablonu oraz unikalna cecha, tzw. formaty danych. Aby nie być gołosłownym, zaprezentujemyponiżej przykładowy szablon:

<?xml version="1.0" ?>

<opt:root>

<opt:prolog />

<opt:dtd template="html5" />

<html>

<head>

<title>{$pageTitle}</title>

</head>

<body>

<p>Polecamy nasze produkty:</p>

<ul>

<li opt:section="products"><a parse:href="$products.url">{$products.name}</a></li>

</ul>

</body>

</html>

</opt:root>

Oprócz tego, Open Power Template udostępnia obiektowy interfejs programistyczny dla PHP 5.2/5.3 dostosowanydo potrzeb integracji z popularnymi frameworkami, które niebawem poznamy. Do naszej dyspozycji są trzynajważniejsze elementy interfejsu:1. Opt_Class - główna klasa zarządzająca konfiguracją.2. Opt_View - klasa reprezentująca tzw. widok, czyli określony szablon i towarzyszące mu dane ze skryptu:3. Opt_Output_Interface - interfejs systemu wyjścia, który mówi, gdzie wysłać wygenerowany kod.Podobnie jak w naszym prostym systemie edukacyjnym, nasza aplikacja WWW tworzy sobie odpowiednią liczbąwidoków i przekazuje do nich dane. Jednak zamiast wykonywać je od razu, przekazuje je do jednego z systemówwyjścia, który zajmuje się wykonaniem szablonu i wysłaniem wyniku w określone miejsce. Przykładowo, jeśliskorzystamy z systemu wyjścia Opt_Output_Http, dane polecą do przeglądarki, a oprócz tego możemy teżstworzyć sobie system wyjścia Email, który podany szablon potraktuje jako np. treść wiadomości e-mail, którątrzeba wysłać pod określony adres.

Open Power Template 286

InstalacjaBibliotekę można pobrać ze strony www.invenzzia.org [1] - pobieramy najnowsze wydanie z gałęzi 2.0.x, które jestomówione w tym podręczniku. W ściągniętym archiwum znajdziemy katalog /lib z właściwym kodemźródłowym biblioteki. Pozostałe katalogi to przykłady, dokumentacja oraz testy. Przenosimy wspomniany kataloggdzieś do drzewa katalogowego naszej aplikacji WWW.Druga czynność to stworzenie folderów na szablony. Pierwszy z nich nazwijmy /templates - będą tamźródłowe wersje szablonów. W drugim, /templates_c Open Power Template będzie zapisywać skompilowanewersje, dlatego upewnijmy się, że PHP posiada do niego prawa zapisu.Open Power Template jest częścią większej rodziny bibliotek Open Power Libs, dla których potrzeb opracowanezostało wspólne mikrojądro oferujące m.in. automatyczną ładowarkę czy system obsługi błędów. Znajdziemy je wkatalogu /lib/Opl, podczas gdy właściwy kod systemu szablonów będzie w /lib/Opt. Gdybyśmy chcieli użyćw przyszłości innych bibliotek z tej rodziny, wystarczy wgrać je tuż obok, gdyż będą one korzystać z tego samegojądra.Kolejna rzecz to ustawienie automatycznej ładowarki:

<?php

require('/sciezka/do/opl/Opl/Base.php');

Opl_Loader::setDirectory('/sciezka/do/opl/');

Opl_Loader::register();

Autoloader OPL ma charakter uniwersalny, tzn. można go także wykorzystać do ładowania innych bibliotek zkompatybilnym schematem nazewnictwa (np. Doctrine), aczkolwiek powyższy sposób jest dobry jedynie dlabibliotek OPL. Poniżej jest przedstawione bardziej uniwersalne podejście:

<?php

require('/sciezka/do/bibliotek/Opl/Base.php');

Opl_Loader::addLibrary('Opl', array('basePath' =>

'/sciezka/do/bibliotek/'));

Opl_Loader::addLibrary('Opt', array('basePath' =>

'/sciezka/do/bibliotek/'));

Opl_Loader::addLibrary('InnaBiblioteka', array('basePath' =>

'/sciezka/do/bibliotek/', 'handler' => null));

Opl_Loader::register();

Opcja handler ustawiona na null pozwala wyłączyć dodatkowe opcje ładowania klas specyficzne dla OPL-a, któremogą nie znaleźć zastosowania przy innych bibliotekach. I to wszystko. Jeśli poustawialiśmy dobrze ścieżki,możemy już zacząć korzystać z OPT.

Pierwszy szablonPrzypomnijmy sobie jeszcze raz szablon z początku rozdziału:

<?xml version="1.0" ?>

<opt:root xmlns:opt="http://xml.invenzzia.org/opt">

<opt:prolog />

<opt:dtd template="html5" />

<html>

<head>

<title>{$pageTitle}</title>

</head>

Open Power Template 287

<body>

<p>Polecamy nasze produkty:</p>

<ul>

<li opt:section="products"><a parse:href="$products.url">{$products.name}</a></li>

</ul>

</body>

</html>

</opt:root>

Możemy tu zauważyć kilka charakterystycznych dla OPT zachowań. Szablon jest domyślnie traktowany jakdokument XML, dlatego powinien posiadać prolog <?xml version="1.0" ?>, a co więcej - jest on podanywyłącznie do wiadomości parsera. Jeśli chcemy wygenerować prolog w kodzie wyjściowym, korzystamy z instrukcjiopt:prolog, która podana bez argumentów nadaje im domyślne wartości. Oprócz tego chcielibyśmy dodaćodpowiedni DTD. Komendy DTD także idą na cele parsera biblioteki, stąd też bierze się instrukcja opt:dtd. Maona predefiniowanych kilka szablonów DTD dla najpopularniejszych języków, np. html5, dzięki czemu ichwstawianie jest łatwiejsze.XML wymaga od nas jeszcze jednej ważnej rzeczy, mianowicie w dokumencie nie może być więcej niż jednegogłównego znacznika. W tradycyjnym HTML-u rolę głównego znacznika pełnił element <html>, ale ponieważmamy obok niego jeszcze dwa inne, OPT oddaje nam do dyspozycji opt:root. Oprócz bycia głównymznacznikiem, można w nim poustawiać kilka rzeczy odnoszących się do całego szablonu.Jak widać, przejście na język XML-owy w momencie, gdy XHTML oraz HTML same albo wywodzą się, albo sąpodobne do niego, spowodowało konieczność specjalnego traktowania niektórych elementów, jednak w praktycemusimy je wykonać tylko raz, na samym początku. Gdy zaczniemy pracować z właściwą treścią, sytuacja znaczącosię odwraca. Zerknijmy na linijkę 7. Pokazane jest tam, jak osadzać zmienne ze skryptu w tekściemiędzyznacznikowym. Używamy do tego klamerek, w którym zapisujemy nasze wyrażenie. Zapis ten oznacza, żetytuł strony zostanie załadowany ze zmiennej $pageTitle.W linijce 12 pragniemy wyświetlić kilka produktów jako listę wypunktowaną. Do wyświetlania list służą tzw.sekcje, czyli rodzaj inteligentnych pętli. Jednak zamiast obudowywać element <li> pętlą, wystarczy że dokleimydo niego atrybut opt:section z podaną nazwą sekcji. Jest to prosty sposób powiedzenia: ten kawałek ma byćużyty jako wzorzec do wyświetlenia pojedynczego elementu listy, przy czym nie obchodzi mnie teraz, jak ta listabędzie dokładnie działać. Nazwa sekcji umożliwia nam później dostęp do zmiennych elementu listy, np.{$products.name} - zauważmy, że do separacji służy nam kropka.W tej samej linijce widać też, że aby wczytać zawartość atrybutu ze zmiennej, nie używamy klamerek, lecz donazwy doklejamy parse:, a wyrażenie zapisujemy bezpośrednio w cudzysłowach. Plik zapisujemy do katalogu./templates/.A oto i kod PHP, który powyższy szablon uruchomi:

<?php

require('/sciezka/do/bibliotek/Opl/Base.php');

Opl_Loader::addLibrary('Opl', array('basePath' =>

'/sciezka/do/bibliotek/'));

Opl_Loader::addLibrary('Opt', array('basePath' =>

'/sciezka/do/bibliotek/'));

Opl_Loader::register();

$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'root');

Open Power Template 288

try

{

// Konfiguracja OPT

$tpl = new Opt_Class;

$tpl->sourceDir = './templates/';

$tpl->compileDir = './templates_c/';

$tpl->stripWhitespaces = false;

$tpl->setup();

// Tworzymy widok

$view = new Opt_View('szablon.tpl');

$view->pageTitle = 'Tytuł strony';

$list = array();

$stmt = $pdo->query('SELECT * FROM produkty');

while($row = $stmt->fetch(PDO::FETCH_ASSOC))

{

$list[] = array(

'name' => $row['nazwa'],

'url' => 'produkt.php?id='.$row['id']

);

}

$stmt->closeCursor();

$view->products = $list;

// Renderujemy

$output = new Opt_Output_Http;

$output->setContentType(Opt_Output_Http::HTML, 'utf-8');

$output->render($view);

}

catch(Opt_Exception $exception)

{

$handler = new Opt_ErrorHandler;

$handler->display($exception);

}

Do raportowania błędów Open Power Template używa mechanizmu wyjątków, dlatego cały kod zawarty jest wbloku try...catch. Błędów nie musimy wyświetlać samodzielnie - do dyspozycji jest specjalna klasaOpt_ErrorHandler, która dodatkowo potrafi podawać różne informacje o błędzie, dzięki czemu łatwiej jest usunąćprzyczynę. Sam skrypt składa się z trzech części. W pierwszej musimy skonfigurować bibliotekę, tworząc obiektOpt_Class i ustawiając niektóre właściwości:1. sourceDir - katalog z szablonami źródłowymi (tj. ./templates/)2. compileDir - katalog ze skompilowanymi szablonami (tj. ./templates_c/)3. stripWhitespaces - czy usuwać ze źródeł niepotrzebne białe znaki, utrudniając analizę kodu wynikowego.

Wartość false wyłącza usuwanie.Po skonfigurowaniu wywołujemy metodę setup() i możemy zabrać się za tworzenie widoków. Obsługuje się je podobnie, jak w naszym edukacyjnym systemie z tą różnicą, że nie używamy tutaj metody fabrycznej, lecz

Open Power Template 289

samodzielnie tworzymy cały obiekt. W argumencie konstruktora podajemy nazwę szablonu, a później możemy dowidoku przypisywać różne dane przy pomocy getterów i setterów. Przy okazji spójrzmy na linijki 23 do 33, gdziewypełniana jest lista produktów dla sekcji. Lista jest zwykłą tablicą z elementami numerowanymi od zera, a każdyelement jest tablicą asocjacyjną zawierającą kilka zmiennych.Na samym końcu musimy szablon wykonać. W tym celu potrzebny jest nam jakiś system wyjścia, który zdecyduje,co zrobić z wynikiem. OPT dostarcza domyślnie dwóch takich systemów:1. Opt_Output_Return - zwraca kod jako wynik wykonania z powrotem do skryptu.2. Opt_Output_Http - wysyła kod do przeglądarki i udostępnia opcje zarządzania nagłówkami.Skorzystamy z tego drugiego. Ma on dodatkową pomocniczą metodę setContentType(), która wysyłanagłówek Content-type z informacją dla przeglądarki, co dokładnie przysyłamy z serwera i ew. w jakimkodowaniu. Później wystarczy już tylko wywołać metodę render(), której podajemy za argument widok i towszystko.

SekcjeSekcje to rodzaj inteligentnych pętli i służą do wyświetlania różnego rodzaju list. Zacznijmy od wyświetlenia prostejlisty produktów:

<?xml version="1.0" ?>

<opt:root xmlns:opt="http://xml.invenzzia.org/opt">

<h1>Lista produktów</h1>

<table class="list">

<thead>

<tr>

<td>#</td>

<td>Nazwa</td>

<td>Cena</th>

</tr>

</thead>

<tbody>

<opt:section name="products">

<tr>

<td>{$products.id}</td>

<td>{$products.name}</td>

<td>{$products.price}</td>

</tr>

</opt:section>

</tbody>

</table>

</opt:root>

Aby utworzyć sekcję, stosujemy znacznik opt:section pokazany w linii 14. Jego zawartość określa wygląd pojedynczego elementu listy. Każda sekcja musi posiadać swoją własną nazwę; w szablonie wykorzystujemy ją do odwoływania się do zmiennych elementu: $products.id, zaś w skrypcie posłuży ona do przypisania danych listy. Zauważmy, że w szablonie nie ma żadnych informacji odnośnie szczegółów implementacyjnych. Jest to jedna z charakterystycznych cech sekcji i nie tylko; w założeniu twórca szablonów powinien skupić się jedynie na końcowym efekcie bez zajmowania się tym, jak do niego dojść. Przyjrzyjmy się zatem, jak wygenerować dane dla

Open Power Template 290

naszej listy. Ponownie skorzystamy z pomocy bazy danych z poprzedniego rozdziału, zatem należy pamiętać owcześniejszym połączeniu się z nią poprzez PDO.

$view = new Opt_View('product_list.tpl');

$products = array();

$stmt = $pdo->query('SELECT id, nazwa, cena FROM produkty ORDER BY id');

while($row = $stmt->fetch(PDO::FETCH_ASSOC))

{

// Dodajemy nowy element

$products[] = array(

'id' => $row['id'],

'name' => $row['nazwa'],

'price' => $row['cena']

);

}

$stmt->closeCursor();

// Dodaj listę do widoku

$view->products = $products;

Jak widzimy, pojedynczy element listy to zwykła tablica asocjacyjna, w której klucze odpowiadają indeksomużytym w szablonie. Aby utworzyć listę, wystarczy wszystkie elementy zgrupować w kolejną tablicę i przekazać dowidoku pod nazwą identyczną, jak nazwa sekcji. Po uruchomieniu OPT automatycznie rozwinie je w podaną namlistę.

Uwaga!Domyślnie OPT wymaga, aby indeksy listy były numerowane od zera i nie było w nich luk, zatem nie używaj pola id z bazy danych doindeksowania!

W szablonie nie mamy żadnych informacji o tym czy dane naszej listy umieszczone są w tablicy czy nie. Skąd zatemOPT wie, jak po niej iterować? Odpowiadają za to tzw. formaty danych. Jest to pewien rodzaj przepisu informującyOPT, jak radzić sobie z określonym rodzajem elementów. Przeróbmy nasz skrypt tak, aby umieszczał elementy wobiekcie klasy SplDoublyLinkedList (lista dwukierunkowa) i nauczmy OPT z niego korzystać:

$view = new Opt_View('product_list.tpl');

$products = new SplDoublyLinkedList;

$stmt = $pdo->query('SELECT id, nazwa, cena FROM produkty ORDER BY id');

while($row = $stmt->fetch(PDO::FETCH_ASSOC))

{

// Dodajemy nowy element

$products->push(array(

'id' => $row['id'],

'name' => $row['nazwa'],

'price' => $row['cena']

));

}

$stmt->closeCursor();

// Dodaj listę do widoku

$view->products = $products;

$view->setFormat('products', 'SplDatastructure');

Open Power Template 291

Widzimy, że obiekt SplDoublyLinkedList to coś zupełnie innego niż tablica. Okazuje się, że poinformowanie o tymOPT to kwestia dodania jednej linijki (nr 16). Metoda setFormat() ustawia format danych dla określonegoelementu. SplDatastructure to format dostosowany do pracy z listami, stosami i kolejkami wchodzącymi w składznanego nam już pakietu SPL.

Uwaga!Zmiana formatu danych dowolnego elementu wymaga przekompilowania szablonu. Niestety OPT nie rozpoznaje tego automatycznie,dlatego w tym celu musisz wejść do katalogu /templates_c i usunąć plik odpowiadający naszemu szablonowiproduct_list.tpl. Nie przeszkadza to jednak w codziennym użytkowaniu, ponieważ formaty danych wystarczy ustawić raz dlacałej aplikacji.

Jeśli sekcja obejmuje pojedynczy znacznik HTML, nie musimy tworzyć całego znacznika, ponieważ mamy dodyspozycji skróconą formę atrybutową:

<tr opt:section="products">

<td>{$products.id}</td>

<td>{$products.name}</td>

<td>{$products.price}</td>

</tr>

A co zrobić, gdy chcielibyśmy wyświetlić alternatywny tekst w razie otrzymania pustej listy? Pomoże nam znacznikopt:show:

<?xml version="1.0" ?>

<opt:root xmlns:opt="http://xml.invenzzia.org/opt">

<h1>Lista produktów</h1>

<opt:show name="products">

<table class="list">

<thead>

<tr>

<td>#</td>

<td>Nazwa</td>

<td>Cena</th>

</tr>

</thead>

<tbody>

<opt:section>

<tr>

<td>{$products.id}</td>

<td>{$products.name}</td>

<td>{$products.price}</td>

</tr>

</opt:section>

</tbody>

</table>

<opt:showelse>

<p>Przykro nam, ale nie dodano jeszcze żadnych produktów.</p>

</opt:showelse>

</opt:show>

</opt:root>

Open Power Template 292

Zauważmy, że w tym przypadku nazwa sekcji i inne ewentualne atrybuty trafiają do znacznika opt:show,pozostawiając opt:section pusty. Należy pamiętać o tej subtelności, ponieważ w przeciwnym razie efektymogą być odmienne od zamierzonych.Jednak prawdziwa elastyczność sekcji pojawia się dopiero przy próbie utworzenia list zagnieżdżonych. Pamiętamy,że w PHP i Savancie musieliśmy zajmować się tym samodzielnie, a wszelkie zmiany w strukturze danychgenerowanych przez aplikację zmuszały nas do przepisywania szablonów. W OPT wszystkimi detalami zajmują sięformaty danych, my natomiast musimy jedynie umieścić jedną sekcję w drugiej i to wszystko. Wykorzystajmy tęwłaściwość do wypisania listy tagów skojarzonych z każdym produktem.

<?xml version="1.0" ?>

<opt:root xmlns:opt="http://xml.invenzzia.org/opt">

<h1>Lista produktów</h1>

<opt:show name="products">

<table class="list">

<thead>

<tr>

<td>#</td>

<td>Nazwa</td>

<td>Cena</th>

</tr>

</thead>

<tbody>

<opt:section>

<tr>

<td>{$products.id}</td>

<td><span class="name">{$products.name}</span>

<span class="tags"><opt:section name="tags" str:separator=", ">

<a parse:href="$tags.url">{$tags.name}</a>

<opt:section></span>

</td>

<td>{$products.price}</td>

</tr>

</opt:section>

</tbody>

</table>

</opt:show>

</opt:root>

Utworzenie sekcji zagnieżdżonej nie wymaga od nas absolutnie żadnej dodatkowej czynności oprócz osadzeniejednej sekcji w drugiej. Przy okazji przykład pokazuje inną ciekawą właściwość. Chcielibyśmy, aby nasze tagi byłyodseparowane przecinkiem, przy czym po ostatnim ma go już nie być. W tym celu dodajemy do sekcji atrybutstr:separator. Przestrzeń nazw str informuje OPT, że wartość atrybutu to zwykły tekst, ponieważ domyślniekompilator oczekuje wczytywania jego kształtu ze zmiennej lub innego wyrażenia.Cały proces składania odbywać się będzie po stronie skryptu. Jednak ponieważ nasza baza danych nie zawieratagów, zapiszemy je na sztywno w kodzie, a stworzenie dynamicznej listy tagów dla każdego produktupozostawiamy jako ćwiczenie.

Open Power Template 293

$view = new Opt_View('product_list.tpl');

$products = array();

$stmt = $pdo->query('SELECT id, nazwa, cena FROM produkty ORDER BY id');

while($row = $stmt->fetch(PDO::FETCH_ASSOC))

{

// Dodajemy nowy element

$products[] = array(

'id' => $row['id'],

'name' => $row['nazwa'],

'price' => $row['cena'],

// tagi dla tego produktu.

'tags' => array(0 =>

array('url' => '/tag1', 'name' => 'tag1'),

array('url' => '/tag2', 'name' => 'tag2'),

array('url' => '/tag3', 'name' => 'tag3'),

);

);

}

$stmt->closeCursor();

// Dodaj listę do widoku

$view->products = $products;

$view->setFormat('tags', 'SingleArray');

Ponieważ lista tagów jest zapisana dla każdego produktu z osobna, musimy poinformować o tym OPT, wybierającdla sekcji tags format danych SingleArray. Zachowanie domyślnego formatu jest nieco inne i wymaga od nas, abysekcja tags posiadała swoją własną tablicę, lecz z podwójnym indeksowaniem (pierwsze - produkty; drugie - tagikonkretnego produktu).Sekcje to bardzo wygodne narzędzie do wyświetlania wszekiego rodzaju list. Oprócz opt:section istnieją teżtrzy inne rodzaje sekcji:1. opt:selector - pozwala zdefiniować kilka różnych możliwych wyglądów dla elementów, które są wybierane

na podstawie ich typu.2. opt:grid - wyświetlanie elementów w kolumnach z obsługą dopełniania ostatniego wiersza pustymi

elementami.3. opt:tree - wyświetlanie drzew o dowolnej głębokości.Wrócimy do nich w kolejnych rozdziałach.

Bloki i sortowanie listy produktówW panelach administracyjnych często spotyka się możliwość sortowania listy poprzez klikanie na nagłówkachkolumn. Choć Open Power Template nie dostarcza nam gotowego rozwiązania, udostępnia narzędzia, któreumożliwią nam łatwą jego implementację. Są to tzw. bloki. Każdy blok składa się zawsze z dwóch elementów:1. Obiektu pewnej klasy PHP implementującej interfejs Opt_Block_Interface. Nazywa się on obiektem bloku.2. Grupy znaczników w szablonie, w których dany obiekt będzie uruchamiany. Nazywa się ona portem bloku.Zaczniemy od umieszczenia w naszym szablonie stosownego portu:

<?xml version="1.0" ?>

<opt:root xmlns:opt="http://xml.invenzzia.org/opt">

<h1>Lista produktów</h1>

Open Power Template 294

<opt:show name="products">

<table class="list">

<thead>

<tr>

<td><opt:sort-list str:name="products:id" str:selected="sel">#</opt:sort-list></td>

<td><opt:sort-list str:name="products:name" str:selected="sel">Nazwa</opt:sort-list></td>

<td><opt:sort-list str:name="products:price" str:selected="sel">Cena</opt:sort-list></th>

</tr>

</thead>

<!-- treść tabeli -->

</table>

</opt:show>

</opt:root>

Jak widać, port jest zwykłym znacznikiem. Jego nazwę wybraliśmy sami i niebawem poinformujemy OPT, że ma ongo skojarzyć z odpowiednim typem bloków. Do portu możemy przekazać dowolną liczbę argumentów. U nasdefiniują one etykiety, po których rozpoznamy, jaką kolumnę sortujemy, a także klasy CSS, jakie należy użyć np.gdy dana kolumna jest wybrana.Powyższy rodzaj portów to tzw. porty statyczne. OPT podczas wykonywania automatycznie utworzy dla nichodpowiedni obiekt danego typu. Oprócz tego, istnieją też porty dynamiczne, w których obiekt wczytywany jest zezmiennej. Oznacza to, że możemy utworzyć taki obiekt po stronie skryptu, skonfigurować go tam, a następniepchnąć do szablonu, gdzie zostanie odpalony:

<opt:block from="$obiektBloku" argument="wartość">

... treść ...

</opt:block>

Zanim zaczniemy implementować Opt_Block_Interface, napiszemy sobie interfejs, który pozwoli skryptowi naskonfigurowanie kolumn, po których będziemy sortować:

<?php

class Sorter

{

const ASC = 0;

const DESC = 1;

private $_columns = array();

private $_default = null;

private $_defaultOrder = 0;

private $_selected;

private $_order;

private $_url;

static private $_sorters = array();

public function __construct($name, $url)

{

$this->_sorters[$name] = $this;

Open Power Template 295

$this->_url = $url;

} // end __construct();

static public function get($name)

{

if(!isset(self::$_sorters[$name]))

{

throw new RuntimeException('Podany zestaw reguł sortowania:

'.$name.' nie istnieje.');

}

return self::$_sorters[$name];

} // end get();

public function addColumn($id, $dbField)

{

$this->_columns[$id] = $dbField;

if($this->_default === null)

{

$this->_default = $id;

}

} // end addColumn();

public function setDefault($id, $order)

{

$this->_default = (string)$id;

$this->_defaultOrder = (int)$order;

} // end setDefault();

public function process()

{

$this->_selected = $this->_default;

// Pobierz z adresu URL informację o kolumnie, po której

sortujemy.

if(isset($_GET['col']))

{

if(isset($this->_columns[$_GET['col']))

{

$this->_selected = $_GET['col'];

}

}

// Pobierz informację o kierunku sortowania

if(isset($_GET['ord']))

{

if($_GET['ord'] == 0 || $_GET['ord'] == 1)

{

$this->_order = $_GET['ord'];

}

Open Power Template 296

}

// Zwróć kawałek zapytania SQL

return $this->_columns[$this->_selected].' '.($this->_order == 0 ?

'ASC' : 'DESC');

} // end process();

public function isSelected($id)

{

if($this->_selected != $id)

{

// ten element nie został wybrany.

return null;

}

return $this->_order;

} // end isSelected();

public function getUrl()

{

return $this->_url;

} // end getUrl();

} // end Sorting;

Opis metod jest następujący:1. __construct() - tworzy nowy zestaw reguł sortowania.2. addColumn() - dodaje informację o nowej kolumnie. Pierwszy argument to nasz identyfikator, drugi - nazwa

kolumny w zapytaniu SQL3. setDefault() - ustawia domyślne sortowanie.4. process() - wczytuje z adresu URL informacje o aktualnym sortowaniu i generuje kawałek zapytania SQL.5. isSelected() - metoda ta będzie używana przez nasz obiekt bloku do rozpoznania czy aktualna kolumna jest

wybrana.6. get() - metoda statyczna zwracająca określony zestaw reguł na potrzeby obiektu bloku.Teraz pora na klasę implementującą Opt_Block_Interface. Musimy w niej zaimplementować trzy metody:1. onOpen($attributes) - wywoływana w momencie otwarcia znacznika portu. Powinna zwrócić true, jeśli

chcemy wyświetlić zawartość bloku.2. onClose() - wywoływana w momencie zamykania znacznika bloku.3. onSingle($attributes) - wywoływana, gdy mamy do czynienia z portem w znaczniku pojedynczym:<znacznik />.

Kod źródłowy:

<?php

class Sorter_Block implements Opt_Block_Interface

{

private $_order;

public function onOpen(array $attributes)

{

$data = explode(':', $attributes['name']);

Open Power Template 297

if(sizeof($data) != 2)

{

throw new DomainException('Nieprawidłowa nazwa bloku.');

}

$sorter = Sorter::get($data[0]);

// Sprawdź czy sortujemy według tej kolumny

$this->_order = $sorter->isSelected($data[1]);

$url = $sorter->getUrl();

$url .= (strpos($url, '?') !== null ? '?' : '&');

// Dodaj trochę CSS-a i wygeneruj kod HTML

$class = (isset($attributes['class']) ? $attributes['class'] :

null);

$selected = (isset($attributes['selected']) ?

$attributes['selected'] : null);

if($this->_order != null)

{

echo '<a href="'.$url.'col='.$data[1].'&ord='.(int)(!$this->_order).'" '.($selected !== null ?

'class="'.$selected.'" : '').'>';

}

else

{

echo '<a href="'.$url.'col='.$data[1].'&ord=0" '.($class !== null ? 'class="'.$class.'" : '').'>';

}

return true;

} // end onOpen();

public function onClose()

{

if($this->_order !== null)

{

if($this->_order == 0)

{

echo '↓</a>';

}

else

{

echo '↑</a>';

}

}

else

{

echo '</a>';

}

} // end onClose();

Open Power Template 298

public function onSingle(array $arguments)

{

/* pusto */

} // end onSingle();

} // end Sorter_Block;

Nasz mechanizm sortowania będzie przekazywać informacje o aktualnym sortowaniu za pośrednictwemdodatkowych argumentów w adresie URL. Dlatego nasz blok będzie generować wokół nazwy kolumny znacznik<a>, który po kliknięciu spowoduje posortowanie elementów według danej kolumny, a w przypadku już wybranej -odwróci kolejność sortowania. Oczywiście wybraną kolumnę należy wyróżnić odpowiednią klasą CSS orazdodatkową strzałką pokazującą kierunek sortowania.Zauważmy, że OPT nie zabrania nam generowania HTML-a przez kod PHP. Oczywiście generowanie w ten sposóbwiększego kawałka kodu mijałoby się z celem i byłoby wyjątkowo nieczytelne, ale dla pojedynczych znacznikównie ma żadnych przeciwwskazań przeciwko programowaniu w ten sposób.Pora na podłączenie naszego systemu sortowania pod listę. Dla zachowania czytelności pozbyliśmy się obsługitagów:

$view = new Opt_View('product_list.tpl');

$sorter = new Sorter('products', 'product_list.php');

$sorter->addColumn('id', '`id`');

$sorter->addColumn('name', '`nazwa`');

$sorter->addColumn('price', '`cena`');

$products = array();

$stmt = $pdo->query('SELECT id, nazwa, cena FROM produkty ORDER BY

'.$sorter->process());

while($row = $stmt->fetch(PDO::FETCH_ASSOC))

{

// Dodajemy nowy element

$products[] = array(

'id' => $row['id'],

'name' => $row['nazwa'],

'price' => $row['cena'],

);

}

$stmt->closeCursor();

// Dodaj listę do widoku

$view->products = $products;

Wygląda na to, że to już wszystko. Reguły sortowania są ustawione, odpowiedni kawałek zapytania SQLgenerowany, w szablonie oznaczone kolumny... jednak po uruchomieniu nagłówki kolumn znikają. Oczywiście -zapomnieliśmy poinformować OPT, że opt:sort-list jest blokiem. W przypadku takich nieznanychznaczników kompilator po prostu je ignoruje wraz z zawartością, stąd zniknięcie tekstu w nagłówkach. Musimyzarejestrować naszą klasę Sorter_Block w OPT, dlatego w momencie inicjowania biblioteki dodajemy:

$tpl = new Opt_Class;

$tpl->sourceDir = './templates/';

Open Power Template 299

$tpl->compileDir = './templates_c/';

$tpl->stripWhitespaces = false;

$tpl->register(Opt_Class::OPT_BLOCK, 'opt:sort-list', 'Sorter_Block');

$tpl->setup();

Po ponownym skompilowaniu szablonów nasza lista będzie już mogła być sortowana po nagłówkach kolumn.Przekonaliśmy się właśnie, że kluczem do efektywnego pisania szablonów jest przygotowanie sobie odpowiedniegozaplecza, bowiem większość napisanego przed chwilą kodu to rozmaite klasy PHP. Moglibyśmy osiągnąćidentyczny efekt przy pomocy pętli oraz instrukcji warunkowych bezpośrednio w szablonie, ale przecież wprawdziwej aplikacji nie mamy jednej listy, tylko co najmniej kilkanaście. Dlatego tak ważne jest, aby poświęcićchwilkę czasu na zaprogramowanie obsługi takich fragmentów, jak sortowanie czy stronicowanie oraz aby systemszablonów udostępniał odpowiednie narzędzia do ich osadzania w szablonach. W przypadku OPT są to bloki orazdziałające na podobnej zasadzie komponenty, które mają jednak dużo bardziej rozbudowany interfejs dostosowanydo wyświetlania formularzy. Zauważmy, że gdy przyjdzie nam tworzyć np. listę użytkowników, w szablonie będzieto już kwestia dodania dodatkowego znacznika w nagłówku każdej kolumny i nic więcej!

ZakończenieW rozdziale tym pokazaliśmy, jak zbudować i obsłużyć szablon dla dynamicznej listy produktów przy pomocybiblioteki Open Power Template. Jest to jednak tylko wycinek jej możliwości, ponieważ jest ona projektowana, byporadzić sobie nawet z najtrudniejszymi wymaganiami. Filozofia tworzenia szablonów jest tutaj zupełnie inna, niż wpoznanym wcześniej Savancie, dlatego nie powinniśmy tutaj polegać na swojej intuicji i przenosić tego, cofunkcjonowało w PHP z nadzieją, że zadziała i tutaj. Zauważmy, że w podanych przykładach nie użyliśmy postronie szablonów ani klasycznej pętli, ani zwykłej instrukcji warunkowej, chociaż oczywiście OPT udostępnia takieinstrukcje, jak opt:if czy opt:foreach.Następny i zarazem ostatni system szablonów, jaki poznamy, to PHPTAL. Jest on ogniwem pośrednim między OPT,a Savantem, z nowoczesnym językiem szablonów, który jednak jest nieco bardziej zbliżony do klasycznegoprogramowania.

Przypisy[1] http:/ / www. invenzzia. org

Smarty 300

Smarty

SmartyNasze praktyczne zmagania z systemami szablonów rozpoczniemy od biblioteki Smarty.

InstalacjaW 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 praktycesprowadza się on jedynie do skopiowania gdzieś plików i dołączenia do skryptu jednego z nich. Dokładna proceduraopisana jest poniżej:1. Wchodzimy na stronę http:/ / www. smarty. net/ i pobieramy stamtąd najnowszą dostępną wersję (w chwili

pisania tego tekstu - 2.6.16).2. Zakładamy, że nasze skrypty są w katalogu kursphp. Tworzymy w nim nowy podkatalog, np. smarty .3. Otwieramy ściągnięte archiwum i kopiujemy do naszego nowostworzonego katalogu zawartość folderu libs.4. Aby załadować bibliotekę do naszej aplikacji, dołączamy plik Smarty.class.php.Zanim zaczniemy, musimy jeszcze utworzyć dwa dodatkowe katalogi:1. /templates - tu trzymać będziemy nasze szablony. PHP musi mieć uprawnienia do odczytu.2. /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 zmieniać - po prostu należy przydzielić dla PHP prawa do zapisu i nic więcej.

Pierwszy skryptSzablon 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 jednakzmiana systemu oznaczenia wprowadzania zmiennych Smarty. Są to dwa pola z klasy smarty z plikulibs/Smarty.class.php. var $left_delimiter, oraz var $right_delimiter) Jednak uważaj: to, że w szablonie stosujemytaką zmienną, nie znaczy wcale, że jeżeli stworzymy w naszym skrypcie analogiczną zmienną, to ją nam w tymmiejscu wyświetli. Zmienne wewnątrz szablonów i zmienne PHP są dwiema zupełnie osobnymi rzeczami. Zmienneszablonowe 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

Smarty 301

$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):1.1. Na początku ładujemy bibliotekę.2. 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 ichskompilowanymi wersjami, ale dyrektyw jest znacznie więcej.

3. Tutaj ustawiamy, jaką wartość ma mieć szablonowa zmienna $data.4.4. 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! Dzisiajjest (tu aktualna data)!"

Więcej o zmiennychSmarty to coś więcej, niż zwykłe umieszczanie danych w kodzie HTML. W zasadzie po stronie szablonów mamy dodyspozycji całkiem rozbudowany język programowania obsługujący m.in. tablice i obiekty z PHP, a także rozmaiteformy manipulacji danymi. Załóżmy, że mamy jakąś listę wiadomości, jednak z oczywistych przyczyn na stroniegłównej pragniemy wyświetlić jedynie nagłówki i kilkanaście początkowych wyrazów. Możemy cały mechanizmprzetwarzania zrealizować po stronie PHP, lecz z tym różnie bywa, zwłaszcza gdy kod PHP do pobierania listywykorzystywany jest jeszcze w paru innych miejscach, gdzie taka właściwość jest niewskazana. To jednak nieproblem, 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

Smarty 302

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 zdanymi. 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 podajemynazwę indeksu. Smarty oferuje także alternatywną składnię, zbliżoną bardziej do PHP: $wiadomosc[tytul]. Wostatniej zmiennej, pojawiają się jeszcze dodatkowe znaki. Jest to tzw. modyfikator i służy, jak nazwa wskazuje, dokońcowej obróbki danych, najczęściej związanej bezpośrednio z procesem wyświetlania. Tutaj nakazujemy przyciąćdługość wiadomości do 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.

PoradaJeśli treść pobieralibyśmy z bazy danych, a dodatkowo trzymalibyśmy w niej naprawdę długie artykuły, warto rozważyć możliwośćskracania jeszcze na poziomie języka SQL (istnieją stworzone do tego celu funkcje), podczas gdy PHP lub Smarty zajmowałby siępolerowaniem, tj. zaokrągleniem do pełnych wyrazów. W ten sposób unikamy pobierania ogromnych partii danych, które i tak byłybypóźniej stracone.

Smarty 303

SekcjeW systemie Smarty sekcja jest jednym z rodzajów pętli. Najczęściej wykorzystuje się ją do tworzenia wszelkiegorodzaju list. Pokażemy teraz, jak wykorzystać omawianą bibliotekę do wygenerowania listy newsów, a następnie jakpołą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łykod HTML między nimi jest powtarzany w kółko i służy za szablon pojedynczego newsa. W znacznikuotwierają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 wnim 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 dopieronazwę 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

Smarty 304

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 zmiennymwewnętrznym odpowiednie wartości. Następnie przekazujemy ją do parsera metodą assign(). Jako ćwiczenie spróbujutworzyć 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. kategoriioraz przypisanych do każdej z nich produktów. Wróćmy się do naszej bazy danych biblioteki z rozdziału o PHPData Objects. Spróbujemy wyświetlić jej zawartość na ekranie, korzystając z pakietu Smarty. Jest to niecotrudniejsze, 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>

Smarty 305

Zwróć uwagę, że w podrzędnej sekcji dotyczącej książek, musimy "podpiąć się" pod sekcję nadrzędną, aby Smartywiedział, w jaki sposób powiązane są ich elementy. Odwołując się do rekordów książek, musimy podać w nawiasachkwadratowych 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 wrozdziale poświęconym PDO - wykorzystaliśmy tam po prostu ID kategorii jako element wiążący, jednak w tymprzypadku 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, jakpoprzednio, natomiast dane książek posortujemy w pierwszej kolejności według nazw kategorii, a dopiero późniejwedług ich własnych tytułów. Zauważmy, że dzięki temu mamy zagwarantowane, że książki znajdujące się w tejsamej kategorii znajdą się na liście wyników obok siebie. Teraz wystarczy podczas pobierania utrzymywać dwiezmienne: $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, toznaczy, że przeszliśmy już do książek z następnej kategorii i musimy w tym celu zwiększyć $i o 1, tak aby dane tetrafiły do kolejnego rekordu. Dodatkowo zapamiętujemy ID nowej kategorii. Oto ilustracja tego algorytmu w kodziePHP:

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

{

Smarty 306

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.

Instrukcje warunkoweSmarty posiada także instrukcję warunkową if, dzięki której można testować różne warunki i wyświetlać fragmentykodu 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 tojakoś specjalnie zaznaczyć. Aby nie komplikować kodu, przyjmiemy w przykładzie, że newsem dnia jest pierwszy zelementó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

Smarty 307

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 poprawnewyrażenie - jeżeli idzie Ci układanie takowych po stronie PHP, również w Smartym nie będziesz mieć z nimiproblemów. Jedynie trzeba przyzwyczaić się do nowych operatorów. Biblioteka preferuje użycie tekstowychzamienników, aczkolwiek symboliczny zapis z PHP także jest dozwolony. Oto lista najważniejszych operatorów:

Smarty 308

Smarty PHP

eq ==

neq, ne !=

gt >

lt <

ge, gte >=

le, lte <=

not !

Dostęp do zmiennych sesyjnychZ poziomu Smarty możemy mieć dostęp do zmiennych sesyjnych, które ustawiliśmy sobie z poziomu skryptówPHP. 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 inadawania uprawnień, tj. podczas logowania tworzymy w zmiennej sesyjnej obraz uprawnień nadanychuż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}

Uwaga!Pamiętaj, że powyższy przykład ukrywania części stron przed użytkownikami nie może być jedyną metodą budowania systemuuprawnień! Użytkownik wcześniej czy później musi wysłać zapytanie do serwera i to tam musi nastąpić weryfikacja, czy ma onuprawnienia do wykonywania tej czynności. Metoda "zasłony dymnej", jak powyżej, może być tylko formą estetyki, a nie obrony.

W analogiczny sposób można odwoływać się do innych rodzajów danych wejściowych. Wartości pól formularzazapisane są w $smarty.post, z adresu URL w $smarty.get. Istnieje też możliwość dostania się do ciasteczek:$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ówXSS.

Smarty 309

ZakończenieBiblioteka Smarty ma dużo większe możliwości, niż są w stanie pomieścić założenia tego podręcznika. W sieciznaleźć można dużo artykułów na temat tego systemu szablonów, także w języku polskim. Oprócz przetwarzaniaszablonów, Smarty posiada również zaawansowany moduł cache'owania wyników, szczególnie przydatny nastronach z dużym ruchem.Przejdziemy teraz do omówienia alternatywnej biblioteki Open Power Template.

310

Frameworki

Czym jest framework?

Czym jest framework?W tym rozdziale poznamy kolejną kategorię pakietów programistycznych zwanych frameworkami. Zadaniemframeworka jest dostarczenie szkieletu do tworzenia aplikacji (lub pewnej jej części), a także zbioru ogólnychfunkcjonalności, które programista może rozszerzać, by dopasować je do konkretnych potrzeb. Framework może byćrozumiany zatem jako szczególny przypadek biblioteki programistycznej, od której jednak odróżnia go kilkaszczegółów:1. Odwrócenie sterowania - przepływ sterowania w aplikacji jest narzucany przez framework, nie przez

programistę.2. Rozszerzalność - funkcjonalność frameworka może być rozszerzana przez programistę poprzez nadpisywanie

lub dodawanie nowych elementów.3. Niemodyfikowalny kod - kod frameworka nie powinien być modyfikowany przez programistę podczas

rozszerzania.Przyjrzyjmy się, jak tworzyliśmy aplikacje WWW do tej pory. Dostawaliśmy do rąk język i musieliśmysamodzielnie zaprogramować całą strukturę aplikacji od początku. Nie chodziło tu tylko o napisanie koduposzczególnych akcji, ale też rozwiązanie takich kwestii, jak moment ładowania konfiguracji, łączenie się z baządanych, sposób generowania wynikowego kodu HTML itd. Zauważmy, że wymyślanie tego za każdym razem odnowa mija się z celem i jest wysoce nieefektywne. Przecież moglibyśmy wydzielić ogólną funkcjonalność,zapakować ją w zbiór funkcji i klas, a później na jej podstawie tworzyć kolejne aplikacje. Jeśli dokonaliśmy takiegopodziału, brawo - stworzyliśmy właśnie framework.Oczywiście taki framework byłby prawdopodobnie dość prymitywny, ale od strony technicznej spełnia wszystkiecechy tego rodzaju pakietów. Mamy ogólną funkcjonalność związaną z komunikacją z bazami danych czy obsługąformularzy, którą następnie dostosowujemy do potrzeb konkretnej aplikacji. Przepływ sterowania, czyli mechanizmwykonywania akcji, też najczęściej zaszyty będzie we wnętrzu frameworka. Możemy pójść jednak o krok dalej,przeanalizować strukturę istniejących aplikacji i zaprojektować framework od zera już jako framework, a nie jakopochodna procesu tworzenia aplikacji. I faktycznie - dla PHP istnieje wiele gotowych frameworków, którewystarczy ściągnąć i zastosować. To właśnie jest powodem ich wyjątkowej popularności. Firmy informatyczne niemuszą wydawać pieniędzy na opracowywanie autorskich rozwiązań, lecz po prostu mogą użyć dobrzezaprojektowane i przetestowane frameworki. Co więcej, oszczędzają w ten sposób pieniądze na szkoleniu nowychpracowników. Wystarczy w ogłoszeniu o pracę napisać, że wymagana jest znajomość frameworka XXX i nowyprogramista może niemal od razu zacząć pracę.

Uwaga!Framework nie jest gotową aplikacją. Jest zbiorem kodu i pewnych metod postępowania, dzięki którym możesz efektywnie budowaćaplikacje.

Czym jest framework? 311

Co wchodzi w skład typowego frameworka?Mamy już ogólne pojęcie, czym framework jest i czym nie jest, pora zatem zastanowić się, w czym może nasframework wyręczyć. Podstawowe elementy większości frameworków to:1.1. Mechanizm uruchamiania i przetwarzania akcji.2.2. Mechanizm tworzenia logiki biznesowej aplikacji.3.3. Zarządzanie konfiguracją.4.4. Zarządzanie komunikacją z bazą danych.5.5. Obsługa formularzy.6.6. System szablonów.7.7. Obsługa błędów.8.8. Mechanizmy bezpieczeństwa, uwierzytelniania i kontroli dostępu.9.9. Generatory kodu.

Dlaczego powinienem używać frameworka?Framework to gotowe komponenty do budowy aplikacji WWW zaprojektowane przez doświadczonychprogramistów. Są one dobrze udokumentowane i dzięki temu łatwe w użyciu. Jako programista oszczędzasz czas naprzysłowiowe "wymyślanie koła od nowa", ponieważ jedyne co musisz zrobić, to nauczyć się nimi posługiwać imożesz skupić się w całości na budowaniu aplikacji.Ponadto dzięki ogólnej dostępności frameworków, programuje w nich wielu ludzi. Nowy programista w zespole,który umie korzystać z frameworka, na którym pracujesz, bez większych problemów zrozumie już napisany kod ibędzie mógł od razu rozpocząć w nim pracę. Autorskie rozwiązania, zwłaszcza pisane przez początkujących, pełnesą poważnych błędów projektowych i trudne w analizie, przez co przejęcie do dalszego rozwoju takiej aplikacji tokoszmar.

Dlaczego nie powinienem używać frameworka?W przypadku większości typów aplikacji WWW nie ma żadnych przeciwwskazań do korzystania z gotowychframeworków. Mają one wszystko czego potrzeba i są dobrą szkołą programowania. Zdecydowana większośćframeworków WWW napisanych w PHP (i nie tylko) działa według jednego i tego samego schematu, a różnice sąminimalne. Problem pojawia się dopiero wtedy, gdy ten schemat z jakiegoś powodu nam nie odpowiada, ponieważprawdopodobnie nie znajdziemy wtedy niczego, co spełniałoby nasze potrzeby.Ogólnie mówiąc, sytuacja wygląda identycznie, jak z systemami szablonów i bibliotekami ORM. Aby napisać dobryframework, potrzebny jest czas i doświadczenie. Aby napisać dobry framework, który nie byłby powielaniem tego,co już zostało napisane, potrzebna jest dodatkowo kreatywność oraz umiejętność planowania. Te kryteria spełnianiewiele zespołów projektowych, dlatego lepiej jest nauczyć się korzystać z gotowych projektów.

PodsumowanieW następnym rozdziale poznamy złożone wzorce projektowe MVC, MVP oraz ich pochodne. Są one podstawądziałania większości frameworków i dlatego ich zrozumienie jest niezbędne. Następnie napiszemy prosty,minimalistyczny framework oparty o wzorzec MVC, aby pokazać w praktyce zasadę jego działania, zaś na końcuomówimy trzy wybrane frameworki PHP i pokażemy, jak za ich pomocą zbudować blog internetowy.

312

Bezpieczeństwo

313

Dobre praktyki

314

Inne

Edytory PHP< PHP

Prosimy nie traktować tej strony jako darmowej reklamy dla tych czy innych edytorów. Opisy niespełniającezasady neutralnego punktu widzenia są usuwane!

Choć kod PHP można tworzyć już w zwykłym notatniku, o wiele lepiej jest zaopatrzyć się w specjalny edytorwyposaż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ś wartyumieszczenia, sporządź odpowiedni opis i dołącz tu. Prosimy jedynie pamiętać o zachowaniu zasady neutralnegopunktu widzenia.

WieloplatformoweEdytory dostępne dla systemów rodziny Windows, dla MacOS, systemów unikso-podobnych i czasem jeszczeinnych.

Eclipsefree softwareAplikacja rozwijana przez IBM. Za pomocą pluginów można ją rozszerzać o dowolne funkcjonalności. Eclipse jestaplikacją wieloplatformową napisaną w Javie. Dla PHP cały czas rozwijany jest specjalny plugin: PHPEclipse. Mawbudowany parser PHP. Umożliwia m.in. debugowanie kodu, auto uzupełnianie i wiele innych. Przy pomocyEclipse można także projektować bazy danych. W tym celu należy zainstalować plugin Azzurri Clay. Umożliwia ontworzenie 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 iinnych. Program udostępniany na warunkach Eclipse Public License.Więcej informacji na oficjalnej stronie programu [1] i stronach domowych pluginów: PHPEclipse [2] i Azzurri Clay[3]

Eclipse PHP Development Toolfree softwareNarzę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). Publikowanena Eclipse Public License.Więcej informacji na stronie domowej programu [4].

Edytory PHP 315

NetBeans IDEfree softwareNetBeans IDE to wieloplatformowe, zintegrowane środowisko programistyczne napisane w Javie. Jest rozwijaneprzez firmę Sun Microsystems (Oracle) i obecnie posiada obsługę m.in. C/C++, baz danych, Java, Ruby, PHP i wieleinnych. Wspiera także większość popularnych systemów kontroli wersji. NetBeans jest porównywany często doEclipse, choć ma mniej możliwości konfiguracji, co dla niektórych może być zaletą, gdyż przez to IDE jest mniejzłożone oferując jednocześnie całą potrzebną funkcjonalność. Dla developerów PHP istnieje specjalnieprzygotowana wersja, którą można pobrać z tej strony [5]. Aktualna, stabilna wersja środowiska to 7.0. Od tej wersjiIDE posiada pełne wsparcie dla systemu kontroli wersji GIT, podpowiadanie znaczników dla HTML5 i trochęmniejszych zmian.W poprzednich wersjach środowiska zaimplementowano m. in. obsługę frameworka Symfony [6] i zend framework[7], usprawnioną integrację z MySQL oraz jeszcze lepszą obsługę testów PHPUnit [8]. Wsparcie dla PHP wNetBeans jest ciągle ulepszane o czym zawsze można przeczytać na NetBeans for PHP Weblog [9]

Więcej informacji na stronie NetBeans PHP Development [10].

Vimfree softwareEdytor ten jest bardzo znany w środowisku użytkowników Linuksa. Pracuje w trybie tekstowym i obsługuje się goza pomocą klawiatury (można też używać myszki, jeśli nasz terminal ją obsługuje), ale posiada za to rewelacyjnemożliwości kolorowania składni oraz personalizacji. Osoby korzystające z graficznych interfejsów użytkownikamogą używać gVima (wykorzystującego GTK), posiadającego prosty interfejs okienkowy oraz możliwość używaniamyszki. 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 ikilku innych.Więcej informacji na oficjalnej stronie programu [11].

Zend StudiokomercyjnyBardzo dobry edytor PHP dla zaawansowanych programistów sieciowych. Ma wiele funkcji ułatwiających pracę nadużymi projektami m.in.: auto uzupełnianie kodu, debugger dla skryptów PHP. Wbudowany w program serwer dajemożliwość analizowania skryptów na swoim komputerze. Program przeznaczony jest dla dobrze znających językPHP programistów sieciowych.Więcej informacji na oficjalnej stronie programu [1].

Unikso-podobneEdytory pod systemy Linuksowe, GNU, *BSD i wszelkie inne systemy unikso-podobne.

Bluefishfree softwareAutorzy 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 [12].

Edytory PHP 316

Katefree softwareKde Advanced Text Editor jest wygodnym edytorem tekstowym pozwalającym na wygodną pracę z dziesiątkamiplików jednocześnie. Posiada obsługę plików przez ftp, zarządzanie projektami, kolorowanie składni częściejużywanych języków, wybór stron kodowych, makra, obsługę wyrażeń regularnych i wiele innych udogodnień. Jeston dostępny w większości dystrybucji. Jest sztandarowym edytorem środowiska graficznego KDE. Kate jestedytorem opublikowanym na licencji GNU LGPL.Więcej informacji na stronie oficjalnej edytora [13].

Quanta Plusfree softwareZaawansowane środowisko programistyczne działające w systemach unikso-podobnych (środowisko KDE). Posiadawiele 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 nawarunkach GNU GPL.Więcej informacji na oficjalnej stronie programu [14].

WindowsEdytory przeznaczone dla systemów Windows.

Crimson Editorfreeware (free software?)Zaawansowany i wygodny w obsłudze edytor, umożliwia m. in.: obsługę FTP, zaznaczanie kolumn, definiowaniewłasnych makr, podpinanie zewnętrznych programów (np. kompilatorów), zarządzanie projektami. Zauważonebłędy: słaba obsługa dużych plików (> 0.5 MB), słaba obsługa otwierania plików w otoczeniu sieciowym wWindows 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. Wersjadarmowa (freeware, choć z dopiskiem Now it is open source zostały opublikowane źródła niewydanej oficjalniewersji 3.71) do wszelakich zastosowań. Sporym mankamentem jest istnienie wersji tylko dla Windows.Więcej informacji na oficjalnej stronie programu [15].

Delphi for PHPsharewareŚrodowisko Delphi jest potężnym RAD-em, bardzo przyjemnym w użytkowaniu. Wersja dla PHP została wydana 22lipca 2007. Możliwe pobranie 30-dniowego triala.Więcej informacji na oficjalnej stronie programu [16], polskim cenniku [17].

Dev-PHP IDEfree 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

Edytory PHP 317

Windows.Więcej informacji na oficjalnej stronie programu [18].

DreamweaversharewareZnany na całym świecie, potężny program firmy Adobe, umożliwiający pisanie rozbudowanych stron HTML, atakże łatwe tworzenie aplikacji internetowych w popularnych językach programowania skryptowego, m.in. PHP iASP. Posiada takie udogodnienia jak: kolorowanie składni, numerowanie wierszy, auto uzupełnianie kodu. Programdostę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 [19].

E-NetfreewareDarmowy 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óbprzyspiesza i ułatwia pisanie dokumentów internetowych. Dla ułatwienia i przyspieszenia generowania kodu istniejesystem podpowiedzi dla HTML, CSS, JavaScript i PHP.Więcej informacji na oficjalnej stronie programu [20].

HateML ProfreewareGodny uwagi pretendent do ulubionego narzędzia zawodowego webmastera. Oprócz standardowych funkcjiedytoró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ówi wartości dla każdego tagu w aktualnych dokumencie – włącznie ze zdarzeniami itp., otwieranie plików, na którychaktualnie znajduje się kursor - świetna funkcja w przypadku dołączonych w skrypcie innych plików, bibliotekaplików dołączonych w bieżącym dokumencie i wiele innych).Więcej informacji na oficjalnej stronie programu [21].

KiciafreewarePolski edytor, nadaje się zarówno dla webmasterów, jak i dla programistów. Pozwala na edycję programów idokumentó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 edytorzezawarte 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[22].

Edytory PHP 318

Notatnik SPfreewareUniwersalny edytor (najprawdopodobniej w zamierzeniu programistyczny) z kolorowaniem składni dla wielujęzyków programowania, nada się również doskonale do PHP. Niestety nie można w jednym oknie programupracować z wieloma plikami. Innym problemem jest to, że program rozpoczyna numerowanie składni od wiersza nr0, 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 okodowaniu 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 [23].

Notepad ++free softwareDarmowy, uniwersalny edytor o otwartym kodzie, oferujący podświetlanie składni niemal 40 językówprogramowania. W oknie programu można uruchomić wiele (dziesiątki czy nawet setki przy odpowiedniej ilościRAM) 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 [24].

PajączeksharewarePajączek to rozbudowany polski edytor WWW napisany przez firmę Cream Software. Program jest płatny, alenajnowsza 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, czynp. wszystkie ukryte, mając więcej miejsca na kod.Więcej informacji na oficjalnej stronie programu [25].

PHP DesignersharewarePHP Designer jest zaawansowanym edytorem przystosowanym zarówno do edycji, debugowania, analizowania ipublikowania skryptów PHP. Oprócz kolorowania składni umożliwia auto uzupełnianie kodu, jest przystosowany doedycji wielu innych technologii wykorzystywanych na stronach WWW. Edytor wyposażony jest również wmoż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 [26].

PSPadfreeware 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

Edytory PHP 319

komputerze korzystać dwie osoby, każda musi zainstalować swą własną kopię, ponieważ PSPad nie przewidujemożliwości personalizacji ustawień.Więcej informacji na oficjalnej stronie edytora [27].

WebSite ProfreewareJest to polski, prosty edytor stron posiadający kolorowanie składni m.in. php. Doskonale nadaje się do szybkiejedycji bądź pisania prostych skryptów.Autor porzucił tworzenie tego edytora i na oficjalnej stronie programu [28] dostępny jest już jedynie klucz potrzebnydo korzystania z niego, natomiast ściągnąć go można ze strony dobreprogramy.pl [29].

kED 2freewareJest 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 spisznacznikó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 orazumożliwia skojarzenie z kEDem plików htm, html, php, css, xml, js i vbs). Autor porzucił tworzenie tego edytora zewzględu na brak wolnego czasu i środków. Na oficjalnej stronie [30] programu od niedawna została usuniętamożliwość download-u lecz można go pobrać z tego mirroru. [31]

MacOSEdytory przeznaczone dla systemów Apple'a.

Bluefishfree softwareAutorzy 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 [12].

DreamweaversharewareZnany na całym świecie, potężny program firmy Adobe, umożliwiający pisanie rozbudowanych stron HTML, atakże łatwe tworzenie aplikacji internetowych w popularnych językach programowania skryptowego, m.in. PHP iASP. Posiada takie udogodnienia jak: kolorowanie składni, numerowanie wierszy, auto uzupełnianie kodu. Programdostę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 [19].

Edytory PHP 320

Przypisy[1] http:/ / www. eclipse. org[2] http:/ / phpeclipse. de[3] http:/ / www. azzurri. jp/ en/ software/ clay/[4] http:/ / www. zend. com/ pdt[5] http:/ / netbeans. org/ downloads/ index. html[6] http:/ / www. symfony-project. org/[7] http:/ / framework. zend. com/[8] http:/ / www. phpunit. de/[9] http:/ / blogs. sun. com/ netbeansphp/[10] http:/ / netbeans. org/ features/ php/ index. html[11] http:/ / www. vim. org[12] http:/ / bluefish. openoffice. nl/ index. html[13] http:/ / kate. kde. org/[14] http:/ / quanta. kdewebdev. org/[15] http:/ / www. crimsoneditor. com/[16] http:/ / www. codegear. com/ products/ delphiforphp[17] http:/ / www. borland. pl/ delphi/ delphi_for_php/ cennik. shtml[18] http:/ / devphp. sourceforge. net/[19] http:/ / www. adobe. com[20] http:/ / www. edytor. wri. pl[21] http:/ / migajek. com/[22] http:/ / www. komputerswiat. pl/ download/ index. aspx?akcja=pokaz_jeden& sid=6000[23] http:/ / www. programypc. pl/ notatnik;sp;4;0;pl,program,1378. html[24] http:/ / notepad-plus. sourceforge. net[25] http:/ / www. pajaczek. pl/[26] http:/ / www. mpsoftware. dk/ phpdesigner. php[27] http:/ / www. pspad. com[28] http:/ / website. e-clipse. org/[29] http:/ / dobreprogramy. pl/ index. php?dz=2& id=407& t=22[30] http:/ / www. freezone. fc. pl/[31] http:/ / www. b227. pl/ download/ przydatne_programy/ dla_webmastera/ ked2. html

Autorzy 321

Autorzy

AutorzyChoć Wikibooks może edytować każdy, istnieje pewna grupa ludzi aktywnie opiekujących się tym podręcznikiemprzez cały proces jego powstawania. Nie tylko stworzyli wiele rozdziałów, ale także dbali o jednolitość całości orazpoprawność. W grupie tej znajduje się aktualnie jedna osoba:•• Tomasz "Zyx" JędrzejewskiJeżeli chcesz pomóc, po prostu włącz się do akcji. Dotychczasowi członkowie grupy udzielą wszystkich wskazówekna temat edycji.

Dla twórców podręcznika

Dla twórców podręcznikaNiniejsza strona zbiera zalecenia dla autorów i standardy, w jakich pisany będzie podręcznik. Wszelkie uwagi ipropozycje powinny być dyskutowane na stronie dyskusji.

OprogramowanieWszystkie informacje powinny być w miarę aktualne i dotyczyć przynajmniej PHP 5.2/5.3 oraz MySQL 5.0.Biblioteki programistyczne opisujemy również w oparciu o ostatnią wersję.Jeżeli chcesz opisać jakąś bibliotekę, zgłoś to w dyskusji i zaproponuj spis treści. Wykaz propozycji można znaleźćna podstronie Do zrobienia.

NawigacjaNa górze każdej podstrony prosimy zamieszczać następujący szablon:którego kod jest następujący:

{{subst:naw|PHP|poprzedni|nastepny}}

Pola poprzedni oraz nastepny wypełniamy nazwami odpowiednich rozdziałów. Spis treści dostępny jest na stroniegłównej podręcznika i wszelkie ważniejsze edycje należy uprzednio konsultować w dyskusji.

StylPodręcznik ten nie ma przypominać pracy zaliczeniowej ze studiów, ale dawać czytelnikowi możliwość zrozumieniaczegoś. Nie unikajmy zatem przykładów, nawet tych banalnych oraz niekoniecznie związanych z programowaniem iczęsto wracajmy do omówionych już wcześniej spraw. Definicje muszą być opatrzone konkretnym przykładem lubwyjaśnieniem, po co taka rzecz istnieje. Co jakiś czas tekst zachęca do samodzielnych eksperymentów i pokazuje,jak się za nie można zabrać. Wskazane są przydatne miejsca, adresy, techniki analizy kodu.Myślą przewodnią niniejszego podręcznika jest tzw. zasada złotego środka. Czytelnik po przeczytaniu powinienmieć wyrobiony nawyk pytania siebie, czy dana rzecz jest mu rzeczywiście potrzebna w oryginalnym kształcie.Wszystkie zagadnienia powinny mieć czytelnie wyszczególnione wady oraz zalety bez dołączanych twierdzeńsugerujących, że jest to panaceum na wszystkie problemy świata.Bardzo ważne jest graficzne rozłożenie tekstu na stronie, aby nie sprawiał wrażenia chaotycznego. Podczas edycjizawsze możemy podejrzeć, jak wprowadzone zmiany będą się prezentować. Unikajmy za wszelką cenę

Dla twórców podręcznika 322

jednozdaniowych akapitów oraz nieracjonalnego dzielenia nimi jednolitej treści. Wskazane jest stosowanie listwypunktowanych lub numerowanych. Są bardziej przejrzyste niż normalny tekst, a przy tym zwięzłe. Używaj teżramek pomocniczych, których spis został zamieszczony niżej.Przykładowe kody źródłowe muszą być napisane czytelnie, w oparciu o identyczne formatowanie.1.1. Nawiasy klamrowe otwieramy w nowej linijce.2.2. Wcięcia trójznakowe.3. W nazewnictwie posługujemy się camelStyle (tj. zmienne, funkcje itd. nazywamy jako nazwaFunkcji, a nie

nazwa_funkcji albo nazwafunkcji).4.4. Poszczególne części algorytmu staramy się separować linijką przerwy.5.5. Kod musi być skomentowany, najlepiej komentarzami jednolinijkowymi.6. Na końcu skryptu nie wstawiamy znaku ?>W kodach źródłowych staramy się unikać nieprawidłowych nawyków:1. Niepotrzebne zmienne tymczasowe - jeżeli są potrzebne, wyjaśniamy dlaczego. Pamiętajmy o tym szczególnie

przy omawianiu baz danych, gdzie zapytania piszemy bezpośrednio w funkcji/metodzie, bez żadnej pomocniczejzmiennej $query, $zapytanie itd.

2.2. Wszystkie zmienne wcześniej inicjujemy.3. W programowaniu obiektowym każdą metodę poprzedzamy przedrostkiem public, private itd.4. Nie stosujemy elementów składni typowych dla PHP 4 (np. var), chyba że w opisach objaśniających różnice

między wersjami.5. Konstrukcje używane niezgodnie z przeznaczeniem powinny być omijane. Zaliczają się do nich m.in.

funkcja("$zmienna");

Kod musi być zawarty w znacznikach <source lang="php"> ... </source>, np.

<?php

echo 'Hello world';

Uwaga!Większość rozdziałów została napisana jeszcze przed wprowadzeniem znacznika SOURCE do oprogramowania MediaWiki, dlategokorzystają ze starszego stylu. Jeśli możesz, zaktualizuj formatowanie.

Neutralny punkt widzeniaZgodnie z zasadą Wikibooks, podręcznik powinien utrzymany być w konwencji ***neutralnego punktuwidzenia***. Dotyczy to w szczególności takich rozdziałów, jak Edytory PHP czy Pomoc, które wcale nie służą doreklamowania własnych aplikacji bądź serwisów WWW. Wszelkie kontrowersyjne materiały będą usuwane alboprzeredagowane bardzo szybko.

Dobór bibliotekW podręczniku, oprócz PHP, omawiane są też wybrane biblioteki programistyczne stworzone w tym języku.Ponieważ bibliotek jest dużo, a tylko niektóre z nich trzymają odpowiedni poziom, utworzony został zbiórwarunków, jakie biblioteka musi spełnić, aby mogła być omówiona w podręczniku w trosce o jego jakość.1. Obecność infrastruktury internetowej - system kontroli wersji, bugtracker, forum dla użytkowników2. Przynajmniej rok aktywnego rozwoju - w ciągu roku od pojawienia się podręcznika biblioteka musi być cały czas

aktywnie rozwijana (nowe wersje, poprawianie błędów, nowa funkcjonalność). Podstawą jest aktywność wrepozytorium SVN lub innego systemu kontroli wersji.

3. Dostępne wydanie stabilne - biblioteka musi mieć przynajmniej jedno wydanie stabilne (tzw. wersja 1.0) abymogła być umieszczona w podręczniku

Dla twórców podręcznika 323

4. Dostępna kompletna dokumentacja - biblioteki bez dokumentacji lub jedynie ze szczątkową dokumentacją (np.wygenerowany minimalistyczny APIDoc) nie mogą być umieszczone w podręczniku.

5. Wsparcie dla PHP 5.2/PHP 5.3 - biblioteka musi bezproblemowo pracować na podanych wersjach PHP.6.6. Konsultacja na stronie dyskusji podręcznika

Uwaga!Rozdziały o bibliotekach niespełniających powyższych warunków będą usuwane!

Pamiętaj, że podręcznik przede wszystkim poświęcony jest PHP. Rozdziały o bibliotekach mają jedynie pokazaćzagadnienie i służyć za wstęp do danego tematu. Pełne omówienie biblioteki wykracza poza ramy tego podręcznika.

Podstrony•• Do zrobienia• Wytyczne dla rozdziałów (czyli co każdy powinien zawierać)

SzablonyOto wykaz szablonów używanych w podręczniku:

Opis Kod Efekt

Ostrzeżenie czytelnika {{Uwaga|Tekst

ostrzeżenia}} Uwaga!Tekst ostrzeżenia

Porada {{Porada|Tekst porady}}

PoradaTekst porady

Informacja {{Infobox|Tekst

informacji}} Tekst informacji

Definicja {{Definicja|Tekst

definicji}} Tekst definicji

Do zrobienia {{TODO|co zrobić}}

Dozrobienia:co zrobić

Do zrobienia

•• do wstawiania w sekcji•• stosować tylko w rozdziałach, w których

większość tekstu jest napisana

{{RDoZrobienia}}

Ta sekcja jest zalążkiem. Jeślimożesz, rozbuduj ją [1].

Artykuł do poprawy {{dopracować|powód}}

Dla twórców podręcznika 324

Przypisy[1] http:/ / pl. wikibooks. org/ w/ index. php?title=PHP/ Inne/ Dla_tw%C3%B3rc%C3%B3w_podr%C4%99cznika& action=edit

325

Archiwum - zawiera fragmenty rozdziałów,które były kiedyś zaczęte, lecz zostały

odłożone na później lub są już niepotrzebnez różnych przyczyn (np. zawarta w nichtreść jest przekazywana w zupełnie inny

sposób).

SQL Injection

SQL InjectionAtak typu SQL Injection polega na takiej zmianie jednego lub kilku parametrów zapytania (query) wysyłanego dobazy 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 wkolumnie wyswietl mają wartość 1. Strona daneklienta.php, do której prowadzą łącza ze strony listaklientow.phpwyś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 IDKowalski. 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ślonychklientów, ale ktoś wpada na pomysł zrobienia czegoś takiego: daneklienta.php?klient=Tajny, gdzie klient o ID Tajnyto klient, do którego profilu link nie jest wyświetlany przez plik listaklientow.php, jednak skrypt wyświetla daneklienta o dowolnym ID! Wtedy już mamy problem. Baza danych dostaje rozkaz wyświetlenia informacji dla IDTajny, 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 cracker) wyśle wzmiennej 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.Jednakże najgroźniejsza sytuacja jest w formularzach logowania. Wystarczy, że cracker poda login:

' or 1=1 --

i ma dostęp do konta administratora.

SQL Injection 326

Zabezpieczanie się przed SQL InjectionZabezpieczyć się przed atakiem typu SQL Injection można bardzo łatwo - wystarczy przefiltrować cudzysłowy orazapostrofy z parametru wysyłanego do zapytania SQL. Wiele opcji hostingowych ma domyślnie wyłączonyznienawidzony przez programistów parametr magic_quotes_gpc (dla serwerów Apache), która dodaje znak \ przedkażdym potencjalnie niebezpiecznym znakiem parametru. Używając PDO, wystarczy używać funkcji prepare() iustawiać zmienne. W starych funkcjach mysql_* można 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']);

Jeśli parametr, który chcemy pobrać jest np liczbą, i chcemy żeby był wartością tylko tego typu możemy zastosowaćrzutowanie albo użyć funkcji intval, np.

$id = (int)$_GET["id"]; //Wersja z rzutowaniem

$id = intval($_GET["id"]); //Wersja z intval

Spowoduje to, że jeśli wpiszemy coś innego niż liczbę zostanie ona zamieniona na 0

Dodatkowe informacjeWartym odnotowana jest jeszcze przypadek, w którym mamy do czynienia z serwerami z włączonymmagic_quotes_gpc(). Jeśli nie chcemy/nie możemy go wyłączyć, możemy skorzystać z funkcji stripslashes(), któradziała odwrotnie do funkcji mysql_escape_string():

$id = stripslashes($_GET['id']);

Hashowanie

KodowaniePHP udostępnia możliwość kodowania co jest procesem obustronnym - dane można zarówno zakodować jak izdekodować. Przykładowymi funkcjami kodującymi są base64.•• base64_encode - koduje ciąg•• base64_decode - dekoduje ciąg

<?php

$tekst = "Przykładowy tekst z różnymi znakami specjalnymi @#$ do

zakodowania.";

$zakodowane = base64_encode($tekst);

echo($zakodowane);

//

UHJ6eWuzYWRvd3kgdGVrc3QgeiBy879ueW1pIHpuYWthbWkgc3BlY2phbG55bWkgQCMkIGRvIHpha29kb3dhbmlhLg==

$zdekodowane = base64_decode($zakodowane);

echo($zdekodowane);

// Przykładowy tekst z różnymi znakami specjalnymi @#$ do zakodowania.

if($tekst == $zdekodowane)

Hashowanie 327

echo("Wszystko w porządku.");

?>

HashowaniePonadto języku PHP dane można również hashować (w odróżnieniu od szyfrowania jest to proces jednostronny)używając do tego specjalnych funkcji.

<?php

$form = $_POST['formularz'];

$hash = sha1($form);

echo ($hash);

?>

Powyżej przedstawiony jest skrypt hashujący dane odbierane z formularza POST. Funkcji tej można używać np. dohashowania haseł użytkowników, w skrypcie rejestracji, tak by nie dały się rozszyfrować.Funkcje hashujące

•• sha1•• md5•• hash•• crc32•• cryptW przypadku hashowania plików zazwyczaj wymagane jest użycie innej funkcji. Najczęściej wystarczy dodać"_file" (np. md5_file) jednak nie jest to reguła. Zaleca się unikać hashowania dużych plików gdyż operacja ta możeznacznie obciążyć maszynę, na której uruchomiony zostanie ów skrypt.

Wykorzystanie hashowania w logowaniu/rejestracjiWykorzystanie hashowania jest niezwykle proste. Przy rejestracji, należy zahashować hasło i dodać je w takiejformie do bazy danych. Wtedy przy logowaniu, musimy zahashować hasło i porównać go z rekordem w baziedanych.

PHP Injection 328

PHP InjectionPHP Injection polega na dopisywaniu przez złośliwych użytkowników fragmentów kodu do przesyłanychzmiennych 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 taksamo, jak w przypadku SQL Injection, jednak atak nie ma bezpośredniego (lub nie ma w ogóle) wpływu na bazędanych.

Przykładowy atakZałóżmy że użytkownik może wpisać swoje imię w formularzu, które zostanie wyświetlone. W formularzu jestjedynie pole do wpisania imienia, przesyłany metodą POST. Wyświetlanie:

<?php

$imie = $_GET['imie'];

echo("Witaj $imie !");

?>

Lecz jeżeli użytkownik wpisze:

<?php phpinfo(); ?>

To wygenerowany kod HTML będzie wyglądał tak:

Witaj <?php phpinfo(); ?> !

Jak się zabezpieczyć

Ulepszenie poprzedniego skryptu:

<?php

$imie = htmlspecialchars($_GET['imie']);

echo("Witaj $imie !");

?>

I już atak tego typu nam nie straszny - ostre nawiasy zostaną zastąpione kodami &lt oraz &gt, co uniemożliwiwykonanie funkcji w ten sposób.

Bazy danych i sesje 329

Bazy danych i sesjePrzypomnijmy sobie najpierw wcześniejszy materiał. Używane były w nim tablice z użytkownikami.Zaczniemy od utworzenia tabeli sesje (korzystając z bazy danych produkty).

CREATE TABLE `produkty`.`sesje` (

`id` INT( 100 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,

`login` VARCHAR( 100 ) NOT NULL ,

`haslo` VARCHAR( 1000 ) NOT NULL

) ENGINE = MYISAM ;

Teraz, kiedy mamy gotowy skrypt, musimy zmienić pierwszą część skryptu sesji - wszystko do komentarzaWlasciwy skrypt zamieniamy na:

<?php

function czyIstnieje($login, $haslo)

{

$haslo = sha1($haslo);

try

{

$pdo = new PDO('mysql:host=localhost;dbname=produkty', 'root',

'root');

$pdo -> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$stmt = $pdo -> query('SELECT id, login, haslo FROM sesje');

foreach($stmt as $dane)

{

if($dane['login'] == $login && $dane['haslo'] == $haslo)

{

// O, jest ktos taki - zwroc jego ID

return $dane['id'];

/* Chcesz uzyc loginu uzytkownika? dodaj po inicjacji sesji

$_SESSION['user'] = $user;

oraz przed tym nawiasem klamrowym ponizej, ale po tym komentarzu

$user = $dane['user'];

*/

}

}

$stmt -> closeCursor();

}

catch(PDOException $e)

{

echo 'Połączenie nie mogło zostać utworzone: ' . $e->getMessage();

}

Bazy danych i sesje 330

// Jeżeli doszedłeś a tutaj, to takiego użytkownika nie ma

return false;

} // end czyIstnieje();

Aby utworzyć konta, należy utworzyć taki plik (pamiętaj, każdy może sobie utworzyć konto aż do kasacji):

<?php

try

{

if($_SERVER['REQUEST_METHOD'] == 'POST')

{

$pdo = new

PDO('mysql:host=localhost;dbname=produkty', 'root', 'root');

$stmt = $pdo -> query('SELECT id, login, haslo FROM sesje');

$id = '1';

foreach($stmt as $dane)

{

$id++;

}

$haslo = sha1($_POST['haslo']);

$magic = get_magic_quotes_gpc();

if ($magic != "1") $login = mysql_escape_string($_POST['login']);

$stmt -> closeCursor();

$add = $pdo -> exec('INSERT INTO `sesje` (`id`,

`login`, `haslo`) VALUES(

\''.$id.'\',

\''.$login.'\',

\''.$haslo.'\')');

if($add > 0)

{

echo 'Dodano? '.$ilosc;

}

else

{

echo 'Wystąpił błąd podczas dodawania

rekordów!';

}

}

else

{

echo '

<form method="post" action="sesje_5.php">

<p>Login: <input type="text" name="login"/></p>

<p>Hasło: <input type="password" name="haslo"/></p>

<p><input type="submit" value="Dodaj"/></p>

</form>

Bazy danych i sesje 331

';

}

}

catch(PDOException $e)

{

echo 'Wystapil blad biblioteki PDO: ' . $e->getMessage();

}

?>

Smarty

SmartyNasze praktyczne zmagania z systemami szablonów rozpoczniemy od biblioteki Smarty.

InstalacjaW 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 praktycesprowadza się on jedynie do skopiowania gdzieś plików i dołączenia do skryptu jednego z nich. Dokładna proceduraopisana jest poniżej:1. Wchodzimy na stronę http:/ / www. smarty. net/ i pobieramy stamtąd najnowszą dostępną wersję (w chwili

pisania tego tekstu - 2.6.16).2. Zakładamy, że nasze skrypty są w katalogu kursphp. Tworzymy w nim nowy podkatalog, np. smarty .3. Otwieramy ściągnięte archiwum i kopiujemy do naszego nowostworzonego katalogu zawartość folderu libs.4. Aby załadować bibliotekę do naszej aplikacji, dołączamy plik Smarty.class.php.Zanim zaczniemy, musimy jeszcze utworzyć dwa dodatkowe katalogi:1. /templates - tu trzymać będziemy nasze szablony. PHP musi mieć uprawnienia do odczytu.2. /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 zmieniać - po prostu należy przydzielić dla PHP prawa do zapisu i nic więcej.

Pierwszy skryptSzablon 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

Smarty 332

libs/Smarty.class.php. var $left_delimiter, oraz var $right_delimiter) Jednak uważaj: to, że w szablonie stosujemytaką zmienną, nie znaczy wcale, że jeżeli stworzymy w naszym skrypcie analogiczną zmienną, to ją nam w tymmiejscu wyświetli. Zmienne wewnątrz szablonów i zmienne PHP są dwiema zupełnie osobnymi rzeczami. Zmienneszablonowe 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):1.1. Na początku ładujemy bibliotekę.2. 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 ichskompilowanymi wersjami, ale dyrektyw jest znacznie więcej.

3. Tutaj ustawiamy, jaką wartość ma mieć szablonowa zmienna $data.4.4. 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! Dzisiajjest (tu aktualna data)!"

Więcej o zmiennychSmarty to coś więcej, niż zwykłe umieszczanie danych w kodzie HTML. W zasadzie po stronie szablonów mamy dodyspozycji całkiem rozbudowany język programowania obsługujący m.in. tablice i obiekty z PHP, a także rozmaiteformy manipulacji danymi. Załóżmy, że mamy jakąś listę wiadomości, jednak z oczywistych przyczyn na stroniegłównej pragniemy wyświetlić jedynie nagłówki i kilkanaście początkowych wyrazów. Możemy cały mechanizmprzetwarzania zrealizować po stronie PHP, lecz z tym różnie bywa, zwłaszcza gdy kod PHP do pobierania listywykorzystywany jest jeszcze w paru innych miejscach, gdzie taka właściwość jest niewskazana. To jednak nieproblem, 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!',

Smarty 333

'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 zdanymi. 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 podajemynazwę indeksu. Smarty oferuje także alternatywną składnię, zbliżoną bardziej do PHP: $wiadomosc[tytul]. Wostatniej zmiennej, pojawiają się jeszcze dodatkowe znaki. Jest to tzw. modyfikator i służy, jak nazwa wskazuje, dokońcowej obróbki danych, najczęściej związanej bezpośrednio z procesem wyświetlania. Tutaj nakazujemy przyciąćdługość wiadomości do 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.

Smarty 334

PoradaJeśli treść pobieralibyśmy z bazy danych, a dodatkowo trzymalibyśmy w niej naprawdę długie artykuły, warto rozważyć możliwośćskracania jeszcze na poziomie języka SQL (istnieją stworzone do tego celu funkcje), podczas gdy PHP lub Smarty zajmowałby siępolerowaniem, tj. zaokrągleniem do pełnych wyrazów. W ten sposób unikamy pobierania ogromnych partii danych, które i tak byłybypóźniej stracone.

SekcjeW systemie Smarty sekcja jest jednym z rodzajów pętli. Najczęściej wykorzystuje się ją do tworzenia wszelkiegorodzaju list. Pokażemy teraz, jak wykorzystać omawianą bibliotekę do wygenerowania listy newsów, a następnie jakpołą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łykod HTML między nimi jest powtarzany w kółko i służy za szablon pojedynczego newsa. W znacznikuotwierają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 wnim 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 dopieronazwę 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++)

{

Smarty 335

$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 zmiennymwewnętrznym odpowiednie wartości. Następnie przekazujemy ją do parsera metodą assign(). Jako ćwiczenie spróbujutworzyć 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. kategoriioraz przypisanych do każdej z nich produktów. Wróćmy się do naszej bazy danych biblioteki z rozdziału o PHPData Objects. Spróbujemy wyświetlić jej zawartość na ekranie, korzystając z pakietu Smarty. Jest to niecotrudniejsze, 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}

Smarty 336

</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 Smartywiedział, w jaki sposób powiązane są ich elementy. Odwołując się do rekordów książek, musimy podać w nawiasachkwadratowych 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 wrozdziale poświęconym PDO - wykorzystaliśmy tam po prostu ID kategorii jako element wiążący, jednak w tymprzypadku 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, jakpoprzednio, natomiast dane książek posortujemy w pierwszej kolejności według nazw kategorii, a dopiero późniejwedług ich własnych tytułów. Zauważmy, że dzięki temu mamy zagwarantowane, że książki znajdujące się w tejsamej kategorii znajdą się na liście wyników obok siebie. Teraz wystarczy podczas pobierania utrzymywać dwiezmienne: $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, toznaczy, że przeszliśmy już do książek z następnej kategorii i musimy w tym celu zwiększyć $i o 1, tak aby dane tetrafiły do kolejnego rekordu. Dodatkowo zapamiętujemy ID nowej kategorii. Oto ilustracja tego algorytmu w kodziePHP:

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

Smarty 337

`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.

Instrukcje warunkoweSmarty posiada także instrukcję warunkową if, dzięki której można testować różne warunki i wyświetlać fragmentykodu 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 tojakoś specjalnie zaznaczyć. Aby nie komplikować kodu, przyjmiemy w przykładzie, że newsem dnia jest pierwszy zelementó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,

Smarty 338

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 poprawnewyrażenie - jeżeli idzie Ci układanie takowych po stronie PHP, również w Smartym nie będziesz mieć z nimiproblemów. Jedynie trzeba przyzwyczaić się do nowych operatorów. Biblioteka preferuje użycie tekstowychzamienników, aczkolwiek symboliczny zapis z PHP także jest dozwolony. Oto lista najważniejszych operatorów:

Smarty 339

Smarty PHP

eq ==

neq, ne !=

gt >

lt <

ge, gte >=

le, lte <=

not !

Dostęp do zmiennych sesyjnychZ poziomu Smarty możemy mieć dostęp do zmiennych sesyjnych, które ustawiliśmy sobie z poziomu skryptówPHP. 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 inadawania uprawnień, tj. podczas logowania tworzymy w zmiennej sesyjnej obraz uprawnień nadanychuż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}

Uwaga!Pamiętaj, że powyższy przykład ukrywania części stron przed użytkownikami nie może być jedyną metodą budowania systemuuprawnień! Użytkownik wcześniej czy później musi wysłać zapytanie do serwera i to tam musi nastąpić weryfikacja, czy ma onuprawnienia do wykonywania tej czynności. Metoda "zasłony dymnej", jak powyżej, może być tylko formą estetyki, a nie obrony.

W analogiczny sposób można odwoływać się do innych rodzajów danych wejściowych. Wartości pól formularzazapisane są w $smarty.post, z adresu URL w $smarty.get. Istnieje też możliwość dostania się do ciasteczek:$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ówXSS.

Smarty 340

ZakończenieBiblioteka Smarty ma dużo większe możliwości, niż są w stanie pomieścić założenia tego podręcznika. W sieciznaleźć można dużo artykułów na temat tego systemu szablonów, także w języku polskim. Oprócz przetwarzaniaszablonów, Smarty posiada również zaawansowany moduł cache'owania wyników, szczególnie przydatny nastronach z dużym ruchem.Przejdziemy teraz do omówienia alternatywnej biblioteki Open Power Template.

Źródła i autorzy artykułu 341

Źródła i autorzy artykułuO podręczniku  Źródło: http://pl.wikibooks.org/w/index.php?oldid=161519  Autorzy: Akira, Derbeth, DrJolo, Lethern, MonteChristof, NuclearWarfare, Piotr, Zyx, 5 anonimowych edycji

Czym jest PHP  Źródło: http://pl.wikibooks.org/w/index.php?oldid=189750  Autorzy: Akira, Beau, Derbeth, DrJolo, Gesso, Lethern, MSWin, Pavroo, Piotr, Silmethule, Tsca, Zyx, 19anonimowych edycji

Możliwości  Źródło: http://pl.wikibooks.org/w/index.php?oldid=189752  Autorzy: ABach, Akira, DrJolo, Lethern, Pavroo, Piotr, Tsca, Zyx, 3 anonimowych edycji

Jak się uczyć?  Źródło: http://pl.wikibooks.org/w/index.php?oldid=189751  Autorzy: Akira, DrJolo, Lethern, Pavroo, Piotr, Zyx, 12 anonimowych edycji

Opis instalacji  Źródło: http://pl.wikibooks.org/w/index.php?oldid=155709  Autorzy: Adiblol, Akira, DrJolo, Lethern, Piotr, Quentinv57, Zyx, 10 anonimowych edycji

Apache HTTP Server  Źródło: http://pl.wikibooks.org/w/index.php?oldid=187211  Autorzy: Akira, Amras, Lethern, Marcin Łukasz Kiejzik, PanzerMaus, Piotr, Qbk, Sasek, Silmethule, Zyx, 27anonimowych edycji

MySQL 5  Źródło: http://pl.wikibooks.org/w/index.php?oldid=144207  Autorzy: Akira, C64club, Chrumps, Derbeth, Lethern, PanzerMaus, Piotr, Stv, Zyx, 18 anonimowych edycji

PHP  Źródło: http://pl.wikibooks.org/w/index.php?oldid=181551  Autorzy: Akira, Derbeth, Gesso, Lethern, Moryc, Piotr, Silmethule, Warszk, Zyx, 30 anonimowych edycji

Pierwszy skrypt  Źródło: http://pl.wikibooks.org/w/index.php?oldid=187417  Autorzy: Adam majewski, Akira, DrJolo, Lethern, Marcin Łukasz Kiejzik, Pavroo, Piotr, Zoltax, Zyx, 13anonimowych edycji

Zmienne i tablice  Źródło: http://pl.wikibooks.org/w/index.php?oldid=186821  Autorzy: Akira, Derbeth, DrJolo, Kwpolska, Lethern, Mikador, Piotr, TheAdam0s, Zyx, 30 anonimowych edycji

Formularze  Źródło: http://pl.wikibooks.org/w/index.php?oldid=189888  Autorzy: Akira, Derbeth, DrJolo, Mikador, Piotr, Riklaunim, Silmethule, Warszk, Zyx, 18 anonimowych edycji

Struktury kontrolne  Źródło: http://pl.wikibooks.org/w/index.php?oldid=58948  Autorzy: Akira, DrJolo, Neddy, Piotr, Zyx

Instrukcja if  Źródło: http://pl.wikibooks.org/w/index.php?oldid=180395  Autorzy: Akira, Beau, CzarnyInaczej, CzarnyZajaczek, DrJolo, Marcin Łukasz Kiejzik, Neddy, Piotr, Silmethule, Zyx,10 anonimowych edycji

Instrukcja switch  Źródło: http://pl.wikibooks.org/w/index.php?oldid=139632  Autorzy: Akira, DrJolo, Lethern, Piotr, Silmethule, Zyx, 4 anonimowych edycji

Instrukcja for  Źródło: http://pl.wikibooks.org/w/index.php?oldid=156644  Autorzy: Akira, CzarnyZajaczek, Derbeth, DrJolo, Lethern, Pavroo, Piotr, Zyx, 2 anonimowych edycji

Instrukcja while  Źródło: http://pl.wikibooks.org/w/index.php?oldid=145012  Autorzy: Akira, DrJolo, MonteChristof, Piotr, Zyx, 2 anonimowych edycji

Instrukcja do while  Źródło: http://pl.wikibooks.org/w/index.php?oldid=180396  Autorzy: Akira, Beau, CzarnyZajaczek, DrJolo, Piotr, Silmethule, Zyx

Instrukcja foreach  Źródło: http://pl.wikibooks.org/w/index.php?oldid=179011  Autorzy: Akira, DrJolo, Lethern, Pavroo, Piotr, Wutsje, Zyx, 19 anonimowych edycji

Funkcje  Źródło: http://pl.wikibooks.org/w/index.php?oldid=185919  Autorzy: Akira, CzarnyZajaczek, DrJolo, Mikador, Piotr, Wohin, Zyx, 9 anonimowych edycji

Inne elementy składni  Źródło: http://pl.wikibooks.org/w/index.php?oldid=174453  Autorzy: Akira, CzarnyInaczej, Harry lp, Piotr, Zyx, 14 anonimowych edycji

Każdy popełnia błędy  Źródło: http://pl.wikibooks.org/w/index.php?oldid=156039  Autorzy: Akira, Asbb, Aure, Jerzym, Mikador, Piotr, Silmethule, Zjem ci chleb, Zyx, 9 anonimowych edycji

Korzystanie z dokumentacji  Źródło: http://pl.wikibooks.org/w/index.php?oldid=167416  Autorzy: Akira, DrJolo, Karol Dąbrowski, Kj, Piotr, Silmethule, Zyx, 4 anonimowych edycji

Studium przypadku: Księga gości  Źródło: http://pl.wikibooks.org/w/index.php?oldid=180397  Autorzy: Akira, Beau, CzarnyZajaczek, Derbeth, Felix, Ktoosiu, Lethern, Piotr, Spacebirdy, Stv,Wutsje, Zyx, 18 anonimowych edycji

Ćwiczenia  Źródło: http://pl.wikibooks.org/w/index.php?oldid=193439  Autorzy: Karol Karolus, Zyx, 1 anonimowych edycji

Przetwarzanie tekstu  Źródło: http://pl.wikibooks.org/w/index.php?oldid=184995  Autorzy: Akira, Aure, Beau, Lethern, Silmethule, Zyx, 13 anonimowych edycji

Podstawy wyrażeń regularnych  Źródło: http://pl.wikibooks.org/w/index.php?oldid=194862  Autorzy: Beau, Lethern, Riklaunim, Silmethule, Zyx, 10 anonimowych edycji

Obsługa ciastek  Źródło: http://pl.wikibooks.org/w/index.php?oldid=180400  Autorzy: Akira, Beau, Cathy Richards, Lethern, Maciejo93, Zyx, 8 anonimowych edycji

Sesje  Źródło: http://pl.wikibooks.org/w/index.php?oldid=195913  Autorzy: Akira, Beau, Derbeth, Filemon, Hosiryuhosi, Yrazec, Zyx, 20 anonimowych edycji

Wysyłanie e-maili  Źródło: http://pl.wikibooks.org/w/index.php?oldid=189290  Autorzy: Akira, Beau, Derbeth, Jerzym, Marcinmw, Yrazec, Zyx, 8 anonimowych edycji

Internacjonalizacja  Źródło: http://pl.wikibooks.org/w/index.php?oldid=180415  Autorzy: Akira, Beau, Kwpolska, Zyx, 3 anonimowych edycji

System plików  Źródło: http://pl.wikibooks.org/w/index.php?oldid=191760  Autorzy: Akira, Beau, Derbeth, Jerzym, Marcin412, MonteChristof, Pavroo, Piotr, T ziel, Zyx, 11 anonimowychedycji

Data i czas  Źródło: http://pl.wikibooks.org/w/index.php?oldid=187333  Autorzy: Kleer94, Pavroo, Zyx, 8 anonimowych edycji

Czym jest programowanie obiektowe?  Źródło: http://pl.wikibooks.org/w/index.php?oldid=191480  Autorzy: Beau, Karol Karolus, Lethern, Zyx, 11 anonimowych edycji

Klasy i obiekty  Źródło: http://pl.wikibooks.org/w/index.php?oldid=194798  Autorzy: Akira, Beau, Karol Karolus, Kj, Lethern, Pavroo, Piotr, Zjem ci chleb, Zyx, 41 anonimowych edycji

Konstruktory i destruktory  Źródło: http://pl.wikibooks.org/w/index.php?oldid=180407  Autorzy: AdvMDev, Akira, Beau, Pomstiborius, Zyx, 7 anonimowych edycji

Dziedziczenie  Źródło: http://pl.wikibooks.org/w/index.php?oldid=186429  Autorzy: AdvMDev, Beau, Don Daniello, Kleer94, Ktoosiu, Lethern, Michcioo87, Pomstiborius, Yrazec, Zyx, 14anonimowych edycji

Interfejsy  Źródło: http://pl.wikibooks.org/w/index.php?oldid=180016  Autorzy: Cathy Richards, Lethern, Yrazec, Zyx, 7 anonimowych edycji

Wyjątki  Źródło: http://pl.wikibooks.org/w/index.php?oldid=195512  Autorzy: Mikador, Pavroo, WRIM, Yrazec, Zyx, 6 anonimowych edycji

Elementy statyczne  Źródło: http://pl.wikibooks.org/w/index.php?oldid=180018  Autorzy: Yrazec, Zyx, 2 anonimowych edycji

Metody magiczne  Źródło: http://pl.wikibooks.org/w/index.php?oldid=180096  Autorzy: Vinyanov, Yrazec, Zyx, 2 anonimowych edycji

Iteratory  Źródło: http://pl.wikibooks.org/w/index.php?oldid=126760  Autorzy: Zyx

Automatyczne ładowanie  Źródło: http://pl.wikibooks.org/w/index.php?oldid=185011  Autorzy: Harry lp, Zyx, 2 anonimowych edycji

Ćwiczenia  Źródło: http://pl.wikibooks.org/w/index.php?oldid=128883  Autorzy: Zyx

Wstęp do baz danych  Źródło: http://pl.wikibooks.org/w/index.php?oldid=188192  Autorzy: Adam majewski, Akira, Derbeth, Kpaz, Lethern, Piotrala, Silmethule, Wohin, Zyx, 13 anonimowychedycji

Źródła i autorzy artykułu 342

Projekt bazy danych  Źródło: http://pl.wikibooks.org/w/index.php?oldid=185467  Autorzy: Akira, Derbeth, Lethern, Zyx, 7 anonimowych edycji

Zarządzanie rekordami  Źródło: http://pl.wikibooks.org/w/index.php?oldid=185805  Autorzy: Derbeth, Zyx, 5 anonimowych edycji

Pobieranie rekordów  Źródło: http://pl.wikibooks.org/w/index.php?oldid=88877  Autorzy: Akira, Piotrala, Silmethule, Zjem ci chleb, Zyx, 1 anonimowych edycji

Relacje i indeksy  Źródło: http://pl.wikibooks.org/w/index.php?oldid=180405  Autorzy: Akira, Beau, Derbeth, Lethern, MonteChristof, Silmethule, Wohin, Zyx, 17 anonimowych edycji

Biblioteka PDO  Źródło: http://pl.wikibooks.org/w/index.php?oldid=196114  Autorzy: Akira, Beau, Fidytek, Kwpolska, Miszal3miszal2, Pavroo, Silmethule, Zyx, 22 anonimowych edycji

Jak to się robiło kiedyś?  Źródło: http://pl.wikibooks.org/w/index.php?oldid=182015  Autorzy: Akira, Beau, Koziolek91, Kwpolska, Silmethule, Zyx, 13 anonimowych edycji

phpMyAdmin  Źródło: http://pl.wikibooks.org/w/index.php?oldid=182012  Autorzy: Adam majewski, Akira, Chrumps, Lethern, MonteChristof, Piotrala, Zjem ci chleb, Zyx, 8 anonimowychedycji

Studium przypadku: System newsów  Źródło: http://pl.wikibooks.org/w/index.php?oldid=189485  Autorzy: Beau, ChP94, Karol Karolus, Lethern, Pavroo, Piotrala, Silmethule, Zyx, 11anonimowych edycji

Bazy danych - co dalej?  Źródło: http://pl.wikibooks.org/w/index.php?oldid=58904  Autorzy: Zyx

Czym jest system szablonów?  Źródło: http://pl.wikibooks.org/w/index.php?oldid=197101  Autorzy: Akira, Zyx, 7 anonimowych edycji

Prosty edukacyjny system szablonów  Źródło: http://pl.wikibooks.org/w/index.php?oldid=176357  Autorzy: Zyx, 4 anonimowych edycji

Open Power Template  Źródło: http://pl.wikibooks.org/w/index.php?oldid=185892  Autorzy: Pavroo, Silmethule, Zyx, 4 anonimowych edycji

Smarty  Źródło: http://pl.wikibooks.org/w/index.php?oldid=186658  Autorzy: Beau, Jerzym, Zyx, 6 anonimowych edycji

Czym jest framework?  Źródło: http://pl.wikibooks.org/w/index.php?oldid=151744  Autorzy: Harry lp, Zyx

Edytory PHP  Źródło: http://pl.wikibooks.org/w/index.php?oldid=176874  Autorzy: Adiblol, Akira, DrJolo, E.s.t, Gp8, Jerzym, Kadet1090, Kwpolska, Lethern, Marcinmw, MonteChristof,Pawelkg, Pawlosck, Piotr, Provent, Ptak82, Qbk, Silmethule, Sobak2, Stv, Warszk, Wojtek19945, Zyx, 33 anonimowych edycji

Autorzy  Źródło: http://pl.wikibooks.org/w/index.php?oldid=128890  Autorzy: Akira, DrJolo, Lethern, Zyx, 4 anonimowych edycji

Dla twórców podręcznika  Źródło: http://pl.wikibooks.org/w/index.php?oldid=154151  Autorzy: Akira, Cathy Richards, Derbeth, Karol Dąbrowski, Piotr, Tomta1, Zyx

SQL Injection  Źródło: http://pl.wikibooks.org/w/index.php?oldid=195386  Autorzy: Ananas96, Kwpolska, Matbi, Sniper89, 8 anonimowych edycji

Hashowanie  Źródło: http://pl.wikibooks.org/w/index.php?oldid=184016  Autorzy: Lethern, 9 anonimowych edycji

PHP Injection  Źródło: http://pl.wikibooks.org/w/index.php?oldid=196306  Autorzy: Aso824, Sniper89, 2 anonimowych edycji

Bazy danych i sesje  Źródło: http://pl.wikibooks.org/w/index.php?oldid=180413  Autorzy: Beau, Kwpolska, Lethern, 2 anonimowych edycji

Smarty  Źródło: http://pl.wikibooks.org/w/index.php?oldid=186658  Autorzy: Beau, Jerzym, Zyx, 6 anonimowych edycji

Źródła, licencje i autorzy grafik 343

Źródła, licencje i autorzy grafikGrafika:Fairytale messagebox info.png  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Fairytale_messagebox_info.png  Licencja: GNU Lesser General Public License  Autorzy:Amada44, Anime Addict AA, Bayo, Dake, Jon Harald Søby, Mapmarks, Rocket000, ZooFariGrafika:Evolution-tasks.png  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Evolution-tasks.png  Licencja: GNU General Public License  Autorzy: Artwork by Tuomas Kuosmanen<tigert_at_ximian.com> and Jakub Steiner <jimmac_at_ximian.com>Grafika:Plume ombre.png  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Plume_ombre.png  Licencja: GNU Free Documentation License  Autorzy: Darkdadaah, Javierme,Mindmatrix, Rilegator, Rocket000Grafika:Nuvola apps important.svg  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Nuvola_apps_important.svg  Licencja: GNU Lesser General Public License  Autorzy: BastiquePlik:Http-request-pl.png  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Http-request-pl.png  Licencja: Creative Commons Attribution-Sharealike 2.5  Autorzy: Http-request.png:Andreas Grupp derivative work: Zyxist (talk)Image:Wiki letter w.svg  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Wiki_letter_w.svg  Licencja: GNU Free Documentation License  Autorzy: Jarkko PiiroinenPlik:Vleesfrigo Smatch.JPG  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Vleesfrigo_Smatch.JPG  Licencja: Public Domain  Autorzy: Blink, Gveret Tered, Jeanhousen, Juiced lemon,LimoWreck, Man vyi, Wstgrafika:Php_schemat_db.png  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Php_schemat_db.png  Licencja: Public Domain  Autorzy: Zyxgrafika:Php_tabela_db.png  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Php_tabela_db.png  Licencja: GNU General Public License  Autorzy: Zyxgrafika:Php-pma-start.png  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Php-pma-start.png  Licencja: nieznany  Autorzy: Cathy Richards, Karol Karolus, Zyxgrafika:Php-pma-baza.png  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Php-pma-baza.png  Licencja: nieznany  Autorzy: Cathy Richards, Karol Karolus, Zyxgrafika:Php-pma-tabela.png  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Php-pma-tabela.png  Licencja: nieznany  Autorzy: Cathy Richards, Karol Karolus, Zyxgrafika:Php-pma-nowa.png  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Php-pma-nowa.png  Licencja: nieznany  Autorzy: Cathy Richards, Karol Karolus, Przemub, Zyxgrafika:Php-pma-przegladaj.png  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Php-pma-przegladaj.png  Licencja: nieznany  Autorzy: Cathy Richards, Karol Karolus, Zyxgrafika:Php-pma-dodaj.png  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Php-pma-dodaj.png  Licencja: nieznany  Autorzy: Cathy Richards, Karol Karolus, Zyxgrafika:Php-pma-eksport.png  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:Php-pma-eksport.png  Licencja: nieznany  Autorzy: Cathy Richards, Karol Karolus, ZyxPlik:TempEngWeb017a_pl.svg  Źródło: http://pl.wikibooks.org/w/index.php?title=Plik:TempEngWeb017a_pl.svg  Licencja: Creative Commons Attribution-Sharealike 3.0  Autorzy: Dreftymac- original; Zyxist - translation

Licencja 344

LicencjaCreative Commons Attribution-Share Alike 3.0//creativecommons.org/licenses/by-sa/3.0/