PHP/Wersja do druku

Z Wikibooks, biblioteki wolnych podręczników.

< PHP


PHP



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


[edytuj] Spis treści

[edytuj] Wprowadzenie

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

[edytuj] Instalacja

  1. Opis instalacji Etap rozwoju: 100% (w dniu {{{2}}})
  2. Apache HTTP Server Etap rozwoju: 25% (w dniu {{{2}}})
  3. MySQL 5 Etap rozwoju: 25% (w dniu {{{2}}})
  4. PHP Etap rozwoju: 100% (w dniu {{{2}}})

[edytuj] Podstawy języka

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

[edytuj] Rozmaitości

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

[edytuj] Bazy danych

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

[edytuj] Systemy szablonów

  1. Czym jest system szablonów? Etap rozwoju: 100% (w dniu {{{2}}})
  2. Smarty Etap rozwoju: 75% (w dniu {{{2}}})
  3. Open Power Template Etap rozwoju: 50% (w dniu {{{2}}})
  4. Sztuczki
  5. Studium przypadku: system newsów na Smarty
  6. Studium przypadku: system newsów na OPT

[edytuj] Programowanie obiektowe

  1. Klasy i obiekty Etap rozwoju: 25% (w dniu {{{2}}})
  2. Konstruktory i destruktory Etap rozwoju: 50% (w dniu {{{2}}})
  3. Dziedziczenie Etap rozwoju: 25% (w dniu {{{2}}})
  4. Interfejsy
  5. Wyjątki
  6. Iteratory
  7. Metody magiczne
  8. Studium przypadku: Hackowanie PDO

[edytuj] Dobra aplikacja

  1. Dlaczego nie piszemy ciurkiem? Etap rozwoju: 75% (w dniu {{{2}}})
  2. Wzorce projektowe
  3. Gdzie trzymać konfigurację? Etap rozwoju: 75% (w dniu {{{2}}})
  4. Przenośność
  5. Silnik
  6. Filtry
  7. Kontrola formularzy
  8. Autoryzacja i logowanie
  9. DAO
  10. MVC

[edytuj] Standard PHP Library

  1. Wstęp do SPL
  2. Operacje na katalogach
  3. Operacje na plikach

[edytuj] Bezpieczeństwo

  1. SQL Injection
  2. Hashowanie
  3. PHP Injection
  4. JS/HTML Injection

[edytuj] Inne

  1. Konfiguracja PHP
  2. PHP w służbie systemu
  3. Edytory PHP Etap rozwoju: 75% (w dniu {{{2}}})
  4. Pomoc
  5. Autorzy Etap rozwoju: 100% (w dniu {{{2}}})
  6. Dla twórców podręcznika Etap rozwoju: 100% (w dniu 13.04.2005)
  7. Wgrywanie plików na serwer


Spis treści Następny rozdział: Czym jest PHP

[edytuj] O podręczniku

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

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

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

Po przeczytaniu podręcznika będziesz wiedział:

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

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

Poprzedni rozdział: O podręczniku Spis treści Następny rozdział: Możliwości

[edytuj] Czym jest PHP?

Wikipedia, nasz siostrzany projekt, zawiera artykuł na temat PHP.

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

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

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

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

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

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

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

PHP jest językiem server-side, tj. pracuje po stronie serwera WWW. Przeciwieństwem są języki client-side pracujące po stronie przeglądarki użytkownika (np. JavaScript w wersji wykonywanej po stronie klienta). Aby wykorzystywać go na własnej stronie, musisz upewnić się, że twój serwer ma zainstalowaną jego obsługę. Zanim przejdziemy dalej, należy zrozumieć zasadę, na jakiej PHP generuje dynamiczne strony WWW.

Kiedy wpisujemy adres w przeglądarce internetowej, żądanie wyświetlenia strony kierowane jest do serwera HTTP zwanego także serwerem WWW. Jeśli stwierdzi na podstawie rozszerzenia pliku, że dany dokument zawiera kod PHP, serwer kieruje do jego interpretera żądanie przetworzenia podanego pliku. Interpreter wyszukuje w jego treści tzw. wstawki PHP wplecione w statyczny kod HTML i zastępuje je wynikiem ich wykonywania. Utworzony kod HTML jest zwracany serwerowi, a ten wysyła go z powrotem do internauty.

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

<html>
<body>
<?php
  echo 'Podaj hasło';
?>
</body>
</html>

To internauta zobaczy jedynie dokument o takiej treści:

<html>
<body>
Podaj hasło
</body>
</html>

Cały PHP zniknie, a na jego miejscu pojawi się utworzony przez niego kod HTML.

Dzięki pracy po stronie serwera, PHP idealnie nadaje się do tworzenia złożonych aplikacji zarządzających dużymi ilościami danych: forami dyskusyjnymi, systemami zarządzania treścią, sklepami internetowymi. Generują one odpowiedni kod HTML dla przeglądarek internautów, a w momencie, kiedy oni go przeglądają, PHP już zakończył nad nim swą pracę. Jest to bardzo istotne, ponieważ wszelkie dalsze reakcje na poczynania użytkownika należy albo pozostawić przeglądarce, albo obsłużyć je za pomocą języka JavaScript.

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

[edytuj] Historia projektu

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

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

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

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

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

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

Na podstawie Wikipedii

Poprzedni rozdział: Czym jest PHP Spis treści Następny rozdział: Jak się uczyć?

[edytuj] Możliwości

[edytuj] Budowa PHP

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

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

[edytuj] Co PHP może zrobić?

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

  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.

[edytuj] Wady PHP

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

  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 wstecznej kompatybilności z olbrzymią bazą już stworzonego kodu PHP, który musi na nowych wersjach działać, najlepiej bez przeróbek. Zmiany są wprowadzane stopniowo i dlatego każdy, kto chciałby związać swoją karierę programisty WWW z językiem PHP, powinien śledzić aktualności pojawiające się na stronach www.zend.com oraz www.php.net.

[edytuj] Popularność PHP

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

Poprzedni rozdział: Możliwości Spis treści Następny rozdział: Instalacja

[edytuj] Jak się uczyć?

Nauka PHP to długi i ciągły proces. Nie należy się spodziewać, że podręcznik ten nauczy kogokolwiek wszystkiego, ponieważ nie jest to jego założenie. Materiały pomocnicze pełnią funkcję wprowadzenia, zainteresowania i pokazania tematu, a cała reszta zależy już od konkretnego człowieka. Pamiętaj, że bez praktyki dużo nie osiągniesz. Dyskutuj z innymi programistami, analizuj cudze skrypty, podpatruj rozwiązania lub też wymyślaj własne. Eksperymenty nie niosą ze sobą żadnych poważnych skutków ubocznych i powinny być praktykowane przez każdego.

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

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

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

[edytuj] Teoria czy praktyka?

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

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

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

  • Nazewnictwo - czy stosujemy nazwy polskie, czy angielskie. W jakim stylu je zapisujemy? nazwa_nazwa czy nazwaNazwa?
  • Jednolity styl kodowania
  • Jakie biblioteki zewnętrzne wykorzystamy? W jakim stopniu?
  • Ogólna budowa całej aplikacji:
    • Jaką drogę pokonują dane podczas tworzenia strony?
    • W jaki sposób dane są przetwarzane?
    • Jak są dane wyświetlane?
    • Jak połączone są ze sobą poszczególne elementy aplikacji?
    • Czy przewidujemy w przyszłości dalszą rozbudowę projektu? Jeśli tak, jakie kroki podejmiemy, aby ją maksymalnie ułatwić?

Projekty aplikacji można sporządzać również na komputerze. Pomocne będą tu wszelkie edytory tekstu, generatory diagramów itd. Po głębszym poznaniu języka PHP można zaznajomić się również z diagramami UML wykorzystywanymi w zawodowych zespołach programistycznych.

[edytuj] Fora dyskusyjne

Niestety, nie każdy problem będziemy w stanie rozwiązać samodzielnie. Wtedy z pomocą przyjdą nam fora dyskusyjne, których dużo jest w polskim Internecie. Pamiętaj, że często aby uzyskać odpowiedź, musisz zastosować się do reguł forum. Wielu administratorów nie lubi użytkowników, którzy umieszczają tematy nie tam, gdzie trzeba oraz nie stosują się do widocznych wszędzie informacji. Zanim zadasz jakieś pytanie, poświęć przynajmniej kilka minut na zapoznanie się z opisami poszczególnych forów, wstawkami zatytułowanymi "Zanim napiszesz posta" itd. Ich przeczytanie to chwilka, ale zapoznanie się z nimi może oszczędzić ci przykrości przy kontakcie z moderatorem. Zamieszczamy ten krótki poradnik, gdyż mnóstwo początkujących programistów nie zwraca na te niby oczywiste rzeczy uwagi, co skutkuje długim oczekiwaniem na pomoc lub konfliktem z moderatorem na samym wstępie.

Zanim napiszesz post, upewnij się, że:

  • Umieszczasz go na odpowiednim forum. Niektóre serwisy posiadają podział na forum dla spraw podstawowych i zaawansowanych. Zdecydowanie zalecamy pisanie na tym pierwszym. Wciskanie się na siłę na forum dla zaawansowanych programistów z tematami "Jak połączyć się z bazą XXX" niemal na pewno zostaną źle odebrane przez internautów.
  • O niektóre sprawy w ogóle nie powinno się pytać, np. "czy można zrobić...", "czy jest jakiś skrypt", "gdzie mogę znaleźć XXX". Prawie na pewno dostaniesz link do wyszukiwarki Google ze złośliwą adnotacją. I to w zasadzie jest prawda. Z pomocą wyszukiwarki często znajdziesz odpowiedź znacznie szybciej, niż gdybyś miał oczekiwać na odpowiedź na forum. Wielu programistów traktuje nawet forum - miejsce rozwiązania problemu jako ostateczność, kiedy inne źródła zawiodły.
  • Problem nie jest wyjaśniony w jakimś artykule w serwisie. Wiele serwisów posiada bazę artykułów i porad, które wyjaśniają niektóre sprawy. Warto się z nimi zapoznać przed przystąpieniem do pisania.

Aby szybko uzyskać odpowiedź, upewnij się, że:

  • Podałeś wersje oprogramowania
  • Podałeś fragment kodu źródłowego powodujący błąd
  • W załączonym kodzie nie ma jakichś ważnych danych osobowych, haseł itd.
  • Wyjaśniłeś dokładnie, co jest z nim nie tak. Nie używaj nigdy pojedynczego zwrotu nie działa, gdyż jest on wieloznaczny i prawie zawsze zostaniesz zapytany o więcej szczegółów
  • Twój post jest poprawnie napisany pod względem ortograficznym. Zła interpunkcja lub robienie błędów w podstawowych wyrazach jest oznaką ignorancji, a tłumaczenia, że to nie jest lekcja polskiego, mogą budzić niesmak. Jeżeli nie jesteś pewny, pisz posty w edytorze tekstu ze sprawdzaniem pisowni.

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

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


Poprzedni rozdział: Jak się uczyć? Spis treści Następny rozdział: Instalacja/Apache

[edytuj] Opis instalacji

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

[edytuj] Serwer WWW

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

PHP komunikuje się z serwerem WWW na trzy sposoby:

  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.

[edytuj] Baza danych

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

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

[edytuj] Biblioteki dodatkowe

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

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

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

[edytuj] Proces instalacji

Instalacja PHP zostanie przeprowadzona w kilku etapach:

  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.

[edytuj] Alternatywny sposób instalacji

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

W Linux Debian robimy: apt-get install apache2 ;)

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

Poprzedni rozdział: Opis instalacji Spis treści Następny rozdział: Instalacja MySQL 5

[edytuj] Instalacja Apache HTTP Server

Aby wygodnie testować skrypty PHP i generowane przez nie witryny internetowe, należy postawić na własnym komputerze prywatny, testowy serwer WWW. Zadanie to jest bardzo proste i sprowadza się do zainstalowania jednego programu.

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

Dokument ten pokazuje, jak zainstalować Apache na różnych systemach operacyjnych.

[edytuj] Instalacja w systemach Unix/Linux

[edytuj] Kompilacja ze źródeł

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

Przechodzimy do katalogu z kodem źródłowym

./configure make make install

[edytuj] Pakiety

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

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

  1. Otwórz plik httpd.conf w katalogu /etc/apache lub jeśli w poprzednim nie ma za dużo treści - plik apache2.conf w tym samym katalogu
  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, będziesz mieć dostęp do znajdujących się w nim plików poprzez adres http://localhost/~nazwa_uzytkownika/.

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

[edytuj] Instalacja w systemach Windows

Pobieramy ze strony httpd.apache.org najnowszą wersję instalacyjną (MSI Installer) dla systemu Windows (w chwili pisania - 2.0.55). Uruchamiamy instalator i po trzech kliknięciach "Next" pojawia się formularz, w którym wpisujemy następujące dane:

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

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

Na następnym ekranie zaznaczamy opcję "Typical" i wybieramy miejsce, do którego ma być zainstalowany serwer: D:\Serwer. Instalator automatycznie utworzy tam katalog Apache2 i skopiuje wszystkie pliki.

Teraz serwer musimy przystosować do pracy i skonfigurować. Choć instalator utworzył nam już katalog D:\Serwer\Apache2\htdocs, z którego domyślnie odczytywane są strony, nie jest najlepszym rozwiązaniem umieszczać tam wszystkich naszych dokumentów. Pozostawimy go tylko do późniejszego zainstalowania w nim różnych menedżerów serwera (np. do zarządzania bazą danych) obsługiwanych z poziomu przeglądarki. Dla naszych własnych plików utworzymy katalog D:\Serwer\www.

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

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

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

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

Pod dyrektywą UserDir dodaj jeszcze poniższy blok:

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

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

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

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

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

[edytuj] Instalacja w systemie Mac OS X

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

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

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

Poprzedni rozdział: Apache Spis treści Następny rozdział: PHP

[edytuj] Instalacja MySQL 5.0

Jeśli chcesz stronę oddać do użytku to na serwerze pewnie już jest, ale tutaj opiszemy jak zainstalować MySQL na swoim komputerze.

[edytuj] Instalacja w systemach Unix/Linux

[edytuj] Kompilacja ze źródeł


[edytuj] Pakiety

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

[edytuj] Konfiguracja i uruchomienie MySQL pod Linuksem

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

$ mysql -u root -p

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

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

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

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

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

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

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

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

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

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

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

