Лучший способ проверить наличие переменной в PHP; isset () явно не работает - PullRequest
183 голосов
/ 06 января 2009

Из 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.)

Ответы [ 17 ]

95 голосов
/ 07 января 2009

Если переменная, которую вы проверяете, будет в глобальной области видимости, вы можете сделать:

array_key_exists('v', $GLOBALS) 
44 голосов
/ 06 сентября 2013

Попытка дать обзор различных обсуждений и ответов:

Не существует единого ответа на вопрос, который может заменить все способы использования isset. Некоторые варианты использования рассматриваются другими функциями, в то время как другие не выдерживают проверки или имеют сомнительная ценность вне кода гольф. Далекие от того, чтобы быть «сломанными» или «противоречивыми», другие случаи использования демонстрируют, почему реакция isset на null является логическим поведением.

Реальные варианты использования (с решениями)

1. Клавиши массива

Массивы можно рассматривать как наборы переменных, а unset и isset обрабатывают их так, как если бы они были. Однако, поскольку они могут быть повторены, подсчитаны и т. Д., Пропущенное значение не совпадает со значением, равным null.

Ответ в этом случае - использовать array_key_exists() вместо isset().

Поскольку для проверки в качестве аргумента функции требуется массив, PHP все равно будет выдавать «уведомления», если сам массив не существует. В некоторых случаях можно с полным основанием утверждать, что каждое измерение должно быть инициализировано первым, поэтому уведомление выполняет свою работу. В других случаях «рекурсивная» функция array_key_exists, которая поочередно проверяет каждое измерение массива, избежит этого, но в основном будет такой же, как @array_key_exists. Это также несколько касательно обработки значений null.

2. Свойства объекта

В традиционной теории «объектно-ориентированного программирования» инкапсуляция и полиморфизм являются ключевыми свойствами объектов; в реализации ООП на основе классов, такой как PHP, инкапсулированные свойства объявляются как часть определения класса и получают уровни доступа (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.

Функциональность, существовавшая только для прогнозирования поведения этих функций (например, "будет ли в массиве, возвращаемом get_defined_vars?" Ключ 'foo'), является излишней, поскольку вы можете просто запустить функцию и найти без вредных последствий.

* * 4a тысячи девяносто-пять. Переменные ($$foo)

Хотя это не совсем то же самое, что и функции, которые превращают набор переменных в ассоциативный массив, в большинстве случаев с использованием «переменных переменных» («присваивание переменной, названной на основе этой другой переменной»), можно и следует изменить, чтобы вместо этого использовать ассоциативный массив.

Имя переменной, по сути, является меткой, присваиваемой значению программистом; если вы определяете его во время выполнения, это не просто метка, а ключ в каком-то хранилище значений ключей. На практике, не используя массив, вы теряете возможность считать, повторять и т. Д .; также может оказаться невозможным иметь переменную «вне» хранилища значений ключей, поскольку она может быть перезаписана $$foo.

После изменения для использования ассоциативного массива код будет доступен для решения 1. Косвенный доступ к свойству объекта (например, $foo->$property_name) может быть решен с помощью решения 2.

5. isset гораздо проще набрать, чем array_key_exists

Я не уверен, что это действительно актуально, но да, имена функций PHP могут быть довольно длинными и непоследовательными. По-видимому, доисторические версии PHP использовали длину имени функции в качестве хеш-ключа, поэтому Rasmus сознательно составил имена функций, такие как htmlspecialchars, чтобы они имели необычное количество символов ...

Тем не менее, по крайней мере, мы не пишем Java, а? ;)

6. Неинициализированные переменные имеют тип

Страница справочника по основам переменных включает следующее утверждение:

Неинициализированные переменные имеют значение по умолчанию своего типа в зависимости от контекста, в котором они используются

Я не уверен, есть ли в движке Zend какое-то понятие «неинициализированный, но известный тип» или это слишком много читает в утверждении.

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

20 голосов
/ 29 декабря 2013

Иногда я немного теряюсь, пытаясь выяснить, какую операцию сравнения использовать в данной ситуации. 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

17 голосов
/ 28 декабря 2009

Вы можете использовать компактную языковую конструкцию для проверки существования нулевой переменной. Переменные, которые не существуют, не будут отображаться в результате, а нулевые значения будут отображаться.

$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 ().

B.t.w. лично я бы избегал таких ситуаций, как чума, где есть семантическая разница между переменной, которой нет, и переменной, имеющей нулевое значение. PHP и большинство других языков просто не думают, что есть.

15 голосов
/ 31 марта 2010

Объясняя NULL, логически думая

Полагаю, очевидный ответ на все это ... Не инициализируйте ваши переменные как NULL, инициализируйте их как нечто, относящееся к тому, для чего они предназначены.

Правильно относиться к NULL

NULL следует рассматривать как «несуществующее значение», что означает NULL. Переменная не может быть классифицирована как существующая для PHP, потому что ей не сказано, к какому типу сущностей она стремится быть. Возможно, он не существует, поэтому PHP просто говорит: «Хорошо, это не так, потому что в этом нет никакого смысла, и NULL - мой способ сказать это».

Аргумент

Давайте поспорим сейчас. «Но NULL - это все равно, что сказать 0 или FALSE или ''.

Неверно, 0-FALSE- '' все еще классифицируются как пустые значения, но они указаны как некоторый тип значения или предопределенный ответ на вопрос. FALSE - это ответ «да» или «нет», '' - это ответ на заголовок, отправленный кем-то, а 0 - ответ на количество или время и т. Д. Они УСТАНОВЛЕНЫ как некоторый тип ответа / результата, который делает их действительными как установленные.

NULL - это просто не ответ, как всегда, он не говорит нам, да или нет, и не говорит нам время, и не говорит нам, что пустая строка была отправлена. Это основная логика в понимании NULL.

Резюме

Речь идет не о создании дурацких функций, чтобы обойти проблему, а просто о том, как ваш мозг смотрит на NULL. Если это НЕДЕЙСТВИТЕЛЬНО, предположите, что это не установлено как ничто. Если вы предварительно определяете переменные, то предварительно определите их как 0, FALSE или "" в зависимости от типа использования, которое вы намереваетесь для них.

Не стесняйтесь цитировать это. Это вне моей логической головы:)

9 голосов
/ 24 января 2009

Свойства объекта могут быть проверены на наличие 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 голосов
/ 04 сентября 2013

В дополнение к обсуждению greatbigmassive того, что означает NULL , рассмотрим, что на самом деле означает "существование переменной".

Во многих языках вы должны явно объявлять каждую переменную перед ее использованием ; это может определять его тип, но, что более важно, оно объявляет scope . Переменная «существует» везде в своей области видимости, и нигде вне ее - будь то целая функция или отдельный «блок».

В своей области видимости переменная присваивает метку , которую вы, программист, выбрали. За пределами этой области эта метка не имеет смысла (если вы используете одну и ту же метку в другой области, то это не имеет значения).

В 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 scope.

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

3 голосов
/ 31 января 2013

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 голосов
/ 23 мая 2014

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

$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 не удосужились это вставить.

2 голосов
/ 28 января 2011

Я не согласен с вашим рассуждением о NULL , и сказать, что вам нужно изменить свое мнение о NULL, просто странно.

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

Что если вы проверяете значения, возвращаемые из базы данных, и один из столбцов имеет значение NULL, вы все равно хотите знать, существует ли оно, даже если значение равно NULL ... nope dont trust isset () здесь.

аналогично

$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 - просто глупый дизайн

...