Найкращий спосіб перевірити наявність змінної в PHP; isset () явно порушено


187

З isset()документів :

isset() will return FALSE if testing a variable that has been set to NULL.

В основному, isset()не перевіряється, чи вказана змінна взагалі, а чи встановлена ​​вона що-небудь, окрім NULL.

Враховуючи це, який найкращий спосіб насправді перевірити наявність змінної? Я спробував щось на кшталт:

if(isset($v) || @is_null($v))

( @необхідне, щоб уникнути попередження, коли $vвоно не встановлено), але is_null()має аналогічну проблему isset(): він повертається TRUEдо невстановлених змінних! Також виявляється, що:

@($v === NULL)

працює точно так само @is_null($v), так що це теж.

Як ми повинні надійно перевірити наявність змінної в PHP?


Редагувати: явно різниця в PHP між змінними, які не встановлені, та змінними, які встановлені на NULL:

<?php
$a = array('b' => NULL);
var_dump($a);

PHP показує, що $a['b']існує і має NULLзначення. Якщо ви додасте:

var_dump(isset($a['b']));
var_dump(isset($a['c']));

ви можете бачити неоднозначність, про яку я говорю з isset()функцією. Ось результат усіх трьох з них var_dump()s:

array(1) {
  ["b"]=>
  NULL
}
bool(false)
bool(false)

Подальше редагування: дві речі.

Один, випадок використання. Масив, який перетворюється на дані оператора SQL UPDATE, де ключами масиву є стовпці таблиці, а значення масиву - значення, що застосовуються до кожного стовпця. Будь-який із стовпців таблиці може містити NULLзначення, що позначається передачею NULLзначення у масиві. Вам потрібен спосіб розмежувати ключ масиву, який не існує, і значення масиву, встановлене на NULL; ось різниця між не оновленням значення стовпця та оновленням значення стовпця NULL.

По- друге, відповідь Zoredache в , array_key_exists()працює правильно, на мій вище випадку використання і для будь-яких глобальних змінних:

<?php
$a = NULL;
var_dump(array_key_exists('a', $GLOBALS));
var_dump(array_key_exists('b', $GLOBALS));

Виходи:

bool(true)
bool(false)

Оскільки це належним чином обробляється практично скрізь, я можу побачити, що між змінними, які не існують, і змінами, на які встановлено NULL, існує якась неоднозначність , я викликаю array_key_exists()офіційний найпростіший спосіб у PHP, щоб справді перевірити наявність змінної .

(Я думаю лише про інший випадок, що стосується властивостей класу, для яких існує property_exists(), який, згідно з його документами , працює аналогічно тому, array_key_exists()що він належним чином розрізняє не встановлений та встановлений NULL.)


Ви не можете перевірити - але навіщо це потрібно?
занадто багато php

12
NULL має дуже специфічне значення в PHP, і це абсолютно окреме поняття від того, встановлена ​​чи ні змінна.
chazomaticus

33
Є підстави розмежовувати нульове та неіснуюче. Наприклад, ви створюєте об'єкт для представлення рядка в таблиці бази даних. Для кожного стовпця в рядку ви створюєте приватну змінну, доступну лише методом отримання об'єкта. Припустимо, значення стовпця є нульовим. Тепер, як цей метод getter знає, чи немає у таблиці такого стовпця, чи цей об’єкт просто має нульове значення? На щастя, в моєму випадку приватна змінна - це фактично запис у приватному масиві, тому я можу використовувати array_key_exists, але це справжня проблема.
Натан Лонг

1
Це було видалено з нових версій PHP, так. На жаль, він не пішов від кожного розгортання PHP. Крім того, здається, що безглузда семантична деталь посперечатися на те, чи ми говоримо про елементи масиву чи змінні. Незалежно від того, яких стандартів, на вашу думку, повинен дотримуватися код, корисно знати, як обходити невідповідність мові PHP.
chazomaticus

2
@chazomaticus Але змінні та елементи масиву принципово різні ; тільки те, що ви можете робити одне й те саме, не означає, що вони є або повинні бути на 100% взаємозамінними. Тут немає "невідповідності мови PHP", просто щось, що вам не подобається / розуміється. Що стосується register_globals, я все ще намагаюся думати про ситуацію, коли навіть це вимагало б такого розрізнення, оскільки все, що було зареєстровано в HTTP-запиті, завжди було б рядком, а не null.
IMSoP

