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