XIII. Colectorul de reziduuri a venit
06.11.2008
Poate ai auzit de termenul GIGO inainte. Daca nu, afla ca vine de la Garbage In, Garbage Out si reprezinta un fapt elementar ce tine de programare: daca iti „hranesti” programul cu ceva rau, mai mult ca sigur vei primi un rezultat la fel de rau.
6549 afisari 0 Rating (3 voturi) 35 min

Asteptand sa expiri

Poate ai auzit de termenul GIGO inainte. Daca nu, afla ca vine de la Garbage In, Garbage Out si reprezinta un fapt elementar ce tine de programare: daca iti „hranesti” programul cu ceva rau, mai mult ca sigur vei primi un rezultat la fel de rau. Indiferent de cum privesti situatia, un rezultat prost nu e un lucru bun pentru un programator care vrea sa se faca remarcat.

Daca ai impresia ca exagerez, lasa-ma sa iti dau un exemplu simplu. Gandeste-te la un calculator de imprumuturi online care permite user-ului sa introduca suma de imprumutat dorita, termenul de finantare si rata dobanzii. Sa presupunem ca aplicatia nu include si o optiune care sa verifice erorile si ca user-ul decide sa introduca magica cifra 0 in campul Termen.

Iti poti inchipui rezultatul. Dupa cateva calcule interne aplicatia va incerca sa imparta intreaga suma platita cu zero. Nu are rost sa discutam multitudinea de mesaje de eroare care vor urma, dar trebuie sa mentionez ca ele ar fi putut fi evitate daca developer-ul s-ar fi gandit sa verifice datele de intrare inainte de efectua calculele.
Care este morala acestei povesti? Daca vrei cu adevarat sa folosesti PHP pentru web development, unul din lucrurile cele mai importate pe care trebuie sa le inveti este cum sa validezi informatiile introduse de utilizator si cum sa manevrezi posibilele date eronate. O astfel de verificare a input-ului reprezinta unul din cele mai importante sisteme de protectie pe care un developer le poate instala intr-o aplicatie, iar un esec in aceasta privinta poate genera probleme serioase sau poate chiar sa cauzeze intreruperea aplicatiei in momentul in care gaseste date nule sau corupte.

In urmatoarele paragrafe iti voi arata cateva trucuri simple pentru a valida datele utilizator, pentru a „prinde” informatiile „rele” inainte sa iti corupa calculele si bazele de date si pentru a oferi notificari utilizatorilor intr-o maniera simpla si inteligibila.

Un vas gol

Acest tutorial presupune faptul ca datele ce trebuiesc validate sunt transmise prin intermediul unui formular. Aceasta nu este singura cale prin care un script PHP poate primi date de la utilizator, dar este cea mai comuna metoda.

O practica comuna este folosirea limbajelor scripting client cum ar fi JavaScript sau VBScript pentru validarea formularelor. Totusi, acest tip de validare nu prezinta siguranta maxima pentru ca nu ai control asupra clientului (browserul), iar daca un user inchide JavaScript-ul din browser-ul sau, toate eforturile tale de a te asigua ca el nu introduce informatii invalide devin inutile. De aceea, majoritatea developerilor experimentati folosesc atat validari client-side cat si server-side. Validarile server-side implica verificarea valorilor transmise serverului printr-un script PHP, precum si luarea de masuri corespunzatoare cand input-ul este incorect.

Sa incepem cu cea mai des intalnita eroare de input: un camp obligatoriu caruia ii lipseste valoarea. Arunca o privire la urmatorul exemplu:

<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
    <form action = '<?php $_SERVER['PHP_SELF'] ?>' method = 'post'>
    Ce tip de umplutura de sandwich ai dori ?
    <br />
    <input type = 'text' name = 'filling'>
    <br />
    <input type = 'submit' name = 'submit' value = 'Salveaza'>
    </form>
<?php
}
else {
    // seteaza variabilele bazei de date 
    $host = 'localhost';
    $user = 'user';
    $pass = 'secret';
    $db = 'sandwiches';
 
    // primeste datele din formular
    $filling = mysql_escape_string($_POST['filling']);
 
    // deschide conexiunea 
    $connection = mysql_connect($host, $user, $pass) or die('Nu ma pot conecta la MySQL!');
 
    // selecteaza baza de date  
    mysql_select_db($db) or die('Nu pot selecta baza de date!');
 
    // creeaza interogarea MySQL
    $query = 'INSERT INTO orders (filling) VALUES ("$filling")';
 
    // executa interogarea
    $result = mysql_query($query) or die("Eroare in query: $query. ".mysql_error());
 
    // inchide conexiunea
    mysql_close($connection);
 
    // afisaza mesajul
    echo "Sandvisul tau cu {$_POST['filling']} este pregatit!";
}
?>
</body>
</html>

