Cod PHP Buggy: Cele mai frecvente 10 greșeli pe care le fac dezvoltatorii PHP

Publicat: 2022-03-11

PHP face relativ ușor să construiți un sistem bazat pe web, ceea ce este în mare parte motivul popularității sale. Dar, în pofida ușurinței sale de utilizare, PHP a evoluat într-un limbaj destul de sofisticat, cu multe cadre, nuanțe și subtilități care îi pot afecta dezvoltatorilor, ducând la ore de depanare atrăgătoare. Acest articol evidențiază zece dintre cele mai frecvente greșeli de care trebuie să se ferească dezvoltatorii PHP.

Greșeala comună #1: Lăsarea referințelor de matrice suspendate după buclele foreach

Nu sunteți sigur cum să utilizați buclele foreach în PHP? Utilizarea referințelor în buclele foreach poate fi utilă dacă doriți să operați pe fiecare element din tabloul pe care îl iterați. De exemplu:

 $arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr is now array(2, 4, 6, 8)

Problema este că, dacă nu ești atent, acest lucru poate avea și unele efecte secundare și consecințe nedorite. Mai exact, în exemplul de mai sus, după ce codul este executat, $value va rămâne în domeniu și va păstra o referință la ultimul element din matrice. Operațiunile ulterioare care implică $value ar putea, prin urmare, să ajungă neintenționat să modifice ultimul element din matrice.

Principalul lucru de reținut este că foreach nu creează un domeniu de aplicare. Astfel, $value din exemplul de mai sus este o referință în domeniul superior al scriptului. La fiecare iterație, foreach setează referința să indice următorul element din $array . Prin urmare, după ce bucla se termină, $value indică în continuare către ultimul element din $array și rămâne în domeniu.

Iată un exemplu de tip de erori evazive și confuze la care poate duce acest lucru:

 $array = [1, 2, 3]; echo implode(',', $array), "\n"; foreach ($array as &$value) {} // by reference echo implode(',', $array), "\n"; foreach ($array as $value) {} // by value (ie, copy) echo implode(',', $array), "\n";

Codul de mai sus va scoate următoarele:

 1,2,3 1,2,3 1,2,2

Nu, nu este o greșeală de tipar. Ultima valoare de pe ultima linie este într-adevăr un 2, nu un 3.

De ce?

După parcurgerea primei bucle foreach , $array rămâne neschimbat, dar, după cum s-a explicat mai sus, $value este lăsat ca referință suspendată la ultimul element din $array (din moment ce acea buclă foreach a accesat $value prin referință ).

Ca rezultat, atunci când trecem prin a doua buclă foreach , par să se întâmple „lucruri ciudate”. Mai exact, deoarece $value este acum accesat prin valoare (adică prin copie ), foreach copiază fiecare element $array secvenţial în $value în fiecare pas al buclei. Ca rezultat, iată ce se întâmplă în timpul fiecărei etape a celei de-a doua bucle foreach :

  • Treceți 1: Copiază $array[0] (adică, „1”) în $value (care este o referință la $array[2] ), deci $array[2] este acum egal cu 1. Deci $array conține acum [1, 2, 1].
  • Pass 2: Copiază $array[1] (adică, „2”) în $value (care este o referință la $array[2] ), deci $array[2] este acum egal cu 2. Deci $array conține acum [1, 2, 2].
  • Trecerea 3: Copiază $array[2] (care acum este egal cu „2”) în $value (care este o referință la $array[2] ), deci $array[2] este în continuare egal cu 2. Deci $array conține acum [1 , 2, 2].

Pentru a beneficia în continuare de utilizarea referințelor în buclele foreach fără a risca aceste tipuri de probleme, apelați unset() pe variabilă, imediat după bucla foreach , pentru a elimina referința; de exemplu:

 $arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } unset($value); // $value no longer references $arr[3]

Greșeala comună #2: Înțelegerea greșită a comportamentului isset() .

În ciuda numelui său, isset() nu numai că returnează false dacă un element nu există, dar returnează și false pentru valorile null .

Acest comportament este mai problematic decât ar putea părea la început și este o sursă comună de probleme.

