PHP: n ansiosta verkkopohjaisen järjestelmän rakentaminen on suhteellisen helppoa, mikä on suuri syy sen suosioon. Mutta sen helppokäyttöisyys huolimatta PHP on kehittynyt melko hienostuneeksi kieleksi monilla kehyksillä, vivahteilla ja hienovaraisuuksilla, jotka voivat puristaa kehittäjiä, mikä johtaa tuntien ajan hiuksia vetävään virheenkorjaukseen. Tässä artikkelissa tuodaan esiin kymmenen yleisintä virhettä PHP-kehittäjät täytyy varoa.
foreach
: n jälkeen silmukatEtkö ole varma, kuinka foreach-silmukoita käytetään PHP: ssä? Viitteiden käyttäminen foreach
silmukat voivat olla hyödyllisiä, jos haluat käyttää kutakin matriisin elementtiä, jota iteroit. Esimerkiksi:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr is now array(2, 4, 6, 8)
Ongelmana on, että jos et ole varovainen, tällä voi olla myös ei-toivottuja sivuvaikutuksia ja seurauksia. Erityisesti yllä olevassa esimerkissä koodin suorittamisen jälkeen $value
jäävät soveltamisalaan ja viittaavat taulukon viimeiseen elementtiin. Seuraavat operaatiot, joihin liittyy $value
voi siksi tahattomasti päätyä modifioimaan matriisin viimeistä elementtiä.
Tärkeintä on muistaa, että foreach
ei luo laajuutta. Siten $value
yllä olevassa esimerkissä on a viite komentosarjan ylimmässä laajuudessa. Jokaisella iteraatiolla foreach
asettaa viitteen osoittamaan $array
seuraavan elementin. Sen jälkeen, kun silmukka on valmis, $value
viittaa edelleen $array
: n viimeiseen elementtiin ja pysyy soveltamisalalla.
Tässä on esimerkki sellaisista välttelevistä ja hämmentävistä virheistä, joihin tämä voi johtaa:
$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), '
';
Yllä oleva koodi tuottaa seuraavat:
1,2,3 1,2,3 1,2,2
Ei, se ei ole kirjoitusvirhe. Viimeisen rivin viimeinen arvo on todellakin 2, ei 3.
Miksi?
Ensimmäisen foreach
silmukka, $array
pysyy muuttumattomana, mutta kuten yllä selitettiin, $value
jätetään roikkuvana viitteenä $array
-sivun viimeiseen elementtiin (koska foreach
-silmukka käyttää $value
viite ).
Tämän seurauksena, kun käymme läpi toisen foreach
”outoja juttuja” näyttää tapahtuvan. Erityisesti, koska $value
on nyt käytössä arvon perusteella (ts kopio ), foreach
kopioita kukin peräkkäinen $array
elementti osaksi $value
silmukan jokaisessa vaiheessa. Seurauksena on, mitä tapahtuu toisen foreach
-vaiheen jokaisessa vaiheessa silmukka:
$array[0]
(eli '1') $value
: ksi (mikä viittaa $array[2]
), joten $array[2]
nyt yhtä kuin 1. Joten $array
sisältää nyt [1, 2, 1].$array[1]
(eli '2') $value
: ksi (mikä viittaa $array[2]
), joten $array[2]
nyt yhtä kuin 2. Joten $array
sisältää nyt [1, 2, 2].$array[2]
(joka on nyt yhtä suuri kuin ”2”) $value
(mikä viittaa $array[2]
), joten $array[2]
on edelleen sama kuin 2. Joten $array
sisältää nyt [1, 2, 2].Hyödynnä silti viitteiden käyttö foreach
-sivulla silmukoita vaarantamatta tällaisten ongelmien riskiä, soita unset()
muuttujassa heti foreach
: n jälkeen silmukka, viitteen poistamiseksi; esim.:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } unset($value); // $value no longer references $arr[3]
isset()
käyttäytymistäNimestään huolimatta isset()
palauttaa epätosi vain, jos kohdetta ei ole olemassa, mutta palauttaa myös false
varten null
arvot .
Tämä käyttäytyminen on ongelmallisempaa kuin se saattaa vaikuttaa aluksi ja on yleinen ongelmien lähde.
Harkitse seuraavaa:
$data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) { // do something here if 'keyShouldBeSet' is not set }
Tämän koodin kirjoittaja oletettavasti halusi tarkistaa, onko keyShouldBeSet
asetettiin $data
. Mutta kuten keskusteltiin, isset($data['keyShouldBeSet'])
tahtoa myös palauta epätosi, jos $data['keyShouldBeSet']
oli asetettu, mutta asetettu arvoon null
. Joten yllä oleva logiikka on puutteellinen.
Tässä on toinen esimerkki:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if (!isset($postData)) { echo 'post not active'; }
Yllä olevassa koodissa oletetaan, että jos $_POST['active']
palauttaa true
, sitten postData
asetetaan välttämättä, ja siksi isset($postData)
palaa true
. Joten päinvastoin, yllä oleva koodi olettaa, että vain tavalla isset($postData)
palaa false
on jos $_POST['active']
palautettu false
yhtä hyvin.
Ei.
Kuten selitettiin, isset($postData)
palaa myös false
jos $postData
asetettiin arvoon null
. Siksi On mahdollista isset($postData)
palata false
vaikka $_POST['active']
palautettu true
. Joten jälleen yllä oleva logiikka on puutteellinen.
Ja muuten, sivupisteenä, jos yllä olevan koodin tarkoituksena oli todella tarkistaa, onko $_POST['active']
palasi totta luottaen isset()
sillä tämä oli joka tapauksessa huono koodauspäätös. Sen sijaan olisi ollut parempi tarkistaa vain $_POST['active']
; eli:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if ($_POST['active']) { echo 'post not active'; }
Tapauksissa kuitenkin missä On on tärkeää tarkistaa, onko muuttuja todella asetettu (eli erottamaan muuttuja, jota ei ole asetettu, ja muuttuja, joka on asetettu arvoon null
), array_key_exists()
menetelmä on paljon vankempi ratkaisu.
Voisimme esimerkiksi kirjoittaa ensimmäisen kahdesta yllä olevasta esimerkistä seuraavasti:
$data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) { // do this if 'keyShouldBeSet' isn't set }
Lisäksi yhdistämällä array_key_exists()
kanssa get_defined_vars()
, voimme luotettavasti tarkistaa, onko nykyisen laajuuden muuttuja asetettu vai ei:
if (array_key_exists('varShouldBeSet', get_defined_vars())) { // variable $varShouldBeSet exists in current scope }
Harkitse tätä koodinpätkää:
class Config { private $values = []; public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];
Jos suoritat yllä olevan koodin, saat seuraavan:
PHP Notice: Undefined index: test in /path/to/my/script.php on line 21
Mikä hätänä?
Asia on, että yllä oleva koodi sekoittaa palauttavat matriisit viitteenä palautettaviin matriiseihin arvon mukaan. Ellet nimenomaisesti kehota PHP: tä palauttamaan matriisi viitteenä (ts. Käyttämällä &
), PHP palauttaa matriisin oletusarvoisesti 'arvon mukaan'. Tämä tarkoittaa, että a kopio matriisista palautetaan, ja siksi kutsuttu funktio ja soittaja eivät pääse samaan ryhmään.
Joten yllä oleva kutsu getValues()
palauttaa a kopio $values
taulukon sijasta viittaus siihen. Tarkastellaan tämä mielessä uudelleen kaksi yllä olevan esimerkin avainta:
// 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'];
Yksi mahdollinen korjaus olisi $values
-palvelun ensimmäisen kopion tallentaminen taulukon palautti getValues()
ja sitten käyttää kyseistä kopiota myöhemmin; esim.:
$vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];
Koodi toimii hyvin (ts. Se tuottaa test
luomatta mitään määrittelemätöntä hakemistoviestiä), mutta riippuen siitä, mitä yrität saavuttaa, tämä lähestymistapa saattaa olla riittävä tai ei. Erityisesti yllä oleva koodi ei muuta alkuperäistä $values
taulukko. Joten jos sinä tehdä Jos haluat, että muokkauksesi (kuten 'testi' -elementin lisääminen) vaikuttavat alkuperäiseen ryhmään, sinun on sen sijaan muokattava getValues()
toiminto palauttaa a viite kohtaan $values
itse taulukko. Tämä tehdään lisäämällä &
ennen funktion nimeä, mikä osoittaa, että sen pitäisi palauttaa viite; eli:
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'];
Tämän tulos on odotetusti test
Mutta jos haluat tehdä asioista sekavampia, harkitse sen sijaan seuraavaa koodinpätkää:
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'];
Jos arvasit, että tämä johtaisi samaan 'määrittelemätön indeksi' -virheeseen kuin aikaisempi array
esimerkiksi olit väärässä. Itse asiassa, Tämä koodi toimii hienosti. Syynä on se, että toisin kuin taulukot, PHP välittää objektit aina viitteenä . (ArrayObject
on SPL-objekti, joka jäljittelee täysin matriisien käyttöä, mutta toimii objektina.)
Kuten nämä esimerkit osoittavat, PHP: ssä ei aina ole täysin selvää, onko kyseessä kopio vai viite. Siksi on olennaisen tärkeää ymmärtää nämä oletuskäyttäytymiset (ts. Muuttujat ja taulukot välitetään arvojen mukaan; objektit välitetään viitteenä) ja myös tarkistaa tarkasti kutsumasi toiminnon sovellusliittymän dokumentaatio, jos se palauttaa arvon kopio taulukosta, viittaus taulukkoon tai viittaus objektiin.
Kaiken tämän lisäksi on tärkeää huomata, että käytäntö palauttaa viittaus matriisiin tai ArrayObject
on yleensä jotain, jota tulisi välttää, koska se antaa soittajalle mahdollisuuden muokata instanssin yksityisiä tietoja. Tämä 'lentää kasvojen edessä' kapseloinnissa. Sen sijaan on parempi käyttää vanhan tyylin 'gettereitä' ja 'settereitä', esim .:
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'
Tämä lähestymistapa antaa soittajalle mahdollisuuden asettaa tai saada mikä tahansa arvo taulukosta tarjoamatta julkista pääsyä muuten yksityiseen $values
itse taulukko.
Ei ole harvinaista törmätä jotain tällaista, jos PHP ei toimi:
$models = []; foreach ($inputValues as $inputValue) { $models[] = $valueRepository->findByValue($inputValue); }
Vaikka tässä ei ehkä ole mitään vikaa, mutta jos noudatat koodin logiikkaa, saatat huomata, että viattoman näköinen puhelu yllä $valueRepository->findByValue()
lopulta johtaa jonkinlaiseen kyselyyn, kuten:
$result = $connection->query('SELECT `x`,`y` FROM `values` WHERE `value`=' . $inputValue);
Tämän seurauksena kukin yllä olevan silmukan iterointi johtaisi erilliseen kyselyyn tietokantaan. Joten jos esimerkiksi syötit silmukalle 1000 arvon matriisin, se tuottaisi 1000 erillistä kyselyä resurssille! Jos tällaista komentosarjaa kutsutaan useissa säikeissä, se saattaa saattaa järjestelmän hiontaan.
Siksi on erittäin tärkeää tunnistaa, milloin kyselysi tehdään koodistasi, ja aina kun mahdollista, kerää arvot ja suorita sitten yksi kysely kaikkien tulosten noutamiseksi.
Yksi esimerkki melko yleisestä paikasta, jossa kysely tapahtuu tehottomasti (ts. Silmukassa), on, kun lomake lähetetään luetteloon arvoista (esimerkiksi tunnukset). Sen jälkeen, kun haluat hakea jokaisen tunnuksen koko tietuetiedot, koodi kiertää taulukon läpi ja tekee erillisen SQL-kyselyn jokaiselle tunnukselle. Tämä näyttää usein tältä:
$data = []; foreach ($ids as $id) { $result = $connection->query('SELECT `x`, `y` FROM `values` WHERE `id` = ' . $id); $data[] = $result->fetch_row(); }
Mutta sama asia voidaan saavuttaa paljon tehokkaammin a yksittäinen SQL-kysely seuraavasti:
$data = []; if (count($ids)) { $result = $connection->query('SELECT `x`, `y` FROM `values` WHERE `id` IN (' . implode(',', $ids)); while ($row = $result->fetch_row()) { $data[] = $row; } }
Siksi on tärkeää tunnistaa, milloin kyselysi tehdään joko suoraan tai epäsuorasti koodisi avulla. Aina kun mahdollista, kerää arvot ja suorita sitten yksi kysely kaikkien tulosten noutamiseksi. Silti myös siellä on noudatettava varovaisuutta, mikä johtaa meidät seuraavaan yleiseen PHP-virheeseemme ...
Vaikka monien tietueiden hakeminen kerralla on ehdottomasti tehokkaampaa kuin yhden kyselyn suorittaminen kutakin riviä varten, tällainen lähestymistapa voi johtaa 'muistin loppumiseen' -tilaan libmysqlclient
kun käytät PHP: itä mysql
laajennus.
Esittelemme katsomalla testiruutua, jossa on rajoitetut resurssit (512 Mt RAM-muistia), MySQL ja php-cli
Käynnistämme seuraavanlaisen tietokantataulukon:
// 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, nyt tarkistetaan resurssien käyttö:
// 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() . '
';
Tuotos:
Before: 224704 Limit 1: 224704 Limit 10000: 224704
Viileä. Näyttää siltä, että kyselyä hallitaan sisäisesti turvallisesti resurssien suhteen.
Vain varmistaaksemme, nostetaan rajaa vielä kerran ja asetetaan 100 000: ksi. Voi ei. Kun teemme sen, saamme:
PHP Warning: mysqli::query(): (HY000/2013): Lost connection to MySQL server during query in /root/test.php on line 11
Mitä tapahtui?
Tässä on kysymys siitä, miten PHP: t mysql
moduuli toimii. Se on oikeastaan vain libmysqlclient
: n välityspalvelin, joka tekee likaisen työn. Kun osa tiedoista valitaan, se menee suoraan muistiin. Koska PHP: n johtaja ei hallinnoi tätä muistia, memory_get_peak_usage()
ei näytä resurssien käytön lisääntymistä, kun ylitämme kyselymme rajan. Tämä johtaa edellä esitetyn kaltaisiin ongelmiin, joissa meidät huijataan itsetyytyväisyyteen ajattelemalla, että muistimme hallinta on kunnossa. Mutta todellisuudessa muistihallintamme on vakavasti puutteellinen ja voimme kokea edellä esitetyn kaltaisia ongelmia.
Voit ainakin välttää yllä olevan väärennöksen (vaikka se ei itse paranna muistisi käyttöä) käyttämällä sen sijaan mysqlnd
moduuli. mysqlnd
on koottu alkuperäiseksi PHP-laajennukseksi ja se tekee käyttää PHP: n muistinhallintaa.
Siksi, jos suoritamme yllä olevan testin käyttämällä mysqlnd
pikemminkin kuin mysql
, saamme paljon realistisemman kuvan muistin käytöstä:
Before: 232048 Limit 1: 324952 Limit 10000: 32572912
Ja se on muuten vielä pahempaa. PHP-dokumentaation mukaan mysql
käyttää kaksinkertaisen määrän resursseja kuin mysqlnd
tietojen tallentamiseksi, joten alkuperäinen komentosarja käyttää mysql
käytti vielä enemmän muistia kuin tässä on esitetty (suunnilleen kaksi kertaa enemmän).
Tällaisten ongelmien välttämiseksi harkitse kyselyidesi koon rajoittamista ja silmukan käyttöä, jolla on pieni määrä iteraatioita. esim.:
$totalNumberToFetch = 10000; $portionSize = 100; for ($i = 0; $i query( 'SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize'); }
Kun otetaan huomioon sekä tämä PHP-virhe että virhe # 4 Yllä ymmärrämme, että koodisi on mieluiten saavutettava terve tasapaino toisaalta sen välillä, että kyselysi ovat liian rakeisia ja toistuvia verrattuna siihen, että kukin yksittäinen kyselysi on liian suuri. Kuten on totta useimpien asioiden kanssa elämässä, tarvitaan tasapainoa; Kumpikaan äärimmäinen ei ole hyvä ja voi aiheuttaa ongelmia PHP: n toimimattomuuden kanssa.
Jossakin mielessä tämä on itse asiassa enemmän ongelma itse PHP: ssä kuin jotain, johon törmäät virheenkorjauksessa, mutta sitä ei ole koskaan käsitelty riittävästi. PHP 6: n ydin oli tarkoitus tehdä Unicode-tietoiseksi, mutta se keskeytettiin, kun PHP 6: n kehitys keskeytettiin vuonna 2010.
Mutta se ei missään tapauksessa vapauta kehittäjää oikein UTF-8 ja vältetään virheellinen oletus, että kaikki merkkijonot ovat välttämättä ”tavallinen vanha ASCII”. Koodi, joka ei kykene käsittelemään muita kuin ASCII-merkkijonoja, on tunnetusti otettu käyttöön gnarly heisenbugs koodiisi. Jopa yksinkertainen strlen($_POST['name'])
puhelut voivat aiheuttaa ongelmia, jos joku, jolla on sukunimi, kuten 'Schrödinger', yrittää kirjautua järjestelmään.
Tässä on pieni tarkistuslista tällaisten ongelmien välttämiseksi koodissasi:
mb_*
toiminnot vanhojen merkkijonofunktioiden sijaan (varmista, että 'multibyte' -laajennus sisältyy PHP-koontiversioon).latin1
).json_encode()
muuntaa ei-ASCII-symbolit (esim. 'Schrödinger' tulee 'Schr u00f6dinger'), mutta serialize()
tekee ei .Erityisen arvokas resurssi tässä suhteessa on UTF-8-pohjamaali PHP: lle ja MySQL: lle lähettäjä Francisco Claria tällä blogilla.
$_POST
sisältää aina POST-tietosiNimestään huolimatta $_POST
taulukko ei aina sisällä POST-tietojasi, ja se löytyy helposti tyhjältä. Tämän ymmärtämiseksi katsotaanpa esimerkkiä. Oletetaan, että teemme palvelinpyynnön jQuery.ajax()
: lla soita seuraavasti:
// js $.ajax({ url: 'http://my.site/some/path', method: 'post', data: JSON.stringify({a: 'a', b: 'b'}), contentType: 'application/json' });
(Huomaa muuten contentType: 'application/json'
tässä. Lähetämme tietoja JSON-muodossa, joka on melko suosittu sovellusliittymissä. Se on oletusarvo esimerkiksi lähetettäessä KulmaJS $http
palvelu .)
Esimerkkimme palvelinpuolella yksinkertaisesti jätämme $_POST
taulukko:
// php var_dump($_POST);
Yllättäen tulos on:
array(0) { }
Miksi? Mitä tapahtui JSON-merkkijonollemme {a: 'a', b: 'b'}
?
Vastaus on se PHP jäsentää POST-hyötykuorman automaattisesti vain, kun sen sisältötyyppi on application/x-www-form-urlencoded
tai multipart/form-data
. Syyt tähän ovat historiallisia - nämä kaksi sisältötyyppiä olivat pohjimmiltaan ainoat, joita käytettiin vuosia sitten, kun PHP: t $_POST
toteutettiin. Joten minkä tahansa muun sisältötyypin (jopa nykyään melko suosittu, kuten application/json
) kanssa, PHP ei lataa POST-hyötykuormaa automaattisesti.
Koska $_POST
on supermaailma, jos ohitamme sen yhden kerran (mieluiten komentosarjamme varhaisessa vaiheessa), muokattu arvo (ts. POST-hyötykuorma mukaan lukien) voidaan sitten viitata koko koodissamme. Tämä on tärkeää, koska $_POST
käytetään yleisesti PHP-kehyksissä ja melkein kaikissa mukautetuissa komentosarjoissa pyyntötietojen purkamiseksi ja muuntamiseksi.
Joten esimerkiksi käsiteltäessä POST-hyötykuormaa sisältötyypillä application/json
, meidän on jäsennettävä pyynnön sisältö manuaalisesti (ts. Purettava JSON-tiedot) ja ohitettava $_POST
muuttuja seuraavasti:
// php $_POST = json_decode(file_get_contents('php://input'), true);
Sitten kun heitämme $_POST
taulukko, näemme, että se sisältää oikein POST-hyötykuorman; esim.:
array(2) { ['a']=> string(1) 'a' ['b']=> string(1) 'b' }
Katso tätä koodikappaletta ja yritä arvata, mitä se tulostaa:
for ($c = 'a'; $c <= 'z'; $c++) { echo $c . '
'; }
Jos vastasit a: sta z: hen, saatat olla yllättynyt tietäessäsi, että olet väärässä.
Kyllä, se tulostaa 'a' - 'z', mutta sitten se tulee myös tulosta 'aa' - 'yz'. Katsotaanpa miksi.
PHP: ssä ei ole char
tietotyyppi; vain string
on käytettävissä. Tätä silmällä pitäen string
: n lisääminen z
PHP-tuottoina aa
:
php> $c = 'z'; echo ++$c . '
'; aa
Sekoittamaan asioita edelleen aa
on leksikografisesti Vähemmän kuin z
:
php> var_export((boolean)('aa' <'z')) . '
'; true
Siksi yllä esitetty esimerkkikoodi tulostaa kirjaimet a
kautta z
, mutta sitten myös tulosteet aa
kautta yz
. Se pysähtyy, kun se saavuttaa za
, joka on ensimmäinen kohtaama arvo, jonka se 'ylittää' z
:
php> var_export((boolean)('za' <'z')) . '
'; false
Tässä tapauksessa tässä on yksi tapa asianmukaisesti selaa PHP: n arvojen 'a' - 'z' läpi:
for ($i = ord('a'); $i <= ord('z'); $i++) { echo chr($i) . '
'; }
Tai vaihtoehtoisesti:
$letters = range('a', 'z'); for ($i = 0; $i Yleinen virhe # 9: Koodausstandardien huomiotta jättäminen
Vaikka koodausstandardien ohittaminen ei johda suoraan PHP-koodin virheenkorjaukseen, se on silti todennäköisesti yksi tärkeimmistä asioista, joista on keskusteltava.
Koodausstandardien huomiotta jättäminen voi aiheuttaa joukon ongelmia projektissa. Parhaimmillaan se johtaa koodiin, joka on epäjohdonmukainen (koska jokainen kehittäjä 'tekee omaa juttuaan'). Pahimmassa tapauksessa se tuottaa PHP-koodia, joka ei toimi tai voi olla vaikea (joskus melkein mahdoton) navigoida, mikä tekee siitä erittäin vaikeaa virheenkorjauksen, parannuksen, ylläpidon. Tämä tarkoittaa tiimisi vähentynyttä tuottavuutta, mukaan lukien paljon hukkaan menevää (tai ainakin tarpeetonta) työtä.
Onneksi PHP-kehittäjille on olemassa PHP-standardien suositus (PSR), joka koostuu seuraavista viidestä standardista:
- PSR-0 : Automaattisäätöstandardi
- PSR-1 : Peruskoodausstandardi
- PSR-2 : Koodaustyyliopas
- PSR-3 : Logger-käyttöliittymä
- PSR-4 : Autoloader
PSR luotiin alun perin markkinoiden tunnetuimpien alustojen ylläpitäjien panosten perusteella. Zend, Drupal, Symfony, Joomla ja toiset myötävaikuttanut näihin standardeihin ja noudattaa niitä nyt. Jopa PEAR, joka yritti olla standardi vuosia ennen sitä, osallistuu PSR: ään nyt.
Jossakin mielessä sillä ei ole väliä mikä koodausstandardisi on, kunhan hyväksyt standardin ja pidät siitä kiinni, mutta PSR: n seuraaminen on yleensä hyvä idea, ellei sinulla ole pakottavaa syytä projektissasi tehdä toisin . Yhä useammat ryhmät ja projektit noudattavat PSR: ää. Tt on tällä hetkellä ehdottomasti tunnustettu useimpien PHP-kehittäjien 'standardiksi', joten sen käyttäminen auttaa varmistamaan, että uudet kehittäjät tuntevat koodausstandardisi ja ovat tyytyväisiä joukkoon liittyessään.
Yleinen virhe # 10: väärinkäyttö empty()
Jotkut PHP-kehittäjät haluavat käyttää empty()
boolen tarkistuksiin melkein kaikesta. On kuitenkin tapauksia, joissa tämä voi johtaa sekaannukseen.
Palataan ensin taulukkoon ja ArrayObject
esiintymiä (jotka jäljittelevät taulukoita). Kun otetaan huomioon niiden samankaltaisuus, on helppo olettaa, että taulukot ja ArrayObject
tapaukset käyttäytyvät identtisesti. Tämä osoittautuu kuitenkin vaaralliseksi olettamukseksi. Esimerkiksi PHP 5.0: ssa:
// 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 mikä vielä pahempaa, tulokset olisivat olleet erilaiset ennen PHP 5.0: ta:
// Prior to PHP 5.0: $array = []; var_dump(empty($array)); // outputs bool(false) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false)
Tämä lähestymistapa on valitettavasti varsin suosittu. Esimerkiksi tämä on tapa ZendDbTableGateway
of Zend Framework 2 palauttaa tiedot soitettaessa current()
päällä TableGateway::select()
tulos, kuten asiakirja ehdottaa. Kehittäjä voi helposti joutua tämän virheen uhriksi tällaisten tietojen avulla.
Näiden ongelmien välttämiseksi parempi tapa tarkistaa tyhjät taulukkorakenteet on käyttää 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 muuten, koska PHP heittää 0
kohteeseen false
, count()
voidaan käyttää myös if ()
-alueella olosuhteet tyhjien taulukoiden tarkistamiseksi. On myös syytä huomata, että PHP: ssä count()
on matriisien jatkuva monimutkaisuus (O(1)
toiminta), mikä tekee vielä selvemmäksi, että se on oikea valinta.
Toinen esimerkki, kun empty()
voi olla vaarallista, kun yhdistetään se taikuusluokkafunktioon __get()
. Määritetään kaksi luokkaa ja on test
omaisuus molemmissa.
Määritetään ensin Regular
luokka, joka sisältää test
normaalina ominaisuutena:
class Regular { public $test = 'value'; }
Määritetään sitten Magic
luokka, joka käyttää taikuutta __get()
operaattori käyttää sen test
omaisuus:
class Magic { private $values = ['test' => 'value']; public function __get($key) { if (isset($this->values[$key])) { return $this->values[$key]; } } }
Selvä, nyt katsotaan mitä tapahtuu, kun yritämme käyttää test
näiden luokkien ominaisuus:
$regular = new Regular(); var_dump($regular->test); // outputs string(4) 'value' $magic = new Magic(); var_dump($magic->test); // outputs string(4) 'value'
Hieno toistaiseksi.
Katsotaan nyt, mitä tapahtuu, kun soitamme empty()
jokaisesta näistä:
var_dump(empty($regular->test)); // outputs bool(false) var_dump(empty($magic->test)); // outputs bool(true)
Uh. Joten jos luotamme empty()
: iin, meidät voidaan johtaa harhaan uskomaan, että test
omaisuus $magic
on tyhjä, kun taas todellisuudessa se on asetettu 'value'
.
Valitettavasti, jos luokka käyttää taikuutta __get()
-toiminnon avulla noutaa kiinteistön arvo, ei ole hölynpölyä tapaa tarkistaa, onko kyseisen ominaisuuden arvo tyhjä vai ei. Luokan ulkopuolella voit tarkistaa vain, onko null
arvo palautetaan, ja se ei välttämättä tarkoita, että vastaavaa avainta ei ole asetettu, koska se tosiasiallisesti voisi on ollut aseta kohteeseen null
Sen sijaan, jos yritämme viitata Regular
: n olemattomaan ominaisuuteen luokan esiintymästä, saamme seuraavanlaisen ilmoituksen:
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
Joten pääasia tässä on, että empty()
Menetelmää tulisi käyttää varoen, koska se voi johtaa hämmentäviin - tai jopa harhaanjohtaviin - tuloksiin, jos se ei ole varovainen.
Paketoida
PHP: n helppokäyttöisyys voi viedä kehittäjät väärään mukavuuteen, jättäen itsensä alttiiksi pitkälle PHP-virheenkorjaukselle joidenkin kielen vivahteiden ja omaleimaisuuden vuoksi. Tämä voi johtaa siihen, että PHP ei toimi ja tässä kuvattuja ongelmia.
PHP-kieli on kehittynyt merkittävästi 20 vuoden historiansa aikana. Hienovaraisuuksiin tutustuminen on kannattava pyrkimys, koska se auttaa varmistamaan, että tuottamasi ohjelmisto on skaalautuvampi, kestävämpi ja ylläpidettävämpi.