Відповіді:


97

Якщо змінна, яку ви перевіряєте, була б у глобальному масштабі, ви можете:

array_key_exists('v', $GLOBALS) 

3
А-а-а! ЗАРАЗ ви говорите! Як би ви це зробили для, скажімо, властивостей класу?
chazomaticus

22
Як варіант, якщо перевірка також повинна працювати для локальних змінних областей, on може зробити a, $defined_vars = get_defined_vars();а потім протестувати через array_key_exists('v', $defined_vars);.
Henrik Opel

1
Мені це виглядає трохи некрасиво, але у випадку, коли ви насправді перевіряєте елемент масиву, це має набагато більше сенсу: isset($foo[$bar])стаєarray_key_exists($bar, $foo)
Arild

property_existsздається перспективним, за винятком цього:> Функція property_exists () не може виявити властивості, магічно доступні за допомогою магічного методу __get.
alexw

@alexw Змінні, "створені" через __get, дійсно не існують. __get - це довільний код, який використовується як резервний запас для неіснуючих змінних, який може повертати все, що завгодно, незалежно від того, чи зберігалися будь-які відповідні дані.
Brilliand

46

Спроба дати огляд різних дискусій та відповідей:

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


Щодо класів та властивостей, на жаль, property_exists () не працює, коли властивість є масивом, наприклад: Class {public $ property = array ()}. Здається помилку.
Ендрю

1
@Andrew Здається, що для мене добре працює: 3v4l.org/TnAY5 Догляд, щоб надати повний приклад?
IMSoP

так, здається, працює нормально, щось не так було з моїм налаштуванням. Вибачте за помилкову тривогу :)
Ендрю

20

Іноді я трохи втрачаюсь, намагаючись зрозуміти, яку операцію порівняння використовувати в тій чи іншій ситуації. isset()стосується лише неініціалізованих або явно нульових значень. Передача / призначення нуля - це чудовий спосіб забезпечити логічне порівняння, як працює.

Тим не менше, трохи важко подумати, тому ось проста матриця, яка порівнює, як різні значення будуть оцінені різними операціями:

|           | ===null | is_null | isset | empty | if/else | ternary | count>0 |
| -----     | -----   | -----   | ----- | ----- | -----   | -----   | -----   |
| $a;       | true    | true    |       | true  |         |         |         |
| null      | true    | true    |       | true  |         |         |         |
| []        |         |         | true  | true  |         |         |         |
| 0         |         |         | true  | true  |         |         | true    |
| ""        |         |         | true  | true  |         |         | true    |
| 1         |         |         | true  |       | true    | true    | true    |
| -1        |         |         | true  |       | true    | true    | true    |
| " "       |         |         | true  |       | true    | true    | true    |
| "str"     |         |         | true  |       | true    | true    | true    |
| [0,1]     |         |         | true  |       | true    | true    | true    |
| new Class |         |         | true  |       | true    | true    | true    |

Для розміщення таблиці я трохи стиснув мітки:

  • $a; відноситься до оголошеної, але непризначеної змінної
  • все інше в першому стовпці посилається на призначене значення, наприклад:
    • $a = null;
    • $a = [];
    • $a = 0;
  • стовпці посилаються на операції порівняння, наприклад:
    • $a === null
    • isset($a)
    • empty($a)
    • $a ? true : false

Усі результати булеві, trueнадруковані та falseопущені.

Ви можете запустити тести самостійно, перевірити цю суть:
https://gist.github.com/mfdj/8165967


Можливо, поза межами цього питання, але ви можете додати "0"до таблиці, для повноти та ясності emptyоперації
Rik Schaaf

17

Ви можете використовувати компактну мовну конструкцію для перевірки наявності нульової змінної. Змінні, які не існують, не відображатимуться в результаті, тоді як нульові значення відображатимуться.

$x = null;
$y = 'y';

$r = compact('x', 'y', 'z');
print_r($r);

// Output:
// Array ( 
//  [x] => 
//  [y] => y 
// ) 

У випадку вашого прикладу:

if (compact('v')) {
   // True if $v exists, even when null. 
   // False on var $v; without assignment and when $v does not exist.
}

Звичайно, для змінних у глобальній області ви також можете використовувати array_key_exists ().

До речі, я б уникав таких ситуацій, як чума, де є семантична різниця між змінною, що не існує, та змінною, що має нульове значення. PHP і більшість інших мов просто не вважають, що існує.


