Спроба дати огляд різних дискусій та відповідей:
Не існує однозначної відповіді на питання, яке може замінити всі способи, якими issetможна скористатися. Деякі випадки використання розглядаються іншими функціями, в той час як інші не витримують уваги або не мають сумнівної цінності поза кодом гольфу. Інші випадки використання далеко не "зламані" або "непослідовні" демонструють, чому issetреакція на nullце є логічною поведінкою.
Реальні випадки використання (з рішеннями)
1. Клавіші масиву
Масиви можна розглядати як колекції змінних, unsetа також issetобробляти їх так, ніби вони є. Однак, оскільки їх можна повторити, підрахувати тощо, відсутнє значення не є таким, як значення null.
Відповідь у цьому випадку полягає у використанні array_key_exists()замістьisset() .
Оскільки це потрібно, щоб масив перевірявся як аргумент функції, PHP все одно підніме "сповіщення", якщо сам масив не існує. У деяких випадках можна справедливо стверджувати, що спочатку кожен параметр повинен бути ініціалізований, тому повідомлення виконує свою роботу. В інших випадках "рекурсивна" array_key_existsфункція, яка перевіряла кожен вимір масиву по черзі, уникала цього, але в основному була б такою ж, як @array_key_exists. Це також є дещо дотичним до поводження з nullцінностями.
2. Властивості об'єкта
У традиційній теорії "об'єктно-орієнтованого програмування" інкапсуляція та поліморфізм є ключовими властивостями об'єктів; в класі на основі реалізації ООП як РНР, інкапсульовані властивості , оголошені як частина визначення класу, і з урахуванням рівнів доступу ( public, protectedабо private).
Однак PHP також дозволяє динамічно додавати властивості до об’єкта, як ви клацаєте масив, а деякі користувачі використовують об'єкти, що не належать до класу (технічно, екземпляри вбудованого stdClass, який не має методів або приватних функціональних можливостей) у подібних шлях до асоціативних масивів. Це призводить до ситуацій, коли функція може захотіти знати, чи була додана певна властивість до даного їй об'єкта.
Як з ключами масиву, рішення для перевірки властивостей об'єкта входить в мові, називається, досить розумно,property_exists .
Невиправдані випадки використання з обговоренням
3. register_globalsта інші забруднення глобального простору імен
Ця register_globalsфункція додала змінні до глобальної області, імена яких визначалися аспектами HTTP-запиту (параметри GET та POST та файли cookie). Це може призвести до невдалого та незахищеного коду, саме тому він був відключений за замовчуванням з PHP 4.2, випущеного серпня 2000 року та повністю видалений у PHP 5.4, випущеному березня 2012 року . Однак можливо, що деякі системи все ще працюють із увімкненою або емуляційною функцією. Також можна "забруднити" глобальний простір імен іншими способами, використовуючи globalключове слово або $GLOBALSмасив.
По-перше, register_globalsнавряд чи сам несподівано створить nullзмінну, оскільки значення GET, POST та файли cookie завжди будуть рядками (з тим, що ''все ще повертаються trueз isset), а змінні в сеансі повинні бути повністю під контролем програміста.
По-друге, забруднення змінної зі значенням nullє лише проблемою, якщо це перевищує деяку попередню ініціалізацію. "Перезапис" неініціалізованої змінної з nullпроблемою був би лише проблематичним, якщо код десь ще відрізнявся між двома станами, тому сама по собі ця можливість є аргументом проти такого розмежування.
4. get_defined_varsіcompact
Кілька рідко використовуваних функцій у PHP, наприклад, get_defined_varsта compact, дозволяють вам обробляти імена змінних так, ніби вони були ключами у масиві. Для глобальних змінних надглобальний масив$GLOBALS дозволяє подібний доступ і є більш поширеним. Ці методи доступу будуть вести себе по-різному, якщо змінна не визначена у відповідній області.
Після того, як ви вирішили трактувати набір змінних як масив, використовуючи один із цих механізмів, ви можете робити всі ті ж операції на ньому, що і на будь-якому звичайному масиві. Отже, див. 1.
Функціональність, яка існувала лише для того, щоб передбачити поведінку цих функцій (наприклад, "чи буде ключ" foo "в масиві, повернутий get_defined_vars?") Є зайвим, оскільки ви можете просто запустити функцію і виявити без негативних наслідків.
4а. Змінні змінні ( $$foo)
Хоча це не зовсім те саме, що функції, які перетворюють набір змінних в асоціативний масив, більшість випадків, що використовують "змінні змінні" ("присвоєння змінній, названій на основі цієї іншої змінної"), можуть і повинні бути змінені, щоб використовувати замість цього асоціативний масив .
Ім’я змінної, по суті, - це мітка, яка надається значенню програмістом; якщо ви визначаєте його під час виконання, це насправді не мітка, а ключ у деякому магазині ключових значень. Більш практично, не використовуючи масив, ви втрачаєте здатність рахувати, повторювати тощо; також може стати неможливим наявність змінної "поза" сховищем ключа-значення, оскільки це може бути перезаписано $$foo.
Як тільки буде змінено на використання асоціативного масиву, код підлягає вирішенню 1. Доступ до непрямих властивостей об'єкта (наприклад $foo->$property_name) можна вирішити з рішенням 2.
5. issetтак набагато простіше набрати, ніжarray_key_exists
Я не впевнений, що це насправді актуально, але так, назви функцій PHP можуть бути досить довгими і непослідовними часом. Мабуть, доісторичні версії PHP використовували довжину імені функції як хеш-ключ, тож Rasmus навмисно склав імена функцій на кшталт, htmlspecialcharsщоб вони мали незвичну кількість символів ...
Все-таки принаймні ми не пишемо Java, так? ;)
6. Неініціалізовані змінні мають тип
Сторінка керівництва з основ змінних включає це твердження:
Неініціалізовані змінні мають значення за замовчуванням свого типу залежно від контексту, в якому вони використовуються
Я не впевнений, чи є якесь поняття в Zend Engine "неініціалізованого, але відомого типу", чи це занадто багато читається в заяві.
Ясно, що це не має жодної практичної різниці для їх поведінки, оскільки поведінка, описана на цій сторінці для неініціалізованих змінних, ідентична поведінці змінної, значення якої є null. Щоб вибрати один приклад, $aі $bв цьому коді вийде як ціле число 42:
unset($a);
$a += 42;
$b = null;
$b += 42;
(Перший підніме повідомлення про незадекларовану змінну в спробі змусити вас написати кращий код, але це не вплине на те, як насправді працює код.)
99. Визначення, якщо функція запущена
(Тримаю цю останню, оскільки вона набагато довша за інші. Можливо, я відредагую її згодом ...)
Розглянемо наступний код:
$test_value = 'hello';
foreach ( $list_of_things as $thing ) {
if ( some_test($thing, $test_value) ) {
$result = some_function($thing);
}
}
if ( isset($result) ) {
echo 'The test passed at least once!';
}
Якщо some_functionможна повернутись null, є ймовірність, що echoне буде досягнуто, навіть якщо він some_testповернувся true. Намір програміста був виявити, коли $resultвін ніколи не був встановлений, але PHP не дозволяє їм це робити.
Однак з цим підходом є й інші проблеми, які стають зрозумілими, якщо додати зовнішню петлю:
foreach ( $list_of_tests as $test_value ) {
// something's missing here...
foreach ( $list_of_things as $thing ) {
if ( some_test($thing, $test_value) ) {
$result = some_function($thing);
}
}
if ( isset($result) ) {
echo 'The test passed at least once!';
}
}
Оскільки $resultніколи не ініціалізується явно, воно набуде значення, коли пройде перший тест, що робить неможливим визначити, пройшли чи ні наступні тести чи ні. Це насправді надзвичайно поширена помилка, коли змінні не ініціалізуються належним чином.
Щоб виправити це, нам потрібно зробити щось у рядку, де я прокоментував, що щось не вистачає. Найбільш очевидне рішення - встановити $result"кінцеве значення", яке some_functionніколи не може повернутися; якщо це так null, то решта коду буде добре працювати. Якщо немає природного кандидата на термінальне значення, оскільки він some_functionмає надзвичайно непередбачуваний тип повернення (що, мабуть, сам по собі поганий знак), то $foundзамість цього може бути використане додаткове булеве значення, наприклад .
Мислимий експеримент один: very_nullконстанта
PHP теоретично може забезпечити спеціальну константу - як і null- для використання в якості термінального значення тут; імовірно, було б незаконним повернути це з функції, або це було б примусовано null, і те саме, ймовірно, стосуватиметься передачі його як аргументу функції. Це зробить цей дуже конкретний випадок дещо простішим, але як тільки ви вирішили перефактурувати код - наприклад, поставити внутрішній цикл в окрему функцію - це стане марним. Якщо константа могла бути передана між функціями, ви не можете гарантувати, що some_functionне повернете її, тому вона більше не буде корисною як універсальне кінцеве значення.
Аргумент для виявлення неініціалізованих змінних у цьому випадку зводиться до аргументу для цієї спеціальної константи: якщо ви заміните коментар на unset($result), а трактуєте це інакше $result = null, ви вводите "значення", $resultяке не може бути передано навколо, і може бути лише виявляється за допомогою конкретних вбудованих функцій.
Думав експеримент два: лічильник завдання
Ще один спосіб думати про те, що ifзапитує останній , - "чи щось зробив завдання $result?" Замість того, щоб вважати це особливим значенням $result, ви можете, можливо, вважати це як "метадані" щодо змінної, трохи схоже на "змінне вбивство" Perl. Тому замість того , issetви могли б назвати його has_been_assigned_to, і замість того unset, reset_assignment_state.
Але якщо так, то навіщо зупинятися на буле? Що робити, якщо ви хочете знати, скільки разів пройшов тест; ви можете просто розширити метадані на ціле число і мати get_assignment_countі reset_assignment_count...
Очевидно, що додавання такої функції матиме компроміс у складності та продуктивності мови, тому її потрібно ретельно зважити на очікувану корисність. Як і у випадку very_nullконстанти, вона була б корисною лише в дуже вузьких обставинах і була б аналогічно стійкою до повторного факторингу.
Очевидно, що очевидно питання, чому двигун виконання PHP повинен заздалегідь припустити, що ви хочете відслідковувати такі речі, а не залишати вас це робити явно, використовуючи звичайний код.