Teraz już możemy uruchomić serwer: Edytujemy plik /var/lib/mysql/mysqld.conf (w innych dystrybucjach np. /var/lib/mysql/mysqld.conf)i, jeśli baza będzie służyć tylko nam na naszym komputerze (a tak zakłądamy w podręczniku), należy odkomentować (usunąć znak # z początku) linijkę, w której znajduje sie zapis "skip-networking". W ten sposób odetniemy użytkownikom innych komputerów możliwość zdalnego logowania się do naszej bazy.

# su -mysql
$ /etc/rc.d/./rc.mysqld start &

Serwer powinien się uruchomić.

Jeśli chcemy zwiększyc bezpieczeństwo, możemy użyć skryptu mysql_secure_installation. Jest on dobrze opisany podczas wykonywania - prawie, że wizard. Pozostawi jedynie konto roota dla bazy, wyrzuci konta anonimowe i bazę test wraz z zawartymi w niej tabelami.

[edytuj] Instalacja w systemach Windows

Poniższy sposób instalacji dotyczy systemów Windows 2000 oraz Windows XP.

  1. Wejdź na stronę www.mysql.com
  2. W dziale "Downloads" zlokalizuj kategorię "Windows" i pobierz plik oznaczony jako "Windows (x86)" (34.9 MB). Upewnij się, że ściągasz najnowszą wersję.
  3. 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. Wybierz opcję 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. Wejdź na stronę www.mysql.com
  2. W dziale "Downloads" zlokalizuj kategorię "Windows" i pobierz plik oznaczony jako "Windows (x86)" (34,9 MB). Upewnij się, że ściągasz najnowszą wersję.
  3. 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. Zaznacz opcję "Configure the MySQL Server now" i kliknij "Finish". Uruchomione zostanie narzędzie konfiguracji serwera.
  9. Kliknij "Next", wybierz "Standard configuration" i ponownie kliknij "Next"
  10. W kolejnym kliknij na "Next" bez zaznaczania czegokolwiek.
  11. Kliknij "Execute", aby zapisać plik konfiguracyjny.

MySQL został zainstalowany. Jego jedynym użytkownikiem jest "root" z pustym hasłem. Aby uruchomić bazę, należy uruchomić plik "mysqld.exe". Możesz zatrzymywać bazę poprzez komendę "mysqladmin.exe -uroot -p shutdown". W razie problemów z "Call to undefined function mysqli_connect()" skopiuj libmysql.dll z folderu "php5" do "Apache2\bin"

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

Do zrobienia Do zrobienia:
instalacja na systemie Windows Me - jest tam inna procedura


[edytuj] Instalacja w systemie Mac OS X

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

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

[edytuj] Korzystanie z bazy

Poprzedni rozdział: Instalacja MySQL 5 Spis treści Następny rozdział: [[../../Pierwszy skrypt/]]

[edytuj] Instalacja PHP 5.2

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

[edytuj] Instalacja w systemach Unix/Linux

[edytuj] Kompilacja ze źródeł

Na początek pobieramy źródła projektu dostępne na stronie www.php.net. Rozpakowujemy je w katalogu /usr/src:

tar -xvf php-5.2.0.tar.gz

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

cd php-5.2.0

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

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

Oto opis poszczególnych opcji:

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

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

make
make install

Kopiujemy plik konfiguracyjny PHP do odpowiedniego katalogu:

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

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

  • error_reporting - ustawić na E_ALL | E_STRICT
  • doc_root - ustawiamy ścieżkę do katalogów kont, w których zamierzamy docelowo używać PHP: /home/*/www (ta sama, co w Apache)

Otwieramy plik konfiguracji serwera Apache: /etc/apache/httpd.conf. Sprawdzamy, czy program "apxs" nie dodał nigdzie linijki

LoadModule php5_module modules/libphp5.so

Jeżeli nie, dopisujemy ją. Ponadto musimy dodać

AddType application/x-httpd-php .php .php5

Aby serwer wiedział, jakie pliki należy przepuszczać przez interpreter PHP. Zapisujemy zmiany i na koniec restartujemy/uruchamiamy serwer Apache:

/usr/local/apache2/bin/apachectl start

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

<?php phpinfo(); ?>

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

Porada Opisana tutaj procedura kompilacji ze źródeł jest identyczna także dla wersji 5.2.x, a z dużą dozą prawdopodobieństwa także i dla przyszłych wydań.


[edytuj] Pakiety

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

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

  • error_reporting = E_ALL | E_STRICT - poziom raportowania błędów
  • doc_root = "/home/*/www" - katalogi kont użytkowników
  • register_globals = Off
  • magic_quotes_gpc = Off
  • magic_quotes_runtime = Off

Trzy ostatnie dyrektywy służą do zachowania kompatybilności ze skryptami pisanymi pod PHP 3 i pierwsze wersje PHP 4, aktualnie zdecydowanie odradza się korzystanie z nich nie tylko ze względów bezpieczeństwa, ale też z powodu planów ich wycofania w PHP 6. Dlatego pozostawiamy je wyłączone i tak też będziemy pisać nasze skrypty.

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

[edytuj] Instalacja w systemach Windows

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

By zacząć działać, musimy ściagnąć najnowszą wersję PHP ze strony www.php.net w wersji dla systemu Windows. W chwili powstawania tego tekstu najnowszą wersją było PHP 5.1.1 (obecna wersja to 5.2.10). Ściągnięty plik ( tzw. binarki, przykładowo wyglądające: 'php-5.2.6-Win32.zip' ) rozpakowujemy do katalogu D:/Serwer/php5/. Następnie zmieniamy rozszerzenie pliku php.ini-recommended na php.ini i zabieramy się za jego edytowanie:

  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 wprowadzamy ś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 ustawiamy, jakie dodatkowe moduły mają być ładowane przy starcie PHP. Należy usunąć średnik sprzed następujących linii:
 extension=php_gd2.dll
 extension=php_mysql.dll
 extension=php_mysqli.dll
 extension=php_pdo.dll
 extension=php_pdo_mysql.dll

Pierwszy moduł to biblioteka obsługi obrazków. Dwa następne zapewniają możliwość komunikowania się z bazą MySQL starszym skryptom PHP. Ostatnie moduły to nowa biblioteka PHP Data Objects służąca komunikacji z bazami danych skonfigurowana do działania z bazą danych MySQL. Jeżeli jakiejś linijki brakuje w twym pliku, dopisz ją.

Ostatnim krokiem jest podłączenie PHP do serwera Apache. Jeżeli zamierzamy zrobić to jako CGI, na koniec pliku konfiguracyjnego Apache dopisujemy:

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

W przypadku modułu linijek jest nieco mniej:

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

Należy jeszcze dodać jeszcze jedną linijkę:

 PHPIniDir "D:/Serwer/php5"

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

 <?php phpinfo(); ?>

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

[edytuj] Instalacja w systemie Mac OS X

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

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

Poprzedni rozdział: Instalacja/PHP Spis treści Następny rozdział: Zmienne i tablice

[edytuj] Pierwszy skrypt

W tym rozdziale napiszemy pierwszy skrypt PHP.

[edytuj] Wprowadzenie

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

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

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


  echo 'Hello world!';

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

 <?php
   echo
      'Hello world!';
 ?>

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

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

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

Zauważ, że choć w skrypcie mamy trzy komendy wyświetlenia trzech tekstów, przeglądarka wyświetli je nam w jednej linijce. To dlatego, że przecież PHP nie wysłał żadnego znacznika HTML nakazującego ładne połamanie wyniku - obejrzyj sobie źródło strony w przeglądarce i przekonaj się o tym. Oto poprawiona wersja skryptu:

<?php
   echo 'To jest tekst 1<br/>';
   echo 'To jest tekst 2<br/>';
   echo 'A to jest tekst 3<br/>';
 ?>
Porada Mieszanie kodu PHP z HTML-em spotykane jest najczęściej jedynie w prostych skryptach. W złożonych aplikacjach znacznie utrudnia jakiekolwiek modyfikacje wyglądu. W dalszych rozdziałach tego podręcznika nauczymy się korzystać z szablonów, które pomogą nam całkowicie oddzielić jedno od drugiego, lecz na razie wszystko będzie wymieszane.


[edytuj] Komentarze

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

Istnieją trzy rodzaje komentarzy:

<?php
   /*
   komentarz wieloliniowy
   może być rozbijany na wiele linijek.
   Cały ten tekst jest ignorowany przez interpreter
   */
 
    // to jest komentarz jednoliniowy - obowiązuje do końca danej linijki
   # to jest jeszcze jeden komentarz jednoliniowy
 
 ?>

Oto przykładowe zastosowanie komentarzy:

<?php
   /*
   Generator miniaturek obrazków
   Wersja: 1.0
   Autor: Adam Kowalski (adres@email.com)
   Licencja: GNU GPL
 
   Użycie: tu opis użycia...
   */
 
   // początek generowania obrazka
   echo 'Tu są jakieś komendy';
   echo 'Dużo komend...';
 
   // zmniejszanie
   echo 'Tu obrazek się zmniejsza';
 
   // wysyłanie wyniku
   echo 'Tu wysyłamy obrazek do przeglądarki';
 
 ?>

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

Poprzedni rozdział: Pierwszy skrypt Spis treści Następny rozdział: Formularze

[edytuj] Zmienne i tablice

W tym rozdziale dowiesz się, jak PHP przechowuje dane oraz pozwala na zarządzanie nimi.

[edytuj] Dane

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

 8

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

 6.454

To jest wyrażenie liczbowe reprezentujące ułamek, czyli liczbę zmiennoprzecinkową.

 0x6F44

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

 07647

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

 'To jest tekst bez znaków specjalnych'
 "To też jest tekst, ale ze znakami specjalnymi"

Powyżej mamy dwa wyrażenia reprezentujące tekst. Pomiędzy nimi istnieje istotna różnica. Apostrofy korzystają z jednego tylko znaku specjalnego: \' - pozwala on oznaczyć wewnątrz tekstu znak apostrofu. Gdybyśmy zapomnieli o poprzedzającym backslashu, PHP uznałby, że w tym momencie kończy się wyrażenie tekstowe i dalej jest już normalny skrypt.

Cudzysłów posiada więcej takich kodów formatujących:

 "\n - tak robimy zejście do nowej linijki w systemach Linux"
 "\r - tak w systemach Mac"
 "\r\n - a tak w systemach operacyjnych Windows"
 "Wyświetlimy cudzysłów: \" ..."
 "Wstawimy tabulator: \t"
 "Wstawiamy zmienną $zmienna"

W obu przypadkach, aby wyświetlić backslash, należy napisać go podwójnie:

 'Oto backslash: \\ '

Otrzymamy dzięki temu tekst:

 Oto backslash: \

Oto bardziej skomplikowane wyrażenie:

 5 + 7

Reprezentuje ono sumę dwóch mniejszych wyrażeń.

 'Tekst A '.'Tekst B'

Oto wyrażenie będące połączeniem dwóch mniejszych wyrażeń tekstowych. Znaki kropka oraz plus to tzw. operatory. Wykonują one pewne operacje na dwóch innych wyrażeniach i zwracają jakąś wartość, zatem same także stają się wyrażeniem. Podobnie, jak w matematyce, obowiązuje pewna kolejność ich wykonywania, a zmieniać ją możemy za pomocą nawiasów:

 5 * (6 + 8)
Uwaga! Wyrażeniem nazywamy dowolny element języka PHP reprezentujący jakąś informację, którą można przetwarzać.

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

<?php

   echo 7;

 ?>

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

[edytuj] Funkcje

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

<?php

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

 ?>

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

Uwaga! Uwaga!
echo oraz jego synonim print nie są funkcjami ani wyrażeniami! Są to instrukcje języka PHP i to tłumaczy, dlaczego nie musimy stosować przy nich nawiasów.

[edytuj] Zmienne

Innym pojęciem matematycznym jest zmienna reprezentująca informację nieznaną w trakcie pisania skryptu. Jest to taki pojemnik, do którego możemy w trakcie wykonywania skryptu wstawiać wszelkie informacje, zapamiętując je w ten sposób. Bez zmiennych nie można mówić o jakimkolwiek przetwarzaniu danych, ponieważ komputer musi mieć jakąś możliwość umieszczania gdzieś wyników obliczeń oraz przechowywania niezbędnych programom danych.

Każda zmienna posiada własną, unikalną nazwę, która jednoznacznie ją identyfikuje. Język PHP wymaga, aby zaczynała się ona od znaku dolara, a następnie od litery lub podkreślenia. Dalsze znaki mogą być także cyframi. Można stosować duże litery, ale dla interpretera ma to istotne znaczenie. $zmienna i $Zmienna to dwie różne zmienne. Przykłady prawidłowych nazw zmiennych:

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

Przykłady nieprawidłowych nazw:

$5, $
Porada PHP zezwala na używanie w nazwach zmiennych także znaków o kodach od 128 do 255, wśród których znajdują się m.in. polskie litery.


Aby przypisać wartość do zmiennej, należy ułożyć wyrażenie z operatorem =. Po lewej stronie umieszczamy naszą zmienną, a po prawej inne wyrażenie określające wstawianą wartość. Oto, jak wygląda to w praktyce:

<?php

   // inicjujemy zmienna $czas aktualnym czasem w sekundach od 1.1.1970
   $czas = time();

   echo 'Od 1.1.1970 minęło '.$czas.' sekund<br/>';
   echo 'Od 1.1.1970 minęło '.($czas / 60).' minut<br/>';
   echo 'Od 1.1.1970 minęło '.($czas / 3600).' godzin';

 ?>

W powyższym przykładzie stworzyliśmy zmienną $czas, wprowadzając do niej aktualny czas. Następnie wykorzystaliśmy ją w obliczeniach. Poznaliśmy w ten sposób jedno z zastosowań zmiennych. Zachowaliśmy w nich wynik działania jednej sekcji programu, aby potem używać go wielokrotnie gdzie indziej bez konieczności każdorazowego odwoływania się do funkcji i zbędnego liczenia po sto razy tego samego. Wprawdzie pobieranie aktualnego czasu nie jest czasochłonną operacją, ale na razie jesteśmy jeszcze na początkowym etapie nauki i musimy dbać o prostotę przykładów.

PHP, w przeciwieństwie do innych języków programowania, ma bardzo liberalne reguły stosowania zmiennych. W ogóle nie trzeba ich nigdzie deklarować, a interpreter sam na bieżąco dopasuje typ informacji do naszych potrzeb. Dana zmienna jest tworzona podczas pierwszego jej wykorzystania w skrypcie. Jest sporo sytuacji, w których zachowanie to jest pożądane, lecz normalnie może utrudnić ono pracę. Wyobraź sobie taką sytuację: programista potrafi najczęściej bardzo szybko pisać i od czasu do czasu zdarza mu się wcisnąć dwa klawisze w złej kolejności. Powstaje literówka. Jeżeli zostanie ona popełniona podczas wpisywania nazwy, PHP w teorii nie zgłosi tego jako błędu i programista będzie musiał spędzić dużo czasu na jej odnalezienie. Słowo "teoria" nie znalazło się tu przypadkowo. Podczas instalowania PHP wspominaliśmy o poziomach raportowania błędów. Im wyższy poziom, tym większej ilości rzeczy czepia się PHP. Na poziomie E_ALL zdefiniowanym w rekomendowanym pliku php.ini takie beztroskie podejście do zmiennych nie jest tolerowane. Tutaj PHP wymaga już, aby podczas pierwszego użycia zmiennej została jej przypisana jakaś wartość, ponieważ inaczej otrzymamy powiadomienie (ang. notice) o próbie odwołania się do nieistniejącej zmiennej. Popatrzmy sobie na ten przykład:

<?php

   echo $a * 16 + 5;

 ?>

Zmienna $a nie została tu wcześniej zadeklarowana. Otwórz swój plik php.ini, odnajdź dyrektywę error_reporting i zmień jej wartość na E_ALL | ~E_NOTICE. Wyłączysz w ten sposób wyświetlanie powiadomień. Zrestartuj serwer i uruchom powyższy skrypt. Wynikiem powinno być "5". PHP bez pytania podstawił do $a wartość neutralną 0. Przywróć teraz poprzedni poziom (E_ALL | E_STRICT) i ponownie uruchom ten skrypt. Oprócz wyniku, ujrzysz też komunikat:

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

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

<?php

   $a = 0;

   echo $a * 16 + 5;

 ?>

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

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

<?php

   $tekst = 'To jest jakiś tekst';
   $tekstMaly = strtolower($tekst);
   $tekstBezpieczny = addslashes($tekstMaly);
   echo $tekstBezpieczny;

 ?>
<?php

   $format = 'd.m.Y';
   echo date($format);

 ?>

W pierwszym skrypcie niepotrzebnie po każdym etapie przetwarzania tekstu tworzymy dla niego nową zmienną. Możemy to poprawić na dwa sposoby. Pierwszy:

<?php

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

 ?>

Drugi, w którym w ogóle opuszczamy zmienne:

<?php

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

 ?>

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

<?php
   echo date('d.m.Y');
 ?>

W tym jednak przypadku może być to uznane za kwestię dyskusyjną. Jeżeli nasz skrypt bardzo często będzie formatować różne daty, a my będziemy chcieli mieć możliwość jej prostego konfigurowania, warto pokusić się o zapisanie formatu w jakiejś zmiennej. W ten sposób poprzez zmianę jej wartości w jednym miejscu zostanie ona uwzględniona w całym skrypcie.

Nauczenie się, kiedy warto użyć zmiennych, a kiedy nie, to kwestia praktyki. W niniejszym podręczniku będziemy zwracali na tę kwestię baczną uwagę. Jeśli zajdzie potrzeba użycia zmiennych tymczasowych - wyjaśnimy, dlaczego, bowiem całkowite rezygnowanie z ich użycia także może rodzić wiele problemów.

[edytuj] Typy zmiennych

W sekcji poświęconej rodzajom danych w PHP dowiedziałeś się, że istnieje pewne rozróżnienie na tekst i liczby. Skoncentrujemy się teraz na poznaniu większej ilości typów oraz pokazaniu, jak PHP dokonuje konwersji między nimi.

Istnieją trzy kategorie typów: wielkości skalarne, typy złożone oraz typy specjalne. Dokumentacja wymienia jeszcze jedną, lecz stworzoną na jej własne potrzeby do zaznaczania niektórych rzeczy. Powiemy o niej później.

[edytuj] Wielkości skalarne

Pierwszym typem skalarnym jest liczba całkowita. Jej angielskim określeniem jest integer albo int. Może być ona zapisana w trzech systemach liczbowych: dziesiętnym, szesnastkowym albo ósemkowym:

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

Możemy także korzystać z wartości ułamkowych zwanych liczbami zmiennoprzecinkowymi (ang. floating point numbers albo skrótowo float). Przy ich zapisywaniu obowiązują reguły języka angielskiego, więc części całkowite od ułamkowych oddzielamy za pomocą kropki. Także i tu mamy do wyboru kilka sposobów zapisu:

<?php
  $a = 1.234; 
  $a = 1.2e3; 
  $a = 7E-10;
 ?>

Specjalnie wyróżniony został tzw. typ logiczny (boolean) przyjmujący jedynie wartości FALSE i TRUE. Jest on bardzo ważny, ponieważ wiele funkcji generuje właśnie w nim rezultat, czy operacja się powiodła. Wyrażenia porównawcze także generują wartości logiczne.

Ostatnim z typów skalarnych jest ciąg tekstowy (ang. string). Zdążyliśmy już powiedzieć nieco o nim, np. że istnieją dwie składnie zapisywania ciągów. Ta oparta na apostrofach posiada minimalny zestaw kodów formatujących pozwalających na wstawienie do tekstu innych apostrofów oraz ukośników wstecznych:

<?php

   echo 'To jest tekst zapisany w apostrofach. Kody formatujące pozwalają
     umieścić w tekście wyłącznie inne apostrofy: \' albo backslashe: \\. Wszystko inne,
     np. \n zostanie wyświetlone jako zwyczajny tekst';

 ?>

Uruchom powyższy skrypt i porównaj sobie wynik z przeglądarki z treścią wpisaną w apostrofy. Spróbuj usunąć backslash sprzed apostrofu i zobacz, co się stanie. Skrypt się nie uruchomi, ponieważ wystąpił błąd składni. PHP myśli, że tekst kończy się już tutaj i dalej jest inna część algorytmu, co nie jest oczywiście prawdą. Więcej możliwości formatowania posiada tekst ograniczony cudzysłowami:

<?php

   echo "To jest tekst zapisany w cudzysłowach. Za pomocą kodów formatujących możemy
    umieszczać wiele rzeczy: \" \\ \n \r \$";

 ?>

Cudzysłowy zezwalają na "proste" umieszczanie w nich wartości zmiennych (zm. tymczasowa stworzona dla zilustrowania zagadnienia):

<?php

   $czas = time();

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

 ?>

Jednak nie nadużywaj tego. Wstawianie zmiennych w ten sposób jest kilka razy wolniejsze, niż łączenie ich z ciągiem operatorem kropki.

<?php

  // mozna takze użyć apostrof
  echo "Aktualny czas w sekundach: ".time()." sek.";

 ?>

Niektórzy początkujący programiści niezbyt rozumieją ideę tej możliwości i próbują wykorzystywać ciągi do wprowadzania wartości zmiennych jako parametrów do funkcji (zm. tymczasowa użyta dla zilustrowania zagadnienia):

<?php

  $formatDaty = 'd.m.Y';
  echo date("$formatDaty");

 ?>

Unikaj takiej konstrukcji, jak ognia. Choć PHP ją akceptuje, nie jest to prawidłowe użycie tej struktury języka. Więcej... przy złożonych typach powoduje zniekształcenie danych. Jeżeli spotkasz kogoś piszącego w ten sposób, poinformuj go o tym.

[edytuj] Inne typy

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

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

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

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

<?php

   $zmienna = 4856;

   unset($zmienna);

   echo $zmienna;

 ?>

Zmiennej już nie ma, dlatego polecenie echo nie pokaże nic.

[edytuj] Konwersja typów

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

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

<?php

  // wyświetl liczbę całkowitą jako ułamek
  echo (float) 10;

 ?>

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

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

<?php

  $a = 547;

  echo 'Typ zmiennej $a to: '.gettype($a);

 ?>

[edytuj] Więcej o operatorach

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

Oto wykaz najciekawszych operatorów na początek.

Operator Nazwa Składnia Opis
/ Dzielenie wyrażenie / wyrażenie Reprezentuje wynik dzielenia. Drugie wyrażenie nie może być zerem.
% Dzielenie modulo wyrażenie % wyrażenie Reprezentuje resztę z dzielenia. Drugie wyrażenie nie może być zerem.
* Mnożenie wyrażenie * wyrażenie Reprezentuje iloczyn dwóch wyrażeń.
+ Dodawanie wyrażenie + wyrażenie Reprezentuje sumę dwóch wyrażeń.
- Odejmowanie wyrażenie - wyrażenie Reprezentuje różnicę dwóch wyrażeń.
. Łączenie (Konkatenacja) wyrażenie . wyrażenie Reprezentuje połączenie dwóch wyrażeń w ciąg tekstowy.
++ Inkrementacja (zwiększenie) $zmienna++ Reprezentuje wartość zmiennej, a następnie zwiększa ją o 1.
++ Inkrementacja (zwiększenie) ++$zmienna Reprezentuje wartość zmiennej powiększoną o 1.
-- Dekrementacja (zmniejszenie) $zmienna-- Reprezentuje wartość zmiennej, a następnie zmniejsza ją o 1.
-- Dekrementacja (zmniejszenie) --$zmienna Reprezentuje wartość zmiennej pomniejszoną o 1.

Zwróć uwagę na cztery ostatnie pozycje. Wyszczególnione zostały tam tzw. operatory jednoargumentowe, czyli takie, które operują wyłącznie na jednym wyrażeniu. W dodatku koniecznie musi być ono zmienną, ponieważ modyfikują one jej wartość, powiększając lub zmniejszając o jeden. Każdy z tych operatorów został podany podwójnie, ponieważ w zależności od tego, czy postawimy go przed, czy po zmiennej, otrzymamy nieco inne rezultaty. Porównaj:

<?php
   // najpierw składnia $zmienna++

   $zmienna = 5;

   echo 'Stan 1: '.($zmienna++).'<br/>';
   echo 'Stan 2: '.$zmienna.'<br/><br/>';

   // teraz składnia ++$zmienna
   echo 'Restart zmiennej...<br/>';
   $zmienna = 5;

   echo 'Stan 1: '.(++$zmienna).'<br/>';
   echo 'Stan 2: '.$zmienna.'<br/>';

 ?>

Skrypt ten wygeneruje nam kilka linijek:

Stan 1: 5
 Stan 2: 6

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

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

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

<?php

   $a = $b = $c = $d = $e = 5;

 ?>

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

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

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

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

<?php

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

   echo $tekst;

 ?>

Rezultat:

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

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

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

[edytuj] Tablice

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

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

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

Uwaga! Tablica to zmienna zbiorcza, grupująca pojedyncze elementy mające właściwości zmiennych, do których odwoływać możemy się za pomocą indeksów.

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

<?php

   $tablica = array();

 ?>

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

<?php

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

 ?>

Początkowe "0" określa, od jakiej liczby zacząć numerowanie kolejnych elementów. Po strzałce wymieniamy ich wartości oddzielone przecinkiem. Teraz spróbujmy dostać się do jakiejś z nich.

<?php

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

   echo 'Pod numerem 5 kryje się wartość '.$tablica[5];

 ?>

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

<?php

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

   echo 'Pod numerem 5 kryje się wartość '.$tablica[rand(0, 8)];

 ?>

W tym skrypcie za wybranie indeksu tablicy odpowiada funkcja rand() zwracająca losową[1]wartość z tablicy.

Indeksy tablicy wcale nie muszą być numeryczne. PHP dopuszcza także tekstowe wersje. Mamy wtedy do czynienia z tablicami asocjacyjnymi.

<?php

   $artykul = array(
     'tytul' => 'Tytuł artykułu',
     'data' => date('d.m.Y'),
     'tresc' => 'To jest treść artykułu'
   );

   echo '<h1>'.$artykul['tytul'].'</h1>';
   echo '<p>Napisany dnia '.$artykul['data'].'</p>';
   echo '<p>'.$artykul['tresc'].'</p>';

 ?>

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

<?php

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

   // modyfikuj losowy element tablicy
   $tablica[rand(0, 8)] = 6;

   echo '<pre>';
   var_dump($tablica);
   echo '</pre>';

 ?>

Najpierw przypisaliśmy do losowego elementu tablicy nową wartość: 6. Całość wyświetlamy funkcją var_dump(). Przydaje się ona przy poszukiwaniu błędów w skrypcie. Potrafi zaprezentować w czytelnej formie każdy typ danych, więc możemy za jej pomocą kontrolować, czy na danym etapie wykonywania rezultaty są takie, jakie być powinny. Podobne działanie ma funkcja print_r().

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

<?php

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

   echo $tablica;

 ?>

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

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

[edytuj] Przypisy

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

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

[edytuj] Formularze

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

[edytuj] Ogólnie o danych wejściowych

Interaktywne aplikacje PHP odbierają od użytkownika setki informacji. Pozwalają one nie tylko zorientować się, kto nas odwiedził, ale także odczytywać zawartość formularzy oraz adresów URL. Mechanizm ich odbierania ewoluował stopniowo. Pierwsze wersje rejestrowały wszystkie nadesłane parametry jako zwyczajne zmienne, lecz począwszy od PHP 4 zarzucono to, gdyż zagrażało bezpieczeństwu wielu skryptów. Dodając nowe parametry można było rejestrować nowe zmienne, które mogły nadpisać zmienne używane w kodzie aplikacji powodując jego błędne działanie (np. zyskanie praw administratora strony przez atakującego). Programista nie mógł także policzyć, ile danych w ogóle do niego trafiło i w jakich zmiennych są zawarte.

Obecnie wszystkie pola są rejestrowane w specjalnych, tworzonych przez skrypt, tablicach asocjacyjnych posegregowane według miejsca, z którego nadeszły. PHP potrafi odbierać informacje:

  • Metodą GET (parametry w adresach URL)
  • Metodą POST (formularze)
  • z serwera
  • z ciasteczek
  • z sesji (emulowanych przez PHP)

Tutaj zajmiemy się trzema pierwszymi pozycjami. Ciastka oraz sesje zostaną omówione w dalszych rozdziałach podręcznika.

[edytuj] Adresy URL

Dane pochodzące z adresu URL przechowywane są w tablicy $_GET.

Tę metodę należy używać tylko, gdy skrypt nie wykonuje operacji mających efekty uboczne (np. może być wyszukiwanie, ale już nie dodawanie lub usuwanie rekordów). W przeciwnym wypadku roboty indeksujące stronę i proxy ładujące strony z wyprzedzeniem mogą niechcący wykonywać operacje na serwerze. Z drugiej strony cache przeglądarki i dostawców internetowych może spowodować zignorowanie zapytań.

Możemy prosto obejrzeć sobie jej konstrukcję:

<?php

   var_dump($_GET);

 ?>

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

array(0) { }

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

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

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

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

<?php

   if(count($_GET) == 2)
   {
      echo 'Witaj, '.$_GET['imie'].' '.$_GET['nazwisko'].'!';
   }
   else
   {
      echo 'Nieprawidłowa liczba parametrów!';
   }

 ?>

Nie przejmuj się istnieniem konstrukcji, której jeszcze nie poznaliśmy. Niektórzy pewnie domyślili się, co ona robi, ale szczegóły będą podane już w następnym rozdziale. Na razie wpiszmy ją tak, jak jest. Funkcja count() pojawiająca się w kodzie zwraca ilość elementów w tablicy. Sprawdzamy w ten sposób, czy użytkownik podał to, co trzeba. Kontrola nadchodzących danych jest niezwykle istotna i nigdy nie wolno jej zlekceważyć. Pominięcie tego aspektu zazwyczaj kończy się dla skryptu tragicznie, bo jeżeli coś jest do zepsucia, na pewno znajdzie się ktoś, kto tego dokona.

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

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <title>Formularz HTML</title>
 </head>
 <body>
  <form method="get" action="nazwaskryptu.php">
   <p>
    <label>Podaj imię: <input type="text" name="imie"/></label>
   </p>
   <p>
    <label>Podaj nazwisko: <input type="text" name="nazwisko"/></label>
   </p>
   <p>
    <input type="submit" value="OK"/>   
   </p>
  </form>
 </body>
</html>

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

[edytuj] Formularze

Obsługa formularzy z prawdziwego zdarzenia, którymi można przesyłać setki informacji, odbywa się dosyć podobnie, jak adresów. Różnica jest taka, że wszystko wysyła się wyłącznie z formularza, który posiada parametr "method" ustawiony na "post" oraz że korzysta się z tablicy $_POST wewnątrz samego skryptu. Przeróbmy nasze ostatnie dzieło tak, aby pracowało w ten sposób.

<?php

   if(count($_POST) == 2)
   {
      echo 'Witaj, '.$_POST['imie'].' '.$_POST['nazwisko'].'!';
   }
   else
   {
      echo 'Nieprawidłowa liczba parametrów!';
   }

 ?>

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

<html>
  <head>
   <title>Formularz HTML</title>
  </head>
  <body>
    <form method="post" action="nazwaskryptu.php">
    Podaj imię: <input type="text" name="imie"/><br/>
    Podaj nazwisko: <input type="text" name="nazwisko"/><br/>
    <input type="submit" value="OK"/>   
    </form>
  </body>
 </html>

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

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

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

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

[edytuj] Serwer

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

<?php

   echo 'Witaj, twój adres IP to '.$_SERVER['REMOTE_ADDR'].'!';

 ?>

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

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

<?php

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

 ?>

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

<?php

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

 ?>

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

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

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

<?php

   echo 'Przybyłeś do nas ze strony: '.$_SERVER['HTTP_REFERER'].'!';

 ?>

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

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

[edytuj] Struktury kontrolne

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

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

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

  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

[edytuj] Instrukcja if

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

<?php

   if(count($_GET) == 2)
   {
      echo 'Witaj, '.$_GET['imie'].' '.$_GET['nazwisko'].'!';
   }
   else
   {
      echo 'Nieprawidłowa liczba parametrów!';
   }

?>
 

Instrukcja if pozwala na wykonanie części kodu tylko wtedy, kiedy spełniony jest określony warunek i opcjonalne dodawanie alternatywnych instrukcji w razie jego fałszywości. Z if-a korzysta się niezwykle często, ponieważ niemal zawsze musimy sprawdzać, czy dane informacje są prawidłowe, czy funkcja poprawnie połączyła się z plikiem itd. Poniżej napiszemy skrypt, który będzie potrafił rozwiązywać równanie kwadratowe y = ax2 + bx + c. Przypomnijmy, że ilość jego rozwiązań rzeczywistych zależy od wartości tzw. współczynnika Δ (Δ = b2 − 4ac). Jeżeli obliczony wynik (Δ) jest dodatni, istnieją dwa rozwiązania. Jeżeli ujemny - nie ma żadnych. Dla zera równanie daje jedno rozwiązanie (podwójne).

<?php
   // 1
   if(!isset($_GET['a']))
   {
      $_GET['a'] = 0;
   }
   if(!isset($_GET['b']))
   {
      $_GET['b'] = 0;
   }
   if(!isset($_GET['c']))
   {
      $_GET['c'] = 0;
   }
 
   // 2
   if($_GET['a'] == 0)
   {
      die('Nieprawidłowy parametr A!');
   }
 	
   // 3
   $delta = pow($_GET['b'], 2) - 4 * $_GET['a'] * $_GET['c'];
 	
   // 4
   if($delta > 0)
   {
      // 5
      echo 'Delta dodatnia. Dwa rozwiązania:<ul>';
      echo '<li>'.round((-$_GET['b']-sqrt($delta))/(2*$_GET['a']), 5).'</li>';
      echo '<li>'.round((-$_GET['b']+sqrt($delta))/(2*$_GET['a']), 5).'</li>';
      echo '</ul>';
   }
   else if($delta < 0)
   {
      // 6
      echo 'Delta ujemna. Brak rozwiązań!';
   }
   else
   {
      // 7
      echo 'Delta = 0. Jedno rozwiązanie: '.round((-$_GET['b'])/(2*$_GET['a']), 5);
   }
?>
 

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

  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:

<?php
   if(wyrazenie)
   {
      // blok kodu
   }
   else if(wyrazenie)
   {
      // blok kodu
   }
   else
   {
      // blok kodu
   }
?>
 

Obowiązkowe jest podawanie pierwszego z członów zaczynającego się od if. Dwa pozostałe są opcjonalne, przy czym ilość else if może być dowolna. Kolejność podawania kolejnych typów członów ukazana jest na przykładzie.

  • if - wykonuje się, gdy spełniony został podany warunek
  • else if - jeżeli nie został spełniony poprzedni warunek, PHP testuje aktualny i jeżeli jest prawdziwy, wykonuje ten kawałek kodu.
  • else - wykonywane, jeżeli żaden z powyższych warunków nie został spełniony.

Jeżeli blok kodu zawiera tylko jedną instrukcję, PHP dopuszcza możliwość opuszczenia nawiasów klamrowych, np.

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

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

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

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

A teraz kilka przykładów...

<?php
$liczba1 = $_POST['liczba1']; //Zakładamy, że do skryptu wysłano formularz z polami liczba1
$liczba2 = $_POST['liczba2']; // i liczba2. Pobieramy je...

   if($liczba1 == 1 && $liczba2 == 2) 
   {
      die('Liczba 1 wynosi 1, a liczba 2 wynosi 2');
   }
?>
 

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

<?php
$liczba1=$_POST['liczba1']; //Zakładamy, że do skryptu wysłano formularz z polami liczba1
$liczba2=$_POST['liczba2']; // i liczba2. Pobieramy je...

   if($liczba1==1 || $liczba2==1)
   {
      die('Liczba 1, lub liczba 2 wynosi 1');
   }
?>
 

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

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

<?php
   if(FALSE == 0)
   {
      echo 'Prawda!';
   }
?>
 

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

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

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

  • isset($zmienna) - zwraca prawdę, jeżeli zmienna istnieje.
  • empty($zmienna) - zwraca prawdę, jeżeli zmienna ma wartość NULL.
  • is_null($zmienna) - zwraca prawdę, jeżeli zmienna ma wartość NULL.
  • is_string($zmienna) - zwraca prawdę, jeżeli zmienna jest ciągiem tekstowym.
  • is_integer($zmienna) - zwraca prawdę, jeżeli zmienna jest liczbą całkowitą.
  • is_float($zmienna) - zwraca prawdę, jeżeli zmienna jest liczbą zmiennoprzecinkową.
  • is_numeric($zmienna) - zwraca prawdę, jeżeli zmienna jest liczbą.
  • is_bool($zmienna) - zwraca prawdę, jeżeli zmienna ma wartość logiczną.
  • is_array($zmienna) - zwraca prawdę, jeżeli zmienna jest tablicą.

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

[edytuj] Instrukcja switch

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

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

<?php

    if($_GET['act'] == 'dodaj')
    {
       echo 'Dodawanie danych';
    }
    elseif($_GET['act'] == 'edytuj')
    {
       echo 'Edycja danych';
    }
    elseif($_GET['act'] == 'usun')
    {
       echo 'Usuwanie danych';
    }
    else
    {
       echo 'Wyświetlanie danych';
    }

 ?>

Rozwiązanie to nie jest wygodne nie tylko ze względu na objętość takiego kodu i brak czytelności. Przypuśćmy, że z jakiegoś powodu musimy zmienić miejsce, z którego pobieramy informację o akcji. Trzeba to zrobić w czterech miejscach, a tych może być w teorii jeszcze więcej. Znacznie łatwiejszą w użyciu, a przy tym wydajniejszą alternatywą jest instrukcja switch. Działa ona w ten sposób, że wybiera spośród dostępnego zbioru określoną wartość na podstawie wartości pewnego wyrażenia i wykonuje zdefiniowany dla niej kod. Przepiszmy raz jeszcze powyższy przykład:

<?php
 
 	switch($_GET['act'])
 	{
 		case 'dodaj':
 			echo 'Dodawanie danych';			
 			break;
 		case 'edytuj':
 			echo 'Edycja danych';
 			break;
 		case 'usun':
 			echo 'Usuwanie danych';
 			break;
 		default:
 			echo 'Wyświetlenie danych';	
 	}
 
 ?>

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

<?php
 
 	switch($_GET['act'])
 	{
 		case 'dod':
 			echo 'Jak nie damy komendy "break", to pokaże nam się też...<br/>';
 		case 'dodaj':
 			echo 'Dodawanie danych';			
 			break;
 		case 'edytuj':
 			echo 'Edycja danych';
 			break;
 		case 'usun':
 			echo 'Usuwanie danych';
 			break;
 		default:
 			echo 'Wyświetlenie danych';	
 	}

 ?>

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

Dodawanie danych

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

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

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

Kod stanu może zawierać inne struktury kontrolne:

<?php

 	switch($_GET['act'])
 	{
 		case 'dodaj':
 			if($_SERVER['REQUEST_METHOD'] == 'POST')
 			{
 				echo 'Dodawanie danych';
 			}
 			else
 			{
 				echo 'Formularz dodawania';
 			}
 			break;
 		case 'edytuj':
 			echo 'Edycja danych';
 			break;
 		case 'usun':
 			echo 'Usuwanie danych';
 			break;
 		default:
 			echo 'Wyświetlenie danych';	
 	}
 
 ?>

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

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

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

[edytuj] Instrukcja for

[edytuj] Pętle

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

Uwaga! Pętlą nazywamy strukturę kontrolną powtarzającą dany kod do czasu spełnienia określonego warunku.

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

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

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

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

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

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

[edytuj] Proste wyświetlanie tablic

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

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

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

<?php
 
 	$plik = file('plik.txt');
 	
 	echo '<ul>';
 	for($i = 0, $x = count($plik); $i < $x; $i++)
 	{
 		echo '<li>'.trim($plik[$i]).'</li>';	
 	}
 	echo '</ul>';
 
 ?>

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

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

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

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

[edytuj] Break i Continue

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

Mamy prosty ciąg tekstowy:

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

Wiemy o nim trzy rzeczy:

  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:

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

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

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

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

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

		if($tekst{$i} == ' ')
 		{
 			continue;
 		}

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

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

		if($tekst{$i} == '.')
 		{
 			break;
 		}

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

		if($tekst{$i} == ';')
 		{
 			$t++;
 			$tablica[$t] = '';
 		}
 		else
 		{
 			$tablica[$t] .= $tekst{$i};
 		}

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

	}
 
 	echo '<pre>';
 	print_r($tablica);
 	echo '</pre>';
 ?>

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

