Как избежать использования isset () и empty ()

98

У меня есть несколько старых приложений, которые выдают много сообщений «xyz is undefined» и «undefined offset» при работе на уровне ошибки E_NOTICE, потому что наличие переменных не проверяется явно с помощью isset()и consorts.

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

Однако мне не нравится то, что делают с моим кодом сотни isset() empty()и array_key_exists(). Он раздувается, становится менее читаемым, не приобретая ничего с точки зрения ценности или смысла.

Как я могу структурировать свой код без лишних проверок переменных, оставаясь при этом совместимым с E_NOTICE?

Пекка
источник
6
Я полностью согласен. Поэтому мне так нравится Zend Framework, там очень хорош модуль запросов. Если я работаю над небольшим приложением, я обычно кодирую простой класс запроса с магическими методами __set и __get, которые работают аналогично запросу ZF. Таким образом, я избегаю всех случаев использования isset и empty в моем коде. Таким образом, все, что вам нужно использовать, это либо if (count ($ arr)> 0) для массивов перед повторением по ним, либо if (null! == $ variable) в нескольких критических местах. Выглядит намного чище.
Ричард Кноп

Ответы:

128

Для тех, кто заинтересован, я расширил эту тему до небольшой статьи, в которой приведенная ниже информация представлена ​​в несколько лучше структурированной форме: Полное руководство по isset PHP и пусто


ИМХО, вам следует подумать не просто о том, чтобы сделать приложение «совместимым с E_NOTICE», а о реструктуризации всего этого. Наличие сотен точек в вашем коде, которые регулярно пытаются использовать несуществующие переменные, звучит как довольно плохо структурированная программа. Попытки получить доступ к несуществующим переменным никогда не должны происходить, другие языки отказываются от этого во время компиляции. Тот факт, что PHP позволяет вам это делать, не означает, что вы должны это делать.

Эти предупреждения призваны помочь вам, а не раздражать вас. Если вы получаете предупреждение «Вы пытаетесь работать с тем, чего не существует!» , ваша реакция должна быть "Ой, моя проблема, позвольте мне исправить это как можно скорее". Как еще вы собираетесь отличить «переменные, которые прекрасно работают без определения» и действительно неправильный код, который может привести к серьезным ошибкам ? Это также причина, по которой вы всегда, всегда разрабатываете с отчетом об ошибках, обращенным к 11, и продолжаете работать над своим кодом, пока не останется ни одногоNOTICEвыпущен. Отключение отчетов об ошибках предназначено только для производственных сред, чтобы избежать утечки информации и улучшить взаимодействие с пользователем даже при наличии ошибок в коде.


Чтобы уточнить:

Вы всегда будете нуждаться issetили emptyгде-то в вашем коде, единственный способ уменьшить их появление - правильно инициализировать ваши переменные. В зависимости от ситуации это можно сделать разными способами:

Аргументы функции:

function foo ($bar, $baz = null) { ... }

Нет необходимости проверять, установлены ли $barили $bazвнутри функции, потому что вы просто устанавливаете их, все, о чем вам нужно беспокоиться, это оценивать их значение как trueили false(или что-то еще).

Обычные переменные где угодно:

$foo = null;
$bar = $baz = 'default value';

Инициализируйте свои переменные в верхней части блока кода, в котором вы собираетесь их использовать. Это решает !issetпроблему, гарантирует, что ваши переменные всегда имеют известное значение по умолчанию, дает читателю представление о том, над чем будет работать следующий код, и тем самым также служит своего рода самодокументированием.

Массивы:

$defaults = array('foo' => false, 'bar' => true, 'baz' => 'default value');
$values = array_merge($defaults, $incoming_array);

То же, что и выше, вы инициализируете массив значениями по умолчанию и перезаписываете их фактическими значениями.

В остальных случаях, скажем, шаблон, в котором вы выводите значения, которые могут или не могут быть установлены контроллером, вам просто нужно проверить:

<table>
    <?php if (!empty($foo) && is_array($foo)) : ?>
        <?php foreach ($foo as $bar) : ?>
            <tr>...</tr>
        <?php endforeach; ?>
    <?php else : ?>
        <tr><td>No Foo!</td></tr>
    <?php endif; ?>
