Как избежать isset() и empty()

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

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

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

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

Ответ 1

Для тех, кого это интересует, я расширил эту тему в небольшой статье, которая предоставляет следующую информацию в несколько более структурированной форме: Окончательное руководство по PHP isset И пустой


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

Эти предупреждения помогут вам, а не раздражать вас. Если вы получите предупреждение "Ты пытаешься работать с чем-то, чего не существует!", Твоя реакция должна быть "Ой, мой плохой, позволь мне исправить это как можно скорее". Как еще вы собираетесь рассказать о разнице между "переменными, которые работают просто отлично undefined" и честно неправильный код, что может привести к серьезным ошибкам? Это также является причиной того, что вы всегда, всегда, разрабатываете с сообщением об ошибках обращено в 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/false, используйте true или false, а не null. Единственным исключением из этого могут быть сторонние библиотеки, которые хотят, чтобы null означал что-то, но поскольку null так сложно обнаружить в PHP, мне еще не найти любую библиотеку, которая делает это.

Ответ 2

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

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() и т.д.

Ответ 3

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

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

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

Ответ 4

Я не возражаю против использования array_key_exists(), на самом деле я предпочитаю использовать эту специальную функцию вместо того, чтобы полагаться на функции hack, которые могут изменить их поведение в будущем , например 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 = 'https://stackoverflow.com/questions/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 = 'https://stackoverflow.com/questions/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')));

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

Ответ 5

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

Ответ 6

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

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")) // really =, not ==
if($pass=POST("pass"))
if($login=="Admin" && $pass==...) {
  // login and pass are not empty, login is "Admin" and pass is ...
  $authorized = true;
  ...
}

Ответ 7

Сделайте функцию, которая возвращает 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"];
}
?>

Ответ 8

Я не уверен, что ваше определение читаемости, но правильное использование пустых(), isset() и try/throw/catch блоков, очень важно для всего процесса. Если ваш E_NOTICE поступает из $_GET или $_POST, то они должны быть проверены на пустое() прямо вместе со всеми другими проверками безопасности, которые должны пройти эти данные. Если он поступает из внешних каналов или библиотек, он должен быть завернут в try/catch. Если он поступает из базы данных, необходимо проверить значение $db_num_rows() или его эквивалент. Если он поступает из внутренних переменных, они должны быть правильно инициализированы. Часто эти типы уведомлений связаны с назначением новой переменной возврату функции, которая возвращает FALSE при сбое, которые должны быть завернуты в тесте, который в случае сбоя может либо присвоить переменной приемлемое значение по умолчанию что код может обрабатывать или бросать исключение, которое может обрабатывать код. Эти вещи делают код длиннее, добавляют дополнительные блоки и добавляют дополнительные тесты, но я не согласен с вами в том, что я думаю, что они определенно добавляют дополнительную ценность.

Ответ 9

Программное обеспечение

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

Ответ 10

Как насчет использования оператора @? например:.

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

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

if(isset($foo) && $foo) { /* ... */ }