JustPaste.it

PHP w praktyce

Kolejny artykuł dla początkujących programistów w PHP mający na celu pokazać im, co robić, by skrypty były przejrzystsze i szybsze.

Na początku chciałbym podziękować menicowi za to, że podsunął mi pomysł na napisanie tego artykułu. Mam nadzieję, że pomoże on nie tylko Tobie, ale i innym, którzy dopiero zaczynają, lub niedawno zaczęli swą przygodę z PHP. Wiele wskazówek zaczerpnąłem z serii artykułów "To 21 PHP programming mistakes" opublikowanej przez Sterlinga Hughesa na www.zend.com i tam też odsyłam bardziej dociekliwych.

PHP ma to do siebie, że jest językiem prostym do nauczenia się, przez co wiele osób może go używać. Zaleta ta staje się jednak wadą, gdy do pisania zasiadają początkujący programiści. Przyczyną tego jest po prostu brak doświadczenia i wyczucia tak potrzebnego w programowaniu, lub nabycie złych nawyków na początku nauki. To jednak da się nadrobić i mam nadzieję, że zawarte poniżej porady pomogą wielu osobom.

Zmienne tymczasowe

Zauważyłem, że wielu początkujących z namiętnym upodobaniem stosuje na skalę przemyslową w swoich skryptach zmienne tymczasowe, czyli tworzenie zmiennych na zapisanie wyniku wykonania jakiejś funkcji, i których zawartość potem wrzucamy do kolejnej funkcji itd. Rozwiązanie takie jest bardzo niepraktyczne. Wydłuża niepotrzebnie kod, lecz także spowalnia jego działanie. Oto mały przykład: wysłanie zapytania SQL do bazy:

<?php
...
$sql = "SELECT id, text, author, title".
"FROM articles".
"WHERE author = '$author'";
mysql_query($sql);
...
?>

Widzimy, że użytkownik najpierw tworzy zmienną tymczasowo $sql. Utworzenie zmiennej trochę czasu procesorowi zajmuje, bo wbrew pozorom podczas właśnie takiej głupiej niby operacji przydziału pamięci trzeba wykonać tak dużo rzeczy, że nie wiem. Następnie PHP musi coś do tej zmiennej wpisać. Zapytanie zostało rozbite na trzy teksty połączone operatorem łączenia, czyli kropką. Teraz też trzeba się trochę orobić, bo najpierw trzeba pierwszy tekst wpisać do pamięci, potem przydzielić pamięć na dłuższy tekst, wpisać ten pierwszy i drugi, a potem do tego trzeci. Następnie przechodzi się do wykonania funkcji mysql_query. PHP znów musi wykonać już któreś z kolei kopiowanie tekstu do funkcji i dopiero wtedy wykonywane jest to, co zrobić chcemy. Widzimy, że mamy tu szereg zbędnych czynności, które bez problemu można wyeliminować. Na pierwszy ogień pójdzie zmienna tymczasowa:

<?php
...
mysql_query("SELECT id, text, author, title".
"FROM articles".
"WHERE author = '$author'");
...
?>

Efekt ten sam, a pozbyliśmy się zbędnego przydziału pamięci na kolejną zmienną i wywaliliśmy jedno zbędne kopiowanie. Kolejnym krokiem jest usunięcie dwóch kolejnych zbędnych łączeń tekstu - skoro string może rozciągać się na parę linii, wykorzystajmy tę właściwość:

<?php
...
mysql_query("SELECT id, text, author, title
FROM articles
WHERE author = '$author'"
);
...
?>

Niby takie kosmetyczne zmiany, a życie już staje się szybsze.

Podsumowując: stosuj zmienne tymczasowe jak najrzadziej. Po co deklarować 5 zmiennych, skoro wyniki można przekazywać między funkcjami w taki sposób: funkcja1(funkcja2(funkcja3(funkcja4('tekst tekst tekst')))); . Jeśli jest to zbyt zagmawtwane, nic trudniejszego. Parametry funkcji też mogą być rozciągnięte na kilka linijek.

Nieprawidłowe wprowadzanie zmiennych do funkcji

Niektózy początkujący programiści niezbyt dobrze rozumieją, że każda zmienna ma TYP, czyli może przechowywać informacje określonego rodzaju. Wynika to z tego, że typ ten można płynnie zmienić, co w niektórych przypadkach kończy się po prostu katastrofą. Oto mały przykład niewłaściwego myślenia:

<?php
$fp = fopen('plik.txt', 'r');
while($line = fgets("$fp", 1024)){ // blad
print $line;
}
fclose("$fp"); // blad
?>

Na początku funkcja fopen tworzy zmienną przechowującą uchwyt do pliku, w PHP jest to po prostu zmienna przechowująca zasób. Zmiennej tej wymagają funkcje fgets() i fclose() - potrzebny jest im ten uchwyt, by wiedziały, na czym mają jakieś operacje wykonać :). Ale pojawia się problem. Użytkownik wprowadza zmienną w taki sposób: "$fp". Powoduje to, że PHP konwertuje zasób na... tekst, rezultatem czego jest to, że fgets i fclose dostają zamiast uchwytu jakiś dziwny ciąg w stylu "Resource id #4" :). Usuwając cudzysłowie skrypt zaczyna działać poprawnie.

Echo, print, printf

Umiejętność wyboru rozwiązania, które najlepiej [czyt: najszybciej i najdokładniej] spełni nasze oczekiwania też jest bardzo pożądaną umiejętnością. Zanim zaczniesz coś pisać, zastanów się, czy lepiej zrobić to na bazie, czy na plikach, czy za pom. klas i przestrzeni nazw, czy zwykłych funkcji? Czy wybrane rozwiązanie posiada zbyt duże możliwości jak na nasze potrzeby i przez to działa wolniej, niż prostsze? No właśnie... ostatnie pytanie jest szczególnie ważne, a zaprezentuję je na przykładzie trzech różnych funkcji wysyłających tekst do przeglądarki: echo, print() i printf().

Funkcja printf() służy do formatowania danych przed wysłaniem ich do przeglądarki. Dlatego też przed dokonaniem tego faktu przetrząsa cały tekst w poszukiwaniu najdziwniejszych znaczników. Trochę to zabiera czasu, więc przed jej użyciem zastanówmy się, czy lepiej użyć zwykłego print(). Oto przykład poprawnego użycia printf():

<?php
// Trzy postacie liczby PI
 
printf("Pi to: %.2f\n<br>\n", M_PI);
printf("Pi to także: %.3f\n<br>\n", M_PI);
printf("oraz: %.4f\n<br>\n", M_PI);
?>

Jak widać, użyliśmy tutaj znacznika %.xf do określenia, do ilu miejsc po przecinku ma być wyświetlona liczba PI. Aby dokonać czegoś takiego w normalny sposób, musielibyśmy tutaj użyć paru dodatkowych funkcji. A teraz trochę mniej poprawny przykład:

<?php
$imie = 'Tomasz Jędrzejewski';
$ksywa = 'ZyxwvU';
$wiek = 14;
 
// wyobraz sobie, ze tu jest jeszcze troche kodu :)
 
printf('Nazywam się %s, ksywa %s i mam %d lat.', $imie, $ksywa, $wiek);
?>

W miejsce printf() powinna być w tym przykładzie użyta funkcja print(), ew. echo:

print("Nazywam się $imie, ksywa $ksywa i mam $wiek lat.");

Pamiętaj: nie używaj printf() tam, gdzie zadanie możesz z takim samym skutkiem zrealizować używając funkcji print()/echo.

Jeden 'global', dwa 'globale'...

Aby przenosić zmienne zewnętrzne do wnętrza funkcji (wyjątkiem są predefiniowane tablice $_GET, $_POST itp.), należy użyć instrukcji global:

global $zmienna1, $zmienna2, $zmienna3, $zmienna4;

W zasadzie powinniśmy się starać umieszczać wszystkie potrzebne nam zmienne w jednym wywołaniu global, lecz jeśli trzeba cos dodać warunkowo, możemy oczywiście użyć dwóch:

   global $zmienna1, $zmienna2, $zmienna3;
if($parametr1 == 5){
global $zmienna4, $zmienna5;
}

Problem pojawia się, gdy lista tych zmiennych generowana jest dynamicznie np. na podstawie tablicy asocjacyjnej. Oto przykład pozornie działającego kodu dołączającego zmienne z takiej tablicy:

   foreach($table as $variable){
eval('global $'.$variable.';');
}

