Pułapki programowania w PHP część 1
|Przedstawiam zbiór różnych, wybranych pułapek programowania w języku PHP. Nie wiem czy sama nazwa „pułapki” jest trafiona. Być może lepszym słowem byłyby trudności, absurdy lub nieścisłości. Zdecydujcie sami.
Celem tego wpisu jest pokazanie złych praktyk (złego projektu) samego kodu PHP. Na podstawie tego można wyciągnąć pewne wnioski, które będą pomocne przy tworzenia własnego kodu, oraz pozwolą na lepsze wyczucie języka. Uważam, że język powinien być narzędziem programisty a nie ideologią, dlatego warto znać również jego słabe strony. Zapraszam do czytania:
- Funkcja json_decode zamienia zapisany jako string obiekt JSON na strukturę obsługiwaną przez PHP. Zwraca ona null jeżeli przekazany string jest niepoprawny. Nawet w przypadku gdy przekazany string jest faktycznym (prawidłowym i dozwolonym zapisem JSON) nullem. Czyli, jeżeli zdecydujesz się na jej używanie to po każdym jej wywołaniu należy wywołać json_last_error i sprawdzić czy proces parsowania przebiegł prawidłowo. W innym przypadku nigdy nie będziesz mieć pewności, że proces konwersji przebiegł bez problemu.
- Operator porównania „==”. Jego znaczenie nie jest takie oczywiste, bo „string” == TRUE i „string”==0 ale w drugą stronę uwaga … TRUE != 0. Inne ciekawe przypadki to 123 == „123string” ale oczywiście .. „123” != „123string”. Ten ostatni przykład w skrajnych przypadkach może spowodować, że twój hash hasła nie zawsze będzie bezpieczny (przykład). Kolejne ciekawe przypadki to: „3” == ” 3″, „2.7” == „2.70”, oraz „123” == „0123”. Inaczej już przy 123 != 0123, ponieważ 0123 jest zapisem w notacji ósemkowej.
- Obsługa błędów (choć to jest temat na osobny wpis). W PHP istnieje niespójne podejście do tego co jest dozwolone a co nie. Przykłady błędów które nie powodują przerwania skryptu (zgłaszają jedynie istnienie problemu):
- użycie niezdefiniowanej stałej (notice)
- próba uzyskania dostępu do atrybutu czegoś co nie jest obiektem (notice)
- próba uzyskania dostępu do nie istniejącego atrybutu (warning)
- foreach (7 as $v) (warning)
Z drugiej strony błędy, które powodują przerwanie skryptu:
- próba wywołania niezdefiniowanej funkcji (fatal error)
- próba dostępu do nieistniejącej stałej klasy, np. $obj::name (fatal error)
- brak średnika w ostatnim wyrażeniu w pliku (lub na końcu bloku) (parse error)
- użycie stałej jako nazwy funkcji, zmiennej lub klasy (parse error)
- Niektóre wbudowane funkcje konfliktują z funkcjami zwracającymi referencję. Przykład jest lekko skomplikowany ale zachęcam do przeczytania tego wpisu.
- Wyjątki wyrzucone w metodzie __autoload lub destruktorze powodują błędy fatal error. Błąd ten został dla destruktora poprawiony w wersji 5.3.6. W wersji tej poprawiono również inny ciekawy błąd. Weźmy oto taką konstrukcją:
new SomeClass(someFunction())
Jeżeli someFunction() wyrzuci wyjątek to konstruktor klasy SomeClass nie zostanie wywołany, natomiast jej destruktor tak !
- Inkrementacja jest również ciekawa. Inkrementujcą (++) NULL otrzymamy 1. Dekremetując (–) NULL otrzymamy NULL. Co ciekawe, dekrementując zmienną typu string otrzymamy ten sam string, przy inkrementacji już to tak nie działa.
- Funkcja create_function. Jest to nic innego jak wywołanie eval w odpowiednim opakowaniu. Tworzy ona funkcję z zwyczajną nazwą i inicjuje ją globalnie (w ten sposób nie będzie ona nigdy zebrana przez garbage collector – uwaga na pętle!). Mało tego utworzona funkcja nie wie nic o aktualnym zasięgu zmiennych (current scope).
- Puste linie przed lub pod <?php … ?> zawsze traktowane są jak zwykły tekst i wypuszczane prosto do ciała odpowiedzi (response). W ten sposób powodują najczęściej błędy typu „headers already sent”. Trudno temu zapobiec. Najlepiej jest nie zamykać pliku tagiem „?>” i pilnować pierwszych linii. Widziałem również kiedyś rozwiązanie z ob_start, jednak nie zalecam.
- Funkcja register_tick_function akceptuje jako parametr domknięcie (closure) lub dowolny inny callback . Niestety funkcja odwrotna unregister_tick_function akceptuje tylko string. Swoją drogą sama przydatność tych funkcji jest dla mnie zagadką.
- Argumenty funkcji mogą być określonego typu (type hints). Choć wiele wbudowanych funkcji wymaga zmiennych typu int lub string to sam programista nie może takiego wymagania stworzyć. Rozważmy poniższy przykład:
function test(string $s) {} test("hello world");
Wynikiem uruchomienia takiego kodu będzie (jakkolwiek to brzmi):
Catchable fatal error: Argument 1 passed to test() must be an instance of string, string given, called in ...
Niektóry z tych punktów mogą okazać się oczywiste, inne nie. Niemniej pokazują one jednak jakie trudność czekają na programistę PHP. Są to problemy, które powstały na poziomo samego języka PHP i rozwiązanie ich nie zawsze jest możliwe. W następnym wpisie postaram się przedstawić kolejnego tego typu zagadnienie (tak, to jest tylko mała ich część). PHP jest językiem któremu daleko do ideału … ale pomimo tego radzi sobie na dzisiejszym rynku doskonale.