Din exemplul de mai sus se poate observa ca transmiterea acestuia  fara a introduce nici o informatie va rezulta intr-un inregistrarea goala ce va fi adaugata in baza de date (presupunand ca nu avem constrangeri NOT NULL pentru tabelul orders). Pentru a evita acest lucru, e important sa verifici ca formularul contine date valide si abia apoi poti executa instructiunea INSERT:

<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
    <form action = '<?php $_SERVER['PHP_SELF'] ?>' method = 'post'>
    Ce tip de umplutura de sandwich ai dori ?
    <br />
    <input type = 'text' name = 'filling'>
    <br />
    <input type = 'submit' name = 'submit' value = 'Salveaza'>
    </form>
<?php
}
else {
    // verifica datele cerute
    // die daca sunt absente
    if (!isset($_POST['filling']) || trim($_POST['filling']) == '') {
        die("EROARE: Nu poti avea un sandwich fara umplutura!");
    }
    else {
        $filling = mysql_escape_string(trim($_POST['filling']));
    }
 
    // seteaza variabilele bazei de date
    $host = 'localhost';
    $user = 'user';
    $pass = 'secret';
    $db = 'sandwiches';
 
    // deschide conexiunea 
    $connection = mysql_connect($host, $user, $pass) or die('Nu ma pot conecta la MySQL!');
 
    // selecteaza baza de date 
    mysql_select_db($db) or die('Nu pot selecta baza de date!');
 
    // creeaza interogarea MySQL
    $query = 'INSERT INTO orders (filling) VALUES ("$filling")';
 
    // executa interogarea
    $result = mysql_query($query) or die("Error in query: $query. ".mysql_error());
 
    // inchide conexiunea
    mysql_close($connection);
 
    // afisaza mesajul
    echo " Sandvisul tau cu {$_POST['filling']} este pregatit!";
}
?>
</body>
</html>

Verificarea erorii in acest caz e simpla si logica: functia trim() e folosita pentru a sterge spatiile de la inceputul si sfarsitul datelor introduse, valoare returnata fiind apoi comparata cu un sir gol. Daca avem egalitate, atunci campul a fost introdus gol, iar script-ul afiseaza un mesaj de eroare inainte ca MySQL sa apara in peisaj.

O greseala comuna, mai ales printre incepatori, este inlocuirea combinatiei isset() si trim() cu functia PHP empty() ce iti spune daca o variabila e goala. Aceasta nu e o idee buna de obicei, pentru ca empty() are un defect : va returna true chiar daca o variabila contine cifra 0. Urmatorul exemplu ilustreaza acest lucru:

<?php
 
// nici o informatie, returneaza gol
$data = '';
echo empty($data) ? "$data e gol" : "$data nu e gol";
echo "<br />n";
 
// cateva informatii, nu returneaza gol
$data = '1';
echo empty($data) ? "$data e gol" : "$data nu e gol";
echo "<br />n";
 
// cateva date, returneaza gol
$data = '0';
echo empty($data) ? "$data e gol" : "$data nu e gol"; 
 
?>

Asadar, daca formular tau nu trebuie sa contina un camp cu date non-goale si non-zero, empty() reprezinta o varianta buna pentru a-l valida. Dar daca sirul de valori valide pentru campul tau include cifra 0, ramai la combinatia isset() si trim().

Nu e genul meu

Acum stii cum sa "prinzi" cea mai simpla dintre erori - datele care lipsesc - si cum sa opresti procesarea script-ului inainte sa se produca vreo paguba. Ce faci insa daca datele sunt prezente, dar tipul lor si marimea e gresita? Testul care verifica valorile lipsa nu se va declansa, dar calculele si baza de date tot pot fi afectate. In mod evident, trebuie sa adaugi verificari suplimentare care sa verifice tipul informatiei introduse de utilizator. Iata un exemplu care ilustreaza acest lucru:

<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
    <form action = '<?php $_SERVER['PHP_SELF']?>' method = 'post'>
    Cate sandwich-uri doresti ? (min 1, max 9)
    <br />
    <input type = 'text' name = 'quantity'>
    <br />
    <input type = 'submit' name = 'submit' value = 'Salveaza'>
    </form>