</table>

Если вы обнаружите, что регулярно используете array_key_exists, вам следует оценить, для чего вы его используете. Единственный раз, когда это имеет значение, здесь:

$array = array('key' => null);
isset($array['key']); // false
array_key_exists('key', $array); // true

Однако, как указано выше, если вы правильно инициализируете свои переменные, вам не нужно проверять, существует ли ключ или нет, потому что вы знаете, что это так. Если вы получаете массив из внешнего источника, то значение будет , скорее всего , не будет , nullно '', 0, '0', falseили нечто подобное, то есть значение , которое можно оценить с issetили empty, в зависимости от вашего намерения. Если вы регулярно устанавливаете ключ массива в значение nullи хотите, чтобы он означал что угодно, но false, например, если в приведенном выше примере разные результаты issetи array_key_existsимеют значение для логики вашей программы, вы должны спросить себя, почему. Само существование переменной не должно иметь значения, важно только ее значение. Если ключ - это true/ falseflag, используйтеtrueили falseнет null. Единственным исключением из этого правила могут быть сторонние библиотеки, которые хотят nullчто-то значить, но, поскольку их nullтак сложно обнаружить в PHP, мне еще предстоит найти какую-либо библиотеку, которая это делает.

обмануть
источник
4
Верно, но большинство неудачных попыток доступа происходят по типу if ($array["xyz"])вместо isset()или array_key_exists()которые я считаю в некоторой степени законными, определенно не структурными проблемами (поправьте меня, если я ошибаюсь). Добавление array_key_exists()для меня выглядит ужасной тратой.
Pekka
9
Я не могу придумать ни одного случая, когда бы я использовал array_key_existsвместо простого isset($array['key'])или !empty($array['key']). Конечно, оба добавляют в код 7 или 8 символов, но я бы не назвал это проблемой. Это также помогает прояснить ваш код: if (isset($array['key']))означает, что эта переменная действительно необязательна и может отсутствовать, тогда if ($array['key'])как означает просто «если истина». Если вы получите уведомление о последнем, вы знаете, что ваша логика где-то облажалась.
deceze
6
Я считаю, что разница между isset () и array_key_exists () заключается в том, что последний вернет true, если значение равно NULL. isset () не будет.
Htbaa
1
Верно, но я не мог придумать разумного варианта использования, когда мне нужно было бы различать несуществующую переменную и установленный ключ, значение которого равно нулю. Если значение оценивается как ЛОЖЬ, различие должно быть без различия. :)
deceze
1
Ключи массивов, безусловно, раздражают больше, чем неопределенные переменные. Но если вы не уверены, содержит ли массив ключ или нет, это означает, что либо вы сами не определили массив, либо вы берете его из источника, который вы не контролируете. Ни один из сценариев не должен происходить очень часто; и если это произойдет, у вас есть все основания проверить, содержит ли массив то, что вы думаете. Это мера безопасности, ИМО.
kijin
37

Просто напишите для этого функцию. Что-то вроде:

function get_string($array, $index, $default = null) {
    if (isset($array[$index]) && strlen($value = trim($array[$index])) > 0) {
        return get_magic_quotes_gpc() ? stripslashes($value) : $value;
    } else {
        return $default;
    }
}

который вы можете использовать как

$username = get_string($_POST, 'username');

Сделайте то же самое для тривиальной вещи , как get_number(), get_boolean(), get_array()и так далее.

