Vďaka PHP je relatívne ľahké vybudovať webový systém, čo je veľkou príčinou jeho popularity. Ale napriek jeho ľahkému použitiu PHP sa vyvinulo do dosť sofistikovaného jazyka s mnohými štruktúrami, nuansami a jemnosťami, ktoré môžu vývojárov pohrýzť, čo vedie k hodinám ladenia za vlasy. Tento článok zdôrazňuje desať najbežnejších chýb Vývojári PHP treba si dať pozor.
foreach
slučkyNeviete, ako používať slučky foreach v PHP? Používanie referencií v foreach
slučky môžu byť užitočné, ak chcete pracovať s každým prvkom v poli, ktoré iterujete. Napríklad:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr is now array(2, 4, 6, 8)
Problém je v tom, že ak si nedáte pozor, môže to mať aj niektoré nežiaduce vedľajšie účinky a následky. Konkrétne vo vyššie uvedenom príklade po vykonaní kódu $value
zostane v rozsahu a bude obsahovať odkaz na posledný prvok v poli. Následné operácie zahŕňajúce $value
by preto mohlo nechtiac skončiť úpravou posledného prvku v poli.
Hlavná vec, ktorú treba pamätať, je foreach
nevytvára rozsah. Teda $value
vo vyššie uvedenom príklade je a odkaz v hornom rozsahu skriptu. Pri každej iterácii foreach
nastaví odkaz na ďalší prvok $array
. Po dokončení cyklu teda $value
stále ukazuje na posledný prvok $array
a zostáva v rozsahu pôsobnosti.
Tu je príklad druhu úhybných a mätúcich chýb, ku ktorým to môže viesť:
$array = [1, 2, 3]; echo implode(',', $array), '
'; foreach ($array as &$value) {} // by reference echo implode(',', $array), '
'; foreach ($array as $value) {} // by value (i.e., copy) echo implode(',', $array), '
';
Vyššie uvedený kód poskytne nasledujúce výstupy:
1,2,3 1,2,3 1,2,2
Nie, nejde o preklep. Posledná hodnota v poslednom riadku je skutočne 2, nie 3.
Prečo?
Po prechode prvým foreach
slučka, $array
zostáva nezmenený, ale, ako je vysvetlené vyššie, $value
je ponechaný ako visiaci odkaz na posledný prvok v $array
(od tej doby foreach
sprístupnená slučka $value
od odkaz ).
Vo výsledku, keď prechádzame druhým foreach
slučka, zdá sa, že sa vyskytujú „divné veci“. Konkrétne od $value
je teraz prístupný podľa hodnoty (tj kópia ), foreach
kópie každé postupné $array
prvok do $value
v každom kroku slučky. Výsledkom je, že čo sa stane počas každého kroku druhého foreach
slučka:
$array[0]
(tj. „1“) do $value
(čo je odkaz na $array[2]
), takže $array[2]
teraz sa rovná 1. Takže $array
teraz obsahuje [1, 2, 1].$array[1]
(tj. „2“) do $value
(čo je odkaz na $array[2]
), takže $array[2]
teraz sa rovná 2. Takže $array
teraz obsahuje [1, 2, 2].$array[2]
(ktoré sa teraz rovná „2“) do $value
(čo je odkaz na $array[2]
), takže $array[2]
stále sa rovná 2. Takže $array
teraz obsahuje [1, 2, 2].Ak chcete stále využívať výhody použitia referencií v foreach
slučky bez rizika vzniku týchto druhov problémov, volajte unset()
na premennej, bezprostredne za foreach
slučka, na odstránenie referencie; napr .:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } unset($value); // $value no longer references $arr[3]
isset()
správanieNapriek svojmu názvu isset()
nielenže vráti hodnotu false, ak položka neexistuje, ale - vráti tiež false
pre null
hodnoty .
Toto správanie je problematickejšie, ako by sa na prvý pohľad mohlo zdať, a je častým zdrojom problémov.
Zvážte nasledovné:
$data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) { // do something here if 'keyShouldBeSet' is not set }
Autor tohto kódu pravdepodobne chcel skontrolovať, či keyShouldBeSet
bolo nastavené v $data
. Ako už bolo uvedené, isset($data['keyShouldBeSet'])
bude tiež return false if $data['keyShouldBeSet']
bol nastavená, ale bola nastavená na null
. Vyššie uvedená logika je teda chybná.
js previesť časovú pečiatku na dátum
Tu je ďalší príklad:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if (!isset($postData)) { echo 'post not active'; }
Vyššie uvedený kód predpokladá, že ak $_POST['active']
vráti true
, potom postData
budú nevyhnutne nastavené, a preto isset($postData)
vráti true
. Vyššie uvedený kód teda predpokladá, že iba tak isset($postData)
vráti false
je ak $_POST['active']
vrátené false
tiež.
Nie.
Ako bolo vysvetlené, isset($postData)
vráti tiež false
ak $postData
bol nastavený na null
. Preto je možné pre isset($postData)
vrátiť sa false
aj keď $_POST['active']
vrátené true
. Vyššie uvedená logika je teda chybná.
A mimochodom, ako bočný bod, ak bolo zámerom vo vyššie uvedenom kóde skutočne znova skontrolovať, či $_POST['active']
vrátil sa true, spoliehal sa na isset()
pretože to bolo v každom prípade zlé rozhodnutie o kódovaní. Namiesto toho by bolo lepšie znova skontrolovať $_POST['active']
; tj .:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if ($_POST['active']) { echo 'post not active'; }
Pre prípady však kde je dôležité skontrolovať, či bola premenná skutočne nastavená (tj. aby sa rozlišovalo medzi premennou, ktorá nebola nastavená, a premennou, ktorá bola nastavená na null
), array_key_exists()
metóda je oveľa robustnejšie riešenie.
Prvý z vyššie uvedených dvoch príkladov by sme mohli napríklad prepísať takto:
$data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) { // do this if 'keyShouldBeSet' isn't set }
Navyše kombináciou array_key_exists()
s get_defined_vars()
, môžeme spoľahlivo skontrolovať, či bola alebo nebola nastavená premenná v aktuálnom rozsahu:
if (array_key_exists('varShouldBeSet', get_defined_vars())) { // variable $varShouldBeSet exists in current scope }
Zvážte tento útržok kódu:
class Config { private $values = []; public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];
Ak spustíte vyššie uvedený kód, získate nasledovné:
PHP Notice: Undefined index: test in /path/to/my/script.php on line 21
Čo je zle?
Problém je v tom, že vyššie uvedený kód zamieňa vracajúce sa polia odkazom s vracajúcimi sa poliami podľa hodnoty. Pokiaľ výslovne nepoviete PHP, aby vrátilo pole pomocou referencie (tj. Pomocou &
), PHP štandardne vráti pole „podľa hodnoty“. To znamená, že a kópia poľa sa vráti a preto volaná funkcia a volajúci nebudú mať prístup k rovnakej inštancii poľa.
Takže vyššie uvedená výzva na getValues()
vracia a kópia z $values
pole skôr ako odkaz na ňu. Z tohto dôvodu si preštudujme dva kľúčové riadky z vyššie uvedeného príkladu:
// 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'];
Jednou z možných opráv by bolo uložiť prvú kópiu $values
pole vrátené getValues()
a potom s touto kópiou následne pracovať; napr .:
$vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];
Tento kód bude fungovať dobre (tj. Bude vydávať test
bez generovania správy „nedefinovaného indexu“), ale v závislosti od toho, čo sa snažíte dosiahnuť, môže alebo nemusí byť tento prístup adekvátny. Vyššie uvedený kód konkrétne nezmení pôvodný $values
pole. Takže ak ty robiť ak chcete, aby vaše úpravy (napríklad pridanie prvku „test“) ovplyvnili pôvodné pole, musíte namiesto toho upraviť getValues()
funkcia vrátiť a odkaz do $values
samotné pole. To sa deje pridaním &
pred názvom funkcie, čím naznačuje, že by mala vrátiť referenciu; tj .:
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'];
Výstupom bude podľa očakávania test
.
Aby to však nebolo tak jednoduché, zvážte radšej nasledujúci útržok kódu:
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'];
Ak ste uhádli, že by to malo za následok rovnakú chybu „nedefinovaného indexu“ ako naša predchádzajúca array
napríklad si sa mýlil. V skutočnosti, toto kód bude fungovať dobre. Dôvod je ten, že na rozdiel od polí, PHP vždy odovzdáva objekty odkazom . (ArrayObject
je objekt SPL, ktorý úplne napodobňuje použitie polí, ale funguje ako objekt.)
Ako ukazujú tieto príklady, v PHP nie je vždy úplne zrejmé, či máte na mysli kópiu alebo odkaz. Je preto nevyhnutné porozumieť týmto predvoleným vlastnostiam (tj. Premenné a polia sa odovzdávajú hodnotou; objekty sa odovzdávajú odkazom) a tiež starostlivo skontrolovať dokumentáciu API pre funkciu, ktorú voláte, aby ste zistili, či vracia hodnotu, a kópia poľa, odkaz na pole alebo odkaz na objekt.
Všetko, čo bolo povedané, je dôležité poznamenať, že prax vrátenia odkazu na pole alebo ArrayObject
je všeobecne niečo, čomu by ste sa mali vyhnúť, pretože poskytuje volajúcemu možnosť upravovať súkromné údaje inštancie. Toto „letí do tváre“ zapuzdreniu. Namiesto toho je lepšie použiť starý štýl „getre“ a „setre“, napr .:
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'
Tento prístup dáva volajúcemu možnosť nastaviť alebo získať ľubovoľnú hodnotu v poli bez poskytnutia verejného prístupu k inak súkromnému $values
samotné pole.
Nie je nezvyčajné naraziť na niečo také, ak váš PHP nefunguje:
$models = []; foreach ($inputValues as $inputValue) { $models[] = $valueRepository->findByValue($inputValue); }
Aj keď tu nemusí byť absolútne nič zlé, ale ak sa budete riadiť logikou v kóde, môžete zistiť, že nevinne vyzerajúci hovor hore na $valueRepository->findByValue()
nakoniec vyústi do nejakého dotazu, napríklad:
$result = $connection->query('SELECT `x`,`y` FROM `values` WHERE `value`=' . $inputValue);
Výsledkom by bolo, že každá iterácia vyššie uvedenej slučky by mala za následok samostatný dopyt do databázy. Ak by ste napríklad do slučky zadali pole s 1 000 hodnotami, vygenerovalo by to 1 000 samostatných dotazov na zdroj! Ak sa takýto skript volá vo viacerých vláknach, mohlo by to systém potenciálne zastaviť.
Je preto kľúčové rozpoznať, kedy váš kód zadáva dotazy, a vždy, keď je to možné, zhromaždiť hodnoty a potom spustiť jeden dopyt, aby sa načítali všetky výsledky.
Jedným z príkladov pomerne bežného miesta, kde sa môžete stretnúť s neefektívnym uskutočňovaním dotazov (t. J. V slučke), je odoslanie formulára so zoznamom hodnôt (napríklad ID). Potom, aby sa získali úplné záznamové údaje pre každý z ID, bude kód prechádzať cez pole a robiť pre každý ID samostatný dotaz SQL. Často to bude vyzerať asi takto:
$data = []; foreach ($ids as $id) { $result = $connection->query('SELECT `x`, `y` FROM `values` WHERE `id` = ' . $id); $data[] = $result->fetch_row(); }
Ale to isté sa dá dosiahnuť oveľa efektívnejšie v a slobodný SQL dotaz takto:
$data = []; if (count($ids)) { $result = $connection->query('SELECT `x`, `y` FROM `values` WHERE `id` IN (' . implode(',', $ids)); while ($row = $result->fetch_row()) { $data[] = $row; } }
Je preto kľúčové rozpoznať, kedy sa váš kód priamo alebo nepriamo dotazuje. Kedykoľvek je to možné, zhromaždite hodnoty a potom spustite jeden dotaz, aby sa načítali všetky výsledky. Aj tu však musíme byť opatrní, čo nás vedie k našej ďalšej bežnej chybe PHP ...
Aj keď načítanie mnohých záznamov naraz je určite efektívnejšie ako spustenie jedného dotazu na načítanie každého riadku, takýto prístup môže potenciálne viesť k stavu „nedostatok pamäte“ v libmysqlclient
pri použití PHP mysql
predĺženie.
Na ukážku sa pozrime na testovaciu skrinku s obmedzenými zdrojmi (512 MB RAM), MySQL a php-cli
.
Spustíme databázovú tabuľku takto:
// 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 query($query); // write 2 million rows for ($row = 0; $row <2000000; $row++) { $query = 'INSERT INTO `test` VALUES ($row'; for ($col = 0; $col query($query); }
Dobre, poďme skontrolovať využitie zdrojov:
// connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); echo 'Before: ' . memory_get_peak_usage() . '
'; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1'); echo 'Limit 1: ' . memory_get_peak_usage() . '
'; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000'); echo 'Limit 10000: ' . memory_get_peak_usage() . '
';
Výkon:
Before: 224704 Limit 1: 224704 Limit 10000: 224704
V pohode Zdá sa, že dopyt je bezpečne riadený interne, čo sa týka zdrojov.
Pre istotu však ešte raz limit zvýšime a nastavíme ho na 100 000. Uh Oh. Keď to urobíme, dostaneme:
PHP Warning: mysqli::query(): (HY000/2013): Lost connection to MySQL server during query in /root/test.php on line 11
Čo sa stalo?
Problémom je spôsob, akým PHP mysql
modul funguje. Je to skutočne iba proxy server libmysqlclient
, ktorý vykonáva špinavú prácu. Keď je vybratá časť údajov, ide priamo do pamäte. Pretože táto pamäť nie je spravovaná správcom PHP, memory_get_peak_usage()
nezvýši využitie zdrojov, keď v dotaze zvyšujeme limit. To vedie k problémom, ako je ten, ktorý sme demonštrovali vyššie, kde sme podvedení k uspokojeniu s domnienkou, že správa pamäte je v poriadku. Ale v skutočnosti je naša správa pamäte vážne chybná a môžeme naraziť na problémy, ako je ten, ktorý je uvedený vyššie.
Vyššej falošnej falošnej ukážke sa môžete vyhnúť (aj keď sama o sebe nezlepší využitie vašej pamäte) namiesto toho pomocou mysqlnd
modul. mysqlnd
je zostavený ako natívne rozšírenie PHP a to robí použite správcu pamäte PHP.
Preto, ak spustíme vyššie uvedený test pomocou mysqlnd
namiesto mysql
získame oveľa realistickejší obraz o využití pamäte:
Before: 232048 Limit 1: 324952 Limit 10000: 32572912
A mimochodom, je to ešte horšie. Podľa dokumentácie PHP, mysql
používa dvakrát toľko zdrojov ako mysqlnd
na ukladanie údajov, takže pôvodný skript pomocou mysql
skutočne využilo ešte viac pamäte, ako je tu zobrazené (zhruba dvakrát toľko).
Ak sa chcete vyhnúť takýmto problémom, zvážte obmedzenie veľkosti svojich dotazov a použitie slučky s malým počtom iterácií; napr .:
$totalNumberToFetch = 10000; $portionSize = 100; for ($i = 0; $i query( 'SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize'); }
Keď vezmeme do úvahy aj túto chybu PHP, aj chyba # 4 vyššie si uvedomujeme, že existuje zdravá rovnováha, ktorú váš kód v ideálnom prípade musí dosiahnuť medzi tým, že vaše dotazy budú príliš podrobné a opakujúce sa na jednej strane, a naopak, keď budú vaše jednotlivé dotazy príliš veľké. Rovnako ako vo väčšine vecí v živote, aj tu je potrebná rovnováha; buď extrém nie je dobrý a môže spôsobiť problémy s nesprávnym fungovaním PHP.
V istom zmysle je to skutočne viac problém samotného PHP ako niečoho, na čo by ste narazili pri ladení PHP, ale nikdy to nebolo adekvátne vyriešené. Jadro PHP 6 malo byť upravené na Unicode, ale to bolo pozastavené, keď bol vývoj PHP 6 pozastavený ešte v roku 2010.
To však vývojára v žiadnom prípade nezbavuje správne podáva UTF-8 a vyhnúť sa chybnému predpokladu, že všetky reťazce budú nevyhnutne „obyčajný starý ASCII“. Kód, ktorý nedokáže správne spracovať reťazce iné ako ASCII, je notoricky známy tým, že predstavuje gnarly heisenbugs do vášho kódu. Dokonca jednoduché strlen($_POST['name'])
hovory by mohli spôsobiť problémy, ak by sa niekto s priezviskom ako „Schrödinger“ pokúsil prihlásiť do vášho systému.
Tu je malý kontrolný zoznam, aby ste sa vyhli problémom v kóde:
mb_*
namiesto starých reťazcových funkcií (uistite sa, že je vo vašom zostavení PHP zahrnuté rozšírenie „multibyte“).latin1
).json_encode()
prevádza symboly iné ako ASCII (napr. „Schrödinger“ sa stáva „Schr u00f6dinger“), ale serialize()
robí nie .Obzvlášť cenným zdrojom v tomto ohľade je UTF-8 Primer pre PHP a MySQL príspevok od Francisco Claria na tomto blogu.
$_POST
bude vždy obsahovať vaše údaje POSTNapriek svojmu názvu sa $_POST
pole nebude vždy obsahovať vaše údaje POST a dá sa ľahko nájsť prázdne. Aby sme tomu porozumeli, pozrime sa na príklad. Predpokladajme, že zadáme požiadavku na server pomocou jQuery.ajax()
volať nasledovne:
// js $.ajax({ url: 'http://my.site/some/path', method: 'post', data: JSON.stringify({a: 'a', b: 'b'}), contentType: 'application/json' });
(Mimochodom, všimnite si contentType: 'application/json'
tu. Údaje odosielame ako JSON, čo je pre API veľmi populárne. Je to predvolené nastavenie, napríklad pre zverejňovanie v AngularJS $http
služby .)
Na strane servera nášho príkladu jednoducho vypíšeme $_POST
pole:
// php var_dump($_POST);
Výsledkom bude prekvapivo:
array(0) { }
Prečo? Čo sa stalo s našim reťazcom JSON {a: 'a', b: 'b'}
?
Odpoveď je taká PHP analyzuje užitočné zaťaženie POST automaticky iba vtedy, keď má typ obsahu application/x-www-form-urlencoded
alebo multipart/form-data
. Dôvody sú historické - tieto dva typy obsahu boli v podstate jediné, ktoré sa používali pred rokmi, keď PHP $_POST
bol implementovaný. Takže pri akomkoľvek inom type obsahu (dokonca aj pri tých, ktoré sú dnes veľmi populárne, napríklad application/json
), PHP automaticky nenačíta užitočné zaťaženie POST.
Od $_POST
je superglobálny, ak ho prekonáme raz (najlepšie na začiatku nášho skriptu) bude upravená hodnota (t. j. vrátane užitočného zaťaženia POST) odkazovateľná v celom našom kóde. To je dôležité, pretože $_POST
sa bežne používa v rámci PHP a takmer vo všetkých vlastných skriptoch na extrakciu a transformáciu údajov žiadosti.
Napríklad pri spracovaní užitočného zaťaženia POST s typom obsahu application/json
musíme ručne analyzovať obsah požiadavky (t. J. Dekódovať údaje JSON) a prepísať $_POST
premenná takto:
// php $_POST = json_decode(file_get_contents('php://input'), true);
Potom, keď vypíšeme $_POST
pole, vidíme, že správne obsahuje užitočné zaťaženie POST; napr .:
array(2) { ['a']=> string(1) 'a' ['b']=> string(1) 'b' }
Pozrite sa na túto ukážku kódu a skúste hádať, čo sa bude tlačiť:
for ($c = 'a'; $c <= 'z'; $c++) { echo $c . '
'; }
Ak ste odpovedali „a“ až „z“, možno vás prekvapí, že ste sa mýlili.
Áno, bude tlačiť „a“ až „z“, ale potom bude tiež tlačte „aa“ až „yz“. Pozrime sa prečo.
V PHP neexistujú char
Dátový typ; iba string
je k dispozícii. Z tohto dôvodu zvyšujeme string
z
vo výťažkoch PHP aa
:
php> $c = 'z'; echo ++$c . '
'; aa
Napriek tomu veci ešte viac zamieňajú, aa
je lexikograficky menej než z
:
php> var_export((boolean)('aa' <'z')) . '
'; true
Preto vyššie uvedený vzorový kód vytlačí písmená a
až z
, ale potom tiež výtlačky aa
prostredníctvom yz
. Zastaví sa, keď dosiahne za
, čo je prvá hodnota, ktorú zaznamená, že je „väčšia ako“ z
:
php> var_export((boolean)('za' <'z')) . '
'; false
V takom prípade existuje jeden spôsob, ako správne slučka medzi hodnotami „a“ až „z“ v PHP:
for ($i = ord('a'); $i <= ord('z'); $i++) { echo chr($i) . '
'; }
Alebo alternatívne:
$letters = range('a', 'z'); for ($i = 0; $i Bežná chyba č. 9: Ignorovanie štandardov kódovania
Aj keď ignorovanie štandardov kódovania priamo nevedie k nutnosti ladenia kódu PHP, stále je to pravdepodobne jedna z najdôležitejších vecí, o ktorej sa tu bude diskutovať.
Ignorovanie štandardov kódovania môže spôsobiť v projekte celý rad problémov. V lepšom prípade to vedie k nekonzistentnému kódu (pretože každý vývojár si „robí svoje veci“). Ale v najhoršom prípade produkuje kód PHP, ktorý nefunguje alebo ktorého navigácia je zložitá (niekedy takmer nemožná), takže je nesmierne ťažké ho odladiť, vylepšiť alebo udržiavať. A to znamená zníženie produktivity vášho tímu vrátane množstva zbytočného (alebo prinajmenšom zbytočného) úsilia.
Našťastie pre vývojárov PHP existuje odporúčanie štandardov PHP (PSR), ktoré obsahuje týchto päť štandardov:
- PSR-0 : Automatické načítanie štandard
- PSR-1 : Základný štandard kódovania
- PSR-2 : Sprievodca štýlom kódovania
- PSR-3 : Logger Interface
- PSR-4 : Autoloader
PSR bolo pôvodne vytvorené na základe vstupov od správcov najuznávanejších platforiem na trhu. Zend, Drupal, Symfony, Joomla a iné prispeli k týmto štandardom a v súčasnosti sa nimi riadia. Dokonca aj PEAR, ktorý sa roky predtým snažil byť štandardom, sa teraz zúčastňuje PSR.
V istom zmysle takmer nezáleží na tom, aký je váš štandard kódovania, pokiaľ sa na norme dohodnete a budete sa jej držať, ale dodržiavanie PSR je všeobecne dobrý nápad, pokiaľ nemáte na svojom projekte nejaký pádny dôvod, prečo by ste mali postupovať inak . Stále viac tímov a projektov je v súlade s PSR. Spoločnosť Tt je v tomto okamihu jednoznačne uznaná ako „štandard“ väčšiny vývojárov PHP, takže jej použitie pomôže zabezpečiť, aby noví vývojári boli oboznámení a vyhovovali vášmu štandardu kódovania, keď sa stanú členom vášho tímu.
Bežná chyba č. 10: Zneužitie empty()
Niektorí vývojári PHP radi používajú empty()
na boolovské šeky takmer na všetko. Existujú prípady, keď to môže viesť k zámene.
Najskôr sa vráťme k poliam a ArrayObject
inštancie (ktoré napodobňujú polia). Vzhľadom na ich podobnosť je ľahké predpokladať, že polia a ArrayObject
inštancie sa budú správať identicky. To sa však ukazuje ako nebezpečný predpoklad. Napríklad v 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?
A aby toho nebolo málo, výsledky by boli pred PHP 5.0 odlišné:
// Prior to PHP 5.0: $array = []; var_dump(empty($array)); // outputs bool(false) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false)
Tento prístup je bohužiaľ dosť populárny. Napríklad toto je cesta ZendDbTableGateway
of Zend Framework 2 returns data when calling current()
dňa TableGateway::select()
výsledok, ako navrhuje doktor. S takýmito údajmi sa vývojár môže ľahko stať obeťou tejto chyby.
Ak sa chcete vyhnúť týmto problémom, lepším prístupom ku kontrole štruktúr prázdneho poľa je použitie 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)
A mimochodom, keďže PHP odovzdáva 0
do false
, count()
možno použiť aj v rámci if ()
podmienky na kontrolu prázdnych polí. Za zmienku tiež stojí, že v PHP count()
je konštantná zložitosť (O(1)
prevádzka) na poliach, čo ešte viac objasňuje, že je to správna voľba.
Ďalším príkladom, keď empty()
môže byť nebezpečné, keď ho skombinujete s funkciou magickej triedy __get()
. Poďme definovať dve triedy a mať test
majetok v oboch.
Najprv si zadefinujeme Regular
trieda, ktorá obsahuje test
ako bežná vlastnosť:
komu podáva správu CFO
class Regular { public $test = 'value'; }
Potom definujme Magic
trieda, ktorá používa kúzlo __get()
operátorovi prístup k jeho test
nehnuteľnosť:
class Magic { private $values = ['test' => 'value']; public function __get($key) { if (isset($this->values[$key])) { return $this->values[$key]; } } }
Dobre, pozrime sa, čo sa stane, keď sa pokúsime získať prístup k test
majetok každej z týchto tried:
$regular = new Regular(); var_dump($regular->test); // outputs string(4) 'value' $magic = new Magic(); var_dump($magic->test); // outputs string(4) 'value'
Zatiaľ v poriadku.
Teraz sa však pozrime, čo sa stane, keď zavoláme empty()
na každom z nich:
var_dump(empty($regular->test)); // outputs bool(false) var_dump(empty($magic->test)); // outputs bool(true)
Uf. Ak sa teda spoliehame na empty()
, môžeme byť mylne presvedčení, že test
majetok $magic
je prázdny, zatiaľ čo v skutočnosti je nastavený na 'value'
.
Bohužiaľ, ak trieda používa kúzlo __get()
funkcia na získanie hodnoty vlastnosti, neexistuje spoľahlivý spôsob, ako skontrolovať, či je hodnota vlastnosti prázdna alebo nie. Mimo rozsahu triedy môžete skontrolovať iba to, či null
hodnota sa vráti a to nevyhnutne neznamená, že nie je nastavený zodpovedajúci kľúč, pretože v skutočnosti je mohol bol nastaviť do null
.
Naopak, ak sa pokúsime odkazovať na neexistujúcu vlastnosť a Regular
inštancie triedy, dostaneme oznámenie podobné tomuto:
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
Hlavným bodom je teda to, že empty()
Táto metóda by sa mala používať opatrne, pretože môže spôsobiť mätúce - alebo dokonca potenciálne zavádzajúce - výsledky, ak nie je opatrný.
Zabaliť
Ľahké použitie PHP môže vývojárov priviesť k falošnému pocitu pohodlia, vďaka čomu môžu zostať zraniteľní voči zdĺhavému ladeniu PHP kvôli niektorým nuansám a zvláštnostiam jazyka. To môže mať za následok nefunkčnosť PHP a problémy, ako sú tie, ktoré sú tu opísané.
Jazyk PHP sa počas svojej 20-ročnej histórie významne vyvinul. Oboznámiť sa s jeho jemnosťami je užitočné úsilie, pretože pomôže zabezpečiť, aby softvér, ktorý vyrábate je škálovateľnejšia, robustnejšia a udržiavateľnejšia.