<?php
 
 	$tekst = 'Komenda; Komenda; Komenda; Komenda. To już pomijamy.';
 	$tablica = array(0 => '');
 	$t = 0;
 
 	for($i = 0; $i < strlen($tekst); $i++)
 	{
  		switch($tekst{$i})
 		{
 			case ' ':
 				continue 2;
 			case '.':
 				break 2;
 			case ';':
 				$t++;
 				$tablica[$t] = '';
 				break;
 			default:
 				$tablica[$t] .= $tekst{$i};		
 		}
 	}
 	
 	echo '<pre>';
 	print_r($tablica);
 	echo '</pre>';
 ?>

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

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

[edytuj] Instrukcja while

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

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

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

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

<?php
 	$f = fopen('plik.txt', 'r'); // 1
 	
 	while(!feof($f)) // 2
 	{
 		echo fgets($f, 16); // 3
 	}
 	
 	fclose($f); // 4
 ?>

Opis działania:

  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:

<?php
 	$i = 0;
 	while($i < 10)
 	{
 		echo $i.'<br/>';
 		$i++;
 	} 
 ?>

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

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

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

[edytuj] Instrukcja do while

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

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

<?php
 	do
 	{
 		echo "Podaj i: \n";
 		fscanf(STDIN, "%d\n", $i); // 1
 	}
 	while($i < 10);
 ?>

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

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

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

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

<?php
 	echo "Podaj i: \n";
 	fscanf(STDIN, "%d\n", $i);

 	while($i < 10)
 	{
 		echo "Podaj i: \n";
 		fscanf(STDIN, "%d\n", $i);
 	}
 ?>

Tutaj musimy powielić kod dwa razy, bo przecież przed sprawdzeniem warunku wypada się choć raz zapytać użytkownika, co należy sprawdzić. W poprzednim przykładzie mogliśmy do tego celu użyć kodu wewnątrz pętli, ponieważ mieliśmy zagwarantowane wykonanie jej kodu przynajmniej raz.

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

[edytuj] Instrukcja foreach

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

<?php
 	$plik = file('plik.txt');
 	
 	echo '<ul>';
 	foreach($plik as $linia)
 	{
 		echo '<li>'.trim($linia).'</li>';	
 	}
 	echo '</ul>';
 
 ?>

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

foreach($plik as $linia)

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

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

<?php
 	$plik = file('plik.txt');
 	
 	echo '<ul>';
 	foreach($plik as $numer => $linia)
 	{
 		echo '<li>Linia #'.$numer.': '.trim($linia).'</li>';	
 	}
 	echo '</ul>';
 
 ?>

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

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

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

<?php
  	$plik = file('plik.txt');
 
  	foreach($plik as $linia)
  	{
  		$linia = 'Próba skasowania';
  	}
  	
 	echo '<pre>';
 	print_r($plik);
 	echo '</pre>';
 ?>

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

<?php
  	$plik = file('plik.txt');
 
  	foreach($plik as $i => $linia)
  	{
  		// jeszcze jakiś napis sobie doklejmy
  		$plik[$i] = trim($linia).' [OK]';
  	}
  	
 	echo '<pre>';
 	print_r($plik);
 	echo '</pre>';
 ?>

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

<?php
  	$plik = file('plik.txt');
 
  	foreach($plik as &$linia)
  	{
  		$linia = trim($linia).' [OK]';
  	}
  	
 	echo '<pre>';
 	print_r($plik);
 	echo '</pre>';
 ?>

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

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

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

[edytuj] Funkcje

Funkcja jest pojęciem znanym z matematyki: to zbiór operacji przypisujący danej grupie parametrów jakiś rezultat. Posiada własną nazwę, za pomocą której można się do niej odwoływać, pobiera dane i generuje wynik ponownie zwracany do programu. PHP posiada bardzo dużą ilość predefiniowanych funkcji, lecz nie zawsze mogą one zaspokoić nasze potrzeby. Załóżmy, że mamy w kodzie witryny WWW jakiś często powtarzający się algorytm. Pisanie go za każdym razem od nowa naraża nas na stratę czasu i ryzyko błędów. Możemy jednak napisać sobie własną funkcję z jego kodem i następnie odwoływać się do niej, gdy będzie to potrzebne. Gdybyśmy w przyszłości chcieli dodać jakąś nową możliwość lub wykryli błąd, poprawiamy go w jednym miejscu i zmiana widoczna jest wszędzie. W tym rozdziale omówione zostaną wszystkie tajniki tworzenia własnych funkcji i przyłóż do niego szczególną uwagę, ponieważ jest to rzecz, której nie można nie znać.

[edytuj] Tworzenie własnych funkcji

Tworzenie funkcji w PHP jest niezwykle proste. Stosujemy następującą konstrukcję:

function nazwaFunkcji(parametry)
 {
   // kod funkcji
 }

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

<?php
 
 	function formatujTekst($tekst)
 	{
 		echo '<font color="red">'.strtoupper($tekst).'</font>';	
 	}
 	
 	formatujTekst('to jest tekst 1');
 	formatujTekst('to jest tekst 2');
 ?>

Stworzyliśmy tutaj funkcję formatujTekst, dzięki której ustalimy jednolite formatowanie dla tekstów prezentowanych na stronie. Pobiera ona jeden parametr: $tekst. Zauważ, że nazwę tę piszemy ze znakiem dolara. Gdybyśmy chcieli podać więcej parametrów, oddzielamy je od siebie przecinkami. Można też nie brać żadnego, zostawiając puste nawiasy. Deklaracje te mówią skryptowi, pod jakimi zmiennymi wprowadzane wartości mają być widoczne wewnątrz funkcji.

Kod funkcji jest dowolnym poprawnym kodem PHP i możemy w nim wykonywać wszystkie operacje, z tworzeniem nowych funkcji włącznie. Jednak zauważ, że nasza pierwsza funkcja nie zwraca wartości. Zamiast tego wynik wysyła od razu na ekran i próba wykonania

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

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

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

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

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

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

<?php
 
 	function wyswietlKatalog($sciezka, $tylkoPliki = 0) // 1
 	{
 		$dir = opendir($sciezka); // 2
 		$wynik = array();
 		while($f = readdir($dir)) // 3
 		{
 			if(is_file($sciezka.$f)) // 4
 			{
 				$wynik[] = $f; // 5
 			}
 			elseif(is_dir($sciezka.$f) && $f != '.' && $f != '..' && !$tylkoPliki) // 6
 			{
 				$wynik[] = $f;
 			}	
 		}
 		closedir($dir); // 7
 		
 		return $wynik; // 8
 	} // end wyswietlKatalog();
 
 	function pokazListe(Array $lista) // 9
 	{
 		echo '<ul>';
 		foreach($lista as $element)
 		{
 			echo '<li>'.$element.'</li>';		
 		}
 		echo '</ul>';	
 	} // end pokazListe();
 
 	pokazListe(wyswietlKatalog('./katalog1/')); // 10
 	echo '<br/>';
 	pokazListe(wyswietlKatalog('./katalog2/', true)); // 11
 ?>

Opis skryptu:

  1. Oto deklaracja funkcji wyświetlania katalogów. Znak równości oraz wartość po drugim parametrze oznacza, że jest on opcjonalny. Jeżeli go nie podamy przy wywołaniu, przyjmie on wartość domyślną. Opcjonalnych parametrów może być więcej, z tym że podajemy je zawsze na końcu. W naszej funkcji opcjonalny parametr określa, czy funkcja ma zwracać wszystko (domyślny stan: 0), czy jedynie pliki (stan 1).
  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 parametrów obiektowych, a od PHP 5.1 - tablic, które również są typem złożonym. Robimy to właśnie w taki sposób. Jeśli próbowalibyśmy wysłać tutaj np. liczbę, PHP zgłosiłby błąd.
  10. Wywołanie funkcji z jednym parametrem 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 parametrów. Pozostała jeszcze jedna rzecz, a mianowicie pobieranie ich zupełnie nieokreślonej liczby. Uruchom taki oto skrypt:

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

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

<?php
 
 	function funkcja()
 	{
 		$parametry = func_get_args();
 		echo '<ul>';
 		foreach($parametry as $id => $wartosc)
 		{
 			echo '<li>'.$id.' - '.$wartosc.'</li>';
 		}
 		echo '</ul>';
 	}
 
 	funkcja(1, 2, 3, 4, 5);
 ?>

Istnieje także func_get_arg(numer) pobierająca wartość konkretnego parametru. Obie te funkcje operują bezpośrednio na funkcji, dlatego PHP nakłada kilka ograniczeń na ich stosowanie. Najlepiej jest pobrać wszystkie niezbędne parametry na samym początku tworzonej funkcji, aby uniknąć kłopotów.

[edytuj] Widzialność zmiennych

Napiszmy taki skrypt:

<?php
 
 	$zmienna = 'To jest zmienna';
 
 	function funkcja()
 	{
 		echo $zmienna.'<br/>';
 	}
 
 	funkcja();
 	echo $zmienna.'<br/>';
 ?>

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

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

<?php
 
 	$zmienna = 'To jest zmienna';
 
 	function funkcja()
 	{
 		global $zmienna;
 		echo $zmienna.'<br/>';
 	}
 
 	funkcja();
 	echo $zmienna.'<br/>';
 ?>

Słowo kluczowe global informuje PHP, że wymienione po nim zmienne mają zostać zaimportowane ze stosu głównego.

[edytuj] Static

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

<?php
 
 	function koloruj()
 	{
 		static $i = 0;
 		
 		$i++;
 		
 		if($i % 2 == 0)
 		{
 			return '#ffffff';
 		} else {
 		        return '#cccccc';	
                }
 	} // end koloruj();
 	
 	echo '<table width="30%">';
 	for($x = 0; $x < 10; $x++)
 	{
 		echo '<tr><td bgcolor="'.koloruj().'">'.$x.'</td></tr>';	
 	}
 	echo '</table>';
 ?>

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

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

[edytuj] Rekurencja

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

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

<?php
 
 	function wyswietlKatalog($sciezka)
 	{
 		$dir = opendir($sciezka);
 		echo '<ul>';
 		while($f = readdir($dir))
 		{
 			if(is_dir($sciezka.$f) && $f != '.' && $f != '..')
 			{
 				echo '<li>'.$f;
 				wyswietlKatalog($sciezka.$f.'/'); // 1
 				echo '</li>';
 			}
 		}
 		echo '</ul>';
 		closedir($dir);
 	} // end wyswietlKatalog();
 
 	wyswietlKatalog('../../');
 ?>

Funkcja wyswietlKatalog() w przypadku napotkania katalogu w aktualnie sprawdzanej ścieżce, wywołuje samą siebie (1), z doklejoną do dotychczasowej ścieżki nazwą tego katalogu. W ten sposób możemy pobrać całe drzewo, niemniej w przypadku rozbudowanych struktur może trwać to nawet kilkanaście sekund! Musimy być także świadomi, że funkcja nie może zagnieżdżać się rekurencyjnie w nieskończoność. W pewnym momencie w PHP zwyczajnie zapcha się stos i wykonywanie takiego skryptu zostanie przerwane. Dlatego stosuj rekurencję z rozwagą i nie nadużywaj jej w miejscach, w których nie jest konieczna.

[edytuj] Użyteczne funkcje

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

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

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

<?php
    if(function_exists('imap_open'))
    {
        echo 'IMAP dostępny';
    }
    else
    {
        echo 'IMAP niedostępny';
    }
 ?>

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

<?php
    if(extension_loaded('imap'))
    {
        echo 'IMAP dostępny';
    }
    else
    {
        echo 'IMAP niedostępny';
    }
 ?>
Do zrobienia Do zrobienia:
Napisać o call_user_func() (z sensownym przykładem!)
Do zrobienia Do zrobienia:
Napisać sensowną funkcję.

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

[edytuj] Inne elementy składni

[edytuj] Include i require

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

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

normalny.php:

<?php
 
 	function pokazTytul($tytul)
 	{
 		echo '<h1>'.$tytul.'</h1>';	
 	} // end pokazTytul();
 	
 	function pokazParagraf($tekst)
 	{
 		echo '<p>'.$tekst.'</p>';
 	} // end pokazParagraf();
 	
 	function pokazListe(Array $tablica)
 	{
 		echo '<ul>';
 		foreach($tablica as $element)
 		{
 			echo '<li>'.$element.'</li>';
 		}
 		echo '</ul>';
 	} // end pokazListe();
 ?>

opcjonalny.php:

<?php
 
 	function pokazTytul($tytul)
 	{
 		echo '<h1>'.$tytul.'</h1>';	
 	} // end pokazTytul();
 	
 	function pokazParagraf($tekst)
 	{
 		echo '<p style="font-weight:bold;">'.$tekst.'</p>';
 	} // end pokazParagraf();
 	
 	function pokazListe(Array $tablica)
 	{
 		echo '<ol>';
 		foreach($tablica as $element)
 		{
 			echo '<li>'.$element.'</li>';
 		}
 		echo '</ol>';
 	} // end pokazListe();
 ?>

Jeszcze raz zwracamy uwagę, że oba pliki tworzą funkcje o takich samych nazwach, dlatego naraz może być załadowany tylko jeden z nich. Oto plik index.php, który na podstawie tego, czy ustawiony jest parametr "styl", decyduje, który z powyższych skryptów zostanie załadowany:

<?php
 
 	if(!isset($_GET['styl']))
 	{
 		require('./normalny.php');
 	}
 	else
 	{
 		require('./opcjonalny.php');
 	}
 	
 	pokazTytul('Tytuł');
 	pokazParagraf('To jest paragraf');
 	
 	pokazListe(array(0 =>
 		'To',
 		'Jest',
 		'Lista'	
 	));
 ?>

Choć require wywołuje się identycznie, jak funkcję, funkcją nie jest. Różnica między nim, a include jest taka, że pierwsza w przypadku nieznalezienia pliku generuje komunikat Fatal error zatrzymujący skrypt, druga tylko ostrzeżenie. Istnieją także include_once oraz require_once, które są ignorowane, jeśli próbujemy po raz drugi dołączyć ten sam plik.

Budowanie kompletnej strony z mniejszych plików jest bardzo pożyteczne. Generalnie nie zaleca się pisania wszystkiego ciurkiem bez podziału na funkcje, mniejsze moduły itd. gdyż zmniejsza to odporność skryptu na błędy, wprowadza chaos i utrudnia dodawanie/modyfikowanie nowych opcji. Przyjrzyjmy się, jak zatem zorganizować naszą witrynę. Przede wszystkim zróbmy sobie jeden katalog na wszystkie pliki z funkcjami. Może on się nazywać np. includes. Umieszczamy w nim funkcje ułatwiające komunikację z bazą danych, przetwarzanie tekstu, autoryzację i wykonujące wszystkie inne rutynowe operacje. W katalogu głównym zawierającym skrypty dostępne z przeglądarki (np. index.php, rejestracja.php itd.), umieszczamy także jeden plik o nazwie common.php. Ma on następującą postać:

<?php

    require('../includes/database.php');
    require('../includes/text.php');
    require('../includes/session.php');
    require('../includes/authorize.php');
    require('../includes/templates.php');
    require('../includes/functions.php');
    require('../includes/layout.php');
    require('../includes/main.php');

    initSystem();

 ?>

common.php ma jedno tylko zadanie: załadować cały silnik i zainicjować jego pracę. Teraz w plikach typu index.php generujących właściwą zawartość strony wpisujemy na początku:

<?php
    require('./common.php');

    // tutaj kod strony

 ?>

W taki właśnie sposób wprowadziliśmy organizację w strukturę naszej witryny. Jeżeli silnikowi przybędzie dodatkowy plik, dopisujemy go jedynie do common.php, a będzie on dostępny na wszystkich podstronach.

Przyszła pora na przyjrzenie się wydajności procesu dołączania. Wiele innych źródeł zachęca do dołączania plików bez podawania ścieżki, np.

require('plik.php');

Wbrew pozorom, niepodanie przed nazwą zwyczajnego ./ ma wpływ na wydajność.

require('./plik.php');

Różnice leżą w przetwarzaniu każdej z tych ścieżek przez PHP. W konfiguracji interpretera jest dyrektywa include_path. Informuje ona, jakie ścieżki mają być przeszukiwane po kolei w przypadku niezdefiniowania położenia pliku. Tak więc przy pierwszym sposobie PHP testuje każdy z wprowadzonych tam katalogów, a sprawdzanie to nieco trwa. Owszem, istnieją sytuacje, kiedy trzeba się tym posłużyć (np. przy dołączaniu bibliotek PEAR instalujących się w systemie w dziwnych miejscach), ale na co dzień pożałowanie tych dwóch dodatkowych znaków może nas kosztować nawet dwukrotny spadek wydajności już dla zaledwie kilku dołączanych w ten sposób skryptów. ./ wyraźnie informuje interpreter: "interesuje mnie tylko bieżący katalog. Nie szukaj nigdzie indziej."

Kolejną istotną kwestią jest bezpieczeństwo. W sieci wciąż można spotkać wiele artykułów, które polecają taki sposób wykonania modułowości:

<?php

    include($_GET['co']); // NIGDY TEGO NIE UŻYWAJ!

 ?>

Jest to jawne pogwałcenie zasad bezpieczeństwa - nigdy nie zezwalać użytkownikowi na dostęp do jakiegokolwiek pliku. Oczywiście, dla większości osób adres www.strona.pl/?co=informacje.html nie będzie podejrzany, ale wystarczy tu już nieco lepsza znajomość PHP i systemów operacyjnych, by wpisać www.strona.pl/?co=/etc/passwd i w ten sposób odczytać listę haseł serwera. Wydawać się to może śmieszne i prostackie, ale przez zwyczajne niedbalstwo programistów "padło" wiele witryn, w tym także tych należących do publicznych instytucji. Proponowanie powyższego sposobu jako recepty na modułowość to najprostsze w świecie zaproszenie hackera do zniszczenia naszej witryny. Jeżeli naprawdę potrzebujemy czegoś takiego, __musimy__ sprawdzić, że podana nazwa zawiera wyłącznie dozwolone znaki. Nie pomoże dodawanie nazwy katalogu przed ścieżką (atakujący może to ominąć podając ".."), ani doklejanie rozszerzenia do nazwy plików (przez bug w PHP można je odciąć za pomocą znaku NUL (%00 w URL)).


[edytuj] Stałe

Spójrzmy raz jeszcze na przykład z plikiem common.php. Jak nietrudno się domyślić, większe projekty składają się z pewnej liczby katalogów. Pojawia się tu problem, skąd skrypt ma wiedzieć, gdzie leżą potrzebne mu pliki? Teoretycznie możemy ścieżki wpisywać ręcznie przy każdej konieczności, lecz jest to bardzo nieefektywne. Jeżeli dodatkowo aplikacja posiada jakiś silnik ze zbiorem używanych w różnych miejscach funkcji, może nawet okazać się to niemożliwe. Typowa strona internetowa składa się bowiem z paru sekcji, np. panelu administracyjnego, części głównej, forum, zlokalizowanych najczęściej w osobnych podkatalogach. Gdybyśmy na stałe zdefiniowali jakąś ścieżkę w silniku, nie wiedzielibyśmy, do której sekcji ona prowadzi.

Zdefiniujmy zatem wszystkie używane w projekcie ścieżki w pliku common.php za pomocą zmiennych. Już na pierwszy rzut oka widać, iż rozwiązanie to nie jest najlepsze, bowiem każdą zmienną ze ścieżką musimy przenosić do funkcji z użyciem global, a ponadto istnieje ryzyko, że w którymś miejscu omyłkowo ją nadpiszemy. Remedium na te kłopoty są stałe. Są to aliasy na pewne wartości, których po utworzeniu nie można już modyfikować. Najogólniej wykorzystuje się w je dla często powtarzających się w skrypcie wartości. Przyjrzyjmy się poniższemu skryptowi:

<?php
 	define('DIR_GLOWNY', './');
 	define('DIR_SILNIK', './includes/');
 	define('DIR_ZDJECIA', './zdjecia/');
 	
 	require(DIR_SILNIK.'autoryzacja.php');
 	require(DIR_SILNIK.'funkcje.php');
 	
 ?>

Do zdefiniowania stałych używamy funkcji define(), w której definiujemy nazwę stałej oraz jej wartość. Zwyczajowo stałe mają nazwy złożone z samych dużych liter, a wartościami mogą być wyłącznie typy skalarne (czyli nie tablice, nie obiekty oraz nie zasoby). Podczas wywoływania stałych nie poprzedzamy znakiem dolara.

Oto wszystkie cechy stałych:

  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:

<?php
 	error_reporting(1 | 2 | 4 | 8);	
 ?>