BalusC
источник
5
Это выглядит хорошо, и также выполняет проверку magic_quotes. Ницца!
Pekka
Отличная функция! Большое спасибо за то, что поделились.
Майк Мур
3
Обратите внимание, что $ _POST ['something'] может возвращать массив, например входные данные с <input name="something[]" />. Это вызовет ошибку (поскольку обрезка не может применяться к массивам) с использованием приведенного выше кода, в этом случае следует использовать is_stringи, возможно strval. Это не просто тот случай, когда следует использовать get_arrayлибо одно, либо другое, поскольку пользовательский ввод (вредоносный) может быть чем угодно, а синтаксический анализатор пользовательского ввода в любом случае никогда не должен выдавать ошибку.
Ciantic
1
Я использую такую ​​же функцию, но определенную как таковую: function get_value (& $ item, $ default = NULL) {return isset ($ item)? $ item: $ по умолчанию; } Преимущество этой функции в том, что вы можете вызывать ее с массивами, переменными и объектами. Недостатком является то, что $ item после этого инициализируется (до нуля), если это не так.
Мат,
Вам следует отключить магические кавычки глобально, вместо того, чтобы работать с ними в одной функции. В Интернете есть множество источников, объясняющих волшебные цитаты.
Кайла
13

Я считаю, что одним из лучших способов справиться с этой проблемой является доступ к значениям массивов GET и POST (COOKIE, SESSION и т. Д.) Через класс.

Создайте класс для каждого из этих массивов и объявите __getи __setметоды ( перегрузка ). __getпринимает один аргумент, который будет именем значения. Этот метод должен проверять это значение в соответствующем глобальном массиве, используя isset()или, empty()и возвращать значение, если оно существует, или null(или какое-либо другое значение по умолчанию) в противном случае.

После этого вы можете уверенно обращаться к значениям массива следующим образом: $POST->usernameи при необходимости выполнять любую проверку без использования каких-либо isset()s или empty()s. Если usernameне существует в соответствующем глобальном массиве, то nullбудет возвращено, поэтому никаких предупреждений или уведомлений не будет.

Джамол
источник
1
Это отличная идея, и я готов реструктурировать код. +1
Пекка
К сожалению, вы не сможете сделать эти экземпляры суперглобальными, если не назначите их $ _GET или $ _POST, что было бы довольно некрасиво. Но вы, конечно, можете использовать статические классы ...
ThiefMaster
1
Вы не можете использовать геттеры и сеттеры в «статических классах». и написание одного класса для каждой переменной - плохая практика, так как подразумевает дублирование кода, что плохо. Не думаю, что это решение наиболее адекватное.
Мат,
Открытый статический член класса действует как суперглобальный, то есть: HTTP :: $ POST-> username, где вы создаете экземпляр HTTP :: $ POST в некоторый момент перед его использованием, т.е. Класс HTTP {public static $ POST = array (); ...}; HTTP :: $ POST = new someClass ($ _ POST); ...
velcrow
6

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


Однако я использую простую функцию, которая пригодится в этой и некоторых других ситуациях при работе с индексами массива :

function Value($array, $key, $default = false)
{
    if (is_array($array) === true)
    {
        settype($key, 'array');

        foreach ($key as $value)
        {
            if (array_key_exists($value, $array) === false)
            {
                return $default;
            }

            $array = $array[$value];
        }

        return $array;
    }

    return $default;
}

Допустим, у вас есть следующие массивы:

$arr1 = array
(
    'xyz' => 'value'
);

$arr2 = array
(
    'x' => array
    (
        'y' => array
        (
            'z' => 'value',
        ),
    ),
);

Как получить «значение» из массивов? Просто:

Value($arr1, 'xyz', 'returns this if the index does not exist');
Value($arr2, array('x', 'y', 'z'), 'returns this if the index does not exist');

У нас уже есть одномерные и многомерные массивы, что еще мы можем сделать?


Возьмем, к примеру, следующий фрагмент кода:

$url = '/programming/1960509';
$domain = parse_url($url);

if (is_array($domain) === true)
{
    if (array_key_exists('host', $domain) === true)
    {
        $domain = $domain['host'];
    }

    else
    {
        $domain = 'N/A';
    }
}
else
{
    $domain = 'N/A';
}

Довольно скучно, правда? Вот еще один подход с использованием Value()функции:

$url = '/programming/1960509';
$domain = Value(parse_url($url), 'host', 'N/A');

В качестве дополнительного примера возьмем RealIP()функцию для теста:

$ip = Value($_SERVER, 'HTTP_CLIENT_IP', Value($_SERVER, 'HTTP_X_FORWARDED_FOR', Value($_SERVER, 'REMOTE_ADDR')));

