Спроба дати огляд різних дискусій та відповідей:
Не існує однозначної відповіді на питання, яке може замінити всі способи, якими 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 повинен заздалегідь припустити, що ви хочете відслідковувати такі речі, а не залишати вас це робити явно, використовуючи звичайний код.