Использование функций isset() и array_key_exists()

Навеяно проблемой, с которой я столкнулся из-за неправильного использования функции isset() в одном китайском движке интернет магазина. Там isset() применялась для определения существования элемента массива с некоторым ключом. Самое неприятное в данной ситуации то, что такую ошибку допускают многие. Это не первый случай, когда isset() используется именно в таком контексте, хотя это в корне неверно. Но в этот раз неграмотность разработчиков стоила мне потерянного времени, что и сподвигло меня на эту заметку.

Еще больше меня «порадовали» комментарии одного «незнакомого» знакомого, который удивился моему негодованию. Как оказалось, о существовании функции array_key_exists() он что-то слышал, но сам никогда ею не пользовался. Более того, где-то читал, что ее лучше заменять на isset(), так как последняя работает быстрее.

Собственно, об уровне квалификации PHP программистов сказано немало и добавить к этому нечего.




Инициализация и удаление переменной в PHP

В PHP переменная инициализируется в момент присваивания ей значения. Неинициализированной переменная считается в двух случаях:

  • ей не присваивалось никакого значения;
  • ей было присвоено значение типа NULL или она была передана функции unset().

Присваивание NULL значения удаляет переменную, аналогично функции unset(), то есть делает ее неинициализированной.

Если попытаться использовать переменную, которая была удалена с помощью unset(), будет сгенерирована ошибка уровня E_NOTICE. С переменной, приведенной к NULL, такого не произойдет.

Функция isset()

Функция проверяет факт инициализации переменной. isset() вернет false, если передать ей в качестве параметра неинициализированную переменную. Тоже самое произойдет и с ключом массива, который имеет NULL значение. Для ключей массива действуют такие же правила приведения к NULL, как и для переменных.

$nullVar = null;

var_dump($nullVar);

$array = array(
    'key_str' => 'foo',
    'key_null' => null,
    'key_int' => 1
);

var_dump($array);

Результат:

null

array
  'key_str' => string 'foo' (length=3)
  'key_null' => null
  'key_int' => int 1

Функция array_key_exists()

Работа функции array_key_exists() похожа на isset(), но с той лишь разницей, что даже для ключа с NULL значением будет возвращено TRUE.

$array = array(
    'key_str' => 'foo',
    'key_null' => null,
    'key_int' => 1
);

var_dump(array_key_exists('key_null', $array)); // Вернет boolean true

Чтобы полностью удалить пару ключ-значение необходимо использовать unset().

Для массива использование NULL значения ключа удобно, если наличие ключа требуется сохранить. Можно будет рассчитывать на контекст, в котором данный ключ будет использован. Например, это удобно при заполнении полей формы дефолтными значениями. Например, при использовании конструкции echo, NULL значение будет приведено к пустой строке.

То, к чему я клоню, мне кажется очевидным. Если вы работаете с ключами массива, то используйте именно ту функцию, которая для этого предназначена. Ко всему прочему, использование array_key_exists() позволяет точно понять логику приложения. Никакой двусмысленности не возникает.

Скорость работы isset() и array_key_exists()

О том, что array_key_exists() работает медленнее isset() пишут даже в комментариях к соответствующей странице PHP мануала. Жалуются на то, что более медленная работа array_key_exists() заметна на массивах с количеством пар ключ-значение, превышающих 200.

Ничего не остается, как прогнать банальный тест:

$testArray = array(
    883209 => 568420,
    553314 => 266114,
    133088 => 367615,
    // ...
);

$timerStart = microtime();

for ($i = 0; $i < 100000; $i++) {
    //if (isset($testArray[rand(1, 99999)]));
    if (array_key_exists(rand(1, 99999), $testArray));
};

$timerEnd = microtime();

echo $timerEnd - $timerStart;

Ассоциативный массив $testArray содержит 10 000 элементов. Это несколько больше, чем упомянутые 200.

Далее прогоняем цикл с количеством итераций 100 000. В теле цикла, по очереди, сначала делаем проверку с помощью isset(), затем с помощью array_key_exists(). Ключ, наличие которого проверяем, генерируется случайным образом, чтобы свести к минимуму вероятность его существования в массиве.

isset() array_key_exists()
0.687027 0.728652

