PHP улеснява сравнително лесното изграждане на уеб базирана система, което е голяма част от причината за нейната популярност. Но независимо от лекотата на използване, PHP се превърна в доста усъвършенстван език с много рамки, нюанси и тънкости, които могат да ухапят разработчиците, което води до часове отстраняване на грешки за издърпване на косата. Тази статия подчертава десет от най-често срещаните грешки, които PHP разработчици трябва да се пазите от.
foreach
циклиНе сте сигурни как да използвате цикли foreach в PHP? Използване на препратки в foreach
цикли могат да бъдат полезни, ако искате да оперирате всеки елемент в масива, който итерирате. Например:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr is now array(2, 4, 6, 8)
Проблемът е, че ако не внимавате, това може да има и нежелани странични ефекти и последици. По-конкретно, в горния пример, след изпълнението на кода, $value
ще остане в обхвата и ще съдържа препратка към последния елемент в масива. Последващи операции, включващи $value
следователно може непреднамерено да модифицира последния елемент в масива.
Основното нещо, което трябва да запомните е, че foreach
не създава обхват. По този начин, $value
в горния пример е a справка в горния обхват на скрипта. На всяка итерация foreach
задава препратката към точка към следващия елемент на $array
. Следователно след завършване на цикъла $value
все още сочи към последния елемент на $array
и остава в обхвата.
Ето пример за вида укриващи се и объркващи грешки, до които това може да доведе:
$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), '
';
Горният код ще изведе следното:
1,2,3 1,2,3 1,2,2
Не, това не е печатна грешка. Последната стойност на последния ред наистина е 2, а не 3.
Защо?
След преминаване през първия foreach
цикъл, $array
остава непроменена, но, както е обяснено по-горе, $value
е оставена като висяща препратка към последния елемент в $array
(тъй като този foreach
цикъл е достъпен $value
от справка ).
каква е втората стъпка от капиталовото бюджетиране?
В резултат на това, когато преминем през втория foreach
цикъл, изглежда, че се случват „странни неща“. По-конкретно, тъй като $value
сега е достъпен по стойност (т.е. чрез копие ), foreach
копия всяка последователна $array
елемент в $value
във всяка стъпка от цикъла. В резултат ето какво се случва по време на всяка стъпка от втората foreach
цикъл:
$array[0]
(т.е. „1“) в $value
(което е препратка към $array[2]
), така че $array[2]
сега е равно на 1. Значи $array
сега съдържа [1, 2, 1].$array[1]
(т.е. „2“) в $value
(което е препратка към $array[2]
), така че $array[2]
сега е равно на 2. Значи $array
сега съдържа [1, 2, 2].$array[2]
(което сега е равно на „2“) в $value
(което е препратка към $array[2]
), така че $array[2]
все още е равно на 2. Така че $array
сега съдържа [1, 2, 2].За да продължите да се възползвате от използването на референции в foreach
цикли, без да рискувате от този вид проблеми, извикайте unset()
върху променливата, веднага след foreach
цикъл, за да премахнете препратката; напр .:
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } unset($value); // $value no longer references $arr[3]
isset()
поведениеВъпреки името си, isset()
не само връща false, ако елемент не съществува, но също връща false
за null
стойности .
Това поведение е по-проблематично, отколкото може да изглежда в началото и е често срещан източник на проблеми.
Помислете за следното:
$data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) { // do something here if 'keyShouldBeSet' is not set }
Авторът на този код вероятно е искал да провери дали keyShouldBeSet
бе зададено в $data
. Но, както беше обсъдено, isset($data['keyShouldBeSet'])
ще също връщане на false, ако $data['keyShouldBeSet']
беше зададен, но е зададен на null
. Така че горната логика е погрешна.
Ето още един пример:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if (!isset($postData)) { echo 'post not active'; }
Горният код предполага, че ако $_POST['active']
връща true
, след това postData
задължително ще бъде зададено и следователно isset($postData)
ще се върне true
. И обратно, горният код приема, че само по този начин isset($postData)
ще се върне false
е ако $_POST['active']
върнати false
както добре.
Не.
Както беше обяснено, isset($postData)
също ще върне false
ако $postData
бе зададено на null
. Ето защо е възможно за isset($postData)
за връщане false
дори ако $_POST['active']
върнати true
. И така, горната логика е погрешна.
И между другото, като странична точка, ако целта в горния код наистина е била отново да провери дали $_POST['active']
върна вярно, разчитайки на isset()
тъй като това беше лошо решение за кодиране във всеки случай. Вместо това би било по-добре просто да проверите отново $_POST['active']
; т.е.:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if ($_POST['active']) { echo 'post not active'; }
За случаите обаче къде е важно е да се провери дали дадена променлива наистина е зададена (т.е., за да се прави разлика между променлива, която не е зададена, и променлива, която е зададена на null
), array_key_exists()
метод е много по-стабилно решение.
Например, можем да пренапишем първия от горните два примера, както следва:
$data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) { // do this if 'keyShouldBeSet' isn't set }
Освен това, чрез комбиниране array_key_exists()
с get_defined_vars()
, можем надеждно да проверим дали променлива в текущия обхват е зададена или не:
if (array_key_exists('varShouldBeSet', get_defined_vars())) { // variable $varShouldBeSet exists in current scope }
Помислете за този кодов фрагмент:
class Config { private $values = []; public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];
Ако изпълните горния код, ще получите следното:
PHP Notice: Undefined index: test in /path/to/my/script.php on line 21
Какво не е наред?
Проблемът е, че горният код обърква връщащите масиви чрез препратка към връщащите масиви по стойност. Освен ако изрично не кажете на PHP да върне масив чрез препратка (т.е. с помощта на &
), PHP по подразбиране ще върне масива 'по стойност'. Това означава, че a копие на масива ще бъде върнат и следователно извиканата функция и повикващият няма да имат достъп до същия екземпляр на масива.
Така че горното обаждане до getValues()
връща a копие от $values
масив, а не препратка към него. Имайки това предвид, нека преразгледаме двата ключови реда от горния пример:
// 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'];
Една възможна корекция би била запазването на първото копие на $values
масив, върнат от getValues()
и след това да оперира с това копие впоследствие; напр .:
$vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];
Този код ще работи добре (т.е. ще изведе test
, без да генерира съобщение с „недефиниран индекс“), но в зависимост от това, което се опитвате да постигнете, този подход може или не може да бъде адекватен. По-специално, горният код няма да промени оригинала $values
масив. Така че, ако вие направете искате вашите модификации (като добавяне на елемент „test“) да повлияят на оригиналния масив, вместо това ще трябва да промените getValues()
функция за връщане на a справка към $values
самия масив. Това става чрез добавяне на &
преди името на функцията, като по този начин показва, че тя трябва да върне препратка; т.е.:
на какъв език са написани дискорд ботовете
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'];
Резултатът от това ще бъде test
, както се очаква.
Но за да направите нещата по-объркващи, помислете вместо това за следния кодов фрагмент:
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'];
Ако се досещате, че това би довело до същата грешка „недефиниран индекс“ като нашата по-рано array
например, сгрешихте. Всъщност, това кодът ще работи добре. Причината е, че за разлика от масивите, PHP винаги предава обекти по препратка . (ArrayObject
е SPL обект, който напълно имитира използването на масиви, но работи като обект.)
Както показват тези примери, не винаги е напълно очевидно в PHP дали имате работа с копие или препратка. Следователно е от съществено значение да се разберат тези поведения по подразбиране (т.е. променливите и масивите се предават по стойност; обектите се предават по препратка) и също така внимателно да се провери документацията на API за функцията, която извиквате, за да видите дали връща стойност, a копие на масив, препратка към масив или препратка към обект.
Всичко казано, важно е да се отбележи, че практиката на връщане на препратка към масив или ArrayObject
обикновено е нещо, което трябва да се избягва, тъй като предоставя на повикващия възможността да променя личните данни на инстанцията. Това „лети в лицето” на капсулирането. Вместо това е по-добре да използвате „гетери“ и „сетери“ от стар стил, напр .:
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'
Този подход дава възможност на повикващия да зададе или да получи каквато и да е стойност в масива, без да предоставя публичен достъп до иначе частния $values
самия масив.
Не е необичайно да попаднете на нещо подобно, ако вашият PHP не работи:
$models = []; foreach ($inputValues as $inputValue) { $models[] = $valueRepository->findByValue($inputValue); }
Макар че тук може да няма абсолютно нищо лошо, но ако следвате логиката в кода, може да откриете, че невинно изглеждащият призив по-горе да $valueRepository->findByValue()
в крайна сметка води до някаква заявка, като например:
$result = $connection->query('SELECT `x`,`y` FROM `values` WHERE `value`=' . $inputValue);
В резултат на това всяка итерация на горния цикъл ще доведе до отделна заявка към базата данни. Така че, ако например сте предоставили масив от 1000 стойности на цикъла, той ще генерира 1000 отделни заявки към ресурса! Ако такъв скрипт бъде извикан в множество нишки, той потенциално може да спре системата да се спре.
Следователно е от решаващо значение да разпознаете кога се правят заявки от вашия код и, когато е възможно, да събирате стойностите и след това да стартирате една заявка, за да извлечете всички резултати.
Един пример за доста често срещано място за среща с заявки, които се извършват неефективно (т.е. в цикъл), е когато формуляр се публикува със списък със стойности (идентификатори например). След това, за да се извлекат пълните данни за запис за всеки от идентификаторите, кодът ще премине през масива и ще направи отделна SQL заявка за всеки идентификатор. Това често изглежда по следния начин:
$data = []; foreach ($ids as $id) { $result = $connection->query('SELECT `x`, `y` FROM `values` WHERE `id` = ' . $id); $data[] = $result->fetch_row(); }
Но едно и също нещо може да бъде постигнато много по-ефективно в a неженен SQL заявка, както следва:
$data = []; if (count($ids)) { $result = $connection->query('SELECT `x`, `y` FROM `values` WHERE `id` IN (' . implode(',', $ids)); while ($row = $result->fetch_row()) { $data[] = $row; } }
Следователно е от решаващо значение да разпознаете кога се правят заявки, пряко или косвено, от вашия код. Когато е възможно, съберете стойностите и след това изпълнете една заявка, за да извлечете всички резултати. И все пак там трябва да се внимава, което ни води до следващата ни често срещана грешка в PHP ...
Въпреки че извличането на много записи наведнъж е определено по-ефективно от изпълнението на една заявка за всеки ред за извличане, такъв подход може потенциално да доведе до състояние на „липса на памет“ в libmysqlclient
при използване на PHP mysql
удължаване.
За да демонстрираме, нека да разгледаме тестово поле с ограничени ресурси (512MB RAM), MySQL и php-cli
Ще заредим таблица на база данни като тази:
// 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); }
Добре, сега да проверим използването на ресурси:
// 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() . '
';
Изход:
Before: 224704 Limit 1: 224704 Limit 10000: 224704
Готино. Изглежда, че заявката се управлява безопасно вътрешно по отношение на ресурсите.
Само за да сме сигурни, нека увеличим лимита още веднъж и да го зададем на 100 000. А-а. Когато правим това, получаваме:
PHP Warning: mysqli::query(): (HY000/2013): Lost connection to MySQL server during query in /root/test.php on line 11
Какво стана?
Въпросът тук е в начина, по който PHP | | + + _ | модул работи. Това всъщност е само прокси за mysql
, което върши мръсната работа. Когато е избрана част от данните, тя отива директно в паметта. Тъй като тази памет не се управлява от мениджъра на PHP, libmysqlclient
няма да покаже никакво увеличение на използването на ресурси, тъй като увеличаваме ограничението в заявката си. Това води до проблеми като този, демонстриран по-горе, при които ни подвеждат да се самоуспокояваме, мислейки, че управлението на паметта ни е наред. Но в действителност управлението на паметта ни е сериозно недостатъчно и можем да изпитаме проблеми като този, показан по-горе.
Можете поне да избегнете горепосочената глава (въпреки че тя сама по себе си няма да подобри използването на паметта), като вместо това използвате memory_get_peak_usage()
модул. mysqlnd
се компилира като естествено PHP разширение и то прави използвайте мениджъра на паметта на PHP.
Следователно, ако изпълним горния тест, използвайки mysqlnd
вместо mysqlnd
, получаваме много по-реалистична картина на използването на паметта ни:
mysql
И между другото е дори по-лошо от това. Според PHP документацията, Before: 232048 Limit 1: 324952 Limit 10000: 32572912
използва два пъти повече ресурси от mysql
за съхраняване на данни, така че оригиналният скрипт използва mysqlnd
наистина използва дори повече памет, отколкото е показана тук (приблизително два пъти повече).
За да избегнете подобни проблеми, помислете за ограничаване на размера на вашите заявки и използване на цикъл с малък брой итерации; напр .:
mysql
Когато разгледаме и тази грешка в PHP и грешка # 4 по-горе, ние осъзнаваме, че има здравословен баланс, който вашият код в идеалния случай трябва да постигне между това, от една страна, да имате твърде подробни и повтарящи се запитвания, срещу това, че всяка ваша индивидуална заявка е твърде голяма. Както е вярно с повечето неща в живота, необходим е баланс; и двете крайности не са добри и могат да причинят проблеми с PHP, който не работи правилно.
В известен смисъл това наистина е по-скоро проблем в самия PHP, отколкото нещо, с което бихте се сблъскали при отстраняване на грешки в PHP, но никога не е било адресирано адекватно. Ядрото на PHP 6 трябваше да бъде осъзнато от Unicode, но това беше спряно, когато разработването на PHP 6 беше спряно през 2010 г.
Но това в никакъв случай не освобождава разработчика от правилно предаване на UTF-8 и избягване на погрешното предположение, че всички низове непременно ще бъдат „обикновен стар ASCII“. Кодът, който не се справя правилно с низове, различни от ASCII, е известен с въвеждането на gnarly heisenbugs във вашия код. Дори просто $totalNumberToFetch = 10000; $portionSize = 100; for ($i = 0; $i query( 'SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize'); }
обажданията могат да създадат проблеми, ако някой с фамилно име като „Schrödinger“ се опита да влезе във вашата система.
Ето малък контролен списък, за да избегнете подобни проблеми във вашия код:
strlen($_POST['name'])
функции вместо старите низови функции (уверете се, че разширението “multibyte” е включено във вашата PHP компилация).mb_*
по подразбиране).latin1
преобразува символи, които не са ASCII (напр. „Schrödinger“ става „Schr u00f6dinger“), но json_encode()
прави не .Особено ценен ресурс в това отношение е UTF-8 Primer за PHP и MySQL публикувано от Франсиско Клария в този блог.
serialize()
винаги ще съдържа вашите POST данниВъпреки името си, $_POST
array не винаги ще съдържа вашите POST данни и може лесно да се намери празен. За да разберем това, нека разгледаме един пример. Да приемем, че правим заявка за сървър с $_POST
обадете се както следва:
jQuery.ajax()
(Между другото, обърнете внимание на // js $.ajax({ url: 'http://my.site/some/path', method: 'post', data: JSON.stringify({a: 'a', b: 'b'}), contentType: 'application/json' });
тук. Изпращаме данни като JSON, което е доста популярно за API. Това е по подразбиране, например, за публикуване в AngularJS contentType: 'application/json'
обслужване .)
От страна на сървъра на нашия пример просто изхвърляме $http
масив:
$_POST
Изненадващо, резултатът ще бъде:
// php var_dump($_POST);
Защо? Какво се случи с нашия JSON низ array(0) { }
какво измерва ценовата еластичност на търсенето
Отговорът е такъв PHP анализира POST полезен товар автоматично само когато има тип съдържание {a: 'a', b: 'b'}
или application/x-www-form-urlencoded
. Причините за това са исторически - тези два типа съдържание по същество бяха единствените, използвани преди години, когато PHP’s multipart/form-data
е изпълнен. Така че с всеки друг тип съдържание (дори с тези, които са доста популярни днес, като $_POST
), PHP не зарежда автоматично полезния товар на POST.
Тъй като application/json
е суперглобален, ако го заменим веднъж (за предпочитане в началото на нашия скрипт), модифицираната стойност (т.е., включително полезния товар на POST) ще бъде референтна в целия ни код. Това е важно, тъй като $_POST
се използва често от PHP рамки и почти всички персонализирани скриптове за извличане и трансформиране на данни за заявки.
Така например, когато обработваме POST полезен товар с тип съдържание $_POST
, трябва ръчно да анализираме съдържанието на заявката (т.е. да декодираме JSON данните) и да заменим application/json
променлива, както следва:
$_POST
Тогава, когато изхвърляме // php $_POST = json_decode(file_get_contents('php://input'), true);
масив, виждаме, че той правилно включва полезния товар на POST; напр .:
$_POST
Погледнете тази примерна част от кода и опитайте да познаете какво ще отпечата:
array(2) { ['a']=> string(1) 'a' ['b']=> string(1) 'b' }
Ако сте отговорили с „а“ до „z“, може да се изненадате да разберете, че сте сгрешили.
Да, ще отпечата „а“ до „z“, но след това ще го направи също отпечатайте „aa“ през „yz“. Нека да видим защо.
В PHP няма for ($c = 'a'; $c <= 'z'; $c++) { echo $c . '
'; }
тип данни; само char
е на разположение. Имайки това предвид, увеличаване на string
string
в PHP добиви z
:
aa
И все пак, за да объркаме нещата, php> $c = 'z'; echo ++$c . '
'; aa
е лексикографски по-малко от aa
:
z
Ето защо примерният код, представен по-горе, отпечатва буквите php> var_export((boolean)('aa' <'z')) . '
'; true
през a
, но след това също разпечатки z
през aa
. Спира, когато достигне yz
, което е първата стойност, която среща, че е 'по-голяма от' za
:
z
В този случай ето един от начините да правилно цикъл през стойностите от 'а' до 'z' в PHP:
php> var_export((boolean)('za' <'z')) . '
'; false
Или алтернативно:
for ($i = ord('a'); $i <= ord('z'); $i++) { echo chr($i) . '
'; }
Въпреки че игнорирането на стандартите за кодиране не води пряко до необходимостта от отстраняване на грешки в PHP кода, това все още е може би едно от най-важните неща за обсъждане тук.
Пренебрегването на стандартите за кодиране може да доведе до множество проблеми в проекта. В най-добрия резултат се получава код, който е непоследователен (тъй като всеки разработчик „прави своето нещо“). Но в най-лошия случай той произвежда PHP код, който не работи или може да бъде труден (понякога почти невъзможен) за навигация, което прави изключително трудно отстраняването на грешки, подобряването, поддържането. А това означава намалена производителност на вашия екип, включително много загубени (или поне ненужни) усилия.
За щастие на разработчиците на PHP има препоръка за стандарти за PHP (PSR), състояща се от следните пет стандарта:
PSR първоначално е създаден въз основа на данни от поддръжници на най-признатите платформи на пазара. Zend, Drupal, Symfony, Joomla и други допринесоха за тези стандарти и сега ги спазват. Дори PEAR, който се опита да бъде стандарт в продължение на години преди това, участва в PSR сега.
В някакъв смисъл почти няма значение какъв е вашият стандарт за кодиране, стига да се съгласите за стандарт и да се придържате към него, но спазването на PSR обикновено е добра идея, освен ако нямате убедителна причина в проекта си да правите друго . Все повече и повече екипи и проекти са в съответствие с PSR. Tt определено е признат на този етап като „стандарт“ от повечето разработчици на PHP, така че използването му ще помогне да се гарантира, че новите разработчици са запознати и се чувстват комфортно с вашия стандарт за кодиране, когато се присъединят към вашия екип.
$letters = range('a', 'z'); for ($i = 0; $i
Някои разработчици на PHP обичат да използват empty()
за булеви проверки за почти всичко. Има случаи обаче, когато това може да доведе до объркване.
Първо, нека се върнем към масивите и empty()
екземпляри (които имитират масиви). Предвид тяхната прилика е лесно да се приеме, че масивите и ArrayObject
екземплярите ще се държат идентично. Това обаче се оказва опасно предположение. Например в PHP 5.0:
ArrayObject
И за да направим нещата още по-лоши, резултатите биха били различни преди 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?
За съжаление този подход е доста популярен. Например това е начинът // Prior to PHP 5.0: $array = []; var_dump(empty($array)); // outputs bool(false) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false)
на Zend Framework 2 връща данни при извикване ZendDbTableGateway
на current()
резултат, както предлага документът. Разработчикът може лесно да стане жертва на тази грешка с такива данни.
За да се избегнат тези проблеми, по-добрият подход за проверка на празни масивни структури е да се използва TableGateway::select()
:
count()
И между другото, тъй като PHP хвърля // 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)
до 0
, false
може да се използва и в рамките на count()
условия за проверка на празни масиви. Също така си струва да се отбележи, че в PHP, if ()
е постоянна сложност (count()
операция) върху масиви, което прави още по-ясно, че това е правилният избор.
Друг пример, когато O(1)
може да бъде опасно, когато го комбинирате с функцията магически клас empty()
Нека дефинираме два класа и имаме __get()
собственост и в двете.
Първо нека дефинираме test
клас, който включва Regular
като нормално свойство:
test
Тогава нека дефинираме class Regular { public $test = 'value'; }
клас, който използва магията Magic
оператор за достъп до неговия __get()
Имот:
test
Добре, сега да видим какво се случва, когато се опитаме да получим достъп до class Magic { private $values = ['test' => 'value']; public function __get($key) { if (isset($this->values[$key])) { return $this->values[$key]; } } }
свойство на всеки от тези класове:
test
Засега глоба.
Но сега да видим какво се случва, когато се обадим $regular = new Regular(); var_dump($regular->test); // outputs string(4) 'value' $magic = new Magic(); var_dump($magic->test); // outputs string(4) 'value'
на всеки от тях:
empty()
Ъъъ. Така че, ако разчитаме на var_dump(empty($regular->test)); // outputs bool(false) var_dump(empty($magic->test)); // outputs bool(true)
, можем да бъдем подведени да вярваме, че empty()
собственост на test
е празно, докато в действителност е зададено на $magic
.
За съжаление, ако клас използва магията 'value'
функция за извличане на стойност на свойство, няма надежден начин да проверите дали стойността на това свойство е празна или не. Извън обхвата на класа наистина можете да проверите само дали __get()
стойност ще бъде върната и това не означава непременно, че съответният ключ не е зададен, тъй като всъщност е бих могъл бил е комплект до null
.
изобразяване от страна на клиента срещу изобразяване от страна на сървъра
За разлика от това, ако се опитаме да се позовем на несъществуващо свойство на null
екземпляр на клас, ще получим известие, подобно на следното:
Regular
Така че основната точка тук е, че 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
методът трябва да се използва внимателно, тъй като може да доведе до объркващи - или дори потенциално подвеждащи - резултати, ако човек не е внимателен.
Лесната употреба на PHP може да приспи разработчиците във фалшиво чувство за комфорт, оставяйки себе си уязвими за продължително отстраняване на грешки в PHP поради някои нюанси и особености на езика. Това може да доведе до неработене на PHP и проблеми като описаните тук.
Езикът PHP се е развил значително през своята 20-годишна история. Запознаването с неговите тънкости е полезно начинание, тъй като ще помогне да се гарантира, че софтуер, който произвеждате е по-мащабируема, здрава и поддържаема.