<?php
}
else {
    // verifica datele cerute
    // die daca nu exista
    if (!isset($_POST['quantity']) || trim($_POST['quantity']) == '') {
        die ("EROARE : Nu le putem face daca nu spui cate vrei!");
    }
 
    // verifica daca input-ul este un numar
    if (!is_numeric($_POST['quantity'])) {
        die ("EROARE: Ce ai spus nu este un numar!");
    }
 
    // verifica daca input-ul este un numar intreg
    if (intval($_POST['quantity']) != $_POST['quantity']) {
        die ("EROARE: Nu pot face jumatati, sferturi sau treimi... mi-as pierde slujba!");
    }
 
    // verifica daca input-ul este o cifra intre 1-9
    if (($_POST['quantity'] < 1) || ($_POST['quantity'] > 9)) {
        die ("EROARE: Nu pot sa fac decat intre 1 si 9 sandwich-uri pe comanda!");
    }
 
    // proceseaza informatia
    echo "Iti pregatesc {$_POST['quantity']} sandwich-uri. Sper ca le poti manca pe toate!";
}
?>
</body>
</html>

Observa ca din momentul in care am stabilit ca input-ul contine cateva informatii, am adaugat o serie de teste pentru a ma asigura ca tipul acestuia este corect si ca indeplineste constrangerile. In primul rand, am verificat daca valoarea e numerica cu ajutorul functiei is_numeric() ce testeaza un sir de caractere pentru a vedea daca reprezinta un numar.

Presupunand ca e vorba de un numar, trebuie sa te asiguri ca este un intreg intre 1-9. Pentru a afla daca e un numar intreg am folosit functia intval() pentru a extrage partea intreaga din valoare si am testat-o pentru a vedea daca este egala cu numarul propriu-zis. Valorile float ( cum ar fi 2.5) vor pica acest test; valorile intregi il vor trece. Testul final inainte de a avea unda verde este sa vezi daca se incadreaza undeva intre 1 si 9. Acest lucru se poate realiza usor cu cateva teste de inegalitate.

Daca tot suntem la acest subiect, trebuie sa mentionez si functia strlen() ce ia ca parametru un sir de caractere si  returneaza lungimea acestuia. Aceasta iti va fi de ajutor pentru a te asigura ca datele introduse in campul din formular nu depasesc o anumita lungime. Urmatorul exemplu iti arata cum:

<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
    <form action = '<?php $_SERVER['PHP_SELF']?>' method = 'post'>
    Introdu o porecla intre 6 si 10 caractere:
    <br />
    <input type = 'text' name = 'nick'>
    <br />
    <input type = 'submit' name = 'submit' value = 'Salveaza'>
    </form>
<?php
}
else {
    // verifica datele cerute
    // die daca nu exista
    if (!isset($_POST['nick']) || trim($_POST['nick']) == '') { 
        die ('EROARE: Haide, sunt singur ca te poti gandi la o porecla! Ce zici de Pooky?');
    }
 
    // verifica daca input-ul are lungimea corecta
    if (!(strlen($_POST['nick']) >= 6 && strlen($_POST['nick']) <= 10)) {
        die ("'EROARE: Porecla este fie prea lunga, fie prea scurta!");
    }
 
    // proceseaza datele
    echo "Voi accepta porecla {$_POST['nick']} deoarece vad ca esti tu!";
}
?>
</body>
</html>

In acest caz, functia strlen() e folosita pentru a verifica daca sirul introdus nu e nici prea lung, nici prea scurt. Iti va fi de ajutor si pentru a te asigura ca datele corespund cu cerintele de lungime ale bazei de date. De exemplu, daca ai un camp MySQL VARCHAR(10), sirurile mai lungi de peste 10 caractere vor fi taiate. Functia strlen() poate juca rolul unui sistem de detectare timpurie in acest caz, notificand user-ul asupra neconcordantelor de lungime si evitand coruperea informatiilor.

Jocul de-a intalnirea

Verificarea datelor calendaristice reprezinta un alt aspect important al validarii input-ului. E mult prea usor pentru un user sa selecteze o data ca 29-Feb-2005 sau 31-Apr-2005, dintre care nici una nu e valida. Asadar, e important sa verifici daca valorile datelor oferite de user sunt valide inainte de a le folosi intr-un calcul.

In PHP acest lucru e mult mai simplu decat in alte limbaje datorita functiei checkdate(). Aceasta functie accepta 3 argumente: luna, zi si an si returneaza o valoare booleana care indica daca data e valida sau nu. Urmatorul exemplu demonstreaza acest lucru:

<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
    <form action = '<?php $_SERVER['PHP_SELF']?>' method = 'post'>
    Scrie ziua ta de nastere:
    <br /><br />
    <select name = 'day'>
    <?php
    // genereaza numerele zilelor
    for ($x = 1; $x <= 31; $x++) {
        echo "<option value = $x>$x</option>";
    }
    ?>
    </select>
    <select name = 'month'>
    <?php
    // genereaza numele lunilor
    for ($x = 1; $x <= 12; $x++) {
        echo "<option value=$x>".date('F', mktime(0, 0, 0, $x, 1, 1)).'</option>';
    }
    ?>
    </select>
    <select name = 'year'>
    <?php
    // genereaza valorile anilor
    for ($x = 1950; $x <= 2005; $x++) {
        echo "<option value=$x>$x</option>";
    }
    ?>
    </select>
    <br /><br />
    <input type = 'submit' name = 'submit' value = 'Salveaza'>
    </form>