Аккуратно, а? ;)

Аликс Аксель
источник
6
«Полагаться на функции взлома, которые могут изменить свое поведение в будущем» ?! Извините, но это самая нелепая вещь, которую я слышал за всю неделю. Прежде всего, issetи emptyявляются языковыми конструкциями , а не функции. Во-вторых, если какие-либо базовые библиотечные функции / языковые конструкции изменят свое поведение, вы можете оказаться в затруднительном положении. Что, если array_key_existsизменится его поведение? Ответ - не будет, пока вы используете его, как описано в документации. И issetзадокументирован для использования именно так. Функции наихудшего случая считаются устаревшими по сравнению с одной или двумя основными версиями. Синдром НИЗ - это плохо!
deceze
Я извиняюсь deceze, но в первую очередь хака в курсивом в случае , если вы не заметили. =) Во-вторых, вы имеете в виду, что не следует полагаться на array_key_exists()проверку наличия ключа в массиве ?! array_key_exists()была создана именно для этого , я скорее полагаться на него для этой цели , чем isset()и специально empty()чье официальное описание: «определить , является ли пустая переменная», не упоминает ничего , если она на самом деле существует. Ваш комментарий и голос "против" - одни из самых нелепых, которые я видел за весь месяц .
Аликс Аксель,
3
Я не говорю issetи emptyне более или менее надежны , чем array_key_existsи может сделать точно такую же работу. Ваш второй, многословный пример может быть написан $domain = isset($domain['host']) ? $domain['host'] : 'N/A';с использованием только основных языковых функций, без дополнительных вызовов функций или объявлений (обратите внимание, что я не обязательно выступаю за использование тернарного оператора; о)). Для обычных скалярных переменных вам все равно нужно будет использовать issetили empty, и вы можете использовать их для массивов точно так же. «Надежность» - плохая причина для того, чтобы этого не делать.
deceze
1
Вы высказали свою точку зрения, хотя я не согласен с большей частью того, что вы сказали. Я думаю, что вы ошиблись в более чем 90% случаев, например, я все время использую значение «0» в скрытых полях форм. Тем не менее я считаю, что предложенное мной решение не заслуживает отрицательного голосования и вполне может быть полезно для Пекки.
Аликс Аксель,
2
Хотя у @deceze есть точка зрения на пользовательские функции - я обычно придерживаюсь той же позиции - подход value () выглядит достаточно интересным, и я собираюсь взглянуть на него. Я думаю, что ответ и продолжение позволят каждому, кто наткнется на него позже, составить собственное мнение. +1.
Pekka
3

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

вава
источник
1
isset () вещи. Если по умолчанию все равно нулю, это избавит от множества проблем.
vava
2
А что это за «все»? Для PHP было бы напрасной тратой воображать каждое мыслимое имя переменной и устанавливать для каждого значение NULL, чтобы ленивый разработчик мог избежать ввода пяти символов.
Lotus Notes,
5
@Byron, послушайте, это действительно просто, многие другие языки делают это, Ruby и Perl в качестве нескольких примеров. ВМ знает, использовалась ли переменная раньше или нет, не так ли? Он всегда может вернуть null вместо сбоя с сообщением об ошибке или без него. И речь не о паршивых 5 символах, а о написании params["width"] = params["width"] || 5для установки значений по умолчанию вместо всей этой ерунды с isset()вызовами.
vava
3
Извините за то, что воскресил старую ветку. Двумя худшими ошибками PHP были: register_globalsи magic_quotes. Эти проблемы делают неинициализированные переменные почти безобидными по сравнению с ними.
staticsan
3

Я использую эти функции

function load(&$var) { return isset($var) ? $var : null; }
function POST($var) { return isset($_POST[$var]) ? $_POST[$var] : null; }

Примеры

$y = load($x); // null, no notice

// this attitude is both readable and comfortable
if($login=POST("login") and $pass=POST("pass")) { // really =, not ==
  // executes only if both login and pass were in POST
  // stored in $login and $pass variables
  $authorized = $login=="root" && md5($pass)=="f65b2a087755c68586568531ad8288b4";
}
Ян Туро
источник
2
Я тоже использую это, но помню, что в некоторых случаях ваши переменные будут инициализированы автоматически: например, load ($ array ['FOO']) создаст ключ FOO в $ array.
Мат,
2