3
PHP не робить, але я б не сказав, що більшість інших мов не мають. Більшість будь-якої мови, яка оголошує змінні, призведе до помилки, якщо змінна не була оголошена, але ви можете встановити їх NULL. Семантично NULLмає означати "відсутність ресурсу", але не визначення змінної є помилкою програміста.
М Міллер

1
@MMiller Безумовно, але писати код, який слідує одним шляхом у випадку "відсутність ресурсу" та інший шлях у випадку "помилки програміста", є досить безглуздим. Якщо ви хочете виявити незадекларовані змінні під час налагодження, використовуйте інструмент статичного аналізу, як ви хочете знайти потенційні помилки на будь-якій мові.
IMSoP

@MMiller, Класно, як ти навіть про це думав.
Pacerier

1
@MMiller Але це не працює як спростування, оскільки висловлювання у відповіді явно про "змінну не існує", а ваш зустрічний приклад - про властивість об'єкта / хеш-ключ не існує . Відмінність цих випадків не просто випадкова.
IMSoP

1
@MMiller - це справді кращий приклад. І все-таки після 20+ років програмування строгими мовами ситуації, коли мені потрібні були розрізнення undefinedі nullтакі рідкісні, що я цього не пропускаю. IMHO, головне використання для цього undefined- "помилка програміста на не строгій мові". Суворою мовою, якщо мені потрібен окремий стан client did not state a value, я оголошую значення, відповідне ситуації, і перевіряю його. Найгірший випадок, потрібно додати окрему змінну прапора. Але робити це рідко - краще, ніж ВЖЕ впоратися з ДВОМИ різними нецінні станами !!
ToolmakerSteve

15

Пояснення NULL, логічно мислення

Я думаю, що очевидною відповіддю на все це є ... Не ініціалізуйте свої змінні як NULL, не задумайте їх як щось, що стосується того, що вони мають намір стати.

Поводьтеся з NULL належним чином

NULL слід трактувати як "неіснуюче значення", що означає значення NULL. Змінна не може бути класифікована як існуюча для PHP, оскільки їй не було сказано, яким типом сутності вона намагається бути. Можливо, це ще не існує, тому PHP просто каже: "Чудово, це не так, тому що це все одно не має сенсу, і NULL - це мій спосіб сказати це".

Суперечка

Давайте зараз сперечаємось. "Але NULL - це як сказати 0 або FALSE або" ".

Неправильно, 0-FALSE- '' все ще класифікуються як порожні значення, але вони вказані як певний тип значення або заздалегідь визначений відповідь на питання. FALSE - це відповідь "так" або "ні ", - це відповідь на назву, яку хтось подав, а 0 - відповідь на кількість або час тощо. Вони встановлюються як певний тип відповіді / результату, що робить їх дійсними як встановлені.

NULL - це просто не відповідь ніколи, він не каже нам так чи ні, і не говорить нам час, і не повідомляє нам, що подано порожню рядок. Це основна логіка в розумінні NULL.

Підсумок

Справа не в створенні дурних функцій, щоб подолати проблему, це просто змінити погляд вашого мозку на NULL. Якщо це NULL, припустимо, що він не встановлений як нічого. Якщо ви заздалегідь визначаєте змінні, то попередньо визначте їх як 0, FALSE або "", залежно від типу використання, який ви маєте для них.

Сміливо цитуйте це. Це поза моєю логічною головою :)


5
Чудова відповідь. Так багато разів я бачу людей, які скачуть про те, як вони ненавидять ту чи іншу особливість мови. Але вони, здається, припускають, що "якщо це не робиться МОЄМО способом, значить, це порушено". Так, є погані дизайнерські рішення. Але є і дуже пильні розробники!
curtisdf

23
Є ВЕЛИЧЕЗНА різниця між неустановленою змінною та змінною === null. Один не існує, інший має значення null. Аргументи, що null означає відсутність значення, просто не відповідають дійсності. Null ЗНАЧЕННЯ типу null. Це абсолютно допустиме значення, і немає ніяких причин для php трактувати це як неіснуюче значення, що це, на жаль, робить. Було б добре, якби неіснуючі змінні були нульовими, а кожна існуюча змінна не була нульовою, а призначення нуля в змінну зніме її. Але є МНОГО ситуацій, коли функції повертають null як фактичне значення. Тоді нас обгризають, бо немає кривавого способу перевірити це.
енрі