<?php
}
else {
    // verifica daca data e valida
    if (!checkdate($_POST['month'], $_POST['day'], $_POST['year'])) {
        die("EROARE: Data {$_POST['day']}-{$_POST['month']}-{$_POST['year']} nu exista!");
    }
 
    // proceseaza data
    echo "Ai introdus {$_POST['day']}-{$_POST['month']}-{$_POST['year']} - ceea ce reprezinta o data valida.";
}
?>
</body>
</html>

Incearca sa tastezi o data invalida si vei vedea cum PHP te atentioneaza. Daca salvezi acest input intr-un tabel MySQL, e interesant de stiut ca MySQL nu realizeaza o verificare riguroasa a datei inainte de a accepta o valoare DATE, DATETIME sau TIMESTAMP. In schimb, se asteapta ca developer-ul sa construiasca o optiune de verificare a datei in cadrul aplicatiei propriu-zise. Ceea ce MySQL va face, daca gaseste o valoare evident ilegala, este sa transforme data intr-o valoare zero, ceea ce nu prea te ajuta. Citeste mai mult despre asta la http://dev.mysql.com/doc/mysql/en/datetime.html.

Daca tot dezbatem acest subiect, hai sa vorbim putin si despre elementele cu selectie multipla cum ar fi meniurile drop-down, listele checkbox si radio button. Pentru cazurile in care cel putin o valoare trebuie selectata, developer-ul trebuie sa verifice ca macar una din optiunile disponibile a fost aleasa de catre utilizator. Acest lucru implica, in mare, o folosire inteligenta a isset() si pentru meniurile multi-select - functiile is_array() si sizeof(). Urmatorul exemplu ilustreaza acest lucru:

<html>
<head></head>
<body>
<?php
if (!isset($_POST['submit'])) {
?>
    <form action = '<?php $_SERVER['PHP_SELF'] ?>' method = 'post'>
    Blat pizza:
    <br />
    <input type = 'radio' name = 'blat' value = 'subtire si crocant'>Subtire si crocant
    <input type = 'radio' name = 'blat' value = 'pufos'>Pufos
    <br />
Branza:
    <br />
    <select name = 'branza'>
        <option value = 'mozzarella'>Mozzarella</option>
        <option value = 'parmesan'>Parmesan</option>
        <option value = 'gruyere'>Gruyere</option>
    </select>
    <br />
    Topping-uri:
    <br />
    <select multiple name = 'toppings[]'>
        <option value = 'rosii'>Rosii</option>
        <option value = 'masline'>Masline</option>
        <option value = 'pepperoni'>Pepperoni</option>
        <option value = 'ceapa'>Ceapa</option>
        <option value = 'ardei iute'>Ardei iute</option>
        <option value = 'cabanos'>Cabanos</option>
    </select>
    <br />
    <input type = 'submit' name = 'submit' value = 'Salveaza'>
    </form>
<?php
}
else {
    // verifica radio button
    if (!isset($_POST['base'])) {
        die('Trebuie sa selectezi un tip de blat');
    }
 
    // verifica list box
    if (!isset($_POST['cheese'])) {
        die("Trebuie sa selectezi un tip de branza");
    }
 
    // verifica multi-select 
    if (!is_array($_POST['toppings']) || sizeof($_POST['toppings']) < 1) {
        die('Trebuie sa selectezi cel putin un topping');
    }
 
    // proceseaza datele
    echo "O pizza {$_POST['base']} {$_POST['cheese']} cu ";
    foreach ($_POST['toppings'] as $topping) echo $topping.", ";
    echo "e pregatita!";
 
}
?>
</body>
</html>

Fara a insista prea mult, functia isset() verifica doar faptul ca cel putin unul din seturile de optiuni a fost selectat si printeaza un mesaj eroare daca nu avem de a face cu acest lucru. Observa cum se valideaza meniurile dropdown cu selectie multipla: cand formularul este transmis, valorile alese sunt imbricate intr-un vector, iar functiile PHP is_array() si sizeof() sunt folosite pentru a testa acel vector si pentru a se asigura ca el contine macar un element.

Copyright © 2008-2010 E-LEARN.ro. Toate drepturile rezervate. Conceput si realizat de Neokinetics Software.