Добро пожаловать в оператор объединения с нулевым значением (PHP> = 7.0.1):

$field = $_GET['field'] ?? null;

PHP говорит:

Оператор объединения с нулевым значением (??) был добавлен в качестве синтаксического сахара для общего случая необходимости использования тернарного элемента вместе с isset (). Он возвращает свой первый операнд, если он существует и не равен NULL; в противном случае возвращается второй операнд.

Александр Тебальди
источник
1

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

<?php
function isset_globals($method, $name, $option = "") {
    if (isset($method[$name])) {    // Check if such a variable
        if ($option === "empty" && empty($method[$name])) { return false; } // Check if empty 
        if ($option === "stringLength" && strlen($method[$name])) { return strlen($method[$name]); }    // Check length of string -- used when checking length of textareas
        return ($method[$name]);
    } else { return false; }
}

if (!isset_globals("$_post", "input_name", "empty")) {
    echo "invalid";
} else {
    /* You are safe to access the variable without worrying about errors! */
    echo "you uploaded: " . $_POST["input_name"];
}
?>
огонь дракона
источник
0

Программное обеспечение не запускается волшебным образом по милости божьей. Если вы ожидаете чего-то, чего не хватает, нужно правильно с этим справиться.

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

Более того, это делает ваше приложение недоступным для обслуживания, и вы сходите с ума, когда случаются неожиданные вещи. Строгость языка является обязательной, и PHP изначально ошибочен во многих аспектах. Если вы не знаете, из вас получится плохой программист.

knoopx
источник
Я хорошо осведомлен о недостатках PHP. Как я указал в вопросе, я говорю о капитальном ремонте старых проектов.
Pekka
Согласовано. Мне, как давнему разработчику PHP, довольно сложно осваивать новые языки, такие как Java, где нужно все декларировать.
Джунейт 07
0

Я не уверен, каково ваше определение читабельности, но правильное использование блоков empty (), isset () и try / throw / catch очень важно для всего процесса.

Если ваш E_NOTICE поступает из $ _GET или $ _POST, тогда они должны быть проверены на empty () вместе со всеми другими проверками безопасности, которые эти данные должны пройти.

Если он поступает из внешних каналов или библиотек, он должен быть заключен в файл try / catch.

Если он поступает из базы данных, следует проверить $ db_num_rows () или его эквивалент.

Если он исходит от внутренних переменных, они должны быть правильно инициализированы. Часто эти типы уведомлений приходят из-за присвоения новой переменной возвращаемого значения функции, которая возвращает FALSE в случае сбоя. Они должны быть заключены в тест, который в случае сбоя может либо присвоить переменной приемлемое значение по умолчанию, которое может обработать код, либо выдать исключение, которое может обработать код.

Эти вещи делают код длиннее, добавляют дополнительные блоки и дополнительные тесты, но я не согласен с вами в том смысле, что я думаю, что они определенно добавляют дополнительную ценность.

Mlutz
источник
-2

А как насчет использования @оператора?

Например:

if(@$foo) { /* Do something */ }

Вы можете сказать, что это плохо, потому что вы не контролируете, что происходит «внутри» $ foo (например, если это был вызов функции, содержащий ошибку PHP), но если вы используете этот метод только для переменных, это эквивалентно:

if(isset($foo) && $foo) { /* ... */ }
Мат
источник
if(isset($foo))на самом деле достаточно. Он вернется, TRUEесли выражение оценивается как TRUE.
Джунейт 07
2
@ ColorWP.com также вернет true, если выражение оценивается как false.
Jon Hulka
Вы должны использовать параметр @ (чтобы игнорировать уведомление) только в коде, который на самом деле не находится в стадии дальнейшей разработки, или в одноразовом коде или быстром исправлении в существующих проектах, которые вы не хотите никому показывать. Но это обычный обходной путь для быстрого взлома.
rubo77