2
Я знаю, що ми "не повинні" перевіряти існування змінної у php, пекло, немає навіть реального способу перевірити це. Я не збираюся писати код, який залежає від нього, тому що це неможливо в php. Це обмеження php. Очевидно є різниця між невстановленою та нульовою змінною, але php не дає способів їх розрізнити. Але багато мета-функціоналу залежить від цього: це означає, що читання неіснуючих вар видає повідомлення, isset($a['x'])скаже вам помилкове xзначення, але воно з'явиться в count($a).. compactбуде працювати над усіма наборами змінних, у тому числі nullsтощо.
енрі

3
Ця відповідь є хибною в одному з головних способів: в програмуванні OO null є логічним вибором, щоб означати "немає об'єкта". Наприклад, у надзвичайних обставинах, коли функція може повернути об'єкт або немає об'єкта, нульовим є очевидний вибір. Технічно в PHP може використовуватися хибне або будь-яке інше значення, яке вважається помилковим у булевому контексті, але тоді ви втрачаєте деяку смислову чистоту. Таким чином, null є цілком розумним значенням для ініціалізації змінної, яка повинна містити об'єкт у підсумку, оскільки це має відношення до того, що він має стати.
chazomaticus

3
Поки PHP видає помилки для невизначених змінних, але не для null, тоді є різниця. Якщо null та undefined були дійсно однаковою концепцією, тоді PHP повинен вважати, що за замовчуванням не визначені / недекларовані змінні зникають на нуль і ніколи не видаватимуть помилки, але ніхто не хоче цього, оскільки це кошмар розвитку. Нульові та невизначені можуть не дуже відрізнятися в контексті семантики значень, але вони сильно відрізняються, коли мова йде про написання чіткого та налагоджуваного коду.
Кріс Міддлтон

9

Властивості об'єкта можуть бути перевірені на наявність об'єктів property_exists

Приклад одиничного тесту:

function testPropertiesExist()
{
    $sl =& $this->system_log;
    $props = array('log_id',
                   'type',
                   'message',
                   'username',
                   'ip_address',
                   'date_added');

    foreach($props as $prop) {
        $this->assertTrue(property_exists($sl, $prop),
                           "Property <{$prop}> exists");
    }
}

4

Як додаток до великого обговорення великого значення того, що означає NULL , розглянемо, що насправді означає "існування змінної".

На багатьох мовах вам потрібно чітко оголосити кожну змінну, перш ніж її використовувати ; це може визначати його тип, але важливіше, що він оголошує сферу його застосування . Змінна "існує" скрізь у своїй області застосування, і ніде поза нею - будь то ціла функція, чи єдиний "блок".

У межах своєї області змінна присвоює деяке значення мітці, яку ви вибрали програміст. Поза межами сфери застосування ця мітка є безглуздою (чи використовуєте ви ту саму мітку в іншому обсязі - це в основному не має значення).

У PHP змінні декларувати не потрібно - вони оживають, як тільки вам знадобляться. Коли ви пишете в змінну вперше, PHP виділяє запис у пам'яті для цієї змінної. Якщо ви читаєте зі змінної, яка наразі не має записи, PHP вважає, що ця змінна має значення NULL.

Однак автоматичні детектори якості коду, як правило, попереджають вас, якщо ви використовуєте змінну, не спочатку "ініціалізуючи" її. По-перше, це допомагає виявляти помилки друку, такі як присвоєння, $thingIdале читання з $thing_id; але по-друге, воно змушує вас розглянути сферу, над якою ця змінна має значення, як саме декларація.

Будь-який код, який переймається наявністю змінної "існує", є частиною сфери цієї змінної - незалежно від того, була вона ініціалізована чи ні, ви як програміст надали значення цієї мітки в цій точці коду. Оскільки ви його використовуєте, він у певному сенсі повинен "існувати", а якщо він існує, він повинен мати неявне значення; в PHP це неявне значення null.

Через те, як працює PHP, можна написати код, який розглядає простір імен існуючих змінних не як область міток, якій ви дали значення, а як якусь сховище ключових значень. Ви можете, наприклад, запустити такий код: $var = $_GET['var_name']; $$var = $_GET['var_value'];. Тільки те, що ти можеш, не означає, що це гарна ідея.

