PHP/Wersja do druku

Z Wikibooks, biblioteki wolnych podręczników.
< PHP
Przejdź do nawigacji Przejdź do wyszukiwania


PHP




Róg strony.svg
 
 


Witamy w kursie PHP na Wikibooks. Podręcznik nie jest jeszcze ukończony, jeśli czujesz się na siłach - chętnie przyjmiemy twoją pomoc.


Spis treści[edytuj]

Wprowadzenie[edytuj]

  1. O podręczniku Etap rozwoju: 100%
  2. Czym jest PHP Etap rozwoju: 100%
  3. Możliwości Etap rozwoju: 100%
  4. Jak się uczyć? Etap rozwoju: 100%

Instalacja[edytuj]

  1. Opis instalacji Etap rozwoju: 100%
  2. Apache HTTP Server Etap rozwoju: 50%
  3. MySQL 5 Etap rozwoju: 50%
  4. PHP Etap rozwoju: 100%

Podstawy języka[edytuj]

  1. Pierwszy skrypt Etap rozwoju: 100%
  2. Zmienne i tablice Etap rozwoju: 100%
  3. Formularze Etap rozwoju: 100%
    1. Instrukcja if Etap rozwoju: 100%
    2. Instrukcja switch Etap rozwoju: 100%
    3. Instrukcja for Etap rozwoju: 100%
    4. Instrukcja while Etap rozwoju: 100%
    5. Instrukcja do while Etap rozwoju: 100%
    6. Instrukcja foreach Etap rozwoju: 100%
  4. Funkcje Etap rozwoju: 100%
  5. Inne elementy składni Etap rozwoju: 100%
  6. Każdy popełnia błędy Etap rozwoju: 100%
  7. Korzystanie z dokumentacji Etap rozwoju: 100%
  8. Studium przypadku: Księga gości Etap rozwoju: 100%
  9. Ćwiczenia Etap rozwoju: 75%

Rozmaitości[edytuj]

  1. Przetwarzanie tekstu Etap rozwoju: 100%
  2. Podstawy wyrażeń regularnych Etap rozwoju: 50%
  3. Obsługa ciastek Etap rozwoju: 100%
  4. Sesje Etap rozwoju: 100%
  5. Wysyłanie e-maili Etap rozwoju: 100%
  6. Internacjonalizacja Etap rozwoju: 100%
  7. System plików Etap rozwoju: 75%
  8. Data i czas Etap rozwoju: 100%
  9. Ćwiczenia

Programowanie obiektowe[edytuj]

  1. Czym jest programowanie obiektowe? Etap rozwoju: 100%
  2. Klasy i obiekty Etap rozwoju: 100%
  3. Konstruktory i destruktory Etap rozwoju: 100%
  4. Dziedziczenie Etap rozwoju: 100%
  5. Interfejsy Etap rozwoju: 100%
  6. Wyjątki Etap rozwoju: 100%
  7. Elementy statyczne Etap rozwoju: 100%
  8. Metody magiczne Etap rozwoju: 100%
  9. Iteratory Etap rozwoju: 100%
  10. Automatyczne ładowanie Etap rozwoju: 100%
  11. Ćwiczenia Etap rozwoju: 25%

Zaawansowane programowanie[edytuj]

  1. Domknięcia
  2. Przestrzenie nazw
  3. Archiwa PHAR
  4. Podstawy przetwarzania XML-a
  5. Wzorce projektowe
  6. XDebug
  7. Ćwiczenia

Bazy danych[edytuj]

  1. Wstęp do baz danych Etap rozwoju: 100%
    1. Projekt bazy danych Etap rozwoju: 100%
    2. Zarządzanie rekordami Etap rozwoju: 100%
    3. Pobieranie rekordów Etap rozwoju: 100%
    4. Relacje i indeksy Etap rozwoju: 100%
  2. Biblioteka PDO Etap rozwoju: 100%
  3. ORM i biblioteka Doctrine
  4. Jak to się robiło kiedyś? Etap rozwoju: 100%
  5. phpMyAdmin Etap rozwoju: 100%
  6. Studium przypadku: System newsów Etap rozwoju: 25%
  7. Bazy danych - co dalej? Etap rozwoju: 100%
  8. Ćwiczenia

Systemy szablonów[edytuj]

  1. Czym jest system szablonów? Etap rozwoju: 100%
  2. Prosty edukacyjny system szablonów Etap rozwoju: 100%
  3. Wybrane systemy szablonów: Etap rozwoju: 100%
    1. Savant
    2. Open Power Template Etap rozwoju: 75%
    3. PHPTAL
    4. Smarty
  4. Ćwiczenia

Frameworki[edytuj]

  1. Czym jest framework? Etap rozwoju: 100%
  2. Wzorce złożone: MVC, MVP i pochodne
  3. Prosty framework edukacyjny
  4. Wybrane frameworki:
    1. Codeigniter Framework
    2. CakePhP Framework
    3. Prado Framework
    4. Yii Framework
    5. Symfony Framework
    6. Zend Framework
  5. Studium przypadku: Prosty blog
  6. Ćwiczenia

Bezpieczeństwo[edytuj]

  1. Wstęp do zagadnień bezpieczeństwa
  2. Techniki ataków
  3. Zabezpieczanie sesji
  4. Bezpieczne zarządzanie danymi
  5. Kontrola formularzy
  6. Obrona przed botami
  7. Podstawy kryptografii
  8. Mechanizmy uwierzytelniania
  9. Mechanizmy kontroli uprawnień
  10. Połączenia szyfrowane
  11. Ćwiczenia

Dobre praktyki[edytuj]

  1. Standardy kodowania
  2. Dokumentowanie kodu
  3. Testowanie aplikacji

Inne[edytuj]

  1. Edytory PHP Etap rozwoju: 75%
  2. Autorzy Etap rozwoju: 100%
  3. Dla twórców podręcznika Etap rozwoju: 100%(w dniu 13.04.2005)


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óżnych przyczyn (np. zawarta w nich treść jest przekazywana w zupełnie inny sposób).

  1. SQL Injection Etap rozwoju: 25%
  2. Hashowanie Etap rozwoju: 25%
  3. PHP Injection Etap rozwoju: 00%
  4. Bazy danych i sesje Etap rozwoju: 100%


Wprowadzenie[edytuj]

O podręczniku[edytuj]

Podręcznik ten nie jest "jeszcze jednym kursem PHP". To zbiorowe opracowanie, które nie tylko uczy, jak programować w języku PHP, lecz również pokazuje, jak robić to dobrze. Staramy się demonstrować najnowsze możliwości projektu, stawiając na aktualność. Podręcznik może być czytany przez wszystkich: zarówno przez osoby, które jeszcze nigdy nie miały do czynienia z programowaniem, jak i tych znających już jakieś języki.

PHP, choć na pierwszy rzut oka prosty do nauczenia, jest w rzeczywistości bardzo rozbudowanym projektem i zwyczajne wypisanie listy konstrukcji oraz funkcji nie wystarczy, aby powiedzieć o sobie "jestem doświadczonym programistą". Doświadczenie przychodzi z czasem, jednak w tym podręczniku staramy się to maksymalnie ułatwić, koncentrując się także na praktycznej stronie programowania. Pokazujemy, czego należy unikać, na co uważać, a w "Studiach przypadku" wyjaśniamy, jak zdobytą wiedzę wykorzystać do napisania skryptu realizującego konkretne zadanie.

Od czytelników tego podręcznika wymagamy pewnej znajomości języka HTML i orientowania się w tworzeniu stron WWW. Związane jest to z omawianym tu materiałem ukierunkowanym w głównej mierze na pisanie dynamicznych witryn internetowych. Jeżeli czujesz, że brakuje Ci wiedzy, zacznij swoją przygodę od poznania języka HTML. Znajomość programowania i algorytmów nie jest konieczna, aczkolwiek pozwoli na szybsze przyswajanie materiału.

Po przeczytaniu podręcznika będziesz wiedział:

  • Co to jest PHP i jak działa?
  • Jak postawić na własnym komputerze prosty serwer WWW.
  • Jak pisać aplikacje w języku PHP
  • Jak zarządzać bazami danych
  • W jaki sposób napisać profesjonalny i łatwy w rozbudowie dynamiczny serwis WWW
  • Jak poprawić bezpieczeństwo swych aplikacji i ustrzec się przed włamywaczami

Podręcznik poprawiać i uzupełniać może każdy. Jeśli tylko masz czas i nie widzisz żadnych przeszkód w liberalnej licencji GNU FDL, pomóż w jego rozwoju: dopisz rozdział, zaktualizuj zawartość, popraw błędy (dalsze informacje). Każda pomoc jest ważna, gdyż tworzą to ludzie dla ludzi. Pamiętaj jednak, aby nie używać żadnych materiałów objętych prawem autorskim, a wszelkie większe edycje konsultować w dyskusji z innymi.


Czym jest PHP?[edytuj]

PHP to skryptowy język programowania - programy w nim napisane nie są kompilowane do postaci kodu maszynowego zrozumiałego dla procesora, lecz wykonywane przez specjalną aplikację zwaną interpreterem PHP. PHP został pierwotnie stworzony do wspomagania tworzenia dynamicznych stron WWW, lecz obecnie można w nim pisać także zwyczajne aplikacje dla systemu operacyjnego.

PHP jest projektem open-source. Każdy może pobrać za darmo jego kopię, zainstalować i używać bez żadnych ograniczeń. Do kodu źródłowego zapewniony jest pełny dostęp - jeżeli programista ma odpowiednie zdolności, może go modyfikować do woli oraz nadsyłać własne propozycje zmian do osób nadzorujących projekt. Dzięki takiej wolności PHP rozwija się bardzo dynamicznie, a w Internecie można znaleźć setki modyfikacji oraz dodatkowych modułów.

Mimo otwartości kodu, nad rozwojem oficjalnej wersji projektu czuwa firma Zend Company z Izraela założona przez twórców PHP. Zapewnia ona dodatkowe narzędzia i opiekę prawną, a także wyznacza kierunki rozwoju projektu.

Bardzo istotną cechą PHP jest skalowalność. Może go używać z powodzeniem zarówno początkujący programista, dzięki uproszczeniom składni, jak i ekspert, który znajdzie tutaj wszystkie zaawansowane narzędzia niezbędne do napisania rozbudowanej aplikacji. PHP znaleźć można wszędzie: od prywatnych stron domowych przez gry internetowe aż do potężnych witryn korporacji lub portali. Opanowanie języka jest proste także dlatego, iż Internet pełen jest przewodników oraz artykułów pokazujących, jak w praktyce wykorzystywać wiele z jego możliwości.

Kiedy zaczynaliśmy pisać podręcznik, pod sam koniec 2005 roku, najnowszą dostępną wersją PHP była 5.1.1. Obecnie (czerwiec 2009 roku) dostępna jest wersja 5.3.0. Jej możliwości są zbliżone do tych oferowanych przez konkurencyjne języki skryptowe dla stron WWW: ASP, JSP. Staramy się, aby podręcznik opisywał możliwie najnowszą wersję, jednak z powodu dużej ilości materiału niekiedy mogą pojawiać się fragmenty odnoszące się do starszych wydań. W podręczniku nie opisujemy PHP 4.x, praktycznie nierozwijanego, ale wciąż popularnego na serwerach oraz wśród starszych programistów.

PHP jest wykonywany po stronie serwera. Oznacza to, że PHP nie jest interpretowany (przetwarzany) przez przeglądarkę, lecz przez specjalny program na serwerze. Co innego HTML, JavaScript w wersji wykonywanej po stronie klienta. Jak działa interpretator PHP? To proste - przerabia instrukcje PHP na HTML i JavaScript w wersji do interpretacji po stronie klienta, które trafiają na Twój komputer i wiesz co się dzieje. A przy okazji - Wikibooks i jej siostrzany projekt - Wikipedia - są napisane w PHP.

Jak PHP współpracuje ze stroną WWW?[edytuj]

PHP jest językiem server-side, tj. pracuje po stronie serwera WWW. Przeciwieństwem są języki client-side pracujące po stronie przeglądarki użytkownika (np. JavaScript w wersji wykonywanej po stronie klienta). Aby wykorzystywać go na własnej stronie, musisz upewnić się, że twój serwer 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 HTTP zwanego także serwerem WWW. Jeśli stwierdzi on, na podstawie rozszerzenia pliku, że dany dokument zawiera kod PHP, 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 HTML jest zwracany serwerowi, a ten wysyła go z powrotem do internauty.

W tym procesie kod PHP nigdy nie opuszcza serwera. Internauta zawsze otrzyma wyłącznie utworzony przez PHP kod HTML. Oto przykład. Jeśli mamy plik PHP o następującej treści:

<html>
<body>
<?php
  echo 'Podaj 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żymi ilościami danych: forami dyskusyjnymi, systemami zarządzania treścią, sklepami internetowymi. Generują one odpowiedni kod HTML dla przeglądarki, a w momencie kiedy internauta przegląda stronę, PHP już zakończył nad nią swą pracę. Jest to bardzo istotne, ponieważ wszelkie dalsze reakcje na poczynania użytkownika należy albo pozostawić przeglądarce, albo obsłużyć je za pomocą języka JavaScript.

W PHP stworzono m.in. aplikację MediaWiki, za pomocą której podręcznik ten jest oficjalnie dostępny w ramach projektu Wikibooks. PHP zarządza tutaj pobieraniem treści żądanej strony z bazy, sformatowaniem jej, a w przypadku kliknięcia na opcję "Edytuj" - dodaniem nowej wersji tekstu. JavaScript pracujący po stronie przeglądarki użytkownika ułatwia edycję tekstu, obsługując przyciski automatycznie wstawiające niektóre rodzaje formatowania oraz znaki narodowe. Jest to dobry przykład współpracowania tych dwóch języków w naprawdę dynamicznej aplikacji internetowej.

Historia projektu[edytuj]

Pierwsza wersja PHP, rozpowszechniana pod nazwą PHP/FI ("Personal Home Page/Forms Interpreter"), została stworzona przez Rasmusa Lerdorfa w roku 1994 jako zestaw skryptów Perla służący do monitorowania internautów odwiedzających jego witrynę. Gdy ruch stał się zbyt duży, przepisał je w języku C, dodając przy tym nowe opcje. Niedługo później ludzie zaczęli prosić go o możliwość użycia tych narzędzi na swoich stronach, zatem 8 czerwca 1995 roku autor udostępnił publicznie kod źródłowy (PHP Tools 1.0). Już kilka miesięcy później projekt przekształcił się w zalążek znanego obecnie języka programowania, gdy został połączony z innym narzędziem Rasmusa Lerdorfa - "Form Interpreter", które dało drugi człon nazwy. W 1997 roku pojawiło się PHP/FI 2.0, posiadające wtedy kilka tysięcy aktywnych użytkowników na całym świecie oraz obsługujące 50 tys. domen. Co ciekawe, wersja ta spędziła większość "życia" na beta testach. Oficjalne wydanie było tylko jedno i ukazało się w listopadzie 1997 roku.

W 1997 roku projektem zainteresowali się dwaj izraelscy programiści: Zeev Suraski i Andi Gutmans. Odkryli oni, że PHP/FI ma zbyt małe możliwości na potrzeby aplikacji eCommerce, którą tworzyli na uniwersytecie. Zdecydowali wtedy, że przepiszą kod PHP całkowicie od nowa, korzystając z pomocy już istniejącej społeczności PHP. W czerwcu 1998 roku ogłosili PHP 3.0 jako następcę PHP/FI, którego dalszy rozwój został wtedy zatrzymany. Był to wielki krok naprzód. PHP 3.0 posiadało całkowicie nową architekturę, która znacznie zwiększała wydajność. Pojawiły się w niej zalążki programowania obiektowego, ale najważniejszą cechą aplikacji była jej modularność. Użytkownicy mogli rozszerzać teraz funkcjonalność języka poprzez dodawanie nowych modułów.

Krótko po wydaniu PHP 3, w zimie 1998 Zeev Suraski oraz Andi Gutmans jeszcze raz zabrali się za przepisywanie kodu źródłowego PHP, korzystając z doświadczeń nabytych przy pracach nad poprzednią wersją. Za główne cele obrali poprawienie modułowości oraz wydajności złożonych aplikacji. Choć dotychczasowa wersja potrafiła sobie z nimi poradzić, nie była jednak stworzona do tego celu i przegrywała przez to z innymi rozwiązaniami.

W połowie roku 1999 ukazał się oficjalnie Zend Engine, nowy silnik języka skryptowego, wokół którego niedługo później zaczęto budować PHP 4. Jego nazwa to kompromisowe połączenie imion twórców projektu. Nowa, oparta o niego wersja PHP, ukazała się w maju 2000 roku. Tak jak poprzednio, był to potężny krok naprzód. Programiści mieli do dyspozycji teraz wiele nowych narzędzi, konstrukcji językowych oraz bezpieczniejszy system wejścia/wyjścia. Od strony administracyjnej pojawiło się oficjalne wsparcie dla wielu nowych serwerów. Przez cztery lata od chwili wydania ukazały się trzy kolejne edycje tej wersji oznaczone numerami: 4.1, 4.2 oraz 4.3. W każdej z nich odczuwalne było zwiększenie bezpieczeństwa, szybkości działania oraz możliwości. W 2004 roku obsługiwały one łącznie 20% wszystkich domen sieciowych. Również daleko po premierze PHP 5, "czwórka" była bardzo chętnie wykorzystywana przez administratorów, ze względu na dużą stabilność.

W 2002 roku Zeev Suraski oraz Andi Gutmans ponownie rozpoczęli znaczącą modernizację silnika PHP mającą na celu dodanie do tego języka modelu obiektowego z prawdziwego zdarzenia. W lutym 2003 ukazała się pierwsza wersja alpha nowej wersji PHP oznaczonej numerem 5.0.0. Stabilna wersja ukazała się prawie półtora roku później, w lipcu 2004 roku. Nowości sprawiły, że PHP zaczął konkurować z innymi rozwiązaniami server-side jak równy z równym. Pojawił się całkowicie nowy model programowania obiektowego, przez co niestety została utracona część kompatybilności z poprzednimi wersjami PHP w niektórych skryptach. Jest to spowodowane zmianą sposobu reprezentacji obiektów. Przebudowano także wiele modułów, w tym do obsługi XML'a i komunikacji z bazą danych, czyniąc je bardziej przyjaznymi dla programistów.

W połowie roku 2005 zaczęły pojawiać się oficjalne sygnały, że rozpoczęto wstępne prace nad PHP 6. Obecnie publicznie dostępne są codzienne snapshoty rozwojowego repozytorium kodu źródłowego, które można ściągnąć i przetestować. Głównym celem jest dalsze dążenie do ujednolicenia projektu, wprowadzenia kolejnych możliwości wymaganych przez złożone projekty (m.in. pełne wsparcie Unicode czy system cache'owania kodu). Usuwane są też kolejne archaiczne rozwiązania pochodzące jeszcze z czasów PHP/FI oraz PHP3, co w przypadku najstarszych skryptów ponownie spowoduje problemy z kompatybilnością.

Na podstawie Wikipedii


Możliwości[edytuj]

Budowa PHP[edytuj]

PHP ma budowę modułową. Jądro projektu zapewnia obsługę wszystkich elementów języka oraz dostęp do podstawowego zestawu funkcji. Aby dodać więcej możliwości, instaluje się dodatkowe moduły. Część z nich ma charakter oficjalny i jest dołączona do każdej dystrybucji, pozostałe zgrupowane są w tzw. repozytorium PECL i należy je samodzielnie pobrać.

PHP, dzięki otwartości kodu źródłowego, pracuje bez problemu na niemal każdym istniejącym obecnie 32-bitowym systemie operacyjnym (z Uniksem oraz Windowsem na czele) oraz pozwala na łatwą integrację z większością serwerów WWW, np. Apache, IIS, OmniHTTPD.

Co PHP może zrobić?[edytuj]

PHP 5.1 oferuje swoim programistom wiele możliwości:

  1. Komunikacja z wieloma popularnymi bazami danych poprzez jednolity interfejs.
  2. Obsługa wielu popularnych protokołów sieciowych, m.in. SSL, IMAP, SMTP, IRC.
  3. Profesjonalne wsparcie standardu XML.
  4. Tworzenie obrazków w wielu popularnych formatach graficznych.
  5. Wiele funkcji obróbki tekstu.
  6. Wyrażenia regularne.
  7. Bardzo elastyczne tablice o mieszanych kluczach.
  8. Wsparcie dla usług sieciowych (SOAP, XML-RPC).
  9. Zaawansowany model programowania obiektowego.
  10. Możliwość zintegrowania z platformą .NET.
  11. Obsługa obiektów COM w Windows.
  12. Funkcje kryptograficzne.
  13. Funkcje konwersji kalendarza.
  14. Funkcje konwersji kodowań.
  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ę systemu operacyjnego, a nawet programów okienkowych z graficznym interfejsem użytkownika. Dlatego stanowi atrakcyjną alternatywę dla języków takich jak Perl. Instalator napisany w PHP, uruchamiany z konsoli systemowej, posiada m.in. repozytorium PEAR.

Wady PHP[edytuj]

Początkowe lata rozwoju miały jednak w sobie coś z pospolitego ruszenia. Z tamtego okresu do dziś przetrwało trochę niekonsekwencji oraz nieścisłości, które sprawiają pewne kłopoty programistom. Twórcy powoli je usuwają, ale potrwa to jeszcze wiele lat.

  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 do bardziej czytelnej dla nich postaci, oszczędzając wiele czasu. PHP za każdym uruchomieniem wykonuje całą pracę od zera i choć jego interpreter jest niezwykle wydajny, z powodu nadkładania sobie pracy szybko traci zapas mocy. Możliwość kompilacji skryptów dostępna jest wyłącznie jako rozszerzenie.

Twórcy PHP świadomi są tych ograniczeń i starają się nie ignorować programistów. Problemem jest zachowanie kompatybilnoś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 oraz www.php.net.

Popularność PHP[edytuj]

PHP jest, mimo swoich wad, niezwykle popularny. Wykonać w nim można wszystkie typy witryn internetowych: od stron domowych przez gry internetowe, aż do komercyjnych portali oraz witryn wielkich korporacji. Polska społeczność PHP jest bardzo aktywna i napisała do tego języka wiele poradników co w połączeniu z łatwym dostępem do książek, daje naprawdę atrakcyjny produkt, jeśli chodzi o wsparcie. Nietrudno jest także zlokalizować dobry hosting, który za niewielką opłatą udostępni na serwerze konto z PHP oraz bazą danych.


Jak się uczyć?[edytuj]

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 przez każdego.

Nie przejmuj się, że twoje pierwsze projekty będą miały w sobie nieco z chaotyczności. To normalny proces, niemniej nie można na tym poprzestać. Za drugim razem zadaj sobie pytanie: co poprzednio mogłem zrobić lepiej? Czego mnie ten projekt nauczył? W ten sposób stopniowo dojdziesz do wprawy.

Duże znaczenie ma również rozumienie samych komputerów i algorytmów. PHP jest normalnym językiem programowania i pewnych spraw nie da się ominąć. Teoria bardzo dobrze uzupełnia praktykę, dlatego dokształcaj się także pod tym względem. Pozwoli Ci to zrozumieć, dlaczego dany problem rozwiązywany jest tak, a nie inaczej lub dlaczego w ogóle się nim zajmujemy.

Cała wiedza o PHP zgromadzona jest w obszernej dokumentacji (www.php.net/docs.php), zawierającej m.in. opis wszystkich modułów. Należy nauczyć się sprawnego poruszania po niej i orientowania się, gdzie co leży. Temu zagadnieniu poświęcony został jeden z rozdziałów podręcznika. Do dokumentacji warto zaglądać regularnie, gdyż jest ona zawsze zgodna z najnowszą dostępną wersją PHP. Stamtąd najszybciej się dowiesz, czy nie pojawiły się nowe funkcje, parametry, możliwości wykorzystania istniejących elementów. Jeżeli zamierzasz wiązać się na dłużej ze środowiskiem PHP, adres [www.php.net] powinien nawet znaleźć się wśród najczęściej przeglądanych przez Ciebie witryn. Stanie w miejscu, kiedy projekt ten posuwa się naprzód, jest kiepską alternatywą i bez orientowania się w zachodzących zmianach szybko odkryjesz, że twoje skrypty z tajemniczych powodów nie chcą pracować na nowych wersjach, co przyprawia o złość klienta.

Teoria czy praktyka?[edytuj]

Wielu początkujących programistów zadaje sobie pytanie - czy skupić się na teorii, czy też raczej na praktyce? Odpowiedź jest prosta: nie można się skupiać wyłącznie na jednym zagadnieniu. Ważna jest bowiem wiedza jak działa ten język programowania, ale nie można nie wiedzieć jak go wykorzystać.

Przed przystąpieniem do pisania skryptu powinniśmy rozrysować sobie jego projekt na papierze oraz ustalić pewne konwencje, jakich będziemy się trzymali w kodzie. Istotne jest, aby planowane przedsięwzięcie umieć zrealizować w praktyce. Choć projekty często tworzymy, aby nauczyć się czegoś nowego, poprzeczka nie powinna być windowana zbyt wysoko, gdyż wtedy nigdy go nie ukończymy. O wiele lepsze jest stopniowe poznawanie coraz bardziej złożonych elementów.

W książce tej duży nacisk położony jest na ponowne wykorzystywanie raz napisanego kodu. Uważamy, że nie ma sensu raz po raz na nowo odkrywać koła i tworzyć wszystkiego od nowa. Dobry programista po pewnym czasie utworzy sobie zbiór bibliotek z najczęściej używanymi funkcjami, które będzie przenosić między kolejnymi projektami, oszczędzając sobie zbędnej pracy. Praktyki te także powinny być w naszym planie uwzględnione. Inne sprawy, na które powinniśmy zwrócić uwagę, to:

  • nazewnictwo - czy stosujemy nazwy polskie, czy angielskie; w jakim stylu je zapisujemy: nazwa_nazwa czy nazwaNazwa,
  • jednolity styl kodowania,
  • jakie biblioteki zewnętrzne wykorzystamy 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, generatory diagramów itd. Po głębszym poznaniu języka PHP można zaznajomić się również z diagramami UML wykorzystywanymi w zawodowych zespołach programistycznych.

Fora dyskusyjne[edytuj]

Niestety, nie każdy problem będziemy w stanie rozwiązać samodzielnie. Wtedy z pomocą przyjdą nam fora dyskusyjne, których dużo jest w polskim Internecie. Zanim zadasz jakieś pytanie, użyj wyszukiwarki, często bowiem odpowiedź została już wcześniej udzielona. Pamiętaj, że często aby uzyskać odpowiedź, musisz zastosować się do reguł forum. Poświęć przynajmniej kilka minut na zapoznanie się z regulaminem oraz zaleceniami. Ich przeczytanie to 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ługim oczekiwaniem 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 pewno zostaną źle odebrane przez internautów.
  • Użyłeś wyszukiwarki i nie znalazłeś odpowiedzi na swoje pytanie. Z pomocą wyszukiwarki często znajdziesz odpowiedź znacznie szybciej, niż gdybyś miał oczekiwać na odpowiedź na forum.
  • Problem nie jest wyjaśniony w jakimś artykule w serwisie. Wiele serwisów posiada bazę artykułów i porad, a takż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 edytorze tekstu ze sprawdzaniem pisowni.

Oczywiście istotna jest także sprawa tytułu tematu. Najlepiej kiedy streszcza on istotę problemu, np. "Problem z połączeniem z bazą danych". Nazwa "Mam problem, pomocy!" nie jest już dobra, gdyż nie mówi, co jest w środku. W efekcie temat może zostać niezauważony przez osobę znającą rozwiązanie. Zwróć też uwagę, czy forum nie wymaga dodania jakiegoś krótkiego prefiksu, np. "[php]" w celu łatwiejszego skatalogowania tematu.

Na koniec pamiętaj - nawet jeżeli zostaniesz przez moderatora publicznie upomniany, nie jest to jeszcze powód do rozpaczy. Zadaniem moderatora jest utrzymanie porządku i nie oznacza to, iż żywi on do ciebie przez to jakąś osobistą urazę. Po prostu przeproś i w przyszłości staraj się unikać podobnego błędu.


Instalacja[edytuj]

Opis instalacji[edytuj]

PHP jest dostępny na niemal każdym popularnym systemie operacyjnym, lecz do efektywnego korzystania z niego potrzebne jest dodatkowe oprogramowanie.

Serwer WWW[edytuj]

PHP pracuje po stronie serwera, dlatego aby móc testować nasze skrypty w przeglądarce internetowej, musimy "postawić" taki na własnym komputerze. Nasz wybór padł na program o otwartych źródłach Apache HTTP Server rozwijany przez Apache Foundation (httpd.apache.org). Jest to najchętniej wykorzystywany serwer WWW na świecie, obsługujący prawie 60% wszystkich witryn, a przy tym bezpieczny i łatwy w konfiguracji. Dostępne jest wiele wersji na różne systemy operacyjne.

PHP komunikuje się z serwerem WWW na trzy sposoby:

  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ż dla każdego żądania musi zostać uruchomiona nowa, niezależna kopia interpretera, jednak oferuje administratorowi zmiany wielu ustawień dotyczących bezpieczeństwa wykonywanych skryptów.
  2. Moduł serwera - do najpopularniejszych serwerów można podłączyć PHP jako moduł. Znacząco zwiększa wydajność, lecz utrudnia zachowanie odpowiedniego poziomu bezpieczeństwa na serwerze.
  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łabo udokumentowane oraz wymaga skorzystania z nieoficjalnych dodatków, przez co stanowi wyzwanie nawet dla doświadczonych administratorów.

Baza danych[edytuj]

PHP sprawdza się najlepiej przy przetwarzaniu danych, lecz do ich przechowywania używana jest najczęściej niezależna aplikacja, np. relacyjna baza danych. Dane w bazie przechowywane są jako rekordy w tabelach o określonej strukturze, a słowo "relacyjny" oznacza, iż mogą istnieć wszelkiego rodzaju powiązania między tabelami. Do komunikacji z bazami wykorzystywany jest specjalny język SQL, którego podstawy także niebawem poznamy.

Wśród programistów PHP największą popularnością cieszy się baza danych darmowa do użytku domowego MySQL produkowana przez szwedzką firmę MySQL AB (www.mysql.com). Także i ona współpracuje z każdym popularnym systemem operacyjnym, a przy tym nietrudno znaleźć pomoc w przypadku ewentualnych kłopotów.

Biblioteki dodatkowe[edytuj]

Jeżeli pracujesz na systemie Unix/Linux i chciałbyś zainstalować PHP ze źródeł, musisz upewnić się, że posiadasz zainstalowane wszystkie dodatkowe biblioteki. Zazwyczaj powinny być one już dostępne np. jako pakiety, ale na wszelki wypadek podamy adresy, pod którymi można je zdobyć:

Instalując PHP w systemach Windows, wszystkie biblioteki dostarczane są razem z pakietem instalacyjnym.

Proces instalacji[edytuj]

Instalacja PHP zostanie przeprowadzona w kilku etapach:

  1. instalacja Apache,
  2. instalacja MySQL,
  3. instalacja PHP,
  4. integracja PHP z serwerem WWW,
  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. W podręczniku nie będziemy opisywać, jak je z nich zainstalować, ponieważ opis rozrósłby się do niewyobrażalnych rozmiarów. Jeżeli jesteś zainteresowany tym sposobem instalacji, poszukaj odpowiedniego poradnika w dokumentacji twojej dystrybucji.

Alternatywny sposób instalacji[edytuj]

Możemy również posłużyć się gotowym pakietem WAMP, który zawiera wszystkie wymienione wyżej składniki. Przykładowym zestawem tego typu jest program VertrigoServ, lub KrasnalServ, który zawiera polskie tłumaczenie i znacznie ułatwia instalacje i zarządzanie poszczególnymi komponentami serwera.

W zeszycie ćwiczeń podręcznika Informatyki dla gimnazjum znajduje się dział Serwer WWW wraz z bazą danych i PHP opisujący w skrócie instalację i uruchomienie pakietu XAMPP.


Instalacja Apache HTTP Server[edytuj]

Aby wygodnie testować skrypty PHP i generowane przez nie witryny internetowe, należy postawić na własnym komputerze prywatny, testowy serwer WWW.

Najpopularniejszym wykorzystywanym w Internecie serwerem WWW jest program o otwartych źródłach Apache HTTP Server rozwijany przez Apache Foundation. Zarówno dokumentację, jak i pakiety instalacyjne można znaleźć pod adresem httpd.apache.org.

Moduł ten opisuje, jak zainstalować Apache na różnych systemach operacyjnych. Możemy również skorzystać z pakietu XAMPP, który zainstaluje Apache wraz z potrzebnymi nam serwerem MySQL oraz interpreterem PHP.

Instalacja w systemach Unix/Linux[edytuj]

Kompilacja ze źródeł[edytuj]

Wydajemy polecenia w konsoli: su [enter], hasło administratora

Przechodzimy do katalogu z kodem źródłowym

./configure
make
make install

Pakiety[edytuj]

Serwer Apache oraz inne niezbędne programy można także zainstalować z pakietów dołączonych do Twojej dystrybucji Linuksa, jednak z powodu ich różnorodności nie będziemy w tym podręczniku omawiać szczegółowo instalacji na każdym z nich. Podajemy jedynie ogólne wskazówki, jak przystosować pakiet do uruchamiania przykładów z podręcznika. Bardzo prawdopodobne, że na stronie internetowej twojej dystrybucji znajduje się artykuł opisujący dokładnie instalację zestawu Apache + PHP, dlatego tam też odsyłamy po szczegóły.

Aby zainstalować serwer Apache z pakietu, upewnij się, że twoja dystrybucja go zawiera (pamiętaj, aby wybrać wersję 2.x), a następnie postępuj zgodnie z instrukcją instalacji dla twojego menedżera pakietów. Przeważnie pakiet automatycznie konfiguruje też serwer do pracy. Pliki konfiguracyjne odnajdziesz najczęściej w katalogu /etc/apache, natomiast strony WWW można otwierać z /var/www. Zmienimy sobie ten ostatni katalog tak, aby każdy użytkownik systemu miał na swoim koncie katalog www.

  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 katalogu
  2. Znajdź dyrektywę UserDir i ustaw ją na /home/*/www
  3. 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 oraz dostęp przez localhost/~test)

Możesz teraz uruchomić serwer Apache poprzez skrypt startowy demona znajdujący się wraz z innymi (np. w katalogu /etc/rc.d/ albo /etc/init.d/ w zależności od dystrybucji). Pamiętaj, że jest to na razie tylko serwer, do którego wciąż musimy podpiąć PHP. Zajmiemy się tym w następnym rozdziale.

Instalacja w systemach Windows[edytuj]

Pobieramy ze strony httpd.apache.org 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ące dane:

Network Domain localhost
Server Name localhost
Administator's Email Address admin@localhost

Zaznaczanie opcji for all users jest niekonieczne, ponieważ stawiamy serwer testowy na prywatnym komputerze. Jeżeli jest on podłączony do sieci lokalnej, jest to nawet niewskazane, ponieważ później zainstalujemy obsługiwany z przeglądarki menedżer baz danych, który ktoś mógłby wykorzystać do zniszczenia nam projektów.

Na następnym ekranie zaznaczamy opcję "Typical" i 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ż katalog D:\Serwer\Apache2\htdocs, z którego domyślnie odczytywane są strony, nie jest najlepszym rozwiązaniem umieszczać tam wszystkich naszych dokumentów. Pozostawimy go tylko w celu późniejszego zainstalowania w nim różnych menedżerów serwera (np. do zarządzania bazą danych) obsługiwanych z poziomu przeglądarki. Dla naszych własnych plików utworzymy katalog D:\Serwer\www.

Otwórz teraz plik konfiguracyjny serwera: D:\Serwer\Apache2\conf\httpd.conf lub też "D:\Serwer\Apache2\conf\default\httpd.conf". Odnajdź w nim następujące dyrektywy i przypisz im wartości:

Listen 127.0.0.1:80
ServerName localhost
DirectoryIndex index.html index.php index.php5
UserDir "D:/Serwer/www"
DocumentRoot "D:/Serwer/www"

Należy tu również pamiętać, że zmieniając DocumentRoot, trzeba dokonać zmian również około 20 linijek poniżej. Nakazuje to następujący komentarz : "# This should be changed to whatever you set DocumentRoot to".

UWAGA: W niektórych wersjach apache dyrektywa UserDir ma oddzielny plik. Znajduje się on w "D:\Serwer\conf\extra\httpd-userdir.conf" . Należy wtedy odkomentować linijkę # Include conf/extra/httpd-userdir.conf (tzn. usunąć znak #), jak również linijkę "# LoadModule userdir_module modules/mod_userdir.so"

Pod dyrektywą UserDir dodaj jeszcze poniższy blok:

 <Directory "D:/Serwer/www">
   AllowOverride All
   Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec ExecCGI
   Order allow,deny
   Allow from all
 </Directory>

Jego zadaniem jest powiadomienie serwera, jakie akcje są dozwolone wewnątrz tego katalogu.

Po instalacji i uruchomieniu aplikacji powinno pokazać się dosowe okno. Świadczy ono o tym, że serwer pracuje. Należy pamiętać o tym, aby nie zamykać tego okna ponieważ zakończy to działanie serwera.

Teraz testujemy, czy wszystko przebiegło poprawnie. Otwórz przeglądarkę i wpisz w niej http://localhost/. Powinna pojawić ci się domyślna strona startowa serwera. Aby przetestować katalog "www", utwórz w nim katalog np. test, zapisz w nim plik index.html, edytując go np. notatnikiem wpisz w jego treści "Hello World" , następnie uruchom w przeglądarce: http://localhost/~test/. Jeżeli wyświetlił się napis, wszystko jest OK. W systemach 2000/XP serwer Apache może pracować jako usługa systemu operacyjnego. Zamiast okienka dosowego, dostępna będzie w zasobniku systemowym ikonka programu "Apache Monitor", pozwalająca na włączanie, wyłączanie oraz restartowanie zainstalowanych usług Apache. Jeśli taka ikonka nie wyświetla się w zasobniku systemowym, należy uruchomić "D:\Serwer\bin\ApacheMonitor.exe". Aby mieć do niej dostęp na stałe, można dodać skrót do tego programu do Autostartu w Menu Start.

Należy pamiętać, że domyślna konfiguracja serwera Apache może różnić się znacząco od tych, które są ustawione w całych zestawach instalacyjnych (Krasnal, WebServ itd.) - np. często wykorzystywany mod_rewrite należy samemu włączyć po instalacji Apache'a, poprzez edycję pliku httpd.conf (znajdujemy linijkę #LoadModule rewrite_module modules/mod_rewrite.so i usuwamy # na początku.)

Instalacja w systemie Mac OS X[edytuj]

Standardowo Mac OS X ma zainstalowany serwer Apache - możesz go włączyć w panelu preferencji Sharing jako Personal Web Sharing.

Jeśli chcesz nowszą wersję Apache, możesz zainstalować pakiet Apache2 za pomocą programów Fink oraz FinkCommander.

Apache można również skompilować samemu. Potrzebna będzie instalacja dodatku Developer Tools z płyty systemu Mac OS X, dalej należy postępować jak w przypadku systemów Unix/Linux.


Instalacja MySQL 5.0[edytuj]

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

Instalacja w systemach Unix/Linux[edytuj]

Kompilacja ze źródeł[edytuj]

$ ./configure --prefix=/usr/local/mysql
$ make
# make install

Pakiety[edytuj]

Także serwer baz danych MySQL można zainstalować z pakietów. Przeważnie jest on już wtedy w pełni gotowy do pracy i wymaga jedynie uruchomienia. Po szczegóły instalacji odsyłamy do instrukcji twojego menedżera pakietów. Jeśli pakiet MySQL nie był zainstalowany domyślnie w Twoim komputerze, być może będziesz musiał sam skonfigurować i uruchomić serwer.

Konfiguracja i uruchomienie MySQL pod Linuksem[edytuj]

Poniższy opis został sprawdzony w dystrybucji Slackware Linux. Pod innymi dystrybucjami może pójść tylko łatwiej. Żeby przekonać się, czy serwer MySQL działa, należy spróbować się na niego zalogować. Jako root:

$ mysql -u root -p

Jeśli MySQL zapyta o hasło to znaczy, że działa. Gdy zaś otrzymamy komunikat w stylu "cannot connect", musimy sprawdzić i poprawić/uzupełnić/dokończyć konfigurację:

Czy w katalogu /etc jest plik my.cnf? Jeśli go nie ma, należy skopiować w któryś z gotowych plików my-[small|medium|large|huge].cnf. Przykładowo dla prostej bazy danych na zwykłym domowym PC do ćwiczeń w zupełności wystarczy model small - nie zużyje wielkich połaci RAM-u.

# cp /etc/my-small.cnf /etc/my.cnf

Teraz należy utworzyć grupę mysql i użytkownika mysql (niewykluczone, że już istnieje), nadać mu hasło i prawa do katalogów i programów związanych z MySQL.

# useradd mysql
# passwd mysql
# groupadd mysql
# chown mysql:mysql /usr/bin/mysql*

Nieskonfigurowany MySQL daje nam możliwość łatwego wyboru miejsca, w którym będą przechowywane bazy. Można wybrać standardowe miejsce w /var/lib/mysql, ale byłoby dobrze nie przechowywać ważnych danych na partycji systemowej. Wielu użytkowników ma katalog /home domyślnie przez dystrybucję lub z własnego wyboru umieszczony na innej partycji lub nawet innym dysku. Warto wtedy utworzyć w /home katalog na bazy, który przeżyje każdą katastrofę łącznie z ponowną instalacją systemu. Obojętnie, które rozwiązanie wybierzesz, dalej będę się posługiwał nazwą /ścieżka/do/bazy.

# mkdir /ścieżka/do/bazy            (np. /home/mysql_bazy)
# chown -R mysql:mysql /sciezka/do/bazy

Następnie należy przygotować katalog roboczy MySQL-a. Należy to zrobić jako użytkownik mysql - inaczej będziemy mieli trudności z uruchomieniem i działaniem serwera, prawami dostępu etc.

# su - mysql
$ mysql_install_db  --datadir="/ścieżka/do/bazy"
$ exit  (wyjście do roota)

Jeśli instalowaliśmy bazę w innym niż domyślny katalogu, należałoby uwzględnić tę zmianę w skrypcie startowym serwera. W tym celu otwieramy w dowolnym edytorze plik /etc/rc.d/rc.mysqld, odnajdujemy zapis mysqld_safe --datadir=/var/lib coś_tam_dalej i zmieniamy go na mysqld_safe --datadir=/ścieżka/do/bazy coś_tam_dalej. Serwer musi wiedzieć, z jakim katalogiem będzie pracował. W innych dystrybucjach (np. Debian) skrypty startowe umieszczane są w katalogu /etc/init.d/. Przy okazji zajmiemy się automatycznym uruchamianiem serwera na przyszłość.

# chmod 755 /etc/rc.d/rc.mysqld

Teraz już możemy uruchomić serwer: Edytujemy plik /var/lib/mysql/mysqld.conf (w innych dystrybucjach np. /var/lib/mysql/mysqld.conf)i, jeśli baza będzie służyć tylko nam na naszym komputerze (a tak zakładamy w podręczniku), należy odkomentować (usunąć znak # z początku) linijkę, w której znajduje się 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ększyć bezpieczeństwo, możemy użyć skryptu mysql_secure_installation. Jest on dobrze opisany podczas wykonywania - prawie, że wizard. Pozostawi jedynie konto roota dla bazy, wyrzuci konta anonimowe i bazę test wraz z zawartymi w niej tabelami.

Instalacja w systemach Windows[edytuj]

Poniższy sposób instalacji dotyczy systemów Windows 2000 oraz Windows XP. W następnym akapicie są opisane różnice dla przypadku Windows 98.

  1. Wejdź na stronę [1].
  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. Wybierz katalog, do którego chcesz zainstalować serwer.
  6. Zaakceptuj proponowane przez instalator komponenty pakietu.
  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. Zaznacz opcję "Configure the MySQL Server now" i kliknij "Finish". Uruchomione zostanie narzędzie konfiguracji serwera.
  10. Wybierz "Reconfigure Instance" i kliknij "Next".
  11. Wybierz "Standard configuration" i kliknij "Next".
  12. W kolejnym kliknij na "Next" bez zaznaczania czegokolwiek.
  13. Zaznacz opcję "Modify Security Settings" i wpisz we wszystkie pola hasło "root".
  14. Kliknij "Execute", aby zapisać zmiany.

MySQL został zainstalowany i jest dostępny jako usługa systemu Windows. Możesz go zatrzymywać lub restartować poprzez Panel Sterowania > Narzędzia administracyjne > Usługi.

Poniższy sposób instalacji dotyczy systemu Windows 98.

  1. Wykonaj kolejno kroki od 1. do 9. z opisu instalacji w Windows 2000/XP (powyżej).
  2. Kliknij "Next", wybierz "Standard configuration" i ponownie kliknij "Next".
  3. W kolejnym kliknij na "Next" bez zaznaczania czegokolwiek.
  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 -p shutdown".

W razie problemów z "Call to undefined function mysqli_connect()" skopiuj libmysql.dll z folderu "php5" do "Apache2\bin"

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

Instalacja w systemie Mac OS X[edytuj]

Wystarczy pobrać gotowy pakiet (.dmg) ze strony domowej MySQL. Zamieszczony jest na nim standardowy instalator. Dodatkowo warto kliknąć na plik .prefPane, który doda do systemowych preferencji możliwość startowania i zatrzymywania serwera.

Po instalacji Developer Tools z płyty systemu Mac OS X można skompilować bazę ze źródeł, tak samo jak w innych systemach uniksowych.


Instalacja PHP 5.2[edytuj]

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

Instalacja w systemach Unix/Linux[edytuj]

Pakiety[edytuj]

Niemal wszystkie dystrybucje systemu Linux mają w swoich zasobach pakiety PHP. Jeżeli nie czujesz się na siłach instalować projektu ze źródeł, możesz skorzystać z tego rozwiązania i postępować odpowiednio według instrukcji systemu pakietów używanego w twojej dystrybucji. Pakiety mają jednak kilka wad, a najważniejszą z nich jest konieczność polegania na intuicji autora pakietów. Interpreter nie zawsze jest skonfigurowany tak, jak byśmy chcieli, a ponadto należy ręcznie uaktywnić sobie dodatkowe moduły. Choć nowe wersje PHP ukazują się przeważnie w kilkumiesięcznych odstępach, niekiedy dystrybucje wyposażone są w naprawdę stare wersje interpretera. Przypominamy, że podręcznik ten pisany jest pod wersje PHP 5.1.x/5.2.x, więc jeśli twój pakiet zainstaluje Ci starszą, niektóre przykłady mogą nie działać prawidłowo.

Po zainstalowaniu PHP z pakietu odnajdź w swoim systemie plik php.ini i upewnij się, że następujące dyrektywy są ustawione na:

  • error_reporting = E_ALL | E_STRICT - poziom raportowania błędów
  • 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 wersje PHP 4, aktualnie zdecydowanie odradza się korzystanie z nich nie tylko ze względów bezpieczeństwa, ale też z powodu planów ich wycofania w PHP 6. Dlatego pozostawiamy je wyłączone i tak też będziemy pisać nasze skrypty.

Upewnij się ponadto, że PHP ładuje moduły php_pdo.so, php_pdo_mysql.so, php_mysql.so, php_zlib.so oraz php_gd2.so.

Kompilacja ze źródeł[edytuj]

Jeśli zdecydowałeś się na kompilację ze źródeł, zacznij od ich pobrania - źródła projektu dostępne są na stronie www.php.net. Rozpakowujemy je w katalogu /usr/src:

tar -xvf php-5.2.0.tar.gz

Jeżeli ściągnąłeś inną wersję projektu albo źródła są skompresowane innym algorytmem, odpowiednio zmodyfikuj nazwę swojego pliku. Po rozpakowaniu powinien pojawić się katalog php-5.2.0. Przechodzimy do niego:

cd php-5.2.0

Teraz rozpoczynamy konfigurowanie naszej kompilacji poprzez podanie odpowiednich dyrektyw do skryptu configure.

./configure --with-apxs2=/usr/local/apache2/bin/apxs --with-config-file-path=/etc/apache
--with-zlib --with-mysql=/usr/local/mysql
--with-mysqli=/usr/local/mysql/bin/mysql_config 
--with-pdo-mysql=/usr/local/mysql/bin/mysql_config --with-gd --enable-gd-native-ttf
--with-libxml-dir=/usr/local/libxml

Oto opis poszczególnych opcji:

  • --with-apxs2=/usr/local/apache2/bin/apxs - informujemy, że chcemy skompilować PHP jako moduł serwera Apache i podajemy ścieżkę do programu apxs dostarczanego wraz z nim.
  • --with-config-file-path=/etc/apache - informujemy, że plik konfiguracyjny PHP znajdować się będzie w katalogu /etc/apache.
  • --with-zlib - dodajemy bibliotekę Zlib potrzebną niektórym modułom.
  • --with-mysql=/usr/local/mysql - aktywujemy najstarsze rozszerzenie do obsługi baz danych MySQL, podając ścieżkę do katalogu, w którym znajduje się folder z nagłówkami C serwera.
  • --with-mysqli=/usr/local/mysql/bin/mysql_config - aktywujemy nowe rozszerzenie do obsługi baz danych MySQL, podając ścieżkę do programu mysql_config generującego odpowiednie pliki.
  • --with-pdo-mysql=/usr/local/mysql/bin/mysql_config - aktywujemy sterownik dla bazy MySQL dla biblioteki PHP Data Objects omawianej w tym podręczniku. Ścieżka także prowadzi do programu mysql_config.
  • --with-gd - aktywujemy bibliotekę GD do generowania obrazków.
  • --enable-gd-native-ttf - aktywujemy wbudowaną obsługę czcionek TTF w bibliotece GD.
  • --with-libxml-dir=/usr/local/libxml - podajemy ścieżkę do katalogu biblioteki libxml, dzięki czemu aktywne będą moduły do obsługi XML-a w PHP.

Kiedy skrypt konfiguracyjny zakończy działanie, wydajemy dwa kolejne polecenia kompilacji oraz instalacji PHP:

make
make install

Kopiujemy plik konfiguracyjny PHP do odpowiedniego katalogu:

cp php.ini-recommended /etc/apache/php.ini

Otwieramy ten plik i modyfikujemy w nim następujące dyrektywy:

  • error_reporting - ustawić na E_ALL | E_STRICT
  • doc_root - 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 koniec restartujemy/uruchamiamy serwer Apache:

/usr/local/apache2/bin/apachectl start

Pora na przetestowanie działania PHP. Do katalogu /usr/local/apache2/htdocs kopiujemy plik phpinfo.php o treści:

<?php phpinfo(); ?>

W przeglądarce wpisujemy http://localhost/phpinfo.php - powinien ukazać się bardzo długi raport nt zainstalowanej wersji PHP (wersja, konfiguracja, moduły itd.). Jeżeli zamiast tego pokazany zostanie wpisany wyżej kod, oznacza to, że instalacja nie przebiegła poprawnie i na którymś etapie został popełniony błąd.

Instalacja w systemach Windows[edytuj]

Instalacja PHP jako jedyna nie polega na klikaniu dalej. Taka możliwość oczywiście istnieje, lecz wtedy nie można skonfigurować interpretera do pracy jako moduł serwera.

By zacząć działać, musimy ściagnąć najnowszą wersję PHP ze strony www.php.net w wersji dla systemu Windows. W chwili powstawania tego tekstu najnowszą wersją było PHP 5.1.1. Ś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 z bazami danych, skonfigurowana do działania z bazą danych MySQL. Nie należy dopisywać linii z plikami bibliotek w php.ini, lecz jedynie odblokowywać istniejące! W pliku .ini są uwzględnione wszystkie pliki bibliotek istniejące fizycznie 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 pliku konfiguracyjnego Apache dopisujemy:

 ScriptAlias /php5/ "D:/Serwer/php5/"
 AddType application/x-httpd-php .php
 Action application/x-httpd-php "/php5/php-cgi.exe"

W przypadku modułu linijek jest nieco mniej:

 LoadModule php5_module "D:/Serwer/php5/php5apache2_2.dll"
 AddType application/x-httpd-php .php

Należy 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"

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

 <?php phpinfo(); ?>

W przeglądarce wpisz http://localhost/phpinfo.php - powinien pokazać Ci się bardzo długi raport na temat zainstalowanej 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 X[edytuj]

Można zainstalować gotowy pakiet PHP5 za pomocą Fink i FinkCommander.

Kompilacja ze źródeł będzie wymagała instalacji dodatkowych bibliotek (dla ułatwienia można je zainstalować FinkCommanderem) oraz Developer Tools z płyty systemowej Mac OS X. Dalej kompilacja przebiega tak samo jak dla Unix/Linux.


Podstawy języka[edytuj]

Pierwszy skrypt[edytuj]

W tym rozdziale napiszemy pierwszy skrypt PHP.

Skrypt PHP[edytuj]

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

Przeglądarka otrzymuje tylko już przetworzony kod, w tym przypadku <? echo 2*2; ?> zostało zamienione na 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 programem PHP.

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ń; bardziej szczegółowo zostanie to omówione w kolejnych rozdziałach.

Konsola[edytuj]

Możemy uruchamiać skrypty z linii poleceń (CLI)

Polecenia :

 php 1.php

uruchamia skrypt 1.php

Polecenie :

php -a

uruchamia tryb interaktywny:

Interactive mode enabled

wprowadzamy proste polecenie :


<?php echo "hi!"; ?>

i kończymy CTRL-D.

Wynik :

 Parse error:  syntax error, unexpected '<' in php shell code on line 1

Próbujemy :

 echo "hi!";

Wynik :

hi!


Sprawdzanie wersji php:

php -v

przykładowy wynik :

PHP 5.5.9-1ubuntu4.14 (cli) (built: Oct 28 2015 01:34:46) 
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
with Zend OPcache v7.0.3, Copyright (c) 1999-2014, by Zend Technologies

Skrypt PHP wewnątrz dokumentu HTML[edytuj]

Jak wspomnieliśmy wcześniej, skrypty PHP możemy mieszać ze zwykłym kodem HTML. Kod naszych algorytmów zamykany jest wewnątrz specjalnych wstawek wyłapywanych przez interpreter oraz zmienianych później na wygenerowany kod. Tak też zrobimy w naszym pierwszym skrypcie, który tradycyjnie wyświetli na ekranie przeglądarki napis "Hello world!".

 1 <?xml version="1.0" encoding="utf-8" standalone="no"?>
 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
 3 <html xmlns="http://www.w3.org/1999/xhtml">
 4  <head>
 5   <title>Pierwszy skrypt PHP</title>
 6  </head>
 7  <body>
 8  <?php
 9    echo 'Hello world!';
10  ?>
11  </body>
12 </html>

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

echo 'Hello world!';

Nakazuje ona wyświetlenie tekstu "Hello world!" w przeglądarce. Tekst do wyświetlenia ograniczyliśmy apostrofami. Średnik na końcu informuje o zakończeniu komendy. Możemy rozbić to na kilka linijek, ale dla PHP nie będzie to miało większego znaczenia - końcem komendy jest właśnie średnik.

1 <?php
2 echo 
3      'Hello world!';
4 ?>

Przejście do nowej linii poza apostrofami jest jednym z tzw. białych znaków ignorowanych przez interpreter. Innymi są spacja oraz tabulacja. Między tekstem, a komendą echo możemy wstawić niezliczoną liczbę tabulatorów i zejść do nowej linii, ale nie zmieni to w żaden sposób tego, jak PHP wykona nasz skrypt, gdyż znaki te zostaną zignorowane.

W skrypcie możemy umieścić więcej wyrażeń, oczywiście odseparowanych średnikami:

1 <?php
2 echo 'To jest tekst 1';
3 echo 'To jest tekst 2';
4 echo 'A to jest tekst 3';
5 ?>

Zauważ, że choć w skrypcie mamy trzy komendy wyświetlenia trzech tekstów, przeglądarka wyświetli je nam w jednej linijce. Jest tak dlatego, ponieważ nowa linia oznaczana jest specjalnym znacznikiem HTML, którego tam nie umieściliśmy. Oto poprawiona wersja skryptu:

1 <?php
2 echo 'To jest tekst 1<br>';
3 echo 'To jest tekst 2<br>';
4 echo 'A to jest tekst 3<br>';
5 ?>

Komentarze[edytuj]

Kiedy twoje skrypty staną się bardziej rozbudowane, w kodzie przyda się pewna organizacja. Pomogą tu z pewnością komentarze służące do opisywania, co robi dana część algorytmu, jak działa, jakie ma wymagania itd. Mogą także pomóc w usystematyzowaniu całości lub też zamieszczeniu informacji o autorze oraz prawach autorskich. Komentarz jest całkowicie ignorowany przez interpreter PHP i nie wpływa na wynik jego działania.

Istnieją trzy rodzaje komentarzy:

 1 <?php
 2 /*
 3   komentarz wieloliniowy
 4   może być rozbijany na wiele linijek.
 5   Cały ten tekst jest ignorowany przez interpreter
 6 */
 7  
 8 // to jest komentarz jednoliniowy - obowiązuje do końca danej linijki
 9 # to jest jeszcze jeden komentarz jednoliniowy
10 
11 ?>

Oto przykładowe zastosowanie komentarzy:

 1 <?php
 2 /*
 3  * Generator miniaturek obrazków
 4  *  Wersja: 1.0
 5  *  Autor: Adam Kowalski (adres@example.com)
 6  *  Licencja: GNU GPL
 7  *
 8  * Użycie: tu opis użycia...
 9  */
10 
11 // początek generowania obrazka
12 echo 'Tu są jakieś komendy';
13 echo 'Dużo komend...';
14 
15 // zmniejszanie
16 echo 'Tu obrazek się zmniejsza';
17 
18 // wysyłanie wyniku
19 echo 'Tu wysyłamy obrazek do przeglądarki';
20   
21 ?>

Cały kod został opisany i dzięki temu nawet po wielu miesiącach autor będzie wiedział, co do czego służy. Komentarze przydają się przy zbiorowych pracach nad projektem. Można w nich umieszczać informacje, co należy w danym fragmencie poprawić albo jak on funkcjonuje. W procesie usuwania błędów (debugowania) komentarzy można używać do chwilowego wyłączania kawałków kodu, aby zobaczyć, czy to przypadkiem one nie powodują problemu oraz jak aplikacja radzi sobie bez nich. Stosowanie komentarzy należy do dobrych praktyk programistycznych i nigdy nie należy o nich zapominać.

Znaczniki wstawek PHP[edytuj]

Oficjalnymi znacznikami rozpoczynającymi i kończącymi kod PHP są <?php ... ?>, jednak wciąż można spotkać starszą formę <? ... ?>. Nie zalecamy jej stosowania, ponieważ wiele serwerów ma ją wyłączoną i Twoje 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ę do koń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że być źródłem poważnych problemów, o czym przekonamy się z dalszych rozdziałów. Nasz skrypt będzie zatem prezentować się następująco:

1 <?php
2 echo 'To jest tekst 1<br>';
3 echo 'To jest tekst 2<br>';
4 echo 'A to jest tekst 3<br>';

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


Zmienne i tablice[edytuj]

Samo wyświetlanie tekstu jest niezbyt ciekawe. Wszystkie programy komputerowe operują na danych, zatem w ję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.

Dane[edytuj]

PHP, 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 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ślona kolejność ich wykonywania, a zmieniać ją możemy za pomocą nawiasów:

 5 * (6 + 8)

Pamiętaj, że PHP potrafi automatycznie rozpoznawać typ wyrażenia i w razie potrzeby odpowiednio go konwertować. Dla przykładu instrukcja echo wymaga danych tekstowych. W skrypcie:

1 <?php
2 echo 7;

Interpreter dokona konwersji liczby na tekst, a dopiero potem spowoduje jego wyświetlenie.

Funkcje[edytuj]

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

1 <?php
2 
3 echo 'Dzisiaj mamy: '.date('d.m.Y').'<br/>';
4 echo 'Od 1.1.1970 minęło: '.time().' sekund';

Jak widać, składnia funkcji jest następująca: nazwaFunkcji(parametry). Jeśli funkcja nie posiada parametrów, nawiasy są puste. Jeżeli jest ich więcej, niż jeden, oddzielamy je od siebie przecinkiem. Zaś sam parametr jest niczym innym, jak... pewnym wyrażeniem. Wynik działania funkcji również jest wyrażeniem, dlatego możemy ją wpleść w nasz tekst za pomocą operatora kropki.

Powyższy kod zachowa się następująco:

  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 strony wynikowej.
  3. Następna linijka wywoła się analogicznie, wywołanie funkcji zakończy się zwróceniem wyniku, który zostanie połączony z sąsiednimi wyrażeniami w jeden tekst.

Zmienne[edytuj]

Innym pojęciem matematycznym jest zmienna, zawierająca pewną informację, przeważnie uzyskaną w trakcie wykonywania skryptu. Można traktować ją jako pojemnik, do którego będziemy mogli w trakcie wykonywania skryptu wstawić dowolną informację, zapamiętując ją w ten sposób. Umożliwia to przechowywanie i przetwarzanie danych 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, co ma 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', $

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 w praktyce:

1 <?php
2 // inicjujemy zmienna $czas aktualnym czasem w sekundach od 1.1.1970
3 $czas = time();
4 $czas2 = $czas / 60;
5 
6 echo 'Od 1.1.1970 minęło '.$czas.' sekund<br/>';
7 echo 'Od 1.1.1970 minęło '.$czas2.' minut<br/>';
8 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ę sekund zwró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 sekcji programu, aby potem używać go wielokrotnie gdzie indziej, bez konieczności każdorazowego odwoływania się do funkcji i zbędnego liczenia tego samego.

PHP, w przeciwieństwie do innych języków programowania, ma bardzo liberalne reguły stosowania zmiennych. Nie trzeba 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 sporo sytuacji, 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łniona podczas 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, a programista będzie musiał spędzić dużo czasu na odnalezienie przyczyny. Słowo "teoria" nie znalazło się tu przypadkowo. Podczas instalowania PHP wspominaliśmy o poziomach raportowania błędów. Im wyższy poziom, tym większej ilości rzeczy czepia się PHP. Na poziomie E_ALL zdefiniowanym w rekomendowanym pliku php.ini takie beztroskie podejście do zmiennych nie jest tolerowane. Tutaj PHP wymaga już, aby podczas pierwszego użycia zmiennej została jej przypisana jakaś wartość, ponieważ inaczej otrzymamy powiadomienie (ang. notice) o próbie odwołania się do nieistniejącej zmiennej. Popatrzmy sobie na ten przykład:

1 <?php
2 
3 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świetlanie powiadomień. Zrestartuj serwer i uruchom powyższy skrypt. Wynikiem powinno być "5". PHP bez pytania podstawił do $a wartość neutralną 0. Przywróć teraz poprzedni poziom (E_ALL | E_STRICT) i ponownie uruchom ten skrypt. Oprócz wyniku, ujrzysz też komunikat:

Notice: Undefined variable: a in D:\Serwer\www\katalog\twojskrypt.php on line 3

Sytuację można rozwiązać, ręcznie inicjując zmienną $a wartością 0:

1 <?php
2 $a = 0;
3 echo $a * 16 + 5;

Zalecane jest, aby wszystkie skrypty pracowały bezproblemowo przy włączonych powiadomieniach. Jeżeli zajdzie sytuacja, że odwołanie się do zmiennej, która może jeszcze nie istnieć, jest potrzebne, istnieje kilka sposobów "oszukania" PHP, lecz poznamy je w dalszej części podręcznika.

Początkujący programiści mają tendencję do tworzenia dużej liczby tzw. zmiennych tymczasowych, które nie wnoszą absolutnie niczego do programu poza wydłużeniem kodu i zmniejszeniem wydajności. Po każdym etapie przetworzenia jakiejś informacji, umieszczana jest ona w nowej zmiennej. Takie podejście jest nieprawidłowe. Oto przykłady "złych" skryptów:

1 <?php
2 
3 $tekst = 'To jest jakiś tekst';
4 $tekstMaly = strtolower($tekst);
5 $tekstBezpieczny = addslashes($tekstMaly);
6 echo $tekstBezpieczny;

Przykład 2:

1 <?php
2 
3 $format = 'd.m.Y';
4 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:

1 <?php
2 
3 $tekst = 'To jest jakiś tekst';
4 $tekst = strtolower($tekst);
5 $tekst = addslashes($tekst);
6 echo $tekst;

Drugi sposób rozwiązania tego problemu - bez użycia zmiennych (już trochę mniej czytelny):

1 <?php
2 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 do funkcji.

1 <?php
2 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 my będziemy chcieli mieć możliwość zmieniania tych formatów w przyszłości, użycie zmiennych bardzo ułatwiłoby sprawę - 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ędziemy zwracali na tę kwestię baczną uwagę. Jeśli zajdzie potrzeba użycia zmiennych tymczasowych - wyjaśnimy, dlaczego, bowiem całkowite rezygnowanie z ich użycia także może rodzić wiele problemów.

Typy[edytuj]

Do 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 jeszcze jedną, lecz stworzoną na jej własne potrzeby do zaznaczania niektórych rzeczy (powiemy o niej później).

Wielkości skalarne[edytuj]

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:

1 <?php
2 $a = 1234; // liczba całkowita
3 $a = -123; // liczba całkowita ujemna
4 $a = 0123; // zapis ósemkowy (odpowiednik dziesiętnego 83)
5 $a = 0x1A; // zapis szesnastkowy (odpowiednik dziesiętnego 26)

Możemy także korzystać z wartości ułamkowych zwanych liczbami zmiennoprzecinkowymi (ang. floating point numbers albo skrótowo float), które są przybliżeniem liczb rzeczywistych (ważne - nigdy nie zawierają dokładnej wartości, prawie zawsze jest to odrobinę różniąca się liczba, dlatego mówi się o "przybliżeniu"). Przy ich zapisywaniu obowiązują reguły języka angielskiego, więc części całkowite od ułamkowych oddzielamy za pomocą kropki. Także i tu mamy do wyboru kilka sposobów zapisu:

1 <?php
2 $a = 1.234; 
3 $a = 1.2e3; 
4 $a = 7E-10;

Kolejnym typem jest typ logiczny (boolean), przyjmujący jedynie wartości FALSE i TRUE. Jest on używany przez wiele funkcji do zwracania rezultatu, czy operacja się powiodła. Wyrażenia porównawcze (czy równy, czy większy itd.) 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. o istnieniu dwóch składni zapisywania ciągów. Ta oparta na apostrofach dopuszcza mniejszy zestaw kodów formatujących (pozwalających na wstawienie do tekstu innych apostrofów oraz ukośników wstecznych):

1 <?php
2 echo 'To jest tekst zapisany w apostrofach. Kody formatujące pozwalają
3    umieścić w tekście wyłącznie inne apostrofy: \' albo backslashe: \\. Wszystko inne,
4    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 sprzed apostrofu (w tekście: \') i zobacz, co się stanie. Skrypt się nie uruchomi, ponieważ wystąpił błąd składni. PHP napotka w sumie trzy apostrofy, a więc między drugim i trzecim będzie nierozpoznany dla parsera tekst, natomiast trzeci będzie niedomknięty.

Więcej możliwości formatowania posiada tekst ograniczony cudzysłowami:

1 <?php
2 echo "To jest tekst zapisany w cudzysłowach. Za pomocą kodów formatujących możemy
3   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 w prymitywnym przykładzie:

1 <?php
2 $czas = time();
3 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 z ciągiem operatorem kropki.

1 <?php
2 // tutaj można użyć cudzysłowu jak i apostrofu
3 echo "Aktualny czas w sekundach: ".time()." sek.";

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

1 <?php
2 $formatDaty = 'd.m.Y';
3 echo date("$formatDaty");

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

Inne typy[edytuj]

Typami złożonymi w PHP są tablice oraz obiekty. Tablice poznamy jeszcze w tym rozdziale, natomiast obiektami oraz samym programowaniem obiektowym zajmiemy się w dalszej części podręcznika.

Istnieją jeszcze dwa typy specjalne: resource oraz NULL. Pierwszy z nich reprezentuje wszelkiego rodzaju połączenia z bazami danych, otwarte przez PHP pliki itd. Drugi to wartość pusta. Za jego pomocą możemy "zasymulować", że zmienna nie istnieje lub nie zawiera wartości. Pojawia się w trzech sytuacjach:

  • Do zmiennej przypisana została stała NULL.
  • Do zmiennej nie została przypisana jeszcze żadna wartość (zgłaszane jest wtedy powiadomienie)
  • Zmienna została zniszczona poleceniem unset().

Przyjrzyjmy się wspomnianemu w trzecim punkcie poleceniu. Czasem jakąś zmienną trzeba zniszczyć. Najlepiej nadaje się do tego polecenie unset():

1 <?php
2 $zmienna = 4856;
3 
4 unset($zmienna);
5 
6 echo $zmienna;

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

Konwersja typów[edytuj]

PHP potrafi sam rozpoznać typ informacji przypisanej do zmiennej oraz automatycznie konwertować go w zależności od potrzeb. Przykładowo liczby ułamkowe użyte tam, gdzie potrzeba całkowitych, są zaokrąglane w górę do zera. Wartości logiczne mogą być reprezentowane cyframi 0 (FALSE) oraz 1 (TRUE). Ciągi tekstowe mogą być konwertowane do liczb, jeżeli pierwszy z nich (pomijając wiodące białe znaki) znaków jest cyfrą. W przeciwnym przypadku PHP dobiera wartość 0.

Możemy sami wymusić konwersję typów:

1 <?php
2 // wyświetl liczbę całkowitą jako ułamek
3 echo (float) 10;

W nawiasie przed wartością piszemy angielską nazwę typu: integer, int, string, boolean, float, double (liczba zmiennoprzecinkowa mogąca przybierać znacznie większe wartości). Unikaj jakiejkolwiek konwersji typów złożonych: tablic, obiektów, zasobów, gdyż w każdym z tych przypadków informacje zostają całkowicie utracone; zamiast nich zwracana jest po prostu nazwa typu złożonego - to dlatego ostrzegaliśmy przed wprowadzaniem wartości zmiennych do funkcji przez cudzysłowy.

PHP posiada funkcję gettype() zwracającą nazwę aktualnego typu danych zawartych w zmiennej:

1 <?php
2 $a = 547;
3 echo 'Typ zmiennej $a to: '.gettype($a);

Więcej o operatorach[edytuj]

Dotychczas poznałeś już kilka operatorów, np. + czy =. Jak zdążyłeś już zauważyć, po obu stronach takiego operatora stoją wyrażenia, na których wykonuje on operacje i zwraca wynik. Sam jest zatem wyrażeniem. Operatory mają określoną kolejność wykonywania wzorowaną na matematyce. Możemy ją naginać do własnych potrzeb, stosując nawiasy.

Oto wykaz najciekawszych operatorów na początek.

Operator Nazwa Składnia Opis
/ Dzielenie wyrażenie / wyrażenie Reprezentuje wynik dzielenia. Drugie wyrażenie nie może być zerem.
% Dzielenie modulo wyrażenie % wyrażenie Reprezentuje resztę z dzielenia. Drugie wyrażenie nie może być zerem.
* Mnożenie wyrażenie * wyrażenie Reprezentuje iloczyn dwóch wyrażeń.
+ Dodawanie wyrażenie + wyrażenie Reprezentuje sumę dwóch wyrażeń.
- Odejmowanie wyrażenie - wyrażenie Reprezentuje różnicę dwóch wyrażeń.
. Łączenie (Konkatenacja) wyrażenie . wyrażenie Reprezentuje połączenie dwóch wyrażeń w ciąg tekstowy.
++ 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, czyli takie, które operują wyłącznie na jednym wyrażeniu. W dodatku koniecznie musi być ono zmienną, ponieważ modyfikują one jej wartość, powiększając lub zmniejszając o jeden. Każdy z tych operatorów został podany podwójnie, ponieważ w zależności od tego, czy postawimy go przed, czy po zmiennej, otrzymamy nieco inne rezultaty. Porównaj:

 1 <?php
 2 // najpierw składnia $zmienna++
 3 
 4 $zmienna = 5;
 5 
 6 echo 'Stan 1: '.($zmienna++).'<br/>';
 7 echo 'Stan 2: '.$zmienna.'<br/><br/>';
 8 
 9 // teraz składnia ++$zmienna
10 echo 'Restart zmiennej...<br/>';
11 $zmienna = 5;
12 
13 echo 'Stan 1: '.(++$zmienna).'<br/>';
14 echo 'Stan 2: '.$zmienna.'<br/>';

Skrypt ten wygeneruje nam kilka linijek:

Stan 1: 5
Stan 2: 6

Restart zmiennej...
Stan 1: 6
Stan 2: 6

Przeanalizuj wyniki działania. Okazuje się, że w składni $zmienna++ najpierw dostajemy wartość zmiennej, a dopiero potem zwiększamy ją o jeden (dlatego zmiana widoczna jest dopiero w drugim stanie). ++$zmienna najpierw powiększa, potem zwraca, w efekcie czego otrzymujemy w obu stanach liczbę "6". Identyczna zasada obowiązuje operator --.

Zajmijmy się teraz przypisywaniem danych do zmiennej. Wiemy już, że operator przypisania po lewej stronie wymaga zmiennej, po prawej wyrażenia, którego wartość trzeba w niej umieścić. Skoro operator to też jest wyrażenie, to jaką wartość ono reprezentuje? Okazuje się, że tę, która jest przypisywana. Możemy wobec tego zastosować sprytną sztuczkę, o której wbrew pozorom wie niezbyt wielu programistów. Zainicjujmy pięć zmiennych naraz tą samą wartością:

1 <?php
2 $a = $b = $c = $d = $e = 5;

PHP najpierw przypisze "5" do zmiennej $e, zwracając jednocześnie "5" tak, by mogło być ono przypisane do $d, potem do $c, $b i na końcu $a. W ten sposób jednym wielkim wyrażeniem zainicjowaliśmy pięć zmiennych naraz.

Poznany już operator przypisania nie jest jedynym, jaki istnieje w PHP. Aby ułatwić modyfikację wartości zmiennych o liczby inne niż jeden, stworzono całą gamę operatorów łączących w sobie przypisywanie oraz jakąś operację matematyczną. Oto i one.

Operator Składnia Równoważna postać Opis
/= $zmienna /= wyrazenie $zmienna = $zmienna / wyrażenie Dzieli zmienną przez wyrażenie i umieszcza w niej wynik. Wyrażenie nie może być zerem.
%= $zmienna %= wyrazenie $zmienna = $zmienna % wyrażenie Umieszcza w zmiennej resztę z dzielenia tej zmiennej przez wyrażenie, które oczywiście nie może być zerem.
*= $zmienna *= wyrazenie $zmienna = $zmienna * wyrażenie Mnoży zmienną przez wyrażenie i zapisuje w niej wynik.
+= $zmienna += wyrazenie $zmienna = $zmienna + wyrażenie Dodaje do zmiennej wyrażenie i zapisuje w niej wynik.
-= $zmienna -= wyrazenie $zmienna = $zmienna - wyrażenie Odejmuje od zmiennej wyrażenie i zapisuje w niej wynik.
.= $zmienna .= wyrazenie $zmienna = $zmienna . wyrażenie Dołącza do zmiennej tekstowej nowy fragment.

Te operatory po prostu skracają zapis i czynią go czytelniejszym. Warto o nich pamiętać szczególnie przy operacjach na ciągach tekstu, kiedy nasz algorytm składa jakąś treść, doczepiając kolejne jej partie do wybranej zmiennej:

1 <?php
2 
3 $tekst = 'Litwo, ojczyzno moja! Ty jesteś jak zdrowie<br/>';
4 $tekst .= 'Ile Cię trzeba cenić, ten tylko się dowie<br/>';
5 $tekst .= 'Kto Cię stracił, dziś piękność twą w całej ozdobie<br/>';
6 $tekst .= 'Widzę i opisuję, bo tęsknię po tobie.<br/>';
7 
8 echo $tekst;

Rezultat:

Litwo, ojczyzno moja! Ty jesteś jak zdrowie
Ile Cię trzeba cenić, ten tylko się dowie
Kto Cię stracił, dziś piękność twą w całej ozdobie
Widzę i opisuję, bo tęsknię po tobie.

Kolejne partie tekstu były doklejane do właściwej zmiennej tak, że na końcu otrzymaliśmy spójny i kompletny tekst. Identycznie jest z pozostałymi operatorami. += dodaje wartość do zmiennej, -= odejmuje itd.

Na tym zakończymy na razie temat operatorów, lecz to jeszcze nie wszystko. Już niedługo zapoznamy się z operatorami służącymi do porównywania danych oraz podstawami operatorów logicznych. Będą nam one potrzebne przy omawianiu instrukcji warunkowych i pętli.

Tablice[edytuj]

Zaznajomimy się teraz z tablicami, pierwszym złożonym typem danych. Cofnijmy się do czasów szkoły podstawowej/gimnazjum na lekcję matematyki i przypomnijmy nasz pierwszy kontakt z pojęciem funkcji. Była tam mowa, że funkcję można przedstawiać w postaci tabelki:

x 0 1 2 3 4 5 6 7 8
y 5 3 8 7 9 24 15 2 19

Spróbujmy przenieść taką tabelkę w świat programowania. Widzimy, że wszystkie "igreki" są ze sobą powiązane, ponieważ wszystkie są możliwymi wynikami funkcji. Gdyby to zapisać jako zupełnie osobne zmienne, mielibyśmy problem z późniejszym dostaniem się do nich. Popatrz na to w ten sposób: użytkownik wprowadza argument, a my mamy dla niego odnaleźć wartość spośród tych podanych. Skąd PHP może wiedzieć, że akurat ta grupa zmiennych jest ze sobą w jakiś sposób powiązania, a tym bardziej znać regułę wybierania "tej właściwej"? Na razie nie jest to w ogóle możliwe. Co nam pozostaje? Zapisać wszystkie wartości w strukturze zwanej tablicą.

Wiemy już, że tablica jest zmienną, która grupuje sobie mniejsze zmienne opisywane przez ich indeksy (wartości x w naszej tabelce funkcji). Najważniejsze jest jednak to, że przy odwoływaniu się do nich, potrzebny nam indeks możemy określić zwyczajnym wyrażeniem! To oznacza, że zadanie wymienione akapit wyżej staje się realne. Zanim je zaprogramujemy, nieco informacji o składni tablic. Na początek utwórzmy pustą tablicę:

1 <?php
2 $tablica = array();

Spróbujmy wprowadzić do niej wartości naszej funkcji:

1 <?php
2 $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 przyda się nam jeżeli nie chcemy zacząć numeracji od 0, ale np. od 1, piszemy wtedy array( 1=> 5.... Teraz spróbujmy dostać się do jakiejś z nich.

1 <?php
2 $tablica = array(0 => 5, 3, 8, 7, 9, 24, 15, 2, 19);
3 echo 'Pod numerem 5 kryje się wartość '.$tablica[5];

Pomiędzy nawiasami kwadratowymi wprowadzić musimy wyrażenie określające indeks tablicy, który chcielibyśmy odczytać. Możemy teraz pokusić się o napisanie skryptu losującego elementy:

1 <?php
2 $tablica = array(0 => 5, 3, 8, 7, 9, 24, 15, 2, 19);
3 $liczba_losowa = rand(0, 7);
4 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 czynienia z tablicami asocjacyjnymi.

 1 <?php
 2 $artykul = array(
 3   'tytul' => 'Tytuł artykułu',
 4   'data' => date('d.m.Y'),
 5   'tresc' => 'To jest treść artykułu'
 6 );
 7 
 8 echo '<h1>'.$artykul['tytul'].'</h1>';
 9 echo '<p>Napisany dnia '.$artykul['data'].'</p>';
10 echo '<p>'.$artykul['tresc'].'</p>';

Mogą istnieć także tablice mieszane, w których występują zarówno indeksy tekstowe, jak i numeryczne. Pamiętaj, że każdy element tablicy zachowuje się, jak zwykła zmienna, dlatego także możesz przypisywać do niego dowolne wartości już po utworzeniu tablicy.

1 <?php
2 
3 $tablica = array(0 => 5, 3, 8, 7, 9, 24, 15, 2, 19);
4 
5 // modyfikuj losowy element tablicy
6 $tablica[rand(0, 7)] = 6;
7 
8 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 formie każdy typ danych, więc możemy za jej pomocą kontrolować, czy na danym etapie wykonywania rezultaty są takie, jakie być powinny. Podobne działanie ma funkcja print_r(), przy czym var_dump() generuje gotowy kod HTML, otoczony znacznikami <pre> </pre> i sformatowany, podczas gdy print_r() zwraca tekst sformatowany, ale bez znaczników HTML.

Zobacz teraz, jak zachowuje się $tablica, kiedy próbujemy ją wywołać samodzielnie:

1 <?php
2 $tablica = array(0 => 5, 3, 8, 7, 9, 24, 15, 2, 19);
3 
4 echo $tablica;

Skrypt ten pokaże nam tylko jeden napis: Array, czyli... nazwę typu. Właśnie z tego powodu ostrzegaliśmy przed wprowadzaniem danych do funkcji jako funkcja("$zmienna");. Gdyby w zmiennej była tablica, do funkcji zamiast niej dotarłby napis Array i całość przestałaby działać. Osobną kwestią jest wydajność takiego zmuszania do konwersji do tekstu, a potem z powrotem na tekst właściwy.

Upewnij się, że rozumiesz już istotę działania tablic, gdyż bardzo przydadzą się nam one w następnym rozdziale.

Przypisy

  1. Tak naprawdę komputer nie potrafi losować liczb. Za całą tą zasłonką kryją się różne skomplikowane wzory matematyczne inicjowane najczęściej aktualnym czasem, dające wrażenie losowości wyników.


Poprzedni rozdział: Zmienne i tablice
Spis treści
Następny rozdział: Struktury kontrolne

Formularze[edytuj]

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

Protokół HTTP[edytuj]

Działanie protokołu HTTP.

Podstawą funkcjonowania stron WWW jest protokół HTTP, którego używa przeglądarka i serwer. Zasada jego działania jest bardzo prosta. Gdy chcemy obejrzeć dokument pod podanym adresem URL, wysyłamy do serwera tzw. żądanie HTTP zawierające lokalizację zasobu oraz garść informacji o nas samych. Serwer odnajduje 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 jest dokument. Przeglądarka odbiera wszystko i rozpoczyna działanie. Protokół jest stosowany zarówno do pobierania kodu 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 wygenerowania strony potrzebne są dodatkowe informacje, które najczęściej przechowywane są w bazie danych, a rzadziej - w plikach. Skrypty pobierają z nich dane, poddają je obróbce i osadzają w kodzie HTML, który jest odsyłany do klienta.

Istotną częścią protokołu HTTP są rodzaje żądań (zwane "metodami") informujące o tym, co próbujemy zrobić. Dwa podstawowe 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 na niektórych serwerach są one z nieznanych powodów poblokowane. Protokołem HTTP zajmiemy się dokładniej w dalszej części podręcznika, tymczasem na razie będą nas interesować te dwie metody. Pierwsza z nich jest wykorzystywana podczas zwykłego pobierania z serwera dokumentu, natomiast druga - przy formularzach.

Ogólnie o danych wejściowych[edytuj]

W żą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 wygenerowania odpowiedzi. Mechanizm ich odbierania ewoluował stopniowo. Pierwsze wersje języka rejestrowały wszystkie nadesłane parametry, dane formularzy itd. jako zmienne, lecz było to wyjątkowo niebezpieczne. Dodając nowe parametry można było rejestrować nowe zmienne, które mogły nadpisać zmienne używane w kodzie aplikacji powodując jego błędne działanie (np. zyskanie praw administratora strony przez atakującego). Programista nie mógł także policzyć, ile danych wł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 potrafi odbierać 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łach podręcznika.

Adresy URL[edytuj]

Do adresu URL można dołączać po znaku zapytania dodatkowe zmienne, np. www.example.com/strona.php?zmienna=wartosc&inna_zmienna=wartosc. Są one przechowywane w tablicy $_GET. Ten sposób przekazywania danych wykorzystujemy tylko, gdy skrypt nie wykonuje operacji mających efekty uboczne (np. może być wyszukiwanie, ale już nie dodawanie lub usuwanie rekordó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 i dostawców internetowych może spowodować zignorowanie zapytań.

Przyjrzymy się zawartości tablicy:

1 <?php
2 var_dump($_GET);

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

array(0) { }

Oznacza to, że nie otrzymaliśmy tą drogą żadnych danych. Dodajmy teraz do adresu ciąg ?parametr=wartosc. Po odświeżeniu zobaczymy:

array(1) { ["parametr"]=> string(7) "wartosc" }

Tablica zawiera jeden element o nazwie parametr przechowujący wpisaną w adresie wartość.

Napiszemy teraz prosty skrypt wyświetlający informacje powitalne na podstawie danych z adresu:

1 <?php
2 if(sizeof($_GET) == 2)
3 {
4    echo 'Witaj, '.$_GET['imie'].' '.$_GET['nazwisko'].'!';
5 }
6 else
7 {
8    echo 'Nieprawidłowa liczba parametrów!';
9 }

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, co trzeba. Kontrola nadchodzących danych jest niezwykle istotna i nigdy nie wolno jej zlekceważyć. Pominięcie tego aspektu zazwyczaj kończy się dla skryptu tragicznie, bo jeżeli coś jest do zepsucia, na pewno znajdzie się ktoś, kto tego dokona.

Wywołując skrypt z parametrami "imie" oraz "nazwisko" możemy wpływać na wyświetlane informacje: http://localhost/~programowanie_php/nazwaskryptu.php?imie=Adam&nazwisko=Kowalski. Dla lepszego efektu stwórzmy prosty formularz XHTML wysyłający dane metodą GET:

 1 <?xml version="1.0" encoding="utf-8" standalone="no"?>
 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
 3 <html xmlns="http://www.w3.org/1999/xhtml">
 4  <head>
 5   <title>Formularz XHTML</title>
 6  </head>
 7  <body>
 8   <form method="get" action="nazwaskryptu.php">
 9    <p>
10     <label>Podaj imię: <input type="text" name="imie"/></label>
11    </p>
12    <p>
13     <label>Podaj nazwisko: <input type="text" name="nazwisko"/></label>
14    </p>
15    <p>
16     <input type="submit" value="OK"/>   
17    </p>
18   </form>
19  </body>
20 </html>

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

Formularze[edytuj]

Obsł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 jest taka, że wszystko wysyła się wyłącznie z formularza, który posiada parametr "method" ustawiony na "post" oraz że korzysta się z tablicy $_POST wewnątrz samego skryptu. Przeróbmy nasze ostatnie dzieło tak, aby pracowało w ten sposób.

1 <?php
2 if(count($_POST) == 2)
3 {
4    echo 'Witaj, '.$_POST['imie'].' '.$_POST['nazwisko'].'!';
5 }
6 else
7 {
8    echo 'Nieprawidłowa liczba parametrów!';
9 }

W skrypcie podmieniamy jedynie nazwy tablic na $_POST. W formularzu musimy jeszcze zmienić metodę:

 1 <html>
 2 <head>
 3  <title>Formularz HTML</title>
 4 </head>
 5 <body>
 6   <form method="post" action="nazwaskryptu.php">
 7   Podaj imię: <input type="text" name="imie"/><br/>
 8   Podaj nazwisko: <input type="text" name="nazwisko"/><br/>
 9   <input type="submit" value="OK"/>   
10   </form>
11 </body>
12 </html>

I gotowe. Wyślij teraz formularz. Zauważ, że żadna informacja nie jest doklejana do adresu, ponieważ transmisja odbywa się niejako innym kanałem.

Z kursów języka HTML wiadomo, że istnieją różne typy pól formularzy. Oto, jakie wartości otrzymuje od nich PHP:

  • <input type="text" name="nazwa"/>
    - skrypt otrzymuje $_POST['nazwa'] z wartością wpisaną w pole formularza.
  • <input type="hidden" name="nazwa" value="wartosc"/>
    - skrypt otrzymuje $_POST['nazwa'] z wartością wpisaną w danym znaczniku. Użyteczne do przesyłania formularzem ukrytych informacji, o których typowy użytkownik wiedzieć nie musi.
  • <input type="radio" name="nazwa" value="wartosc"/>
    - pozycje należące do tej samej grupy muszą mieć identyczną nazwę. $_POST['nazwa'] będzie zawierać wartość tej pozycji, która jest aktualnie zaznaczona.
  • <input type="checkbox" name="nazwa"/>
    - jeśli pole jest zaznaczone, $_POST['nazwa'] zawierać będzie słowo "on".
  • <select name="nazwa">...</select>
    - $_POST['nazwa'] zawierać będzie wartość wybranego z listy elementu.
  • <input type="submit" name="nazwa"/>
    - zmienna $_POST['nazwa'] zostanie utworzona, jeżeli akurat ten przycisk zostanie wciśnięty.

Dzięki temu można do formularzy wstawiać kilka przycisków "submit" i reagować inaczej w zależności od tego, który z nich został naciśnięty.

Serwer[edytuj]

Na sam koniec zostawiliśmy sobie kilka informacji przekazywanych interpreterowi przez serwer WWW (np. Apache). Zacznijmy od określenia adresu IP gościa:

1 <?php
2 echo 'Witaj, twój adres IP to '.$_SERVER['REMOTE_ADDR'].'!';

Ten skrypt pokaże nam adres IP komputera lub sieci, w której znajduje się internauta. W tym drugim przypadku dalsze informacje są niepewne. Serwerowi potrzebny jest wyłącznie ten adres, aby móc gdzieś wysłać rezultat, a pola $_SERVER['HTTP_X_FORWARDED_FOR'] lub $_SERVER['HTTP_CLIENT_IP'] są bardzo niepewne - generuje je właśnie serwer proxy, ale nic nie stoi na przeszkodzie, by podszył się pod nie hacker. Uznawanie ważności informacji tu zawartych było przyczyną wielu błędów bezpieczeństwa w popularnym skrypcie forów dyskusyjnych phpBB i jeżeli naprawdę zależy Ci na nim, pozostań przy REMOTE_ADDR, a resztę traktuj wyłącznie jako ciekawostkę.

Korzystając z funkcji gethostbyaddr() możemy uzyskać nazwę hosta, którego dotyczy adres IP:

1 <?php
2 echo 'Witaj, twój host to '.gethostbyaddr($_SERVER['REMOTE_ADDR']).'!';

Spróbujmy zidentyfikować przeglądarkę użytkownika:

1 <?php
2 echo 'Twoja przeglądarka została zidentyfikowana jako: '.$_SERVER['HTTP_USER_AGENT'].'!';

Pole to zawiera wyłącznie surowe informacje. Każda przeglądarka wpisuje tu co innego i dlatego wyciągnięcie ładnych i pogrupowanych w odpowiednie kategorie danych to zadanie na więcej, niż jeden artykuł. Pozostańmy zatem przy takiej postaci, a przetwarzaniem nagłówków przeglądarek zajmiemy się kiedy indziej.

Adres strony, z której przybyliśmy do naszego skryptu, znajduje się w zmiennej $_SERVER['HTTP_REFERER']. Można wykorzystać go do utworzenia linków "Cofnij" albo do detekcji, jakich słów kluczowych używają ludzie trafiający do nas z wyszukiwarek. Zauważ - szukane frazy zazwyczaj dołączane są właśnie do adresów URL i w ten sposób można je zdobyć.

Uwaga: nie można polegać na obecności i prawidłowości tej informacji. Istnieją przeglądarki i zapory ogniowe, które ją usuwają lub wstawiają tam dowolny adres podany przez użytkownika.

1 <?php
2 echo 'Przybyłeś do nas ze strony: '.$_SERVER['HTTP_REFERER'].'!';

W chwili obecnej to tyle, jeżeli chodzi o pobieranie danych. Dalszych informacji dowiemy się w trakcie następnych rozdziałów w miarę potrzeby.

Poprzedni rozdział: Formularze
Spis treści
Następny rozdział: Instrukcja if

Struktury kontrolne[edytuj]

Programy wykonujące szereg zawsze tych samych instrukcji nie pozwalają rozwinąć skrzydeł. Jeżeli aplikacja ma być w pełni interaktywna, musi umieć reagować na poczynania użytkownika lub sytuacje kryzysowe na podstawie zadanych warunków. Tu do akcji wkraczają instrukcje kontrolne. Są one podstawowym budulcem wielu algorytmów i ich poznanie jest niezbędne. Za to my zyskamy pełną kontrolę nad wszystkim, mogąc efektywnie reagować na wszystkie zdarzenia.

Jedną z instrukcji kontrolnych - if - widziałeś już w poprzednim rozdziale. Zauważyłeś też pewnie, iż nie była ona zakończona średnikiem, a zamiast tego pojawiały się przy niej nawiasy klamrowe tworzące tzw. blok kodu. Jest to ciąg komend złożony z wyrażeń i innych struktur kontrolnych, czytelnie oznakowany. W połączeniu z odpowiednią instrukcją, PHP może decydować, czy wykonać go, czy nie, czy też powtórzyć raz jeszcze. Klamry pełnią tutaj rolę drogowskazu pokazującego, co się danej instrukcji tyczy.

PHP posiada siedem struktur kontrolnych, lecz my poznamy sześć z nich. Siódma jest tak rzadko stosowana, że większość zaawansowanych programistów nie umie z niej korzystać, a ponadto wymaga ona od nas pewnej dodatkowej wiedzy. Oto lista wszystkich instrukcji:

  1. Instrukcja if
  2. Instrukcja switch
  3. Instrukcja for
  4. Instrukcja while
  5. Instrukcja do while
  6. Instrukcja foreach
Poprzedni rozdział: Struktury kontrolne
Spis treści
Następny rozdział: Instrukcja switch

Instrukcja if[edytuj]

Z tą instrukcją zetknęliśmy się już przy okazji omawiania formularzy. Przypomnijmy jeszcze raz ten przykład:

1 <?php
2 if(count($_GET) == 2)
3 {
4    echo 'Witaj, '.$_GET['imie'].' '.$_GET['nazwisko'].'!';
5 }
6 else
7 {
8    echo 'Nieprawidłowa liczba parametrów!';
9 }

Instrukcja if pozwala na wykonanie części kodu tylko wtedy, kiedy spełniony jest określony warunek, oraz opcjonalne dodawanie alternatywnych instrukcji w razie jego fałszywości. Z if-a korzysta się niezwykle często, ponieważ niemal zawsze musimy sprawdzać, czy dane informacje są prawidłowe, czy funkcja poprawnie połączyła się z plikiem itd. Poniżej napiszemy skrypt, który będzie potrafił rozwiązywać równanie kwadratowe . 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żeli ujemny - nie ma żadnych. Dla zera równanie daje jedno rozwiązanie (podwójne).

 1 <?php
 2 // 1
 3 if(!isset($_GET['a']))
 4 {
 5    $_GET['a'] = 0;
 6 }
 7 if(!isset($_GET['b']))
 8 {
 9    $_GET['b'] = 0;
10 }
11 if(!isset($_GET['c']))
12 {
13    $_GET['c'] = 0;
14 }
15  
16 // 2
17 if($_GET['a'] == 0)
18 {
19    die('Nieprawidłowy parametr A!');
20 }
21  	
22 // 3
23 $delta = pow($_GET['b'], 2) - 4 * $_GET['a'] * $_GET['c'];
24 
25 // 4
26 if($delta > 0)
27 {
28    // 5
29    echo 'Delta dodatnia. Dwa rozwiązania:<ul>';
30    echo '<li>'.round((-$_GET['b']-sqrt($delta))/(2*$_GET['a']), 5).'</li>';
31    echo '<li>'.round((-$_GET['b']+sqrt($delta))/(2*$_GET['a']), 5).'</li>';
32    echo '</ul>';
33 }
34 elseif($delta < 0)
35 {
36    // 6
37    echo 'Delta ujemna. Brak rozwiązań w zbiorze liczb rzeczywistych!';
38 }
39 else
40 {
41    // 7
42    echo 'Delta = 0. Jedno rozwiązanie: '.round((-$_GET['b'])/(2*$_GET['a']), 5);
43 }

Jest to pierwszy tak długi kod zawarty w tym podręczniku, dlatego omówimy go sobie w punktach. Numery odpowiednich fragmentów zaznaczone są w kodzie komentarzami jednolinijkowymi.

  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 tej zmiennej nie ma. Musimy wtedy podstawić za nią neutralną wartość 0, ponieważ inaczej skrypt będzie nam zgłaszać powiadomienia.
  2. Tutaj zaczynamy właściwy algorytm. Najpierw sprawdzamy, czy rzeczywiście mamy do czynienia z równaniem kwadratowym. Parametr a musi być różny od zera. W razie problemów instrukcją die() zatrzymujemy skrypt w tym miejscu.
  3. Liczymy współczynnik Δ. Funkcja pow(liczba, potega) podnosi podaną liczbę do odpowiedniej potęgi i działa szybciej, niż ręczne mnożenie wartości.
  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 pierwiastek z podanej liczby. Zwróć uwagę na użycie nawiasów do zasugerowania właściwej kolejności działań.
  6. Gdy Δ jest ujemna, równanie nie ma rozwiązania.
  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. Jej formalna składnia jest następująca:

 1 <?php
 2 if(wyrazenie)
 3 {
 4    // blok kodu
 5 }
 6 elseif(wyrazenie)
 7 {
 8    // blok kodu
 9 }
10 else
11 {
12    // blok kodu
13 }

Obowiązkowe jest podawanie pierwszego z członów zaczynającego się od if. Dwa pozostałe są opcjonalne, przy czym 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.

1 <?php
2 if($zmienna == 6)
3    echo 'Wartość zmiennej wynosi 6';

W tym podręczniku jednak nie będziemy jej stosować z powodu pogorszenia czytelności kodu, niemniej warto wiedzieć, iż składnia ta jest w pełni poprawna, ponieważ część programistów stosuje ją w praktyce.

Wyrażenie warunkowe powinno przyjmować wartości logiczne TRUE lub FALSE. Oto kilka przydatnych operatorów:

Operator Nazwa Składnia Opis
== Równość wyrażenie == wyrażenie Zwraca prawdę, jeżeli oba wyrażenia mają identyczną wartość.
=== Równość wyrażenie === wyrażenie Zwraca prawdę, jeżeli oba wyrażenia mają identyczną wartość oraz typ.
!= Nierówność wyrażenie != wyrażenie Zwraca prawdę, jeżeli oba wyrażenia mają różne wartości.
!== Nierówność wyrażenie !== wyrażenie Zwraca prawdę, jeżeli oba wyrażenia mają różne wartości i/lub typ.
< Mniejsze niż wyrażenie < wyrażenie Zwraca prawdę, jeżeli lewe wyrażenie ma mniejszą wartość od prawego.
> Większe niż wyrażenie > wyrażenie Zwraca prawdę, jeżeli lewe wyrażenie ma większą wartość od prawego.
<= Mniejsze lub równe wyrażenie <= wyrażenie Zwraca prawdę, jeżeli lewe wyrażenie ma mniejszą lub równą wartość prawemu.
>= Większe lub równe wyrażenie >= wyrażenie Zwraca prawdę, jeżeli lewe wyrażenie ma większą lub równą wartość prawemu.
! Negacja (nie) !wyrażenie Zwraca prawdę, jeżeli wyrażenie jest fałszywe i fałsz, jeśli prawdziwe.
&& Koniunkcja logiczna (i) wyrażenie && wyrażenie Zwraca prawdę, jeżeli oba wyrażenia są prawdziwe.
|| Alternatywa logiczna (lub) wyrażenie || wyrażenie Zwraca prawdę, jeżeli przynajmniej jedno z wyrażeń jest prawdziwe.

A teraz kilka przykładów...

1 <?php
2 //Zakładamy, że do skryptu wysłano formularz z polami liczba1 i liczba2
3 if($_POST['liczba1'] == 1 && $_POST['liczba2'] == 2) 
4 {
5    die('Liczba 1 wynosi 1, a liczba 2 wynosi 2');
6 }

Powyższy skrypt sprawdza, czy zmienna "liczba1" wynosi 1 i zmienna "liczba 2" wynosi 2.

1 <?php
2 //Zakładamy, że do skryptu wysłano formularz z polami liczba1 i liczba2
3 if($_POST['liczba1'] == 1 || $_POST['liczba2'] == 1) 
4 {
5    die('Liczba 1, lub liczba 2 wynosi 1');
6 }

Natomiast ten skrypt sprawdza, czy zmienna "liczba1", lub zmienna "liczba2" wynosi 1.

Wszystkie operatory podane wcześniej w tabelce przydają się przy konstruowaniu warunków. Pewnego wyjaśnienia domagają się == oraz ===. Popatrz sobie na taki przykład:

1 <?php
2 if(FALSE == 0)
3 {
4    echo 'Prawda!';
5 }

PHP automatycznie sprowadzi tu sobie obie wartości do identycznego typu i wtedy dopiero je porówna. Dlatego skrypt wyświetli napis "Prawda!". Zamień teraz ten operator na ===. Po odświeżeniu zobaczymy, że teraz nic się nie pokazało. To dlatego, że zażądaliśmy, aby i typy obu wyrażeń były identyczne, podczas gdy nie są. Operator ten przydaje się przy niektórych funkcjach zwracających różne typy wartości w zależności od powodzenia operacji.

Oprócz tego w warunkach przyda się nam kilka funkcji:

  • isset($zmienna) - zwraca prawdę, jeżeli zmienna istnieje.
  • empty($zmienna) - zwraca prawdę, jeżeli zmienna ma wartość 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ą.
Poprzedni rozdział: Instrukcja if
Spis treści
Następny rozdział: Instrukcja for

Instrukcja switch[edytuj]

Instrukcja switch zwana jest także instrukcją wyboru. Jej działanie jest podobne do szczególnego przypadku poznanej ostatnio instrukcji warunkowej. Pokażemy to na przykładzie.

Nietrudno znaleźć witryny internetowe, które w jednym pliku grupują kilka różnych zadań wykonywanych w zależności od parametru, np. index.php?act=dodaj, index.php?act=usun itd. Możemy to zaprogramować za pomocą dużego ifa:

 1 <?php
 2 if($_GET['act'] == 'dodaj')
 3 {
 4    echo 'Dodawanie danych';
 5 }
 6 elseif($_GET['act'] == 'edytuj')
 7 {
 8    echo 'Edycja danych';
 9 }
10 elseif($_GET['act'] == 'usun')
11 {
12    echo 'Usuwanie danych';
13 }
14 else
15 {
16    echo 'Wyświetlanie danych';
17 }

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 to zrobić w czterech miejscach, a tych może być w teorii jeszcze więcej. Znacznie łatwiejszą w użyciu, a przy tym wydajniejszą alternatywą jest instrukcja switch. Działa ona w ten sposób, że wybiera spośród dostępnego zbioru określoną wartość na podstawie wartości pewnego wyrażenia i wykonuje zdefiniowany dla niej kod. Przepiszmy raz jeszcze powyższy przykład:

 1 <?php
 2 if(!isset($_GET['act']))
 3 {
 4    $_GET['act'] = 'index';
 5 }
 6 switch($_GET['act'])
 7 {
 8    case 'dodaj':
 9       echo 'Dodawanie danych';			
10       break;
11    case 'edytuj':
12       echo 'Edycja danych';
13       break;
14    case 'usun':
15       echo 'Usuwanie danych';
16       break;
17    default:
18       echo 'Wyświetlenie danych';	
19 }

W nawiasie polecenia switch definiujemy, jakiemu wyrażeniu pragniemy sprawdzić wartość. Wewnątrz nawiasów klamrowych używamy struktury case wartość:, aby zdefiniować dopuszczalne wartości, czyli stany wyrażenia. Po dwukropku piszemy odpowiedni kod. Jeżeli żaden ze stanów nie spełnia naszych oczekiwań, istnieje także klauzula default: pisana na samym końcu opisująca domyślne zachowanie. Jej już nie musimy dodawać, jeżeli tego nie potrzebujemy, niemniej często się ona przydaje. Zwróć uwagę na komendę break; stojącą na końcu każdego kodu przypisanego do case. Mówi ona, że wykonywanie przerywane jest w tym miejscu i PHP ma skoczyć do końca switcha, a nie przypadkiem wykonać kolejny stan. Taka oryginalna budowa wynika z jednego prostego powodu: jeżeli chcemy dla trzech różnych stanów wykonać to samo zadanie, po prostu piszemy pod rząd trzy case'y, potem kod i na końcu przerwanie. W powyższym przykładzie dodaliśmy alternatywną nazwę pierwszej akcji: "dod". Kod po przeróbkach wygląda tak:

 1 <?php
 2 if(!isset($_GET['act']))
 3 {
 4    $_GET['act'] = 'index';
 5 }
 6 switch($_GET['act'])
 7 {
 8    case 'dod':
 9       echo 'Jak nie damy komendy "break", to pokaże nam się też...<br/>';
10    case 'dodaj':
11       echo 'Dodawanie danych';			
12       break;
13    case 'edytuj':
14       echo 'Edycja danych';
15       break;
16    case 'usun':
17       echo 'Usuwanie danych';
18       break;
19    default:
20       echo 'Wyświetlenie danych';	
21 }

Wywołując skrypt jako nazwapliku.php?act=dodaj, zobaczymy:

Dodawanie danych

Wywołując skrypt jako nazwapliku.php?act=dod, zobaczymy:

Jak nie damy komendy "break", to pokaże nam się też...
Dodawanie danych

PHP wykonał zarówno stan "dod", jak i następujący po nim "dodaj", gdyż w tym pierwszym brakowało komendy break.

Kod stanu może zawierać inne struktury kontrolne:

 1 <?php
 2 if(!isset($_GET['act']))
 3 {
 4    $_GET['act'] = 'index';
 5 }
 6 switch($_GET['act'])
 7 {
 8    case 'dodaj':
 9       if($_SERVER['REQUEST_METHOD'] == 'POST')
10       {
11          echo 'Dodawanie danych';
12       }
13       else
14       {
15           echo 'Formularz dodawania';
16       }
17       break;
18    case 'edytuj':
19       echo 'Edycja danych';
20       break;
21    case 'usun':
22       echo 'Usuwanie danych';
23       break;
24    default:
25       echo 'Wyświetlenie danych';	
26  }

Tutaj dodaliśmy instrukcję if, która sprawdza, czy żądanie nadeszło do nas z formularza HTTP, który należy przetworzyć, czy normalnie (wtedy wyświetlamy formularz). $_SERVER['REQUEST_METHOD'] zawiera nazwę metody, za pomocą której odbyło się żądanie HTTP.

Instrukcję switch warto stosować, kiedy wybieramy konkretną możliwość z określonego w kodzie zbioru. Dla PHP zaletą jest, że "wie", jakie są wszystkie stany. Instrukcja warunkowa if jest bardziej ogólna i tam interpreter sprawdza po prostu po kolei wszystkie warunki, aż natrafi na pasujący, nie zagłębiając się w jakieś większe zależności między danymi. Switcha nie należy używać, kiedy mamy tylko dwa stany, gdyż takie zagranie przypominałoby wytoczenie haubicy do zabicia komara. Instrukcja ta nie sprawdza się także przy wszystkich bardziej ogólnych warunkach rodzaju "mniejszy, większy".

Poprzedni rozdział: Instrukcja switch
Spis treści
Następny rozdział: Instrukcja while

Instrukcja for[edytuj]

Pętle[edytuj]

Wszystkie kolejne struktury kontrolne, jakie poznamy, określa się jednym wspólnym terminem: pętle.

Wiemy już, że pętla powtarza w kółko pewien fragment kodu. Różnice między poszczególnymi rodzajami dotyczą tego, jak i kiedy jest ona przerywana. Na początek zajmiemy się pętlą for. Pokazuje ona pazurki, kiedy zliczamy ilość wywołań pętli i na podstawie tego określamy, czy trzeba ją przerwać, czy nie. W for definiujemy trzy wyrażenia:

  • Startu - najczęściej inicjuje licznik wywołań
  • Końca - warunek zakończenia
  • Iteracji - najczęściej zwiększa licznik wywołań

Oddzielone są one średnikami. Pokażemy to na przykładzie skryptu wyświetlającego liczby od 0 do 9.

1 <?php
2 for($i = 0; $i < 10; $i++)
3 {
4 	echo $i.'<br/>';	
5 }

Warunek startu tworzy nową zmienną $i z wartością zero. Następnie określamy, że dopóki $i jest mniejsze od 10, pętla ma się powtarzać. Przy każdym cyklu należy zwiększyć wartość $i o 1.

Proste wyświetlanie tablic[edytuj]

Pętla for jest użyteczna przy wyświetlaniu tablic z indeksami numerycznymi. Mamy plik tekstowy z zawartością:

Litwo, ojczyzno moja! Ty jesteś jak zdrowie,
Ile cię trzeba cenić, ten tylko się dowie,
Kto cię stracił, dziś piękność twą w całej ozdobie
Widzę i opisuję, bo tęsknię po tobie.

Zastosujemy funkcję file(), aby wczytać go do pamięci z jednoczesnym rozbiciem na poszczególne wiersze zapisane w tablicy. W ten sposób będziemy je mogli wyświetlić jako elementy listy wypunktowanej:

1 <?php
2 $plik = file('plik.txt');
3  	
4 echo '<ul>';
5 for($i = 0, $x = count($plik); $i < $x; $i++)
6 {
7 	echo '<li>'.trim($plik[$i]).'</li>';	
8 }
9 echo '</ul>';

Do określenia ilości wierszy użyliśmy poznanej już wcześniej funkcji count(). Przy wyświetlaniu stosujemy jeszcze jedną: trim(). Usuwa ona z początku i końca każdego wiersza białe znaki, tj. spacje, zejścia do nowej linii, tabulatory. Wynikiem działania skryptu jest zawartość pliku wyświetlona w liście wypunktowanej.

Zwróć uwagę na specyficzną budowę wyrażenia inicjacji pętli. Pragniemy utworzyć dwie zmienne, dlatego oddzielamy je przecinkami. Podobną sztuczkę możemy zastosować również w wyrażeniu iteracyjnym. Można się zapytać, dlaczego zastosowaliśmy tak rozbudowaną konstrukcję. Przecież dopuszczalne jest także napisanie:

5 for($i = 0; $i < count($plik); $i++)

W typowych sytuacjach obie konstrukcje zachowają się podobnie, lecz warto pamiętać o pewnym niuansie technicznym. Pierwsza z konstrukcji pobiera ilość elementów tablicy na samym początku. Jeżeli któryś cykl pętli doda jakiś element, nie zostanie on uwzględniony. W drugim przypadku ilość ta jest pobierana po każdym cyklu, zatem pętla dysponuje bieżącymi informacjami o wielkości tablicy i wszelka jej zmiana zostanie uwzględniona w ilości wykonanych cykli. Sposób ten jest jednak mniej wydajny od pierwszego.

Break i Continue[edytuj]

Przy okazji omawiania instrukcji switch poznaliśmy komendę break. Ma ona bardzo duże zastosowanie przy pętlach, które potrafi przerywać. Istnieje także kolejne polecenie: continue. Przerywa ono jedynie aktualny cykl pętli i powoduje rozpoczęcie następnego.

Mamy prosty ciąg tekstowy:

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

Wiemy o nim trzy rzeczy:

  1. Spacje ignorujemy
  2. Kropka oznacza koniec wprowadzania komend
  3. Średnik separuje komendy

Naszym zadaniem jest wprowadzenie komend do tablicy, aby można je było łatwiej przetwarzać. Skrypt ten będziemy pisać kawałek po kawałku. Na początek stwórzmy sobie parę zmiennych:

1 <?php 
2 $tekst = 'Komenda; Komenda; Komenda; Komenda. To już pomijamy.';
3 $tablica = array(0 => '');
4 $t = 0;

$tekst to tekst do przetworzenia. $tablica jest miejscem docelowym komend z "firmowo" utworzonym pierwszym pustym elementem. $t to licznik mówiący, do którego elementu tablicy wprowadzamy znaki.

Rozpoczynamy pętlę. Do pobrania długości ciągu użyjemy funkcji strlen(). $i to licznik położenia w ciągu tekstowym. Wskazuje na aktualnie przetwarzany znak:

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

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

 7 	if($tekst[$i] == ' ')
 8 	{
 9  		continue;
10  	}

Zauważ, jak odwołujemy się do określonego znaku wewnątrz ciągu: $tekst[$i]. Numer znaku (począwszy od zera) podajemy jako indeks w nawiasach kwadratowych, identycznie jak w tablicach.

Druga możliwość - po napotkaniu kropki przerwać pętlę wcześniej:

11 	if($tekst[$i] == '.')
12  	{
13  		break;
14  	}

Przechodzimy do ewentualności trzeciej. Przy średniku należy przesunąć się na nowy element tablicy wynikowej i zainicjować go pustym ciągiem. Każdy inny znak wprowadzamy do aktualnego elementu tablicy:

15 	if($tekst[$i] == ';')
16  	{
17  		$t++;
18  		$tablica[$t] = '';
19  	}
20  	else
21  	{
22  		$tablica[$t] .= $tekst[$i];
23  	}

Teraz dopełnienie formalności, tj. zamknięcie pętli i wyświetlenie zawartości tablicy funkcją var_dump():

24 }
25  
26 echo '<pre>';
27 var_dump($tablica);
28 echo '</pre>';

Zapytajmy się, jak przerwać pętlę, jeżeli jesteśmy aktualnie w instrukcji switch? Wywołanie break i continue będzie się przecież odnosiło do niej, a tego nie chcemy. Rozwiązaniem jest podanie po nich numeru określającego, której instrukcji wzwyż dotyczy wywołanie. Przepiszmy jeszcze raz powyższy kod z wykorzystaniem instrukcji wyboru (notabene nawet bardziej pasującej w tym przypadku):

 1 <?php
 2 $tekst = 'Komenda; Komenda; Komenda; Komenda. To już pomijamy.';
 3 $tablica = array(0 => '');
 4 $t = 0;
 5  
 6 for($i = 0; $i < strlen($tekst); $i++)
 7 {
 8 	switch($tekst[$i])
 9 	{
10 		case ' ':
11 			continue 2;
12 		case '.':
13 			break 2;
14 		case ';':
15 			$t++;
16 			$tablica[$t] = '';
17 			break;
18 		default:
19 			$tablica[$t] .= $tekst[$i];		
20 	}
21 }
22  	
23 echo '<pre>';
24 var_dump($tablica);
25 echo '</pre>';

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

Poprzedni rozdział: Instrukcja for
Spis treści
Następny rozdział: Instrukcja do while

Instrukcja while[edytuj]

Kolejną pętlą jest while, będąca znacznie prostszą odmianą poznanej ostatnio pętli for. Wymagany jest tu jedynie warunek zakończenia, a pętla wykonuje się, dopóki jest on prawdziwy. Oto prosty przykład:

1 <?php
2 while(rand(0,10) != 8)
3 {
4 	echo 'Jeszcze nie wylosowałem!<br/>';
5 }

Pętla ta będzie wykonywała się, dopóki funkcja rand() nie wylosuje liczby 8. Jeżeli dostaniemy ją już w pierwszym sprawdzeniu, napis nie pojawi się w ogóle.

Ze względu na taką ogólną konstrukcję while przydaje się tam, gdzie musimy coś powtarzać do czasu osiągnięcia pewnego stanu. Sztandarowym przykładem jest czytanie pliku, gdzie takim specyficznym stanem, w którym musimy przerwać naszą pracę, jest jego koniec. Zastosowanie pętli while będzie tu o wiele lepsze, niż obliczanie na podstawie wielkości pliku, ile "segmentów" musimy pobrać i zabawa z licznikami.

1 <?php
2 $f = fopen('plik.txt', 'r'); // 1
3  	
4 while(!feof($f)) // 2
5 {
6 	echo fgets($f, 16); // 3
7 }
8  	
9 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. Dopóki nie osiągniemy końca pliku...
  3. Pobieraj z niego kolejne 16-znakowe bloki.
  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 z rozdziału poprzedniego:

1 <?php
2 $i = 0;
3 while($i < 10)
4 {
5 	echo $i.'<br/>';
6 	$i++;
7 }

Tu także można stosować komendy break oraz continue poznane w poprzednim rozdziale.

Pętla while przyda się nam, gdy zaczniemy omawiać komunikację z bazami danych. Zostanie tam wykorzystana do pobierania rekordów.

Poprzedni rozdział: Instrukcja while
Spis treści
Następny rozdział: Instrukcja foreach

Instrukcja do while[edytuj]

W przeciwieństwie od normalnego while, tutaj warunek sprawdzany jest na końcu, tak więc pętla zostanie wykonana przynajmniej raz. Nie jest to często wykorzystywana właściwość, ale warto o niej pamiętać.

Składnia pętli do while jest dość specyficzna. Przed nawiasem klamrowym pojawia się jedynie słowo kluczowe do, a while z warunkiem znajduje się na samym końcu. Przedstawimy to na przykładzie takiego oto skryptu:

1 <?php
2 do
3 {
4 	echo "Podaj i: \n";
5 	fscanf(STDIN, "%d\n", $i); // 1
6 }
7 while($i < 10);

Nie będziemy uruchamiali go w przeglądarce, ale w linii komend. Powyższy skrypt pracuje w konsoli systemowej i może pobierać stamtąd dane poprzez wiersz zaznaczony jako 1 (nie przejmuj się, że nie rozumiesz jego budowy. Poznamy ją dalej). Aby uruchomić skrypt, uruchom konsolę i ustaw się poleceniem cd na katalogu, w którym zainstalowałeś PHP. Następnie wydaj następujące polecenie:

php -f /scieżka/do/skrypt.php

Jako wartość parametru -f musisz podać pełną ścieżkę do skryptu, który chcesz uruchomić.

Zauważ, że dzięki użyciu pętli do while, nie musimy umieszczać w skrypcie dwa razy kodu do pytania się o zmienną $i. Oto analogiczny kod z wykorzystaniem normalnego while:

1 <?php
2 echo "Podaj i: \n";
3 fscanf(STDIN, "%d\n", $i);
4 
5 while($i < 10)
6 {
7 	echo "Podaj i: \n";
8 	fscanf(STDIN, "%d\n", $i);
9 }

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.

Poprzedni rozdział: Instrukcja do while
Spis treści
Następny rozdział: Funkcje

Instrukcja foreach[edytuj]

Ostatnią pętlą jest foreach. Ma ona specyficzne zastosowanie, ponieważ służy wyłącznie do przeglądania zawartości typów złożonych: tablic oraz obiektów. Kod wewnątrz niej jest powtarzany dla każdego z elementów tablicy, a on sam jest na ten czas przenoszony do tworzonej przez pętlę zmiennej. Wróćmy do naszego przykładu z pętlą for odczytującego zawartość pliku. Przepiszemy go z wykorzystaniem foreach:

1 <?php
2 $plik = file("plik.txt");
3  	
4 echo '<ul>';
5 foreach($plik as $linia)
6 {
7 	echo '<li>'.trim($linia).'</li>';	
8 }
9 echo '</ul>';

Teraz skrypt ma o wiele bardziej przejrzystą budowę. Przyjrzyjmy się deklaracji pętli:

5 foreach($plik as $linia)

Mówi nam ona, że pętla ma analizować tablicę $plik, a aktualnie przetwarzany element ma być zapisany w zmiennej $linia.

Foreach umożliwia nam także zwracanie nazw indeksów elementów:

1 <?php
2 $plik = file('plik.txt');
3  	
4 echo '<ul>';
5 foreach($plik as $numer => $linia)
6 {
7 	echo '<li>Linia #'.$numer.': '.trim($linia).'</li>';	
8 }
9 echo '</ul>';

Foreach ma tę przewagę nad innymi pętlami, że wie, jakie elementy należą do tablicy i zawsze przetworzy tylko je. Gdybyśmy przed wyświetleniem pliku usunęli z niego np. linijkę 1, pętla for nie dałaby rady, próbując przetworzyć nieistniejący element. Nie robi oczywiście tego dla złośliwości, lecz dlatego, że operuje na liczniku i nie wie, do czego jest on przez nas dalej wykorzystywany.

1 <?php
2 $plik = file('plik.txt');
3 unset($plik[1]); // usuwamy linijkę o indeksie 1
4 echo '<ul>';
5 foreach($plik as $numer => $linia)
6 {
7 	echo '<li>Linia #'.$numer.': '.trim($linia).'</li>';	
8 }
9 echo '</ul>';

Tworzone przez foreach zmienne są jedynie kopiami oryginalnych wartości, dlatego próba ich modyfikacji wewnątrz pętli w żaden sposób nie wpłynie na zawartość tablicy:

 1 <?php
 2 $plik = file('plik.txt');
 3  
 4 foreach($plik as $linia)
 5 {
 6 	$linia = 'Próba skasowania';
 7 }
 8   	
 9 echo '<pre>';
10 var_dump($plik);
11 echo '</pre>';

Wewnątrz pętli próbujemy przypisać wartość do zmiennej $linia. Owszem, udaje nam się to, ale nowa treść nie trafia w ogóle do tablicy i systemowe wyświetlenie jej zawartości ukazuje brak jakiejkolwiek reakcji. Czy zatem możliwe jest dokonywanie przypisań wewnątrz foreach? Oczywiście. Są dwie sztuczki. Pierwsza polega na wykorzystaniu zwracanego przez pętlę indeksu. Pousuwajmy z tablicy zbędne białe znaki:

 1 <?php
 2 $plik = file('plik.txt');
 3  
 4 foreach($plik as $i => $linia)
 5 {
 6 	// jeszcze jakiś napis sobie doklejmy
 7 	$plik[$i] = trim($linia).' [OK]';
 8 }
 9   	
10 echo '<pre>';
11 var_dump($plik);
12 echo '</pre>';

Rozwiązanie to jest nieco trikowe, ale działa. Możemy jednak zastosować coś innego. PHP posiada pewien element zwany referencją. Ogólnie rzecz biorąc jest to odnośnik do zmiennej, który zachowuje się tak, jak ona. Modyfikacja referencji powoduje także modyfikację oryginalnego elementu. Począwszy od PHP 5, referencje można używać w pętli foreach. Wystarczy poprzedzić w jej deklaracji zmienną $linia znakiem &:

 1 <?php
 2 $plik = file('plik.txt');
 3  
 4 foreach($plik as &$linia)
 5 {
 6 	$linia = trim($linia).' [OK]';
 7 }
 8   	
 9 echo '<pre>';
10 var_dump($plik);
11 echo '</pre>';

Teraz modyfikacja zmiennej $linia jest równoznaczna z modyfikacją aktualnego elementu w tablicy $plik, ponieważ zmienna jest takim właśnie odnośnikiem. O referencjach szerzej powiemy w rozdziale Inne elementy składni.

Poprzedni rozdział: Instrukcja foreach
Spis treści
Następny rozdział: Inne elementy składni

Funkcje[edytuj]

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, czyli patrząc na definicję - element zbioru X - wymieniamy jako argumenty funkcji, a po jej wykonaniu otrzymujemy wynik, czyli element zbioru Y. W matematyce jedną z najbardziej znanych funkcji jest sinus, który dla każdej wartoś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ż bardziej praktyczne funkcje z punktu widzenia generowania stron WWW, np. strip_tags(), która z podanego tekstu usuwa znaczniki HTML. Oprócz tego, programista może samodzielnie tworzyć własne funkcje i omówienie tego zagadnienia 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 zamkniemy czę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 po prostu wywołujemy ją. Dobry podział skryptu na funkcje ma jeszcze jedną zaletę - jeśli w jakimś kawałku kodu znajdziemy 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ólna uwaga.

Tworzenie własnych funkcji[edytuj]

Każ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:

1 function nazwaFunkcji(argumenty)
2 {
3    // kod funkcji
4 }

Od tego miejsca możemy wywoływać naszą funkcję w identyczny sposób, jak te dostępne w PHP.

1 <?php
2 function formatujTekst($tekst)
3 {
4    echo '<font color="red">'.strtoupper($tekst).'</font>';	
5 }
6  	
7 formatujTekst('to jest tekst 1');
8 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 dolara. Gdybyśmy chcieli podać więcej argumentów, oddzielamy je od siebie przecinkami. Jeżeli funkcja nie będzie uż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 kolejnej funkcji włącznie. Jednak zauważmy, że nasza pierwsza funkcja nie zwraca wartości. Zamiast tego wynik wysyła od razu na ekran i próba wykonania

$zmienna = formatujTekst('to jest tekst 1');

nic by nam nie dała. Aby zwrócić cokolwiek jako wynik, musimy skorzystać z komendy return:

1 <?php
2 function formatujTekst($text)
3 {
4    return '<font color="red">'.strtoupper($text).'</font>';	
5 }
6  	
7 echo formatujTekst('to jest tekst 1').'<br/>';
8 echo formatujTekst('to jest tekst 2').'<br/>';

Po return podajemy wyrażenie generujące wartość do zwrócenia.

Zwróćmy uwagę na fakt, iż przy deklarowaniu argumentów nie podajemy żądanego typu danych. O to musimy zadbać sami, umieszczając na początku funkcji odpowiednie instrukcje warunkowe i w razie kłopotów zgłosić błąd. Napiszmy sobie skrypt wyświetlający zawartość katalogu. Stworzymy w nim jedną funkcję zwracającą zawartość podanego katalogu jako tablicę. Druga funkcja będzie uniwersalnym wyświetlaczem tablic. Dlaczego tak - zaraz wyjaśnimy.

 1 <?php
 2 function wyswietlKatalog($sciezka, $tylkoPliki = 0) // 1
 3 {
 4 	$dir = opendir($sciezka); // 2
 5 	$wynik = array();
 6 	while($f = readdir($dir)) // 3
 7 	{
 8 		if(is_file($sciezka.$f)) // 4
 9 		{
10 			$wynik[] = $f; // 5
11 		}
12 		elseif(is_dir($sciezka.$f) && $f != '.' && $f != '..' && !$tylkoPliki) // 6
13 		{
14 			$wynik[] = $f;
15 		}	
16 	}
17 	closedir($dir); // 7
18 	
19 	return $wynik; // 8
20 } // end wyswietlKatalog();
21  
22 function pokazListe(array $lista) // 9
23 {
24 	echo '<ul>';
25 	foreach($lista as $element)
26 	{
27 		echo '<li>'.$element.'</li>';		
28 	}
29 	echo '</ul>';	
30 } // end pokazListe();
31  
32 pokazListe(wyswietlKatalog('./katalog1/')); // 10
33 echo '<br/>';
34 pokazListe(wyswietlKatalog('./katalog2/', true)); // 11

Opis skryptu:

  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ą. Opcjonalnych argumentów może być więcej, z tym że podajemy je zawsze na końcu. W naszej funkcji opcjonalny argument określa, czy funkcja ma zwracać wszystko (domyślny stan: 0), czy jedynie pliki (stan 1).
  2. Otwieramy katalog o podanej ścieżce
  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 takim kontekście ją wywołujemy. Jeżeli rzeczywiście mamy plik, dodajemy go do tablicy wynikowej jako kolejny element.
  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. Mamy tu kolejno: czy element jest katalogiem, czy nie ma on nazwy "." i ".." oraz czy funkcja ma od programisty zezwolenie na pobieranie katalogów.
  7. Zamykamy katalog
  8. Zwracamy tablicę jako wynik
  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ć tutaj np. liczbę, PHP zgłosiłby błąd.
  10. Wywołanie funkcji z jednym argumentem i przekierowanie wyniku do funkcji wyświetlającej listę.
  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:

1 <?php 
2 function funkcja($a)
3 {
4 	echo $a;
5 }
6  
7 funkcja(1, 2, 3, 4, 5);

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

 1 function funkcja()
 2 {
 3 	$argumenty = func_get_args();
 4 	echo '<ul>';
 5 	foreach($argumenty as $id => $wartosc)
 6 	{
 7 		echo '<li>'.$id.' - '.$wartosc.'</li>';
 8 	}
 9 	echo '</ul>';
10 }
11  
12 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 na samym początku tworzonej funkcji, aby uniknąć kłopotów.

Widzialność zmiennych[edytuj]

Napiszmy taki skrypt:

 1 <?php
 2 $zmienna = 'To jest zmienna';
 3  
 4 function funkcja()
 5 {
 6 	echo $zmienna.'<br/>';
 7 }
 8  
 9 funkcja();
10 echo $zmienna.'<br/>';

Próbuje on wyświetlić dwa razy wartość tej samej zmiennej: z wnętrza funkcji oraz bezpośrednio w skrypcie. Po uruchomieniu okazuje się, że tylko bezpośrednie wyświetlenie podało nam prawidłowy wynik. echo wewnątrz funkcji nie pokazało żadnej wartości. Dlaczego? Zmienna przecież istnieje. I owszem, lecz tylko w tej części skryptu, w której została utworzona. PHP ma zaimplementowaną tzw. widzialność zmiennych - dla każdej funkcji tworzony jest osobny stos, niezależny od drugiego. Jeżeli więc utworzymy zmienną bezpośrednio w skrypcie, nie będzie ona istnieć w żadnej z naszych funkcji, gdyż te mają własne stosy. Ma to wyeliminować konflikty nazewnictwa.

Istnieje jednak sposób na powiedzenie PHP, że używana w funkcji zmienna jest już stworzona w stosie głównym skryptu. Po lekkiej modyfikacji skryptu otrzymujemy:

 1 <?php
 2 $zmienna = 'To jest zmienna';
 3  
 4 function funkcja()
 5 {
 6 	global $zmienna;
 7 	echo $zmienna.'<br/>';
 8 }
 9 
10 funkcja();
11 echo $zmienna.'<br/>';

Słowo kluczowe global informuje PHP, że wymienione po nim zmienne mają zostać zaimportowane ze stosu głównego. Działa ono nawet wtedy, jeśli zmienna o danej nazwie nie istnieje, dlatego korzystanie z funkcji używających global musi być bardzo uważne.

Static[edytuj]

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

 1 <?php
 2 function koloruj()
 3 {
 4    static $i = 0;
 5  		
 6    $i++;
 7 
 8    if($i % 2 == 0)
 9    {
10       return '#ffffff';
11    }
12    return '#cccccc';	
13 } // end koloruj();
14  	
15 echo '<table width="30%">';
16 for($x = 0; $x < 10; $x++)
17 {
18    echo '<tr><td bgcolor="'.koloruj().'">'.$x.'</td></tr>';	
19 }
20 echo '</table>';

Powyższy przykład koloruje naprzemiennie wiersze w tablicy. Sztuczka ta nie wymaga finezji: po prostu zwiększamy licznik i sprawdzamy, czy dzieli się bez reszty przez dwa. Jeśli tak, wstawiamy jeden kolor, jeśli nie - drugi. Z pomocą instrukcji switch można rozszerzyć algorytm na więcej kolorów.

Tutaj kolor jest zwracany przez odpowiednią funkcję. Zapamiętuje ona sobie stan wewnętrznego iteratora $i między kolejnymi wywołaniami przy pomocy słowa kluczowego static. Gdybyś usunął tę linijkę, funkcja cały czas zwracałaby ten sam kolor, gdyż zmienna tworzona byłaby w kółko od nowa z domyślną wartością 0.

Rekurencja[edytuj]

Rekurencja w programowaniu oznacza odwoływanie się funkcji do samej siebie. Jest użyteczna, w niektórych sytuacjach wręcz niezbędna, lecz pochłania znacznie więcej zasobów, dlatego należy korzystać z niej ostrożnie.

Za pomocą rekurencji możemy wyświetlić w PHP drzewo katalogów:

 1 <?php
 2 function wyswietlKatalog($sciezka)
 3 {
 4 	$dir = opendir($sciezka);
 5 	echo '<ul>';
 6 	while($f = readdir($dir))
 7 	{
 8 		if(is_dir($sciezka.$f) && $f != '.' && $f != '..')
 9 		{
10 			echo '<li>'.$f;
11 			wyswietlKatalog($sciezka.$f.'/'); // 1
12 			echo '</li>';
13 		}
14 	}
15 	echo '</ul>';
16 	closedir($dir);
17 } // end wyswietlKatalog();
18  
19 wyswietlKatalog('../../');

Funkcja wyswietlKatalog() w przypadku napotkania katalogu w aktualnie sprawdzanej ścieżce, wywołuje samą siebie (1), z doklejoną do dotychczasowej ścieżki nazwą tego katalogu. W ten sposób możemy pobrać całe drzewo, niemniej w przypadku rozbudowanych struktur może trwać to nawet kilkanaście sekund!

Sprawdźmy następujący skrypt:

 1 <?php
 2 function wypisz($tekst, $ile)
 3 {
 4    echo $ile.': '.$tekst.'<br/>';
 5    if($ile > 0)
 6    {
 7       wypisz($tekst, $ile - 1);
 8    }
 9 } // end wypisz();
10 
11 wypisz('Witaj', 30);

Demonstruje on pewną właściwość rekurencji - możemy nią zastąpić pętle, odpalając naszą funkcję rekurencyjnie określoną liczbę razy. Jako licznik służy nam wartość argumentu $ile. Gdy jest ona większa od zera, funkcja wywołuje samą siebie, zmniejszając go o 1, aż dojdziemy do zera. Nie ma w tym nic dziwnego. Takie rozumienie funkcji jest podstawą tzw. programowania funkcyjnego charakterystycznego dla takich języków programowania, jak Ocaml 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 stan dotychczasowej. 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żony wywoł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 przypadkach może to być zadanie bardzo trudne. Oto prosta implementacja rekurencyjna funkcji silnia, która w matematyce zdefiniowana jest następująco:

 1 <?php
 2 function silnia($n)
 3 {
 4   if($n > 0)
 5   {
 6      return $n * silnia($n - 1);
 7   }
 8   return 1;
 9 } // end silnia();
10 
11 echo silnia(6);

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

 1 <?php
 2 function silnia($n)
 3 {
 4    $wynik = 1;
 5    while($n > 0)
 6    {
 7       $wynik *= $n--;
 8    }
 9    return $wynik;
10 } // end silnia();
11 
12 echo silnia(6);

Użyteczne funkcje[edytuj]

PHP dysponuje kilkoma funkcjami do zarządzania funkcjami. Brzmi to może dość śmiesznie, lecz w praktyce bywa bardzo przydatne.

Na początek zastanówmy się, kiedy PHP sprawdza, że funkcja nie istnieje. Okazuje się, że nie dzieje się to w momencie kompilacji, lecz wykonywania skryptu. Ma to swoje uzasadnienie przy konstruowaniu modułowych skryptów (zajmiemy się nimi w następnym rozdziale). Pierwszy plik PHP odwołuje się do funkcji zdefiniowanych w drugim, lecz ten z kolei ładowany jest później. Gdyby z powodu nieistnienia jednej z nich skrypt byłby przerywany w momencie kompilacji, skrypt nie miałby żadnych szans na działanie. Ponadto nie dałoby się pracować ze skryptami korzystającymi z rozszerzeń, których na serwerze nie ma. Ma to sens, przecież instrukcją warunkową możemy zdefiniować alternatywny kod dla tych uboższych serwerów.

PHP ułatwia nam zadanie jeszcze bardziej. Za pomocą function_exists() możemy sprawdzić, czy podana przez nas funkcja istnieje. Narzędziem tym można sondować zarówno nasze własne, jak i definiowane przez rozszerzenia funkcje. W poniższym przykładzie wykorzystamy to do sprawdzenia, czy serwer posiada obsługę protokołu IMAP:

1 <?php
2 if(function_exists('imap_open'))
3 {
4    echo 'IMAP dostępny';
5 }
6 else
7 {
8    echo 'IMAP niedostępny';
9 }

Innym sposobem sprawdzenia, czy rozszerzenie jest załadowane, jest skorzystanie z funkcji extension_loaded(), która ma tę przewagę, że działa także z rozszerzeniami obiektowymi, w których zwykłych funkcji nie ma:

1 <?php
2 if(extension_loaded('imap'))
3 {
4    echo 'IMAP dostępny';
5 }
6 else
7 {
8    echo 'IMAP niedostępny';
9 }

Jak dobrze korzystać z funkcji?[edytuj]

Funkcje to jedno z podstawowych narzędzi programisty. Omówiliśmy techniczne aspekty ich działania w języku PHP, 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 i wracamy 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.
  2. Jeśli kod danej funkcji staje się bardzo długi, możemy rozważyć jej rozbicie na podproblemy, które zapiszemy w mniejszych funkcjach. Powinniśmy to zrobić zwłaszcza wtedy, gdy dany problem pojawia się wielokrotnie w różnych postaciach. Próbujemy wtedy wyciągnąć wspólny mianownik, a różnice obsłużyć poprzez konfigurację argumentami.
  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 krok ma 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żny nie będzie tego robił. Oczywiście zdarzają się pomyłki; nie wszystkie uzasadnienia okazują się poprawne, ale duże projekty ulepszane są cały czas. Jeśli coś się nie sprawdziło, jest po prostu przerabiane. Sztuka polega na tym, że jeś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że niektórzy uważni czytelnicy już zauważyli, że w dotychczasowych przykładach pojawiały się zarówno funkcje, które np. 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 dowodzeniu poprawności programu. Każda operacja języka programowania może mieć tzw. skutki uboczne. Jest to dowolny efekt 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 ma już inną wartość, co potencjalnie może wpłynąć na działanie dalszej części kodu. Pomimo swojej nazwy, skutki uboczne często są właśnie spodziewanymi rezultatami jakiejś operacji. Nie należy ich rozumieć dosłownie, lecz wł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 na napisany 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 on wykorzystany. 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 do przeglądarki wysyłany jest tekst z listą katalogów. Wywołując ją dwukrotnie, użytkownik dostanie taką listę dwa razy.

Jeśli chcemy napisać dobrą funkcję, po prostu musimy znać wszystkie jej skutki uboczne i wiedzieć, czy faktycznie są 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:

1 <?php
2 function kolorujTekst($tekst)
3 {
4    echo '<font color="red">'.$tekst.'</font>';	
5 }

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

1 <?php
2 function pogrubTekst($tekst)
3 {
4    echo '<strong>'.$tekst.'</strong>';	
5 }

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

Poprzedni rozdział: Funkcje
Spis treści
Następny rozdział: Każdy popełnia błędy

Inne elementy składni[edytuj]

Include i require[edytuj]

Tworzenie dynamicznych stron byłoby bardzo kłopotliwe, gdybyśmy musieli pracowicie kopiować wszystkie utworzone przez nas funkcje do każdego pliku PHP z osobna. Na szczęście PHP udostępnia mechanizmy na dołączanie jednego skryptu do drugiego. Służą do tego instrukcje include, require, include_once oraz require_once.

Rozpatrzmy taką sytuację: mamy dwa pliki z funkcjami definiującymi wygląd treści: normalny.php oraz opcjonalny.php. Stworzone w nich są identyczne funkcje różniące się jedynie zawartością. We właściwym skrypcie dołączamy jeden z tych plików decydując o tym, w jakim stylu zaprezentowane zostaną dane na stronie. Oto kod dwóch dołączanych plików.

normalny.php:

 1 <?php
 2 function pokazTytul($tytul)
 3 {
 4 	echo '<h1>'.$tytul.'</h1>';	
 5 } // end pokazTytul();
 6 
 7 function pokazParagraf($tekst)
 8 {
 9 	echo '<p>'.$tekst.'</p>';
10 } // end pokazParagraf();
11 
12 function pokazListe(array $tablica)
13 {
14 	echo '<ul>';
15 	foreach($tablica as $element)
16 	{
17 		echo '<li>'.$element.'</li>';
18 	}
19 	echo '</ul>';
20 } // end pokazListe();

opcjonalny.php:

 1 <?php
 2 function pokazTytul($tytul)
 3 {
 4 	echo '<h1>'.$tytul.'</h1>';	
 5 } // end pokazTytul();
 6 
 7 function pokazParagraf($tekst)
 8 {
 9 	echo '<p style="font-weight:bold;">'.$tekst.'</p>';
10 } // end pokazParagraf();
11 	
12 function pokazListe(array $tablica)
13 {
14 	echo '<ol>';
15 	foreach($tablica as $element)
16 	{
17 		echo '<li>'.$element.'</li>';
18 	}
19 	echo '</ol>';
20 } // 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:

 1 <?php
 2 if(!isset($_GET['styl']))
 3 {
 4 	require('./normalny.php');
 5 }
 6 else
 7 {
 8 	require('./opcjonalny.php');
 9 }
10  	
11 pokazTytul('Tytuł');
12 pokazParagraf('To jest paragraf');
13 
14 pokazListe(array(0 =>
15 	'To',
16 	'Jest',
17 	'Lista'	
18 ));

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 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ę pisania wszystkiego ciurkiem bez podziału na funkcje, mniejsze moduły itd., gdyż zmniejsza to odporność skryptu na błędy, wprowadza chaos i utrudnia dodawanie/modyfikowanie nowych opcji. Przyjrzyjmy się, jak zatem zorganizować naszą witrynę. Przede wszystkim zróbmy sobie jeden katalog na wszystkie pliki z funkcjami. Może on się nazywać np. includes. Umieszczamy w nim funkcje ułatwiające komunikację z bazą danych, przetwarzanie tekstu, autoryzację i wykonujące wszystkie inne rutynowe operacje. Dodatkowo tworzymy katalog actions, w którym bę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:

 1 <?php
 2 require('../includes/config.php');
 3 require('../includes/dispatch.php');
 4 require('../includes/session.php');
 5 require('../includes/authorize.php');
 6 require('../includes/templates.php');
 7 require('../includes/functions.php');
 8 require('../includes/layout.php');
 9 
10 initSystem();
11 dispatchAction(isset($_GET['act']) ? $_GET['act'] : 'index');

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

 1 <?php
 2 function dispatchAction($action)
 3 {
 4    if(!ctype_alpha($action))
 5    {
 6       displayError('Nazwa akcji zawiera nieprawidłowe znaki.');
 7    }
 8    if(!file_exists('../actions/'.$action.'.php'))
 9    {
10       displayError('Podana akcja nie istnieje.');
11    }
12    require_once('../actions/'.$action.'.php');
13    $action .= 'Action';
14 
15    $action();
16 } // 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 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 nazwie nazwaakcjiAction(). Dlatego do nazwy akcji doklejamy słowo Action i wywołujemy funkcję, wczytując jej nazwę 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 naszym przypadku załatwiają je już mechanizmy przeznaczone dla require_once.

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

Stałe[edytuj]

Spó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 niewygodne zwł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 rzut oka widać, iż rozwiązanie to nie jest najlepsze, bowiem każdą zmienną ze ścieżką musimy przenosić do funkcji z użyciem global, a ponadto istnieje ryzyko, że w którymś miejscu omyłkowo ją nadpiszemy. Remedium na te kłopoty są stałe. Są to aliasy na pewne wartości, których po utworzeniu nie można już modyfikować. Najogólniej wykorzystuje się je dla często powtarzających się w skrypcie wartości. Przyjrzyjmy się poniższemu skryptowi:

1 <?php
2 define('DIR_GLOWNY', './');
3 define('DIR_SILNIK', './includes/');
4 define('DIR_ZDJECIA', './zdjecia/');
5  	
6 require(DIR_SILNIK.'autoryzacja.php');
7 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. Stałe nie mają znaku dolara ($) przed nazwą
  2. Stałe mogą być definiowane oraz używane wszędzie bez zważania na zasady dotyczące zakresu ich dostępności
  3. Stałe nie mogą być ponownie definiowane lub "oddefiniowane" po tym jak raz zostały zdefiniowane
  4. Stałe mogą zawierać tylko wartości skalarne

Oprócz przechowywania ścieżek do katalogów, stałe mają zastosowanie podczas pisania bibliotek programistycznych. Często zdarza się, że do funkcji musimy przekazać jakąś wartość liczbową identyfikującą konkretny stan. Ponieważ spamiętywanie cyferek jest uciążliwe, tworzy się dla nich stałe o bardziej czytelnych nazwach. Możemy to pokazać na podstawie funkcji error_reporting() pozwalającej ustawić poziom raportowania błędów przez PHP. Da się ją wywoływać w ten sposób:

error_reporting(1 | 2 | 4 | 8);

Jednak w takiej postaci chyba żaden programista nie potrafiłby odczytać, jaki właściwie poziom raportowania błę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 w sposób pokazany powyżej. Rozpatrzmy przypadek systemu raportowania stanu pojazdu. Stworzyliśmy sobie funkcję raportującą pobierającą pięć wartości logicznych (prawda-fałsz) opisujących, które elementy są sprawne, a które nie. Nasz skrypt wygląda tak:

 1 <?php
 2 // Raportowanie stanu pojazdu
 3 function stan($silnikOK, $kolaOK, $swiatlaOK, $skrzyniaOK, $paliwoOK)
 4 {
 5 	if($silnikOK)
 6 	{
 7 		echo 'Silnik jest sprawny<br/>';		
 8 	}
 9 	if($kolaOK)
10 	{
11 		echo 'Koła są sprawne<br/>';		
12 	}
13 	if($swiatlaOK)
14 	{
15 		echo 'Światła są sprawne<br/>';		
16 	}
17 	if($skrzyniaOK)
18 	{
19 		echo 'Skrzynia jest sprawna<br/>';		
20 	}
21 	if($paliwoOK)
22 	{
23 		echo 'Paliwo jest w baku<br/>';		
24 	}
25 } // end stan();
26  	
27 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ęc fakt, iż komputer zapisuje wszystko w postaci zerojedynkowej i prześlijmy wszystkie parametry jako jedną liczbę o długości pięciu bitów (od 0 do 32). W stałych zdefiniujemy nazwy poszczególnych elementów przypisując im kolejne potęgi dwójki, czyli kolejne bity liczby. Następnie za pomocą operatora alternatywy bitowej zbudujemy z nich odpowiednią kombinację:

 1 <?php
 2 define('SILNIK', 1);
 3 define('KOLA', 2);
 4 define('SWIATLA', 4);
 5 define('SKRZYNIA', 8);
 6 define('PALIWO', 16);
 7  
 8 // Raportowanie stanu pojazdu
 9 function stan($stan)
10 {
11 	if($stan & SILNIK)
12 	{
13 		echo 'Silnik jest sprawny<br/>';		
14 	}
15 	if($stan & KOLA)
16 	{
17 		echo 'Koła są sprawne<br/>';		
18 	}
19 	if($stan & SWIATLA)
20 	{
21 		echo 'Światła są sprawne<br/>';		
22 	}
23 	if($stan & SKRZYNIA)
24 	{
25 		echo 'Skrzynia jest sprawna<br/>';		
26 	}
27 	if($stan & PALIWO)
28 	{
29 		echo 'Paliwo jest w baku<br/>';		
30 	}
31 } // end stan();
32 
33 stan(SILNIK | SKRZYNIA | PALIWO);

Operatorem koniunkcji bitowej możemy sprawdzić, czy dany element jest sprawny. Cały ten sposób jest określany mianem ustawiania flag (każda stała to jedna flaga) i jest powszechnie wykorzystywany w programowaniu ze względu na wydajność. Efektywniej jest przesłać pięć wartości jednym parametrem, niż tyle samo pięcioma. Przyjrzyjmy się zatem tajemnicy tego sposobu. Najpierw - jak reprezentowana jest każda flaga w postaci binarnej:

SILNIK (1):   00001
KOLA (2):     00010
SWIATLA (4):  00100
SKRZYNIA (8): 01000
PALIWO (16):  10000

W każdej liczbie będącej potęgą dwójki "zapalony" jest tylko jeden bit - z tej własności korzystamy. Kiedy składamy kilka potęg dwójki operatorem alternatywy bitowej, zapalamy tym samym poszczególne bity:

SILNIK | SKRZYNIA | PALIWO:

SILNIK (1):   00001
SKRZYNIA (8): 01000
PALIWO (16):  10000
-------------------
REZULTAT:     11001

Jeżeli w danej kolumnie którakolwiek z wartości "zapala" bit, będzie on zapalony także w rezultacie. W operatorze koniunkcji bitowej używanym do testowania, dany bit rezultatu jest zapalany jedynie w wypadku jego obecności naraz w obu podanych liczbach:

$rezultat & SILNIK 
REZULTAT:     11001
SILNIK (1):   00001
-------------------
              00001

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

$rezultat & 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 jest cała tajemnica tego sposobu.

Ścieżki oraz include_path[edytuj]

Zastanówmy się, skąd PHP wie gdzie szukać naszych plików. Podczas uruchamiania skryptu PHP ustawia mu katalog 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 (katalog nadrzędny). Załadowanie kolejnego skryptu poprzez require nie ma wpływu na tę ścieżkę! Wracając do naszego przykładu z silnikiem strony oznacza to, że gdybyśmy chcieli w pliku ../includes/dispatch.php zał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 zalecane tworzenie akcji bezpośrednio w katalogu publicznym, np. rejestracja.php, logowanie.php, lecz stworzenie 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 zastosowanie mechanizmu 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 systemach Windows, PHP od razu ładuje plik z podanej lokalizacji i nie wykonuje żadnych innych przeszukań. Ostatnia możliwość to podana od razu nazwa pliku lub katalogu:

require('plik.php');

PHP wykorzystuje wtedy mechanizm include_path. Jest to zwyczajna lista ścieżek oddzielonych średnikami, pod którymi należy szukać danego pliku. PHP sprawdza je po kolei, dopóki nie trafi. Jej domyślna wartość jest zdefiniowana 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 repozytorium klas PEAR, którym nie będziemy się teraz zajmować. Listę ścieżek można modyfikować z poziomu skryptu PHP przy pomocy funkcji set_include_path() oraz get_include_path(). Utwórz dwa pliki o nazwie wczytany.php. Jeden z nich umieść w katalogu publicznym, drugi w katalogu nadrzędnym. Ich kod niech będzie następujący:

1 <?php
2 echo __FILE__;

__FILE__ jest specjalną stałą, która zawsze zawiera ścieżkę bezwzględną pliku, w którym się ona znajduje. Dzięki niej 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:

1 <?php
2 require('wczytany.php');

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

1 <?php
2 set_include_path('..'.PATH_SEPARATOR.'.');
3 require('wczytany.php');

Tym razem załadowany został plik z katalogu nadrzędnego, ponieważ po modyfikacji PHP w pierwszej kolejności szuka właśnie tam. Usuńmy teraz nadrzędny wczytany.php. PHP załadował plik z katalogu publicznego, gdyż w nadrzędnym nie znalazł tego, czego szukał. Ten eksperyment powinien pokazać istotę działania include_path. Użyty w 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 wraz ze 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:

1 <?php
2 // wczytuj zawsze z bieżącego katalogu
3 require('./wczytany.php');

Include_path przydaje się w sytuacjach, gdy potrzebna nam większa elastyczność lub chcemy skorzystać z bibliotek PHP udostępnianych na serwerze dla ogółu (np. wspomniane już repozytorium PEAR, które zazwyczaj umieszczane jest 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ęzykiem niezależnym od platformy, dlatego skrypty napisane pod Windowsem powinny działać także na serwerze z zainstalowanym Linuksem, ale gdy zaczynamy bawić się plikami, mogą wystąpić problemy. Poniżej zostały zebrane wszystkie najważniejsze różnice, o których trzeba pamiętać, pisząc własne skrypty. Powinny zapamiętać je w szczególności osoby pracujące pod systemem Windows, który bardzo rzadko instaluje się na serwerach WWW.

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że znaleźć plików na serwerze Linuksowym, w pierwszej kolejności sprawdź czy jeśli np. zaczynasz w wywołaniach nazwy plików dużą literą, faktycznie tak się zaczynają na dysku. Warto korzystać także z separatora katalogów w stylu uniksowym, czyli ukośnika w prawo. Jest on rozpoznawany także przez Windows, natomiast ukośnik w lewo przez systemy uniksowe - nie. PHP przechowuje odpowiedni ukośnik charakterystyczny dla systemu, na którym pracuje, w stałej DIRECTORY_SEPARATOR.

Referencje[edytuj]

Referencja to swego rodzaju alias na zmienną, dzięki czemu może ona występować pod dwoma nazwami. Popatrzmy na taki przykład:

1 <?php 
2 $a = 5;
3 $b = &$a; // tworzymy referencje
4  
5 echo 'B: '.$b.'; A: '.$a.'<br/>';
6 $b = 6;
7 echo 'B: '.$b.'; A: '.$a.'<br/>';
8 $a = 7;
9 echo 'B: '.$b.'; A: '.$a.'<br/>';

Referencję można utworzyć, stawiając przed zmienną źródłową znak &. Po uruchomieniu przykładu zobaczymy, że modyfikując jedną z tych zmiennych, zmieniała się także druga. Jest tak dlatego, że obie te zmienne reprezentują w rzeczywistości tę samą wartość.

Referencje są bardzo użyteczne przy pracy z dużymi zbiorami danych. Przeprowadzając operację:

$b = &$a;

nie wykonujemy żadnego żmudnego kopiowania tych danych, lecz tworzymy do nich kolejny odnośnik. 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 problem zniknął, ponieważ możemy poinformować PHP, że zmienna wartości ma być referencją. Dzięki temu możliwe będzie modyfikowanie przez nią zawartości tablicy:

1 <?php
2 $tablica = array(0 => 1, 2, 3, 4, 5, 6);
3    
4 foreach($tablica as $id => &$element)
5 {
6    $element = rand(1, 6);  
7 }
8   
9 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, zatem sposób okazuje się skuteczny.

Dzięki referencjom funkcje mogą pozornie zwracać więcej wartości - poprzez referencyjne parametry. Napiszemy sobie teraz funkcję do kolorowania tekstów dłuższych, niż 5 znaków. W zależności od długości funkcja zwraca wartość 1 lub 0. Zmodyfikowany tekst jest oddawany tą samą drogą, którą się dostał, tj. referencyjnym parametrem. W celu przetestowania przetworzymy sobie dwa ciągi. Jeden liczy sobie trzy znaki, drugi jest wyraźnie dłuższy.

 1 <?php
 2 function przetworz(&$tekst)
 3 {
 4    if(strlen($tekst) > 5)
 5    {
 6       $tekst = '<font color="red">'.$tekst.'</font>';
 7       return 1;    
 8    }
 9    return 0; 
10 } // end przetworz();
11    
12 // Probujemy przerobic krotki tekst
13 $tekst = 'Jan';
14    
15 if(przetworz($tekst))
16 {
17    echo 'Przetworzony tekst: '.$tekst.'<br/>';
18 }
19 else
20 {
21    echo 'Błąd: za krótki tekst! '.$tekst.'<br/>';
22 }
23    
24 // A teraz dlugi
25 $tekst = 'Ala ma kota';
26  
27 if(przetworz($tekst))
28 {
29    echo 'Przetworzony tekst: '.$tekst.'<br/>';
30 }
31 else
32 {
33    echo 'Błąd: za krótki tekst! '.$tekst.'<br/>';
34 }

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.

 1 <?php
 2 function funkcja(&$a)
 3 {
 4    // treść
 5 }
 6 
 7 // proba 1
 8 funkcja('test');
 9    
10 // proba 2
11 $zmienna = 'test';
12 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órej potrzebny 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 PHP implementuje 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, PHP nie będzie tracić czasu na jej robienie!

Należy wystrzegać się także wywoływania funkcji ze zmuszaniem do przekazywania referencji, gdy funkcja sobie tego wyraźnie nie życzy: funkcja(&zmienna) - zapis taki był poprawny jeszcze w PHP 4, lecz w PHP 5 został wycofany i generuje ostrzeżenie.

Eval[edytuj]

Niektóre algorytmy pisane przez programistów PHP generują kod w tym języku, który następnie jest wykonywany. Można go zapisać do pliku i załadować instrukcją include, lecz nie zawsze jest to optymalne rozwiązanie. PHP oferuje swoim programistom instrukcję eval, która wykonuje podany w ciągu tekstowym kod jako skrypt PHP.

1 <?php
2 $zmienna = 5;
3 eval('echo $zmienna;');

Eval najpierw spowoduje kompilację ciągu echo $zmienna;, a następnie jego wykonanie, czego efektem będzie wyświetlenie się w przeglądarce wartości zmiennej. Nie nadużywaj tej własności zbyt często. Wykonywanie tworzonego przez nas kodu PHP jest ciekawą opcją, 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 praktycznie nigdy 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:

$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. W przypadku eval PHP musi natomiast po raz drugi uruchamiać kompilator.

Poprzedni rozdział: Inne elementy składni
Spis treści
Następny rozdział: Korzystanie z dokumentacji

Każdy popełnia błędy[edytuj]

Błąd w programowaniu jest wynikiem pomyłki/literówki w trakcie pisania aplikacji albo niedostrzeżeniem jakiejś cechy projektowanego algorytmu. Wielu początkujących programistów jest przerażonych, gdy na ekranie pojawią im się tajemnicze komunikaty, a w parze z tym idzie niezaradność. Dlatego w tym rozdziale pragniemy skupić się na zagadnieniu błędu. Omówimy komunikaty zgłaszane przez PHP, techniki pomagające zlokalizować i usunąć błędy, a także powiemy nieco o pakietach testowych od strony teoretycznej, ponieważ do praktyki pozostało nam wciąż parę zagadnień związanych ze składnią.

Komunikaty błędów PHP[edytuj]

Przetworzenie skryptu PHP składa się w istocie z dwóch faz:

  • Interpretacji - 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:

1 <?php
2 if(isset($zmienna)
3 {
4    echo $zmienna;
5 }

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

Jest to błąd składni powiązany z fazą kompilacji. PHP nie może skompilować skryptu, ponieważ w podanym pliku w linii 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 ), lecz zamiast tego przeszedł do kolejnej linijki, dostał { i zgłosił błąd. Po dodaniu brakującego nawiasu skrypt zaczyna poprawnie działać.

Po usunięciu pierwszego błędu postanowiliśmy rozbudować skrypt i wywołaliśmy funkcję inicjującą naszą aplikację WWW.

1 <?php 
2 if(isset($zmienna))
3 {
4    echo $zmienna
5    inicjujAplikacje();
6 }

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 5

Owo tajemnicze T_STRING jest tzw. tokenem. Kompilatory mają tendencję do ułatwiania pracy zarówno sobie, jak i programiście je tworzącemu. Wszystkie elementy pełniące identyczną funkcję, np. zmienne, grupowane są pod jedną wspólną nazwą zwaną tokenem. Tworzenie składni jest teraz bardzo proste. Jeżeli chcemy, aby w jakimś miejscu można było podać dowolną zmienną, używamy tokenu i kompilator już wie, co można tam umieszczać. Informacje o błędnym użyciu tokenu PHP wyświetla w sposób jawny, jak widać na powyższym przykładzie. Komunikat informuje nas teraz, że PHP natknął się na ciąg tekstowy, oczekując przecinka albo średnika. Spójrzmy linijkę wyżej - rzeczywiście, brakuje nam średnika. Mógłbyś zapytać - czemu ciąg tekstowy, skoro to jest funkcja? Kompilator po prostu nie dotarł jeszcze do występujących dalej nawiasów, więc wstępnie zaklasyfikował nazwę funkcji jako tekst. Gdyby udało mu się tam dotrzeć, połączyłby z nią nawiasy i powstał nowy token: T_FUNCTION.

1 <?php 
2 $zmienna = 'wartosc';
3 if(isset($zmienna))
4 {
5    echo $zmienna;
6    inicjujAplikacje();
7 }

Poprawiliśmy usterkę i zainicjowaliśmy zmienną $zmienna jakąś wartością, jednak nasz skrypt nadal nie działa. Tym razem mamy do czynienia z problemem innego kalibru:

Fatal error: Call to undefined function inicjujAplikacje() in /home/uzytkownik/www/kurs/aplikacja.php on line 5

Jest to błąd wykonywania skryptu. PHP poinformował nas w ten sposób, że dotarł do wywołania funkcji inicjujAplikacje(), lecz taka nie istnieje. Pozostało nam jedynie odkryć przyczynę tego stanu - może nie dołączyliśmy jakiejś ważnej biblioteki? Zwróć uwagę, że PHP sprawdza istnienie funkcji w momencie wykonywania skryptu, a nie kompilacji. Gdybyśmy nie zainicjowali zmiennej $zmienna, kod wewnątrz instrukcji if nie zostałby wykonany i problem pozornie by nie wystąpił. To oczywiście tylko złudzenie. On tam jest, lecz PHP nie wykonał tego kawałka kodu i nie wykrył problemu. Spróbuj wykonać taki eksperyment. Praktyka ta ma swoje uzasadnienie - przypomnijmy sobie instrukcję eval z końca poprzedniego rozdziału. Tam nazwa funkcji była ustalana w momencie wykonywania, gdyż tylko wtedy mają prawo istnieć jakiekolwiek zmienne.

Komunikaty Fatal error pojawiają się zawsze wtedy, gdy w trakcie wykonywania wystąpi problem uniemożliwiający dalszą pracę interpreterowi. Przykładem jest podanie do instrukcji require nazwy nieistniejącego pliku. PHP go nie odnajdzie i zatrzyma wykonywanie. Warto nadmienić, że użycie include spowoduje "tylko" pokazanie się ostrzeżenia, a skrypt będzie dalej wykonywany.

Innym rodzajem komunikatu o błędzie jest Warning, czyli ostrzeżenie. W odróżnieniu od Fatal error lub Parse error, Warning nie przerywa działania naszego skryptu. Przykładem pojawiania się tego komunikatu może być podanie nieprawidłowych parametrów w jakiejś funkcji:

1 <?php
2 $tekst = 'to jest jakiś tekst';
3 sort($tekst);

Widzimy, że zmienna $tekst nie jest tablicą, więc użycie tej zmiennej w funkcji sort(), która służy do sortowania tablic, wywołuje pojawienie się komunikatu o błędzie typu Warning:

Warning: sort() expects parameter 1 to be array, string given in /home/uzytkownik/www/kurs/aplikacja.php on line 3

Mówi on, że funkcja sort() jako parametr pierwszy może przyjąć tylko tablice, a dostała co innego.

Ostatnim z omówionych komunikatów błędów będą tzw. notices, czyli powiadomienia. Mają one niski priorytet i służą do informowania o miejscach, które potencjalnie mogą stanowić źródło problemu. Oto jedna z takich sytuacji:

1 <?php
2 echo $zmienna;

Skrypt próbuje tutaj wyświetlić zawartość zmiennej $zmienna, lecz nie jest ona zdefiniowana. Może to być zarówno świadome działanie (wiemy, że zmienna może w tym miejscu nie istnieć, ale dopuszczamy to) lub też wynik literówki w jej nazwie, co oczywiście niosłoby dla nas poważne konsekwencje. Dlatego interpreter na wszelki wypadek wyświetli w tym miejscu komunikat:

Notice: Undefined variable: zmienna in /home/uzytkownik/www/kurs/aplikacja.php on line 2

Podobny komunikat pojawi się, gdy spróbujemy odczytać wartość nieistniejącego elementu tablicy. Do dobrych zwyczajów należy takie pisanie skryptów, aby nie generowały one tego typu komunikatów z kilku powodów:

  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. 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. 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 je przed pierwszym użyciem domyślną wartością.

Operator @[edytuj]

Jeżeli dowolne wyrażenie PHP poprzedzimy znakiem @, interpreter nie wyświetli żadnego komunikatu, jeżeli wykryje w nim błąd. Nie oznacza to naturalnie, że błąd ten nagle znika. On tam jest, lecz my nie jesteśmy o tym powiadamiani. Dlatego należy bardzo rozważnie postępować z jego użyciem. @ przydaje się głównie przy funkcjach do komunikacji z zewnętrznymi źródłami danych, np. plikami. Jeżeli PHP wykryje np. brak żądanego pliku, nie tylko generuje określony wynik, ale także pokazuje komunikat Warning, co nie zawsze jest zjawiskiem pożądanym. Operatorem tym możemy także "zakazać" wyświetlania komunikatów Notice o niezdefiniowaniu zmiennej, jeżeli tego naprawdę potrzebujemy. Poniżej pokazany przykład ma za zadanie pokazać datę modyfikacji pliku lub nasz własny komunikat, jeżeli on nie istnieje. Na początek wariant najprostszy:

1 <?php
2 echo filemtime('plik.txt');

Kiedy plik.txt nie będzie istnieć, interpreter wygeneruje stosowny komunikat, którego my w takiej formie oglądać nie chcemy. Dlatego wcześniej sprawdzimy drugą funkcją istnienie pliku.

1 <?php
2 if(file_exists('plik.txt'))
3 {
4 	echo filemtime('plik.txt');
5 }
6 else
7 {
8 	echo 'Podany plik nie istnieje.';
9 }

Co dwie funkcje to nie jedna, lecz pojawia się nam tu problem wydajności. Odczyt czegokolwiek z twardego dysku stanowi duży narzut czasowy dla oprogramowania, dlatego powinniśmy się starać wykonywać jak najmniej takich operacji. Wiemy z dokumentacji, że samo filemtime() zwraca nam false, jeśli plik nie istnieje, przeszkadza nam tu jedynie "firmowe" ostrzeżenie. Dlatego właśnie skorzystamy z @, aby się go pozbyć:

 1 <?php
 2 $wynik = @filemtime('plik.txt');
 3 if($wynik !== FALSE)
 4 {
 5 	echo $wynik;
 6 }
 7 else
 8 {
 9 	echo 'Podany plik nie istnieje.';
10 }

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.

1 <?php
2 @echo filemtime('plik.txt') or die('Podany plik nie istnieje.');

Poziom raportowania błędów[edytuj]

Poziom raportowania błędów określa, na które zdarzenia PHP ma reagować wyświetleniem stosownego komunikatu. Docelowo ustawia się go w pliku konfiguracyjnym php.ini, w dyrektywie error_reporting. Podczas instalacji według tego podręcznika, zaleciliśmy użycie najwyższego możliwego poziomu E_ALL | E_STRICT, który zgłasza dosłownie wszystko. Dzięki temu możemy w porę reagować na każdy problem i mieć pewność, że po zainstalowaniu aplikacji na innym serwerze nie zostaniemy zasypani toną komunikatów. Jednak takie ustawienie należy stosować tylko w fazie tworzenia/testowania naszych skryptów. Gdy już umieścimy je na stronie internetowej, należy wyłączyć wyświetlanie wszystkich błędów, ponieważ takie komunikaty często ujawniają wiele informacji o skrypcie i używanym oprogramowaniu, które mogą ułatwić atak na nasz serwis.

Poziom raportowania można też tymczasowo zmieniać z poziomu skryptu funkcją error_reporting():

1 <?php
2 error_reporting(E_ALL ^ E_NOTICE);
3 echo 'Teraz komunikaty Notice nie działają: '.$nieistniejacaZmienna;

Funkcja ta ma jeszcze jedną użyteczną właściwość, mianowicie zwraca jako rezultat dotychczasowy poziom raportowania, dzięki czemu możemy go potem przywrócić:

1 <?php
2 $poprzedni = error_reporting(E_ALL ^ E_NOTICE);
3 echo 'Teraz komunikaty Notice nie działają: '.$nieistniejacaZmienna;
4 
5 error_reporting($poprzedni);
6 echo 'A teraz już tak: '.$nieistniejacaZmienna;

Techniki debugowania[edytuj]

Inny rodzaj błędów związany jest z algorytmami produkującymi niewłaściwe rezultaty. Odnaleźć przyczynę problemu jest tu znacznie trudniej, ponieważ nie jesteśmy raczeni żadnymi komunikatami i musimy sami dochodzić, co się właściwie stało. Jedynym sposobem jest poumieszczanie w kodzie na chwilę własnych informacji, które dokładnie powiadomią nas, które fragmenty są wykonywane w jakiej kolejności i z jakimi danymi. Metoda wywodzi się w prostej linii z języka C/C++, gdzie w kodzie umieszcza się tzw. asercje. Jeżeli kompilujemy kod programu w trybie debug (wykrywanie błędów), w oznaczanych przez nas miejscach dodawane są odpowiednie instrukcje sprawdzające poprawność danych i raportujące ewentualne problemy. Kiedy kompilujemy program w wersji "oficjalnej", preprocesor nie umieszcza już tych instrukcji w kodzie. Oprócz tego do wielu języków istnieją specjalne debugery, które pozwalają śledzić wykonywanie krok po kroku lub zatrzymać na wybranej linii, co przydaje się przy analizie złożonych algorytmów.

PHP żadnego preprocesora nie ma (można nawet powiedzieć, że sam nim jest dla języka HTML), dlatego sami musimy poumieszczać w kodzie odpowiednie wstawki. Najprostszą z nich jest echo 'Test';. Komendę taką umieszczamy, chcąc sprawdzić, czy algorytm dociera do danego miejsca. Po uruchomieniu, jeśli na ekranie pokaże nam się rzeczony napis, oznacza to, iż akurat to miejsce działa dobrze. Instrukcją echo możemy także wyświetlać dane lub sprawdzać kolejność wykonywania operacji, wstawiając różne komunikaty. Komenda die() nie tylko wyświetla komunikat, ale także zatrzymuje skrypt. Do wyświetlania zawartości tablic lub obiektów możemy zastosować funkcje var_dump() albo print_r(). Po usunięciu usterki wszystkie poczynione przez nas wstawki usuwamy z kodu.

PHP posiada specjalną funkcję obsługi asercji: assert(). Generuje ona komunikat błędu, jeżeli podane jej wyrażenie ma wartość false:

1 <?php
2 $zmienna = 3; 
3 assert($zmienna == 6);

Jednak to jeszcze nie wszystko. Musimy funkcją assert_options() włączyć to sprawdzanie:

1 <?php
2 assert_options(ASSERT_ACTIVE, 1);
3 assert_options(ASSERT_WARNING, 1);
4  
5 $zmienna = 3;
6  
7 assert($zmienna == 6);

Po skończeniu debugowania wcale nie musimy usuwać wywołań funkcji assert(). Wystarczy ustawić opcję ASSERT_ACTIVE na 0 i przestaje być ona aktywna.

Do debugowania skryptów PHP mogą być także przydatne zewnętrzne moduły w stylu Xdebug (dostępny w repozytorium PECL tj. http://pecl.php.net/package/Xdebug a nawet jako paczka w niektórych dystrybucjach Linuksa). Rozwiązania takie są dosyć ciekawe, ponieważ pokazują znacznie więcej użytecznych dla programisty informacji. Przykładowo, Xdebug w momencie wystąpienia błędu pokazuje całą listę wywołań funkcji nadrzędnych. Rozważmy sytuację, gdy pewnej funkcji używamy w kilku modułach, lecz co jakiś czas generuje ona błąd. Dzięki obecności listy możemy natychmiast określić, który moduł wywołuje ją nieprawidłowo. Xdebug można również skonfigurować do pracy z zewnętrznymi środowiskami IDE do tworzenia skryptów (np. PDT wchodzący w skład pakietu Eclipse).

Pakiety testowe[edytuj]

W praktyce okazuje się, że ręczne sprawdzanie aplikacji po każdej wprowadzonej zmianie bywa uciążliwe i jest naraż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ę one najczęściej do napisania dodatkowego zestawu testów, który wywołuje różne funkcje w określony sposób i sprawdza czy wynik ich działania zgadza się ze spodziewanym. Po wprowadzeniu większych zmian wystarczy jedynie uruchomić system testujący, którego wynikiem będzie raport, ile testów zostało pomyślnie zaliczonych i gdzie pojawiły się błędy.

Możemy wyróżnić dwa rodzaje testów:

  1. Testy funkcjonalne - sprawdzają funkcjonalność gotowej aplikacji, np. wypełniając formularz określonymi danymi i weryfikując odpowiedź programu.
  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 nie w samych funkcjach.

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

Poprzedni rozdział: Każdy popełnia błędy
Spis treści

Korzystanie z dokumentacji[edytuj]

Najważniejszym miejscem dla każdego programisty PHP jest dokumentacja projektu. Jest ona o tyle ważna, że informacja o nowościach w składni w pierwszej kolejności pojawia się właśnie tutaj. Źródła w rodzaju artykułów niekoniecznie są aktualizowane wraz z nowymi wersjami i nie można się na nich w pełni opierać. Dokumentacja przydaje się też przy poszukiwaniu nowych funkcji, modułów lub mało znanych możliwości.

Angielska wersja dokumentacji dostępna jest pod adresem www.php.net/manual/en. Istnieje także polska, lecz jest ona bardzo rzadko uaktualniana, a ilość i jakość przetłumaczonych rozdziałów pozostawiają nieco do życzenia. Przyjrzyjmy się zatem jej strukturze.

Dokumentacja PHP powstaje w oparciu o XML-ową aplikację DocBook, dlatego jej struktura jest bardzo uporządkowana. W całym tekście przyjęte są jednolite konwencje stylistyczne, a nawigacja jest intuicyjna. Kody źródłowe drukowane są na szarym tle, z kolorowaną składnią, jeżeli dotyczy ona źródeł PHP. W wielu miejscach umieszczone są uwagi, ostrzeżenia oraz porady. Na dole każdej strony znajduje się sekcja z komentarzami programistów. Naprawdę warto od niej zaczynać studiowanie dokumentacji, ponieważ często znajdują się tam o wiele ciekawsze przykłady użycia lub naprawdę przydatne sztuczki i algorytmy. Ilość komentarzy do najpopularniejszych funkcji jest ogromna i obejmuje niemal wszystko: własne implementacje, przykłady i komentarze odnośnie użycia, linki do artykułów oraz inne.

Instalacja PHP[edytuj]

Informacje dotyczące instalowania PHP znajdują się w rozdziale II. Opisane zostały tam przykłady oraz dodatkowe wiadomości dla wszystkich platform i serwerów. Nie wystarczą one jednak do skonfigurowania PHP do własnych potrzeb, ponieważ spis dyrektyw znajduje się gdzie indziej.

Spis dyrektyw konfiguracyjnych dla systemów Unix znajduje się w rozdziale IX ("Dodatki") i oznaczony jest literą "F": "Configure options". Nie zawiera on jednak spisu komend aktywujących poszczególne moduły. Te należy sobie odszukać na stronie poświęconej każdemu z nich.

Pozycję niżej znajduje się wykaz dyrektyw pliku php.ini, także bez listy modułów. Ogólnie wszystko, co związane z instalowaniem jakiegokolwiek rozszerzenia, znajduje się na poświęconej mu stronie dokumentacji w sekcji "Installation".

Składnia języka[edytuj]

Aby dowiedzieć się, jak przedstawia się składnia języka, należy zajrzeć do rozdziału "Language reference". Jest to typowy spis dostępnych komend, instrukcji oraz elementów wyrażeń ułożony tematycznie. Najbardziej przydatny będzie dla programistów mających już pewną wprawę w tworzeniu aplikacji oraz skryptów, gdyż opisy nie są tak przystępne, jak w kursach i książkach. Ich zadaniem jest dostarczenie wyczerpującej wiedzy o danym elemencie i wypisanie wszystkich możliwych zagadnień z nim związanych.

Rozdział poświęcony programowaniu obiektowemu jest zdublowany. W PHP 5 ten element został napisany całkowicie od nowa, tracąc część kompatybilności ze starymi wersjami i stąd konieczność podania dwóch osobnych opisów w zależności od posiadanej wersji. Do części dla PHP 4 polecamy już sięgać jedynie z powodów historycznych.

"Security" oraz "Features"[edytuj]

W tych dwóch rozdziałach znajdują się informacje praktyczne pokazujące niektóre aspekty charakterystyczne dla PHP. Tutaj możesz dowiedzieć się o:

  1. Ładowaniu plików na serwer
  2. Bezpieczeństwie skryptów
  3. Kilku niezbyt lubianych przez programistów opcjach: Register globals oraz Magic quotes.
  4. Metodach raportowania błędów
  5. Obsłudze ciastek i sesji
  6. Pisaniu skryptów dla tzw. trybu bezpiecznego wykorzystywanego na produkcyjnych serwerach.

Spis funkcji[edytuj]

Jest to jeden z najważniejszych rozdziałów dla programisty. Zawiera spis wszystkich funkcji pogrupowanych według modułów. Na początku każdego podrozdziału znajduje się skrótowy opis rozszerzenia, wymagania oraz sposób jego zainstalowania. Należy tu zwrócić uwagę na następujące frazy:

No external libraries are needed to build this extension.

To rozszerzenie nie wymaga obecności żadnych dodatkowych bibliotek w systemie.

There is no installation needed to use these functions; they are part of the PHP core.

Funkcje te są częścią jądra PHP i nie da się ich wywalić. Są zatem zawsze dostępne.

The SimpleXML extension is enabled by default. To disable it, use the --disable-simplexml configure option.

Komunikat w tym stylu oznacza, że domyślnie rozszerzenie to jest zawsze aktywne, ale istnieje możliwość jego wyłączenia.

Następnie na stronie znajduje się kilka prostych przykładów i lista stałych definiowanych przez dany moduł. Na końcu wyszczególniony jest alfabetyczny spis wszystkich funkcji modułu wraz z lakonicznie zaznaczonym działaniem. Aby dowiedzieć się więcej, należy po prostu kliknąć na wybraną funkcję.

Opis funkcji zaczyna się od składni. Wygląda on mniej więcej tak:

bool setcookie ( string name [, string value [, int expire [, string path [, string domain [, bool secure]]]]] )

Zaczyna się on od nazwy typu danych zwracanego przez funkcję. W dokumentacji stosowana jest następująca konwencja:

  • int, string, float, bool - skalarne typy danych: liczby, ciągi tekstowe, wartości logiczne.
  • array - tablica
  • mixed - funkcja może zwrócić jeden z kilku rodzajów typów. W tym wypadku trzeba samodzielnie doczytać w opisie, co jest zwracane w jakich sytuacjach.

Jeżeli funkcja zwraca obiekt, podawana jest jego nazwa.

W nawiasach okrągłych podany jest spis parametrów, również z zaznaczonymi typami. Nawiasy kwadratowe oznaczają parametry opcjonalne. Z podanego powyżej przykładu wynika, że możemy podać:

  1. nazwę
  2. nazwę i wartość
  3. nazwę, wartość i czas wygaśnięcia
  4. nazwę, wartość, czas wygaśnięcia i ścieżkę
  5. nazwę, wartość, czas wygaśnięcia, ścieżkę i domenę
  6. nazwę, wartość, czas wygaśnięcia, ścieżkę, domenę oraz nakaz wysłania ciastka połączeniem szyfrowanym

Dokładniejsze informacje, co robi jaki parametr, znajdują się w opisie funkcji. Kończy się on sekcją "See also" odsyłającą do funkcji podobnych lub uzupełniających zawarte tu informacje.

W przypadku nowych, obiektowych rozszerzeń, nazwa funkcji jest dwuczęściowa: obiekt::funkcja.

Do PHP stworzono już bardzo dużo modułów i ogrom ich spisu może przerazić. Oto krótki przewodnik, gdzie co można znaleźć:

  1. Arrays - wszystko, co związane z tablicami
  2. ctype - sprawdzanie zawartości ciągów tekstowych
  3. Exif - wyciąganie dodatkowych informacji z plików JPG i TIFF
  4. Filesystem - operacje na plikach
  5. Function handling - zarządzanie funkcjami
  6. HTTP - kilka funkcji do protokołu HTTP (np. wysyłanie ciastek)
  7. Image functions - biblioteka GD do generowania obrazków
  8. Math - funkcje matematyczne
  9. Misc. - funkcje niepasujące nigdzie indziej (np. kolorowanie składni)
  10. MySQL - najstarszy zbiór funkcji do komunikacji z bazą MySQL
  11. mysqli - "Improved MySQL", komunikacja z nowymi wersjami bazy MySQL.
  12. Network - funkcje sieciowe
  13. Output Control - buforowanie wyjścia skryptu
  14. 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 tekstowych
  17. Variables handling - rozpoznawanie typów zmiennych itd.

Zachęcamy do przejrzenia tych rozdziałów i zorientowania się w ich strukturze. Dobra orientacja w dokumentacji PHP sprawia, że nie trzeba nawet przykładać dużej wagi do "wkuwania" funkcji na pamięć.

Poprzedni rozdział: Korzystanie z dokumentacji
Spis treści
Następny rozdział: Ćwiczenia

Studium przypadku: Księga gości[edytuj]

Dotychczasowe rozdziały miały w sobie więcej teorii i nie ukazywały prawdziwej istoty tworzenia skryptów dla stron WWW. Najlepszym rodzajem nauki jest praktyczne stworzenie skryptu z prawdziwego zdarzenia i pokazanie w ten sposób, jak poszczególne elementy języka ze sobą się łączą. W tym rozdziale stworzymy samodzielnie funkcjonalną księgę w PHP. Wykorzystamy w niej pętle, tablice, instrukcje warunkowe, formularze oraz dołączanie plików zewnętrznych. Ponadto poznamy kilka interesujących funkcji.

Plan[edytuj]

Księgę gości oprzemy o pliki tekstowe. Każdy wpis będzie charakteryzowany przez:

  • Tytuł
  • Autora
  • Datę
  • Adres WWW (opcjonalny)
  • Treść

Wszystkie te dane zostaną zapisane w pliku w pojedynczym wierszu, a oddzielone będą znakiem "|". Aby wyeliminować sytuację, gdy ktoś wpisze np. w treść taki znak, albo (co gorsza) naciśnie enter, każda kolumna zostanie zakodowana w formacie Base64. Oczywiście nie będziesz musiał sam pisać odpowiedniego algorytmu - PHP posiada odpowiednie funkcje. Base64 został pierwotnie zaprojektowany do przesyłania 8-bitowych wiadomości w 7-bitowych protokołach. Do zapisu są tu używane wyłącznie litery, cyfry, ukośnik oraz znak równości, zatem nie ma obawy o uszkodzenie formatu pliku księgi. Zakodowana wiadomość jest o ok. 33% dłuższa od oryginału.

Księga nie będzie zapisywać i odczytywać z pliku bezpośrednio. W zewnętrznym pliku napiszemy sobie dwie funkcje zajmujące się wyłącznie tym. Księga będzie miała dostęp do danych jedynie za ich pomocą. Rozwiązanie to jest bardzo użyteczne. Kiedy poznasz już bazy danych, z pewnością zapragniesz przesiąść się właśnie na nie. Nie będziesz musiał przepisywać całej księgi. Po prostu napiszesz do tych dwóch funkcji nowy kod. Dzięki takiemu rozwiązaniu źródła skryptu będą uporządkowane i czytelne, a ponadto bardzo łatwe do modyfikacji.

dane.php[edytuj]

Plik dane.php zawierać będzie dwie funkcje: dodajWpis() oraz pobierzWpisy() zajmujące się wyłącznie operowaniem na danych. Będziemy go pisać po kawałku.

<?php
 	define('WPISY', './wpisy.txt');
 
 	function dodajWpis($tytul, $autor, $www, $tresc)
 	{
 		// Ucinanie bialych znakow
 		$tytul = trim($tytul);
 		$autor = trim($autor);
 		$www = trim($www);
 		$tresc = trim($tresc);

Nazwę pliku zapisaliśmy za pomocą stałej. To na wypadek, gdyby zachciało nam się go kiedyś przenieść albo zmienić mu nazwę. Pierwszy etap obrabiania danych to ucięcie białych znaków (spacji, tabulatorów itp.) z początku i końca każdego ciągu. Nie są nam one do niczego potrzebne, a jedynie utrudniają nam sprawę.

		// Kontrola danych
 		
 		if(strlen($tytul) < 3)
 		{
 			return false;
 		}
 		
 		if(strlen($autor) < 3)
 		{
 			return false;
 		}
 		
 		if(strlen($tresc) < 10)
 		{
 			return false;
 		}
 
 		if(strlen($www) > 0)
 		{
 			// Jesli adres nie zaczyna sie od http:// to dodaj to
 			if(strpos($www, 'http://') !== 0)
 			{
 				$www = 'http://'.$www;
 			}		
 		}

Tutaj zajmujemy się kontrolą danych. Tytuł i autor muszą mieć przynajmniej trzy znaki, a treść 10. Długość pobieramy funkcją strlen(). Jeżeli podaliśmy adres WWW, za pomocą funkcji strpos() sprawdzamy, czy na jego początku jest na pewno dodany identyfikator protokołu. Zwróć uwagę na użyty operator: !==. Wspomniana funkcja zwraca pozycję pierwszego znaku szukanego ciągu (liczoną od zera), a jeżeli go nie znajdzie, zwraca false. Normalnie 0 i false są równoważne, dlatego musimy wymusić sprawdzenie także typu, bowiem nam chodzi o 0 jako pozycję identyfikatora w adresie, a nie informację, że go nie ma.

Jeżeli któraś z informacji nie będzie prawidłowo podana, funkcja zwróci do skryptu wartość false.

		// Dodawanie
 	
 		$f = fopen(WPISY, 'a');
 		
 		$dane = array(0 =>
 			base64_encode(htmlspecialchars($tytul)),
 			base64_encode(htmlspecialchars($autor)),
 			time(),
 			base64_encode(htmlspecialchars($www)),
 			base64_encode(nl2br(htmlspecialchars($tresc)))		
 		);
 		
 		fwrite($f, implode('|', $dane)."\r\n");
 		fclose($f);
 		return true;
 	} // end dodajWpis();

Ostatni akord to zapisanie wpisu w pliku. Na początek otwieramy go w trybie dopisywania (parametr a), następnie budujemy tablicę z danymi wpisu. Parametry przybyłe z formularza kodujemy w Base64, środkowa kolumna to czas dodania wpisu w sekundach od 1.1.1970. Dzięki takiemu jego zapisowi, będziemy go mogli później dowolnie formatować funkcją date(). Przy przetwarzaniu tekstu użyliśmy jeszcze kilku funkcji:

  • htmlspecialchars() - wszystkie wprowadzone tagi HTML są zamieniane na zwykły tekst (ograniczniki są zastępowane odpowiadającymi im encjami, np. < zmienia się w &lt;). W ten sposób nikt nie rozwali nam księgi złośliwym kodem.
  • nl2br() - zamienia znaki nowej linii na znaczniki <br/>.
  • implode() - łączy tablicę w ciąg tekstowy, wstawiając pomiędzy poszczególne elementy podany w pierwszym parametrze znak. Na końcu utworzonego przez nią rekordu wpisu musimy dodać samodzielnie znaki zejścia do nowej linii: \r\n, koniecznie w cudzysłowach, a nie w apostrofach.

Zwróć uwagę na rozbudowane wywołania funkcji:

base64_encode(nl2br(htmlspecialchars($tresc)))

Oznacza ona, że najpierw zawartość zmiennej $tresc trafia do funkcji najbardziej w prawo, tj. htmlspecialchars(). Z niej przechodzi do nl2br(), a z niej do base64_encode(). Alternatywne kursy preferują w tym miejscu czytelną, ale mniej wydajną formę zapisu:

$tresc = htmlspecialchars($tresc);
$tresc = nl2br($tresc);
$tresc = base64_encode($tresc);

Nasz kod ma tę przewagę, że wynik jednej funkcji od razu trafia do drugiego, tymczasem powyżej po drodze trafia do zmiennej, co ma szczególnie negatywny wpływ na wydajność przy dużych danych. Ponadto zapis ten często nie jest opatrzony stosownym komentarzem i wyrabia złe nawyki tworzenia mnóstwa niepotrzebnych zmiennych tymczasowych, o czym już wspominaliśmy. Trzeba zapamiętać, że nawet podawany przez nas wariant konstrukcji księgi nie jest "tym jedynym słusznym". Istnieje jeszcze wiele innych sposobów jej zaprogramowania.

Przyszła kolej na funkcję pobierającą wpisy.

	function pobierzWpisy()
 	{
 		$wpisy = array_reverse(file(WPISY));
 		
 		$i = 1;
 		$rezultat = array();

Wpisy pobieramy funkcją file(), która automatycznie rozbija nam plik na tablicę względem wierszy. Tam jednak najnowsze wpisy są na końcu, a my chcemy je wyświetlić w kolejności odwrotnej: najnowsze na górze. Dlatego od razu przepuszczamy wynik przez funkcję array_reverse() odwracającą tablicę. Następnie inicjujemy licznik wpisów $i oraz tablicę $rezultat, do której będziemy pakować sformatowane odpowiednio wpisy. Ją później przekażemy księdze do wyświetlenia.

		foreach($wpisy as $wpis)
 		{
 			$wpis = explode('|', trim($wpis));
 			
 			$rezultat[] = array(
 				'id' => $i,
 				'tytul' => base64_decode($wpis[0]),
 				'autor' => base64_decode($wpis[1]),
 				'data' => date('d.m.Y, H:i', $wpis[2]),
 				'www' => base64_decode($wpis[3]),
 				'tresc' => base64_decode($wpis[4])		
 			);
 			$i++;		
 		}
 		return $rezultat;	
 	} // end pobierzWpisy();
 
 ?>

Po kolei formatujemy każdy wpis. Do ich pobierania wykorzystaliśmy pętlę foreach(). Na wejściu przepuszczamy je przez funkcję trim(), bowiem file() pozostawia w tablicach znaki zejścia do nowej linii. Dopiero po ich usunięciu możemy funkcją explode() rozbić ciąg z powrotem na tablicę (czyli wykonać operację odwrotną do implode()). Wpis przepuszczamy przez base64_decode(), aby zdekodować informacje, formatujemy datę i pakujemy to do tablicy $rezultat. Później zwracamy ją.

ksiega.php[edytuj]

W pliku tym znajdzie się główna część funkcjonalna księgi. To jego powinniśmy uruchamiać z poziomu przeglądarki. Zaczynamy jego pisanie od nagłówka HTML oraz dołączenia uprzednio napisanego pliku:

<!DOCTYPE html 
	PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="pl" lang="pl">
   <head>
   <meta http-equiv="content-type" content="text/html; charset=utf-8">
   <title>Księga gości Wikibooks</title>
   </head>
   <body>
   	<h1>Księga gości Wikibooks</h1>
 <?php
 
 	require('./dane.php');

Właściwy kod rozpocznie się instrukcją warunkową, która zadecyduje, czy wysłany został formularz dodawania, czy też wystarczy wyświetlić aktualną zawartość księgi. PHP zapisuje informację o użytej metodzie wysyłania żądania w specjalnej zmiennej $_SERVER['REQUEST_METHOD'], która może przyjąć wartości POST (formularz) albo GET (zwyczajne żądanie). Z niej właśnie skorzystamy:

	if($_SERVER['REQUEST_METHOD'] == 'POST')
 	{
 		// Dodawanie wpisu
 		if(dodajWpis($_POST['tytul'], $_POST['autor'], $_POST['www'], $_POST['tresc']))
 		{
 			echo '<p>Dziękujemy, wpis został dodany prawidłowo.</p>';
 		}
 		else
 		{
 			echo '<p>Proszę wypełnić prawidłowo formularz.</p>';
 		}
 		echo '<p><a href="ksiega.php">Powrót</a></p>';	
 	}

Kontrola danych jest realizowana w pliku dane.php i nie ma potrzeby jej tu powtarzać. Wprowadzamy do funkcji dodajWpis() poszczególne pola formularza i ifem sprawdzamy, jaki jest rezultat, aby móc wygenerować stosowny komunikat.

Jeżeli nie dodajemy aktualnie żadnego nowego wpisu, wyświetlamy te, które już mamy:

	else
 	{
 		// Wyświetlanie wpisów
 		$wpisy = pobierzWpisy();
 		
 		foreach($wpisy as $wpis)
 		{
 			echo '<hr /><p><b>Tytuł: <i>'.$wpis['tytul'].'</i>; 
 				Autor: '.$wpis['autor'].'; Data: '.$wpis['data'];
 			if(strlen($wpis['www']) > 0)
 			{
 				echo '; <a href="'.$wpis['www'].'" target="_blank">Strona WWW</a>';
 			}
 			echo '</b></p>';
 			echo '<p>'.$wpis['tresc'].'</p>';		
 		}
 		
 ?><hr />

Tablicę z wpisami dostajemy z funkcji pobierzWpisy(). Skanujemy ją pętlą foreach i wyświetlamy każdy z elementów. Zauważ, że skrypt potrafi ominąć pole z adresem WWW, jeśli ten nie został podany. Po prostu funkcją strlen() sprawdzamy, czy jego długość jest większa od zera.

Ostatnim akordem będzie dodanie formularza HTML:

 <form method="post" action="ksiega.php">
 <table border="0" width="50%">
 	<tr>
 		<td>Tytuł</td>
 		<td><input type="text" name="tytul"/></td>
 	</tr>
 	<tr>
 		<td>Autor</td>
 		<td><input type="text" name="autor"/></td>
 	</tr>
 	<tr>
 		<td>WWW</td>
 		<td><input type="text" name="www"/></td>
 	</tr>
 	<tr>
 		<td>Treść</td>
 		<td><textarea name="tresc" rows="4" cols="50"></textarea></td>
 	</tr>
 	<tr>
 		<td></td>
 		<td><input type="submit" value="Dodaj"/></td>
 	</tr>
 </table>
 </form>
 <?php
 	
 	}
 
 ?>
   </body>
 </html>

Formularz wysyłamy metodą POST do pliku ksiega.php. Jego wysłanie spowoduje uaktywnienie się dodawania wpisów. Przed wysłaniem ostatnich znaczników musimy dodać jeszcze klamrę kończącą instrukcję warunkową, która decyduje, co należy wykonać. Umieszczenie jej w tym miejscu gwarantuje, że formularz pokaże się tylko podczas wyświetlania wpisów. Jeśli skrypt będzie wysyłać komunikat o dodaniu wpisu, nie zostanie on już dołączony.

To już koniec naszej księgi gości. Utwórz teraz pusty plik wpisy.txt i zacznij korzystanie.

Co dalej?[edytuj]

Napisana tutaj księga nie jest wyrafinowanym szczytem techniki. Jej głównym zadaniem było pokazanie podstawowych mechanizmów tworzenia dynamicznych stron WWW w praktyce. Wraz z powiększaniem się twojej wiedzy o języku PHP możesz rozszerzać ją o nowe możliwości:

  • Blokadę przed floodem.
  • Kasowanie wpisów.
  • Dzielenie wyników na strony.
  • Łatwy w modyfikacji wygląd.

Niektóre z tych rzeczy będą znacznie łatwiejsze do wykonania, kiedy poznasz zasady pracy z bazami danych dającymi nieporównywalnie większe możliwości, niż pliki tekstowe. Na bazach danych oparta jest obecnie zdecydowana większość istniejących aplikacji internetowych. Dzięki temu, że odseparowaliśmy pobieranie danych od ich wyświetlania, przepisanie księgi na MySQL będzie jedynie zabiegiem kosmetycznym. Zdecydowanie polecamy korzystanie z takiego rozwiązania, gdyż daje programiście sporą elastyczność. Nie zawsze bowiem zdarza się, że organizacja naszych danych będzie dokładnie taka, jakiej życzyłby sobie klient. Programując wszystko "na sztywno" wraz z wyglądem i kodem funkcjonalnym aplikacji narażamy się na większe ryzyko popełnienia błędu, zwiększamy rozmiar kodu i czynimy go mniej czytelnym.

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

Poprzedni rozdział: [[../../Studium przypadku - Księga gości/]]
Spis treści
Następny rozdział: [[../../Przetwarzanie tekstu/]]

Ćwiczenia[edytuj]

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

[[../../Odpowiedzi/Podstawy języka|Odpowiedzi]]

Zasada działania PHP[edytuj]

  1. PHP jest językiem skryptowym. Co to oznacza?
  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. Co powinien generować skrypt PHP uruchamiany na serwerze WWW?

Podstawy języka[edytuj]

Szybkie 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 przy printf musi?
  3. Objaśnij różnicę w działaniu cudzysłowów i apostrof.
  4. Do czego służą zmienne? Jakimi atrybutami można je opisać?
  5. Wskaż niepoprawne nazwy zmiennych:
    • $_
    • $zmienna
    • $Napis383
    • $dZi33cI_N30
    • $15newsow
    • $gżegżółka
    • $_i
  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ą one niepotrzebne. Wskaż "błędną" sytuację oraz objaśnij, dlaczego w jednym przypadku zmienna tymczasowa jest pożądana, a w drugim nie?

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 warunkowe[edytuj]

Szybkie 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. Opisz słownie, co opisują wyrażenia oraz ich odwrotności z poprzedniego ćwiczenia.

Funkcje[edytuj]

Poniżej pokazany jest fragment pewnego skryptu. Podana funkcja ma dodawać formatowanie do podanego w argumencie ciągu tekstowego, lecz nie działa zgodnie z zamierzeniami autora. Znajdź błąd i zaproponuj jego rozwią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');
echo '<ul>';
foreach($dane as $linia)
{
   $wiersz = explode('|', trim($linia));
   echo '<li>'.formatuj($wiersz[0]).': '.$wiersz[1].'</li>';
}
echo '</ul>';

Ćwiczenia praktyczne[edytuj]

Ć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óch wariantach: rekurencyjnym oraz przy użyciu pętli for. Spróbuj przy pomocy każdej z nich policzyć silnię z 50 oraz 105. 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ż w pełni wystarczająca, aby stworzyć taki system. Twoim zadaniem jest napisanie funkcji o następujących wł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 w adresie 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.

Rozmaitości[edytuj]

Poprzedni rozdział: Ćwiczenia
Spis treści
Następny rozdział: Podstawy wyrażeń regularnych

Przetwarzanie tekstu[edytuj]

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

Wyszukiwanie ciągów[edytuj]

Podstawową funkcją wyszukiwania jednego ciągu w drugim jest strpos() posiadająca także wariant stripos(), w którym nie gra roli wielkość liter. Użyliśmy jej już raz podczas pisania księgi gości do sprawdzenia, czy wprowadzony adres WWW zawiera identyfikator protokołu. Przypomnijmy jeszcze raz ten skrypt:

<?php
 
 	$adresy = array(0 =>
 		'http://www.wikibooks.pl',
 		'www.wikibooks.pl'	
 	);
 	
 	foreach($adresy as $adres)
 	{
 		if(strpos($adres, 'http://') === 0)
 		{
 			// jest http://
 			echo '<p>'.$adres.'</p>';
 		}
 		else
 		{
 			// nie ma http://
 			echo '<p>http://'.$adres.'</p>';
 		}	
 	}
 
?>

Funkcje działają następująco: za pierwszy parametr podajemy ciąg do przeszukania, za drugi - szukany, a następnie patrzymy na rezultat. strpos() powinien zwrócić nam pozycję, od której zaczyna się tekst, którego szukamy, przy czym znaki liczone są od zera. Jeżeli nic nie zostanie znalezione, dostaniemy wartość false. Z tego powodu powinniśmy zawsze wykorzystywać te funkcje z operatorami === oraz !== sprawdzającymi nie tylko wartość, ale i typ zwracanych danych. Oto interesująca sztuczka, która w jednej linijce pozwoli nam na sprawdzenie, czy ciąg został znaleziony i EWENTUALNE pobranie pozycji jego wystąpienia:

<?php
 
 	$zrodlo = 'Litwo, ojczyzno moja!';
 	$szukany = 'moja';
 	
 	if(($id = strpos($zrodlo, $szukany)) !== false) // wlasnie tu
 	{
 		echo 'Szukany ciąg zaczyna się w znaku '.$id;
 	}
 	else
 	{
 		echo 'Nie znaleziono szukanego ciągu';
 	}
 
?>

Jeśli nie rozumiesz zastosowanej tu sztuczki, przypomnij sobie zabawy z wyrażeniami na początku podręcznika. PHP najpierw wykona przypisanie do zmiennej $id: $id = strpos($zrodlo, $szukany). Operator zwróci przypisywaną wartość, która zostanie wykorzystana z kolei do sprawdzenia, czy coś zostało znalezione: [wyrażenie] !== false. W ten sposób mamy jednocześnie wykonane i sprawdzenie, i pozycję pierwszego wystąpienia w zmiennej $id.

Powyższe dwie funkcje znajdują pierwsze wystąpienie szukanego ciągu. Jeżeli chcemy znaleźć ostatnie, musimy posłużyć się funkcjami strrpos() i strripos().

Modyfikacja ciągów[edytuj]

Zajmiemy się teraz dosyć rozległą kwestią modyfikacji oraz obróbki tekstu. Jest ona niezwykle ważna, jeżeli zamierzasz pisać aplikacje typu forum dyskusyjnego czy księgi gości. Z wiadomych przyczyn nigdy nie powinniśmy zezwolić internaucie na wypisywanie wszystkiego, co mu się podoba i musimy poddać jego tekst elektronicznej obróbce. Za jej pomocą można również ułatwiać formatowanie tekstu. Przykładowo, treść źródłowa tego podręcznika zapisana jest w specjalnym zestawie znaczników dostosowanym do specyfiki Wikibooks. Odpowiednia biblioteka zajmuje się jego przetwarzaniem na język HTML. Tak złożonymi kwestiami nie będziemy się tu jednak zajmować. Wspomniane zostaną przede wszystkim te funkcje dostępne w PHP. Bardziej zaawansowane algorytmy musisz już zaprogramować samodzielnie.

Nagłówki na stronach internetowych przybierają różne style, w zależności od pełnionych funkcji. Część z nich pisana jest kapitalikami, w innych z dużych liter zaczyna się każdy wyraz. Wcale nie musimy zmuszać redaktorów, by to oni zajmowali się wprowadzaniem tytułów w wymaganej postaci. Istnieją odpowiednie algorytmy, które wykonają to za nich. Choć obecnie zadanie sformatowania tytułów można z powodzeniem zrealizować już z wykorzystaniem CSS-a, PHP posiada szereg funkcji do konwersji dużych liter na małe. Pamiętajmy wszak, że PHP to nie tylko strony internetowe.

<?php
 
 	$tekst = 'php jest językiem programowania skryptowego zaprojektowanego dla stron internetowych';
 	
 	echo '<p>Kapitaliki: '.strtoupper($tekst).'</p>';
 	echo '<p>Dużą literą: '.ucfirst($tekst).'</p>';
 	echo '<p>Każdy wyraz dużymi literami: '.ucwords($tekst).'</p>';
?>

Mamy tutaj tekst zapisany w zmiennej (symulującej jakieś źródło danych, ponadto wykorzystamy go kilkakrotnie, stąd ta zmienna) złożony z samych małych liter. Za pomocą funkcji strtoupper() zamieniamy je wszystkie na duże. Odwrotną operację wykonuje strtolower(). ucfirst() zamienia na dużą literę początek pierwszego wyrazu ciągu, a ucwords() - początek każdego wyrazu. Doświadczenie podpowiada nam, że otrzymywane dane nie zawsze są tak klarowne. Jeżeli potrzebne nam automatyczne kapitalizowanie początków zdań, musimy sami napisać odpowiedni algorytm.

Jeżeli chcemy wyciąć fragment jednego ciągu, aby np. poddać go bardziej szczegółowej obróbce, z pomocą przychodzi nam substr(). Podajemy w niej pozycję, od której zamierzamy ciąć oraz (opcjonalnie) ilość znaków do pobrania. Domyślnie funkcja kończy wycinanie na końcu ciągu. W przykładzie połączymy ją z funkcją strpos() do wycię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ę. Przekazujemy ją do substr() jako punkt startowy wycinania i pobieramy ilość znaków równą długości personaliów minus pozycji spacji. W ten sposób w nasze ręce wpadnie nazwisko i będziemy mogli zrobić z nim, co tylko chcemy. Alternatywny sposób rozwiązania tego problemu polega na rozbiciu tego ciągu na tablicę poznaną już funkcją explode(). Przydaje się on, kiedy oprócz nazwiska pragniemy otrzymać także drugi ciąg z imieniem - tu pasuje ona, jak znalazł.

Teraz coś bardziej praktycznego. Na wielu witrynach internetowych można spotkać emotikonki zamieniane na obrazki. Jeżeli nie interesują nas żadne dodatkowe fajerwerki, napisanie konwertera jest bajecznie proste. PHP posiada funkcję str_replace() zamieniającą jeden fragment ciągu na drugi (jej wariant, str_ireplace() nie rozróżnia wielkości liter).

<?php
 
 	$post = 'Tak, zaiste jesteś bardzo zdolny :). Jeszcze nad praktyką popracuj :].';
 
 	// Pierwszy sposób zamiany
 	
 	echo '<p>'.str_replace(':)', '<img src="smile.gif"/>', $post).'</p>';
 	
 	// Drugi, kilka naraz
 	
 	echo '<p>'.str_replace(array(
 		':)',
 		':]',
 		':('	
 	), array(
 		'<img src="smile.gif"/>',
 		'<img src="eye.gif"/>',
 		'<img src="sad.gif"/>'	
 	), $post).'</p>';
 
?>

Pierwszym parametrem jest szukany ciąg, drugim - ciąg docelowy, a trzecim - ciąg, na którym operujemy. Możemy zdefiniować tylko jeden wzorzec do podmiany albo całą grupę, którą podajemy w postaci tablic, jak na przykładzie. Zwróć uwagę, że w przeciwieństwie do omawianego wcześniej substr(), tutaj ciąg, na którym operujemy, wskazywany jest dopiero na końcu.

Zajmując się księgą gości, dbaliśmy, aby do naszych wpisów nie przedostał się HTML. Funkcja htmlspecialchars() zamieniała wtedy znaki specjalne HTML-a na encje, przez co nie mogły być one przetworzone i pojawiały się we wpisie jako statyczny tekst. Okazuje się, że nie jest to jedyne dostępne nam rozwiązanie. Możemy znaczniki wyrzucić całkowicie. Różne warianty prezentuje poniższy przykład:

<?php
 
 	$post = '   To jest post
 	kilkulinijkowy. Który należy przerobić tak,
 	aby go <b>fajnie wyświetlać</b>';
 	
 	// Wariant 1
 	
 	echo '<p>'.nl2br(htmlspecialchars(trim($post))).'</p>';
 	
 	// Wariant 2
 	
 	echo '<p>'.nl2br(strip_tags(trim($post))).'</p>';
 
?>

Zarówno strip_tags(), jak i trim() mają pewne ciekawe dodatkowe parametry, o których nie wszyscy wiedzą. Przy wycinaniu znaczników możemy określić, które z nich mają być zostawiane, np. strip_tags($tekst, '<b><i><u><a>') pozostawi nam pogrubianie, kursywę, podkreślanie oraz linki. trim() natomiast niekoniecznie musi wycinać białe znaki - wystarczy, że podamy nasz własny zestaw jako drugi parametr, a możemy oszczędzić sobie dużo pracy.

ASCII[edytuj]

Kiedy powstawały komputery, powstała konieczność stworzenia uniwersalnego standardu kodowania znaków alfabetu za pomocą kodów liczbowych rozumianych przez maszyny. Umożliwiłoby to pisanie dokumentów, które da się przenosić między maszynami bez konieczności ich konwersji. Tak narodził się siedmiobitowy standard ASCII zawierający 128 kodów. Jego cechą szczególną było istnienie tzw. kodów sterujących (od 0 do 32), dzięki którym można było nawet sterować pracą niektórych urządzeń. W latach późniejszych wykorzystano także ósmy bit (kody od 128 do 255). Jego wykorzystanie różni się w zależności od wariantu systemu kodowania.

PHP udostępnia kilka funkcji ułatwiających pracę z ASCII. Na początku zapoznamy się z funkcjami chr() oraz ord() konwertujących kod na odpowiadający mu znak i vice versa. Oto prosty skrypt , wykonujący to zadanie i pobierający dane z formularza HTML.

<?php
 
 	if($_SERVER['REQUEST_METHOD'] == 'POST')
 	{ // 1
 		if($_POST['kierunek'] == 0) // 2
 		{
 			if(strlen($_POST['dane']) != 1)
 			{
 				die('<p>Nieprawidłowe dane. Proszę podać JEDEN znak!</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>';	
 	}
?>

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. 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. Formularz do komunikacji ze skryptem.

W powyższym przykładzie wykorzystaliśmy funkcję ctype_digit(), która szybko bada, czy podany ciąg składa się wyłącznie z liczb. Istnieje kilkanaście funkcji z serii ctype. Oto kilka z nich:

  • ctype_alpha($tekst) - sprawdza, czy tekst składa się wyłącznie z liter.
  • ctype_lower($tekst) - sprawdza, czy tekst składa się wyłącznie z małych liter.
  • ctype_upper($tekst) - sprawdza, czy tekst składa się wyłącznie z dużych liter.
  • ctype_xdigit($tekst) - sprawdza, czy tekst zawiera wyłącznie znaki do zapisu liczb w systemie szesnastkowym (heksadecymalnym).

Jeśli żądany przez Ciebie zestaw znaków nie jest udostępniany przez żadną z funkcji, możesz skorzystać z odpowiedniego algorytmu:

<?php
 
 	function ctype($ciag, $zestaw)
 	{
 		for($i = 0; $i < strlen($ciag); $i++)
 		{
 			if(strpos($zestaw, $ciag{$i}) === FALSE)
 			{
 				return false;
 			}
 		}
 		return true;		
 	} // end ctype();
 
?>

Algorytm jest bardzo prosty. Przeszukujemy wprowadzony ciąg znaków $ciag i sprawdzamy funkcją strpos(), czy każdy jego znak zawiera się w zestawie $zestaw. Jeśli nie, przerywamy pracę i zwracamy false. Oto przykładowe użycie:

echo ctype('555-1234', '0123456789-');

Jeżeli poszukujesz jeszcze bardziej złożonych związków między znakami, musisz posłużyć się wyrażeniami regularnymi, z których podstawami zaznajomi Cię następny rozdział.

Formatowanie tekstu[edytuj]

Korzenie PHP znajdują się w języku C, więc nic dziwnego, że dziedziczy on po nim funkcję printf() służącą do prezentacji sformatowanych danych.

<?php
 
 	printf('Liczba szesnastkowa: %x', 3342);
 
?>

Funkcja odnajduje w podanym ciągu specjalne kody formatujące rozpoczynające się od znaku procentu i umieszcza na ich miejscu dane z kolejnych parametrów. W powyższym przykładzie zastosowaliśmy kod %x, który spowoduje wyświetlenie się liczby 3342 w systemie szesnastkowym. Przeglądarka wyświetli zatem:

Liczba szesnastkowa: d0e

Za pomocą kodów formatujących możemy też np. określić precyzję wyświetlania ułamków. Aby wyświetlić liczbę π do 4 miejsc po przecinku, napiszemy:

<?php
 
 	printf('Liczba PI: %0.4f', M_PI);
 
?>

To są jedynie podstawy kodów formatujących. Szczegółowy ich opis znajduje się w dokumentacji PHP. Warto nadmienić także istnienie odmian funkcji printf(), np.

  • sprintf() - zwraca wynik jako ciąg tekstowy.
  • vprintf() - pobiera dane do kodów z tablicy przekazanej jako drugi parametr.
  • vsprintf() - pobiera dane do kodów z tablicy przekazanej drugim parametrem i zwraca wynik jako ciąg tekstowy.

Kodowanie[edytuj]

Teraz nieco o przechowywaniu haseł użytkowników przez PHP. Generalnie nigdy nie składuje się ich w postaci jawnej oraz nie koduje się algorytmem dwukierunkowym (czyli dającym teoretyczną możliwość ich rozszyfrowania). Powszechna praktyka poleca stosowanie tzw. funkcji haszujących generujących unikalne, równej długości sygnatury niszczące oryginalny przekaz, przez co nie da się ich już rozszyfrować metodą inną, niż sprawdzenie wszystkiego na wszystkim. Wbrew początkowemu wrażeniu sens takiego działania jest bardzo oczywisty. Kiedy użytkownik rejestruje się w serwisie, haszujemy jego hasło i zapisujemy w profilu. Próbując się zalogować, przysyła nam swoje hasło jeszcze raz. Haszujemy je i wynik porównujemy z tym, co mamy w bazie. Jeżeli uzyskamy identyczne hasze, znaczy to, że użytkownik podał hasło poprawnie i może zostać zalogowany.

PHP posiada wbudowanych kilka algorytmów haszujących:

  • crypt() - najstarszy, generuje 8-znakowe hasze. Nie polecamy do celów autoryzacji.
  • md5() - do niedawna najpopularniejszy algorytm. Generował 32-znakowe ciągi, lecz niedawno ujawniono w nim poważne dziury.
  • sha1() - aktualnie najbezpieczniejszy algorytm haszujący dostępny domyślnie w PHP. Generuje 40-znakowe ciągi.

Zwróć uwagę na jedną rzecz: hasze mają stałą długość, dlatego ilość możliwych kombinacji jest ograniczona. Tymczasem możliwych ciągów jest nieskończenie wiele. Dlatego może się zdarzyć, że dwa różne ciągi generują ten sam hasz i zwiemy to kolizją. W dobrym algorytmie, aby doszło do takiej sytuacji, muszą to być naprawdę różne ciągi. Ponadto zmiana już pojedynczego znaku wewnątrz haszowanego ciągu musi powodować utworzenie zupełnie innego wyniku. Siła algorytmu zależy od tego, jak dużo czasu potrzeba na wykrycie kolizji metodą brute-force, czyli sprawdzenia wszystkiego na wszystkim. Algorytm MD5 został już złamany na tyle, że zwyczajny komputer PC jest w stanie znaleźć kolizję już w ciągu zaledwie ośmiu godzin. Dla SHA1 ilość niezbędnych do sprawdzenia kombinacji jest rzędu 2^64, z czym nie jest w stanie poradzić sobie żaden istniejący obecnie komputer.

Przypuśćmy, że $uzytkownicy jest tablicą asocjacyjną taką, że indeks jest nazwą użytkownika, a wartość hasłem zaszyfrowanym w SHA1. Aby sprawdzić, czy użytkownik wpisał poprawne hasło, musimy wykonać następującą czynność:

<?php

    if(isset($uzytkownicy[$_POST['login']]) && $uzytkownicy[$_POST['login']] == sha1($_POST['haslo']))
    {
        echo 'Dziękujemy, podałeś dobre hasło.';
    }
    else
    {
       echo 'Nieprawidłowy login i/lub hasło.';
    }

?>

Skonstruowanie systemu autoryzacji wymaga także zaimplementowania mechanizmu sesji, aby przekazać fakt bycia zalogowanym między poszczególnymi żądaniami. Zajmiemy się tym w dalszych rozdziałach.

Poprzedni rozdział: Przetwarzanie tekstu
Spis treści
Następny rozdział: Obsługa ciastek

Podstawy wyrażeń regularnych[edytuj]

W poprzednim rozdziale poznaliśmy proste techniki wyszukiwania i manipulacji danymi tekstowymi. Ponadto dowiedzieliśmy się o funkcjach z serii ctype pozwalających na prostą kontrolę zawartości ciągu pod kątem występujących w nim znaków. Jak sam zapewne zdążyłeś zauważyć, narzędzia te nie posiadają jednak żadnych zaawansowanych funkcji. Czy wobec tego możliwe jest manipulowanie ciągami o złożonej strukturze? Okazuje się, że tak - dzięki wyrażeniom regularnym, których omówieniem zajmuje się niniejszy rozdział.

Istota wyrażeń regularnych[edytuj]

Mechanizm wyrażeń regularnych (ang. regular expressions, czasem skracane do regexp) jest tak naprawdę parserem pewnego języka służącego do precyzyjnego definiowania dozwolonego formatu ciągu. Korzystanie z wyrażeń regularnych polega na stworzeniu za jego pomocą tzw. wzorca, a następnie jego porównania odpowiednimi funkcjami z interesującym nas ciągiem. Na wyjściu otrzymujemy informację, czy ciąg pasuje do wzorca, czy też nie.

Wyrażenia regularne mają jeszcze większe możliwości. Dzięki nim wyciągnięcie dowolnych interesujących nas informacji z ciągu nie stanowi kłopotu. Wystarczy, że znamy wzorzec go opisujący, a system wyrażeń zwróci nam dodatkowo tablicę uzyskanych z jego wnętrza danych, których potrzebujemy. Wyrażenia regularne dają nam także dostęp do znacznie bogatszego w możliwości kuzyna funkcji str_replace() z poprzedniego rozdziału. O ile tamta funkcja bezmyślnie zamieniała wszystkie napotkane wystąpienia jakiegoś fragmentu na inny, dzięki wyrażeniom regularnym możemy zdefiniować naprawdę wymyślne mechanizmy zamiany uwzględniające wiele dodatkowych czynników.

Jak widać, wyrażenia regularne to potężne narzędzie, jednak przez to też skomplikowane. Niemniej każdy szanujący się programista powinien znać przynajmniej jego podstawy, ponieważ praktyka zawodowa pokazuje, iż wykorzystywane są one bardzo często.

Pierwszy przykład[edytuj]

Nasze pierwsze spotkanie praktyczne z wyrażeniami regularnymi rozpoczniemy od prostego sprawdzenia, czy wypełnione pole formularza zawiera dokładnie jedną cyfrę. Do porównywania wzorca z ciągiem służy funkcja preg_match(), która zwraca 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
		{
			echo '<p>Nieprawidłowe dane! Skrypt wymaga podania cyfry!</p>';
		}
	}
	else
	{
		echo '<form method="post" action="preg1.php">
			Podaj cyfrę: <input type="text" name="cyfra"/><input type="submit" value="OK"/>
			</form>';	
	}
?>

Wykorzystaliśmy tutaj wzorzec /^[0-9]$/. Zawarty jest on wewnątrz ograniczników /. Poza nimi mogą znajdować się jedynie dodatkowe flagi kontrolne i nic więcej. Znak ^ oznacza początek ciągu, a znak $ koniec lub "prawie" koniec zezwalając na zakończenie wyrażenia przejściem do nowej linii \n. Zastosowane dodatkowo D wymusza interpretację $ jako bezwzględnego końca wyrażenia (możliwość dodania \n w niektórych przypadkach może być luką w bezpieczeństwie skryptu). [0-9] definiuje klasę dozwolonych znaków, jakie mogą pojawić się w danym miejscu. Ostatecznie wzorzec ten opisuje wszystkie ciągi składające się z DOKŁADNIE jednego znaku będącego cyfrą z przedziału 0 do 9.

Istnieje jeszcze jeden sposób powiadomienia parsera, ile znaków chcemy tam widzieć. Jest nim użycie kwantyfikatorów zasięgu. Ich składnia jest następująca:

  • {długość} - dozwolona długość określona jest dokładnie.
  • {długość_min,długość_max} - podany jest przedział dozwolonych długości
  • {długość_min,} - określona jest minimalna długość
  • {,długość_max} - określona jest maksymalna długość

Kwantyfikator umieszczamy po znaku lub klasie dozwolonych znaków, zatem nasze wyrażenie będzie miało postać /^[0-9]{1}$/. W wyrażeniach regularnych można stosować kilka predefiniowanych kwantyfikatorów:

  • * - 0 lub więcej
  • + - 1 lub więcej
  • ? - 0 lub 1 (uwaga: znak ten jest także wykorzystywany w innym kontekście)

Klasy znaków[edytuj]

Nauczymy się teraz bardziej dokładnego definiowania klas znaków, jakich można używać w danym miejscu ciągu. Zasada podstawowa jest bardzo prosta: jeśli w jakimś miejscu napiszemy "a", to parser będzie się tam spodziewać litery "a" występującej dokładnie jeden raz. Jeżeli zastosujemy klasę znaków, definiujemy w ten sposób listę dozwolonych na danej pozycji znaków dokładnie jeden raz. W obu przypadkach "dokładnie jeden raz" można zmienić na dowolną inną długość za pomocą omówionych wyżej kwantyfikatorów. Zatrzymajmy się jednak dokładniej przy tym zwrocie. Skoro dokładnie jeden raz, czemu w takim razie podany wyżej przykład dla wyrażenia /[0-9]/ akceptuje ciągi liczb o dowolnej długości? Aby lepiej pokazać, co naprawdę wtedy ma miejsce, wpisz w formularzu tekst "9a" - o dziwo także i on zostanie przyjęty, mimo że na drugiej pozycji mamy literę! Co jest nie tak? Nic - wyrażenie działa prawidłowo. Parser po prostu osiągnął jego koniec przy sprawdzeniu pierwszego znaku ciągu i resztę przepuścił bez żadnej kontroli. Dlatego istotne jest powiadomienie o tym, gdzie ma znajdować się koniec ciągu.

Tworząc klasę znaków, możemy stosować się do następujących reguł:

  • Wypisujemy w nawiasach kwadratowych wszystkie dopuszczalne znaki, np. [abcdefgh]
  • Wprowadzamy zakres: [a-h] (dopuszczalne małe litery od "a" do "h")
  • Wprowadzamy kilka zakresów: [a-hA-H] (dopuszczalne duże i małe litery od "a" do "h" i od "A" do "H").

Aby wprowadzić jakiś znak specjalny do klasy, poprzedzamy go backslashem: [a-hA-H\-] - znaki duże i małe od "a" do "h" wraz z pauzą. Dysponując tymi wiadomościami, jesteśmy już w stanie napisać pierwszą funkcję kontrolującą (w ograniczonym stopniu) poprawność adresu e-mail:

<?php

	if($_SERVER['REQUEST_METHOD'] == 'POST')
	{
		if(preg_match('/^[a-zA-Z0-9\.\-_]+\@[a-zA-Z0-9\.\-_]+\.[a-z]{2,4}$/D', $_POST['email']))
		{
			echo '<p>Wpisałeś e-mail '.$_POST['email'].'</p>';
		}
		else
		{
			echo '<p>Nieprawidłowe dane! Skrypt wymaga podania adresu e-mail!</p>';
		}
	}
	else
	{
		echo '<form method="post" action="preg2.php">
			Podaj adres e-mail: <input type="text" name="email"/><input type="submit" value="OK"/>
			</form>';	
	}
?>

Omówmy sobie poszczególne partie tego wyrażenia:

  • /^[a-zA-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ętne oraz białe znaki. Jeżeli po otwierającym nawiasie kwadratowym pojawi się symbol ^, będzie to oznaczać negację klasy: "wszystkie znaki, które NIE należą do wymienionych". Jak zdefiniowałbyś "dowolny znak niebędący cyfrą", czyli klasę \D w tradycyjny sposób?

Grupy[edytuj]

Poszczególne fragmenty ciągu mogą być ze sobą łączone w większe grupy, ujmowane w okrągłych nawiasach. Są one wykorzystywane w dwóch celach. Po pierwsze, można do nich zbiorczo zastosować kwantyfikator, żądając, aby np. jakiś fragment powtarzał się od 3 do 5 razy. Za pomocą grup eksportujemy także do PHP interesujące nas dane. Przykładowo, do wyrażenia /^(abc)+$/ pasują ciągi "abc", "abcabc", "abcabcabc" itd.

Przedstawimy teraz, jak wykorzystać wyrażenia regularne w innych dziedzinach, niż tylko kontrola formularzy. Załóżmy, że zlecono nam zadanie przeprojektowania bazy danych, ponieważ stara nie spełnia stawianych jej wymagań. Oczywiście musimy napisać jakiś konwerter, który przeniesie automatycznie dane do nowej bazy. Natknęliśmy się jednak na problem: daty utworzenia rekordów zapisywane są w postaci tekstowej, np. "12 Dec 2006, 16:34", zamiast w łatwych do przetwarzania sekundach od 1.1970. Do rozbicia ciągu na poszczególne fragmenty wykorzystamy wyrażenia regularne:

<?php

	$date = '12 Dec 2006, 16:34';

	if(preg_match('/^(\d{1,2}) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4})\, (\d{1,2})\:(\d{1,2})/', $date, $found))
	{
		// Co nam zwrocilo...
		echo '<h3>Dane: "'.$date.'"</h3>';
		echo '<p>Dzien: '.$found[1].'</p>';
		echo '<p>Miesiac: '.$found[2].'</p>';
		echo '<p>Rok: '.$found[3].'</p>';
		echo '<p>Godzina: '.$found[4].'</p>';
		echo '<p>Minuta: '.$found[5].'</p>';

		$monthConverter = array('Jan' => 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4, 'May' => 5,
			'Jun' => 6, 'Jul' => 7, 'Aug' => 8, 'Sep' => 9, 'Oct' => 10, 'Nov' => 11, 'Dec' => 12);

		echo '<p>Unix timestamp: '.mktime($found[4], $found[5], 0, $monthConverter[$found[2]], $found[1], $found[3]).'</p>';
	}
	else
	{
		echo '<p>Nieprawidłowy format daty!</p>';
	}

?>

W zastosowanym wyrażeniu regularnym pojawia się symbol | - jest to operator wyboru. Ciąg Jan|Feb|Mar oznacza, że w tym miejscu chcemy mieć "Jan" ALBO "Feb" ALBO "Mar". Zauważ, że wszystkie istotne elementy daty zawarliśmy w grupach, a do samej funkcji preg_match() podaliśmy trzeci parametr. Do podanej tam zmiennej zostanie przypisana tablica z treścią pasującego ciągu na indeksie 0 oraz wartościami wszystkich użytych grup na kolejnych indeksach. Teraz możemy już łatwo przekonwertować funkcją mktime() naszą datę na format uniksowy.

Wiki letter w.svg Ta sekcja jest zalążkiem. Jeśli możesz, rozbuduj ją.
Poprzedni rozdział: Podstawy wyrażeń regularnych
Spis treści
Następny rozdział: Sesje

Obsługa ciastek[edytuj]

Czym są nagłówki HTTP?[edytuj]

Serwer w odpowiedzi na żądanie HTTP wysyła nie tylko kod HTML, lecz także zestaw nagłówków pomagających przeglądarce na zidentyfikowanie dostarczanych treści. Nagłówki precyzują typ MIME danych, np. text/html dla dokumentu HTML, kodowanie znaków, zachowanie się serwerów proxy, ustawienia cache'owania danych itd. PHP posiada funkcję header() pozwalającą skryptowi wysłać własne nagłówki. Muszą być one jednak zdefiniowane przed wysłaniem jakiegokolwiek kodu HTML (są to przecież NAGŁÓWKI). W przeciwnym wypadku dostaniemy niemiły komunikat Cannot add header information. Programiści często wykorzystują nagłówki do zdefiniowania typu oraz kodowania w nadsyłanym dokumencie:

<?php

    // Bedziemy wysylac dokument HTML z kodowaniem UTF-8
    header('Content-type: text/html;charset=utf-8');

?>

Ale nie tylko. Wiele stron udostępnia swe archiwa nie poprzez bezpośredni dostęp do katalogu, w którym są trzymane, ale poprzez specjalny skrypt, który wysyła ich zawartość do ściągnięcia. Nagłówki umożliwiają powiadomienie przeglądarki, że teraz będzie szedł plik o takiej i takiej nazwie, który internauta chce pobrać na swój dysk twardy. Nie będzie on jednak ujawniać katalogu na serwerze, gdzie on się znajduje. Cała komunikacja prowadzona jest za pośrednictwem PHP.

<?php
 
 	if(!isset($_GET['plik'])) // 1
 	{
 		die('Podaj nazwę pliku!');
 	}
 
 	$_GET['plik'] = basename($_GET['plik']); // 2
 	
 	if(@is_file('./pdf/'.$_GET['plik']))
 	{
 		// 3
 		header('Content-type: application/pdf');
 		header('Content-Disposition: attachment; filename="'.$_GET['plik'].'"');
 		readfile('./pdf/'.$_GET['plik']);
 	}
 	else
 	{
 		// 4
 		header('HTTP/1.1 404 Not Found');
 		exit('Nie znaleziono pliku '.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. 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ścia między katalogowe, co mogłoby zagrozić bezpieczeństwu. Wyobraź sobie, że ktoś wpisze sobie np. ../strona_hasla.php. Bez tego zabezpieczenia dostałby hasła dostępu do naszej strony, lecz basename() odrzuci niebezpieczny fragment ../. Mamy więc pewność, że internauta będzie ściągać TYLKO i WYŁĄCZNIE to, co chcemy, aby ściągał.
  3. Jeśli stwierdzimy, że plik istnieje, powiadamiamy nagłówkami, że oto nadejdzie dokument PDF jako załącznik o odpowiedniej nazwie. Funkcją readfile() wysyłamy jego zawartość.
  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ł do naszego własnego komunikatu. Nagłówki umożliwiają robienie przekierowań HTTP i właśnie pragniemy pokazać, jak to się robi.

<?php
 
 	if(!isset($_GET['plik']))
 	{
 		die('Podaj nazwę pliku!');
 	}
 
 	$_GET['plik'] = basename($_GET['plik']);
 	
 	if(@is_file('./pdf/'.$_GET['plik']))
 	{
 		header('Content-type: application/pdf');
 		header('Content-Disposition: attachment; filename="'.$_GET['plik'].'"');
 		readfile('./pdf/'.$_GET['plik']);
 	}
 	else
 	{
 		header('Location: http://localhost/~kurs/notfound.php');
 		exit;
 	}
 
?>

Źródło jest zasadniczo podobne do poprzedniego przykładu. Zmiany widać jedynie w bloku else, gdzie wysyłamy tym razem nagłówek Location. Informuje on przeglądarkę, że treści nie będzie i powinna raczej skontaktować się z podanym plikiem. Innymi słowy, robimy przekierowanie internauty pod inny adres. Protokół HTTP 1.1 wymaga, aby w nagłówku był podany pełen adres do żądanego zasobu. HTTP 1.0 nie miał takich ograniczeń.

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 PHP[edytuj]

Chyba każdy internauta słyszał o ciastkach i wyolbrzymianych "zagrożeniach" z nimi związanych. W rzeczywistości są to zwyczajne informacje umieszczane przez witryny WWW w przeglądarkach po to, aby był do nich dostęp między wywołaniami kolejnych podstron w obrębie witryny. Jedyny problem może pojawić się z tzw. ciastkami publicznymi, które może odczytać każda strona w Internecie. Jednak poza tym jest to bardzo pożyteczne narzędzie wykorzystywane m.in. w autoryzacji użytkowników.

Ciastka są ustawiane za pomocą nagłówków HTTP i mają pewien określony termin ważności. Po jego upływie przestają istnieć. Do wysyłania ciastek służy w PHP funkcja setcookie(), a do pobierania wartości tych ustawionych przez wcześniejsze żądania HTTP - specjalna tablica $_COOKIE. Napiszemy teraz prosty skrypt, który robił furorę kilka lat temu, w okresie popularyzacji dynamicznych witryn WWW. Chodzi o umieszczenie prostej informacji dot. ostatniej wizyty internauty u nas. Aby to wykonać, wystarczy przy pierwszym wejściu umieścić na np. miesiąc ciastko z datą ostatniej wizyty, po czym ją sukcesywnie odczytywać.

<?php
 	if(!isset($_COOKIE['wizyta']))
 	{
 		setcookie('wizyta', time(), time() + 30 * 86400);
 		echo 'Witaj, gościu.';
 	}
 	else
 	{
 		setcookie('wizyta', time(), time() + 30 * 86400);
 		echo 'Witaj, ostatni raz odwiedziłeś nas '.date('d.m.Y, H:i', $_COOKIE['wizyta']);	
 	}
 
?>

Trzy pierwsze parametry setcookie() są najważniejsze (ma ona ich trochę więcej). Jest to kolejno: nazwa ciastka, jego wartość oraz data ważności w sekundach od 1.1.1970. Data, a nie okres ważności, stąd przy jego ustawianiu przydaje się funkcja time(). W powyższym skrypcie sprawdzamy, czy ustawialiśmy już ciastko dla danego internauty. Jeśli nie, tworzymy je i wyświetlamy komunikat powitania. W przeciwnym wypadku także aktualizujemy wartość, ale też wyświetlamy datę ostatniej wizyty odczytaną właśnie z ciastka.

Zauważ, że wywołanie setcookie() nie nadpisuje wartości w tablicy $_COOKIE. Dlatego bez problemu można uprościć powyższy skrypt:

<?php
 	setcookie('wizyta', time(), time() + 30 * 86400);
 	if(!isset($_COOKIE['wizyta']))
 	{
 		echo 'Witaj, gościu.';
 	}
 	else
 	{
 		echo 'Witaj, ostatni raz odwiedziłeś nas '.date('d.m.Y, H:i', $_COOKIE['wizyta']);	
 	}
 
?>

Aby istniejące ciastko skasować, wywołujemy funkcję setcookie() z jakąś przeszłą datą ważności:

<?php
 
 	setcookie('wizyta', '', 0);
 
?>

Funkcje buforowania wyjścia[edytuj]

Istnieją sytuacje, kiedy musimy wysłać jakiś kod HTML przed wysłaniem nagłówków HTTP, jednak tradycyjne metody nie pozwalają na to. W PHP można ten problem omijać, używając funkcji buforowania wyjścia. Ogólnie rzecz biorąc, przechwytują one treść wysyłaną przez echo albo print, zapisując ją do specjalnego bufora. Opróżniamy go samodzielnie na samym końcu skryptu, symulując efekt równoczesnego wysyłania nagłówków i kodu HTML. Pokażemy to na przykładzie:

<?php
 
 	ob_start();
 
 	echo '<h1>Tytuł witryny</h1><p>I inne komendy HTML.</p>';
 	
 	setcookie('wizyta', time(), time() + 30 * 86400);
 	if(!isset($_COOKIE['wizyta']))
 	{		
 		echo '<p>Witaj, gościu.</p>';
 	}
 	else
 	{
 		echo '<p>Witaj, ostatni raz odwiedziłeś nas '.date('d.m.Y, H:i', $_COOKIE['wizyta']).'</p>';	
 	}
 
 	ob_end_flush();
 
?>

Zwróć uwagę, że przed stworzeniem ciastka skrypt wysyła już kod HTML. Dlatego wszystkie instrukcje zawarliśmy między funkcjami ob_start() i ob_end_flush(). Pierwsza inicjuje buforowanie wyjścia, a druga kończy je, wysyłając jego zawartość do przeglądarki.

Buforowanie wyjścia może też posłużyć do celów algorytmicznych, kiedy musimy przechwycić wysyłany kod, aby go jeszcze dodatkowo obrobić. Naraz można mieć otwartych kilka buforów działających zgodnie z zasadą stosu, tj. ostatni otwarty bufor będzie pierwszym, z którego pobierzemy zawartość. Poniżej prezentujemy mały skrypt cenzorski. Przechwytuje on tekst i cenzuruje go, chyba że internauta zna sposób aktywujący prawdziwą treść. Jest to coś w sam raz dla walczących z dyktaturami opozycjonistów.

<?php
 	ob_start();	
 	
  	echo '<p>Pan Jan Nowak jest bardzo nieprzyzwoitym człowiekiem. Powiada, że dzień bez łapówki to
 		dzień stracony. Pracuje w urzędzie miejskim Obiektowa i nie wstydzi się swych podejrzanych interesów.</p>';
 	
 	// CENZURA
 	// Pobieramy zbuforowany tekst
 	// I **dla czytelności** przykładu zapisujemy go w zmiennej	
 	$kod = ob_get_clean();
 	
 	if(isset($_GET['real']))
 	{
 		// Wtajemniczeni znają całą prawdę
 		echo $kod;
 	}
 	else
 	{
 		// Reszta może się tylko domyślać
 		echo str_replace(array(
 			'Jan Nowak',
 			'Obiektowo',
 			'Obiektowa'	
 		), array(
 			'Alojzy Kromka',
 			'Hyzia Wólka',
 			'Hyziej Wólki'	
 		), $kod);
 	}
 
?>

Buforowanie wyjścia jest też podstawą tzw. kompresji GZip. Jest to kompresowanie treści strony przed wysłaniem tak, aby zajmowała mniejszą objętość, przez co użytkownik szybciej ją pobierze. Kompresję wspierają wszystkie nowoczesne przeglądarki (np. Opera, Firefox).

Aby uruchomić kompresję GZip, twoja wersja PHP musi mieć doinstalowaną bibliotekę zlib. Wtedy możesz rozpocząć buforowanie poniższym kodem:

 ob_start('ob_gzhandler');
 ob_implicit_flush(0);

Parametr przekazywany do ob_start() to nazwa tzw. uchwytu (handlera) służącego do modyfikacji zbuforowanej treści. ob_gzhandler jest jednym z predefiniowanych uchwytów, zajmującym się właśnie kompresją GZip. Druga z funkcji nakazuje wywołanie uchwytu dopiero, gdy będziemy mieli już cały kod HTML. Musimy o niej pamiętać dlatego, że nie można skompresować danych wyjściowych partiami - musi to być przeprowadzone w sposób ciągły. PHP samodzielnie wykrywa, czy przeglądarka użytkownika posiada obsługę tej możliwości, dlatego nie musisz o tym pamiętać.

Ciastka a bezpieczeństwo[edytuj]

Ciastka są podstawą wielu systemów autoryzacji użytkowników dzięki możliwości przesyłania za ich pomocą danych między stronami. Jednak wielu początkujących programistów PHP nie rozumie lub nie wie, jak robić to bezpiecznie. Ciastko jest zwyczajnym nagłówkiem HTTP i naprawdę nie stanowi dużego problemu przechwycenie jego treści. Dlatego pamiętaj, aby nigdy nie przesyłać nim loginów, haseł zalogowanego użytkownika, ani żadnych innych danych potencjalnie pomocnych przy autoryzacji. Jest to całkowicie zła droga, gdyż systemy autoryzacji pisze się z wykorzystaniem sesji. Sesja jest pewnym rekordem z informacjami identyfikacji danego internauty przechowywanymi w bazie albo w pliku tekstowym na serwerze. Posiada długi, alfanumeryczny identyfikator i to on przesyłany jest ciastkiem. Porównując ID z ciastka, a także takie parametry, jak adres IP czy używana przeglądarka, można wyeliminować przypadki kradzieży ID sesji. Mechanizmu sesji nie musisz pisać samemu, gdyż PHP posiada własny. Omówimy go w następnym rozdziale.


Sesje[edytuj]

Protokół HTTP jest protokołem bezstanowym. Oznacza to, że serwer WWW rozpatruje każde żądanie niezależnie od innych, nie szukając żadnych powiązań w stylu wysyłania ich przez tego samego internautę. Utrudnia to teoretycznie tworzenie wszelkich systemów autoryzacji, które wymagają śledzenia poczynań użytkownika na naszej stronie i przenoszenia jego danych autoryzacyjnych między kolejnymi żądaniami, czyli krótko mówiąc - wymagają obecności systemu sesji. Używając PHP lub innego dynamicznego języka server-side można je jednak zasymulować. "Nasz" język jest o tyle prosty, iż posiada już zaimplementowane stosowne funkcje. My tylko musimy zacząć ich używać.

Działanie sesji w PHP jest bardzo proste. W momencie pierwszego trafienia na stronę interpreter tworzy specjalny, losowy oraz unikalny identyfikator przesyłany między żądaniami za pomocą ciastek lub parametru PHPSESSID doklejanego automatycznie do adresów URL. Na jego podstawie odczytywany jest później odpowiedni plik z danymi sesji zapisany gdzieś na serwerze. Pod koniec przetwarzania żądania wszystkie wprowadzone przez skrypt zmiany są z powrotem zapisywane do wspomnianego pliku tak, aby były widoczne przy wejściu na kolejną podstronę. I tak to się toczy.

Wprowadzenie do sesji[edytuj]

Czas na trochę praktyki. Aby zainicjować mechanizm sesji, wystarczy wywołać funkcję session_start(), najlepiej na początku naszej aplikacji. Od tego momentu do naszej dyspozycji zostaje oddana superglobalna tablica $_SESSION - wszystkie zapisane do niej dane są przesyłane między kolejnymi żądaniami. Popatrzmy na pierwszy, bardzo prosty przykład licznika odwiedzonych już podstron:

<?php
 
	session_start(); // 1
	
	if(!isset($_SESSION['licznik'])) // 2
	{
		$_SESSION['licznik'] = 0;
	}
	
	$_SESSION['licznik']++; // 3
	
	echo 'Odwiedziłeś już '.$_SESSION['licznik'].' podstron!'; // 4
?>

Oto analiza:

  1. Inicjalizujemy sesje
  2. 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. Zmieniamy dane sesji
  4. Odczytujemy dane sesji

Po odświeżeniu strony zauważymy, że licznik wskazuje już "2", po kolejnym - "3". Oznacza to, że PHP zapisuje zmienną $licznik i przesyła ją między naszymi żądaniami. Różnica pomiędzy sesjami, a ciastkami jest taka, że dane te w ogóle nie opuszczają serwera WWW, są przez to (w teorii) bezpieczniejsze.

Prosta autoryzacja użytkowników[edytuj]

Napiszemy teraz prosty skrypt do autoryzacji użytkowników bazujący na sesjach. Jak wspomnieliśmy w poprzednim rozdziale, nie powinno się przesyłać pomiędzy żądaniami haseł oraz loginów użytkowników, nieważne czy w formie ciastek, czy sesji. Alternatywą jest ich ID. Jak przekonasz się w następnych rozdziałach, bazy danych, gdzie większość aplikacji WWW trzyma swoje informacje, wcale nie identyfikują rekordów jakimiś abstrakcyjnymi rzeczami typu login lub tytuł. Operują na zwyczajnych, automatycznie nadawanych i unikalnych liczbach zwanych identyfikatorami (w skrócie pisze się "id"). Dzięki temu można szybko je do siebie porównać, co ma szczególne znaczenie w przypadku ogromnej liczby rekordów. Takie też ID przypisane użytkownikom przesyłane są w sesjach. Oczywiście, zgodnie z praktyką stosowaną w bazach danych, numerację rekordów rozpoczynamy od 1. Rekord o ID równym 0 nie istnieje.

Zatem, jak taki system logowania działa? Kiedy sesja jest już załadowana, skrypt sprawdza zapisany w niej ID użytkownika. Jeżeli jest on większy od zera, ktoś jest już zalogowany i wystarczy tylko pobrać skądś dane jego profilu. W przypadku ID równego 0 mamy do czynienia z kimś anonimowym. Tu, w zależności od sytuacji możemy mu wyświetlać ogólnodostępne treści albo formularz logowania. Pisanie skryptu zaczniemy od stworzenia sobie namiastki bazy danych. Będzie nią tablica $uzytkownicy przechowująca loginy oraz hasła użytkowników. Ponadto napiszemy funkcję czyIstnieje() znajdującą ID użytkownika o podanym loginie i haśle lub false, kiedy takowy nie istnieje. Hasła naturalnie haszujemy poznanym już algorytmem sha1:

<?php
 
	$uzytkownicy = array(1 =>
		array('login' => 'user1', 'haslo' => sha1('ppp')),
		array('login' => 'user2', 'haslo' => sha1('ddd')),
		array('login' => 'user3', 'haslo' => sha1('fff'))
	);
	
	function czyIstnieje($login, $haslo)
	{
		global $uzytkownicy;
		
		$haslo = sha1($haslo);
		
		foreach($uzytkownicy as $id => $dane)
		{
			if($dane['login'] == $login && $dane['haslo'] == $haslo)
			{
				// O, jest ktos taki - zwroc jego ID
				return $id;
			}
		}
		// Jeżeli doszedłeś aż tutaj, to takiego użytkownika nie ma
		return false;
	} // end czyIstnieje();

Teraz zaczniemy właściwy skrypt. Zainicjujemy sesję i obsłużymy sytuację pierwszej wizyty - w takim wypadku gościa oznaczymy jako osobę anonimową (niezalogowaną):

	// Wlasciwy skrypt
 
	session_start();
	
	if(!isset($_SESSION['uzytkownik']))
	{
		// Sesja się zaczyna, wiec inicjujemy użytkownika anonimowego
		$_SESSION['uzytkownik'] = 0;
	}

Krótka piłka - ID większy od 0? Ktoś jest zalogowany:

	if($_SESSION['uzytkownik'] > 0)
	{
		// Ktos jest zalogowany
		echo 'Witaj, '.$uzytkownicy[$_SESSION['uzytkownik']]['login'].' na naszej stronie!';	
	}
	else
	{

Jednak jeśli nie jest, to musimy zaprogramować zarówno pokazywanie formularza logowania, jak i autoryzację użytkownika na podstawie danych z niego. Zaczynamy od tej drugiej opcji. Korzystając z funkcji czyIstnieje() pobieramy ID użytkownika. Jeżeli jest on różny od false, wpisujemy go do sesji, tym samym logując go.

		// Niezalogowany
		if($_SERVER['REQUEST_METHOD'] == 'POST')
		{
			if(($id = czyIstnieje($_POST['login'], $_POST['haslo'])) !== false)
			{
				// Logujemy uzytkownika, wpisal poprawne dane
				$_SESSION['uzytkownik'] = $id;
				echo 'Dziekujemy, zostales zalogowany! <a href="sesje_2.php">Dalej</a>';
			}
			else
			{
				echo 'Podales nieprawidlowe dane, zegnaj! <a href="sesje_2.php">Dalej</a>';
			}		
		}
		else
		{

Kiedy dane nie nadeszły z formularza, znaczy to, że trzeba go wyświetlić:

			echo '<form method="post" action="sesje_2.php">
				Zaloguj sie: <input type="text" name="login"/>
                                <input type="password" name="haslo"/>
				<input type="submit" value="OK"/></form>';		
		}	
	}

I gotowe. Wszystko fajnie, wszystko pięknie, nasza witryna sobie działa, użytkownicy się logują, dodając coraz to nowe treści, lecz pewnego dnia dostajesz e-maila z ostrzeżeniem, że ktoś włamał się do serwisu i wszystko rozwalił. Co nawaliło? Czyżby zawiódł mechanizm sesji?

Bezpieczeństwo sesji[edytuj]

Sytuacja z mechanizmem sesji standardowo dostępnym w PHP jest o tyle śmieszna, że jego autorzy właśnie nam zostawili swobodę działania odnośnie tego, jak go zabezpieczyć przed kradzieżą. Normalnie użyty jest on bowiem dziurawy jak sito. Kradzież sesji nie nastręcza wielkich trudności, lecz na nieszczęście, wiele osób o tym nie pamięta (także autorów artykułów pokazujących, jak z niego korzystać!).

Włamania wykorzystujące dziury w mechanizmach sesji mają swoje fachowe nazwy. Pierwszą z nich jest Session Fixation. Jej działanie jest bardzo proste. Wykorzystujemy tutaj właściwość, że kiedy podamy skryptowi jakiś nieistniejący ID sesji, PHP automatycznie dorobi dane i nie przejmie się tym, że tak naprawdę to my go wygenerowaliśmy, a nie jakiś algorytm losująco-mieszający. Teraz popatrz: podszywasz się pod pracownika jakiegoś serwisu internetowego i każesz nieświadomemu użytkownikowi odwiedzić jakiśtam adres, najlepiej wymagający zalogowania. Do adresu URL doczepiasz ciąg ?PHPSESSID=abcdef. "abcdef" jest wymyślonym przez nas identyfikatorem. Kiedy użytkownik posłusznie się zaloguje, jest zdany na naszą łaskę. Mamy ID jego sesji i możemy działać tak, jakbyśmy byli nim.Jeszcze więcej cwaniactwa i możemy uzyskać nawet sesję administratora, co dla serwisu oznacza oczywiście katastrofę. Aby się przed tym zabezpieczyć, wystarczy po zainicjowaniu mechanizmu sesji dokleić bardzo prosty kod:

<?php
 
	session_start();
	
	if (!isset($_SESSION['inicjuj']))
	{
		session_regenerate_id();
		$_SESSION['inicjuj'] = true;
	}

Przy tworzeniu nowej sesji, dzięki funkcji session_regenerate_id() mamy pewność, że sesja dostanie losowy ID. Teraz nawet, jeżeli podamy zmyślnie ?PHPSESSID=abcdef, nic nam to nie da, bo PHP i tak sobie wszystko wygeneruje po swojemu i zostaniemy w tym samym miejscu, co byliśmy. Jednak nie spoczywaj jeszcze na laurach. ID nadal może zostać wykradziony. Wystarczy, że jakiś ciamajda skopiuje znajomemu link do jakiegoś zasobu, mając przy tym wyłączone ciastka. Oczywiście w linku znajdzie się wtedy jego LOSOWY identyfikator sesji, który w ten sposób zostanie ujawniony przed światem. Znajomy może teraz wędrować sobie po serwisie wykorzystując sesję ciamajdy. session_regenerate_id() tutaj nie zadziałała, bo przecież ta sesja już istnieje. Ataki tego rodzaju określa się mianem Session Hijacking. Zabezpieczenie się przed nimi także jest proste. Wystarczy w sesji przesyłać np. adres IP komputera wraz z nazwą przeglądarki, spod których została ona utworzona, a następnie porównywać je przy kolejnych wizytach z danymi dostarczonymi przez serwer. Jeżeli wystąpi niezgodność, ktoś próbuje użyć cudzej sesji.

<?php
 
	session_start();
	
	if (!isset($_SESSION['inicjuj']))
	{
		session_regenerate_id();
		$_SESSION['inicjuj'] = true;
		$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
	}
	
	
	if($_SESSION['ip'] != $_SERVER['REMOTE_ADDR'])
	{
		die('Proba przejecia sesji udaremniona!');	
	}

Powyższy przykład zawiera łatki przeciwko obu włamaniom (co prawda w przypadku tej drugiej o wiele lepszym rozwiązaniem byłoby utworzenie sesji na nowo w przypadku niezgodności, niż obwieszczanie całemu światu naszego "odkrycia"). Pamiętaj jednak, że najsłabszym ogniwem jest zawsze człowiek. Jeżeli hacker zdobędzie twoje hasło, zabezpieczenia na nic się nie zdadzą, chyba że akurat masz stały adres IP i tak sobie wszystko napisałeś, że na twoje konto można się tylko z niego logować. Tylko kto stosuje tak rygorystyczne i nieporęczne środki bezpieczeństwa?

Podsumowanie[edytuj]

Sesje PHP są bardzo dobrym rozwiązaniem, jednak paradoksalnie wiele profesjonalnych aplikacji pisze własne systemy sesji całkowicie od zera. Powodów jest kilka:

  1. Integracja ze strukturą kodu reszty aplikacji
  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 mimo tego taki sam.
  3. Bezpieczeństwo - kiedy piszesz wszystko od zera, możesz wstawić dodatkowe zabezpieczenia tam, gdzie normalnie nie sięgniesz.
  4. Elastyczność - dlaczego dane sesji muszą być trzymane akurat w pliku? Baza danych jest przecież równie dobra, jeśli nie lepsza.
  5. Specyfika zastosowania - wiele systemów sesji służy tylko celom autoryzacji. Stąd też pisze się je od zera pod to jedno zastosowanie, co umożliwia wprowadzenie pewnych uproszczeń oraz optymalizacji.

Jednak własny system sesji jest odpowiedzialnym kawałkiem kodu, gdyż tam to ty musisz wszystko od A do Z zaprogramować. Na razie jedynie sygnalizujemy taką możliwość jako alternatywę dla systemu sesji wbudowanego w PHP. Który z nich wybierzesz, to zależy tylko od Ciebie.


Poprzedni rozdział: Sesje
Spis treści
Następny rozdział: Internacjonalizacja

Wysyłanie e-maili[edytuj]

Aby wysyłać e-maile z PHP, interpreter musi być skonfigurowany do pracy z demonem pocztowym. Odpowiednie dyrektywy znajdują się w pliku php.ini w sekcji [mail function]. Należy tam podać odpowiednie parametry w zależności od systemu operacyjnego:

  • Dla systemów Win32 - host oraz port, pod jakim pracuje demon pocztowy protokołu SMTP. Możesz próbować łączyć się z twoją własną skrzynką e-mail u jednego z providerów, lecz jeżeli znajdujesz się za firewallem, prawdopodobnie będziesz musiał zainstalować serwer poczty na własnym komputerze (np. Mercury for Win32).
  • Dla systemów Unix należy podać wywołanie oraz ewentualne parametry programu sendmail.

Funkcja mail()[edytuj]

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::mail[edytuj]

Dla osób które lubią używać gotowych bibliotek można polecić moduł mail z pear. Szczegóły znajdziesz pod adresem: http://pear.php.net/package/Mail . Biblioteka jest dosyć prosta i posiada w swojej dokumentacji jasne przykłady.

Wysyłanie załączników jest równie proste - zainteresuj się klasą pear::Mail_Mime.

Przykłady[edytuj]

Następujący przykład spowoduje wysłanie wiadomości e-mail na adres jan_testowy@serwer.pl o temacie "Witaj" i treści "Oto test funkcji mail":

<?php
   if(mail('jan_testowy@serwer.pl', 'Witaj', 'Oto test funkcji mail'))
   {
      echo 'Wiadomość została wysłana';
   }

Nagłówki pozwalają na ustawienie dodatkowych informacji o wiadomości, np. jej nadawcy lub kodowaniu znaków: Możemy również użyć zmiennych:

<?php
   $naglowki = "From: moj@mail.pl".PHP_EOL."Reply-To: moj@mail.pl".PHP_EOL."Content-type: text/plain; charset=iso-8859-2";

   if(mail('jan_testowy@serwer.pl', 'Witaj', 'Oto test funkcji mail', $naglowki))
   {
      echo 'Wiadomość została wysłana';
   }

Nagłówki można podać zarówno wewnątrz cudzysłowów, jak i apostrofów. Użyto tutaj stałej PHP_EOL, zmiennej środowiskowej. Przejść do nowej linii można na dwa sposoby. Pierwszy, uniwersalny i mniej popularny to ta stała, drugi - zależny od systemu serwera - \r\n dla Windows, \n dla Linuksa i \r dla Mac'a. Wewnątrz edytora najlepiej podawać wszystkie nagłówki w jednym ciągu i zejścia zaznaczać przy użyciu właśnie tych kodów. Inaczej może to spowodować wysłanie niepoprawnej wiadomości, gdyż niektóre edytory mają tendencję do zniekształcania tych zejść.

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

<?php
   // Naglowki mozna sformatowac tez w ten sposob.
   $naglowki = "Reply-to: moj@mail.pl <moj@mail.pl>".PHP_EOL;
   $naglowki .= "From: moj@mail.pl <moj@mail.pl>".PHP_EOL;
   $naglowki .= "MIME-Version: 1.0".PHP_EOL;
   $naglowki .= "Content-type: text/html; charset=iso-8859-2".PHP_EOL; 

   //Wiadomość najczęściej jest generowana przed wywołaniem funkcji
   $wiadomosc = '<html> 
   <head> 
      <title>Wiadomość e-mail</title> 
   </head>
   <body>
      <p><b>Treść wiadomości</b>: To jest treść wiadomości z formatowaniem HTML.</p>
   </body>
   </html>';


   if(mail('jan_testowy@serwer.pl', 'Witaj', $wiadomosc, $naglowki))
   {
      echo 'Wiadomość została wysłana';
   }

Dokumentacja[edytuj]

Poprzedni rozdział: Wysyłanie e-maili
Spis treści
Następny rozdział: System plików

Internacjonalizacja[edytuj]

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

Dlaczego "Ż" jest literą?[edytuj]

Uruchom pewien prosty skrypt:

<?php

	echo strtolower('ŻÓŁTA WODA');

?>

W zależności od komputera, pokaże on napis żółta woda albo ŻóŁta woda. Zastanówmy się, skąd jeden interpreter wiedział, że Ż jest literą polskiego alfabetu, która również podlega zamianie, a drugi nie? Odpowiedź kryje się w ustawieniach regionalnych tych maszyn, znanych z terminologii linuksowej pod nazwą locale. PHP domyślnie wykonuje wszystkie operacje na tekstach, ustawia formatowanie walut oraz liczb na podstawie ustawień systemowych. Aby zmienić opcje regionalne dla aktualnie wykonywanego skryptu, należy skorzystać z funkcji setlocale(). Za pierwszy parametr podajemy flagi określające, które aspekty regionalizacji chcemy zmodyfikować, a wszystkie kolejne to identyfikatory możliwych ustawień. PHP będzie próbował każdego z nich po kolei, aż trafi na taki, który jest zainstalowany w systemie. Dzięki temu jedną funkcją można przystosować skrypt do pracy zarówno z serwerami opartymi o Linux, jak i Windows. Poniżej przedstawiamy jeszcze raz skrypt strtolower() skonfigurowany do pracy z polskimi ustawieniami:

<?php

	setlocale(LC_ALL, 'pl_PL', 'pl', 'Polish_Poland.28592');

	echo strtolower('ŻÓŁTA WODA');

?>

Pierwsze dwa identyfikatory (pl_PL oraz pl) dotyczą systemu Linux. Trzeci przeznaczony jest dla rodziny Windows. Listę aktualnie zainstalowanych ustawień można znaleźć w Panel sterowania → Opcje regionalne → Zaawansowane.

Polska data[edytuj]

Pisząc aplikację przeznaczoną na rynek międzynarodowy, powinniśmy pomyśleć także o formatowaniu dat. Poznana już przez nas funkcja date() nawet przy pracy z polskimi ustawieniami wyświetli nam angielskie nazwy miesięcy. Sprawę rozwiązuje strftime(), przy której musimy pamiętać o innej składni formatowania daty. Przyjrzyjmy się przykładowi:

<?php

	setlocale(LC_ALL, 'pl_PL', 'pl', 'Polish_Poland.28592');
	
	// Dzień - nazwa miesiąca - rok
	echo strftime('%d %B %Y');

?>

Po uruchomieniu okazuje się, że nawet ten skrypt nie jest doskonały, ponieważ wprawdzie generuje spolszczoną datę, ale niepoprawną gramatycznie! Otrzymujemy komunikat np. 17 kwiecień 2006, a tymczasem poprawną formą jest 17 kwietnia 2006. W niuanse gramatyczne wgłębiać się nie będziemy - po prostu jest tak, a nie inaczej i musimy samodzielnie napisać sobie funkcję, która nam to przeformatuje. Umożliwiając dodawanie obsługi nowych języków do aplikacji, powinniśmy pomyśleć o możliwości jej podmiany tak, aby twórca nakładki językowej mógł zaprogramować datę zgodnie z wymogami swego języka. Oto przykład takiej funkcji dla języka polskiego. Jest to modyfikacja strftime() dodająca nowy znacznik - %F będący właśnie odmienioną nazwą miesiąca:

<?php
	
	function localStrftime($format, $timestamp = 0)
	{
		if($timestamp == 0)
		{
			// Sytuacja, gdy czas nie jest podany - używamy aktualnego.
			$timestamp = time();
		}

		// Nowy kod - %F dla odmienionej nazwy miesiąca
		if(strpos($format, '%F') !== false)
		{
			$mies = date('m', $timestamp);
			
			// odmienianie
			switch($mies)
			{
				case 1:
					$mies = 'stycznia';
					break;
				case 2:
					$mies = 'lutego';
					break;
				case 3:
					$mies = 'marca';
					break;
				case 4:
					$mies = 'kwietnia';
					break;
				case 5:
					$mies = 'maja';
					break;
				case 6:
					$mies = 'czerwca';
					break;
				case 7:
					$mies = 'lipca';
					break;
				case 8:
					$mies = 'sierpnia';
					break;
				case 9:
					$mies = 'września';
					break;
				case 10:
					$mies = 'października';
					break;
				case 11:
					$mies = 'listopada';
					break;
				case 12:
					$mies = 'grudnia';
					break;			
			}
			// dodawanie formatowania
			return strftime(str_replace('%F', $mies, $format), $timestamp);		
		}
		return strftime($format, $timestamp);	
	} // end localStrftime();
	
	echo localStrftime('%d %F %Y');

?>

Teraz otrzymujemy prawidłowy tekst: 17 kwietnia 2006.

Oto kilka przydatnych kodów dla funkcji strftime():

  • %A - pełna nazwa dnia tygodnia.
  • %B - pełna nazwa dnia miesiąca.
  • %d - numer dnia miesiąca (od 1 do 31)
  • %H - godzina w formacie 24-godzinnym
  • %m - numer miesiąca (od 1 do 12)
  • %M - minuta
  • %S - sekunda
  • %T - aktualny czas (równoważnik %H:%M:%S)
  • %Y - rok jako liczba czterocyfrowa
  • %% - znak %

Więcej kodów można znaleźć na stronie dokumentacji PHP.

Wielojęzyczny interfejs[edytuj]

Kolejnym krokiem na drodze umożliwienia obcokrajowcom przeglądania naszych witryn jest stworzenie kilku wersji językowych. Realizuje się to, tworząc nakładki językowe, w których poszczególnym komunikatom interfejsu przypisane są odpowiednie identyfikatory. Następnie w kodzie aplikacji, zamiast pisać odpowiednie teksty, wywołujemy specjalną funkcję i podajemy ID tekstu do wstawienia. Ta wprowadza w tym miejscu odpowiedni tekst w zależności od wybranego języka. Ponieważ nie znamy jeszcze programowania obiektowego, będziemy musieli napisać odpowiednią bibliotekę strukturalnie:

<?php

	$preferencje = array();
	$tekstyI18n = array();
	
	function zainstalowanyJezyk($jezyk)
	{
		// Tutaj mozemy umiescic kod sprawdzajacy, czy nasz serwis posiada
		// wersje w podanym jezyku
		switch($jezyk)
		{
			case 'pl':
			case 'de':
			case 'fr':
			case 'en':
				return true;		
		}
		return false;	
	} // end zainstalowanyJezyk();

	function uzyjJezyk($jezyk)
	{
		global $preferencje;
		
		if(zainstalowanyJezyk($jezyk))
		{		
			$preferencje['jezyk'] = $jezyk;
		}
	} // end uzyjJezyk();
	
	function wybierzGrupe($grupa)
	{
		global $preferencje, $tekstyI18n;
		
		$tekstyI18n[$grupa] = @parse_ini_file('jezyki/'.$preferencje['jezyk'].'/'.$grupa.'.php');	
	} // end wybierzGrupe();
	
	function wstaw($grupa, $id)
	{
		global $tekstyI18n;
		if(isset($tekstyI18n[$grupa][$id]))
		{
			return $tekstyI18n[$grupa][$id];
		}
		return strtoupper($grupa.':'.$id);	
	} // end wstaw();

?>

W powyższym kodzie tekstom przypisane są nie tylko identyfikatory - podzielono je także na ładowane oddzielnie grupy. W ten sposób strona z newsami może załadować sobie tylko treści komunikatów dla newsów, z pominięciem np. tekstów przeznaczonych dla forum dyskusyjnego. Oto opis funkcji:

  • zainstalowanyJezyk() - zwraca true, jeżeli podany język jest obsługiwany przez nasz skrypt.
  • uzyjJezyk() - ustawia podany język.
  • wybierzGrupe() - ładuje podaną grupę na podstawie aktualnych ustawień języka. Do wczytania grupy używamy funkcji parse_ini_file() przetwarzającej podany plik identycznym parserem, jak ten, który przetwarza php.ini. Dzięki temu zyskujemy duże możliwości małym kosztem.
  • wstaw() - wstawia tekst o podanym ID należący do odpowiedniej grupy na podstawie aktualnych ustawień językowych.

Pliki językowe należy umieszczać w katalogu jezyki/kodjezyka/nazwagrupy.php. Oto przykładowy plik jezyki/pl/global.php:

komunikat = "Komunikat"
hello_world = "Witaj swiecie!"

Korzysta z niego poniższy przykład:

<?php

	require('./i18n.php');
	
	uzyjJezyk('pl');
	wybierzGrupe('global');
	
	
	echo '<h2>'.wstaw('global', 'komunikat').'</h2>';
	echo '<p>'.wstaw('global', 'hello_world').'</p>';

?>

Na początku ładujemy naszą bibliotekę i wybieramy język. Później wczytujemy grupę i wyświetlamy komunikaty. To wszystko - jesteśmy posiadaczami wielojęzycznego interfejsu. Pamiętaj, że jest to tylko przykładowy kod. Aby był on w pełni sprawny, powinieneś umożliwić użytkownikowi zmianę języka, a także zadbać o prawidłową obsługę błędów. Zwróć też uwagę, że dane obecnego systemu przechowywane są w tablicach globalnych. Nie jest to rozwiązanie najlepsze, ale na tym etapie wiedzy powinno nam wystarczyć. Kiedy poznamy programowanie obiektowe, będziesz mógł przepisać ten kod z jego wykorzystaniem, dzięki czemu zyska on na elegancji i bezpieczeństwie.

Do PHP można doinstalować także uniksowy moduł do obsługi wielojęzycznych interfejsów o nazwie gettext. Korzystanie z niego jest jednak dość ryzykowne, ponieważ wiele serwerów nie obsługuje go i aplikacje oparte o ten moduł nie będą działać.

Automatyczna detekcja języka[edytuj]

Przeglądarka internetowa to cenne źródło informacji o internaucie. Przesyła ona m.in. informację o języku, w jakim ona pracuje. Możemy przyjąć, że jest to rodzimy język użytkownika i na podstawie tego automatycznie ustawić mu odpowiednią wersję językową strony. Całość zawarta jest w $_SERVER['HTTP_ACCEPT_LANGUAGE'].

<?php

	$zainstalowane = array( // 1
		'pl' => 'polski',
		'en' => 'angielski',
		'fr' => 'francuski'	
	);
	

	$jezyki = explode(';', $_SERVER['HTTP_ACCEPT_LANGUAGE']); // 2
	$jezyki = explode(',', $jezyki[0]);
	
	$uzyty = null;
	foreach($jezyki as $jezyk)
	{
		if(isset($zainstalowane[$jezyk])) // 3
		{
			$uzyty = $jezyk;
			break;		
		}
	}
	
	if(is_null($uzyty)) // 4
	{
		$uzyty = 'en';
	}
	
	echo 'Wybrany język to '.$zainstalowane[$uzyty];

?>

Analiza powyższego skryptu:

  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 w ogóle potrzebna i możemy ją usunąć. Stąd w drugim explode pojawia się odwołanie $jezyki[0] wskazujące po prostu na pierwszą, istotną część przekazu.
  3. Pętlą przelatujemy całą tablicę. Jeśli zauważymy, że dany język jest zainstalowany, ustawiamy go i przerywamy pętlę.
  4. Jeżeli zmienna $uzyty ma w tym miejscu dalej domyślną wartość null, oznacza to, że żaden z języków użytkownika nie jest obsługiwany. W tym wypadku zmuszamy go do czytania po angielsku.

Naturalnie wypadałoby zadbać, aby powyższy skrypt był bardziej przyjazna użytkownikowi i na pierwszym miejscu sprawdzał informacje zapisane w ciastkach. Dopiero kiedy takowych nie będzie, można pobawić się w detekcję. Inaczej użytkownik nie będzie w stanie zmienić "narzuconego" mu języka nawet, jeśli jego własny będzie obsługiwany przez naszą witrynę.

Internacjonalizacja plikami .mo[edytuj]

Takie 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. Przeglądarki wysyłają informacje o języku. Jeśli użytkownik ma ustawiony w przeglądarce język polski, zobaczy polską wersję strony, jeśli nie - angielską.
  2. Przygotowuje domenę tekstową. Zmień "domena" na coś innego.
  3. Ustawia domenę na kodowanie UTF-8. Zmień "domena" na coś innego.
  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:

<?php
 echo "Polska wiadomość";
 // =
 echo _("Polska wiadomość");

Pobieramy edytor plików .po, np. Poedit i tworzymy pliki .mo (obsługa poniżej). Ważna jest także budowa struktury 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 PoEdit[edytuj]

Obsługa jest bardzo prosta - wybieramy Plik > Nowy katalog, uzupełniamy dane. Zakładka ścieżki jest bardzo ważna. Pierwsze pole NIE może być ścieżką do katalogu z naszymi plikami, lecz do wcześniejszego katalogu (np. dla D:\Serwer\www\gettext\gettext podajemy D:\Serwer\www\gettext) a w polu niżej za pomocą przycisku nr. 2 dodajemy ścieżkę D:\Serwer\www\gettext\gettext oraz jeśli potrzebne jej podkatalogi.

Testowanie[edytuj]

Aby przetestować skrypty, należy zainstalować GNU gettext i:

  • W przypadku systemu Windows - dodać php_gettext.dll do podkatalogu extensions katalogu PHP i aktywować ją w php.ini,
  • W przypadku linuxów należy ją dokompilować do php, XAMPP dla Linuksa zawiera ją wkompilowaną.

Zakończenie[edytuj]

Internacjonalizacja jest bardzo ważnym zagadnieniem. Jeśli naprawdę myślimy o ekspansji na rynki zagraniczne, nie lekceważmy jej. Czas zainwestowany w uczynienie serwisu bardziej przyjaznym obcokrajowcom w przyszłości zaprocentuje ich dobrymi wrażeniami z pobytu u nas.

Poprzedni rozdział: Internacjonalizacja
Spis treści
Następny rozdział: Data i czas

System plików[edytuj]

Przez wcześniejsze rozdziały często przewijały się nam różne funkcje odczytu danych z dysku twardego. Przyszedł czas na zebranie informacji o nich oraz ich usystematyzowanie. Pierwszą rzeczą, o której należy pamiętać, jest wydajność. Wszelkie odwołania do systemu plików są dosyć powolne i często stanowią nawet wąskie gardło w szybkości naszego kodu. Dlatego powinieneś starać się wykonywać ich tak mało, jak tylko się da i buforować wyniki działania niektórych z nich, aby późniejszy kod mógł odwoływać się do nich.

Odczyt danych[edytuj]

Zanim zaczniemy, utwórz sobie plik plik.txt z jakąś długą zawartością (najlepiej w kilku linijkach).

Zawartość pliku można odczytać w PHP na kilka sposobów. Oto pierwszy z nich, wywodzący się jeszcze z języka C:

<?php
	// 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 r nakazuje otwarcie pliku do odczytu. Następnie w pętli pobieramy plik po kawałkach o wielkości jednego kilobajta. W ten sposób dane mogą być przetwarzane "równolegle" z odczytem. Funkcja feof() służy do sprawdzenia, czy osiągnęliśmy koniec pliku. Po zakończonej pracy nasze połączenie z plikiem trzeba zamknąć. Odpowiada za to fclose().

Z powyższego kodu możemy wyrzucić pętlę i pobrać wszystko za jednym zamachem. Wystarczy tylko użyć funkcji filesize(), aby podała nam rozmiar pliku:

<?php
	// 1.1 - Otwieramy
	$_Uchwyt = fopen('plik.txt', 'r');
	
	// 1.2 - Czytamy całą zawartość pliku
	echo fread($_Uchwyt, filesize('plik.txt'));
	
	// 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 tutaj operator @, aby zagłuszyć funkcję fopen() i sprawdzić zwracany wynik. Powinna ona zwrócić nam połączenie z plikiem, tj. wartość typu Resource. Zobaczmy:

<?php
	// 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);
?>

Od PHP 4.3.0 nie trzeba już rozpisywać się, aby wczytać zawartość pojedynczego pliku. Cała czynność jest zautomatyzowana w funkcji file_get_contents(). Aby tu sprawdzić poprawność otwarcia, wystarczy porównać zwrócony wynik z wartością false, która jest zwracana w przypadku błędu:

<?php
	// 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ólnych linijek. 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>';
	// 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 gdy bę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 wykorzystaniem file_get_contents() i porównaj wrażenia.

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
	{
		// 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ć podczas czytania pliku oraz posłużenia się funkcjami typu file_exists(). Wadą takiego rozwiązania jest jednak to, że pisząc kod możemy zakopać się w 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ć.

Zapis danych[edytuj]

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

<?php
	// 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 systemie Linux/Unix sprawdź, czy PHP ma uprawnienia do edycji plików w twoim katalogu roboczym.

W PHP 5.0.0 pojawiła się funkcja file_put_contents(), która upraszcza całą sprawę. Zwraca ona liczbę zapisanych do pliku bajtów i możemy wykorzystać to do kontroli, czy operacja dopisywania faktycznie się udała. Funkcja pobiera dwa parametry: nazwę pliku oraz tekst do wpisania i "firmowo" nie zniekształca danych binarnych.

<?php
	// 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.';	
	}

?>

Zadajmy sobie pytanie, co jeśli musimy dopisać dodatkową treść. Naturalnie file_put_contents() także to potrafi. Trzeba tylko skorzystać z trzeciego parametru, w którym możemy ustawiać flagi. FILE_APPEND jest tym, czego potrzebujemy.

<?php
	// 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 plikach[edytuj]

W wielu przypadkach przydaje się wiedza o tym, co w zasadzie w katalogach mamy. Możemy ją uzyskać, korzystając z rodziny funkcji udostępniających nam różne informacje o plikach. Wszystkie przyjmują za parametr nazwę pliku:

  • is_file() - zwraca true, jeśli obiekt jest plikiem.
  • is_dir() - zwraca true, jeśli obiekt jest katalogiem.
  • is_readable() - zwraca true, jeśli posiadamy prawa do odczytu zawartości obiektu.
  • is_writeable() - zwraca true, jeśli posiadamy prawa do zapisu do obiektu.
  • file_exists() - zwraca true, jeśli plik/katalog istnieje.
  • fowner() - zwraca ID właściciela pliku.
  • fgroup() - zwraca ID grupy, do której plik należy.
  • fperms() - zwraca uprawnienia pliku.
  • filesize() - zwraca wielkość pliku.
  • filemtime() - zwraca czas ostatniej modyfikacji pliku lub false, jeśli nie istnieje.

Przy korzystaniu z nich musimy pamiętać o wydajności. Odczyt wszelkich danych z dysku jest dość powolny, dlatego starajmy się jak najwięcej wycisnąć z pojedynczego wywołania funkcji. Oto przykład: załóżmy, że mamy plik A.txt i na jego podstawie generujemy B.txt zawsze, kiedy ulegnie on zmianie (taki kompilator). Musimy zatem napisać mechanizm sprawdzający, czy można uruchomić kompilację, czy też jest ona zbędna.

<?php

	if(!file_exists('A.txt'))
	{
		die('Plik A.txt nie istnieje!');
	}

	if(file_exists('B.txt'))
	{
		if(filemtime('B.txt') != filemtime('A.txt'))
		{
			echo 'Plik A.txt wymaga kompilacji.';
		}
		else
		{
			echo 'Można czytać z pliku B.txt';
		}	
	}
	else
	{
		echo 'Plik A.txt wymaga kompilacji';
	}

?>

Pozornie wszystko wygląda na poprawne - skrypt prawidłowo raportuje wszystkie sprawy. Jednak robi to zbyt wolno, gdyż przeciążyliśmy go dużą ilością odwołań do dysku twardego. Jeżeli uruchomimy go na witrynie z dużym ruchem, osiągnąłby gorsze wyniki wydajności, niż inne skrypty. Spróbujmy go nieco zmodyfikować. Czy naprawdę potrzebujemy funkcji file_exists()? Okazuje się, że nie. Przecież filemtime() zwróci nam false, jeżeli plik nie będzie istniał i możemy to wykorzystać. Oto poprawiony kod skryptu:

<?php

	$czasA = @filemtime('A.txt');
	
	if($czasA === false)
	{	
		die('Plik A.txt nie istnieje!');
	}
	else
	{
		$czasB = @filemtime('B.txt');
	}

	if($czasB !== false)
	{
		if($czasB != $czasA)
		{
			echo 'Plik A.txt wymaga kompilacji.';
		}
		else
		{
			echo 'Można czytać z pliku B.txt';
		}	
	}
	else
	{
		echo 'Plik A.txt wymaga kompilacji';
	}

?>

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

Ścieżki dostępu[edytuj]

Wydajność i bezpieczeństwo[edytuj]

Zakończenie[edytuj]

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

Poprzedni rozdział: System plików
Spis treści
Następny rozdział: Ćwiczenia

Data i czas[edytuj]

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

Formatowanie daty[edytuj]

Podstawową funkcją do zwracania obecnej daty jest date(), która przyjmuje jako parametr wartość tekstową. I tutaj pojawia się pierwsza zagadka, wielu na początku zastanawiałoby się czemu akurat wartość tekstowa - tekst nie ma wiele wspólnego z datą.

I tutaj kłania się PHP, pomagając wielu programistom. Łańcuch ten, mówi jak należy przeformatować datę - nie trzeba pisać własnych funkcji. W tym parametrze funkcji można umieszczać własne znaczniki, mówiące jakie informacje 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ś?

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

Problemy z formatowaniem[edytuj]

Przyjmijmy, że na końcu daty chcesz dodać wyraz "rok". Wiele osób korzystając z możliwości bezpośredniego formatowania 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 parametrze funkcji 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 na poprzedzeniu liter backslashem. Trzeba pamiętać, że jeżeli format daty zostałby podany pomiędzy podwójnymi cudzysłowami, to przykładową literę "r" należałoby poprzedzić podwójnym ukośnikiem ( zrobiłby się wówczas z nich 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 innych skryptach. W poprzednim sposobie nie ma takiego komfortu.

Lista znaczników[edytuj]

d - Dzień miesiąca, 2 cyfry z wiodącymi zerami

D - Tekstowy opis angielskiej nazwy dnia, trzy litery

j - Dzień miesiąca bez zer wiodących

l (mała litera 'L') - Pełen angielski opis dnia tygodnia

N - 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 litery

w - Liczbowa forma dnia tygodnia

z - Dzień roku (Zaczynając od 0)

W - Numer tygodnia w roku, zgodny z normą ISO-8601, Tygodnie rozpoczynają Poniedziałki (dostępne od PHP 4.1.0)

F - Pełen angielski opis, dnia miesiąca, taki jak January czy March

m - Liczbowa forma miesiąca, z zerami wiodącymi

M - Krótki, angielski opis miesiąca, trzy litery

n - Liczbowa forma miesiąca, bez zer wiodących

t - Ilość dni w danym miesiącu

L - Informacja o tym, czy rok jest przestępnym

o - Numer roku, zgodny z normą ISO-8601. Zwraca to taką samą wartość jak Y, z takim wyjątkiem, że numer tygodnia 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 cyfry

y - Dwie cyfry reprezentujące rok

a - 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ących

G - Godzina, w formacie 24-godzinnym, bez zer wiodących

h - Godzina, w formacie 12-godzinnym, z zerami wiodącymi

H - Godzina, w formacie 24-godzinnym, z zerami wiodącymi

i - Minuty z zerami wiodącymi

s - Sekundy, z zerami wiodącymi

e - Identyfikator strefy czasowej (dodano w PHP 5.1.0)

I (duże i) - Informacja o tym, czy czas jest letni

O - Różnica z czasem Greenwich (GMT) w godzinach

P - Różnica z czasem Greenwich (GMT) z dwukropkiem pomiędzy godzinami i minutami (dodano w PHP 5.1.3)

T - Skrót dla strefy czasowej

Z - Różnica dla strefy czasowej w sekundach. Wyrównanie to jest zawsze ujemne dla stref położonych na zachód od poł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 2822

U - Sekundy liczone od ery UNIX-a (1 stycznia 1970 00:00:00 czasu Greenwich - GMT)

Zmiana daty[edytuj]

Wyś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óry był 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 sobie przypomnieć jednostki i wykorzystać matematykę:

 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 z pomocą PHP, dzięki funkcji strtotime(). Funkcja ta zamienia większość angielskich tekstowych opisów daty i czasu na 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 wszystko za ciebie.

Sprawdzanie daty[edytuj]

Czasem w formularzu wymagane jest sprawdzenie, czy podana data jest prawdziwa. Tutaj bardzo dobrze sprawdzi się 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 daty[edytuj]

Zauważ, ż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ńczenie[edytuj]

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

Vista-vlc.png

Sekcja " PHP/Ćwiczenia/Rozmaitości " znajduje się w budowie

Vista-vlc.png

Jeżeli chcesz rozszerzyć ten podręcznik o tę sekcję, kliknij na ten link.


Programowanie obiektowe[edytuj]

Poprzedni rozdział: Ćwiczenia
Spis treści
Następny rozdział: Klasy i obiekty

Czym jest programowanie obiektowe?[edytuj]

Dotychczas pisane przez nas skrypty miały charakter strukturalny. Programowanie strukturalne jest proste do opanowania i wystarczające dla podstawowych skryptów, jednak wraz z ich rozrastaniem się panowanie nad coraz większą liczbą funkcji staje się coraz trudniejsze. W tym rozdziale nauczysz się nowej techniki programowania zwanej programowaniem obiektowym, która jest wykorzystywana w niemal wszystkich bardziej zaawansowanych aplikacjach. 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 obiektowego[edytuj]

Programowanie zorientowane obiektowo oparte jest na zupełnie innej filozofii, niż dotychczasowe skrypty, jakie pisaliś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 i bierzemy 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 przypadkiem towaru. Nasz chlebek znajduje się w koszyku, ale na półce są jeszcze inne takie obiekty, które mogą zostać wzięte przez innych klientów. Bierzemy też masło i jakiś napój (wszystko to obiekty odpowiednich klas) i z tym wszystkim wędrujemy do obiektu kasa, któremu przekazujemy nasz koszyk. W kasie dowiadujemy się, ile pieniędzy musimy przekazać, aby pobrane obiekty stały się nasze. Po zapłaceniu odbieramy nasze obiekty i bierzemy je do domu, czyli obiektu klasy dom.

W tej krótkiej historyjce zawarliśmy wszystkie podstawowe cechy programowania obiektowego. W kodzie naszego programu tworzymy klasy, które opisują pewien rodzaj przedmiotu, definiując jego właściwości oraz zachowania. W sklepie 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 oraz identycznie się zachowują, lecz każdy koszyk jest na swój sposób wyjątkowy. Wzięliśmy obiekt koszyk o pojemności 10 litrów i aktualnie zawiera on chleb, masło i napój. Klient, który przeszedł obok nas, niesie swój własny koszyk o pojemności 15 litrów, w którym niesie warzywa, mąkę i mięso. Są to dwa różne koszyki, jednak zachowujące się tak samo.

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

W historyjce o wizycie w sklepie mówimy też o towarach, czyli obiektach klasy towar. Powiedzieliśmy także, że "chleb jest towarem". Taki związek nosi nazwę dziedziczenia i również występuje w programowaniu obiektowym. Stwierdzenie to oznacza, że chleb posiada wszystkie właściwości i zachowania towaru (cena, możliwość zmiany ceny przez pracownika), a jednocześnie dodaje nowe, specyficzne dla siebie, np. wagę czy możliwość pokrojenia bochenka na pół, co oczywiście wpłynie też na cenę. Każda klasa może dziedziczyć po dowolnej innej klasie, przejmując jej właściwości i zachowania. Dzięki temu w kasie można wszystko podliczyć. Kasa przetwarza towary, zatem interesuje ją jedynie cena bez względu na to, czy dany towar jest chlebem czy napojem. Gdyby w sklepie była krajalnica, obsługiwałaby ona wyłącznie różne rodzaje chleba - nie możemy położyć na niej napoju, ponieważ tu samo bycie towarem już nie wystarcza; obiekt, który kładziemy, musi być co najmniej 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 po prostu wartość właściwości cena, jednak niektóre rodzaje towarów mogą wymagać bardziej złożonych przeliczników. Tak będzie w przypadku warzyw, gdzie cena jest uzależniona od wagi kupowanego towaru. W klasie warzywa możemy zaznaczyć: "w zwykłym towarze po prostu braliśmy podaną cenę, jednak tutaj będziemy uwzględniać wagę". Gdy warzywa trafią do kasy, kasjer oczywiście wykona obliczanie ceny. W tym miejscu moż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 o prawdziwej 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 jest ze względów wydajnościowych stosowany w niektórych językach kompilowanych, jak C++ i wspominamy o nim jedynie 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 zastanowimy się, jak za jego pomocą opisać środowisko programu komputerowego, a w szczególności skryptu strony internetowej.

Programowanie obiektowe w aplikacjach[edytuj]

Przejście z poziomu analizy świata rzeczywistego do abstrakcyjnego środowiska programu komputerowego sprawia początkującym programistom wiele trudności. Na pierwszy rzut oka ciężko powiedzieć, co w programie powinno być klasą, ile i jakich obiektów tworzyć oraz jak rozdzielić funkcje programu między klasy, by wszystko miało ręce i nogi? 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ą zajmie trochę 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, że moduły mają akcje oraz definiujemy metody pozwalające wywołać akcję o podanej nazwie. Następnie tworzymy sobie specjalizowane klasy reprezentujące poszczególne moduły naszej strony: News, Księga gości itd. Każda klasa zawierać będzie kilka akcji związanych z danym modułem reprezentowanych przez metody. Przykładowo, w klasie Księ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ć od nich wykonania określonych akcji. W obrębie jednego żądania może zostać wykonanych kilka akcji z różnych modułów, dlatego nadzorca musi umieć także przechowywać obiekty tych, które już załadował. Działanie skryptu możemy opisać wtedy następująco:

Przypuśćmy, że chcemy wyświetlić listę wpisów w księdze gości. Dlatego tworzymy obiekt nadzorcy i przekazujemy do niego nazwę odpowiedniego modułu i akcji, po czym każemy mu rozpocząć pracę. Nadzorca załaduje odpowiednią klasę, utworzy jej obiekt i nakaże mu wykonać określoną akcję, dzięki czemu w przeglą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. Bardzo często zdarza się, że tworzymy klasę tylko po to, by mieć dokładnie jeden jej obiekt (ba, czasami nawet wymuszamy odpowiednim kodem niemożność utworzenia większej liczby obiektów). Na początku wydaje się to dziwne, ale zwróćmy uwagę na kilka rzeczy:

  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 tylko pojedynczych ich obiektów.
  2. Obiekty posiadają pola, które są niczym innym, jak kolejnym rodzajem zmiennych. Jest to wygodny sposób organizowania 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. Mając klasę, możemy skorzystać z dziedziczenia. Przecież nasza aplikacja WWW może mieć kilku różnych nadzorcó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ęconym programowaniu 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óre przenoszą te elementy do aplikacji właśnie w postaci obiektów.

Słowo końcowe[edytuj]

Przedstawione 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ć do tego, co zostało tutaj powiedziane. Jeszcze raz przypominamy: informacje, które tu poznasz, są niezbędne do rozumienia dalszej części podręcznika. Mogą wydawać się skomplikowane, ale kiedy już załapiesz, o co w tym wszystkim chodzi, Twoje życie naprawdę stanie się o wiele prostsze.

Klasy i obiekty[edytuj]

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

Tworzenie klas[edytuj]

W językach programowania klasy traktowane są zawsze jako rodzaj typów danych. Można powiedzieć, że klasa jest definicją lub szablonem obiektów. W PHP deklarujemy je słowem kluczowym class, po którym podajemy jej unikatową nazwę. Zasady jej tworzenia są podobne, jak w przypadku nazw zmiennych, tj. nie mogą one zaczynać się od cyfry. Następnie w nawiasach klamrowych umieszczamy informacje o dozwolonych polach oraz metodach, jakie klasa będzie posiadać:

 1 <?php
 2 class Person
 3 {
 4    public $name;
 5    public $surname;
 6                 
 7    public function setFullName($name, $surname)
 8    {
 9       $this->name = $name;
10       $this->surname = $surname;
11    } // end setFullName();
12                 
13    public function getFullName()
14    {
15       return $this->name.' '.$this->surname;               
16    } // end getFullName();  
17 }

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. 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 i wykonywać 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ści ję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 lub przekazujemy obiekt jako argument funkcji/metody, kopiujemy jedynie referencję, a nie obiekt. Wbrew pozorom takie zachowanie jest bardzo praktyczne. W PHP4, gdzie obiekt był jednocześnie wartością, korzystanie z programowania 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:

 1 <?php
 2 // Dolaczamy plik z nasza klasa
 3 require('./Person.php');
 4 
 5 $janusz = new Person;
 6 $janusz->setFullName('Janusz', 'Kowalski');
 7 
 8 $adam = new Person;
 9 $adam->setFullName('Adam', 'Nowak');
10 
11 echo 'Witaj, jestem '.$janusz->getFullName().'<br/>';
12 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 w taki sam sposób, jak to robiliśmy z funkcjami. Metody zawsze działają w imieniu tego obiektu, na którym zostały wywołane, a to prowadzi nas do jednej z kluczowych reguł projektowania obiektowego:

Do klasy Person nie będziemy dodawać metody w stylu polaczSieZBazaDanych(), ponieważ klasa jest definicją obiektów - przypisujemy więc właściwości i metody związane bezpośrednio z opisem człowieka. Łączenie się z bazą danych jest oddzielnym zadaniem. Co więcej, lepiej zrobić to projektując połączenie uniwersalne, na przykład w oddzielnej klasie.

Zmienne obiektowe są referencjami[edytuj]

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

1 <?php
2 function modify($value)
3 {
4    $value += 5;
5 } // end modify();
6 
7 $number = 6;
8 modify($number);
9 echo $number;

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

 1 <?php
 2 require('./Person.php');
 3 
 4 function modify($object)
 5 {
 6    $object->surname = 'Nowak';
 7 } // end modify();
 8 
 9 $janusz = new Person;
10 $janusz->setFullName('Janusz', 'Kowalski');
11 modify($janusz);
12 echo $janusz->getFullName();

Tym razem skrypt wyświetlił nam wartość Janusz Nowak, co oznacza, że zmiana stanu obiektu wprowadzona przez funkcję jest widoczna globalnie. Zachowanie jest jak najbardziej prawidłowe. Funkcja modify() operuje nie 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, że druga także zauważy zmiany.

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

Kontrola dostępu (hermetyzacja)[edytuj]

W przeciwieństwie do funkcji i programowania strukturalnego, klasy posiadają precyzyjne mechanizmy kontroli dostępu do swojego wnętrza. Proces ukrywania części funkcjonalności przed programistą nosi nazwę hermetyzacji i pomaga 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śnie ukrywając wszystkie wewnętrzne aspekty działania klasy. Interpreter będzie pilnować, aby użytkownicy nie wywołali żadnej "wewnętrznej" metody, co mogłoby doprowadzić do błędów w działaniu lub użycia jej niezgodnie z przeznaczeniem.

Poznaliśmy już jeden z modyfikatorów dostępu, public, który podaliśmy przed każdą metodą oraz polem. Jest on wyjątkowy, ponieważ w przypadku metod można go pominąć, zostawiając samo function nazwa(), a w przypadku pól zastąpić synonimem var. My jednak będziemy stosować konwencję, w której zawsze jawnie określamy widzialność elementu.

Pozostałe modyfikatory dostępu to protected oraz private. Pierwszy z nich dotyczy dziedziczenia, dlatego zajmiemy się nim później, a tymczasem przyjrzymy się drugiemu z nich. Mówi on, że dane pole lub metoda jest prywatnym elementem klasy. W praktyce oznacza to, że odwołać się do niego możemy wyłącznie z poziomu innej metody w naszej klasie, a z zewnątrz jest on niedostępny. W naszych przykładach będziemy stosować konwencję, w której prywatne elementy będą posiadać nazwy zaczynające się od podkreślenia.

 1 <?php
 2 class SomeClass
 3 {
 4    private $_field;
 5 
 6    public function getField()
 7    {
 8       // Poprawne odwołanie
 9       return $this->_field;
10    } // end getField();
11 
12 } // end SomeClass;
13 
14 $someObject = new SomeClass;
15 
16 // dostęp jest możliwy poprzez metodę
17 echo $someObject->getField();
18 
19 // błąd, próba dostępu do pola prywatnego!
20 echo $someObject->_field;

Powyższy skrypt się nie wykona. W ostatniej linijce PHP zgłosi nam błąd, jakim jest próba dostania się do prywatnego elementu klasy spoza obiektu. Jednocześnie działa odwołanie umieszczone wewnątrz metody getField(). Taki rodzaj metody zwie się po angielsku getter, a oprócz niego mamy też setter, który pozwala ustawić wartość określonemu polu. Jest to jedna z konwencji stosowanych w hermetyzacji, w myśl której klasa nie powinna, 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ście pozwala nam tworzyć pola dostępne publicznie tylko do odczytu bez możliwości ich modyfikacji. Wystarczy zadeklarować je jako prywatne i stworzyć publiczny getter, bez settera. Przepiszmy zatem naszą klasę Person zgodnie z regułami hermetyzacji, ograniczając nieco dostęp:

 1 <?php
 2 class Person
 3 {
 4    private $_name = null;
 5    private $_surname = null;
 6                 
 7    public function setName($name)
 8    {
 9       if($this->_name === null)
10       {
11          $this->_name = $name;
12          return true;
13       }
14       return false;
15    } // end setName();
16 
17    public function setSurname($surname)
18    {
19       if($this->_surname === null)
20       {
21          $this->_surname = $surname;
22          return true;
23       }
24       return false;
25    } // end setSurname();
26                 
27    public function getName()
28    {
29       return $this->_name;            
30    } // end getName();
31 
32    public function getSurname()
33    {
34       return $this->_surname;            
35    } // end getSurname();
36 
37    public function getFullName()
38    {
39       return $this->_name.' '.$this->_surname;
40    } // end getFullName();
41 } // end Person;

Teraz pola $_name oraz $_surname są prywatne, a ich wartość można odczytać wyłącznie poprzez gettery getName() 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 obiektowi nazwisko, 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żych aplikacjach liczących sobie setki klas, gdzie umożliwia to wymuszenie stosowania się do określonych konwencji i pomaga zapobiegać bałaganowi w kodzie.

Praktyczne zastosowanie[edytuj]

Spróbujmy teraz napisać kod, który będzie przydatny w aplikacji WWW. Stworzymy prosty, obiektowy i rozszerzalny 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ędzie udostę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ęści konfiguracji, dopóki nie trafi na taką, która wczyta to, czego potrzebujemy.

 1 <?php
 2 
 3 class Config
 4 {
 5    private $_config = array();
 6    private $_awaitingLoaders = array();
 7 
 8    public function get($option)
 9    {
10       // Jeśli podana opcja istnieje, zwróć jej wartość
11       if(isset($this->_config[$option]))
12       {
13          return $this->_config[$option];
14       }
15 
16       // Opcja nie istnieje, sprawdzamy, czy któraś z oczekujących ładowarek ją ma.
17       foreach($this->_awaitingLoaders as $id => $loader)
18       {
19          $this->_config = array_merge($this->_config, $loader->load());
20          unset($this->_awaitingLoaders[$id]);
21          if(isset($this->_config[$option]))
22          {
23             return $this->_config[$option];
24          }
25       }
26       return null;
27    } // end get();
28 
29    public function addLoader(ConfigLoader $loader)
30    {
31       $this->_awaitingLoaders[] = $loader;
32    } // end addLoader();
33 } // end Config;

Pojawił się tu nowy element składni: public function addLoader(ConfigLoader $loader) - przed nazwą zmiennej pojawiła się nazwa obiektu. Jest to jedno z kolejnych udoskonaleń programowania obiektowego w PHP, czyli określanie typów argumentów. Działa ono zarówno w metodach, jak i funkcjach i mówi, że dany argument może przyjąć wyłącznie obiekt klasy ConfigLoader. Próba podania dowolnego innego rodzaju wartości zakończy się błędem. W PHP określanie typów argumentów ograniczone jest wyłącznie do klasy, a od PHP 5.1 również do tablic (typ Array). Nie można wymuszać typów skalarnych (np. liczby całkowite) ani zasobów. W naszym przypadku daje to nam pewność, że programista nie będzie próbował nakarmić naszego systemu konfiguracji 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 musi o tym wiedzieć. Dla niego korzystanie z tej klasy ogranicza się do pamiętania, że musi zdefiniować przynajmniej jedną ładowarkę oraz że dostęp do opcji możliwy jest do odczytu poprzez metodę get(). Sposób wczytywania opcji nie jest już przedmiotem jego zainteresowań, dlatego wewnętrzne aspekty działania klasy oraz faktyczny sposó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:

 1 <?php
 2 
 3 class ConfigLoader
 4 {
 5    private $_fileName = '';
 6 
 7    public function setFilename($filename)
 8    {
 9       $this->_fileName = $filename;
10    } // end setFilename();
11 
12    public function load()
13    {
14       if(file_exists($this->_fileName))
15       {
16          return parse_ini_file($this->_fileName);
17       }
18 
19       // jeśli pliku nie ma, zwróć pustą tablicę
20       return array();
21    } // end load();
22 } // 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ływana przez naszą klasę Config musi zwrócić tablicę z opcjami wczytanymi z pliku.

To wszystko, spójrzmy teraz, jak wykorzystać nasz system w praktyce:

 1 <?php
 2 require('./Config.php');
 3 require('./ConfigLoader.php');
 4 
 5 $config = new Config;
 6 
 7 // utworz ladowarki wczytujace rozne fragmenty konfiguracji
 8 $basicConfig = new ConfigLoader;
 9 $basicConfig->setFilename('./config/basic.ini.php');
10 
11 $securityConfig = new ConfigLoader;
12 $securityConfig->setFilename('./config/security.ini.php');
13 
14 $layoutConfig = new ConfigLoader;
15 $layoutConfig->setFilename('./config/layout.ini.php');
16 
17 $config->addLoader($basicConfig);
18 $config->addLoader($securityConfig);
19 $config->addLoader($layoutConfig);
20 
21 // zaladujmy pare opcji
22 echo $config->get('website_name');
23 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. Ponadto przydałoby się, aby system potrafił odpowiednio raportować błędy. Póki co, jeśli pomylimy się w nazwie pliku konfiguracji, dowiemy się o tym dopiero po wnikliwym śledztwie, ponieważ jedynym śladem w ładowarce jest zwrócenie pustej tablicy. Odpowiednie mechanizmy poznamy w dalszej części podręcznika.

Stworzony system konfiguracji wykorzystuje jeszcze jedną technikę programistyczną o nazwie kompozycja. Jest to alternatywny do dziedziczenia sposób rozszerzania funkcjonalności obiektów, który jednak nie wymaga żadnej dodatkowej składni. Obrazowo mówiąc, polega on na tym, że jeden obiekt przechowuje referencje do innych obiektów, które potrafi wykorzystywać lub których funkcjonalność potrafi udostępnić na zewnątrz. W przeciwieństwie do dziedziczenia, kompozycja ma charakter dynamiczny. Możemy napisać algorytm, który w locie skomponuje 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ńczenie[edytuj]

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

Poprzedni rozdział: Klasy i obiekty
Spis treści
Następny rozdział: Dziedziczenie

Konstruktory i destruktory[edytuj]

Metody klas nie muszą być wywoływane wyłącznie przez programistę tworzącego dany skrypt. Istnieje pewna grupa metod, które są wywoływane automatycznie przez interpreter w momencie zajścia jakiegoś zdarzenia - metody takie nazywamy magicznymi, a w PHP możemy poznać je po tym, że ich nazwy rozpoczynają się od dwóch podkreśleń: __.

Pierwszymi magicznymi metodami, jakie poznamy, będą konstruktor i destruktor, wywoływane odpowiednio w momencie tworzenia oraz niszczenia obiektu.

Konstruktor[edytuj]

Konstruktor 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 obiekt Person znajdował się w stanie, który możemy uznać za błędny. Może się zdarzyć, że wskutek pomyłki ktoś zapomni zainicjować 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ęki konstruktorom mamy pewność, że nasz obiekt zawsze będzie poprawnie inicjowany. W naszym przypadku chcemy, aby tworzona osoba od razu posiadała imię i nazwisko.

 1 <?php
 2 class Person
 3 {
 4    private $_name = null;
 5    private $_surname = null;
 6  
 7    public function __construct($name, $surname)
 8    {
 9       $this->_name = $name;
10       $this->_surname = $surname;
11    } // end __construct();
12  
13    public function getName()
14    {
15       return $this->_name;            
16    } // end getName();
17  
18    public function getSurname()
19    {
20       return $this->_surname;            
21    } // end getSurname();
22  
23    public function getFullName()
24    {
25       return $this->_name.' '.$this->_surname;
26    } // end getFullName();
27 } // end Person;

Settery są już nam niepotrzebne. Jak pamiętamy, mogły być one wywołane tylko raz, a skoro teraz imię i nazwisko jest 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 tym wypadku pominąć tak, jak to dotąd robiliśmy. Jednak po dokonanych zmianach musimy już napisać:

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

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

Destruktor[edytuj]

Dotąd nie zajmowaliśmy się zagadnieniem niszczenia obiektów. Jest ono dość specyficzne, ponieważ PHP nie tylko nie 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 sensu niepotrzebnie 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 wszystkich prowadzących do niego referencji. W PHP mamy możliwość zaprogramowania operacji, która ma się wtedy wykonać, dzięki destruktorom. Przykładowo, gdy nasz obiekt reprezentuje otwarty plik, w destruktorze możemy go automatycznie zamknąć. Destruktor jest metodą o nazwie __destruct(), która nie może ani pobierać żadnych argumentów, ani też zwracać wartości. Poniższy przykład ilustruje działanie destruktorów:

 1 <?php
 2 
 3 class Destructable
 4 {
 5    public function __construct()
 6    {
 7       echo 'Obiekt klasy Destructable został stworzony.<br/>';
 8    } // end __construct();
 9 
10    public function __destruct()
11    {
12       echo 'Obiekt klasy Destructable został zniszczony.<br/>';
13    } // end __destruct();
14 } // end Destructable;
15 
16 $firstObject = new Destructable;
17 $secondObject = new Destructable;
18 unset($firstObject);
19 
20 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ńczenia pracy 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 zapamiętanie ich do czasu zniszczenia obiektu.

Więcej o niszczeniu obiektów[edytuj]

Podczas 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 w swoim 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ę wykrywania cyklicznych referencji. Odśmiecacze pamięci większości języków (np. Java) potrafią poprawnie rozpoznawać takie sytuacje i mimo wszystko usunąć niedostępne obiekty, lecz PHP aż do wersji 5.3.0 pozbawiony był takiej możliwości. Większość skryptów wykonuje się krótko, dlatego zazwyczaj nikomu to nie przeszkadzało, jednak przy skomplikowanych, 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:

 1 <?php
 2 
 3 class CircularReference
 4 {
 5 	private $_secondary;
 6 	private $_name;
 7 	
 8 	public function __construct($name)
 9 	{
10 		$this->_name = $name;
11 	} // end __construct();
12 	
13 	public function setSecondary(CircularReference $object)
14 	{
15 		$object->_secondary = $this;
16 		$this->_secondary = $object;
17 	} // end setSecondary();
18 	
19 	public function __destruct()
20 	{
21 		echo 'Obiekt '.$this->_name.' znika.<br/>';
22 	} // end __destruct();
23 } // end CircularReference;
24 
25 // Tworzymy pierwszy obiekt i zapamietujemy referencje w $a
26 $a = new CircularReference('A');
27 
28 // Dodajemy drugi obiekt, lecz nie zostawiamy sobie referencji
29 // Powstaje nam cykl: A ma dostep do B, B ma dostep do A.
30 $a->setSecondary(new CircularReference('B'));
31 
32 // Usun jedyna posiadana referencje
33 unset($a);
34 
35 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 automatyczne wykrywanie cykli może być włączone w pliku php.ini lub poprzez wywołanie funkcji gc_enable(), lecz najprawdopodobniej 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 to wymusić, przerabiając lekko nasz skrypt. Aby PHP nie zawalił nas komunikatami o niszczonych obiektach, dodajmy flagę 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:

 1 <?php
 2 // Uwaga: tylko PHP 5.3.
 3 
 4 class CircularReference
 5 {
 6 	private $_secondary;
 7 	private $_name;
 8 	private $_noMsg;
 9 	
10 	public function __construct($name, $noMsg = false)
11 	{
12 		$this->_name = $name;
13 		$this->_noMsg = $noMsg;
14 	} // end __construct();
15 	
16 	public function setSecondary(CircularReference $object)
17 	{
18 		$object->_secondary = $this;
19 		$this->_secondary = $object;
20 	} // end setSecondary();
21 	
22 	public function __destruct()
23 	{
24 		if(!$this->_noMsg)
25 		{
26 			echo 'Obiekt '.$this->_name.' znika.<br/>';
27 		}
28 	} // end __destruct();
29 } // end CircularReference;
30 
31 gc_enable();
32 
33 // Tworzymy pierwszy obiekt i zapamietujemy referencje w $a
34 $a = new CircularReference('A');
35 
36 // Dodajemy drugi obiekt, lecz nie zostawiamy sobie referencji
37 // Powstaje nam cykl: A ma dostep do B, B ma dostep do A.
38 $a->setSecondary(new CircularReference('B'));
39 
40 // Usun jedyna posiadana referencje
41 unset($a);
42 
43 // Tworz duzo obiektow z cyklami
44 for($i = 0; $i < 10000; $i++)
45 {
46 	$a = new CircularReference('A', true);
47 	$a->setSecondary(new CircularReference('B', true));
48 }
49 
50 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 cykli możemy wymusić, wywołując funkcję gc_collect_cycles(). Przerób powyższy skrypt, usuwając pętlę i zastępując ją wywołaniem tej funkcji. Zauważysz, że nasze obiekty ponownie zostały prawidłowo usunięte przed końcem pracy.

Zakończenie[edytuj]

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

Poprzedni rozdział: Konstruktory i destruktory
Spis treści
Następny rozdział: Interfejsy

Dziedziczenie klas[edytuj]

W 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 klas dziedziczonych, co oznacza, że możemy ich użyć tam, gdzie program spodziewa się dostać ogólniejszy obiekt.

Implementacja dziedziczenia[edytuj]

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

 1 <?php
 2 
 3 class FormElement
 4 {
 5    private $_name;
 6    private $_value;
 7 
 8    public function setName($name)
 9    {
10       $this->_name = $name;
11    } // end setName();
12 
13    public function getName()
14    {
15       return $this->_name;
16    } // end getName();
17    
18    public function setValue($value)
19    {
20       $this->_value = $value;
21    } // end setValue();
22 
23    public function getValue()
24    {
25       return $this->_value;
26    } // end getValue();
27 } // end FormElement;
28 
29 class FormInput extends FormElement
30 {
31    public function display()
32    {
33       echo '<input type="text" name="'.$this->getName().'" value="'.htmlspecialchars($this->getValue()).'" />';
34    } // end display();
35 } // end FormInput;
36 
37 class FormTextarea extends FormElement
38 {
39    private $_width = 60;
40    private $_height = 15;
41 
42    public function setDimensions($width, $height)
43    {
44       if($width < 1 || $height < 1)
45       {
46          return false;
47       }
48       $this->_width = $width;
49       $this->_height = $height;
50 
51       return true;
52    } // end setDimensions();
53 
54    public function display()
55    {
56       echo '<textarea name="'.$this->getName().'" rows="'.$this->_height.'" cols="'.$this->_width.'">'.htmlspecialchars($this->getValue()).'</textarea>';
57    } // end display();
58 } // 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ące wyświetlanie danego pola w określony sposób, a FormTextarea dołożył dodatkowo możliwość ustawiania rozmiarów pola tekstowego. Poniżej pokazany jest przykład wykorzystania naszych klas:

 1 <?php
 2 require('./FormElements.php');
 3 
 4 $fields = array();
 5 
 6 $field = new FormInput;
 7 $field->setName('nickname');
 8 $field->setValue('Wpisz tu swoją nazwę');
 9 
10 $fields[] = $field;
11 
12 $field = new FormTextarea;
13 $field->setName('comment');
14 $field->setDimensions(60, 5);
15 
16 $fields[] = $field;
17 
18 // Wyswietl formularz
19 foreach($fields as $field)
20 {
21    $field->display();
22 }

Kontrola dostępu w dziedziczeniu[edytuj]

Do tej pory poznaliśmy dwa modyfikatory dostępu: public oraz private. W naszym poprzednim przykładzie zauważyliśmy, że w metodzie display() odwoływaliśmy się do nazwy elementu poprzez getName(), zamiast odczytywać ją bezpośrednio z pola $_name. Wynika to z tego, że klasa pochodna (tj. FormInput, FormTextarea itd.) nie ma dostępu do prywatnych pól i metod klasy bazowej. Modyfikator private ogranicza nam dostęp do obiektó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 trzeci modyfikator, 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 dla znaczników formularzy. Wpisywanie ich na sztywno jest kiepskim pomysłem, ponieważ nie zawsze wszystkie atrybuty są potrzebne. Dlatego dodamy metodę, która przyjmie listę atrybutów jako tablicę i wyświetli tylko te niepuste, a następnie wykorzystamy ją w klasie potomnej.

 1 <?php
 2 
 3 class FormElement
 4 {
 5    protected $_name = null;
 6    protected $_value = null;
 7 
 8    public function setName($name)
 9    {
10       $this->_name = $name;
11    } // end setName();
12 
13    public function getName()
14    {
15       return $this->_name;
16    } // end getName();
17    
18    public function setValue($value)
19    {
20       $this->_value = $value;
21    } // end setValue();
22 
23    public function getValue()
24    {
25       return $this->_value;
26    } // end getName();
27 
28    protected function _buildAttributes(array $attributes)
29    {
30       $code = '';
31       foreach($attributes as $name => $value)
32       {
33          if($value !== null)
34          {
35             $code .= ' '.$name.'="'.htmlspecialchars($value).'"';
36          }
37       }
38       return $code;
39    } // end _buildAttributes();
40 } // end FormElement;
41 
42 class FormInput extends FormElement
43 {
44    public function display()
45    {
46       echo '<input'.$this->_buildAttributes(array(
47          'type' => 'text',
48          'name' => $this->getName(),
49          'value' => $this->getValue()
50       )).' />';
51    } // end display();
52 } // 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ż jego wartość była pusta i nie było sensu go wyświetlać. Ponadto tym razem możemy już z poziomu naszych klas potomnych odwoływać się bezpośrednio do $_name oraz $_value, ponieważ zadeklarowaliśmy je jako pola chronione.

Unieważnianie metod[edytuj]

PHP pozwala na tworzenie w klasach potomnych pól oraz metod, które już istnieją w klasie bazowej. Działanie to nazywa 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.

 1 <?php
 2 
 3 class FormCheckboxList extends FormElement
 4 {
 5    private $_options = array();
 6    protected $_value = array();
 7 
 8    public function setOptions(array $options)
 9    {
10       $this->_options = $options;
11       foreach($options as $name => $description)
12       {
13          $this->_value[$name] = false;
14       }
15    } // end setOptions(); 
16 
17    public function setValue($value)
18    {
19       if(!is_array($value))
20       {
21          return false;
22       }
23       foreach($value as $name => $checked)
24       {
25          if(isset($this->_options[$name]))
26          {
27             $this->_value[$name] = (boolean)$checked;
28          }
29       }
30       return true;
31    } // end setValue();
32 
33    public function display()
34    {
35       echo '<ul>';
36       foreach($this->_options as $name => $description)
37       {
38          echo '<li><input'.$this->_buildAttributes(array(
39             'type' => 'checkbox',
40             'name' => $this->_name.'['.$name.']',
41             'checked' => ($this->_value[$name] ? 'checked' : null)
42          )).' /> '.$description.'</li>';
43       }
44       echo '</ul>';
45    } // end display();
46 } // end FormCheckboxList;

Nasz nowy rodzaj elementów formularzy, FormCheckboxList, wyświetla listę checkbox-ów. Wartość elementu nie jest już pojedynczą liczbą czy tekstem, ale zbiorem wartości, który musi być odpowiednio przetworzony. Dlatego stworzyliśmy nową wersję metody setValue(), która upewnia się, że dostała tablicę, a później odpowiednio pakuje jej zawartość do wewnętrznych struktur obiektu. Zmieniliśmy także deklarację pola $_value tak, aby początkowo zawsze zawierało tablicę.

Przejrzystość wymaga, aby klasa FormElement dostała pustą metodę display(). Chcemy dzięki temu powiedzieć, że elementy formularzy mogą się wyświetlać, lecz nie precyzujemy jak. Dokładne zasady wyświetlania ustala sobie każdy rodzaj elementu z osobna. Utwórz pustą, publiczną metodę display() w klasie FormElement.

Przyjrzymy się teraz, jak podczas unieważniania zachowują się modyfikatory dostępu oraz argumenty.

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

 1 <?php
 2 
 3 class Foo
 4 {
 5 	public function metoda1($foo)
 6 	{
 7 		echo $foo;
 8 	} // end metoda1();
 9 
10 	public function metoda2($foo, $bar = '')
11 	{
12 		echo $foo;
13 	} // end metoda2();
14 
15 	public function metoda3($foo)
16 	{
17 		echo $foo;
18 	} // end metoda3();
19 } // end Foo;
20 
21 class Bar extends Foo
22 {
23 	protected $_bar = array();
24 
25 	public function metoda1($foo)
26 	{
27 		echo 'Nowe wywołanie';
28 	} // end metoda1();
29 
30 	public function metoda2($foo, $bar = 'Nowa wartość domyślna')
31 	{
32 		echo 'Nowe wywołanie';
33 	} // end metoda2();
34 
35 	public function metoda3($foo, $bar = 'Nowy argument opcjonalny')
36 	{
37 		echo 'Nowe wywołanie';
38 	} // end metoda3();
39 } // end Bar;

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

35 public function metoda3($foo, $bar)

Jeżeli nie grzebaliśmy nic przy ustawieniach raportowania błędów, które zostały zaproponowane na początku podrę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ć na przyszł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, ma prawo zakładać, że metoda3() posiada jeden wymagany argument i tak też ją wywoła. Tymczasem my beztrosko wprowadzamy mu obiekt klasy Bar, który teoretycznie powinien tu zadziałać, lecz powoduje nam niespodziewany błą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 w klasie 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 jedynie rozszerzyć istniejącą funkcjonalność, a nie całkowicie ją zastępować. Na szczęście PHP pozwala na wywołanie pierwotnej wersji metody:

1 public function metoda($argument)
2 {
3    echo 'Trochę dodatkowych operacji';
4    parent::metoda($argument);
5 } // end metoda();

Konstruktory i destruktory w dziedziczeniu[edytuj]

Konstruktory oraz destruktory zachowują się prawie identycznie, jak zwykłe metody. Gdy klasa potomna nie będzie zawierać konstruktora albo destruktora, PHP po prostu odziedziczy go po klasie bazowej. Jednak jeśli zdecydujemy się 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 w konstruktorach klas potomnych. Jest tak, ponieważ konstruktor jest wywoływany automatycznie tylko podczas tworzenia obiektu konkretnej klasy, do której należy.

Dodajmy konstruktor do klasy FormElement tak, aby można było natychmiast ustawić nazwę:

 1 <?php
 2 
 3 class FormElement
 4 {
 5    protected $_name;
 6    protected $_value;
 7 
 8    public function __construct($name)
 9    {
10       $this->_name = $name;
11    } // end __construct();
12 
13    // dalsza część klasy
14 } // 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, musimy jawnie powiedzieć, że chcemy wywołać także stary:

 1 <?php
 2 
 3 class FormCheckboxList extends FormElement
 4 {
 5    private $_options = array();
 6    protected $_value = array();
 7 
 8    public function __construct($name, array $options)
 9    {
10       parent::__construct($name);
11       $this->setOptions($options);
12    } // end __construct();
13 
14    // dalsza część klasy
15 } // 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:

 1 <?php
 2 
 3 class FormElement
 4 {
 5    protected $_name;
 6    protected $_value;
 7 
 8    public function __construct($name = null)
 9    {
10       $this->_name = $name;
11    } // end __construct();
12 
13    // dalsza część klasy
14 } // 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 abstrakcyjne[edytuj]

W naszej rzeczywistości nie wszystkie pojęcia muszą mieć praktyczne zastosowanie. Niektóre pozwalają po prostu wygodnie 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 specjalny rodzaj klasy stworzony specjalnie po to, aby po nim dziedziczyć. Tworzenie obiektów klas abstrakcyjnych jest zabronione, za to można tworzyć obiekty ich klas potomnych.

Klasę abstrakcyjną deklaruje się, dodając przed słowem class dodatkowy modyfikator abstract. Ponownie przyjrzyjmy 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:

 1 <?php
 2 
 3 abstract class FormElement
 4 {
 5    protected $_name = null;
 6    protected $_value = null;
 7 
 8    public function __construct($name)
 9    {
10       $this->_name = $name;
11    } // end __construct();
12 
13    public function setName($name)
14    {
15       $this->_name = $name;
16    } // end setName();
17 
18    public function getName()
19    {
20       return $this->_name;
21    } // end getName();
22    
23    public function setValue($value)
24    {
25       $this->_value = $value;
26    } // end setName();
27 
28    public function getValue()
29    {
30       return $this->_value;
31    } // end getName();
32 
33    abstract public function display();
34 
35    protected function _buildAttributes(array $attributes)
36    {
37       $code = '';
38       foreach($attributes as $name => $value)
39       {
40          if($value !== null)
41          {
42             $code .= ' '.$name.'="'.htmlspecialchars($value).'"';
43          }
44       }
45       return $code;
46    } // end _buildAttributes();
47 } // 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 w przypadku 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 nadaje się to dla naszej metody display(). Chcemy powiedzieć, że elementy mogą być wyświetlane, ale szczegóły pozostawiamy do zaimplementowania klasom potomnym.

Definicja ta mówi nam jeszcze jedno. Przypuśćmy, że stworzyliśmy klasę dziedziczącą po FormElement, która nie zaimplementowała metody display(). Dopóki jej nie dodamy, nowa klasa także musi być zadeklarowana jako abstrakcyjna!

Do koncepcji abstrakcji będziemy często wracać w następnych rozdziałach.

Słowo "final"[edytuj]

Słowo abstract wymusza na programiście konieczność dziedziczenia po klasie oraz unieważnienia metody. W PHP istnieje 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 bazowej[edytuj]

Przyjrzyjmy się teraz praktycznej obserwacji dotyczącej dziedziczenia. Do tej pory używaliśmy go wyłącznie jako wygodny sposób na współdzielenie kodu, jednak mechanizm ten sięga o wiele głębiej. Napiszmy klasę zarządcy dla naszego formularza:

 1 <?php
 2 
 3 class FormBuilder
 4 {
 5    protected $_elements = array();
 6 
 7    final public function addElement(FormElement $element)
 8    {
 9       $this->_elements[] = $element;
10       return $element;
11    } // end addElement();
12 
13    public function display()
14    {
15       foreach($this->_elements as $element)
16       {
17          $element->display();
18       }
19    } // end display();
20 } // end FormBuilder;

Metoda addElement() została zadeklarowana jako finalna, ponieważ nie przewidujemy możliwości zmiany sposobu 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ę na argument, który musi być obiektem klasy FormElement. Spójrzmy teraz, jak używać nowej klasy:

 1 <?php
 2 
 3 require('./FormElements.php');
 4 require('./FormBuilder.php');
 5 
 6 $form = new FormBuilder;
 7 $form->addElement(new FormInput('name'));
 8 $form->addElement(new FormInput('email'));
 9 $form->addElement(new FormTextarea('comment'))->setDimensions(70, 15);
10 
11 $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 z FormElement, posiada wszystkie jego zachowania oraz właściwości, dlatego obiekt FormInput jest jednocześnie obiektem FormElement. Przy okazji spójrzmy na następującą linijkę:

9 $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 lub metoda zwraca obiekt, możemy tuż po niej napisać -> i dostać się do jego elementów bez potrzeby stosowania zmiennej tymczasowej. Zastosowaliśmy tu pewną sztuczkę: metoda addElement() po prostu zwraca element przekazany jako argument po to, aby nie trzeba było przepisać obiektu do zmiennej, gdy chcemy ustawić jeszcze jakieś 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żna ciągnąć w nieskończoność. Technika ta nosi nazwę fluent interface i spotkamy się z nią jeszcze w dalszej części podręcznika.

Ćwiczenie[edytuj]

Aby przećwiczyć poznane wiadomości, wróćmy do przykładu z systemem konfiguracji. Nadaje się on idealnie do dalszej 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 abstrakcyjnej ConfigLoader. Określ, które metody muszą być abstrakcyjne, a które finalne oraz gdzie zastosować słowo protected. Dodaj niezbędne konstruktory i destruktory oraz interfejs fluent interface do głównej klasy. W IniFileConfigLoader dodaj możliwość wyłączenia sprawdzania, czy plik konfiguracyjny istnieje. Przetestuj swoje modyfikacje na podanym przykładzie:

1 <?php
2 
3 $config = new Config;
4 
5 $config->addLoader(new IniFileConfigLoader('./basic.ini'))->setFileExistsCheck(true);
6 $config->addLoader(new PhpConfigLoader('./extra.php'));
7 
8 echo $config->get('basic_option');
9 echo $config->get('php_option');

Zakończenie[edytuj]

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

1 <?php
2 
3 class Klasa extends A, B
4 {
5    // ...
6 } // end Klasa;

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

Poprzedni rozdział: Dziedziczenie
Spis treści
Następny rozdział: Wyjątki

Interfejsy[edytuj]

Dziedziczenie 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 nie zezwala jednak na tzw. dziedziczenie wielokrotne, czyli możliwość rozszerzenia więcej niż jednej klasy naraz:

1 <?php
2 class MultipleInheritance extends FirstClass, SecondClass
3 {
4 
5 } // end MultipleInheritance;

Przy tradycyjnej konstrukcji systemu obiektowego w języku dziedziczenie wielokrotne wprowadza wiele patologii, a jego 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.

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 metod pozbawione implementacji. Tę dostarcza dopiero klasa, dlatego mówimy, że implementuje ona interfejs. Interfejsów nie dotyczą ograniczenia dziedziczenia. Programista może jednocześnie rozszerzać inną klasę oraz obok implementować dowolną liczbę interfejsów.

Przykładowe użycie[edytuj]

Rozpatrzmy 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óre potrafił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 generatora uprawnień, aby mógł on poprawnie działać.

 1 <?php
 2 
 3 interface AclGeneratorInterface
 4 {
 5    public function generate();
 6    public function isAllowed($resource);
 7 } // end AclGenerator;
 8 
 9 class AclSystem
10 {
11    private $_permissions = array();
12 
13    public function loadGenerator(AclGeneratorInterface $generator)
14    {
15       foreach($generator->generate() as $resource => $access)
16       {
17          if(!isset($this->_permissions[$resource]))
18          {
19             if($access == 2)
20             {
21                // Tutaj dostęp będzie generowany dynamicznie
22                $this->_permissions[$resource] = $generator;
23             }
24             elseif($access == 0 || $access == 1)
25             {
26                $this->_permissions[$resource] = $access;
27             }
28          }
29       }
30    } // end loadGenerator();
31 
32    public function isAllowed($resource)
33    {
34       if(!isset($this->_permissions[$resource]))
35       {
36          return false;
37       }
38       if(is_object($this->_permissions[$resource]) && $this->_permissions[$resource] instanceof AclGeneratorInterface)
39       {
40          return $this->_permissions[$resource]->isAllowed($resource);
41       }
42       return (bool)$this->_permissions[$resource];
43    } // end isAllowed();
44 } // 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 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ć czy dany 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ładowy generator, który wczyta uprawnienia z pliku.

 1 <?php
 2 
 3 class FileGenerator implements AclGeneratorInterface
 4 {
 5    private $_role = '';
 6 
 7    public function __construct($role)
 8    {
 9       $this->_role = (string)$role;
10    } // end __construct();
11 
12    public function generate()
13    {
14       $acl = parse_ini_file('./access/'.$this->_role.'.ini');
15       foreach($acl as &$permission)
16       {
17          if($permission != 0 && $permission != 1)
18          {
19             $permission = 0;
20          }
21       }
22       return $acl;
23    } // end generate();
24 
25    public function isAllowed($resource)
26    {
27       return false;
28    } // end isAllowed();
29 } // 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. Implementowane metody muszą mieć dokładnie taki sam nagłówek, jak w interfejsie.
  2. Klasa nie może implementować dwóch (lub więcej) interfejsów, które mają metodę o tej samej nazwie (od PHP 5.3.9 może, pod warunkiem, że zduplikowane metody mają taki sam nagłówek).
  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:

 1 <?php
 2 interface GooInterface
 3 {
 4 	public function bar($arg1, $arg2);
 5 } // end GooInterface;
 6 
 7 class Foo
 8 {
 9 	public function foo()
10 	{
11 		echo 'foo';
12 	} // end foo();
13 	
14 	public function bar($joe = 'joe')
15 	{
16 		echo 'bar';
17 	} // end bar();
18 } // end Foo;
19 
20 class Bar extends Foo implements GooInterface
21 {
22 	public function joe()
23 	{
24 		echo 'joe';
25 	} // end joe();
26 } // 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óbujemy zaimplementować.

Dziedziczenie interfejsów[edytuj]

Interfejsy można również dziedziczyć dokładnie tak samo, jak klasy, przy pomocy słowa kluczowego extends. Tutaj jednak 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:

 1 <?php
 2 interface Foo
 3 {
 4 	public function foo();
 5 } // end Foo;
 6 
 7 interface Bar
 8 {
 9 	public function bar();
10 } // end Bar;
11 
12 interface Joe extends Foo, Bar
13 {
14 	public function joe();
15 } // end Joe;
16 
17 class Abc implements Joe
18 {
19 	public function foo()
20 	{
21 		echo 'foo';
22 	} // end foo();
23 	
24 	public function bar()
25 	{
26 		echo 'bar';
27 	} // end foo();
28 	
29 	public function joe()
30 	{
31 		echo 'joe';
32 	} // end foo();
33 } // end Abc;
34 
35 $class = new Abc;
36 if($class instanceof Foo)
37 {
38 	echo 'Ten obiekt implementuje interfejs Foo<br/>';
39 }
40 if($class instanceof Bar)
41 {
42 	echo 'Ten obiekt implementuje interfejs Bar<br/>';
43 }
44 if($class instanceof Joe)
45 {
46 	echo 'Ten obiekt implementuje interfejs Joe<br/>';
47 }

Interfejsy wbudowane[edytuj]

Język PHP posiada kilkanaście specjalnych interfejsów rozpoznawanych przez interpreter i przeznaczonych do dodatkowych zastosowań. Wchodzą one w skład tzw. Standard PHP Library, czyli nowej standardowej biblioteki PHP zbudowanej w całości w oparciu o programowanie obiektowe. Będziemy się z nią zapoznawać po kawałku ró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:

1 <?php
2 class Counter
3 {
4 	public $foo;
5 	protected $bar;
6 }
7 
8 $counter = new Counter;
9 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 on interpreter, że klasa, która go implementuje, jest zbiorem elementów, które można policzyć (identycznie, jak elementy w tablicy). Rozbudujmy klasę Config z pierwszego rozdziału tak, aby można było uzyskać informacje o ilości aktualnie załadowanych opcji. Zakładamy, że wykonałeś ćwiczenie z poprzedniego rozdziału dotyczące jej rozbudowy:

 1 <?php
 2 class Config implements Countable
 3 {
 4    private $_config = array();
 5    private $_awaitingLoaders = array();
 6 
 7    public function count()
 8    {
 9       return sizeof($this->_config);
10    } // end count();
11 
12    // pozostała część klasy
13 } // end Config;

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

1 <?php
2 require('./Config.php');
3 $config = new Config;
4 echo 'Ilość elementów w konfiguracji: '.sizeof($config);

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

  • 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 elementu był tablicą. Zaimplementuj wszystkie wymagane metody tak, aby odpowiednio modyfikowały pole $_attributes. Zmodyfikuj klasy odpowiednich elementów tak, aby uwzględniały dodane atrybuty w generowanym kodzie HTML. Poniżej znajduje się przykładowy plik testowy:

 1 <?php
 2  
 3 require('./FormElements.php');
 4 require('./FormBuilder.php');
 5  
 6 $form = new FormBuilder;
 7 $element = $form->addElement(new FormInput('name'));
 8 $element['class'] = 'name';
 9 $element['id'] = 'f_name';
10 
11 if(isset($element['id']))
12 {
13    unset($element['id']);
14 }
15  
16 $form->display();

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

Kiedy stosować?[edytuj]

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 na dziedziczenie, gdyż PHP nie udostępnia żadnych innych mechanizmów jej wstrzykiwania. Jednak gdy pragniemy jedynie zdefiniować listę zachowań, których oczekujemy, bez wnikania w szczegóły ich działania, interfejsy są o wiele 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 nam zależy i odpowiednio wybrać dostępne środki. Polecamy analizować obiektowo napisane skrypty i biblioteki, aby zapoznać 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 swobody wielokrotnego 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, że przyszłe wersje PHP będą oferować jeszcze jeden mechanizm do obejścia tego problemu.

Zakończenie[edytuj]

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

Poprzedni rozdział: Interfejsy
Spis treści
Następny rozdział: Elementy statyczne

Wyjątki[edytuj]

Błędy nie muszą wynikać wyłącznie z nieuwagi programisty. Może je powodować użytkownik poprzez dziwne działania, niewłaściwą konfigurację lub nawet problemy systemowe. Obsługa błędów jest to jeden z istotniejszych elementó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 linii wyświetlane przy komunikatach PHP to znakomite źródło informacji o serwerze, skrypcie oraz umiejętnościach programisty.

Ignorowanie zagrożenia wynika także z tego, że PHP przez długi czas nie miał zadowalających mechanizmów obsł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 musi obejść się smakiem. Jednak brak pliku językowego nie jest już aż tak krytyczny. Prawdopodobnie tłumacze nie zakończyli jeszcze swej pracy, dlatego tymczasowo możemy obejść problem, wczytując komunikaty w domyś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 pliku konfiguracyjnego 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 miejscu skryptu.
  3. Przerwanie pracy - gdy wystąpi błąd, dalsze wykonywanie funkcji zazwyczaj przestaje mieć sens, jednak nie zawsze chcemy przy tym przerwać cały skrypt. System obsługi błędów musi mieć możliwość przerwania tych partii 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 jej pochodnych. 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 w specjalnych blokach informujących, jak wyjątki danego rodzaju obsłużyć. Interpreter przerywa wszystkie aktualnie wykonywane funkcje, dopóki nie trafi na blok, który wie, jak obsłużyć rzucony wyjątek i jemu przekazuje sterowanie. W kodzie obsługi wyjątku możemy sprawdzić, co to jest za błąd oraz np. wyświetlić internaucie informacje o problemie.

Wyjątki w praktyce[edytuj]

Przyjrzyjmy się teraz, jak suchy opis prezentuje się w praktyce. Poniżej prezentujemy zmodyfikowaną wersję klasy FileConfigLoader z naszego systemu konfiguracji omówionego w poprzednich rozdziałach. Dotychczas brak pliku obsł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.

 1 <?php
 2  
 3 class FileConfigLoader extends FileLoader
 4 {
 5    private $_fileName = '';
 6  
 7    public function setFilename($filename)
 8    {
 9       $this->_fileName = $filename;
10    } // end setFilename();
11  
12    public function load()
13    {
14       if(file_exists($this->_filename))
15       {
16          return parse_ini_file($this->_filename);
17       }
18  
19       // jeśli pliku nie ma, rzuć wyjątek
20       throw new Exception('Cannot read the configuration file: '.$this->_filename);
21    } // end load();
22 } // end FileConfigLoader;

Wyjątki rzucamy przy pomocy słowa kluczowego throw, którego argumentem jest wyrażenie dające obiekt klasy Exception lub pochodnych. Może on być wczytany ze zmiennej lub zwrócony przez inną metodę, lecz najczęściej spotykaną konstrukcją jest podana w przykładzie throw new, która w locie tworzy potrzebny obiekt. Konstruktor klasy 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 nie znalazł żadnego bloku potrafiącego go przechwycić i obsłużyć. Przechwytywaniem wyjątków zajmuje się blok try... catch:

 1 <?php
 2 require('./Config.php');
 3 require('./ConfigLoader.php');
 4  
 5 $config = new Config;
 6 
 7 try
 8 {
 9    $config->addLoader(new FileConfigLoader('./config/basic.ini.php'));
10    // nieistniejący plik
11    $config->addLoader(new FileConfigLoader('./config/unexisting.ini.php'));
12 
13    // spróbujmy odczytać nieistniejącą opcję. Jak pamiętamy, spowoduje to
14    // próbę wczytania wszystkich plików oraz odkrycie, że jeden z nich nie
15    // istnieje. Rzucony zostanie wyjątek.
16    echo $config->get('unexisting_option');
17 
18    // to się już nie wykona
19    echo $config->get('website_name');
20 }
21 catch(Exception $exception)
22 {
23    echo 'Wystąpił błąd w linii '.$exception->getLine().': '.$exception->getMessage();
24 }

Jak pamiętamy, nasz system konfiguracji obsługuje leniwe ładowanie, czyli pliki odczytywane są w momencie odwołania do nieznanej opcji. Przy tej okazji obiekt stworzony dla pliku unexisting.ini.php odkryje, że taki plik 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ć przechwycony wyjątek. Jeśli dany blok nie może odnaleźć dopasowania, interpreter wraca do przerywania pracy kolejnych funkcji i metod, dopóki nie natrafi na następny try...catch. Kiedy PHP zlokalizuje już kod wiedzący, jak obsłużyć dany wyją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. Opcja jest niedostępna. Próbujemy załadować pliki.
  3. Ładujemy plik basic.ini.php.
  4. Opcja się nie pojawiła, więc przechodzimy dalej.
  5. Ładujemy plik unexisting.ini.php
  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. Wyświetlamy komunikat i linię, w której wystąpił błąd.
  13. Kontynuujemy pracę skryptu od pierwszej linijki po bloku try ... catch.

Zagnieżdżanie bloków try[edytuj]

Bloki try... catch można zagnieżdżać. PHP próbuje dopasować rzucony wyjątek do najniższego z bloków, który potrafi go obsłużyć i tam przekierowuje działanie skryptu. Zazwyczaj aplikacja posiada jeden główny blok, którego zadaniem jest przechwycenie wszystkich wyjątków i potraktowanie ich jako błędy krytyczne. Wszędzie, gdzie chcemy 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órym miejscu skrypt ma rzucić wyjątek:

 1 <?php
 2 
 3 if(!isset($_GET['where']))
 4 {
 5    $_GET['where'] = 0;
 6 }
 7 
 8 try
 9 {
10    if($_GET['where'] == 0)
11    {
12      throw new Exception('Błąd 0');
13    }
14    echo 'Dalsza część bloku...<br/>';
15    try
16    {
17       if($_GET['where'] == 1)
18       {
19         throw new Exception('Błąd 1');
20       }
21       echo 'Dalsza część bloku podrzędnego...<br/>';
22    }
23    catch(Exception $exception)
24    {
25       echo 'Problem: '.$exception->getMessage().'<br/>';
26    }
27   
28    echo 'Dalsza część skryptu...<br/>';
29 }
30 catch(Exception $exception)
31 {
32    echo 'Błąd krytyczny: '.$exception->getMessage().'<br/>';
33 }

Wywołaj skrypt z argumentem ?where=0 oraz ?where=1. Pierwsze wywołanie rzuci wyjątek Błąd 0, który zostanie przechwycony przez główny blok, który leży najbliżej, i potraktowane jak błąd krytyczny. Wyjątek z drugiego 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 przypadku wciąż wyświetli nam się napis "Dalsza część skryptu". Instrukcje echo umieszczone tuż po throw nie wykonają się nigdy.

Pokażemy teraz przykład praktycznego zastosowania, w którym wykorzystamy klasę SplFileObject z biblioteki Standard PHP Library. Udostępnia ona obiektowy interfejs do operacji na plikach, a ewentualne błędy raportuje jako wyjątki RuntimeException. Naszym zadaniem jest wczytanie konfiguracji strony WWW oraz tekstu powitalnego:

  1. Brak konfiguracji jest krytyczny - bez niej nie jesteśmy w stanie wyświetlić strony.
  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 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 obiektu w pętli foreach, dzięki czemu w zmiennej $line pojawi się treść kolejnych linijek. Przykład pokazuje także, jak wykorzystać try ... catch jako instrukcję sterowania przepływem wykonania skryptu. Przyjrzyjmy się, jak ładowany jest tekst powitalny. Przed wejściem do sekcji krytycznej tworzymy pustą zmienną $text. W bloku try próbujemy ją 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 mamy pewność, ż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 Exception[edytuj]

Klasa Exception jest podstawą wszystkich wyjątków, jakie można rzucać w PHP. Udostępnia ona zbiór podstawowych 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óc przechwytywać tylko pewien rodzaj wyjątków pochodzących z określonego źródła:

 1 <?php
 2 
 3 class CustomException extends Exception { }
 4 
 5 try
 6 {
 7    // kod ...
 8 
 9 }
10 catch(CustomException $exception)
11 {
12   // Przechwytujemy jedynie wyjątki CustomException, a
13   // innymi się nie zajmujemy.
14 }

Konstruktor klasy Exception przyjmuje do trzech argumentów:

  1. Komunikat $message
  2. Kod błędu $code
  3. Poprzedni wyjątek $previous

Ponadto mamy do dyspozycji szereg metod służących do pobierania informacji o wyjątku, gdy już go przechwycimy:

  • 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.

Biblioteka SPL dostarcza zbiór gotowych klas wyjątków dla najczęstszych problemów. Programista powinien je stosować 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.

Dobrą praktyką jest pisanie tzw. kodu idiotoodpornego, który nie sypie się przy podaniu niewłaściwych danych, lecz rozsądnie to sygnalizuje, dzięki czemu reszta aplikacji ma szansę odpowiednio zareagować. Nietrudno zauważyć, że powyż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.

Problem sprzątania[edytuj]

Przerywanie pracy w połowie skomplikowanego procesu nie zawsze jest dobrym pomysłem. Metoda mogła przydzielić sobie sporo zasobów (np. otwarte pliki), a rzucając wyjątek uniemożliwiamy jej ich zwolnienie. Może to doprowadzić do kolejnych problemów w dalszej części skryptu lub nawet uszkodzenia danych. Problem ten znany jest pod nazwą problemu sprzątania, czyli jak zwolnić przydzielone zasoby po rzuceniu wyjątku. Niektóre języki oferują 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 jej jednak 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 go ponownie:

 1 public function complexMethod()
 2 {
 3    try
 4    {
 5       echo 'Skomplikowane obliczenia';
 6 
 7       // domyślne sprzątanie.
 8       $this->_cleanObject();
 9    }
10    catch(Exception $exception)
11    {
12       $this->_cleanObject();
13       throw $exception;
14    }
15 } // end complexMethod();

Podświetlona linijka ukazuje dopiero co przechwycony wyjątek, który rzucamy ponownie, by zajął się nim nadrzędny blok try, ponieważ my chcieliśmy tylko posprzątać.

Wyjątki w PHP[edytuj]

W PHP wyjątki pojawiły się stosunkowo późno. O ile we własnym kodzie nie ma problemu z ich stosowaniem, to większość dostępnych wbudowanych rozszerzeń nie potrafi raportować błędów przy ich pomocy. Obsługę wyjątków posiada jedynie kilka najnowszych, obiektowych modułów:

  • Standard PHP Library
  • Biblioteka PHAR
  • Biblioteka PDO

Niestety i to nie jest regułą. Twórcy PHP nie są konsekwentni i wciąż zdarza im się wprowadzać nowe rozszerzenia z 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 napisaniu specjalnej funkcji obsługi błędów i zainstalowaniu jej w skrypcie:

 1 <?php
 2 function ExceptionErrorHandler($errno, $errstr, $errfile, $errline)
 3 {
 4    if(in_array($errno, array(E_WARNING, E_RECOVERABLE_ERROR)))
 5    {
 6       throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
 7    }
 8    return false;
 9 }
10 
11 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.

Zakończenie[edytuj]

Wyjątki to elegancki mechanizm obsługi błędów, który świetnie współpracuje z programowaniem obiektowym. W następnym rozdziale powrócimy do niego z powrotem, aby omówić elementy statyczne klas, z których można korzystać bez konieczności posiadania obiektów.

Poprzedni rozdział: Wyjątki
Spis treści
Następny rozdział: Metody magiczne

Elementy statyczne[edytuj]

Poznane do tej pory mechanizmy programowania obiektowego bazowały w całości na obiektach, czyli rzeczywistych, niezależnych bytach reprezentujących odpowiednie klasy. Jednak w niektórych sytuacjach tworzenie obiektu tylko po to, by móc wykonać jakąś metodę jest przerostem formy nad treścią. Dlatego PHP, podobnie jak większość innych języków obiektowych, oferuje możliwość tworzenia tzw. elementów statycznych klasy. Do dział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óre właściwości obiektów.

Tworzenie statycznych pól oraz metod[edytuj]

Aby utworzyć statyczne pole lub metodę, dodajemy do jego deklaracji słowo kluczowe static. Następnie możemy wykorzystać 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, ile obiektów klasy zostało już utworzonych.

 1 <?php
 2 
 3 class TypicalClass
 4 {
 5    private $_value;
 6    static private $_objectCount = 0;
 7 
 8    public function __construct($value)
 9    {
10       $this->_value = $value;
11       self::$_objectCount++;
12    } // end __construct();
13 
14    public function getValue()
15    {
16       return $this->_value;
17    } // end getValue();
18 
19    static public function getObjectCount()
20    {
21       return self::$_objectCount;
22    } // end getObjectCount();
23 } // end TypicalClass;
24 
25 $object1 = new TypicalClass('foo');
26 $object2 = new TypicalClass('bar');
27 $object3 = new TypicalClass('joe');
28 
29 echo 'Do tej pory utworzona została następująca ilość ';
30 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ływane w kontekście obiektu, dlatego nie można w nich korzystać ze zmiennej specjalnej $this. Pomimo tego, PHP dopuszcza ich wywoływanie na dwa sposoby:

1 // sposob 1
2 SomeClass::staticMethod();
3 
4 // sposob 2
5 $object = new SomeClass;
6 $object->staticMethod();

Okazuje się, że nic nie przeszkadza, aby metodę statyczną wywołać jak zwykłą, lecz nie będzie to miało żadnego znaczenia. 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 powoduje wygenerowanie komunikatu E_STRICT.

Statyczne elementy w dziedziczeniu[edytuj]

Elementy statyczne także podlegają dziedziczeniu. Z poziomu klasy B rozszerzającej A możemy dostać się do wszystkich 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 aktualnej oraz klas pochodnych. Przydaje się to, gdy potrzebujemy zamknąć jakąś często wykonywaną operację w postaci funkcji, 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 pewnego efektu 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 konstruktor oraz 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ł obiekt jednej z klas rozszerzających wybieranej dynamicznie. Najogólniej ujmując, rozwiązanie polega na utworzeniu statycznej metody factory(), która tworzy nowy obiekt, konfiguruje go i udostępnia skryptowi. Nasza uproszczona 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.

 1 <?php
 2 
 3 class BaseItem
 4 {
 5    static protected $_instances = 0;
 6 
 7    private function __construct()
 8    {
 9       echo 'Tworzymy obiekt podstawowy<br/>';
10    } // end __construct();
11 
12    public function doSomeConfig()
13    {
14       echo 'Konfigurujemy obiekt '.((get_class($this) == 'BaseItem') ? 'podstawowy' : 'specjalny').'<br/>';
15    } // end doSomeConfig();
16 
17    public function work()
18    {
19       echo 'Ja działam: '.get_class($this).'<br/>';
20    } // end work();
21 
22    public static function factory()
23    {
24       $object = new BaseItem;
25       $object->doSomeConfig();
26       self::$_instances++;
27 
28       return $object;
29    } // end factory();
30 } // end BaseItem;
31 
32 class SpecialItem extends BaseItem
33 {
34    private function __construct()
35    {
36       echo 'Tworzymy obiekt specjalny<br/>';
37    } // end __construct();
38 
39    public static function factory()
40    {
41       $object = new SpecialItem;
42       $object->doSomeConfig();
43       parent::$_instances++;
44 
45       return $object;
46    } // end factory();
47 } // end SpecialItem;
48 
49 $baseObject = BaseItem::factory();
50 $specialObject = SpecialItem::factory();
51 
52 $baseObject->work();
53 $specialObject->work();

Zwróćmy uwagę, że obie klasy posiadają prywatny konstruktor. Oznacza to, że programista może utworzyć ich obiekty 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 BaseItem zadeklarowany został chroniony licznik obiektów współdzielony także przez wszystkie klasy rozszerzające. Jednak rozszerzają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 w rzeczywistości obiekty BaseItem. Musimy nadpisać wspomnianą metodę, zmieniając tę jedną linijkę i pozostawiając resztę 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ę klasy obiektu 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[edytuj]

PHP 5.1.0 wprowadził koncepcję stałych klasowych. Zachowują się one dokładnie tak samo, jak poznane już zwykłe stał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 jej nazwę 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 musimy obawiać się o konflikt nazw oraz tworzyć bardzo długich, złożonych identyfikatorów. Przykład zastosowania podany jest poniżej:

 1 <?php
 2 
 3 class File
 4 {
 5    const READ = 1;
 6    const WRITE = 2;
 7 
 8    private $_ptr;
 9    private $_fileName;
10    private $_mode;
11 
12    public function __construct($fileName, $mode = self::READ)
13    {
14       $this->_fileName = $fileName;
15       $this->_mode = $mode;
16 
17       if(!file_exists($fileName))
18       {
19          throw new FileException('Podany plik nie istnieje: '.$fileName);
20       }
21 
22       switch($mode)
23       {
24          case self::READ:
25             $this->_ptr = fopen($fileName, 'r');
26             break;
27          case self::WRITE:
28             $this->_ptr = fopen($fileName, 'w');
29             break;
30          case self::READ | self::WRITE:
31             $this->_ptr = fopen($fileName, 'rw');
32       }
33    } // end __construct();
34 
35    // pozostałe metody
36 } // end File;
37 
38 try
39 {
40    $file = new File('./plik.txt', File::READ);
41 
42    // inne działania
43 }
44 catch(FileException $exception)
45 {
46    die('Nie można otworzyć pliku');
47 }

W przykładzie tworzymy klasę File, która ma reprezentować otwarty plik. Chcielibyśmy w przejrzysty sposób reprezentować tryby otwarcia, dlatego utworzyliśmy dla nich kilka stałych klasowych wykorzystywanych jako binarne 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 za pośrednictwem nazwy interfejsu, jak i nazw klas go implementujących.

Późne wiązanie statyczne[edytuj]

Wróćmy do naszej fabryki. Ma ona pewien drobny mankament. Jeśli programista chce utworzyć więcej klas pochodnych, musi dla każdej z nich od zera napisać fabrykę. Pół biedy, gdy jest ona prosta, ale wyobraźmy sobie, że składa się z ponad 100 linii kodu. Jaką mamy gwarancję, że programista nie popsuje czegoś? Co zrobić, gdy będziemy chcieli dodać funkcjonalność do klasy bazowej, która będzie musiała być uwzględniona przez wszystkie fabryki 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 od jego konfiguracji. Dlatego wprowadźmy dodatkową, chronioną metodę _concreteFactory(), której zadaniem jest wyłącznie utworzenie obiektu i przekazanie go ogólnej fabryce, która dokona reszty. Programista musi jedynie zmodyfikować _concreteFactory(), pozostawiając konfigurację w naszych rękach.

 1 <?php
 2 
 3 class BaseItem
 4 {
 5    static protected $_instances = 0;
 6 
 7    private function __construct()
 8    {
 9       echo 'Tworzymy obiekt podstawowy<br/>';
10    } // end __construct();
11 
12    public function doSomeConfig()
13    {
14       echo 'Konfigurujemy obiekt '.((get_class($this) == 'BaseItem') ? 'podstawowy' : 'specjalny').'<br/>';
15    } // end doSomeConfig();
16 
17    public function work()
18    {
19       echo 'Ja działam: '.get_class($this).'<br/>';
20    } // end work();
21 
22    public static function factory()
23    {
24       $object = self::_concreteFactory();
25       $object->doSomeConfig();
26       self::$_instances++;
27 
28       return $object;
29    } // end factory();
30 
31    protected static function _concreteFactory()
32    {
33       return new BaseItem;
34    } // end _concreteFactory();
35 } // end BaseItem;
36 
37 class SpecialItem extends BaseItem
38 {
39    private function __construct()
40    {
41       echo 'Tworzymy obiekt specjalny<br/>';
42    } // end __construct();
43 
44    protected static function _concreteFactory()
45    {
46       return new SpecialItem;
47    } // end _concreteFactory();
48 } // end SpecialItem;
49 
50 $baseObject = BaseItem::factory();
51 $specialObject = SpecialItem::factory();
52 
53 $baseObject->work();
54 $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 skryptu jest:

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 metody SpecialItem::_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(), od razu powiązał ją na sztywno z _concreteFactory() w tej samej klasie, bez oglądania się na dziedziczenie. W pierwszym rozdziale o programowaniu obiektowym wspominaliśmy o polimorfiźmie oraz o tym, że w PHP wszystkie metody są polimorficzne z definicji i nie trzeba się tym przejmować. Doprecyzujmy teraz: wszystkie niestatyczne metody są polimorficzne, a w przypadku statycznych, musimy skorzystać z tzw. późnego wiązania statycznego (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łamy BaseItem::factory() czy SpecialItem::factory(). Dokonujemy tego poprzez zastąpienie słowa kluczowego self przez static:

 1 <?php
 2 
 3 class BaseItem
 4 {
 5    static protected $_instances = 0;
 6 
 7    private function __construct()
 8    {
 9       echo 'Tworzymy obiekt podstawowy<br/>';
10    } // end __construct();
11 
12    public function doSomeConfig()
13    {
14       echo 'Konfigurujemy obiekt '.((get_class($this) == 'BaseItem') ? 'podstawowy' : 'specjalny').'<br/>';
15    } // end doSomeConfig();
16 
17    public function work()
18    {
19       echo 'Ja działam: '.get_class($this).'<br/>';
20    } // end work();
21 
22    public static function factory()
23    {
24       $object = static::_concreteFactory();
25       $object->doSomeConfig();
26       self::$_instances++;
27 
28       return $object;
29    } // end factory();
30 
31    protected static function _concreteFactory()
32    {
33       return new BaseItem;
34    } // end _concreteFactory();
35 } // end BaseItem;
36 
37 class SpecialItem extends BaseItem
38 {
39    private function __construct()
40    {
41       echo 'Tworzymy obiekt specjalny<br/>';
42    } // end __construct();
43 
44    protected static function _concreteFactory()
45    {
46       return new SpecialItem;
47    } // end _concreteFactory();
48 } // end SpecialItem;
49 
50 $baseObject = BaseItem::factory();
51 $specialObject = SpecialItem::factory();
52 
53 $baseObject->work();
54 $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.

Zastosowanie[edytuj]

Programiści najczęściej wykorzystują elementy statyczne do tworzenia dodatkowych mechanizmów związanych z inicjacją 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 i traktowanie 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 lokalnej przestrzeni funkcji. W aplikacjach obiektowych korzystanie z global jest traktowane jako zła praktyka, która może prowadzić do nieprzewidzianych zachowań. Wszystko powinno być udostępniane za pośrednictwem obiektowych interfejsów, które pilnują, aby w danym miejscu programista miał dostęp wyłącznie do określonych usług. Wbrew pozorom, 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órych moglibyśmy korzystać. Skoro tak, to trzeba go napisać:

 1 <?php
 2 
 3 class Registry
 4 {
 5    private static $_objects = array();
 6 
 7    public static function set($name, $value)
 8    {
 9       if(!is_object($value))
10       {
11          throw new RuntimeException('Trying to assign a non-object value to '.$name.' in the registry.');
12       }
13       self::$_objects[$name] = $value;
14    } // end set();
15 
16    public static function get($name)
17    {
18       if(!isset(self::$_objects[$name]))
19       {
20          throw new OutOfBoundsException($name.' is not a valid registry key.');
21       }
22       return self::$_objects[$name];
23    } // end get();
24 } // end Registry;
25 
26 $config = new Config;
27 $config->addLoader(new FileConfigLoader('./config.ini.php'));
28 
29 Registry::set('config', $config);
30 
31 // gdzies indziej
32 
33 $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óżne ograniczenia. Nasza klasa Registry może przechowywać wyłącznie obiekty. Tablice oraz wartości skalarne nie są dozwolone.

Zakończenie[edytuj]

Elementy statyczne to potężne narzędzie, jednak należy z niego korzystać rozważnie. Ponieważ zachowują się one bardziej jak funkcje i zwykłe zmienne, znacznie trudniej je testować. Zawsze dwa razy zastanów się, czy dana metoda lub pole naprawdę musi być statyczne, zanim dopiszesz do jego prototypu słowo static.

Poprzedni rozdział: Elementy statyczne
Spis treści
Następny rozdział: Iteratory

Metody magiczne[edytuj]

W 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, a nie programista. Można je poznać po tym, że ich nazwy zaczynają się od dwóch podkreśleń, co oznacza, że dwie takie metody powinniśmy już kojarzyć. Są to __construct() oraz __destruct() wywoływane automatycznie przy tworzeniu i niszczeniu obiektu. To jednak tylko czubek góry lodowej.

Dostęp do pól obiektu[edytuj]

Gdy próbujemy odwołać się do nieistniejącego pola klasy, PHP zazwyczaj generuje komunikat E_NOTICE. Dzięki metodom __get() oraz __set() możemy zaprogramować własną akcję i wykorzystać to do swoich celów.

Przyjrzyjmy się naszemu stale ulepszanemu systemowi konfiguracji. Odwoływanie się do opcji konfiguracyjnych przez $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ą:

 1 <?php
 2 class Config implements Countable
 3 {
 4    private $_config = array();
 5    private $_awaitingLoaders = array();
 6  
 7    public function __get($name)
 8    {
 9       return $this->get($name);
10    } // end __get();
11  
12    // pozostała część klasy
13 } // end Config;
14 
15 $config = new Config;
16 $config->addLoader(new FileConfigLoader('./config.ini.php'));
17 echo $config->websiteTitle;

Metoda __get wywoływana jest w podświetlonej linijce, gdy próbujemy dostać się do nieistniejącego pola. PHP pobiera 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 polu jakiejś wartości.

Wywoływanie metod[edytuj]

Magiczne metody potrafią także przechwytywać wywołania metod. Zasada działania jest analogiczna - jeśli PHP stwierdzi, ż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 programista pró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 dodatkowe czynnoś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 panelu administracyjnego 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ż w trakcie 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 że napiszemy 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ła obsługiwać zdarzenia. Przy okazji napiszemy też korzystającą z niego klasę User:

 1 <?php
 2 interface EventDispatcher
 3 {
 4    public function addEventHandler(EventHandler $handler);
 5 }
 6 
 7 class User implements EventDispatcher
 8 {
 9    private $_handlers = array();
10 
11    public function addEventHandler(EventHandler $handler)
12    {
13       $this->_handlers[] = $handler;
14    } // end addEventHandler();
15 
16    public function addUser($login, $password)
17    {
18       foreach($this->_handlers as $handler)
19       {
20          $handler->onAdd($login, $password);
21       }
22       echo 'Użytkownik '.$login.' został dodany<br/>';
23    } // end addUser();
24 } // 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 __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.

 1 <?php
 2 
 3 class EventHandler
 4 {
 5    protected function _checkConditions()
 6    {
 7       return true;
 8    } // end _checkConditions();
 9 
10    public function __call($name, $arguments)
11    {
12       if($this->_checkConditions())
13       {
14          $name = '_'.$name.'Event';
15          if(method_exists($this, $name))
16          {
17             $this->$name($arguments);
18          }
19       }
20    } // end __call();
21 } // end EventHandler;

Zwróćmy uwagę na linijkę 17 - oto odpowiedź, dlaczego nazwa pola klasy nie jest poprzedzona znakiem dolara. W tym miejscu PHP odczytuje nazwę metody do wywołania wprost ze zmiennej, dzięki czemu możemy dynamicznie decydować, co wykonać! Oczywiście warto wcześniej upewnić się, że w ogóle posiadamy odpowiednią metodę do obsł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śli stwierdzi, ż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ędzie rejestrować fakt dodania nowego użytkownika, a druga pomoże nam w organizacji konkursu, gdzie pięciu losowo wybranych nowo zarejestrowanych użytkowników wygrywa nagrodę.

 1 <?php
 2 class LoggingEvent extends EventHandler
 3 {
 4    protected function _onAddEvent($arguments)
 5    {
 6       $f = fopen('./logs/user_log.log', 'a');
 7       fwrite($f, date('r').': '.$arguments[0].' zostal zarejestrowany.');
 8       fclose($f);
 9    } // end _onAddEvent();
10 } // end LoggingEvent;
11 
12 class CompetitionEvent extends EventHandler
13 {
14    protected $_winners = 0;
15 
16    protected function _checkConditions()
17    {
18       // sprawdzmy, czy w ogole mozna jeszcze wygrywac.
19       $this->_winners = trim(file_get_contents('./winner_count.txt'));
20       return ($this->_winners < 5);
21    } // end _checkConditions();
22 
23    protected function _onAddEvent($arguments)
24    {
25       // Losuj zwyciezce z prawdopodobienstwem 0,2%
26       if(rand(0, 1000) < 2)
27       {
28          echo $arguments[