Як уникнути isset () та empty ()


98

У мене є декілька старих додатків, які викидають багато повідомлень "xyz не визначено" та "невизначене зміщення" під час роботи на рівні помилок E_NOTICE, оскільки існування змінних не перевіряється явно за допомогою isset()і сортування.

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

Однак мені не подобається те, що наносить сотням isset() empty()і array_key_exists()s моєму коду. Він роздувається, стає менш читабельним, не набуваючи нічого за значенням чи значенням.

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


6
Я згоден повністю. Ось чому мені так подобається Zend Framework, модуль запитів там дуже хороший. Якщо я працюю над невеликим додатком, я зазвичай кодую простий клас запиту з магічними методами __set та __get, який працює аналогічно запиту ZF. Таким чином я уникаю будь-яких випадків запущених і порожніх у своєму коді. Таким чином, все, що вам потрібно використовувати, це або якщо (count ($ arr)>>) на масивах перед ітерацією над ними, а якщо (null! == $ $ змінна) в кількох критичних місцях. Це виглядає набагато чистіше.
Річард Ноп

Відповіді:


128

Для тих, хто цікавиться, я розширив цю тему на невелику статтю, де подана нижче інформація в дещо кращому структурованому вигляді: Поточний посібник із набору 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/ falseпрапор, тоді використовуйтеtrueабо false, ні null. Єдиним винятком з цього є бібліотеки сторонніх організацій, які хочуть nullщось означати, але оскільки nullце важко виявити в PHP, я ще не міг знайти жодної бібліотеки, яка б це робила.


4
Правда, але більшість незадовільних спроби доступу уздовж ліній if ($array["xyz"])замість isset()або array_key_exists()які я знаходжу кілька легітимним, звичайно , НЕ структурні проблеми (поправ мене , якщо я помиляюся). Додавання array_key_exists()просто схоже на жахливу трату для мене.
Пекка

9
Я не можу придумати жоден випадок, коли я використовував би array_key_existsзамість простого isset($array['key'])або !empty($array['key']). Звичайно, обидва додають 7 чи 8 символів у свій код, але я навряд чи назвав би це проблемою. Це також допомагає уточнити ваш код: if (isset($array['key']))означає, що ця змінна дійсно є необов’язковою і може бути відсутнім, тоді як if ($array['key'])просто означає "якщо відповідає дійсності". Якщо ви отримаєте повідомлення про останнє, ви знаєте, що ваша логіка десь накручена.
деге

6
Я вважаю, що різниця між isset () та array_key_exists () полягає в тому, що останній поверне вірно, якщо значення NULL. isset () не буде.
Htbaa

1
Щоправда, але я не міг придумати розумний випадок використання, коли мені потрібно розмежовувати неіснуючу змінну від набору ключових значень, значення яких є нульовим. Якщо значення оцінюється на FALSE, відмінність має бути без різниці. :)
деге

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()і так далі.


5
Це виглядає добре, а також перевіряє котирування магії. Приємно!
Пекка

Відмінна функція! Велике спасибі за спільний доступ.
Майк Мур

3
Зауважте, що $ _POST ['щось'] може повернути масив, наприклад входи з <input name="something[]" />. Це може спричинити помилку (оскільки обробка не може бути застосована до масивів) за допомогою вищевказаного коду, в цьому випадку слід використовувати is_stringі, можливо strval. Це не просто той випадок, коли слід використовувати get_arrayабо оскільки введення користувача (шкідливе), можливо, що завгодно, і користувач-аналізатор вводу ніколи не повинен викидати помилки.
Сіантік

1
Я використовую той самий вид функції, але визначений як такий: функція get_value (& $ item, $ default, NULL) {return isset ($ item)? $ item: $ за замовчуванням; } Перевага цієї функції полягає в тому, що ви можете викликати її масивами, змінними та об'єктами. Недолік полягає в тому, що елемент $ ініціюється (нульовим) згодом, якщо він не був.
Мат

Вам слід вимкнути магічні цитати в усьому світі, а не займатися ними в 1 функції. В Інтернеті є безліч джерел, що пояснюють магічні цитати.
Кайла

13

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

Створіть клас для кожного з цих масивів та оголосіть __getі __setметоди ( перевантаження ). __getприймає один аргумент, який буде ім'ям значення. Цей метод повинен перевірити це значення у відповідному глобальному масиві, використовуючи isset()або empty()повернути значення, якщо воно існує або null(або якесь інше значення за замовчуванням) в іншому випадку.

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


1
Це відмінна ідея, і я готовий змінити код. +1
Pekka

На жаль, ви не зможете зробити ці екземпляри надглобальними, якщо ви не призначите їх $ _GET або $ _POST, що було б дуже потворно. Але ти можеш використовувати статичні класи, звичайно ...
ThiefMaster

1
Не можна використовувати геттери та сетери на "статичних класах". а написання одного класу на змінну є поганою практикою, оскільки це передбачає дублювання коду, що погано. Я не думаю, що це рішення є найбільш адекватним.
Мат