Виявляється, PHP має набагато кращий спосіб представлення сховищ ключових значень, який називається асоціативними масивами. І хоча значення масиву можна трактувати як змінні, ви також можете виконувати операції над масивом в цілому. Якщо у вас є асоціативний масив, ви можете перевірити, чи містить він ключ, використовуючи array_key_exists().

Ви також можете використовувати об'єкти аналогічним чином, динамічно встановлюючи властивості, і в цьому випадку ви можете використовувати property_exists()точно так само. Звичайно, якщо ви визначаєте клас, ви можете оголосити , які властивості він - ви навіть можете вибрати між public, privateі protectedобсяг.

Хоча існує технічна різниця між змінною (на відміну від ключа масиву або властивості об'єкта), яка не була ініціалізована (або яка була явно unset()), і тією, значення якої є null, будь-яким кодом, який вважає цю різницю значимою. використовує змінні таким чином, що вони не призначені для використання.


1
Дуже хороші бали, хоча не зовсім відповідь на питання.
chazomaticus

1
На чітке запитання "Як нам надійно перевірити наявність змінної в PHP?" моя відповідь - "ти не, і ось чому". І ця відповідь, і великімасивні також відповідають на неявне запитання "чому так isset()поводиться?".
IMSoP

"Якщо ви читаєте зі змінної, яка наразі не має запису, PHP вважає, що ця змінна має значення NULL." Це помилково. Невизначена змінна просто не визначена. При спробі отримати доступ він може повернутись до нуля, але це не має значення.
Гюго Цинк

@HugoZink Не має відношення до чого? Будь-який тест, який ви робите зі значення невизначеної змінної, скаже вам, що це значення null. Чи існує ця цінність, перш ніж поглянути на неї, це питання для філософів, але, що стосується будь-якої спостережуваної поведінки, це значення послідовно null.
IMSoP

3

issetперевіряє, чи встановлена ​​змінна, і якщо так, чи не є її значення NULL. Остання частина, на мій погляд, не входить у сферу цієї функції. Немає гідного вирішення для визначення того, чи є змінною NULL, тому що вона не встановлена або тому, що вона явно встановлена ​​на NULL .

Ось одне можливе рішення:

$e1 = error_get_last();
$isNULL = is_null(@$x);
$e2 = error_get_last();
$isNOTSET = $e1 != $e2;
echo sprintf("isNOTSET: %d, isNULL: %d", $isNOTSET, $isNULL);

// Sample output:
// when $x is not set: isNOTSET: 1, isNULL: 1
// when $x = NULL:     isNOTSET: 0, isNULL: 1
// when $x = false:    isNOTSET: 0, isNULL: 0

Іншим способом вирішення є тестування виходу get_defined_vars():

$vars = get_defined_vars();
$isNOTSET = !array_key_exists("x", $vars);
$isNULL = $isNOTSET ? true : is_null($x);
echo sprintf("isNOTSET: %d, isNULL: %d", $isNOTSET, $isNULL);

// Sample output:
// when $x is not set: isNOTSET: 1, isNULL: 1
// when $x = NULL:     isNOTSET: 0, isNULL: 1
// when $x = false:    isNOTSET: 0, isNULL: 0

2

Я не згоден з вашими міркуваннями про NULL , і сказати, що вам потрібно змінити свою думку про NULL - просто дивно.

Я думаю, що isset () не був розроблений правильно, isset () повинен повідомити вам, чи була встановлена ​​змінна, і вона не повинна стосуватися фактичного значення змінної.

Що робити, якщо ви перевіряєте значення, повернені з бази даних, і один з стовпців має значення NULL, ви все одно хочете знати, чи існує воно, навіть якщо значення NULL ... тут не встановлюється довіра ().

аналогічно

$a = array ('test' => 1, 'hello' => NULL);

var_dump(isset($a['test']));   // TRUE
var_dump(isset($a['foo']));    // FALSE
var_dump(isset($a['hello']));  // FALSE

isset () повинен був бути розроблений так:

if(isset($var) && $var===NULL){....

таким чином, ми залишаємо це програмісту перевірити типи, а не залишати його до isset (), припускати його не там, оскільки значення NULL - його просто дурний дизайн


Ваш приклад - не перевірка існування змінної, а ключа масиву. Рішення для цього існує у вигляді array_key_exists. Ви ніколи не повинні опинятися в ситуації, коли під час виконання не знаєте, чи існує фактична змінна.
IMSoP

@chazomaticus Ну, ви ніколи не повинні опинятися в ситуації, коли register_globals увімкнено, тому я стою за цим твердженням.
IMSoP

О, я згоден. Тим не менш, не кожен може контролювати, де розгорнутий їх код. Корисно мати інформацію для кожної ситуації, незалежно від того, як це має бути чи ні.
chazomaticus

@chazomaticus Якщо проблема у вас register_globals, то ваша відповідь не є зміною isset(). У посібнику PHP зазначається, що "це взагалі хороша практика програмування спочатку ініціалізувати змінні", яка вирішується register_globalsна час проектування, а не на час виконання. Існує також запитання FAQ, яке дає unregister_globals()функцію для вирішення цього питання під час виконання.
IMSoP

2

Я збираюся додати до цього швидкі два центи. Однією з причин цього питання є заплутаною є те, що, здається, цей сценарій повертає той самий результат, коли повідомлення про помилки не повністю:

$a = null;
var_dump($a); // NULL
var_dump($b); // NULL

З цього результату можна припустити, що різниця між $a = nullі зовсім не визначальною $bє нічим.

Повідомлення про помилки кривошипу:

NULL

Notice: Undefined variable: b in xxx on line n
NULL

Примітка: вона кинула невизначену помилку змінної, але вихідне значення var_dumpвсе ще є NULL.

PHP очевидно має внутрішню здатність відрізняти нульову змінну від невизначеної змінної. Мені здається, що для перевірки цього має бути вбудована функція.

Я вважаю, що прийнята відповідь здебільшого хороша, але якби я збирався її реалізувати, я б написав обгортку для неї. Як було сказано раніше у цій відповіді , я маю згоду, що я фактично не стикався з ситуацією, коли це було проблемою. Я, здається, майже завжди опиняється у сценарії, коли мої змінні або встановлені, і визначені, або їх немає (невизначені, не встановлені, нульові, порожні тощо). Не кажучи про те, що така ситуація не виникне в майбутньому, але, як це здається, досить унікальне питання, я не здивований, що розробники PHP не покладалися на це.


Попередження про невизначені змінні є натяком програмісту, що вони зробили щось не так у коді. Поза налагодженням (для якого є інструменти поза мовою) ніколи не повинно виникати потреби в програмі виявлення такого стану, оскільки програміст завжди повинен знати, які змінні вони декларують.
IMSoP

1

Якщо я запускаю наступне:

echo '<?php echo $foo; ?>' | php

Я отримую помилку:

PHP Notice:  Undefined variable: foo in /home/altern8/- on line 1

Якщо я запускаю наступне:

echo '<?php if ( isset($foo) ) { echo $foo; } ?>' | php

Я не отримую помилки.

Якщо у мене є змінна, яку слід встановити, я зазвичай роблю щось на зразок наступного.

$foo = isset($foo) ? $foo : null;

або

if ( ! isset($foo) ) $foo = null;

Таким чином, пізніше в сценарії я можу сміливо використовувати $ foo і знати, що він "встановлений", і що він за замовчуванням призначає нуль. Пізніше я можу, if ( is_null($foo) ) { /* ... */ }якщо мені потрібно і точно знати, що змінна існує, навіть якщо вона є нульовою.

Повна документація про набір читає трохи більше, ніж лише те, що було вкладено спочатку. Так, він повертає false для змінної, яка раніше була встановлена, але тепер є нульовою, але також повертає false, якщо змінна ще не була встановлена ​​(ніколи), а також для будь-якої змінної, яка була позначена як не встановлена. Він також зазначає, що байт NULL ("\ 0") не вважається нульовим і поверне істинним.

Визначте, чи встановлена ​​змінна.

Якщо змінна була знята з unset (), вона більше не буде встановлена. isset () поверне FALSE, якщо тестує змінну, встановлену на NULL. Також зауважте, що байт NULL ("\ 0") не еквівалентний постійній PHP NULL.


Він отримав документи від цього посилання. Прочитайте перше речення, другий абзац розділу опису за наданим вами посиланням. Це саме те, що він цитував вище.
Зоредаче

Це не є поганою практикою для простих сценаріїв, але в складних (наприклад, великих OO) проектах це стає нездійсненним. Крім того, як я вже говорив вище, is_null () повертає TRUE для змінних, які не встановлені, тому насправді немає причин робити те, що ви говорите, окрім уникнення попередження PHP.
chazomaticus

1
Для добре розроблених проектів "великих ОО", чому це взагалі буде проблемою? Чому ви коли-небудь матимете $ foo в тілі методу, який, можливо, не був встановлений до його першого використання?
Beau Simensen

1

Спробуйте використовувати

unset($v)

Здається, єдиний час, коли змінна не встановлюється, це коли вона спеціально не встановлена ​​($ v). Здається, ваш зміст "існування" відрізняється від визначення PHP. NULL, безумовно, існує, це NULL.


Я не впевнений, що ти маєш на увазі. Якщо у вас є масив з одним елементом 'a', вам не доведеться знімати () елемент 'b', щоб елемент 'b' не існував у PHP, він просто не існує. Те саме стосується, наприклад, глобальних змінних, які можна розглядати як елементи масиву $ GLOBALS.
chazomaticus

1
Але я згоден, що змінна зі значенням NULL насправді існує.
chazomaticus

0

Я повинен сказати, що за всі мої програми програмування PHP я ніколи не стикався з проблемою isset()повернення false на нульовій змінній. OTOH, у мене виникли проблеми з помилкою isset()запису нульового масиву - але array_key_exists()в цьому випадку працює правильно.

Для деякого порівняння Icon явно визначає невикористовувану змінну як повернуту, &nullтому ви використовуєте тест is-null в Icon, щоб також перевірити наявність не зміненої змінної. Це робить речі простішими. З іншого боку, Visual BASIC має кілька станів для змінної, яка не має значення (Null, Empty, Nothing, ...), і вам часто доводиться перевіряти наявність декількох з них. Це, як відомо, є джерелом помилок.


0

Відповідно до Посібника з PHP для функції порожній (), "Визначте, чи вважається змінною порожньою змінну. Змінна вважається порожньою, ЯКЩО НЕ ІСНУЄТЬСЯ, або якщо її значення дорівнює FALSE. Empty () не генерує попередження, якщо змінна не існує. " (Мій наголос.) Це означає, що функція empty () повинна кваліфікуватися як "найкращий спосіб перевірити існування змінної в PHP", відповідно до заголовкового запитання.

Однак це недостатньо добре, оскільки функцію порожній () можна обдурити змінною, яка існує, і встановлена ​​на NULL.

Я перериваю свою попередню відповідь, щоб представити щось краще, тому що це менш громіздко, ніж моя оригінальна відповідь (яка йде за цим перериванням, для порівняння).

  function undef($dnc) //do not care what we receive
  { $inf=ob_get_contents();             //get the content of the buffer
    ob_end_clean();                     //stop buffering outputs, and empty the buffer
    if($inf>"")                         //if test associated with the call to this function had an output
    { if(false!==strpos($inf, "Undef"); //if the word "Undefined" was part of the output
        return true;                    //tested variable is undefined
    }
    return false;                       //tested variable is not undefined
  }

Дві прості рядки коду можуть використовувати вищевказану функцію, щоб виявити, чи змінна не визначена:

  ob_start();                           //pass all output messages (including errors) to a buffer
  if(undef($testvar===null))            //in this case the variable being tested is $testvar

Ви можете слідувати за цими двома рядками будь-яким підходящим, як-от цей приклад:

    echo("variable is undefined");
  else
    echo("variable exists, holding some value");

Я хотів поставити виклик ob_start () та ($ testvar === null) всередині функції, і просто передати змінну функції, але вона не працює. Навіть якщо ви спробуєте використати змінну "передати посилання" на функцію, змінна BECOMES визначена, і функція ніколи не може виявити, що вона раніше не була визначена. Тут представлено компроміс між тим, що я хотів зробити, і тим, що насправді працює.

Попереднє означає, що існує інший спосіб завжди уникати попадання в повідомлення про помилку "Не визначена змінна". (Припущення тут полягає в тому, що запобігання такого повідомлення є причиною, чому ви хочете перевірити, чи не вказана змінна.)

   function inst(&$v) { return; }  //receive any variable passed by reference; instantiates the undefined

Просто зателефонуйте до цієї функції, перш ніж зробити щось своєму $ testvar:

   inst($testvar);                //The function doesn't affect any value of any already-existing variable

Звичайно, значення новоінстанційованої змінної встановлено на нульове значення!

(Перерва закінчується)

Отже, вивчивши та експериментуючи, тут є щось гарантоване:

 function myHndlr($en, $es, $ef, $el)
 { global $er;
   $er = (substr($es, 0, 18) == "Undefined variable");
   return;
 }

 $er = false;
 if(empty($testvar))
 { set_error_handler("myHndlr");
   ($testvar === null);
   restore_error_handler();
 }
 if($er)  // will be 1 (true) if the tested variable was not defined.
 { ; //do whatever you think is appropriate to the undefined variable
 }

Пояснення: змінна $ er ініціалізується до значення за замовчуванням "немає помилки". Визначена "функція обробника". Якщо $ testvar (змінна, яку ми хочемо знати, не визначена чи ні), проходить попередній тест функції порожнього (), то робимо більш ретельний тест. Ми називаємо функцію set_error_handler () для використання раніше визначеної функції обробника. Тоді ми робимо просте порівняння ідентичності із залученням $ testvar, ЯКЩО НЕ ВКАЗАНО, БУДЕ ТРИГУВАТИ ПОМИЛКУ. Функція обробника фіксує помилку і спеціально перевіряє, чи є причиною помилки той факт, що змінна не визначена. Результат розміщується в змінній інформації про помилки $ er, яку ми можемо пізніше перевірити, щоб зробити все, що хочемо, в результаті точно знати, чи визначено $ testvar чи ні. Оскільки нам потрібна функція обробника лише для цієї обмеженої мети, ми відновлюємо початкову функцію обробки помилок. Функцію "myHndlr" потрібно оголосити лише один раз; інший код можна скопіювати у будь-які місця, де підходить, для $ testvar або будь-якої іншої змінної, яку ми хочемо перевірити таким чином.


1
Якщо наміром є уникнути попередження про те, що ваші змінні не були оголошені, то рішенням є виправити ваш код, щоб оголосити їх належним чином. Ваша instфункція в основному схожа на @оператора з усунення помилок: "Я знаю, що я щось тут помиляюсь, але я просто хочу, щоб це повідомлення пройшло, не фактично жодним чином змінюючи роботу мого коду".
IMSoP

З іншого боку, методи виявлення геніальні, але я все-таки твердо переконаний, що ніколи не слід використовувати для них інше, ніж повторити ті самі попереджувальні повідомлення, які вони ловлять. (Вам, мабуть, слід уточнити, що ваша версія вихідної буферизації вимагає ввімкнення
помилок_репортажу високих помилок та помилок

0

Я думаю , що єдине повне рішення є звіт повідомлення з

error_reporting(E_ALL); // Enables E_NOTICE

Але вам доведеться виправити всі повідомлення, створені невизначеними змінними, константами, ключами масиву, властивостями класу серед інших. Після того, як ви зробите це, вам не доведеться турбуватися про різницю між нульовими та недекларованими змінними, і неоднозначність зникає.

Увімкнення звітності про сповіщення може бути не гарною альтернативою у будь-яких ситуаціях, але для цього є вагомі причини:

Чому я повинен виправляти помилки E_NOTICE?

У моєму випадку більше року працював у проекті без нього, але звик бути обережним щодо декларування змінних, тому він швидко переходив.


0

Єдиний спосіб дізнатися, чи визначена змінна в поточній області ( $GLOBALSне є надійною) - це array_key_exists( 'var_name', get_defined_vars() ).


1
Я думаю, що це казали багато інших людей раніше, чи я помиляюся?
Стефан Віеркант

-1

Я вважаю за краще використовувати не порожній як найкращий метод, щоб перевірити наявність змінної, яка a) існує, і b) не є нульовою.

if (!empty($variable)) do_something();

2
empty()не перевіряє , якщо змінна дорівнює нулю, він перевіряє , є чи воно помилкове-у, наприклад , не одне з ""(порожній рядок), 0(0 як ціле число), 0.0(0 , як поплавок), "0"(0 у вигляді рядка), NULL, FALSE, array()(порожній масив) та $var;(оголошена змінна, але без значення). Скажімо, у вас є потрібне радіополе у ​​формі з двома входами зі значеннями 0та 1. Якщо ви використовуєте empty()для перевірки, і користувач вибирає його 0, ви ненавмисно помилитесь, "необхідне поле не може бути порожнім". Дивіться посібник php.net/manual/en/function.empty.php
Halil Özgür
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.