ALE... w przykładzie dla każdej zmiennej generujemy nowego globala, wykonujemy go także osobno. Powoduje to spadek wydajności przy dużych tablicach. Możemy to jednak przyspieszyć:

   eval('global $'.implode(', $',$table).';');

Jak widać pozbyliśmy się tu wszystkich zbędnych elementów: pętli, wielokrotnego wywoływania global, wielokrotnego wywoływania eval(). Teraz wszystko wygląda prościej :).

Planowanie to podstawa

Na początku było słowo, a ze słowa powstał kod. I taka jest prawda - bez wcześniejszego dobrego zaplanowania wszystkiego nie uda Ci się żaden bardziej skomplikowany skrypt. Warto poświęcić trochę czasu na opisanie np. systemów autoryzacji, czy rozpisanie wszystkich nazw funkcji. Dlaczego to jest takie ważne? Wiadomo, że na początku plany zmieniają się dość często. Jeśli jesteśmy dopiero na etapie sporządzania projektu, bez problemu możemy wprowadzić do tekstu modyfikacje. Gdybyś natomiast wpierw zabrał się za kodowanie, wynikłoby z tego w takim wypadku wiele nieszczęść.

Specyfikacja projektu powinna zawierać: opis wszystkich podzespołów systemu - zarówno ich działanie, jak i implementację, czyli omówiony zbiór funkcji, klas, czy co tam chcesz; opis działań podjętych po jakiejś akcji użytkownika, np. gdy się zaloguje, to robimy tak a tak, przy każdej zmianie strony tak a nie itp. Ważne jest, by całość była spójna, dlatego po skończeniu jej pisania warto poświęcić trochę czasu na przeczytanie jej od A do Z i wyłapanie wszystkich ewentualnych niezgodności. W trakcie pisania możesz oczywiście przygotowywać przykładowe skrypty testujące różne elementy. Fragmenty z nich wykorzystasz przy konstruowaniu gotowego kodu.

I pamiętaj: gdy zaczniesz pisać, musisz trzymać się tego, co wcześniej opracowałeś. Inaczej nie miałoby to w ogóle sensu. Dlatego tak ważne jest przejrzenie wszystkiego przed przystąpieniem do pracy - późniejsze zmiany mogą mieć fatalne skutki.

Czytanie z plików

Wbrew pozorom sprawa ta jest bardzo ważna - nieumiejętne pobranie danych może spowodować nawet tysiąckrotny spadek wydajności. Opiszę tu pewien przypadek, który wydarzył się jakieś dwa tygodnie temu. Otóż pewna osoba pisała sobie w PHP czytnik plików XML - używała do tego specjalnego rozszerzenia DomXML. Skarżyła się, że w PHP zajmuje jej przetworzenie 12 MB pliku zajmuje 11 minut (!!!), a tymczasem w innym języku skryptowym całość mieści się w 6 sekundach. Różnica kolosalna. Osoba ta myślała, że błąd leży po stronie rozszerzenia, lecz nie zauważył sposobu, w jaki ten plik wczytuje. Wyglądało to tak:

$file = implode("\n", file('file.xml'));

To było całą przyczyną problemu - funkcja FILE najpierw rozdziela plik na poszczególne linie, co wymaga czasu i pamięci. Następnie implode() łączy te linie z powrotem w jeden duży plik, co z kolei wymaga dużo czasu na allokację i pozwalnianie tego wszystkiego. Osobie tej podsunięto taki kod wczytujący:

$f = fopen('file.xml');
$file = fread($f, filesize('file.xml'));
fclose($f);

I zaskoczyło - całość została przetworzona w 3 sekundy.

Ogólnie więc zasada jest taka - jeśli potrzebny ci plik rozdzielony na pojedyncze wiersze - użyj file(). Gdy natomiast wymagasz całego pliku za jednym zamachem, wykorzystaj sposób drugi.

Zakończenie

Na koniec chciałbym podać listę adresów, pod które warto zajrzeć, by poznać PHP w praktyce jeszcze lepiej:

 

Autor: Tomasz "Zyx" Jędrzejewski, www.zyxist.com

 

Źródło: http://webcity.pl/webcity/php_w_praktyce

Licencja: Creative Commons - użycie niekomercyjne - bez utworów zależnych