О том, почему не достаточно проверки if( isset($test[«key»]) ) и почему такое условие может быть вычислено в true, хотя $test[«key»] не существует .
Столкнулся с такой проверкой в Zend Framework. Сразу после выхода версии 1.5 я решил попробовать использовать компонент Zend_Form и протестировать его на примере из мануала.
Тестовый скрипт выводил ошибку при навешивании валидации на форму (Fatal error: Cannot unset string offsets). Или не успели обновить мануал, или ошибка в коде фрэймворка, но скрипт не работал. Во время исследования кода я заметил проверку на наличие ключа в ассоциативном массиве, которая сделана неправильно и может давать осечки.
Проверка была сделана следующим образом:
if( isset($test[«key»]) ) unset($test[«key»]);
С первого взгляда всё работает: если ключ есть, то удалить его. Но это верно, только если $test действительно массив!
Функция предполагает, что она получит массив, но из-за неточности в мануале в эту функцию была передана строка:
$test = «test string»;
if( isset($test[«key»]) ) unset($test[«key»]);
И в ходе выполнения этого кода выводится ошибка «Fatal error: Cannot unset string offsets», это происходит из-за того, что условное выражение вычисляется как true, якобы в массиве $test установлен ключ key, но удалить его не получается, т.к. это вовсе не массив, а строка.
В результате проверки происходит преобразование типов, из строкового в цифровой.
Зачем делается это преобразование?
Я думаю, что интерпретатор «думает» так:
$test — это строка, к которой пытаются обратиться как к массиву. Т.к. к символам строки можно обратиться как к элементам массива ($test[0], $test[1] и т.п.), то ключом является цифра, а в коде написана строка, значит надо преобразовать один тип в другой.
Что говорит документация про преобразование строки в число?
Преобразование строк в числа
Если строка распознается как числовое значение, результирующее значение и тип определяется так, как показано далее.
Строка будет распознана как float, если она содержит любой из символов ‘.’, ‘e’, или ‘E’. Иначе она будет определена как целое.
Значение определяется по начальной части строки. Если строка начинается с верного числового значения, будет использовано это значение. Иначе значением будет 0 (ноль).
В результате код «упрощается» до такого вида:
$test = «test string»;
if( isset($test[0]) ) unset($test[0]);
И проверка начинает работать не так, как предполагалось! Проверяется наличие первого знака в строке (вычисляется как true, т.к. первая буква есть) и далее следует попытка уничтожить (разустановить) первую букву строки, что и приводит к ошибке.
Вывод:
Перед проверкой на наличие ключа в массиве необходимо проверять, действительно ли это массив.
if(is_array($test) && isset($test[«test»])) unset($test[«test»]);
А как же array_key_exists?
Если учитывать, что:
if(is_array($test) && isset($test[«test»]))
ничем не отличается от:
if(is_array($test) && array_key_exists(«test», $test))
, но первый вариант работает в 3-4 раза быстрее, то мне больше нравится isset.
PS: И при этом, запись короче…
и автокомплит в Zend Studio дописывает isset после 3-х введенных знаков, а array_key_exists после 7-и 🙂
Спасибо, совет очень помог при вылавливании бага в проекте. 🙂 Сразу после прочтения сообразил, что массив из которого удаляется элемент на момент возникновения ошибки ещё вовсе не массив.
зачёт!
сам придумал проблему, сам решил ее))
да….зачётно
array_key_exists более корректен т.к. возвращает true на ключ элемента, значение которого = NULL. Чего не делает isset