PHP muudab veebipõhise süsteemi ehitamise suhteliselt lihtsaks, mis on suur osa selle populaarsusest. Kuid hoolimata selle kasutusmugavusest, PHP on arenenud üsna keerukaks keeleks paljude raamistike, nüansside ja peensustega, mis võivad arendajaid hammustada, mis põhjustab tundide pikkust juukseid tõmbavat silumist. Selles artiklis tuuakse välja kümme levinumat viga PHP arendajad tuleb hoiduda.
foreach
silmuseidKas pole kindel, kuidas PHP-s foreachi silmusid kasutada? Viiteid rakenduses foreach
tsüklid võivad olla kasulikud, kui soovite opereerida massiivi kõiki elemente, mida te kordate. Näiteks:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr is now array(2, 4, 6, 8)
Probleem on selles, et kui te pole ettevaatlik, võivad sellel olla ka mõned soovimatud kõrvaltoimed ja tagajärjed. Täpsemalt öeldes ülaltoodud näites pärast koodi täitmist $value
jääb reguleerimisalasse ja sisaldab viidet massiivi viimasele elemendile. Järgmised toimingud, mis hõlmavad $value
võis seega tahtmatult lõpuks massiivi viimast elementi modifitseerida.
Peaasi on meeles pidada, et foreach
ei loo ulatust. Seega $value
ülaltoodud näites on a viide skripti ülaosas. Igal iteratsioonil foreach
määrab viite $array
järgmise elemendi osutamiseks. Pärast tsükli lõppu $value
osutab endiselt $array
viimasele elemendile ja jääb reguleerimisalasse.
Siin on näide sellistest kõrvalehoidvatest ja segavatest vigadest, mis see võib kaasa tuua:
$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), '
';
Ülaltoodud kood väljastab järgmise:
1,2,3 1,2,3 1,2,2
Ei, see pole kirjaviga. Viimase rea viimane väärtus on tõepoolest 2, mitte 3.
Miks?
Pärast esimese foreach
läbimist silmus, $array
jääb muutumatuks, kuid nagu eespool selgitatud, $value
on jäetud rippuvana viidetena $array
viimasele elemendile (kuna foreach
silmusele pääses $value
juurde viide ).
Selle tulemusena, kui läbime teise foreach
silmus, näib juhtuvat 'imelikke asju'. Täpsemalt, kuna $value
on nüüd juurdepääs väärtuse (st koopia ), foreach
koopiad iga järjestikune $array
element sisse $value
aasa igas etapis. Selle tulemusel toimub sekundi foreach
iga sammu ajal järgmine silmus:
$array[0]
(st “1”) $value
(mis on viide $array[2]
), seega $array[2]
nüüd võrdub 1. Seega $array
sisaldab nüüd [1, 2, 1].$array[1]
(s.t “2”) $value
(mis on viide $array[2]
), seega $array[2]
nüüd võrdub 2. Seega $array
sisaldab nüüd [1, 2, 2].$array[2]
(mis nüüd võrdub numbriga 2) $value
(mis on viide $array[2]
), seega $array[2]
ikka võrdne 2. Nii et $array
sisaldab nüüd [1, 2, 2].Et saada siiski kasu foreach
-i viidete kasutamisest tsüklitesse ilma seda tüüpi probleemide ohtu seadmata, helistage unset()
muutuja juures kohe pärast foreach
loop, viite eemaldamiseks; nt:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } unset($value); // $value no longer references $arr[3]
isset()
käitumineVaatamata oma nimele isset()
mitte ainult tagastab vale, kui üksust pole olemas, vaid tagastab ka false
jaoks null
väärtused .
See käitumine on problemaatilisem, kui võib alguses tunduda ja on tavaline probleemide allikas.
Mõelge järgmisele:
$data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) { // do something here if 'keyShouldBeSet' is not set }
Eeldatavalt soovis selle koodi autor kontrollida, kas keyShouldBeSet
määrati $data
. Kuid nagu arutatud, isset($data['keyShouldBeSet'])
tahe ka tagastama vale, kui $data['keyShouldBeSet']
oli määratud, kuid määrati null
. Nii et ülaltoodud loogika on vigane.
Siin on veel üks näide:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if (!isset($postData)) { echo 'post not active'; }
Ülaltoodud kood eeldab, et kui $_POST['active']
tagastab true
, siis postData
tingimata seatakse ja seetõttu isset($postData)
naaseb true
. Nii et vastupidi, ülaltoodud kood eeldab, et ainult viisil isset($postData)
naaseb false
on kui $_POST['active']
tagastatud false
samuti.
Mitte.
Nagu selgitatud, isset($postData)
naaseb ka false
kui $postData
seati väärtuseks null
. Seega on võimalik isset($postData)
jaoks tagasi pöörduma false
isegi kui $_POST['active']
tagastatud true
. Nii et jällegi on ülaltoodud loogika vigane.
Ja muide, kõrvalpunktina, kui ülaltoodud koodis sooviti tõesti uuesti kontrollida, kas $_POST['active']
tagastati tõene, tuginedes isset()
sest see oli igal juhul kehv kodeerimisotsus. Selle asemel oleks olnud parem lihtsalt uuesti kontrollida $_POST['active']
; st .:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if ($_POST['active']) { echo 'post not active'; }
Juhtumite jaoks, aga kus on on oluline kontrollida, kas muutuja on tõesti määratud (st selleks, et eristada muutumatut, mida ei määratud, ja muutujaks, mille väärtuseks määrati null
), array_key_exists()
meetod on palju kindlam lahendus.
Näiteks võiksime ülaltoodud näidetest esimese kirjutada järgmiselt:
$data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) { // do this if 'keyShouldBeSet' isn't set }
Pealegi ühendades array_key_exists()
koos get_defined_vars()
, saame usaldusväärselt kontrollida, kas praeguses reguleerimisalas olev muutuja on määratud või mitte:
if (array_key_exists('varShouldBeSet', get_defined_vars())) { // variable $varShouldBeSet exists in current scope }
Mõelge sellele koodilõigule:
class Config { private $values = []; public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];
Kui käivitate ülaltoodud koodi, näete järgmist:
PHP Notice: Undefined index: test in /path/to/my/script.php on line 21
Mis viga?
Küsimus on selles, et ülaltoodud kood ajab segamini massiivid, mis tagastavad, väärtusega tagastatavate massiividega. Kui te ei ütle PHP-le selgesõnaliselt massiivi viitega tagastamist (st kasutades &
), tagastab PHP massiivi vaikimisi väärtuse järgi. See tähendab, et a koopia massiivi väärtus tagastatakse ja seetõttu ei pääse kutsutud funktsioon ja helistaja massiivi samale eksemplarile juurde.
Nii et ülaltoodud kõne getValues()
tagastab a koopia $values
pigem massiiv kui viide sellele. Seda silmas pidades vaatame üle kaks ülaltoodud näite võtmerida:
// 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'];
Üks võimalik lahendus oleks $values
esimese eksemplari salvestamine massiivi tagastas getValues()
ja seejärel seda koopiat hiljem opereerida; nt:
$vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];
See kood töötab hästi (st väljastab test
ilma määratlemata indeksi sõnumit genereerimata), kuid sõltuvalt sellest, mida proovite saavutada, võib see lähenemine olla piisav või mitte. Eelkõige ei muuda ülaltoodud kood algset $values
massiiv. Nii et kui sina tegema kui soovite, et teie muudatused (näiteks elemendi „test” lisamine) mõjutaksid algset massiivi, peate selle asemel muutma getValues()
funktsioon tagastamiseks a viide kuni $values
massiiv ise. Selleks lisatakse &
enne funktsiooni nime, näidates sellega, et see peaks tagastama viite; st .:
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'];
Selle väljund on ootuspäraselt test
Kuid segasemaks muutmiseks kaaluge selle asemel järgmist koodilõiku:
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'];
Kui arvasite, et selle tulemuseks oleks sama viga määratlemata indeksis kui meie varasem array
näiteks sa eksisid. Tegelikult, seda kood töötab suurepäraselt. Põhjuseks on see, et erinevalt massiividest PHP edastab objektid alati viitena . (ArrayObject
on SPL-objekt, mis jäljendab täielikult massiivide kasutamist, kuid töötab objektina.)
Nagu need näited näitavad, pole PHP-s alati täiesti ilmne, kas tegemist on koopia või viitega. Seetõttu on oluline mõista neid vaikekäitumisi (st muutujad ja massiivid edastatakse väärtuse järgi; objektid edastatakse viitena) ning hoolikalt kontrollida ka funktsiooni, millele helistate, API dokumentatsiooni, et näha, kas see tagastab väärtuse, massiivi koopia, viide massiivile või viide objektile.
Kõike seda öeldes on oluline märkida, et massiivi või ArrayObject
viite tagastamise tava on üldiselt miski, mida tuleks vältida, kuna see annab helistajale võimaluse eksemplari privaatset teavet muuta. See “lendab vastu nägu” kapseldamisele. Selle asemel on parem kasutada vanas stiilis 'gettereid' ja 'setereid', nt:
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'
See lähenemine annab helistajale võimaluse seada või saada massiivi mis tahes väärtust, pakkumata avalikku juurdepääsu muidu privaatsele $values
massiiv ise.
Kui PHP ei tööta, ei pruugi midagi sellist juhtuda:
$models = []; foreach ($inputValues as $inputValue) { $models[] = $valueRepository->findByValue($inputValue); }
Kuigi siin ei pruugi absoluutselt midagi viga olla, kuid kui järgite koodis olevat loogikat, võite leida, et süütu välimusega üleskutse ülaltähele $valueRepository->findByValue()
tulemuseks on lõppkokkuvõttes mingisugune päring, näiteks:
$result = $connection->query('SELECT `x`,`y` FROM `values` WHERE `value`=' . $inputValue);
Selle tulemusena tooks iga ülaltoodud tsükli iteratsioon andmebaasi eraldi päringu. Nii et kui lisate tsüklile näiteks 1000 väärtuse massiivi, genereeriks see ressursile 1000 eraldi päringut! Kui sellist skripti nimetatakse mitmeks lõimeks, võib see süsteemi lihvimise peatada.
Seetõttu on ülitähtis tuvastada, millal teie kood päringuid esitab, ja kui võimalik, koguge väärtused ja käivitage seejärel üks päring kõigi tulemuste toomiseks.
Üks näide üsna tavalisest kohast, kus päringuid tehakse ebaefektiivselt (st silmusena), on see, kui vorm postitatakse koos väärtuste loendiga (näiteks ID-d). Seejärel kõigi ID-de täielike kirjeandmete hankimiseks viib kood massiivi läbi ja teeb iga ID jaoks eraldi SQL-päringu. See näeb sageli välja umbes selline:
$data = []; foreach ($ids as $id) { $result = $connection->query('SELECT `x`, `y` FROM `values` WHERE `id` = ' . $id); $data[] = $result->fetch_row(); }
Kuid sama saab a-s saavutada palju tõhusamalt üksik SQL-päring järgmiselt:
$data = []; if (count($ids)) { $result = $connection->query('SELECT `x`, `y` FROM `values` WHERE `id` IN (' . implode(',', $ids)); while ($row = $result->fetch_row()) { $data[] = $row; } }
Seetõttu on ülioluline tuvastada, kas teie koodiga otseselt või kaudselt päringuid esitatakse. Kui vähegi võimalik, koguge väärtused ja käivitage siis üks päring kõigi tulemuste toomiseks. Kuid ka seal tuleb olla ettevaatlik, mis viib meid järgmise levinud PHP veani ...
Kuigi paljude kirjete korraga toomine on kindlasti tõhusam kui ühe rea jaoks ühe päringu käivitamine, võib selline lähenemine viia libmysqlclient
PHP-de kasutamisel mysql
pikendamine.
Selle demonstreerimiseks vaatame piiratud ressurssidega (512 MB RAM), MySQL-i ja php-cli
Käivitame järgmise andmebaasitabeli:
// 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); }
OK, kontrollime nüüd ressursside kasutamist:
// 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äljund:
Before: 224704 Limit 1: 224704 Limit 10000: 224704
Lahe. Paistab, et päringut hallatakse sisemiselt turvaliselt ressursside osas.
Lihtsalt selleks, et olla kindel, suurendame limiiti veel kord ja määrame selle väärtuseks 100 000. Oeh-oi. Seda tehes saame:
PHP Warning: mysqli::query(): (HY000/2013): Lost connection to MySQL server during query in /root/test.php on line 11
Mis juhtus?
Küsimus on selles, kuidas PHP-d mysql
moodul töötab. See on tegelikult vaid libmysqlclient
puhverserver, mis teeb musta töö. Kui osa andmetest on valitud, läheb see otse mällu. Kuna seda mälu ei halda PHP haldur, memory_get_peak_usage()
ei näita ressursside kasutamise kasvu, kui oleme oma päringus limiiti ületanud. See toob kaasa selliseid probleeme nagu eespool näidatud, kus meid petetakse leplikuks, arvates, et meie mäluhaldus on korras. Kuid tegelikult on meie mäluhaldus tõsiselt puudulik ja võime kogeda selliseid probleeme nagu eespool näidatud.
Võite vähemalt vältida ülaltoodud pea võltsimist (kuigi see ei paranda teie mälu kasutamist), kasutades selle asemel mysqlnd
moodul. mysqlnd
on koostatud loodusliku PHP laiendina ja see teeb kasutage PHP mäluhaldurit.
Seega, kui käivitame ülaltoodud testi, kasutades mysqlnd
asemel mysql
, saame palju realistlikuma pildi oma mälukasutusest:
Before: 232048 Limit 1: 324952 Limit 10000: 32572912
Ja see on muuseas veelgi hullem. PHP dokumentatsiooni kohaselt mysql
kasutab kaks korda rohkem ressursse kui mysqlnd
andmete salvestamiseks, nii et algne skript kasutab mysql
kasutas tõesti isegi rohkem mälu, kui siin näidatud (umbes kaks korda rohkem).
Selliste probleemide vältimiseks kaaluge oma päringute suuruse piiramist ja väikese arvu kordustega tsükli kasutamist; nt:
$totalNumberToFetch = 10000; $portionSize = 100; for ($i = 0; $i query( 'SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize'); }
Kui arvestada nii seda PHP viga kui ka viga nr 4 eespool mõistame, et on olemas hea tasakaal, mille teie kood peaks ideaalis saavutama - ühelt poolt on teie päringud liiga detailsed ja korduvad ning kõik teie üksikud päringud on liiga suured. Nagu elus enamiku asjadega tõsi, on vaja tasakaalu; kumbki äärmus pole hea ja võib põhjustada probleeme, kui PHP ei tööta korralikult.
Mõnes mõttes on see PHP-s endas pigem probleem kui miski, millega PHP silumisel silmitsi seisaksite, kuid sellega pole kunagi piisavalt tegeletud. PHP 6 tuum pidi olema Unicode'i teadlik, kuid see jäi ootele, kui PHP 6 arendamine 2010. aastal peatati.
Kuid see ei vabasta arendajat mingil juhul UTF-8 korralikult kätte andes ja vältides ekslikku oletust, et kõik stringid on tingimata „tavalised vanad ASCII-d”. Kood, mis ei suuda mitte-ASCII stringe korralikult käsitseda, on gnarly juurutamise poolest tuntud heisenbugs oma koodi. Isegi lihtne strlen($_POST['name'])
kõned võivad põhjustada probleeme, kui keegi perekonnanimega nagu „Schrödinger” proovib teie süsteemi sisse logida.
Selliste probleemide vältimiseks koodis on väike kontroll-loend:
mb_*
funktsioone vana stringifunktsioonide asemel (veenduge, et PHP koosseisus oleks laiendus “multibyte”).latin1
).json_encode()
teisendab mitte-ASCII sümbolid (nt „Schrödinger” muutub „Schr u00f6dinger”), kuid serialize()
teeb mitte .Selles osas on eriti väärtuslik ressurss UTF-8 krunt PHP ja MySQL jaoks postitama Francisco Claria siin blogis.
$_POST
sisaldab alati teie POST-andmeidVaatamata nimele on $_POST
massiiv ei sisalda alati teie POST-andmeid ja selle saab hõlpsasti tühjana leida. Selle mõistmiseks vaatame näite. Oletame, et esitame serveri päringu tähisega jQuery.ajax()
helistage järgmiselt:
// js $.ajax({ url: 'http://my.site/some/path', method: 'post', data: JSON.stringify({a: 'a', b: 'b'}), contentType: 'application/json' });
(Muide, märkige siia contentType: 'application/json'
. Saadame andmed JSON-iga, mis on API-de jaoks üsna populaarne. See on vaikimisi näiteks postitamiseks NurgelineJS $http
teenus .)
Meie näite serveri poolel viskame lihtsalt $_POST
massiiv:
// php var_dump($_POST);
Üllatuslikult on tulemus järgmine:
array(0) { }
Miks? Mis juhtus meie JSON-stringiga {a: 'a', b: 'b'}
?
Vastus on selline PHP sõelub POST-i koormuse automaatselt ainult siis, kui selle sisutüüp on application/x-www-form-urlencoded
või multipart/form-data
. Selle põhjused on ajaloolised - need kaks sisutüüpi olid sisuliselt ainsad, mida kasutati aastaid tagasi, kui PHP-d $_POST
rakendati. Nii et mis tahes muu sisutüübi puhul (isegi tänapäeval üsna populaarsed, näiteks application/json
), ei lae PHP POST-i kasulikku koormust automaatselt.
Kuna $_POST
on ülimaailm, kui me selle tühistame üks kord (eelistatavalt meie skripti alguses) on muudetud väärtus (s.h. ka POST-i kasulik koormus) kogu meie koodis viidatav. See on oluline, kuna $_POST
kasutatakse tavaliselt PHP-raamistikes ja peaaegu kõigis kohandatud skriptides päringute andmete eraldamiseks ja teisendamiseks.
Nii et näiteks POST-i kasuliku koormuse töötlemisel sisutüübiga application/json
peame päringu sisu käsitsi sõeluma (st JSON-andmed dekodeerima) ja $_POST
muutuja järgmiselt:
// php $_POST = json_decode(file_get_contents('php://input'), true);
Siis, kui viskame $_POST
massiivi, näeme, et see sisaldab õigesti POST-i kasulikku koormust; nt:
array(2) { ['a']=> string(1) 'a' ['b']=> string(1) 'b' }
Vaadake seda koodinäidist ja proovige arvata, mida see trükib:
for ($c = 'a'; $c <= 'z'; $c++) { echo $c . '
'; }
Kui vastasite a-st kuni z-ni, võite olla üllatunud, kui teate, et eksite.
Jah, see trükib tähe „a” kuni „z”, kuid siis saab ka printida ‘aa’ läbi ‘yz’. Vaatame, miks.
PHP-s pole char
andmetüüp; ainult string
on olemas. Seda silmas pidades suurendades string
z
PHP saagis aa
:
php> $c = 'z'; echo ++$c . '
'; aa
Et veelgi segadusse ajada, aa
on leksikograafiliselt vähem kui z
:
php> var_export((boolean)('aa' <'z')) . '
'; true
Sellepärast trükitakse ülaltoodud näidiskoodiga tähed a
läbi z
, kuid siis ka prindib aa
läbi yz
. See peatub, kui jõuab väärtuseni za
, mis on esimene väärtus, millega ta kokku puutub | suurem kui | z
php> var_export((boolean)('za' <'z')) . '
'; false
Sellisel juhul on siin üks võimalus korralikult loopige PHP-s väärtused a – z läbi:
for ($i = ord('a'); $i <= ord('z'); $i++) { echo chr($i) . '
'; }
Või alternatiivina:
$letters = range('a', 'z'); for ($i = 0; $i Üldine viga nr 9: kodeerimisstandardite eiramine
Kuigi kodeerimisstandardite eiramine ei too otseselt kaasa vajadust PHP-koodi silumiseks, on see siiski ilmselt üks olulisemaid asju, mida siin arutada.
Kodeerimisstandardite eiramine võib projektis põhjustada terve rea probleeme. Parimal juhul on selle tulemuseks kood, mis on ebajärjekindel (kuna iga arendaja teeb 'oma asju'). Halvimal juhul toodab see PHP-koodi, mis ei tööta või mille navigeerimine võib olla keeruline (mõnikord peaaegu võimatu), muutes silumise, täiustamise, hooldamise äärmiselt keeruliseks. Ja see tähendab teie meeskonna tootlikkuse vähenemist, sealhulgas palju raisatud (või vähemalt mittevajalikke) pingutusi.
PHP arendajate õnneks on olemas PHP standardite soovitus (PSR), mis koosneb järgmisest viiest standardist:
- PSR-0 : Autolaadimise standard
- PSR-1 : Kodeerimise põhistandard
- PSR-2 : Kodeerimisstiili juhend
- PSR-3 : Logija liides
- PSR-4 : Autoloader
PSR loodi algselt turul kõige tuntumate platvormide hooldajate sisendite põhjal. Zend, Drupal, Symfony, Joomla ja teised panustas nendesse standarditesse ja järgib neid nüüd. Isegi PEAR, mis püüdis enne seda aastaid standardiks olla, osaleb nüüd PSR-is.
Mõnes mõttes pole peaaegu üldse oluline, milline on teie kodeerimisstandard, kui nõustute standardis ja järgite seda, kuid PSR-i järgimine on üldiselt hea mõte, kui teil pole oma projekti jaoks mingeid kaalukaid põhjusi teisiti teha . Üha rohkem meeskondi ja projekte vastab PSR-ile. Tt tunnistab enamus PHP arendajaid selles osas kindlasti 'standardina', nii et selle kasutamine aitab tagada, et uued arendajad on teie meeskonnaga liitumisel teie kodeerimisstandardiga tuttavad ja rahul.
Üldine viga nr 10: väärkasutamine empty()
Mõnele PHP arendajale meeldib kasutada empty()
tõeväärtuse kontrollimiseks peaaegu kõike. Siiski on juhtumeid, kus see võib põhjustada segadust.
Kõigepealt tuleme tagasi massiivide juurde ja ArrayObject
eksemplarid (mis jäljendavad massiive). Arvestades nende sarnasust, on lihtne eeldada, et massiivid ja ArrayObject
juhtumid käituvad identselt. See osutub siiski ohtlikuks eelduseks. Näiteks PHP 5.0-s:
// 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?
Ja mis veelgi hullem, oleks tulemused enne PHP 5.0 olnud erinevad:
// Prior to PHP 5.0: $array = []; var_dump(empty($array)); // outputs bool(false) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false)
Selline lähenemine on kahjuks üsna populaarne. Näiteks see on viis ZendDbTableGateway
of Zend Framework 2 tagastab current()
helistamisel andmed sisse TableGateway::select()
tulemus, nagu soovitab doktor. Arendaja võib selliste andmetega kergesti selle vea ohvriks sattuda.
Nende probleemide vältimiseks on parem lähenemine tühjade massiivistruktuuride kontrollimiseks kasutada 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)
Ja muide, kuna PHP mängib 0
kuni false
, count()
saab kasutada ka if ()
tingimused tühjade massiivide kontrollimiseks. Samuti väärib märkimist, et PHP-s count()
on massiivide pidev keerukus (O(1)
operatsioon), mis teeb veelgi selgemaks, et see on õige valik.
Teine näide, kui empty()
võib olla ohtlik, kui kombineerida seda maagiaklassi funktsiooniga __get()
. Määratleme kaks klassi ja omame test
vara mõlemas.
Kõigepealt määratleme Regular
klass, mis sisaldab test
tavalise omadusena:
class Regular { public $test = 'value'; }
Seejärel määratleme Magic
klass, mis kasutab maagiat __get()
operaator juurdepääsu oma test
vara:
class Magic { private $values = ['test' => 'value']; public function __get($key) { if (isset($this->values[$key])) { return $this->values[$key]; } } }
Olgu, vaatame nüüd, mis juhtub, kui proovime juurde pääseda test
nende klasside omadus:
$regular = new Regular(); var_dump($regular->test); // outputs string(4) 'value' $magic = new Magic(); var_dump($magic->test); // outputs string(4) 'value'
Siiani korras.
Kuid nüüd vaatame, mis juhtub, kui helistame empty()
kõigil neist:
var_dump(empty($regular->test)); // outputs bool(false) var_dump(empty($magic->test)); // outputs bool(true)
Uhh. Nii et kui loodame empty()
-le, võime meid eksitada uskudes, et test
vara $magic
on tühi, samas kui tegelikult on see seatud väärtusele 'value'
.
Kahjuks, kui klass kasutab maagiat __get()
funktsiooni kinnisvara väärtuse hankimiseks pole lollikindlat viisi kontrollida, kas see atribuudi väärtus on tühi või mitte. Väljaspool klassi ulatust saate tõesti kontrollida ainult seda, kas null
väärtus tagastatakse ja see ei tähenda tingimata, et vastavat võtit pole määratud, kuna see tegelikult on võiks on olnud seatud kuni null
.
Seevastu, kui proovime viidata Regular
olematule omadusele klassi eksemplari, saame teate, mis sarnaneb järgmisega:
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
Nii et siin on peamine mõte see, et empty()
meetodit tuleks kasutada ettevaatlikult, kuna see võib anda segadust tekitavaid või isegi potentsiaalselt eksitavaid tulemusi, kui see pole ettevaatlik.
Pakkima
PHP kasutusmugavus võib viia arendajad valesse mugavustundesse, jättes end keele mõningate nüansside ja eripärade tõttu haavatavaks PHP pikale silumisele. Selle tulemuseks võib olla PHP mittetoimimine ja sellised probleemid nagu siin kirjeldatud.
PHP keel on oma 20-aastase ajaloo jooksul märkimisväärselt arenenud. Selle peensustega tutvumine on väärt ettevõtmine, sest see aitab tagada, et teie toodetud tarkvara on skaleeritavam, jõulisem ja hooldatavam.