Тест, безусловно, очень простой и достаточно субъективный. Было бы неплохо сделать замеры используемой памяти, например. Но глядя на результаты этого теста, у меня нет никакого желания продолжать заниматься мышиной возней. Очевидно, что выигрыша в скорости попросту нет.

Функция isset() и свойства объекта

Совсем клиническим случаем можно считать использование isset() для проверки существования свойства у объекта.

Не так часто, как в случае с массивами, но доводится встречать подобные условия:

class Foo
{
    public $property = null;

    public function __construct() {
        
    }
}

$object = new Foo();

if (!isset($object->property)) {
    echo 'property not exists';
}

Случай вопиющий, так как использование NULL в качестве дефолтного значения свойств – очень распространенная практика. И проверять наличие свойства объекта через isset() просто недопустимо.

Для проверки существования свойства у объекта в PHP существует функция property_exists(). Как и array_key_exists(), property_exists() вернет TRUE даже в том случае, если значение свойства приведено к NULL.

Также замечу, что для использования property_exists() не требуется наличие объекта класса. Проверку можно произвести по имени класса и имени свойства.

class Foo
{
    public $property = null;

    public function __construct() {
        
    }
}

var_dump(property_exists('Foo', 'property')); // Вернет true

Такое вот занимательное программирование.

Комментарии (7)

  1. Виктор

    Вечер добрый!
    Очень классные статьи, большое спасибо за них!

    Насчет функции isset. А если такой случай — есть функция getUsers($data), которая возвращает пользователей из БД. Если передано значение $data[‘where’] (ассоциативный массив по которому происходит фильтрация), то произвести фильтрацию. У меня в коде if (isset($data[‘where’])), в данном случае я верно пишу? Т.к. мне нужно знать не только существует ли ключ но так же чтобы у него было значение. (Кстати, тогда даже наверно if (is_array($data[‘where’])) т.к. ф-ии нужно передавать именно ассоциативный массив)

    • Вы все верно поняли. На самом деле, практически повсеместно используется isset(). Иногда из-за этого возникают логические ошибки в приложениях, что и стало причиной написания данной заметки.

  2. максим

    Не надо людей вводить в заблуждение и навязывать свои ошибочные мысли.
    В мануале от создателей PHP явно сказано, что isset используется и для проверки элементов массива.
    И это правильно. А вот использовать элемент массива с NULL и считать этот элемент существующим, это ошибка.
    Вот из-за таких как ты и приходится потом сутками сидеть и разгребать больные коды и не понимать, почему же блин переменные NULL существуют.

    • Максим, во-первых, я вам вам не «ты». А во-вторых, мир сложнее, чем вам кажется и нужно беречь свои нервы, а то не хватит сил на разгребание больных кодов :))
      И если NULL значение в массиве — это ошибка, то это ошибка в интерпретаторе PHP. Но, думаю, дело все в том, что вы просто не понимаете смысл типа данных NULL.

      • RonBarhash

        Олег, вы не правы в ряде моментов. Не стоит «гнать» на PHP разработчиков, т.к. те кто ними являются, пишут хороший код. А то, что вы всех, кто написал строчку кода на PHP называете PHP-программистом, это лично ваши «психо-половые» проблемы. Второй момент, насчет NULL: в языке PHP смысл языковой конструкции null отличается от той же конструкции SQL … и не стоит и пытаться друг на друга «натягивать». Это не проблема языка, т.к. авторы языка вольны толковать как им вздумается «сколько людей, столько и мнений.». Третий момент, если Вы знаете, что PHP-разработчики пишут плохой код, то зачем вы его используете? Пишите сами или используете код других языков. Я могу понять, что писали вы пост под влиянием эмоций, но я так и не понял, что было не так в том коде «китайского движка» и что у вас не выходило? :) Напишите название движка, чтобы люди знали о том, и что тут могут вылезти проблемы при использовании и место возникновения проблемы.

  3. Александр

    На 100 000 итерации isset работает в 6 раз быстрее.

  4. Александр

    Ключевая проблема в твоем коде в rand. Там есть забор энтропии для реализации псевдо-рандома, который в свою очередь работает с ОС, на которой работает интерпретатор. Для корректного анализа убери его и узнаешь много интересного, что isset действительно много быстрее.

Добавить комментарий для максим Отменить ответ

Ваш e-mail не будет опубликован. Обязательные поля помечены *