Lecz dla osoby postronnej cyfry połączone operatorem alternatywy bitowej są zwyczajnie niezrozumiałe. Zamiast cyfr, można użyć odpowiadające im stałe zdefiniowane przez PHP:

<?php
 	error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);	
 ?>

Kolejnym ciekawym zagadnieniem, którego realizację ułatwiają stałe, jest przekazywanie parametrów do funkcji w sposób pokazany powyżej. Rozpatrzmy przypadek systemu raportowania stanu pojazdu. Stworzyliśmy sobie funkcję raportującą pobierającą pięć wartości logicznych (prawda-fałsz) opisujących, które elementy są sprawne, a które nie. Nasz skrypt wygląda tak:

<?php
 	// Raportowanie stanu pojazdu
 	function stan($silnikOK, $kolaOK, $swiatlaOK, $skrzyniaOK, $paliwoOK)
 	{
 		if($silnikOK)
 		{
 			echo 'Silnik jest sprawny<br/>';		
 		}
 		if($kolaOK)
 		{
 			echo 'Koła są sprawne<br/>';		
 		}
 		if($swiatlaOK)
 		{
 			echo 'Światła są sprawne<br/>';		
 		}
 		if($skrzyniaOK)
 		{
 			echo 'Skrzynia jest sprawna<br/>';		
 		}
 		if($paliwoOK)
 		{
 			echo 'Paliwo jest w baku<br/>';		
 		}
 	} // end stan();
 	
 	
 	stan(true, false, false, true, true);
 ?>

Znów mamy identyczny problem: wywołując funkcję gdzieś w skrypcie musimy pamiętać, jaka jest kolejność parametrów, a postronna osoba już w ogóle nie zrozumie, co do czego ma iść. Wykorzystajmy więc fakt, iż komputer zapisuje wszystko w postaci zerojedynkowej i prześlijmy wszystkie parametry jako jedną liczbę o długości pięciu bitów (od 0 do 32). W stałych zdefiniujemy nazwy poszczególnych elementów przypisując im kolejne potęgi dwójki. Następnie za pomocą operatora alternatywy bitowej zbudujemy z nich liczbę:

<?php
 	define('SILNIK', 1);
 	define('KOLA', 2);
 	define('SWIATLA', 4);
 	define('SKRZYNIA', 8);
 	define('PALIWO', 16);
 
 	// Raportowanie stanu pojazdu
 	function stan($stan)
 	{
 		if($stan & SILNIK)
 		{
 			echo 'Silnik jest sprawny<br/>';		
 		}
 		if($stan & KOLA)
 		{
 			echo 'Koła są sprawne<br/>';		
 		}
 		if($stan & SWIATLA)
 		{
 			echo 'Światła są sprawne<br/>';		
 		}
 		if($stan & SKRZYNIA)
 		{
 			echo 'Skrzynia jest sprawna<br/>';		
 		}
 		if($stan & PALIWO)
 		{
 			echo 'Paliwo jest w baku<br/>';		
 		}
 	} // end stan();
 	
 	
 	stan(SILNIK | SKRZYNIA | PALIWO);
 ?>

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

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

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

SILNIK | SKRZYNIA | PALIWO:

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

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

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

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

$rezultat & SILNIK
REZULTAT:     11001
SWIATLA (4):  00100
-------------------
              00000

Teraz żadna z jedynek nie powtarza się w obu wierszach naraz, więc działanie da nam wynik 0, czyli fałsz. To jest cała tajemnica tego sposobu.

[edytuj] Referencje

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

<?php
 
   $a = 5;
   $b = &$a; // tworzymy referencje
 
   echo 'B: '.$b.'; A: '.$a.'<br/>';
   $b = 6;
   echo 'B: '.$b.'; A: '.$a.'<br/>';
   $a = 7;
   echo 'B: '.$b.'; A: '.$a.'<br/>';
 ?>

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

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

$b = &$a;

nie wykonujemy żadnego żmudnego kopiowania tych danych, lecz tworzymy do nich kolejny odnośnik. Jest to wielokrotnie szybsze, lecz sprawia, że modyfikacja "pseudo kopii" odbije się także na oryginale.

Referencje w połączeniu z innymi strukturami nadają im pewne dodatkowe właściwości. Przypomnij sobie pętlę foreach. Wspominaliśmy tam, iż dodatkowe zmienne reprezentujące indeks oraz wartość elementu tablicy są kopiami, dlatego nie można ich jawnie stosować do modyfikowania przetwarzanej tablicy. W PHP 5 ten problem zniknął, ponieważ możemy poinformować PHP, że zmienna wartości ma być referencją. Dzięki temu możliwe będzie modyfikowanie przez nią zawartości tablicy:

<?php
 
   $tablica = array(0 => 1, 2, 3, 4, 5, 6);
   
   foreach($tablica as $id => &$element)
   {
     $element = rand(1, 6);  
   }
  
   print_r($tablica);

 ?>

W tym przykładzie wypełniamy tablicę losowymi numerami poprzez zmienną $element tworzoną przez pętlę. Jest to możliwe, gdyż zadeklarowaliśmy znakiem &, iż ma to być referencja do właściwego elementu. Gdyby usunąć ten znak, funkcja print_r() pokazałaby nam dalej pierwotną zawartość tablicy. Tak się jednak nie dzieje, zatem sposób okazuje się skuteczny.

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

<?php

   function przetworz(&$tekst)
   {
     if(strlen($tekst) > 5)
     {
       $tekst = '<font color="red">'.$tekst.'</font>';
       return 1;    
     }
     return 0; 
   } // end przetworz();
   
   // Probujemy przerobic krotki tekst
   $tekst = 'Jan';
   
   if(przetworz($tekst))
   {
     echo 'Przetworzony tekst: '.$tekst.'<br/>';
   }
   else
   {
     echo 'Błąd: za krótki tekst! '.$tekst.'<br/>';
   }
   
   // A teraz dlugi
   $tekst = 'Ala ma kota';
   
   if(przetworz($tekst))
   {
     echo 'Przetworzony tekst: '.$tekst.'<br/>';
   }
   else
   {
     echo 'Błąd: za krótki tekst! '.$tekst.'<br/>';
   }
 
 ?>

Sposób ten także działa, ponieważ w drugim (dobrym) przypadku po wywołaniu funkcji przetworz($tekst) w zmiennej znalazła się zmodyfikowana postać.

Przekazywanie parametrów poprzez referencje ma pewne ograniczenia. Nie można do takiego parametru podać wartości stałej, ponieważ referencja musi operować na zmiennych.

<?php
   function funkcja(&$a)
   {

   }

   // proba 1
   funkcja('test');
   
   // proba 2
   $zmienna = 'test';
   funkcja($zmienna);

 ?>

Pierwszy zapis spowoduje błąd, gdyż nie przekazaliśmy wartości w postaci zmiennej. Wystrzegaj się także wywoływania funkcji ze zmuszaniem do przekazywania referencji, gdy funkcja sobie wyraźnie tego nie życzy: funkcja(&$zmienna). Zapis taki był poprawny jeszcze w PHP 4, lecz w PHP 5 został wycofany i domyślnie pokazuje się ostrzeżenie przy napotkaniu gdziekolwiek takiego fragmentu.

[edytuj] Eval

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

<?php
     $zmienna = 5;
 
     eval("echo $zmienna;");
 ?>

Eval najpierw spowoduje kompilację ciągu echo $zmienna;, a następnie jego wykonanie, czego efektem będzie wyświetlenie się w przeglądarce wartości zmiennej. Nie nadużywaj tej własności zbyt często. Wykonywanie tworzonego przez nas kodu PHP jest ciekawą opcją, lecz mogącą prowadzić do spadku wydajności, trudnych do zauważenia błędów i luk bezpieczeństwa. PHP jest tak zaprojektowany, że nigdy nie potrzeba w nim używać eval(). Oto dwa przykłady, kiedy nie trzeba stosować eval.

1. Chcemy wykonać funkcję, której nazwę mamy zapisaną w zmiennej $funkcja. Choć możemy napisać

eval($funkcja."($a, $b, $c)");

do dyspozycji mamy o wiele lepszy sposób:

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

2. Chcemy wstawić w wyrażeniu wartość zmiennej, której nazwa zapisana jest w innej zmiennej (np. $zmienna). Zamiast pisać

eval('echo $'.$zmienna.';');

możemy zrobić:

echo $zmienna;

Oba powyższe przykłady wykonają się szybciej, ponieważ są kompilowane wraz z właściwym skryptem. W przypadku eval PHP musi natomiast po raz drugi uruchamiać kompilator.

Uwaga! Uwaga!
Nigdy nie przekazuj do instrukcji eval danych z tablic $_POST, $_GET albo $_COOKIE, ponieważ realnie zagrażasz w ten sposób zarówno swojej aplikacji, jak i serwerowi.

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

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

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

[edytuj] Komunikaty błędów PHP

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

  • Kompilacji - kod skryptu tłumaczony jest na wewnętrzny zestaw instrukcji interpretera. Faza ta często jest także zwana parsowaniem.
  • Wykonywania - właściwe wykonanie skryptu.

Każda z nich generuje nieco inne komunikaty błędów, dzięki czemu wiemy, gdzie szukać przyczyny problemów. Napiszmy sobie taki skrypt:

<?php

     if(isset($zmienna)
     {
       echo $zmienna;
     }
 
 ?>

Po jego uruchomieniu powinniśmy ujrzeć następujący komunikat:

Parse error: syntax error, unexpected '{' in /home/uzytkownik/www/kurs/aplikacja.php on line 4

Jest to błąd składni powiązany z fazą kompilacji. PHP nie może skompilować skryptu, ponieważ w podanym pliku w linii 4 natrafił na nieprawidłową konstrukcję składniową. Nie oznacza to jednak, że błąd jest akurat w tej linii. Zazwyczaj powoduje go jakaś pomyłka nieco wyżej. Przyjrzyjmy się komunikatowi. Mówi on, że PHP natknął się na niespodziewany nawias klamrowy w linii 4. Rzeczywiście, znajduje się on na swym miejscu tak, jak być powinien. Spójrzmy jednak linijkę wyżej: if(isset($zmienna) - okazuje się, że nie zamknęliśmy jednego z nawiasów. PHP oczekiwał znaku ), lecz zamiast tego dostał { i zgłosił błąd. Po dodaniu brakującego nawiasu skrypt zaczyna poprawnie działać.

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

<?php
 
     if(isset($zmienna))
     {
       echo $zmienna
       inicjujAplikacje();
     }
 
 ?>

Po uruchomieniu znów pojawił nam się komunikat błędu!

Parse error: syntax error, unexpected T_STRING, expecting ',' or ';' in /home/uzytkownik/www/kurs/aplikacja.php on line 6

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

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

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

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

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

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

<?php
   $tekst = 'to jest jakiś tekst';
   sort($tekst);
?>

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

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

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

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

<?php
	echo $zmienna;
?>

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

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

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

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

[edytuj] Operator @

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

<?php

  echo filemtime('plik.txt');

 ?>

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

<?php
 
 	if(file_exists('plik.txt'))
 	{
 		echo filemtime('plik.txt');
 	}
 	else
 	{
 		echo 'Podany plik nie istnieje.';
 	}

 ?>

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

<?php
 	$wynik = @filemtime('plik.txt');
 	if($wynik !== FALSE)
 	{
 		echo $wynik;
 	}
 	else
 	{
 		echo 'Podany plik nie istnieje.';
 	}
 
 ?>

Wynik funkcji zapisujemy w zmiennej, ponieważ jest on nam potrzebny w paru miejscach. Aby nie dostawać komunikatu Warning, poprzedziliśmy wywołanie funkcji operatorem @.

Wszystko można zapisać inaczej.

<?php
 	@echo filemtime('plik.txt') or die('Podany plik nie istnieje.');
 ?>
Uwaga! Uwaga!
W powyższym przykładzie założono, że po poleceniu echo nic więcej nie ma. Jeśli jest inaczej, dalsza część skryptu nie zostanie wykonana!
Uwaga! Uwaga!
Operator @ zatrzymuje także komunikaty Parse error. Jeżeli twój skrypt nie działa z niewiadomych przyczyn, w pierwszej kolejności usuń go, aby sprawdzić, czy przypadkiem nie maskuje on komunikatu błędu. Dopiero potem zajmij się diagnozą.
Porada Porada
Zanim użyjesz operatora @, trzy razy się zastanów, czy naprawdę jest Ci on w tym miejscu do czegoś konkretnego potrzebny.

[edytuj] Poziom raportowania błędów

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

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

<?php

   error_reporting(E_ALL ^ E_NOTICE);

   echo 'Teraz komunikaty Notice nie działają: '.$nieistniejacaZmienna;
?>

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

<?php

   $poprzedni = error_reporting(E_ALL ^ E_NOTICE);

   echo 'Teraz komunikaty Notice nie działają: '.$nieistniejacaZmienna;

   error_reporting($poprzedni);

   echo 'A teraz już tak: '.$nieistniejacaZmienna;
?>

[edytuj] Techniki debugowania

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

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

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

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

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

<?php
 	assert_options(ASSERT_ACTIVE, 1);
 	assert_options(ASSERT_WARNING, 1);
 
 	$zmienna = 3;
 
 	assert($zmienna == 6);
 
 ?>

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

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

[edytuj] Pakiety testowe

Bardziej złożone aplikacje pisze się w oparciu o tzw. pakiety testowe. Mając projekt naszej aplikacji, programista buduje zwyczajny szkielet tworzonego modułu oraz układa dla niego zestaw testów. Następnie uzupełnia szkielet funkcjonalnym kodem tak, aby zdać wszystkie testy. Wykonywaniem testów nie musi zajmować się samodzielnie, gdyż stworzone zostały odpowiednie pakiety, np. PHPUnit. Naszym zadaniem jest tylko napisanie procedur testowych, korzystając z przygotowanych narzędzi, a opracowaniem wyników zajmie się pakiet.

Programowanie inicjowane testami, bo tak się ten styl pisania nazywa, ma jeszcze jedną użyteczną cechę. Wydając nowe wersje modułu, możemy wykonywać testy do sprawdzenia, czy moduł wciąż zachowuje się zgodnie z wytycznymi i nie sprawi problemów innym częściom aplikacji. Szczegółów implementacji jednak teraz nie poznamy, ponieważ pakiety pisze się z wykorzystaniem programowania obiektowego, którego jeszcze nie znamy. Do tego zagadnienia wrócimy w dalszej części podręcznika.

Poprzedni rozdział: Każdy popełnia błędy Spis treści Następny rozdział: Studium przypadku/Księga gości

[edytuj] Korzystanie z dokumentacji

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

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

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

[edytuj] Instalacja PHP

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

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

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

[edytuj] Składnia języka

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

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

[edytuj] "Security" oraz "Features"

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

  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.

[edytuj] Spis funkcji

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

No external libraries are needed to build this extension.

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

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

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

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

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

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

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

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

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

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

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

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

  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ł: Przetwarzanie tekstu

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

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

[edytuj] Plan

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

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

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

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

[edytuj] dane.php

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

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

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

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

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

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

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

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

  • htmlspecialchars() - wszystkie wprowadzone tagi HTML są zamieniane na zwykły tekst (ograniczniki są zastępowane odpowiadającymi im encjami, np. < zmienia się w &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ą.

[edytuj] ksiega.php

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

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

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

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

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

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

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

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

Ostatnim akordem będzie dodanie formularza HTML:

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

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

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

[edytuj] Co dalej?

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

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

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

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

Poprzedni rozdział: Studium przypadku/Księga gości Spis treści Następny rozdział: Podstawy wyrażeń regularnych

[edytuj] Przetwarzanie tekstu

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

[edytuj] Wyszukiwanie ciągów

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

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

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

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

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

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

[edytuj] Modyfikacja ciągów

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

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

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

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

Jeżeli chcemy wyciąć fragment jednego ciągu, aby np. poddać go bardziej szczegółowej obróbce, z pomocą przychodzi nam substr(). Podajemy w niej pozycję, od której zamierzamy ciąć oraz ilość znaków do pobrania. W przykładzie połączymy ją z funkcjami strpos() oraz strlen() (podaje długość ciągu) do wycięcia nazwiska z personaliów pewnego człowieka.

<?php
 
 	$nazwa = 'Janusz Kowalski';
 	
 	if(($id = strpos($nazwa, ' ')) !== false)
 	{
 		$nazwisko = substr($nazwa, $id, strlen($nazwa) - $id);
 	}
 	else
 	{
 		$nazwisko = '';
 	}
 	
 	echo $nazwisko;
 
 ?>

W personaliach odnajdujemy spację, która posłuży nam jako marker. strpos() zwróci nam jej pozycję. Przekazujemy ją do substr() jako punkt startowy wycinania i pobieramy ilość znaków równą długości personaliów minus pozycji spacji. W ten sposób w nasze ręce wpadnie nazwisko i będziemy mogli zrobić z nim, co tylko chcemy. Alternatywny sposób rozwiązania tego problemu polega na rozbiciu tego ciągu na tablicę poznaną już funkcją explode(). Przydaje się on, kiedy oprócz nazwiska pragniemy otrzymać także drugi ciąg z imieniem - tu pasuje ona, jak znalazł.

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

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

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

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


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

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

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

[edytuj] ASCII

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

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

<?php
 
 	if($_SERVER['REQUEST_METHOD'] == 'POST')
 	{ // 1
 		if($_POST['kierunek'] == 0) // 2
 		{
 			if(strlen($_POST['dane']) != 1)
 			{
 				die('<p>Nieprawidłowe dane. Proszę podać JEDEN znak!');			
 			}
 			echo '<p>Kod znaku <b>'.$_POST['dane'].'</b> to '.ord($_POST['dane']).'</p>';
 		}
 		else
 		{ // 3
 			if(!ctype_digit($_POST['dane']))
 			{
 				die('<p>Nieprawidłowe dane. Proszę podać kod znaku!');			
 			}
 			echo '<p>Kodowi <b>'.$_POST['dane'].'</b> odpowiada znak '.chr($_POST['dane']).'</p>';		
  		}	
 	}
 	else
 	{ // 4
 		echo '<form method="post" action="ascii.php">
 		<h2>Informator ASCII</h2>
 		<select name="kierunek">
 		<option value="0">Sprawdź kod znaku</option>
 		<option value="1">Sprawdź znak pod kodem</option>
 		</select>
 		<input type="text" name="dane"/>
 		<input type="submit" value="OK"/>		
 		</form>';	
 	}
 ?>

Formularz przesyła nam dwie informacje:

  • Kierunek - określa, czy konwertujemy znak na kod, czy też kod na znak.
  • Dane - znak albo kod do zamiany.

Oto omówienie działania:

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

[edytuj] Formatowanie tekstu

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

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

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

Liczba szesnastkowa: d0e

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

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

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

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

[edytuj] Kodowanie

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

PHP posiada wbudowanych kilka algorytmów haszujących:

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

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

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

<?php

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

 ?>

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

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

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

[edytuj] Podstawy wyrażeń regularnych

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

[edytuj] Istota wyrażeń regularnych

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

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

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

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

[edytuj] Pierwszy przykład

Nasze pierwsze spotkanie praktyczne z wyrażeniami regularnymi rozpoczniemy od prostego sprawdzenia, czy wypełnione pole formularza zawiera dokładnie jedną cyfrę. Do porównywania wzorca z ciągiem służy funkcja preg_match(), która zwraca true, jeżeli zachodzi zgodność.

<?php

	if($_SERVER['REQUEST_METHOD'] == 'POST')
	{
		if(preg_match('/^[0-9]$/D', $_POST['cyfra']))
		{
			echo '<p>Wpisałeś cyfrę '.$_POST['cyfra'].'</p>';
		}
		else
		{
			echo '<p>Nieprawidłowe dane! Skrypt wymaga podania cyfry!</p>';
		}
	}
	else
	{
		echo '<form method="post" action="preg1.php">
			Podaj cyfrę: <input type="text" name="cyfra"/><input type="submit" value="OK"/>
			</form>';	
	}
?>

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

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

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

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

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

[edytuj] Klasy znaków

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

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

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

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

<?php

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

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

  • /^[a-zA-Z\.\-\_]+ - początek adresu składa się z dowolnych znaków alfanumerycznych, kropki, pauzy oraz podkreślenia i jego długość musi wynosić minimum 1 znak.
  • \@ - później ma być małpa
  • [a-zA-Z0-9\.\-\_]+ - analogicznej klasy używamy do zdefiniowania domeny.
  • \.[a-z]{2,4}$/ - domena musi kończyć się kropką, po której spodziewamy się domeny nadrzędnej (np. .pl, .com).

W pełni poprawne wyrażenie sprawdzające poprawność adresu jest znacznie bardziej skomplikowane. Zainteresowanych odsyłamy do odpowiedniego dokumentu RFC definiującego je.

PCRE posiada kilka klas predefiniowanych:

  • . - kropka symbolizuje dowolny znak (za wyjątkiem przełamania linii).
  • \d - dowolna cyfra dziesiętna
  • \D - dowolny znak niebędący cyfrą
  • \s - biały znak (np. spacja, tabulator)

Predefiniowane klasy można ze sobą łączyć wewnątrz nawiasów kwadratowych: [\d\s] - dozwolone cyfry dziesiętne oraz białe znaki. Jeżeli po otwierającym nawiasie kwadratowym pojawi się symbol ^, będzie to oznaczać negację klasy: "wszystkie znaki, które NIE należą do wymienionych". Jak zdefiniowałbyś "dowolny znak niebędący cyfrą", czyli klasę \D w tradycyjny sposób?

[edytuj] Grupy

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

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

<?php

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

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

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

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

?>

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

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

[edytuj] Obsługa ciastek

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

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

<?php

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

 ?>

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

<?php
 
 	if(!isset($_GET['plik'])) // 1
 	{
 		die('Podaj nazwę pliku!');
 	}
 
 	$_GET['plik'] = basename($_GET['plik']); // 2
 	
 	if(@is_file('./pdf/'.$_GET['plik']))
 	{
 		// 3
 		header('Content-type: application/pdf');
 		header('Content-Disposition: attachment; filename="'.$_GET['plik'].'"');
 		readfile('./pdf/'.$_GET['plik']);
 	}
 	else
 	{
 		// 4
 		header('HTTP/1.1 404 Not Found');
 		exit('Nie znaleziono pliku '.$_GET['plik']); // możesz wypisać całą treść strony z komunikatem o błędzie.
 	}
 
 ?>

Powyższy kod może być częścią jakiegoś serwisu, który swoje artykuły udostępnia także w formacie PDF do ściągnięcia, gdyż aktualnie tylko takie pliki można nim wysyłać. Przeanalizujmy go krok po kroku.

  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ń.

Uwaga! Uwaga!
Po wysłaniu nagłówka Location powinniśmy wywołać komendę exit; albo die(), aby zatrzymać nasz skrypt!

Kiedy omówiliśmy sobie już właściwości oraz niektóre możliwości nagłówków, możemy przejść do ciastek (ang. cookies) ustawianych właśnie za ich pomocą.

[edytuj] Ciastka w PHP

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

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

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

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

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

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

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

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

[edytuj] Funkcje buforowania wyjścia

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

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

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

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

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

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

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

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

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

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


[edytuj] Ciastka a bezpieczeństwo

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


[edytuj] Sesje

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

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

[edytuj] Wprowadzenie do sesji

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

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

Oto analiza:

  1. Inicjujemy 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.

[edytuj] Prosta autoryzacja użytkowników

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

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

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

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

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

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

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

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

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

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

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

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

[edytuj] Bezpieczeństwo sesji

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

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

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

?>

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

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

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

[edytuj] Podsumowanie

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

  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

[edytuj] Wysyłanie e-maili

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

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

[edytuj] Funkcja mail()

Aby wysłać e-maila za pomocą PHP, musimy użyć do tego funkcji mail(). Jej składnia jest następująca:

mail(adresat, temat, wiadomość[, nagłówki[, parametry]])

Funkcja zwraca wartość TRUE, jeżeli wiadomość została poprawnie przekazana serwerowi poczty.

[edytuj] Biblioteka pear::mail

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

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

[edytuj] Przykłady

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

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

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

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

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

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

Uwaga! Uwaga!
Nie umieszczaj danych od użytkownika w nagłówkach bez uprzedniego usunięcia z nich przejść do nowej linii. W przeciwnym wypadku użytkownik będzie miał możliwość podrzucenia nowych nagłówków, a nawet dodania załączników do wiadomości i twój skrypt zostanie wykorzystany przez spamboty do rozsyłania spamu.
Porada Porada
Nagłówki używające polskich liter muszą być potraktowane specjalnym kodowaniem, np. Quoted-Printable. W przeciwnym wypadku serwery i czytniki poczty nie rozpoznają kodowania nagłówków i mogą zdeformować polskie znaki lub wręcz uznać je za błąd transferu.

Z poziomu PHP można także wysyłać e-maile w formacie HTML. W tym celu należy dodać do wiadomoś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';
    }
 ?>
Porada Porada
Niektórzy programiści piszą własne implementacje funkcji mail(), samodzielnie łącząc się z serwerem i obsługując protokół SMTP. Pozwala to na uniezależnienie się od ustawień połączenia w pliku php.ini, lecz jest skomplikowane i nie będziemy się tym zagadnieniem tu zajmować.

[edytuj] Dokumentacja

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

[edytuj] Internacjonalizacja

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

[edytuj] Dlaczego "Ż" jest literą?

Uruchom pewien prosty skrypt:

<?php

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

?>

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

<?php

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

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

?>

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

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

[edytuj] Polska data

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

<?php

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

?>

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

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

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

?>

Teraz otrzymujemy prawidłowy tekst: 17 kwietnia 2006.

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

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

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

[edytuj] Wielojęzyczny interfejs

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

<?php

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

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

?>

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

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

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

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

Korzysta z niego poniższy przykład:

<?php

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

?>

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

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

[edytuj] Automatyczna detekcja języka

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

<?php

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

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

?>

Analiza powyższego skryptu:

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

[edytuj] Zakończenie

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

Poprzedni rozdział: Internacjonalizacja Spis treści Następny rozdział: Wstęp do baz danych

[edytuj] System plików

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

[edytuj] Odczyt danych

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

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

<?php

	$f = fopen('plik.txt', 'r');
	
	while(!feof($f))
	{
		echo fread($f, 1024);	
	}
	
	fclose($f);
?>

Każdy dostęp do pliku musi rozpocząć się od jego otwarcia. Zadaniem tym zajmuje się funkcja fopen(). Parametr r nakazuje otwarcie pliku do odczytu. Następnie w pętli pobieramy plik po kawałkach o wielkości jednego kilobajta. W ten sposób dane mogą być przetwarzane "równolegle" z odczytem. Funkcja feof() służy do sprawdzenia, czy osiągnęliśmy koniec pliku. Po zakończonej pracy nasze połączenie z plikiem trzeba zamknąć. Odpowiada za to fclose().

Uwaga! Uwaga!
Zawsze zamykaj swoje pliki. Inaczej możesz zablokować innych użytkowników korzystających ze strony!

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

<?php
	$f = fopen('plik.txt', 'r');
	
	echo fread($f, filesize('plik.txt'));
	
	fclose($f);
?>

Zwróćmy uwagę na jakość podanych przykładów. Zmień nazwę plików, do których się odwołujemy, na jakiś nieistniejący. Oba skrypty wtedy zgłupieją. Pierwszy zaleje nas falą ostrzeżeń przez 30 sekund (potem przestaną się pojawiać), drugi zrobi ich "tylko" kilka (przyczyną jest brak pętli). Dlatego powinniśmy tak przygotować wszystko, abyśmy sami panowali nad komunikatami. Czas stworzyć prymitywną obsługę błędów. Wykorzystamy tutaj operator @, aby zagłuszyć funkcję fopen() i sprawdzić zwracany wynik. Powinna ona zwrócić nam połączenie z plikiem, tj. wartość typu Resource. Zobaczmy:

<?php

	$f = @fopen('inny_plik.txt', 'r') or die('Wystąpił błąd.');
	
	echo fread($f, filesize('inny_plik.txt'));
	
	fclose($f);
?>
Uwaga! Uwaga!
Jeśli skrypt odczytujący zacznie się dziwnie zachowywać, pierwszym krokiem powinno być tymczasowe usunięcie operatora @ - inaczej nigdy nie dowiesz się, co jest przyczyną problemów!
Porada Porada
W celu uproszczenia twojego kodu możesz napisać sobie własny wariant funkcji fopen() posiadający twoją obsługę błędów. Uprości to zarządzanie kodem projektu.

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

<?php

	$tresc = @file_get_contents('plik.txt') or die('Wystąpił błąd.');
	
	echo $tresc;
?>

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

<?php

	$tresc = @file('plik.txt') or die('Wystąpił błąd.');
	
	echo '<ul>';
	foreach($tresc as $linia)
	{
		echo '<li>'.$linia.'</li>';	
	}
	echo '</ul>';
?>

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

implode('', $tresc);

zamiast np.

implode("\n", $tresc);

Pod żadnym pozorem nie odczytuj plików w ten sposób:

implode('', file('plik.txt'));

Sens takiego kodu można streścić w prostym porównaniu: pakować się tylko po to, by się natychmiast rozpakować. Nie służy to niczemu, a konsumuje niezbędny czas. Aby przekonać się, jak mało wydajne jest takie rozwiązanie, spróbuj załadować tak plik tekstowy o wielkości megabajta, następnie powtórz to samo z wykorzystaniem file_get_contents() i porównaj wrażenia.

Porada Jeżeli pragniesz odczytywać pliki binarne, otwieraj je z parametrem rb zamiast r.


Powinniśmy jeszcze wspomnieć o rozwiązaniu tzw. "wg pana od informatyki" (aczkolwiek bardzo go szanujemy). Rozwiązanie najlepiej pokazać na przykładzie:

<?php
	$plik='plik.txt';
	if (file_exists($plik))
	{
		$zawartosc=file($plik);
		if (count($zawartosc)>0)
		{
//nie rob nic. ta linijka jest tu po to, zeby to glupio nie wygladalo.
		}
		else
		{
			die("plik: $plik jest pusty");
		}
	}
	else
	{
		die("plik: $plik nie istnieje"); 
	}
			# ... dalszy kod aplikacji ...
?>

Metoda na "pana nauczyciela" polega na pełnym obsłużeniu wszystkich możliwości jakie mogą wystąpić podczas czytania pliku oraz posłużenia się funkcjami typu file_exists(). Wadą takiego rozwiązania jest jednak to, że pisząc kod możemy zakopać się w if'ach gubiąc główny wątek programu ... a także nie jesteśmy w stanie wymyślić wszystkich możliwych sytuacji, które mogą się zdarzyć.

Uwaga! Uwaga!
Staraj się nie stosować na produkcyjnych stronach rozwiązań w stylu "plik: $plik". W ten sposób ujawniasz napastnikowi strukturę swojego serwisu !

[edytuj] Zapis danych

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

<?php

	$f = fopen('./plik.txt', 'w');
	fwrite($f, 'To jest nowa zawartosc pliku');
	fclose($f);

?>

Po uruchomieniu tego skryptu w plik.txt powinna pojawić nam się nowa zawartość. W przypadku pracy na systemie Linux/Unix sprawdź, czy PHP ma uprawnienia do edycji plików w twoim katalogu roboczym.

W PHP 5.0.0 pojawiła się funkcja file_put_contents(), która upraszcza całą sprawę. Zwraca ona liczbę zapisanych do pliku bajtów i możemy wykorzystać to do kontroli, czy operacja dopisywania faktycznie się udała. Funkcja pobiera dwa parametry: nazwę pliku oraz tekst do wpisania i "firmowo" nie zniekształca danych binarnych.

<?php

	if(file_put_contents('./plik.txt', 'To jest nowa zawartosc pliku') != 0)
	{
		echo 'Udalo sie zapisac nowa zawartosc do pliku.';	
	}

?>

Zadajmy sobie pytanie, co jeśli musimy dopisać dodatkową treść. Naturalnie file_put_contents() także to potrafi. Trzeba tylko skorzystać z trzeciego parametru, w którym możemy ustawiać flagi. FILE_APPEND jest tym, czego potrzebujemy.

<?php

	if(file_put_contents('./plik.txt', ' dopisana tresc', FILE_APPEND) != 0)
	{
		echo 'Udało się dodać zawartość do pliku.';	
	}

?>

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

[edytuj] Informacje o plikach

W wielu przypadkach przydaje się wiedza o tym, co w zasadzie w katalogach mamy. Możemy ją uzyskać, korzystając z rodziny funkcji udostępniających nam różne informacje o plikach. Wszystkie przyjmują za parametr nazwę pliku:

  • is_file() - zwraca true, jeśli obiekt jest plikiem.
  • is_dir() - zwraca true, jeśli obiekt jest katalogiem.
  • is_readable() - zwraca true, jeśli posiadamy prawa do odczytu zawartości obiektu.
  • is_writeable() - zwraca true, jeśli posiadamy prawa do zapisu do obiektu.
  • file_exists() - zwraca true, jeśli plik/katalog istnieje.
  • fowner() - zwraca ID właściciela pliku.
  • fgroup() - zwraca ID grupy, do której plik należy.
  • fperms() - zwraca uprawnienia pliku.
  • filesize() - zwraca wielkość pliku.
  • filemtime() - zwraca czas ostatniej modyfikacji pliku lub false, jeśli nie istnieje.

Przy korzystaniu z nich musimy pamiętać o wydajności. Odczyt wszelkich danych z dysku jest dość powolny, dlatego starajmy się jak najwięcej wycisnąć z pojedynczego wywołania funkcji. Oto przykład: załóżmy, że mamy plik A.txt i na jego podstawie generujemy B.txt zawsze, kiedy ulegnie on zmianie (taki kompilator). Musimy zatem napisać mechanizm sprawdzający, czy można uruchomić kompilację, czy też jest ona zbędna.

<?php

	if(!file_exists('A.txt'))
	{
		die('Plik A.txt nie istnieje!');
	}

	if(file_exists('B.txt'))
	{
		if(filemtime('B.txt') != filemtime('A.txt'))
		{
			echo 'Plik A.txt wymaga kompilacji.';
		}
		else
		{
			echo 'Można czytać z pliku B.txt';
		}	
	}
	else
	{
		echo 'Plik A.txt wymaga kompilacji';
	}

?>

Pozornie wszystko wygląda na poprawne - skrypt prawidłowo raportuje wszystkie sprawy. Jednak robi to zbyt wolno, gdyż przeciążyliśmy go dużą ilością odwołań do dysku twardego. Jeżeli uruchomimy go na witrynie z dużym ruchem, osiągnąłby gorsze wyniki wydajności, niż inne skrypty. Spróbujmy go nieco zmodyfikować. Czy naprawdę potrzebujemy funkcji file_exists()? Okazuje się, że nie. Przecież filemtime() zwróci nam false, jeżeli plik nie będzie istniał i możemy to wykorzystać. Oto poprawiony kod skryptu:

<?php

	$czasA = @filemtime('A.txt');
	
	if($czasA === false)
	{	
		die('Plik A.txt nie istnieje!');
	}
	else
	{
		$czasB = @filemtime('B.txt');
	}

	if($czasB !== false)
	{
		if($czasB != $czasA)
		{
			echo 'Plik A.txt wymaga kompilacji.';
		}
		else
		{
			echo 'Można czytać z pliku B.txt';
		}	
	}
	else
	{
		echo 'Plik A.txt wymaga kompilacji';
	}

?>

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

[edytuj] Zawartość katalogów

[edytuj] Ścieżki dostępu

[edytuj] Wydajność i bezpieczeństwo

[edytuj] Zakończenie

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


Poprzedni rozdział: System plików Spis treści Następny rozdział: Projekt bazy danych

[edytuj] Wstęp do baz danych

Aplikacje bazodanowe to specjalistyczne aplikacje, których głównym celem jest przechowywanie złożonych informacji, zarządzanie nimi oraz ich udostępnianie. Pod pojęciem "zarządzania" rozumie się ich modyfikację, kasowanie i dodawanie, natomiast "udostępnianie" oznacza możliwość ich pobierania na wszystkie możliwe sposoby we wszystkich kombinacjach, posortowane według dowolnego parametru. Bazy danych wykorzystywane są wszędzie tam, gdzie mamy do czynienia ze złożoną organizacją informacji. Również witryny WWW swą zawartość przechowują najczęściej w bazach, podczas gdy skrypty służą jedynie do ich wyświetlania i przetwarzania. Dzięki temu programista nie musi się martwić pisaniem kodu odpowiedzialnego np. za sortowanie wyników - całą pracę wykonali już za niego twórcy bazy danych.

[edytuj] Przegląd terminologii

Przy pracy z bazami danych będziemy stosowali pewną terminologię. Pora, by się z nią zapoznać. Program, który zarządza bazami danych jest nazywany serwerem baz danych. W tym podręczniku będziemy pisali w skrócie serwer DB (DB od angielskiego słowa database oznaczającego po prostu bazę danych). Dane są w nim ułożone w hierarchiczny sposób, który ilustruje poniższy schemat:

Php schemat db.png

Widzimy tu, że serwer DB może utrzymywać kilka różnych baz danych należących do różnych aplikacji. Pojedyncza baza zawiera tabele o określonej strukturze, w których przechowywane są rekordy zawierające dane. Struktura definiuje pola oraz ich typy, w jakich rekordy mogą przechowywać informacje. Poniżej znajduje się graficzna ilustracja zawartości prostej tabeli. Zawiera ona następujące pola:

  • id - unikalny identyfikator rekordu, który pozwala go odnaleźć. Nadawany automatycznie przez serwer DB.
  • name - nazwa produktu.
  • description - opis produktu
  • category - numer kategorii, do jakiej należy produkt.
  • counter - ilość produktów, jaką mamy w magazynie.

Php tabela db.png

Oto kilka dodatkowych informacji o terminach użytych powyżej:

  • tabela - ang. table
  • rekord - zwany też wierszem. Ang. row
  • pole - zwana też kolumną. Ang. field

Pomiędzy tabelami w obrębie bazy mogą występować pewne zależności zwane relacjami. W powyższym przykładzie istnieje relacja między produktami, a kategoriami, do których są przypisane (pole category). Wyróżnia się kilka rodzajów relacji:

  • Jeden do wielu - jednemu rekordowi z tabeli A przypisanych jest kilka rekordów z tabeli B. Przykładem są nasze kategorie i produkty.
  • Wiele do wielu - jednemu rekordowi z tabeli A przypisanych jest kilka rekordów z tabeli B oraz jednemu rekordowi z tabeli B przypisanych jest kilka rekordów z tabeli A. Przykład to książki oraz ich autorzy. Jedna książka może być napisana przez wielu ludzi, jednocześnie pojedynczy człowiek może napisać kilka książek.
  • Jeden do jednego - jednemu rekordowi z tabeli A przypisany jest dokładnie jeden rekord z tabeli B. Relacja ta jest rzadko wykorzystywana.

Relacje można odzwierciedlać w strukturze bazy, a także pobierać dane z ich wykorzystaniem (np. pobrać produkty posortowane według tytułów kategorii, które mieszczą się przecież w innej tabeli). Bazy, w których dozwolone są takie operacje, nazywamy relacyjnymi bazami danych, w przeciwieństwie do płaskich baz danych.

Wszystkie operacje na bazach danych wykonujemy, wysyłając do serwera zapytania (ang. query) sformułowane w specjalnym języku SQL (Structured Query Language). Jego podstawy poznamy w niniejszym podręczniku, lecz po bardziej zaawansowane jego możliwości będziesz musiał sięgnąć do innych źródeł. Terminem ANSI SQL określamy nazwę standardu definiującego język SQL. Różne serwery DB implementują jego założenia lepiej lub gorzej, ale w przypadku korzystania ze złożonych możliwości kompatybilność między nimi nie jest zadowalająca.

[edytuj] Przegląd serwerów DB

Oto krótki przegląd niektórych dostępnych serwerów DB:

  • MySQL - najpopularniejszy serwer DB do zastosowań WWW stworzony przez szwedzką firmę MySQL AB. Można go używać bez żadnych opłat. MySQL słynie ze swej olbrzymiej wydajności, a najnowsza wersja 5.0, z której będziemy korzystać, obsługuje już prawie cały standard ANSI SQL. Początkowo PHP posiadał wbudowaną obsługę tego serwera, lecz w wyniku zmian licencyjnych musiał zrezygnować z tego i obecnie moduł dla MySQL-a należy dodawać ręcznie.
  • PostgreSQL - główny konkurent MySQL-a dostępny na licencji open-source. Jego wydajność jest nieco mniejsza, ale wciąż jest to jedyny darmowy serwer DB, który posiada pełną obsługę standardu ANSI SQL.
  • IBM DB2 Express-C - to bezpłatna edycja znanej bazy IBM DB2, udostępniona na systemy Linux i Windows. Jej darmowe, nielimitowane wykorzystanie możliwe jest w odmianach 32 i 64 bitowych, na serwerach z dwoma procesorami i 4 GB RAM. Dostępne są różne metody wykorzystania DB2 z poziomu PHP, w tym również pełne, bezpłatne środowisko Zend Core for IBM oraz wsparcie dla PDO w PHP5. Więcej informacji można odnaleźć na tej stronie: http://www-306.ibm.com/software/data/db2/udb/edition-expressc.html.
  • SQLite - ten serwer DB jest dość specyficzny, ponieważ w rzeczywistości jest to biblioteka wbudowywana w aplikacje, które go wykorzystują (np. w interpreter PHP). Stąd też do korzystania z niego nie potrzeba żadnych dodatkowych programów. SQLite jest wbudowany domyślnie w PHP, odkąd zmienił się sposób licencjonowania MySQL-a.

W przeszłości PHP posiadał osobne funkcje do komunikacji z każdą z tych baz, dlatego powstawało wiele napisanych w PHP bibliotek unifikujących interfejs (np. ADODB, Creole). Ponadto dodawały one kilka zwiększających wydajność opcji takich, jak cache'owanie wyników zapytań do plików. W PHP 5.1.0 pojawiła się wreszcie wbudowana biblioteka PHP Data Objects, która także udostępnia jednolite API. Skorzystamy z niej w tym podręczniku, posiłkując się dodatkowo polską nakładką Open Power Driver dodającą możliwość cache'owania wyników zapytań.

[edytuj] Spis treści

Ta część podręcznika rozpocznie się od nauki podstaw języka SQL. Następnie nauczysz się podstaw programowania obiektowego, którego znajomość jest niezbędna, aby korzystać z biblioteki PDO. Dowiemy się także, jak w przeszłości komunikowało się z bazami danych oraz napiszemy przykładową aplikację: system newsów.

  1. Podstawy języka SQL
    1. Projekt bazy danych
    2. Zarządzanie rekordami
    3. Pobieranie rekordów
    4. Relacje i indeksy
  2. Wstęp do programowania obiektowego
  3. Biblioteka PDO
  4. Jak to się robiło kiedyś?
  5. phpMyAdmin
  6. Studium przypadku: System newsów
  7. Bazy danych - co dalej?
Poprzedni rozdział: Wstęp do baz danych Spis treści Następny rozdział: Zarządzanie rekordami

[edytuj] Projekt bazy danych

Odstawimy teraz na chwilę PHP i nauczymy się pracować z serwerem baz danych MySQL. Poznamy również podstawy języka SQL do komunikacji z nim.

[edytuj] Wiersz poleceń serwera DB

Aby wykonywać operacje na bazie danych, nie jest konieczne samodzielne tworzenie odpowiednich aplikacji. MySQL posiada specjalny wiersz poleceń, w którym możemy wprowadzać zapytania i oglądać ich wyniki. Serwer DB powinien być już uruchomiony przy starcie systemu operacyjnego, dlatego przystąpimy teraz do rzeczy.

  • Jeżeli pracujesz w systemie uniksowym, odszukaj katalog z danymi binarnymi, w którym mogła po instalacji zostać zlokalizowana aplikacja mysql. Uruchom ją poleceniem:
mysql -u root -p

Lub, jeżeli łączysz się poprzez sockety:

mysql -u root -p -S /tmp/mysql.sock
  • Użytkownik systemu Windows musi otworzyć systemowy wiersz poleceń i komendą cd przełączyć się na katalog z instalacją MySQLa, a następnie na znajdujący się w nim folder bin. Tutaj należy uruchomić program mysql.exe z parametrami -u root -p. Przykładowe postępowanie wygląda tak:
C:\> D:
D:\> cd Serwer/mysql/bin
D:\Serwer\mysql\bin> mysql.exe -u root -p

Podstawą bezpieczeństwa w bazie danych MySQL są użytkownicy, którzy mają jasno określone przywileje dostępu do baz. Najważniejszym z nich jest root, służący na serwerach produkcyjnych jedynie do administracji. Jednak w domowych warunkach można go wykorzystać także do projektowania własnych baz oraz testowania skryptów. W naszym przypadku podaliśmy podczas instalacji, że hasło tego użytkownika to także root. Podaj je, gdy zostaniesz o to zapytany przez wiersz poleceń.

Jeżeli po wpisaniu hasła ujrzysz znak zachęty mysql>, wszystko poszło OK i jesteśmy gotowi do pracy. W wierszu poleceń obowiązują następujące reguły:

  1. Zapytania kończymy średnikiem lub sekwencją \g. Jeżeli zabraknie tego elementu, ENTER zwyczajnie będzie Cię przenosił do nowej linijki!
  2. Pomoc włączamy sekwencją \h
  3. Opuszczamy wiersz poleceń sekwencją \q

[edytuj] Tworzenie bazy danych

Standardowo po instalacji MySQL posiada stworzone dwie domyślne bazy: test oraz mysql. Pierwsza jest pusta, a druga zawiera ustawienia serwera i lepiej nic tam nie grzebać bez dokładnej znajomości jej budowy. My jednak nie skorzystamy z żadnej z nich.

CREATE DATABASE produkty;

To zapytanie utworzy nam nową bazę danych o nazwie produkty. Musimy się teraz na nią przełączyć:

USE produkty;

Po każdym wykonanym zapytaniu wiersz poleceń wyświetla nam informacje o jego rezultacie. Napis Query OK oznacza, że zostało ono zaakceptowane i poprawnie wykonane. Dalej mogą wystąpić informacje diagnostyczne (ilość dokonanych zmian, czas wykonywania lub lista wyników).

[edytuj] Tworzenie tabeli

Jak powiedzieliśmy, właściwe dane przechowywane są w tabelach. Ich tworzenie polega na definiowaniu szczegółowej struktury rekordów. Utworzymy teraz tabelę produkty przechowującą informacje o różnych produktach:

CREATE TABLE `produkty` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`nazwa` VARCHAR(60) NOT NULL,
`opis` TEXT NOT NULL,
`ilosc` SMALLINT DEFAULT '0',
`cena` FLOAT NOT NULL,
`jakosc` TINYINT NOT NULL 
) TYPE = MYISAM;

W pierwszej linijce nakazujemy utworzenie tabeli o podanej nazwie. Nawias rozpoczyna definicję jej struktury. Opisy budowy poszczególnych pól oddzielone są przecinkami, a informacje do każdego z nich podawane są w następującej kolejności:

  1. Nazwa pola
  2. Typ pola
  3. Czy pole może być puste?
  4. Wartość domyślna
  5. Parametry dodatkowe
  6. Klucze i indeksy

Przyjrzyjmy się zatem poszczególnym polom:

  1. id - pole to będzie przechowywało liczbowy, jednoznaczny identyfikator rekordu. Dwa rekordy nie mogą posiadać tego samego ID. Pole to powinny posiadać w zasadzie wszystkie tabele, gdyż jest ono podstawą relacji oraz systemów zarządzania bazą. Parametry to:
    • INT - typ liczbowy
    • NOT NULL - pole nie może być puste
    • AUTO_INCREMENT - MySQL automatycznie będzie dbał o nadawanie nowo dodawanym rekordom kolejnych ID.
    • PRIMARY KEY - klucz główny określający przeznaczenie tego pola jako podstawy do identyfikacji rekordów.
  2. nazwa - tutaj będziemy umieszczali nazwę produktu. Parametry to:
    • VARCHAR(60) - typ tekstowy. Maksymalna długość to 60 znaków.
    • NOT NULL - pole nie może być puste
  3. opis - opis produktu z dodatkiem wazeliny. Parametry to:
    • TEXT - typ tekstowy (maksymalna długość: 64 kB)
    • NOT NULL - pole nie może być puste
  4. ilosc - określa ilość sztuk produktu w magazynie. Parametry to:
    • SMALLINT - typ liczbowy
    • DEFAULT '0' - pole może pozostać puste, a domyślnie nowym rekordom nadajemy tutaj wartość 0.
  5. cena - cena produktu. Parametry to:
    • FLOAT - typ liczbowy z obsługą ułamków (aby można było także grosze uwzględniać)
    • NOT NULL - pole nie może być puste
  6. jakosc - liczbowe oznaczenie jakości produktu. Parametry to:
    • TINYINT - typ liczbowy
    • NOT NULL - pole nie może być puste

Ostatnia linijka zawiera zamknięcie definicji struktury oraz określenie typu tabeli. Nie musisz się nim teraz przejmować. Wystarczy na razie wiedzieć, że domyślnym jest obecnie InnoDB, ale do większości zastosowań używa się wydajniejszego MyISAM.

Zauważyłeś już pewnie, że w zapytaniu wykorzystaliśmy kilka różnych typów liczbowych. Oto dokładniejsza specyfikacja ważniejszych typów:

  1. TINYINT - liczba jednobajtowa. Wartości od -128 do 127.
  2. UNSIGNED TINYINT - liczba jednobajtowa bez znaku. Wartości od 0 do 255. Słowo UNSIGNED przed nazwami kolejnych typów liczbowych robi to samo, co w tym przypadku.
  3. SMALLINT - liczba dwubajtowa. Wartości od -32768 do 32767, a bez znaku od 0 do 65535.
  4. MEDIUMINT - liczba trzybajtowa. Wartości od -8388608 do 8388607, a bez znaku od 0 do 16777215.
  5. INT - liczba czterobajtowa. Wartości od -2147483648 do 2147483647, a bez znaku od 0 do 2147483647.
  6. BIGINT - liczba ośmiobajtowa. Wartości od -9223372036854775808 do 9223372036854775807, a bez znaku od 0 do 18446744073709551615.
  7. FLOAT - liczba zmiennoprzecinkowa czterobajtowa (tak, jak w PHP).
  8. VARCHAR(M) - tekst o długości N od 0 do M znaków, gdzie M < 256. W pamięci zajmuje N + 1 bajtów (dodatkowy zawiera długość tekstu).
  9. CHAR(M) - tekst o długości od 0 do M znaków, gdzie M < 256. W przeciwieństwie do poprzedniego typu, zajmuje w pamięci zawsze M bajtów nawet, jeżeli znajdujący się w nim tekst jest krótszy.
  10. TEXT - typ tekstowy doskonały do przechowywania dłuższych treści. Można w nim zmieścić aż 64 kB danych (65535 znaków).
  11. BLOB - typ do przechowywania danych binarnych, np. plików. Maksymalna wielkość to także 64 kB.
  12. BOOL - typ logiczny, równoważnik zapisu TINYINT(1).
  13. DATE - wyspecjalizowany typ do przechowywania daty. Bardzo rzadko wykorzystywany w poważniejszych aplikacjach PHP ze względu na jego niewygodne przetwarzanie i formatowanie.

W przypadku typów liczbowych bardzo często spotyka się zapisy np. INT(8) lub MEDIUMINT(6). Liczby w nawiasach nie mają nic wspólnego z wielkością danych, jakie można w nich trzymać. Mówią one bazie danych MySQL, że jeśli dana liczba jest krótsza niż np. 8 znaków, to należy ją dopełnić spacjami do tej długości, kiedy będziemy na niej operować, jak na tekście.

[edytuj] Specyfika języka SQL

Napisaliśmy już kilka zapytań i widać z nich, że SQL w wielu miejscach przypomina zwyczajny mówiony angielski. Pora poznać kilka jego cech:

  1. Wielkość liter nie odgrywa żadnej roli, lecz programiści używają ich ze względów estetycznych. Zapis create table `produkty` także jest poprawny.
  2. Odwrócone apostrofy są używane do określania nazw tabel, baz i pól, ale nie są one niezbędne. Zapisy create table produkty oraz id int not null są poprawne, lecz trzeba tu zwrócić uwagę na jedną rzecz, którą zademonstrujemy na przykładzie. Otóż chcielibyśmy mieć w tabeli pole "order". Piszemy więc order int not null, ale nagle okazuje się, że MySQL zgłasza w tej linijce błąd. Co jest nie tak? "order" jest jednym ze słów kluczowych języka SQL i aby tak nazwać tabelę, musimy koniecznie umieścić ją w odwróconych apostrofach. Wielu programistów, aby zapytania nie przypominały grochu z kapustą, z definicji używa ich zatem wszędzie, unikając w ten sposób wszystkich niespodzianek, a także niekompatybilności z nowymi wersjami, które dodają coraz więcej słów kluczowych.

[edytuj] Ćwiczenia

W ramach treningu utwórz następujące tabele:

  1. Tabela klientów sklepu z polami id, imie, nazwisko, wiek, miejscowosc, ulica, numer_domu, numer_mieszkania, telefon.
  2. Tabela dostawców sklepu z polami id, nazwa, dzien_tyg_dostawy, naleznosc
  3. Tabela kategorii produktów z polami id, nazwa, opis, ilosc_produktow.

Pamiętaj, że możesz sprawdzić strukturę już utworzonej tabeli poleceniem:

DESCRIBE tabela;

Jeżeli coś Ci nie wyszło, wykonaj

DROP TABLE tabela;

aby skasować tabelę.

Poprzedni rozdział: Projekt bazy danych Spis treści Następny rozdział: Pobieranie rekordów

[edytuj] Zarządzanie rekordami

Wiemy już, jak utworzyć bazę danych oraz strukturę tabel. Niewątpliwie bez tych operacji nie można zacząć, lecz później podstawowymi operacjami stają się te, dzięki którym możemy zarządzać danymi. Właśnie w tym odcinku dowiemy się, jak dodawać, modyfikować oraz usuwać niepotrzebne rekordy. Na początek proste polecenie, które pozwoli nam na wyświetlenie obecnej zawartości tabeli:

SELECT * FROM `tabela`

Gdzie zamiast tabela wstawiamy nazwę naszej tabeli. Szczegółowy opis tego zapytania poznamy w następnych dwóch odcinkach. Teraz posłuży nam on jedynie do sprawdzania, czy wszystko przebiega poprawnie.

[edytuj] Dodawanie rekordów

Aby dodać rekord do tabeli, należy wysłać zapytanie INSERT. Ma ono generalnie dwie możliwe składnie:

INSERT INTO `tabela` VALUES('Wartość pola 1', 'Wartość pola 2', 'Wartość pola 3');
INSERT INTO `tabela` (`pole1`, `pole2`, `pole3`) VALUES('Wartość pola 1', 'Wartość pola 2', 'Wartość pola 3');

Oba powodują utworzenie nowego rekordu w podanej tabeli, lecz istnieje między nimi pewna różnica. W pierwszym zapytaniu musimy bezwzględnie podać wartości wszystkich pól nowego rekordu, jakie mamy zdefiniowane w strukturze tabeli, w identycznej kolejności. Drugie zapytanie pozwala nam w pierwszym z nawiasów wymienić listę pól, jakie nas interesują i dopiero potem podać ich wartości. W praktyce znacznie częściej używa się właśnie jego, gdyż nie trzeba podawać wartości pól ID, które nadawane są przez bazę automatycznie. Wróćmy zatem do naszej tabeli produkty. Wstawmy do niej kilka rekordów:

INSERT INTO `produkty`
  (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES(
   'Długopisy niebieskie',
   'Długopisy z niebieskim wkładem firmy XXX',
   100,
   2.15,
   3);
INSERT INTO `produkty`
  (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES(
   'Długopisy czerwone',
   'Długopisy z czerwonym wkładem firmy XXX',
   50,
   2.15,
   3);
INSERT INTO `produkty`
  (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES(
   'Zszywacze',
   'Metalowy zszywacz + 100 zszywek.',
   30,
   9.50,
   4);
INSERT INTO `produkty`
  (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES(
   'Karteczki samoprzylepne',
   'Samoprzylepne kartki koloru żółtego 10x10 cm w kompletach po 100 sztuk',
   200,
   3.60,
  2);

Istnieje także możliwość wstawienia kilku rekordów naraz za pomocą jednego zapytania INSERT:

INSERT INTO `produkty` (`nazwa`, `opis`, `ilosc`, `cena`, `jakosc`) VALUES
('Strugaczki', 'Czerwone, do dwóch rozmiarów ołówków', 60, 0.90, 4),
('Gumki do ścierania', 'Gumki do ścierania ołówków firmy ZZZ', 97, 0.50, 3),
('Spinacze do papieru', 'Metalowe spinacze do papieru w kompletach po 50 sztuk.', 68, 0.50, 4);

Tutaj po VALUES podajemy kilka bloków wartości dla kolejnych rekordów, każdy z nich w nawiasach i oddzielony przecinkiem.

Pamiętaj, że wartości tekstowe musimy zawsze podawać w apostrofach. Liczby można podawać zarówno z nimi, jak i bez, lecz podczas programowania apostrofami obejmuje się najczęściej wszystkie wstawiane ze skryptu wartości.

[edytuj] Modyfikowanie rekordów

Czasem zachodzi konieczność zmodyfikowania niektórych informacji. Tu pomocne będzie zapytanie UPDATE. Ma ono bardzo prostą składnię:

UPDATE `tabela` SET `pole1` = 'Nowa wartość', `pole2` = 'Nowa wartość';

Jednak uważaj! Gdybyś wykonał powyższe zapytanie, podstawiając odpowiednie dane, okazałoby się, że zmiany zostały wprowadzone we wszystkich rekordach! Bardzo rzadko jest to pożądana rzecz, ponieważ o wiele częściej chcemy zmodyfikować pewną, konkretną grupę. Do jej uwzględnienia użyjemy nowej klauzuli: WHERE wyrażenie umieszczanej po liście pól do podmiany. Wyrażenie jest dowolnym poprawnym wyrażeniem języka SQL, a układa się je podobnie, jak te w PHP. Szczegółowe informacje o budowaniu wyrażeń poznamy w następnym odcinku, teraz ograniczymy się do kilku prostych sztuczek.

Na początek zmienimy opis i cenę produktu o ID 1:

UPDATE `produkty` SET `opis` = 'Długopisy niebieskie firmy YYY', `cena` = '2.45' WHERE `id` = '1';

Teraz coś trudniejszego: nasz sklep planuje podwyżkę cen najlepszych produktów (jakość 4) o 50 groszy. Wiele osób próbuje robić to, pobierając ceny wszystkich interesujących je produktów i zmieniając je setkami zapytań UPDATE, po jednym dla jednego rekordu. Jest to bardzo niepraktyczne, ponieważ język SQL jest na tyle zaawansowanym narzędziem, że radzi sobie z tym bez trudu:

UPDATE `produkty` SET `cena` = (`cena` + 0.5) WHERE `jakosc` = 4;

Powinniśmy teraz ujrzeć informację, że zmodyfikowane zostały trzy rekordy.

[edytuj] Usuwanie rekordów

Niepotrzebne rekordy kasujemy zapytaniem DELETE. Podobnie, jak w poprzednim przypadku, należy zastosować klauzulę WHERE, aby określić, które z nich chcemy usunąć. Inaczej możemy pożegnać się z całą zawartością tabeli.

DELETE FROM `produkty` WHERE `id` = 3;

Zdarza się, że tabelę trzeba rzeczywiście wyczyścić, np. z danych testowych, aby aplikacja mogła być używana na normalnym serwerze. Jednak wtedy nie powinno się wykorzystywać polecenia DELETE FROM `produkty`. Do tego celu służy specjalne zapytanie:

TRUNCATE `produkty`;

Różnica pomiędzy pierwszym i drugim jest podobna, jak pomiędzy zaznaczeniem wszystkich plików na dysku i kliknięciu "Delete", a uruchomieniem jego formatowania.

Wykonując wiele zapytań DELETE oraz INSERT zauważysz, że pozostają Ci luki w numeracji. Spróbuj dodać teraz jakiś rekord. Jeżeli wykonałeś wcześniej zapytanie kasujące ten o ID 3, MySQL pozostawi tam lukę, nadając nowemu rekordowi następny w kolejności ID - 4. Prawdopodobnie z przyczyn estetycznych niektórym nowym programistom to zachowanie przeszkadza, lecz jest to zupełnie błędne podejście do problemu. Jak wspomnieliśmy bowiem, MySQL jest relacyjną bazą danych, w której rekordy z jednej tabeli mogą być połączone odpowiednimi relacjami z rekordami w drugiej. Wyobraźmy sobie więc, co by się stało, gdybyśmy skasowali jakąś kategorię np. newsów w naszym serwisie, a potem dodali nową i okazało się, że MySQL zaliczył w jej poczet wszystkie newsy z tej usuniętej, gdyż nowy rekord zajął pustą lukę w numeracji. Działanie takie stwarza poważne zagrożenie synchronizacji bazy i wprowadza w nią element losowości. Dlatego zaufaj twórcom serwerów DB, oni naprawdę znają się na rzeczy i nie podejmuj zbędnych prób zmieniania na gorsze tego, co wyraźnie służy zarówno tobie, jak i twojej bazie.

[edytuj] Zamiana rekordów

Bardzo przydatną operacją jest automatyczne podmienianie rekordów. Polega ono na tym, że jeżeli wstawiamy rekord A do tabeli, w której znajduje się już rekord B o podobnym kluczu, jest on automatycznie nadpisywany przez system. MySQL posiada dwa zapytania, które są pomocne przy podmienianiu rekordów, jednak zanim się do nich dobierzemy, musimy utworzyć sobie nową tabelę.

Aby MySQL mógł zdecydować, czy rekord należy nadpisać, musi wiedzieć, które pola tabeli przechowują wartości unikalne, czyli takie, że nie można znaleźć dwóch rekordów o identycznej wartości w tym polu. Wiemy już, że taką właściwość powinno mieć pole id, i rzeczywiście - informujemy o tym bazę, tworząc dla niego tzw. klucz główny (PRIMARY KEY). Dba on o to, aby wartości się nie powtarzały. Jednak klucz ten można nałożyć tylko na jedno pole naraz, a operacja REPLACE przy rekordach mających z definicji nie tylko unikalne, ale też nadawane automatycznie ID, zbyt dużego sensu nie ma. Dlatego istnieje indeks UNIQUE, który może być nałożony na dowolnie dużo pól i także sprawi, że zyskają one tę właściwość. Aby pokazać to w praktyce, utwórzmy tabelę dla elektronicznego słownika (w wersji uproszczonej, oczywiście):

CREATE TABLE `slownik` (
`id` MEDIUMINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`haslo` VARCHAR(40) NOT NULL,
`znaczenie` VARCHAR(255) NOT NULL,
  UNIQUE(
   `haslo` 
  )
) ENGINE = MYISAM;

Mamy tu trzy pola: id, haslo oraz znaczenie. ID służy jedynie celom administracyjnym (wyszukiwanie względem liczby jest zawsze szybsze, niż względem tekstu). Dla użytkownika naszego słownika najważniejsze będzie jednak pole haslo, które także powinno mieć wartości unikalne. Indeks UNIQUE tworzymy tak, jak na przykładzie: po liście pól wstawiamy słowo kluczowe UNIQUE, w którym w nawiasie wymieniamy pola posiadające taką właściwość.

Jeżeli jesteśmy administratorami słownika, mogącymi dodawać do niego nowe hasła, unikalność obsługiwana przez MySQL ma dla nas duże znaczenie. Kiedy będzie w nim bowiem już dużo słów, ze zwykłej pomyłki możemy spróbować dodać po raz drugi to samo. Tradycyjne polecenie INSERT zareagowałoby błędem powiadamiającym, że próbujemy utworzyć rekord, w którym powtarza się unikalna wartość. Dlatego możemy zastosować inne zapytanie: REPLACE. Wstawiamy nim pierwsze trzy hasła:

REPLACE slownik (haslo, znaczenie) VALUES('tree', 'drzewo');
REPLACE slownik (haslo, znaczenie) VALUES('house', 'dom (budynek)');
REPLACE slownik (haslo, znaczenie) VALUES('sign', 'znak');

W tym momencie działają one identycznie, jak znane nam zapytanie INSERT (mają nawet podobną składnię, chociaż dozwolone jest stosowanie także składni polecenia UPDATE). Możemy zapytać się, jaka jest zawartość tabeli i pokażą nam się wszystkie trzy wymienione rekordy. Spróbujmy teraz po raz drugi dodać istniejące hasło (np. tree):

REPLACE slownik (haslo, znaczenie) VALUES('tree', 'drzewo, drzewko');
Query OK, 2 rows affected (0.00 sec)

Zwróć uwagę, jaki komunikat kontrolny pokazał MySQL: zmodyfikowane zostały dwa rekordy. Wyświetlmy zawartość tabeli: okazuje się, że pierwotny rekord tree (z ID równym 1) został skasowany, a na jego miejscu pojawił się nowy, któremu serwer nadał ID 4. Mamy zatem sformułowaną zasadę działania polecenia REPLACE:

  1. Spróbuj dodać nowy rekord.
  2. Jeśli nie powiedzie się z powodu duplikacji unikalnej wartości, usuń stary rekord powodujący kolizję.
  3. I ponownie dodaj nowy rekord.
Uwaga! Uwaga!
Polecenie REPLACE nie jest częścią standardu ANSI SQL, dlatego najprawdopodobniej nie będzie zaimplementowane na bazach innych, niż MySQL!

REPLACE nie jest jedynym poleceniem realizującym zamianę rekordów. Od wersji 4.1.0 MySQL obsługuje także rozszerzenie zapytania INSERT o element ON DUPLICATE KEY UPDATE, zgodne ze standardem ANSI SQL. Dzięki niemu nie trzeba kasować kolidującego rekordu, lecz jedynie nadpisać jego wartości nowymi. Dodajmy ponownie jakieś istniejące hasło:

INSERT INTO slownik (haslo, znaczenie) VALUES('house',  'dom (budynek), rodzaj obiektu mieszkalnego.')
ON DUPLICATE KEY UPDATE znaczenie=VALUES(znaczenie);
Query OK, 2 rows affected (0.02 sec)

Pierwsza część tego zapytania to znany nam już dobrze INSERT. Próbuje on wstawić nowy rekord. Kiedy jednak zajdzie kolizja, do akcji wkracza nowa część: ON DUPLICATE KEY UPDATE, w której możemy zdefiniować sposób nadpisania starego rekordu według składni nazwa_pola = nowa_wartosc, nazwa_pola = nowa_wartosc, .... Możemy tutaj ustawiać statyczne wartości, np. pole = 1, lub też odwołać się do wartości pola, która miała być wstawiona poleceniem INSERT. Służy do tego funkcja VALUES(nazwa_pola). Wykorzystaliśmy ją w powyższym przykładzie, aby wprowadzić nową definicję hasła na miejsce starej.

Dzięki możliwości nadpisywania istniejących rekordów, nasza baza stanie się bardziej elastyczna, a my nie będziemy musieli samodzielnie realizować sprawdzania, czy przypadkiem nie spowodujemy jakiejś kolizji. Jeżeli jednak zamierzamy napisać aplikację, która będzie mogła pracować z różnymi systemami DB, musimy sprawdzić, czy wszystkie one obsługują podaną operację, lub napisać dla każdej z nich części kodu osobno.

[edytuj] Błędy w zapytaniach

Także w języku SQL można popełnić błędy, które uniemożliwią wykonanie zapytania. Każdy serwer DB raportuje je inaczej. Ponieważ sztandarową bazą w tym podręczniku jest MySQL, pokażemy sposoby debugowania zapytań właśnie dla niego.

Zacznijmy od błędów składni. Wykonaj następujące zapytanie:

UPDATE `produkty` SET `opis` = 'Długopisy niebieskie firmy YYY' `cena` = '2.45' WHERE `id` = '1';

Zawiera ono celowo wprowadzony błąd, który powoduje pokazanie się komunikatu

You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to use near
'cena` = '2.45' WHERE `id` = '1'' at line 1

Komunikat pokazał nam użyteczną informację o miejscu lokalizacji problemu. Według niego coś jest nie tak w okolicy ciągu cena` = '2.45' WHERE `id` = '1'. Niepisana zasada mówi, że najczęściej błąd popełniliśmy bezpośrednio przed nim. Zobaczmy więc, co znajduje się w zapytaniu przed odwołaniem do pola "cena". Okazuje się, że brakuje przecinka oddzielającego je od porzedniej definicji nowej wartości. Kiedy go tam umieścimy, wszystko zaczyna prawidłowo działać.

Inny rodzaj problemu może powstać przy pracy z polami "PRIMARY KEY" oraz "UNIQUE" (o nich dalej). Oba te atrybuty nadają polu właściwość unikalności, czyli nie mogą istnieć dwa rekordy o takich samych wartościach w tym miejscu. Przykładowo wywołajmy takie zapytanie INSERT:

INSERT INTO `produkty`
  VALUES(
   '3',
   'Długopisy niebieskie',
   'Długopisy z niebieskim wkładem firmy XXX',
   100,
   2.15,
   3);

Jest w nim wymieniony ID produktu. Jeżeli wykonywałeś wszystkie zapytania w tym odcinku, prawdopodobnie nic się teraz nie stanie, ponieważ rekord o ID 3 niedawno skasowaliśmy. W takim wypadku wykonaj je jeszcze raz, a baza pokaże ci wtedy

Duplicate entry '3' for key 1

Oznacza on tyle, że w kluczu pierwszym (w naszym przypadku jest to pole "id") próbujemy po raz drugi umieścić wartość 3, co jest niedozwolone.

Poprzedni rozdział: Zarządzanie rekordami Spis treści Następny rozdział: Relacje i indeksy

[edytuj] Pobieranie rekordów

Osobnego omówienia wymaga operacja pobierania rekordów z bazy danych, która jest esencją pracy z tego typu aplikacjami. W poprzednim rozdziale poznaliśmy dla potrzeb testowych zapytanie SELECT * FROM tabela, lecz było ono podane wyłącznie, aby można było sprawdzić, czy modyfikacja zawartości tabel rzeczywiście się powiodła. Tymczasem jego możliwości są dużo bardziej skomplikowane i umożliwiają wykonywanie wielu ciekawych rzeczy. Teraz przyjrzymy się im dokładniej.

[edytuj] Filozofia zapytania SELECT

Zapytanie SELECT to w zasadzie zbiór klauzul, które możemy dodawać i odejmować, działających jak filtry dla danych. Jako rezultat działania otrzymujemy zawsze to, co przejdzie przez wszystkie z nich. Podstawową i jedyną obowiązkową klauzulą jest oczywiście SELECT dane pokazująca, co należy pobrać. Za dane możemy podstawić listę wyrażeń odseparowanych przecinkami, które są nam potrzebne. Oto malutki przykład:

SELECT 1

Jego wykonanie w wierszu poleceń spowoduje wyświetlenie się tekstowej tabelki z wynikiem:

+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

Pierwszy wiersz zawiera zawsze nazwy wszystkich kolumn w rekordzie. Nazwy te wykorzystamy, kiedy zaczniemy pisanie skryptów łączących się z bazą, aby pobrać wyniki z rekordów. Kolejne wiersze obrazują wszystkie pasujące rekordy wyników.

Z doświadczenia możemy powiedzieć, że pozostawianie bazie danych MySQL spraw nazewnictwa pól na liście wyników nie należy do najszczęśliwszych i w wielu przypadkach zachodzi potrzeba jego ręcznego określenia nazwy. Można to uczynić, dodając po każdej wartości do pobrania dodatku AS `nowa_nazwa`:

SELECT 1 AS `pole`
+------+
| pole |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

Pobieranie statycznych danych nie wnosi jednak dużo do funkcjonalności baz danych. O wiele więcej zyskamy, kiedy będziemy mogli pobierać rekordy z tabel. Aby móc wymienić nazwy pól w klauzuli SELECT, musimy dołożyć do zapytania taki dodatek: FROM lista_tabel. lista_tabel to lista tabel odseparowanych przecinkami, z których zamierzamy korzystać. W tym odcinku poprzestaniemy na pojedynczej tabeli i dopiero później powiemy sobie, jak można obsłużyć więcej. Spróbujmy dowiedzieć się zatem czegoś o stworzonej ostatnio tabeli produkty:

SELECT id, nazwa FROM produkty;
+----+-------------------------+
| id | nazwa                   |
+----+-------------------------+
|  1 | Dlugopisy niebieskie    |
|  2 | Dlugopisy czerwone      |
|  3 | Zszywacze               |
|  4 | Karteczki samoprzylepne |
+----+-------------------------+
4 rows in set (0.02 sec)

Przypominamy, że zapytania kończymy średnikiem. Tabelka powyżej to wynik jego działania. Widzimy w niej pobrane pola id oraz nazwa wszystkich rekordów w tabeli produkty. Gdybyśmy chcieli pobrać wartości wszystkich pól, moglibyśmy w wykazie danych wpisać po prostu gwiazdkę:

SELECT * FROM produkty;

Okazuje się jednak, że paradoksalnie takie rozwiązanie ma mniejszą wydajność, niż wypisanie wszystkich pól ręcznie! Miej to na uwadze podczas projektowania twoich zapytań.

[edytuj] Klauzula WHERE

Do tej pory MySQL zwracał nam wszystkie rekordy bez wyjątku, lecz w codziennej praktyce na dane nakłada się rozmaite warunki pełniące rolę filtrów. Jeżeli chcemy wiedzieć, którzy użytkownicy naszego serwisu napisali już ponad 200 postów, nie musimy pobierać wszystkiego i dokonywać ręcznej analizy informacji. Wystarczy nałożyć na zapytanie warunek, który nie dopuści do listy wyników tych rekordów, gdzie ilość postów jest mniejsza niż podana wartość. Wszystko to należy do kompetencji klauzuli WHERE warunek, która pojawia się również przy zapytaniach UPDATE oraz DELETE. Najprostszą operacją jest bez wątpienia zwrócenie konkretnego rekordu:

SELECT id, nazwa FROM produkty WHERE id = 3;
+----+-------------------------+
| id | nazwa                   |
+----+-------------------------+
|  3 | Zszywacze               |
+----+-------------------------+
1 row in set (0.01 sec)

Identycznie, jak w przypadku PHP, warunek jest zbiorem wyrażeń połączonych operatorami, których kolejnością także możemy manewrować wykorzystując nawiasy. Oto lista operatorów logicznych oraz operatorów porównania:

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

Operatory AND oraz OR posiadają także warianty && oraz || (tak samo PHP posiada and i or). Oprócz tego, dostępne są tradycyjne operatory arytmetyczne, a także kilka specjalnych, niewystępujących nigdzie indziej:

[edytuj] pole IN(wartosci)

Prawda, jeśli wartość pola znajduje się na liście wartości podanej w nawiasach. Spróbujemy pobrać nim rekordy o ID 1, 2 oraz 4.

SELECT id, nazwa FROM produkty WHERE id IN(1,2,4);

[edytuj] pole NOT IN(wartosci)

Prawda, jeśli wartość pola NIE znajduje się na liście wartości podanej w nawiasach. Poniżej to samo zapytanie, ale omijające rekordy o podanych ID.

SELECT id, nazwa FROM produkty WHERE id NOT IN(1,2,4);

[edytuj] pole BETWEEN mniejszy AND wiekszy

Wartość pola znajduje się w przedziale od mniejszy do wiekszy. Spróbujemy pobrać nim produkty, których ilość w magazynie waha się od 0 do 100:

SELECT id, nazwa FROM produkty WHERE ilosc BETWEEN 0 AND 100;

[edytuj] pole IS NULL

Podczas tworzenia tabel powiedzieliśmy sobie nieco o polach z dozwolonymi wartościami pustymi (null). Za pomocą tego operatora oraz jego przeczenia IS NOT NULL możemy sprawdzać, czy dane pole zawiera wartość pustą, czy nie. W naszej tabeli tylko pole ilosc zezwala na użycie wartości pustych. Sprawdźmy więc, które rekordy takowe posiadają:

SELECT id, nazwa FROM produkty WHERE ilosc IS NULL;

[edytuj] Klauzula ORDER BY

Ta klauzula dodaje możliwość sortowania wyników według określonego kryterium. Tradycyjnie podajemy ją po WHERE. Jej składnia to: ORDER BY lista_wyrazen. lista_wyrazen to lista oddzielonych przecinkami wyrażeń, według wartości których zostaną posortowane rekordy. Domyślnie obowiązuje kolejność od najmniejszego do największego, ale możemy ją odwrócić, dodając po wyrażeniu słowo DESC. Spróbujmy posegregować nasze rekordy względem ceny.

SELECT id, nazwa, cena FROM produkty ORDER BY cena;
+----+-------------------------+------+
| id | nazwa                   | cena |
+----+-------------------------+------+
|  6 | Gumki do scierania      |  0.5 |
|  7 | Spinacze do papieru     |  0.5 |
|  5 | Strugaczki              |  0.9 |
|  1 | Dlugopisy niebieskie    | 2.15 |
|  2 | Dlugopisy czerwone      | 2.15 |
|  4 | Karteczki samoprzylepne |  3.6 |
|  3 | Zszywacze               |  9.5 |
+----+-------------------------+------+
7 rows in set (0.00 sec)

A teraz w odwrotnej kolejności:

SELECT id, nazwa, cena FROM produkty ORDER BY cena DESC;
+----+-------------------------+------+
| id | nazwa                   | cena |
+----+-------------------------+------+
|  3 | Zszywacze               |  9.5 |
|  4 | Karteczki samoprzylepne |  3.6 |
|  1 | Dlugopisy niebieskie    | 2.15 |
|  2 | Dlugopisy czerwone      | 2.15 |
|  5 | Strugaczki              |  0.9 |
|  6 | Gumki do scierania      |  0.5 |
|  7 | Spinacze do papieru     |  0.5 |
+----+-------------------------+------+
7 rows in set (0.00 sec)

Możemy też przyjąć kolejne kryterium sortowania, jeśli dwa rekordy będą miały identyczną cenę. Przyjmijmy, że wtedy będą one sortowane pod względem tytułu.

SELECT id, nazwa, cena FROM produkty ORDER BY cena DESC, nazwa;
+----+-------------------------+------+
| id | nazwa                   | cena |
+----+-------------------------+------+
|  3 | Zszywacze               |  9.5 |
|  4 | Karteczki samoprzylepne |  3.6 |
|  2 | Dlugopisy czerwone      | 2.15 |
|  1 | Dlugopisy niebieskie    | 2.15 |
|  5 | Strugaczki              |  0.9 |
|  6 | Gumki do scierania      |  0.5 |
|  7 | Spinacze do papieru     |  0.5 |
+----+-------------------------+------+
7 rows in set (0.00 sec)

Widzimy teraz, że rekordy "Długopisy czerwone" oraz "Długopisy niebieskie" zamieniły się miejscami. Jeśli połączymy wszystko z klauzulą WHERE, poczujemy prawdziwą potęgę baz danych. Posortujmy według ceny tylko te rekordy, których jakość oznaczona jest jako 3:

SELECT id, nazwa, cena FROM produkty WHERE jakosc = 3
ORDER BY cena DESC,  nazwa;
+----+-----------------------+------+
| id | nazwa                 | cena |
+----+-----------------------+------+
|  2 | Dlugopisy czerwone    | 2.15 |
|  1 | Dlugopisy niebieskie  | 2.15 |
|  6 | Gumki do scierania    |  0.5 |
+----+-----------------------+------+
3 rows in set (0.00 sec)

[edytuj] Klauzula LIMIT

Na stronach internetowych często prezentowane są olbrzymie ilości informacji. Aby wyświetlenie ich spisu nie przeciążało łącza, listę wyników dzieli się na strony (inaczej: porcjuje) tak, że naraz wyświetla się jedynie niewielka jej część, a do reszty możemy się dostać poprzez odpowiednie linki nawigacyjne. Oczywiste jest, że wybieranie tego małego kawałka danych powinno zachodzić po stronie bazy danych, a nie PHP. Tak jest w istocie, dzięki klauzuli LIMIT. Pozwala nam ona na zażądanie jedynie określonego kawałka rekordów pasujących do podanego wyrażenia. Pobiera ona dwie informacje: numer (nie mylić z ID!) rekordu w wynikach, od którego należy zacząć pobieranie oraz interesującą nas ilość rekordów.

Istnieje kilka składni tego polecenia. Pokażemy je na przykładach.

SELECT id, nazwa FROM produkty LIMIT 3;
+----+-----------------------+
| id | nazwa                 |
+----+-----------------------+
|  1 | Dlugopisy niebieskie  |
|  2 | Dlugopisy czerwone    |
|  3 | Zszywacze             |
+----+-----------------------+
3 rows in set (0.02 sec)

LIMIT 3 spowodowało, że zostały pokazane pierwsze trzy rekordy, począwszy od pierwszego. Aby zmienić punkt rozpoczęcia, ilość rekordów poprzedzamy "numerem startowym":

SELECT id, nazwa FROM produkty LIMIT 2, 3;
+----+-------------------------+
| id | nazwa                   |
+----+-------------------------+
|  3 | Zszywacze               |
|  4 | Karteczki samoprzylepne |
|  5 | Strugaczki              |
+----+-------------------------+
3 rows in set (0.00 sec)

Tym razem wyświetliła się nam dalsza część zbioru wyników. Przypominamy, że rasowi informatycy zaczynają liczenie od zera i tak samo jest w przypadku numerów startowych. Dlatego "2" w przykładzie oznacza w rzeczywistości rozpoczęcie od rekordu trzeciego. W poprzednim przykładzie start od pierwszego rekordu moglibyśmy zapisać jako LIMIT 0, 3.

LIMIT nie jest częścią standardu ANSI SQL, dlatego też inne systemy baz danych mogą używać innej składni lub nawet innych sposobów do wykonywania porcjowania danych. Aby zapewnić pewną kompatybilność, MySQL udostępnia niektóre z nich jako alternatywę. Oto powyższy przykład zapisany z wykorzystaniem składni bazy PostgreSQL, który działa także i tu:

SELECT id, nazwa FROM produkty LIMIT 3 OFFSET 2;
+----+-------------------------+
| id | nazwa                   |
+----+-------------------------+
|  3 | Zszywacze               |
|  4 | Karteczki samoprzylepne |
|  5 | Strugaczki              |
+----+-------------------------+
3 rows in set (0.00 sec)

Dlatego jeśli planujesz tworzenie aplikacji pod oba te systemy naraz, pamiętaj o różnicach w implementacji języka SQL między nimi. Dla większej ilości baz danych może być konieczne zupełne zrezygnowanie z klauzuli LIMIT na rzecz odpowiednio konstruowanych warunków WHERE oraz dodatkowych pól w tabelach.

[edytuj] Funkcje grupujące

Język SQL umożliwia stosowanie funkcji do częściowej obróbki danych po stronie serwera. Specyficzną grupą funkcji są tzw. funkcje grupujące. W przeciwieństwie do reszty, operują one na zbiorach rekordów, podając o nich różne istotne informacje. Wiąże się z tym kilka ograniczeń użycia, lecz póki co nie będą nas one dotyczyć, gdyż nie potrafimy pobierać jeszcze danych z kilku tabel naraz. Do tego zagadnienia wrócimy w następnym rozdziale.

Pierwszą funkcją, z jaką się zapoznamy, będzie COUNT(). Podaje ona ilość rekordów, które pasują do warunku.

SELECT COUNT(id) FROM produkty;
+-----------+
| COUNT(id) |
+-----------+
|         7 |
+-----------+
1 row in set (0.00 sec)

Teraz wiemy, że w naszej tabeli jest siedem rekordów. Niektórzy programiści stosują składnię COUNT(*) (pamiętasz, co oznacza gwiazdka w zapytaniach SELECT?), jednak my zdecydowanie odradzamy jej użycie ze względów niższej wydajności.

Zadajmy sobie pytanie, dlaczego COUNT() wymaga podawania konkretnego pola, zamiast np. nazwy tabeli? Wszystko wyjaśni się, kiedy zobaczymy, jak funkcja ta reaguje na pola zezwalające obecność wartości NULL. W naszej tabeli jedynie ilosc zezwala na jej obecność. Wstawmy więc ją do rekordu o ID 5, aby mieć na czym eksperymentować:

UPDATE `produkty` SET `ilosc` = NULL WHERE `id` = 5;

Zobaczmy, co się teraz stanie po wykonaniu funkcji COUNT() na polu ilosc:

SELECT COUNT(ilosc) FROM produkty;
+--------------+
| COUNT(ilosc) |
+--------------+
|            6 |
+--------------+
1 row in set (0.00 sec)

Niespodzianka, zwróciło nam informację o sześciu rekordach, chociaż żadnego nie kasowaliśmy. Spokojnie, wszystko jest w porządku. COUNT() celowo opuszcza wartości NULL, gdyż tak wynika ich definicji. Po co liczyć coś, czego na dobrą sprawę nie ma? Zauważmy, jak mądrze postąpiliśmy, wprowadzając NULL do naszej bazy. Teoretycznie przy braku towaru w magazynie można by ustawiać rekordom wartości 0, lecz wtedy do pobrania gatunków produktów będących jeszcze na stanie musimy zastosować klauzulę WHERE:

SELECT COUNT(id) FROM produkty WHERE ilosc > 0;

Jeżeli zamiast 0 wprowadzimy wartości NULL, ulegnie ono skróceniu:

SELECT COUNT(ilosc) FROM produkty;

Nie tylko ta funkcja grupująca reaguje na wartość NULL. Udowodnimy to na przykładzie liczenia średniej ilości towaru w magazynie. Służy do tego funkcja grupująca AVG(). Hipoteza jest następująca: jeśli funkcja ta jest wrażliwa na obecność NULL, powinna dawać różne wyniki w zależności od tego, czy w rekordzie towaru niewystępującego w magazynie oznaczymy ilość przez NULL, czy przez 0. Sprawdźmy to. Oto ciąg wykonywanych przez nas operacji:

mysql> UPDATE `produkty` SET `ilosc` = NULL WHERE `id` = 5;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT AVG(ilosc) FROM produkty;
+------------+
| AVG(ilosc) |
+------------+
|    90.8333 |
+------------+
1 row in set (0.00 sec)

mysql> UPDATE `produkty` SET `ilosc` = 0 WHERE `id` = 5;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT AVG(ilosc) FROM produkty;
+------------+
| AVG(ilosc) |
+------------+
|    77.8571 |
+------------+
1 row in set (0.00 sec)

Oto opis poczynionych kroków:

  1. Ustawiamy w rekordzie 5 pole ilosc na NULL.
  2. Obliczamy średnią ilość towarów w magazynie. Wynik: 90,8(3)
  3. Ustawiamy w rekordzie 5 pole ilosc na 0.
  4. Obliczamy średnią ilość towaru w magazynie. Wynik: 77,86

Jak widać, AVG() dało nam różne wyniki w zależności od tego, co mieliśmy w polu ilosc, potwierdzając tym samym naszą hipotezę. Zjawisko to bardzo łatwo wytłumaczyć. Wartość 0 traktowana jest jak normalna ilość. Zgodnie ze wzorem na średnią arytmetyczną, sumujemy sześć wartości, lecz dzielimy już przez siedem, z uwzględnieniem naszego zera. Wartość NULL wyraźnie mówi bazie danych MySQL: nie licz mnie, ja nie istnieję. Bądźmy świadomi tych różnic w działaniu, gdyż tyczą się one także pozostałych funkcji grupujących.

Ostatnimi funkcjami grupującymi, które poznamy, będą MAX(), MIN() oraz SUM(). Zwracają one kolejno: największą wartość użytą w danym polu, najmniejszą oraz sumę wszystkich wartości.

[edytuj] Ciekawe sztuczki

Na sam koniec pragniemy pokazać pewną ciekawą sztuczkę, która ukaże potęgę języka SQL i być może zachęci wielu z Was do dalszego pogłębiania swej wiedzy o systemach bazodanowych.

Sytuacja prezentuje się następująco: mamy tabelę, w niej pięć pól mogących przyjmować wartości 1 lub 0. Czy da się pobrać rekordy posortowane według tego, ile pól zostało ustawionych na 1? Odpowiedź brzmi: tak.

Zaczynamy od utworzenia tabeli i wypełnienia jej danymi:

CREATE TABLE `stany` (
`id` SMALLINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`stan1` TINYINT(1) NOT NULL DEFAULT '0',
`stan2` TINYINT(1) NOT NULL DEFAULT '0',
`stan3` TINYINT(1) NOT NULL DEFAULT '0',
`stan4` TINYINT(1) NOT NULL DEFAULT '0',
`stan5` TINYINT(1) NOT NULL DEFAULT '0'
) ENGINE = MYISAM;

Następnie wypełniamy ją testowymi danymi:

INSERT INTO stany (stan1,stan2,stan3,stan4,stan5) VALUES
(1,0,1,1,0),
(0,0,1,1,0),
(0,1,1,1,1),
(1,0,0,0,1),
(0,0,0,0,0),
(0,0,1,0,0),
(1,1,1,1,1),
(0,1,1,0,1);

Możemy już zacząć zabawę:

SELECT id FROM stany ORDER BY (stan1+stan2+stan3+stan4+stan5) DESC;
+----+
| id |
+----+
|  7 |
|  3 |
|  1 |
|  8 |
|  2 |
|  4 |
|  6 |
|  5 |
+----+
8 rows in set (0.00 sec)

Zadziwiające? Nie do końca. Jeżeli dokładnie czytałeś rozdział o klauzuli ORDER BY, być może zauważyłeś, że nigdzie nie ma tam wzmianki o konieczności wymieniania pól. Jest wręcz przeciwnie - czarno na białym było tam napisane "lista wyrażeń".

Aby uczynić nasz wynik bardziej przyjaznym, spróbujmy wyświetlić obok ID sumę "włączonych" pól:

mysql> SELECT id, (stan1+stan2+stan3+stan4+stan5) AS `suma` FROM stany ORDER BY
`suma` DESC;
+----+------+
| id | suma |
+----+------+
|  7 |    5 |
|  3 |    4 |
|  1 |    3 |
|  8 |    3 |
|  2 |    2 |
|  4 |    2 |
|  6 |    1 |
|  5 |    0 |
+----+------+
8 rows in set (0.00 sec)

Czyżby kolejne zaskoczenie? Wymieniając na liście danych do zwrócenia sumę, oznaczyliśmy ją etykietą "suma". Jak się okazuje, całej operacji sumowania nie musimy później powtarzać w klauzuli ORDER BY, ani żadnej innej. Wystarczy, że odwołamy się do utworzonej wcześniej etykietki.

Opisem języka SQL możnaby zapełnić całą książkę. To, co podaliśmy w tym podręczniku, jest jedynie wstępem do tematu, który ma nam wystarczyć na początek przy programowaniu w PHP. Zanim jednak pokażemy, jak wykorzystać potęgę baz danych w tym języku, czeka nas jeszcze jeden rozdział poświęcony relacjom oraz indeksom.

Poprzedni rozdział: Pobieranie rekordów Spis treści Następny rozdział: Wstęp do programowania obiektowego

[edytuj] Relacje i indeksy

Ten rozdział jest już ostatnim na naszej drodze poznawania bazy danych MySQL. Po zapoznaniu się z relacjami oraz istotą działania indeksów, powrócimy do PHP, by nauczyć się wykorzystywać tę moc w naszych skryptach.

[edytuj] Indeksy

Podczas wykonywania polecenia SELECT serwer musi wykonać bardzo dużo operacji: wybieranie danych, sortowanie wyników itd. Ponieważ przeznaczony on jest nie tylko do pracy z tak mikroskopijnymi ilościami rekordów, z jakimi mamy do czynienienia (MySQL pracuje stabilnie nawet przy dziesiątkach milionów rekordów w tabeli), bardzo ważną rolę odgrywa tu optymalizacja wszelkiego rodzaju wyszukiwania oraz sortowania. Popatrzmy: rekordy ułożone są w tabeli w takiej kolejności, w jakiej zostały dodane. Oznacza to, że jeżeli próbujemy wykonać w naszej stworzonej wcześniej bazie wyszukiwanie względem ceny, MySQL musi za każdym razem na nowo przetrząsać od początku do końca wszystko, co tam się znajduje. Kiedy produktów będzie parę tysięcy, może to trwać znacznie dłużej, niż teraz. Tutaj do akcji wkraczają indeksy. Indeks nałożony na pole A jest kopią zawartości tego pola, tyle że posortowaną i odpowiednio ułożoną. Podczas robienia wszelkiego rodzaju poszukiwań względem znajdujących się w nim wartości, MySQL może teraz gwałtownie przyspieszyć. Oto przykład, co mogą nam dać posortowane rekordy: mamy tysiąc wartości posortowanych rosnąco (są one wszystkie większe od zera, górna granica nieustalona). Powiedzmy, że chcemy wiedzieć, jakie rekordy mają wartości z zakresu od 700 do 900. Jako że są one posortowane, MySQL może od razu strzelić sobie w środek tego zbioru i sprawdzić:

  1. Jeżeli wartość środkowego elementu jest mniejsza od podanego zakresu, przeszukujemy tylko późniejsze rekordy.
  2. Jeżeli wartość jest większa od podanego zakresu - tylko wcześniejsze.
  3. Jeżeli mieści się w podanym zakresie - poruszamy się jednocześnie w obu kierunkach, dopóki nie wypadniemy poza zakres.

Dzięki posortowaniu, MySQL odrzuca na wejściu całe tony rekordów, które po prostu nie pasują do kryteriów z samej definicji. Także sortowanie jest prostsze - wystarczy przejechać się po indeksie; nawet nie trzeba żadnej funkcji sortującej uruchamiać. Ceną za takie udogodnienia jest zwiększenie rozmiarów bazy, gdyż sam indeks też potrzebuje trochę miejsca. Dlatego powinniśmy się zastanowić, na których polach indeks nam będzie potrzebny, a na których nie. Ba! Po ich utworzeniu będziemy się nawet mogli przekonać, czy MySQL je wykorzystuje. Wystarczy zapytanie SELECT poprzedzić słowem EXPLAIN, a otrzymamy pełną informację diagnostyczną o jego wykonywaniu. Dodajmy indeks do istniejącej już tabeli:

ALTER TABLE `produkty` ADD INDEX (`cena`);

Zapytanie ALTER służy do modyfikowania struktury już istniejących tabel, lecz nie będziemy poświęcać mu zbyt wiele czasu. Kiedy poznamy pakiet phpMyAdmin służący do zarządzania bazami danych, będzie on wykonywać takie operacje za nas automatycznie. Na razie wystarczy nam wiedzieć, że w ten sposób stworzyliśmy indeks dla pola cena w tabeli produkty. MySQL powinien teraz korzystać z niego, kiedy np. będziemy chcieli sprawdzać przedziały cenowe:

SELECT id, nazwa FROM produkty WHERE cena BETWEEN 2 AND 10;
+----+-------------------------+
| id | nazwa                   |
+----+-------------------------+
|  1 | Dlugopisy niebieskie    |
|  2 | Dlugopisy czerwone      |
|  4 | Karteczki samoprzylepne |
|  3 | Zszywacze               |
+----+-------------------------+
4 rows in set (0.00 sec)

A oto informacje diagnostyczne o zapytaniu:

EXPLAIN SELECT id, nazwa FROM produkty WHERE cena BETWEEN 2 AND 10;
+----+-------------+----------+-------+---------------+------+---------+------+------+-------------+
| id | select_type | table    | type  | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+----------+-------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | produkty | range | cena          | cena | 4       | NULL |    3 | Using where |
+----+-------------+----------+-------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

Możemy stąd dowiedzieć się m.in., jakie klucze MySQL mógł wykorzystać (kolumna "possible_keys"), a jakich faktycznie użył (kolumna "key").

Indeksy można tworzyć także przy tworzeniu tabeli. Tak wyglądałoby polecenie utworzenia tabeli produkty, gdybyśmy od razu chcieli wprowadzić tutaj jakiś indeks:

CREATE TABLE `produkty` (
  `id` int(11) NOT NULL auto_increment,
  `nazwa` varchar(60) NOT NULL,
  `opis` text NOT NULL,
  `ilosc` smallint(6) default '0',
  `cena` float NOT NULL,
  `jakosc` tinyint(4) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `cena` (`cena`)
) ENGINE=MyISAM;

Piszemy po prostu słowo key, po nim podajemy nazwę indeksu, a w nawiasie wymieniamy listę należących do niego pól (indeks może składać się z więcej, niż jednego pola, podobnie też w tabeli może być kilka indeksów).

[edytuj] Relacje jeden do wielu

Dotychczas każda tworzona tabela funkcjonowała niezależnie od innych, lecz takie podejście nie wykorzystuje nawet połowy potęgi baz danych. Prawdziwa siła tkwi w tym, że między tabelami mogą istnieć powiązania, czyli relacje, odzwierciedlające różne zależności między danymi. Pokażemy to na przykładzie internetowej biblioteki. Mamy zbiór kategorii książek zawarty w tabeli kategorie. Przechowuje on informacje o nazwie kategorii oraz ilości dostępnych tam pozycji. W prawdziwej bazie danych biblioteki oczywiście dodalibyśmy jeszcze tutaj różne ozdabiacze w stylu opisu kategorii, możliwości wyboru ikonki, różnych dodatkowych informacji statystycznych, lecz na razie nie w tym rzecz. Oprócz tego istnieje druga tabela, ksiazki z polami nazwa, wydawnictwo, cena itp. Jednak posiada ona coś jeszcze: pole kategoria_id, czyli ID kategorii, do której dana książka jest przypisana. W ten sposób utworzyliśmy relację pomiędzy książkami a kategoriami, a ze względu na rodzaj zależności będziemy ją nazywać relacją jeden do wielu: do jednej kategorii możemy przypisać wiele książek, ale do jednej książki może być przypisana tylko jedna kategoria. Jest to najczęściej spotykany typ relacji.

Przejdźmy do utworzenia tabel:

CREATE TABLE `kategorie` (
  `id` mediumint(9) NOT NULL auto_increment,
  `nazwa` varchar(40) NOT NULL,
  `il_ksiazek` mediumint(9) default NULL,
  PRIMARY KEY  (`id`),
  KEY `il_ksiazek` (`il_ksiazek`)
) ENGINE=InnoDB;

INSERT INTO `kategorie` VALUES (1, 'Literatura polska', 4);
INSERT INTO `kategorie` VALUES (2, 'Literatura zagraniczna', 2);

CREATE TABLE `ksiazki` (
  `id` int(11) NOT NULL auto_increment,
  `nazwa` varchar(100) NOT NULL,
  `wydawnictwo` varchar(50) NOT NULL,
  `cena` float NOT NULL default '0',
  `kategoria_id` mediumint(9) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `kategoria_id` (`kategoria_id`)
) ENGINE=InnoDB;

INSERT INTO `ksiazki` VALUES (1, 'Hamlet', 'AAA', 6.5, 2);
INSERT INTO `ksiazki` VALUES (2, 'Makbet', 'AAA', 6.8, 2);
INSERT INTO `ksiazki` VALUES (3, 'Potop', 'BBB', 18.4, 1);
INSERT INTO `ksiazki` VALUES (4, 'Quo vadis', 'BBB', 17.99, 1);
INSERT INTO `ksiazki` VALUES (5, 'Pan Tadeusz', 'CCC', 13.78, 1);
INSERT INTO `ksiazki` VALUES (6, 'Nad Niemnem', 'CCC', 15.45, 1);

Zwróćmy uwagę na kilka rzeczy:

  1. W tabeli ksiazki dla pola kategoria_id jest utworzony indeks. Nakładanie indeksów na pola relacji jest bardzo dobrą praktyką i tutaj zysk wydajności związany z ich użyciem będzie najlepiej widoczny.
  2. Przy każdym rekordzie w tabeli ksiazki pole kategoria_id przechowuje numeryczny ID rekordu kategorii, do którego dana książka jest przypisana.

Naszą przygodę z relacjami zaczniemy od prostego przykładu: chcemy dowiedzieć się, jaka jest nazwa kategorii, do której przypisana jest książka o ID 3:

SELECT kategorie.nazwa FROM ksiazki, kategorie WHERE kategorie.id = ksiazki.kategoria_id 
AND ksiazki.id = 3;
+-------------------+
| nazwa             |
+-------------------+
| Literatura polska |
+-------------------+
1 row in set (0.02 sec)

W klauzuli FROM wymieniamy aż dwie tabele - w takim wypadku nazwa każdego pola musi być poprzedzona kropką i nazwą stosownej tabeli (dodajmy, że wtedy nie możemy brać takich nazw w odwrócone apostrofy!). Istotą całego zapytania jest ten fragment: kategorie.id = ksiazki.kategoria_id - to właśnie on wiąże ze sobą kategorię z książką.

Powyższe zapytanie można nieco uprościć, stosując aliasy. Alias tworzy się w klauzuli FROM i jest to nic innego, jak skrócona nazwa tabeli, aby zapytanie było krótsze:

SELECT ka.nazwa FROM ksiazki ks, kategorie ka WHERE ka.id = ks.kategoria_id AND ks.id = 3;
+-------------------+
| nazwa             |
+-------------------+
| Literatura polska |
+-------------------+
1 row in set (0.02 sec)

Teraz zamiast pełnej nazwy ksiazki możemy pisać ks, a zamiast kategorie - ka. Aliasy są bardzo przydatne w bardzo dużych zapytaniach, zajmujących niejednokrotnie wiele linijek. Jednak niosą one w sobie coś więcej, niż tylko poprawiają estetykę. Zagłębmy się trochę bardziej w filozofię zadeklarowania chęci użycia danej tabeli w zapytaniu. Zapis FROM ksiazki mówi, że w danym momencie przetwarzania zapytania możemy pracować maksymalnie na jednym rekordzie z podanej tabeli, do którego odnoszą się warunki podane w WHERE. Ale są sytuacje, kiedy naraz musimy pobrać dane z dwóch rekordów w jednej tabeli - aliasy są w tym wypadku konieczne, aby odróżnić jeden rekord od drugiego: FROM ksiazki ks1, ksiazki ks2. Naturalnie może się zdarzyć, że oba aliasy wskażą nam ten sam rekord, ale równie dobrze mogą być one różne, w przeciwieństwie do poprzedniej sytuacji:

SELECT ks1.nazwa AS `ks1_nazwa`, ks2.nazwa AS `ks2_nazwa` FROM ksiazki ks1, ksiazki ks2 
WHERE ks1.id = 3 AND ks2.id = 4;
+-----------+-----------+
| ks1_nazwa | ks2_nazwa |
+-----------+-----------+
| Potop     | Quo vadis |
+-----------+-----------+
1 row in set (0.00 sec)

Widać teraz bardzo wyraźnie, że zapisy ksiazki ks1 oraz ksiazki ks2 są od siebie niezależne, chyba że powiążemy je relacją (tak, możliwe jest tworzenie relacji między rekordami tej samej tabeli!). Na koniec najbardziej wymowny przykład korzystania z relacji jeden do wielu. Radzimy przyjrzeć mu się uważnie, ponieważ podobne zapytania pojawiają się naprawdę często. A mowa jest o pobraniu listy książek wraz z nazwą kategorii, do której są przypisane.

SELECT ks.id, ks.nazwa, ks.wydawnictwo, ks.cena, ka.nazwa AS `kat_nazwa`
FROM ksiazki ks, kategorie ka WHERE ka.id = ks.kategoria_id ORDER BY ks.cena;
+----+-------------+-------------+-------+------------------------+
| id | nazwa       | wydawnictwo | cena  | kat_nazwa              |
+----+-------------+-------------+-------+------------------------+
|  1 | Hamlet      | AAA         |   6.5 | Literatura zagraniczna |
|  2 | Makbet      | AAA         |   6.8 | Literatura zagraniczna |
|  5 | Pan Tadeusz | CCC         | 13.78 | Literatura polska      |
|  6 | Nad Niemnem | CCC         | 15.45 | Literatura polska      |
|  4 | Quo vadis   | BBB         | 17.99 | Literatura polska      |
|  3 | Potop       | BBB         |  18.4 | Literatura polska      |
+----+-------------+-------------+-------+------------------------+
6 rows in set (0.00 sec)

[edytuj] Relacje wiele do wielu

Kolejny typ relacji, którym się zajmiemy, nosi nazwę wiele-do-wielu. Jest on dokładnie tym, co mówi nazwa. Patrząc na naszą księgarnię, możemy nim przypisać autorów do książek, co jest zgodne z intuicją: książka mogła być napisana przez wielu autorów, a jednocześnie każdy autor mógł napisać wiele książek.

Zacznijmy od stworzenia tabeli z autorami:

CREATE TABLE `autorzy` (
`id` SMALLINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`imie` VARCHAR(30) NOT NULL,
`nazwisko` VARCHAR(40) NOT NULL 
) ENGINE = InnoDB;

INSERT INTO `autorzy` (`imie` , `nazwisko`) VALUES
('William', 'Shakespeare'),
('Henryk', 'Sienkiewicz'),
('Adam', 'Mickiewicz'),
('Eliza', 'Orzeszkowa'),
('Jan', 'Kowalski');

Zauważmy, że w relacji wiele-do-wielu nasze tabele nie zawierają pól-kluczy, które mogłyby je spinać. Bynajmniej nie znikają one, a wręcz przeciwnie - istnieją, tyle że w trzeciej tabeli pomocniczej:

CREATE TABLE `autorzy_to_ksiazki` (
`ksiazka_id` INT NOT NULL,
`autor_id` INT NOT NULL,
`udzial` VARCHAR(16) NOT NULL,
INDEX (`ksiazka_id`, `autor_id`) 
) ENGINE = InnoDB;

INSERT INTO `autorzy_to_ksiazki` VALUES (1, 1, 'autor');
INSERT INTO `autorzy_to_ksiazki` VALUES (1, 5, 'tłumacz');
INSERT INTO `autorzy_to_ksiazki` VALUES (2, 1, 'autor');
INSERT INTO `autorzy_to_ksiazki` VALUES (2, 5, 'tłumacz');
INSERT INTO `autorzy_to_ksiazki` VALUES (3, 2, 'autor');
INSERT INTO `autorzy_to_ksiazki` VALUES (4, 2, 'autor');
INSERT INTO `autorzy_to_ksiazki` VALUES (5, 3, 'autor');
INSERT INTO `autorzy_to_ksiazki` VALUES (6, 4, 'autor');

Relacja wiele-do-wielu może przenosić dodatkowe informacje w tabeli pomocniczej, np. w tym przypadku mówi nam także, czy osoba przypisana do danej książki jest autorem, czy tłumaczem. Teraz, kiedy tabele są już gotowe, możemy pokazać parę przykładów obrazujących korzystanie z tej relacji.

Zaczniemy od prostego wybrania wszystkich autorów "Hamleta":

SELECT a.imie, a.nazwisko, ak.udzial FROM autorzy a, autorzy_to_ksiazki ak 
WHERE a.id=ak.autor_id AND ak.ksiazka_id=1;
+---------+-------------+----------+
| imie    | nazwisko    | udzial   |
+---------+-------------+----------+
| William | Shakespeare | autor    |
| Jan     | Kowalski    | tlumacz  |
+---------+-------------+----------+
2 rows in set (0.00 sec)

Autora z książką łączy nam ten warunek: a.id=ak.autor_id AND ak.ksiazka_id=1. Na liście pól pobieramy imię i nazwisko z tabeli autorzy z dołączoną informacją o udziale przy opracowywaniu akurat tej pozycji: ak.udzial. Bierzemy ją z tabeli pomocniczej.

W podobny sposób można dowiedzieć się, przy jakich książkach pracował podany autor (np. Jan Kowalski).

SELECT k.nazwa, ak.udzial FROM ksiazki k, autorzy_to_ksiazki ak WHERE k.id=ak.ksiazka_id 
AND ak.autor_id=5;
+--------+----------+
| nazwa  | udzial   |
+--------+----------+
| Hamlet | tlumacz  |
| Makbet | tlumacz  |
+--------+----------+
2 rows in set (0.00 sec)

Zapytanie to działa identycznie, jak poprzednie. Podstawiliśmy do niego jedynie inną tabelę. Teraz może coś bardziej zaawansowanego. Dowiedzmy się, kto pracował dla nas jako autor i przy jakich książkach:

SELECT a.imie, a.nazwisko, k.nazwa FROM autorzy a, ksiazki k, autorzy_to_ksiazki ak 
WHERE a.id = ak.autor_id AND k.id = ak.ksiazka_id AND ak.udzial='autor';
+---------+-------------+-------------+
| imie    | nazwisko    | nazwa       |
+---------+-------------+-------------+
| William | Shakespeare | Hamlet      |
| William | Shakespeare | Makbet      |
| Henryk  | Sienkiewicz | Potop       |
| Henryk  | Sienkiewicz | Quo vadis   |
| Adam    | Mickiewicz  | Pan Tadeusz |
| Eliza   | Orzeszkowa  | Nad Niemnem |
+---------+-------------+-------------+
6 rows in set (0.00 sec)

Relacje wiele-do-wielu pojawiają się w bardziej złożonych bazach danych, które muszą odzwierciedlać dużą liczbę zależności. Są bardzo wszechstronnym narzędziem i ich znajomość jest wręcz niezbędna.

[edytuj] Optymalizacja

Im większe zapytanie z większą ilością skomplikowanych instrukcji, tym wolniej jest wykonywane. Dlatego dobra struktura bazy danych nie tylko musi być elastyczna, ale też zoptymalizowana w taki sposób, aby jak najwięcej danych dało się pobrać prostymi zapytaniami, korzystającymi z elementarnych funkcji. Brak tej cechy jest mankamentem baz projektowanych nie tylko przez początkujących, ale i wielu zaawansowanych programistów, co negatywnie rzutuje na wydajność ich aplikacji.

Pamiętajmy, że wolna przestrzeń dyskowa jest aktualnie najtańsza w historii ludzkości, a dane liczbowe zajmują śmiesznie małą jej ilość. Podstawową zasadą jest właśnie buforowanie tego, co da się buforować, a pokażemy to na przykładzie systemu newsów z możliwością komentowania. W systemie takim mamy dwie tabele: newsy oraz komentarze połączone relacją jeden-do-wielu. Zazwyczaj przy wyświetlaniu newsów pragniemy podać też, ile jest w nich komentarzy. Pobranie takich informacji jednym zapytaniem wymaga sprytnego zastosowania funkcji grupującej COUNT(), której użycie w relacjach jest obwarowane paroma ograniczeniami. Jednak to nie wszystko. Kiedy tabele zepniemy "sztywno" w warunku WHERE, na liście pokażą się nam tylko te newsy, w których ktoś już dodał jakiś komentarz! Dlatego musimy zastosować specjalną klauzulę służącą do warunkowego spinania tabel: LEFT JOIN. Jest ona użyteczna, ale dość powolna w działaniu. Ostatecznie otrzymujemy coś takiego:

SELECT n.id, n.tytul, COUNT(k.id) AS `il_komentarzy` FROM newsy n LEFT JOIN komentarze k 
ON k.news_id=n.id GROUP BY k.news_id ORDER BY n.id;

Zapytanie to było wykorzystywane praktycznie na stronie WWW autora podręcznika. Działa, lecz przy większej ilości rekordów zaczynają się problemy w stylu zrywania połączenia czy nawet blokowania serwera DB. Naprawdę, nie życzymy nikomu, aby znalazł się w podobnej sytuacji, zwłaszcza że problemowi można zaradzić w bardzo prosty sposób. Dlaczego bowiem zliczać ilość komentarzy przy każdym wyświetlaniu strony? Przecież liczba ta nie zmienia się tak znowu często. Dorzućmy do tabeli newsy pole il_komentarzy. Przy dodawaniu newsa inicjujemy je wartością 0. Przy dodawaniu komentarza zwiększamy jego wartość o 1, przy usuwaniu - zmniejszamy. W ten sposób uzyskaliśmy takie zapytanie końcowe:

SELECT id, tytul, il_komentarzy FROM newsy ORDER BY id;

Wykorzystuje ono elementarną składnię, przez co może wytrzymać znacznie większe obciążenie. Tego typu optymalizacja spotykana jest w wielu zaawansowanych aplikacjach, np. systemie forów dyskusyjnych Invision Power Board, który w ten sposób zapamiętuje sobie ilości postów oraz tematów.

O kolejnym rodzaju optymalizacji już wspominaliśmy - jest to prawidłowe użycie indeksów. Zakładamy je wszędzie tam, gdzie dane będą sortowane lub wybierane według różnych kryteriów. Indeksy zawierają informacje ułożone w pewnym porządku, dlatego po ich wstawieniu w takie miejsca MySQL wykorzysta je do przyspieszenia całego procesu.

Optymalizację można przeprowadzać także z poziomu języka programowania, za pomocą którego komunikujemy się z bazą. Jeżeli nasze dane zmieniają się niezbyt często, nie ma potrzeby pobierania ich na nowo za każdym razem. Wystarczy raz pobrane zapisać gdzieś na serwerze w pliku i przy dalszych wejściach czytać właśnie z niego. Jest to jednak optymalizacja typowo programowa, dlatego zajmiemy się nią później.

[edytuj] Zakończenie

Na tym kończymy naszą przygodę z poznawaniem języka SQL. Jeżeli pragniesz poznać go lepiej, bardzo przydatna okaże się z pewnością dokumentacja serwera MySQL. Poświęć trochę czasu na zapoznanie się z jej rozbudowaną strukturą, gdyż jest to istna kopalnia cennych informacji. Przydatne może być także analizowanie cudzych skryptów oraz własne poszukiwania z użyciem Google. Każdy sposób jest dobry, a znajomości SQL-a nigdy za wiele.

Poprzedni rozdział: Relacje i indeksy Spis treści Następny rozdział: Biblioteka PDO

[edytuj] Wstęp do programowania obiektowego

Zanim nawiążemy nasze pierwsze połączenie z bazą z poziomu PHP, musimy zostać krótko zaznajomieni z podstawami programowania obiektowego, niezbędnego, aby zacząć naszą zabawę z biblioteką PHP Data Objects.

[edytuj] Czym jest OOP?

Skrót OOP pochodzi od terminu Object-Oriented Programming, czyli po polsku "programowanie zorientowane obiektowo". Określa się nim pewną strukturę i organizację kodu źródłowego. Dotychczas wszystkie nasze przykłady bazowały na programowaniu strukturalnym, opartym o funkcje i zmienne globalne. W programowaniu obiektowym programista operuje na klasach i obiektach. Programowanie obiektowe zyskało bardzo dużą popularność dzięki temu, że znacznie lepiej przystaje do rzeczywistości, jest zatem po prostu intuicyjne. Wytłumaczymy to na przykładzie.

Monitor, na którym najprawdopodobniej czytasz ten tekst, jest pewnym obiektem pewnej klasy ("monitor"). Klasa ta definiuje zachowanie oraz możliwe właściwości wszystkich monitorów na świecie (lub danego producenta, jeśli wolisz), natomiast sam obiekt nadaje tym właściwościom odpowiednie wartości. Spójrzmy na to w ten sposób: ty masz monitor kremowy z odświeżaniem 75 Hz, twój kolega ma monitor w ciemnej obudowie z odświeżaniem 85 Hz. Jednak mimo odmiennych właściwości, nadal są to obiekty tego samego rodzaju: monitor, zatem zachowują się identycznie i mogą być używane w identycznych celach. Niemniej nie znaczy to, że wszystkie NARAZ robią to samo. Klasa "monitor" po prostu opisuje ich zachowanie, a co obiekty robią w danej chwili - zależy tylko od nas, programistów. W programowaniu obiektowym identyczny tok rozumowania został przeniesiony do języka programowania.

[edytuj] Podstawy OOP

Aby móc tworzyć obiekty, potrzebna nam jest jakaś klasa. Tworzy się ją w PHP bardzo prosto. Piszemy słowo kluczowe class, podajemy nazwę klasy i wewnątrz nawiasów klamrowych definiujemy całą jej strukturę składającą się z deklaracji pól (czyli opisu, jakie właściwości mogą posiadać jej obiekty) oraz metod będących w zasadzie funkcjami wewnętrznymi klasy, wykonujących jakieś operacje:

<?php

	class osoba
	{
		public $imie; // 1
		public $nazwisko;
		
		public function ustawPersonalia($imie, $nazwisko) // 2
		{
			$this -> imie = $imie; // 3
			$this -> nazwisko = $nazwisko;
		} // end ustawPersonalia();
		
		public function personalia()
		{
			return $this -> imie.' '.$this -> nazwisko;		
		} // end personalia();	
	}
	
	$osoba = new osoba; // 4
	$osoba -> ustawPersonalia('Adam', 'Kowalski'); // 5
	
	echo $osoba -> personalia(); // 6
?>

Oto opis poszczególnych części kodu:

  1. Tutaj deklarujemy, że każdy obiekt klasy "osoba" będzie posiadać pola (dalej będziemy posługiwać się właśnie tym terminem, mówiąc o właściwościach) imie oraz nazwisko. Nazwa pola musi zaczynać się od znaku dolara i być poprzedzona słowem kluczowym public (są też inne, ale ich znaczenie omówimy sobie później).
  2. Tutaj tworzymy metodę klasy (zwaną czasem funkcją wewnętrzną), która będzie wykonywać na jej obiektach określone zadanie - w tym konkretnym przypadku będzie ustawiać personalia. Składnia jest praktycznie identyczna, jak podczas tworzenia normalnych funkcji. Nowością jest słowo kluczowe public na samym początku, i choć tym razem można je bezkarnie opuszczać, jego pisanie jest dobrym zwyczajem.
  3. Każdy obiekt jest w PHP reprezentowany przez zmienną. Aby dostać się do jego pól oraz metod, musimy użyć specjalnego "wskaźnika" ->, a potem wywołać to, co nam jest potrzebne. Zwróć uwagę, że nazw pól nie poprzedzamy już teraz znakiem dolara! Pora także wytłumaczyć, co to za zmienna $this. Przecież nigdzie jej nie tworzymy, ale z niej korzystamy. $this jest właściwie pewnym trikiem. Wskazuje zawsze na obiekt, który wywołał daną metodę, pozwalając jej na modyfikację jego właściwości (po to przecież istnieje).
  4. Mając już klasę, możemy przystąpić do tworzenia obiektów. Robi się to w podany tutaj sposób: operator new tworzy nowy obiekt podanej klasy, co pozwala np. przypisać go do określonej zmiennej. W tym wypadku - do zmiennej $osoba.
  5. Tutaj wywołujemy metodę na stworzonym właśnie obiekcie. Pamiętasz, co napisaliśmy o zmiennej specjalnej $this wewnątrz metody? Będzie ona teraz wskazywała właśnie na obiekt $osoba, dzięki czemu zostaną do niego przypisane imię i nazwisko podane jako parametry metody.
  6. Wywołujemy kolejną metodę, która zwraca nam personalia naszej osoby.

Skopiuj kod powyższej klasy do nowego pliku - zajmiemy się jeszcze jednym przykładem na niej opartym:

<?php

	// tu jest kod klasy

	$bazaOsob = array();
	// Tworzymy pierwsza osobe
	$bazaOsob[0] = new osoba;
	$bazaOsob[0] -> ustawPersonalia('Adam', 'Kowalski');

	// Tworzymy druga osobe
	$bazaOsob[1] = new osoba;
	$bazaOsob[1] -> ustawPersonalia('Jan', 'Nowak');
	
	// Tworzymy trzecia osobe
	$bazaOsob[2] = new osoba;
	$bazaOsob[2] -> ustawPersonalia('Zbigniew', 'Bogacki');
	
	// Zobaczmy, jak skonczyla sie nasza zabawa w Boga
	
	foreach($bazaOsob as $osoba)
	{
		echo 'Jestem '.$osoba -> personalia().'<br/>';
	}
?>

W przykładzie tym stworzyliśmy sobie trzy obiekty osoba i zapisaliśmy do tablicy. Puszczając po niej pętlą foreach możemy zapoznać się z personaliami każdej z nich. Zapis $osoba -> personalia() oznacza po prostu podanie personaliów aktualnie wyświetlanej osoby.

Pamiętaj, że personalia możesz przypisać także bardziej łopatologicznie, np. $osoba -> imie = 'Józef';

[edytuj] Obiekty i zmienne

W naszych dotychczasowych rozważaniach zakładaliśmy, że obiekt jest zmienną. I w sumie mamy rację... ale tylko, mówiąc o przedpotopowym PHP 4. W PHP 5 zmienna obiektowa jest jedynie referencją do właściwego obiektu, zatem napisanie $a = $b, wykona tylko kopię samej referencji, a nie całego obiektu. Obiekt przestaje istnieć, jeżeli nie prowadzi do niego już żadna referencja.

Takie założenie ma dużo zalet. Wystarczy tylko wymienić przekazywanie obiektów jako parametry funkcji/metod lub zwracanie ich jako wynik działania. W PHP 4 było to możliwe jedynie po karkołomnych zabawach z operatorem &. W PHP 5 kod jest czysty i schludny. Jeżeli już naprawdę czujemy potrzebę wykonania kopii obiektu, musimy zapoznać się ze słowem kluczowym clone. Jeszcze raz skopiuj kod naszej klasy do naszego trzeciego przykładu:

<?php

	// Tu wstaw kod klasy
	
	$facet1 = new osoba; // 1
	$facet1 -> ustawPersonalia('Adam', 'Kowalski');
	
	echo '$facet1 to '.$facet1 -> personalia().'<br/>';
	echo '<hr/>';
	// Zobaczymy, co sie stanie
	$facet2 = $facet1; // 2
	$facet2 -> imie = 'Jan';
	
	echo '$facet2 to '.$facet2 -> personalia().'<br/>';
	echo '$facet1 to '.$facet1 -> personalia().'<br/>'; // 3
	echo '<hr/>';
	
	// Operator przypisania nie działa? Pora, aby do akcji wkroczyła inżynieria genetyczna
	// Oto pierwsze w historii klonowanie człowieka:
	
	$facet3 = clone $facet1; // 4
	$facet3 -> imie = 'Michał';
	
	echo '$facet3 to '.$facet3 -> personalia().'<br/>'; // 5
	echo '$facet1 to '.$facet1 -> personalia().'<br/>';
	
	// Sukces!
?>

Krótki opis:

  1. Na początek tworzymy sobie jakiegoś faceta i nadajemy mu imię.
  2. Próbujemy w "tradycyjny" sposób wykonać kopię. Dla potwierdzenia "obiektowi" $facet2 zmieniamy imię.
  3. Jednak podczas wyświetlania okazuje się, że imię zmieniło się także w $facet1! Wniosek: $facet2 i $facet1 to ten sam obiekt.
  4. Aby wymusić skopiowanie samego obiektu, a nie tylko referencji, musimy posłużyć się operatorem clone. Znowu dla testu zmieniamy imię w $facet3.
  5. I osiągamy sukces. W $facet1 imię pozostało niezmienione. Teraz obiekt udało się sklonować.

[edytuj] Krótko o wyjątkach

Dla pełni jasności przy omawianiu biblioteki PDO musimy wspomnieć także o wyjątkach, które zawitały do PHP wraz z pojawieniem się wersji 5.0.0. Wyjątek to obiekt wbudowanej klasy Exception tworzony przez nas, kiedy zajdzie jakaś nieprzewidziana sytuacja. Przechwytuje go specjalny blok kodu, który może wtedy odpowiednio na nią zareagować. Dlaczego po prostu nie napisać sobie do tego zadania prostej funkcji lub posłużyć się poleceniem trigger_error()? Te specjalne bloki przechwytujące wyjątki można w sobie zagnieżdżać. Dzięki temu możliwa jest taka sztuczka: wyobraźmy sobie, że cała nasza aplikacja objęta jest potężnym blokiem, który wyłapuje wszystkie wyjątki związane np. z dostępem do katalogów i prezentuje jednolity komunikat błędu. Jednak fragment tej aplikacji nie traktuje ewentualnych problemów z dostępem w kategoriach życia i śmierci, dlatego tworzymy tam nowy blok, który potraktuje problem łagodniej. Jeżeli teraz wyjątek powstanie wewnątrz niego, zajmie się nim lokalna część aplikacji. Jeśli poza nim - nasz uniwersalny wyłapywacz.

Jeżeli nie za bardzo rozumiesz powyższy opis, nie przejmuj się. Wyjątki będą szeroko omawiane dalej. Teraz informacja ta jest nam potrzebna głównie po to, że biblioteka PDO (i nie tylko) za ich pomocą raportuje kłopoty. Oto przykład działania wyjątków:

<?php
	try // 1
	{
		$tekst = @file_get_contents('plik.txt');
		
		if($tekst === false)
		{
			throw new Exception('Nie można otworzyć pliku plik.txt!'); // 2
		}
		
		echo 'Nasz tekst: '.$tekst; // 3
	}
	catch(Exception $wyjatek) // 4
	{
		echo $wyjatek -> getMessage(); // 5
	}
?>

Opis skryptu:

  1. Tak rozpoczynamy blok, w którym chcemy łapać wyjątki.
  2. Słowem kluczowym throw, po którym podajemy obiekt klasy Exception, wysyłamy wyjątek. Zauważ, że po