Публічний статичний член класу діє як надглобальний, тобто: HTTP :: $ POST-> ім'я користувача, де ви інстанціюєте 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, але в першу чергу хака в курсивом в разі , якщо ви не помітили. =) По-друге, ви маєте на увазі, що не слід покладатися на array_key_exists()перевірку наявності ключа в масиві ?! array_key_exists()була створена саме для цього , я швидше за покладатися на нього для цієї мети , ніж isset()та спеціально empty()чиє офіційне опис: «визначити , чи є порожня змінна», не згадує нічого , якщо вона на насправді існує. Ваш коментар та голосування під голосуванням - одне з найбезглуздіших, я був свідком за місяць .
Алікс Аксель

3
Я кажу issetі emptyне є більш-менш надійним, ніж array_key_existsі може виконати точно таку ж роботу. Ваш другий, довгомоторний приклад може бути записаний як $domain = isset($domain['host']) ? $domain['host'] : 'N/A';з лише основними мовними особливостями, не потрібні додаткові виклики функцій або декларації (зауважте, що я не обов'язково виступаю за використання потрійного оператора; о)). Для звичайних скалярних змінних вам все одно доведеться використовувати issetабо empty, і ви можете використовувати їх для масивів точно так само. "Надійність" - це погана причина цього не робити.
деге

1
Ви зробили свою думку, хоча я не згоден з більшістю речей, які ви сказали. Я думаю, що ви помилилися у 90% + випадках, наприклад, я постійно використовую значення "0" у прихованих полях у формах. Але я вважаю, що рішення, яке я запропонував, не заслуговує на голосування, і Pekka може бути корисним .
Алікс Аксель

2
Хоча в @deceze є точка з користувацькими функціями - я зазвичай займаю однакову позицію - підхід до значення () виглядає досить цікавим, що я буду розглядати це. Я думаю, що відповіді та подальші дії дадуть можливість кожному, хто натрапив на неї пізніше, вирішити свою думку. +1.
Pekka

3

Я тут з тобою. Але дизайнери PHP допустили набагато гірші помилки, ніж це. Окрім визначення спеціальної функції для будь-якого читання значень, її немає ніякого способу.


1
isset () речі. Якщо зробити за замовчуванням все недійсним, це дозволить заощадити багато неприємностей.
vava

2
І що це "все"? Здавалося б, для PHP марно було б уявити собі кожне можливе ім'я змінної та встановити кожне на NULL просто так, щоб ледачий розробник міг уникнути введення 5 символів.
Lotus Notes

5
@Byron, дивись, це дуже просто, багато інших мов це роблять, Ruby та Perl - нечисленні приклади. VM знає, чи використовувалася змінна раніше чи ні, чи не так? Він завжди може повернути нуль замість відмови з повідомленням про помилку або без нього. І це не про паршиві 5 символів, це про те, params["width"] = params["width"] || 5щоб написати встановлені за замовчуванням замість усіх дурниць із isset()дзвінками.
vava

3
Вибачте за відродження старої теми. Дві найгірші помилки PHP були register_globalsі були magic_quotes. Проблеми, які ці спонсори роблять неініціалізованими змінними, у порівнянні виглядають майже нешкідливими.
статистика

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, за задумом, помиляється в багатьох аспектах. Це зробить вас поганим програмістом, якщо ви не знаєте про це.


Я добре знаю недоліки PHP. Як я зазначив у запитанні, я говорю про капітальний ремонт старих проектів.
Pekka

Домовились. Будучи розробником PHP на тривалий час, мені досить важко вписатися на нові мови, як-от Java, де потрібно декларувати все.
Джунейт

0

Я не впевнений, що таке ваше читабельність, але правильне використання порожніх (), isset () та спробувати / кидати / ловити блоки є досить важливим для всього процесу.

Якщо ваш E_NOTICE надходить від $ _GET або $ _POST, їх слід перевірити на порожній () праворуч, а також на всіх інших перевірок безпеки, які повинні пройти ці дані.

Якщо він надходить із зовнішніх каналів або бібліотек, його слід загорнути в спробувати / catch.

Якщо він надходить з бази даних, слід перевірити $ db_num_rows () або його еквівалент.

Якщо вони надходять із внутрішніх змінних, їх слід правильно ініціалізувати. Часто такі типи сповіщень походять від призначення нової змінної до повернення функції, яка повертає FALSE після відмови. Їх слід загорнути в тест, який у разі відмови може присвоїти змінній прийнятне значення за замовчуванням, яке може обробляти код, або викинути виняток, з яким може оброблятися код.

Ці речі роблять код довшим, додають зайві блоки та додають додаткові тести, але я не погоджуюся з вами в тому, що я думаю, що вони, безумовно, додають додаткової цінності.


-2

Що з використанням @оператора?

Наприклад:

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

Ви можете сказати, що це погано, оскільки ви не маєте контролю над тим, що відбувається "всередині" $ foo (якщо це був виклик функції, який містить помилку PHP, наприклад), але якщо ви використовуєте лише цю техніку для змінних, це еквівалентно:

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

if(isset($foo))насправді достатньо. Він повернеться, TRUEякщо вираз буде оцінено до TRUE.
Джунейт

2
@ ColorWP.com вона також поверне true, якщо вираз буде оцінено як false.
Джон Хулька

Вам слід використовувати лише параметр @ (щоб ігнорувати повідомлення) для коду, який насправді не розробляється, або одноразового коду або швидкого виправлення існуючих проектів, який ви не хочете показувати нікому іншим. Але це звичайний спосіб швидкого злому.
rubo77
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.