Luați în considerare următoarele:

 $data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) { // do something here if 'keyShouldBeSet' is not set }

Autorul acestui cod probabil a vrut să verifice dacă keyShouldBeSet a fost setat în $data . Dar, după cum sa discutat, isset($data['keyShouldBeSet']) va returna și false dacă $data['keyShouldBeSet'] a fost setat, dar a fost setat la null . Deci logica de mai sus este greșită.

Iată un alt exemplu:

 if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if (!isset($postData)) { echo 'post not active'; }

Codul de mai sus presupune că, dacă $_POST['active'] returnează true , atunci postData va fi neapărat setat și, prin urmare, isset($postData) va returna true . Deci, invers, codul de mai sus presupune că singurul mod în care isset($postData) va returna false este dacă $_POST['active'] returnat și false .

Nu.

După cum sa explicat, isset($postData) va returna și false dacă $postData a fost setat la null . Prin urmare, este posibil ca isset($postData) să returneze false chiar dacă $_POST['active'] returnat true . Deci, din nou, logica de mai sus este greșită.

Și apropo, ca punct secundar, dacă intenția din codul de mai sus a fost într-adevăr de a verifica din nou dacă $_POST['active'] returnat adevărat, bazându-ne pe isset() pentru că aceasta a fost o decizie de codare proastă în orice caz. În schimb, ar fi fost mai bine să verificați din nou $_POST['active'] ; adică:

 if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if ($_POST['active']) { echo 'post not active'; }

Totuși, pentru cazurile în care este important să se verifice dacă o variabilă a fost într-adevăr setată (adică, pentru a face distincția între o variabilă care nu a fost setată și o variabilă care a fost setată la null ), metoda array_key_exists() este mult mai robustă. soluţie.

De exemplu, am putea rescrie primul dintre cele două exemple de mai sus după cum urmează:

 $data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) { // do this if 'keyShouldBeSet' isn't set }

Mai mult decât atât, combinând array_key_exists() cu get_defined_vars() , putem verifica în mod fiabil dacă o variabilă din domeniul curent a fost setată sau nu:

 if (array_key_exists('varShouldBeSet', get_defined_vars())) { // variable $varShouldBeSet exists in current scope }

Greșeala obișnuită nr. 3: Confuzie despre returnarea prin referință vs. după valoare

Luați în considerare acest fragment de cod:

 class Config { private $values = []; public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

Dacă rulați codul de mai sus, veți obține următoarele:

 PHP Notice: Undefined index: test in /path/to/my/script.php on line 21

Ce s-a întâmplat?

Problema este că codul de mai sus confundă matricele returnate prin referință cu matricele returnate după valoare. Cu excepția cazului în care îi spuneți explicit PHP să returneze o matrice prin referință (adică, folosind & ), PHP va returna implicit matricea „după valoare”. Aceasta înseamnă că o copie a matricei va fi returnată și, prin urmare, funcția apelată și apelantul nu vor accesa aceeași instanță a matricei.

Deci apelul de mai sus la getValues() returnează o copie a matricei $values , mai degrabă decât o referință la acesta. Având în vedere acest lucru, să revedem cele două linii cheie din exemplul de mai sus:

 // getValues() returns a COPY of the $values array, so this adds a 'test' element // to a COPY of the $values array, but not to the $values array itself. $config->getValues()['test'] = 'test'; // getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't // contain a 'test' element (which is why we get the "undefined index" message). echo $config->getValues()['test'];

O posibilă remediere ar fi salvarea primei copii a matricei $values ​​returnată de getValues() și apoi operarea pe acea copie ulterior; de exemplu:

 $vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];

Codul respectiv va funcționa bine (adică va scoate un test fără a genera niciun mesaj „index nedefinit”), dar în funcție de ceea ce încercați să realizați, această abordare poate fi sau nu adecvată. În special, codul de mai sus nu va modifica matricea originală $values . Deci, dacă doriți ca modificările dvs. (cum ar fi adăugarea unui element „test”) să afecteze matricea originală, ar trebui să modificați funcția getValues() pentru a returna o referință la matricea $values ​​în sine. Acest lucru se face prin adăugarea unui & înainte de numele funcției, indicând astfel că ar trebui să returneze o referință; adică:

 class Config { private $values = []; // return a REFERENCE to the actual $values array public function &getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

Rezultatul acestuia va fi test , așa cum era de așteptat.

Dar pentru a face lucrurile mai confuze, luați în considerare următorul fragment de cod:

 class Config { private $values; // using ArrayObject rather than array public function __construct() { $this->values = new ArrayObject(); } public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

Dacă ați ghicit că aceasta ar duce la aceeași eroare de „index nedefinit” ca exemplul nostru array anterior, ați greșit. De fapt, acest cod va funcționa foarte bine. Motivul este că, spre deosebire de matrice, PHP trece întotdeauna obiectele prin referință . ( ArrayObject este un obiect SPL, care imită pe deplin utilizarea matricelor, dar funcționează ca obiect.)

După cum demonstrează aceste exemple, nu este întotdeauna complet evident în PHP dacă aveți de-a face cu o copie sau cu o referință. Prin urmare, este esențial să înțelegeți aceste comportamente implicite (adică variabilele și matricele sunt transmise după valoare; obiectele sunt transmise prin referință) și, de asemenea, să verificați cu atenție documentația API pentru funcția pe care o apelați pentru a vedea dacă returnează o valoare, o copie a unei matrice, o referință la o matrice sau o referire la un obiect.

Toate acestea fiind spuse, este important de reținut că practica de a returna o referință la o matrice sau un ArrayObject este, în general, ceva care ar trebui evitat, deoarece oferă apelantului posibilitatea de a modifica datele private ale instanței. Acest lucru „zboară în fața” încapsulării. În schimb, este mai bine să folosiți „getters” și „setters” în stil vechi, de exemplu:

 class Config { private $values = []; public function setValue($key, $value) { $this->values[$key] = $value; } public function getValue($key) { return $this->values[$key]; } } $config = new Config(); $config->setValue('testKey', 'testValue'); echo $config->getValue('testKey'); // echos 'testValue'

Această abordare oferă apelantului posibilitatea de a seta sau de a obține orice valoare în matrice fără a oferi acces public la matricea $values , altfel privată.

Greșeala comună #4: Efectuarea interogărilor într-o buclă

Nu este neobișnuit să dai peste așa ceva dacă PHP-ul tău nu funcționează:

 $models = []; foreach ($inputValues as $inputValue) { $models[] = $valueRepository->findByValue($inputValue); }

Deși s-ar putea să nu fie absolut nimic în neregulă aici, dar dacă urmați logica din cod, s-ar putea să descoperiți că apelul inocent de mai sus către $valueRepository->findByValue() are ca rezultat o interogare de un fel, cum ar fi:

 $result = $connection->query("SELECT `x`,`y` FROM `values` WHERE `value`=" . $inputValue);

Ca rezultat, fiecare iterație a buclei de mai sus ar avea ca rezultat o interogare separată la baza de date. Deci, dacă, de exemplu, ați furnizat o matrice de 1.000 de valori buclei, aceasta ar genera 1.000 de interogări separate pentru resursă! Dacă un astfel de script este apelat în mai multe fire de execuție, ar putea să oprească sistemul.

Prin urmare, este esențial să recunoașteți când interogările sunt făcute de codul dvs. și, ori de câte ori este posibil, să culegeți valorile și apoi să rulați o interogare pentru a obține toate rezultatele.

Un exemplu de loc destul de obișnuit în care interogările sunt efectuate ineficient (adică, într-o buclă) este atunci când un formular este postat cu o listă de valori (ID-uri, de exemplu). Apoi, pentru a prelua datele de înregistrare complete pentru fiecare ID-uri, codul va trece prin matrice și va efectua o interogare SQL separată pentru fiecare ID. Acesta va arăta adesea cam așa:

 $data = []; foreach ($ids as $id) { $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` = " . $id); $data[] = $result->fetch_row(); }

Dar același lucru poate fi realizat mult mai eficient într-o singură interogare SQL, după cum urmează:

 $data = []; if (count($ids)) { $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` IN (" . implode(',', $ids)); while ($row = $result->fetch_row()) { $data[] = $row; } }

Prin urmare, este esențial să recunoașteți când interogările sunt efectuate, direct sau indirect, de către codul dvs. Ori de câte ori este posibil, adunați valorile și apoi executați o interogare pentru a obține toate rezultatele. Cu toate acestea, trebuie să fim precauți și acolo, ceea ce ne duce la următoarea noastră greșeală comună PHP...

Greșeala comună #5: falsuri și ineficiențe legate de utilizarea memoriei

În timp ce preluarea mai multor înregistrări simultan este cu siguranță mai eficientă decât rularea unei singure interogări pentru fiecare rând de preluat, o astfel de abordare poate duce la o condiție „lipsă de memorie” în libmysqlclient atunci când se folosește extensia mysql a PHP.

Pentru a demonstra, să aruncăm o privire la o casetă de testare cu resurse limitate (512 MB RAM), MySQL și php-cli .

Vom porni un tabel de bază de date ca acesta:

 // connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); // create table of 400 columns $query = 'CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT'; for ($col = 0; $col < 400; $col++) { $query .= ", `col$col` CHAR(10) NOT NULL"; } $query .= ');'; $connection->query($query); // write 2 million rows for ($row = 0; $row < 2000000; $row++) { $query = "INSERT INTO `test` VALUES ($row"; for ($col = 0; $col < 400; $col++) { $query .= ', ' . mt_rand(1000000000, 9999999999); } $query .= ')'; $connection->query($query); }

OK, acum să verificăm utilizarea resurselor:

 // connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); echo "Before: " . memory_get_peak_usage() . "\n"; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1'); echo "Limit 1: " . memory_get_peak_usage() . "\n"; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000'); echo "Limit 10000: " . memory_get_peak_usage() . "\n";

Ieșire:

 Before: 224704 Limit 1: 224704 Limit 10000: 224704

Misto. Se pare că interogarea este gestionată în siguranță intern în ceea ce privește resursele.

Pentru a fi sigur, totuși, haideți să creștem limita încă o dată și să o setăm la 100.000. Uh-oh. Când facem asta, obținem:

 PHP Warning: mysqli::query(): (HY000/2013): Lost connection to MySQL server during query in /root/test.php on line 11

Ce s-a întâmplat?

Problema aici este modul în care funcționează modulul mysql al PHP. Este într-adevăr doar un proxy pentru libmysqlclient , care face treaba murdară. Când o porțiune de date este selectată, aceasta intră direct în memorie. Deoarece această memorie nu este gestionată de managerul PHP, memory_get_peak_usage() nu va afișa nicio creștere a utilizării resurselor pe măsură ce creștem limita în interogarea noastră. Acest lucru duce la probleme precum cea demonstrată mai sus, în care suntem păcăliți în complezență gândindu-ne că gestionarea memoriei noastre este în regulă. Dar, în realitate, gestionarea memoriei noastre este grav defectuoasă și putem întâmpina probleme precum cea prezentată mai sus.

Puteți evita cel puțin headfake-ul de mai sus (deși nu vă va îmbunătăți utilizarea memoriei) folosind în schimb modulul mysqlnd . mysqlnd este compilat ca extensie PHP nativă și folosește managerul de memorie PHP.

Prin urmare, dacă rulăm testul de mai sus folosind mysqlnd mai degrabă decât mysql , obținem o imagine mult mai realistă a utilizării memoriei noastre:

 Before: 232048 Limit 1: 324952 Limit 10000: 32572912

Și e chiar mai rău decât atât, apropo. Conform documentației PHP, mysql folosește de două ori mai multe resurse decât mysqlnd pentru a stoca date, așa că scriptul original care folosea mysql a folosit într-adevăr și mai multă memorie decât se arată aici (aproximativ de două ori mai mult).

Pentru a evita astfel de probleme, luați în considerare limitarea dimensiunii interogărilor și utilizarea unei bucle cu un număr mic de iterații; de exemplu:

 $totalNumberToFetch = 10000; $portionSize = 100; for ($i = 0; $i <= ceil($totalNumberToFetch / $portionSize); $i++) { $limitFrom = $portionSize * $i; $res = $connection->query( "SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize"); }

Când luăm în considerare atât această greșeală PHP, cât și greșeala nr. 4 de mai sus, ne dăm seama că există un echilibru sănătos pe care codul dvs. trebuie să-l atingă în mod ideal între, pe de o parte, ca interogările dvs. să fie prea granulare și repetitive și ca fiecare dintre dvs. interogările individuale să fie prea mari. Așa cum este adevărat în majoritatea lucrurilor din viață, este nevoie de echilibru; oricare dintre extreme nu este bună și poate cauza probleme cu PHP-ul nu funcționează corect.

Greșeala comună #6: Ignorarea problemelor Unicode/UTF-8

Într-un anumit sens, aceasta este într-adevăr mai mult o problemă în PHP în sine decât ceva cu care te-ai întâlni în timpul depanării PHP, dar nu a fost niciodată abordată în mod adecvat. Nucleul PHP 6 urma să fie conștient de Unicode, dar acest lucru a fost suspendat când dezvoltarea PHP 6 a fost suspendată în 2010.

Dar asta nu îl absolvă în niciun caz pe dezvoltator de a preda corect UTF-8 și de a evita presupunerea eronată că toate șirurile vor fi neapărat „ASCII simplu vechi”. Codul care nu reușește să gestioneze în mod corespunzător șirurile non-ASCII este notoriu pentru introducerea unor erori noduroase în codul tău. Chiar și apelurile simple strlen($_POST['name']) ar putea cauza probleme dacă cineva cu un nume de familie precum „Schrodinger” ar încerca să se înscrie în sistemul dumneavoastră.

Iată o mică listă de verificare pentru a evita astfel de probleme în codul dvs.:

  • Dacă nu știi prea multe despre Unicode și UTF-8, ar trebui să înveți cel puțin elementele de bază. Există un primer excelent aici.
  • Asigurați-vă că utilizați întotdeauna funcțiile mb_* în loc de vechile funcții șir (asigurați-vă că extensia „multibyte” este inclusă în versiunea dvs. PHP).
  • Asigurați-vă că baza de date și tabelele sunt setate să utilizeze Unicode (multe versiuni ale MySQL încă folosesc latin1 în mod implicit).
  • Rețineți că json_encode() convertește simboluri non-ASCII (de exemplu, „Schrodinger” devine „Schr\u00f6dinger”), dar serialize() nu face .
  • Asigurați-vă că fișierele de cod PHP sunt, de asemenea, codificate UTF-8 pentru a evita coliziunile atunci când concatenați șiruri cu constante de șir codificate sau configurate.

O resursă deosebit de valoroasă în acest sens este postarea UTF-8 Primer pentru PHP și MySQL de Francisco Claria pe acest blog.

Greșeala comună #7: Presupunând că $_POST va conține întotdeauna datele dvs. POST

În ciuda numelui său, matricea $_POST nu va conține întotdeauna datele POST și poate fi ușor găsită goală. Pentru a înțelege acest lucru, să aruncăm o privire la un exemplu. Să presupunem că facem o cerere de server cu un apel jQuery.ajax() după cum urmează:

 // js $.ajax({ url: 'http://my.site/some/path', method: 'post', data: JSON.stringify({a: 'a', b: 'b'}), contentType: 'application/json' });

(De altfel, notați contentType: 'application/json' aici. Trimitem date ca JSON, care este destul de popular pentru API-uri. Este implicit, de exemplu, pentru postarea în serviciul AngularJS $http .)

Pe partea de server a exemplului nostru, pur și simplu aruncăm matricea $_POST :

 // php var_dump($_POST);

În mod surprinzător, rezultatul va fi:

 array(0) { }

De ce? Ce s-a întâmplat cu șirul nostru JSON {a: 'a', b: 'b'} ?

Răspunsul este că PHP analizează automat o sarcină utilă POST numai atunci când are un tip de conținut application/x-www-form-urlencoded sau multipart/form-data . Motivele pentru aceasta sunt istorice - aceste două tipuri de conținut au fost, în esență, singurele folosite cu ani în urmă, când a fost implementat $_POST PHP. Deci, cu orice alt tip de conținut (chiar și cele care sunt destul de populare astăzi, cum ar fi application/json ), PHP nu încarcă automat sarcina utilă POST.

Deoarece $_POST este un superglobal, dacă îl suprascriem o dată (de preferință la începutul scriptului nostru), valoarea modificată (adică, inclusiv sarcina utilă POST) va fi apoi referibilă în codul nostru. Acest lucru este important, deoarece $_POST este folosit în mod obișnuit de cadrele PHP și aproape toate scripturile personalizate pentru a extrage și transforma datele cererii.

Deci, de exemplu, atunci când procesăm o sarcină utilă POST cu un tip de conținut de application/json , trebuie să analizăm manual conținutul solicitării (adică, să decodăm datele JSON) și să suprascriem variabila $_POST , după cum urmează:

 // php $_POST = json_decode(file_get_contents('php://input'), true);

Apoi, când aruncăm matricea $_POST , vedem că include corect sarcina utilă POST; de exemplu:

 array(2) { ["a"]=> string(1) "a" ["b"]=> string(1) "b" }

Greșeala comună #8: Gândirea că PHP acceptă un tip de date caracter

Priviți acest exemplu de cod și încercați să ghiciți ce se va imprima:

 for ($c = 'a'; $c <= 'z'; $c++) { echo $c . "\n"; }

Dacă ai răspuns de la „a” la „z”, s-ar putea să fii surprins să afli că ai greșit.

Da, va tipări de la „a” la „z”, dar apoi va tipări și „aa” până la „yz”. Să vedem de ce.

În PHP nu există un tip de date char ; numai string este disponibil. Având în vedere asta, incrementând string z în PHP rezultă aa :

 php> $c = 'z'; echo ++$c . "\n"; aa

Totuși, pentru a încurca și mai mult lucrurile, aa este lexicografic mai mic decât z :

 php> var_export((boolean)('aa' < 'z')) . "\n"; true

De aceea, exemplul de cod prezentat mai sus tipărește literele de la a la z , dar apoi tipărește și de la aa la yz . Se oprește când ajunge la za , care este prima valoare pe care o întâlnește și care este „mai mare decât” z :

 php> var_export((boolean)('za' < 'z')) . "\n"; false

Așa fiind, iată o modalitate de a parcurge corect valorile de la „a” la „z” în PHP:

 for ($i = ord('a'); $i <= ord('z'); $i++) { echo chr($i) . "\n"; }

Sau alternativ:

 $letters = range('a', 'z'); for ($i = 0; $i < count($letters); $i++) { echo $letters[$i] . "\n"; }

Greșeala comună #9: Ignorarea standardelor de codare

Deși ignorarea standardelor de codare nu duce direct la necesitatea depanării codului PHP, este probabil unul dintre cele mai importante lucruri de discutat aici.

Ignorarea standardelor de codificare poate cauza o mulțime de probleme într-un proiect. În cel mai bun caz, rezultă un cod care este inconsecvent (din moment ce fiecare dezvoltator „își face treaba lui”). Dar, în cel mai rău caz, produce cod PHP care nu funcționează sau poate fi dificil (uneori aproape imposibil) de navigat, făcându-l extrem de dificil de depanat, îmbunătățit, întreținut. Și asta înseamnă o productivitate redusă pentru echipa ta, inclusiv mult efort irosit (sau cel puțin inutil).

Din fericire pentru dezvoltatorii PHP, există Recomandarea Standardelor PHP (PSR), compusă din următoarele cinci standarde:

  • PSR-0: Standard de încărcare automată
  • PSR-1: Standard de codare de bază
  • PSR-2: Ghid de stil de codare
  • PSR-3: Interfață de înregistrare
  • PSR-4: Încărcare automată

PSR a fost creat inițial pe baza intrărilor de la întreținerii celor mai recunoscute platforme de pe piață. Zend, Drupal, Symfony, Joomla și alții au contribuit la aceste standarde și acum le respectă. Chiar și PEAR, care a încercat să fie un standard cu ani înainte, participă acum la PSR.

Într-un anumit sens, aproape că nu contează care este standardul dvs. de codificare, atâta timp cât sunteți de acord cu un standard și respectați el, dar respectarea PSR este, în general, o idee bună, cu excepția cazului în care aveți un motiv convingător pentru proiectul dvs. să faceți altfel. . Din ce în ce mai multe echipe și proiecte sunt conforme cu PSR. Tt este cu siguranță recunoscut în acest moment drept „standardul” de către majoritatea dezvoltatorilor PHP, așa că folosirea acestuia va ajuta să vă asigurați că noii dezvoltatori sunt familiarizați și confortabil cu standardul dvs. de codare atunci când se alătură echipei dumneavoastră.

Greșeala comună #10: Folosirea greșită a empty()

Unii dezvoltatori PHP le place să folosească empty() pentru verificări booleene pentru aproape orice. Există, totuși, cazuri în care acest lucru poate duce la confuzie.

Mai întâi, să revenim la matrice și instanțe ArrayObject (care imită matrice). Având în vedere asemănarea lor, este ușor să presupunem că matricele și instanțele ArrayObject se vor comporta identic. Aceasta se dovedește, totuși, a fi o presupunere periculoasă. De exemplu, în PHP 5.0:

 // PHP 5.0 or later: $array = []; var_dump(empty($array)); // outputs bool(true) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false) // why don't these both produce the same output?

Și pentru a înrăutăți lucrurile, rezultatele ar fi fost diferite înainte de PHP 5.0:

 // Prior to PHP 5.0: $array = []; var_dump(empty($array)); // outputs bool(false) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false)

Această abordare este, din păcate, destul de populară. De exemplu, acesta este modul în care Zend\Db\TableGateway din Zend Framework 2 returnează date atunci când apelează current() pe TableGateway::select() așa cum sugerează documentul. Dezvoltatorul poate deveni cu ușurință victima acestei greșeli cu astfel de date.

Pentru a evita aceste probleme, cea mai bună abordare pentru verificarea structurilor de matrice goale este să utilizați count() :

 // Note that this work in ALL versions of PHP (both pre and post 5.0): $array = []; var_dump(count($array)); // outputs int(0) $array = new ArrayObject(); var_dump(count($array)); // outputs int(0)

Și întâmplător, deoarece PHP transformă 0 în false , count() poate fi folosit și în condiții if () pentru a verifica matricele goale. De asemenea, este de remarcat faptul că, în PHP, count() este o complexitate constantă (operație O(1) ) pe matrice, ceea ce face și mai clar că este alegerea corectă.

Un alt exemplu când empty() poate fi periculos este atunci când îl combinați cu funcția de clasă magică __get() . Să definim două clase și să avem o proprietate de test în ambele.

Mai întâi să definim o clasă Regular care include test ca proprietate normală:

 class Regular { public $test = 'value'; }

Apoi să definim o clasă Magic care folosește operatorul magic __get() pentru a-și accesa proprietatea de test :

 class Magic { private $values = ['test' => 'value']; public function __get($key) { if (isset($this->values[$key])) { return $this->values[$key]; } } }

OK, acum să vedem ce se întâmplă când încercăm să accesăm proprietatea de test a fiecăreia dintre aceste clase:

 $regular = new Regular(); var_dump($regular->test); // outputs string(4) "value" $magic = new Magic(); var_dump($magic->test); // outputs string(4) "value"

Bine până acum.

Dar acum să vedem ce se întâmplă când apelăm empty() pe fiecare dintre acestea:

 var_dump(empty($regular->test)); // outputs bool(false) var_dump(empty($magic->test)); // outputs bool(true)

Uf. Deci, dacă ne bazăm pe empty() , putem fi induși în eroare să credem că proprietatea de test a lui $magic este goală, în timp ce în realitate este setată la 'value' .

Din păcate, dacă o clasă folosește funcția magică __get() pentru a prelua valoarea unei proprietăți, nu există o modalitate sigură de a verifica dacă acea valoare a proprietății este goală sau nu. În afara domeniului de aplicare al clasei, puteți verifica doar dacă o valoare null va fi returnată și asta nu înseamnă neapărat că cheia corespunzătoare nu este setată, deoarece de fapt ar fi putut fi setată la null .

În schimb, dacă încercăm să facem referire la o proprietate inexistentă a unei instanțe de clasă Regular , vom primi o notificare similară cu următoarea:

 Notice: Undefined property: Regular::$nonExistantTest in /path/to/test.php on line 10 Call Stack: 0.0012 234704 1. {main}() /path/to/test.php:0

Așadar, punctul principal aici este că metoda empty() trebuie utilizată cu grijă, deoarece se poate preda la rezultate confuze – sau chiar potențial înșelătoare – dacă nu este atent.

Învelire

Ușurința de utilizare a PHP poate ameliora dezvoltatorii într-un fals sentiment de confort, lăsându-se vulnerabili la depanarea PHP îndelungată din cauza unora dintre nuanțe și idiosincrazii ale limbajului. Acest lucru poate duce la ca PHP să nu funcționeze și probleme precum cele descrise aici.

Limbajul PHP a evoluat semnificativ de-a lungul istoriei sale de 20 de ani. Familiarizarea cu subtilitățile sale este un efort util, deoarece vă va ajuta să vă asigurați că software-ul pe care îl produceți este mai scalabil, mai robust și mai ușor de întreținut.