JustPaste.it

Każdy popełnia błędy - Podstawy PHP

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

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.

Poprawiwszy usterkę i zainicjowawszy zmienną $zmienna jakąś wartością, 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.

 

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 operacji. Wiemy z dokumentacji, że samo filemtime() zwraca nam false, jeśli plik nie istnieje, przeszkadza nam tu "firmowe" ostrzeżenie. Tu też właśnie skorzystamy z @:

<?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 @.

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.

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. 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;
?>

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.

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

 

Treść pochodzi ze strony WikiBooks i jest udostępniana na licencji GNU FDL

 

Autor: WikiBooks