Jak uniknąć isset () i empty ()

98

Mam kilka starszych aplikacji, które generują wiele komunikatów „xyz is undefined” i „undefined offset” podczas pracy na poziomie błędu E_NOTICE, ponieważ istnienie zmiennych nie jest jawnie sprawdzane przy użyciu isset()i consorts.

Rozważam przepracowanie ich, aby uczynić je kompatybilnymi z E_NOTICE, ponieważ powiadomienia o brakujących zmiennych lub przesunięciach mogą uratować życie, mogą być pewne drobne ulepszenia wydajności do uzyskania i ogólnie jest to czystszy sposób.

Jednak nie podoba mi się to, co powoduje setki isset() empty()i array_key_exists()w moim kodzie. Nadąża się, staje się mniej czytelny, nie zyskując niczego pod względem wartości ani znaczenia.

Jak mogę ustrukturyzować swój kod bez nadmiaru kontroli zmiennych, będąc jednocześnie kompatybilnym z E_NOTICE?

Pekka
źródło
6
Zgadzam się całkowicie. Dlatego tak bardzo lubię Zend Framework, moduł request jest tam bardzo dobry. Jeśli pracuję nad jakąś małą aplikacją, zwykle koduję prostą klasę żądania z magicznymi metodami __set i __get, która działa podobnie do żądania ZF. W ten sposób unikam wszelkich wystąpień isset i empty w moim kodzie. W ten sposób wszystko, czego potrzebujesz, to albo if (count ($ arr)> 0) na tablicach przed iteracją po nich i if (null! == $ zmienna) w kilku krytycznych miejscach. Wygląda na znacznie czystsze.
Richard Knop

Odpowiedzi:

128

Dla zainteresowanych rozszerzyłem ten temat do małego artykułu, który zawiera poniższe informacje w nieco lepiej zorganizowanej formie: The Definitive Guide to PHP isset And empty


IMHO, powinieneś pomyśleć nie tylko o uczynieniu aplikacji „kompatybilną z E_NOTICE”, ale także o zmianie całości. Posiadanie setek punktów w kodzie, które regularnie próbują używać nieistniejących zmiennych, brzmi jak raczej źle skonstruowany program. Próba uzyskania dostępu do nieistniejących zmiennych nigdy nie powinna się zdarzyć, inne języki narzekają na to w czasie kompilacji. Fakt, że PHP pozwala ci to zrobić, nie oznacza, że ​​powinieneś.

Te ostrzeżenia mają ci pomóc , a nie drażnić. Jeśli pojawi się ostrzeżenie „Próbujesz pracować z czymś, co nie istnieje!” Twoja reakcja powinna brzmieć: „Ups, moja wina, naprawię to jak najszybciej”. Jak inaczej zamierzasz odróżnić „zmienne, które działają dobrze, nieokreślone”, a szczerze błędny kod, który może prowadzić do poważnych błędów ? Jest to również powód, dla którego zawsze, zawsze , opracowujesz program z raportowaniem błędów ustawionym na 11 i ciągle podłączasz swój kod, dopóki nie ma ani jednejNOTICEjest wydane. Wyłączenie raportowania błędów dotyczy tylko środowisk produkcyjnych, aby uniknąć wycieku informacji i zapewnić lepsze wrażenia użytkownika, nawet w przypadku błędnego kodu.


Opracować:

Zawsze będziesz potrzebować issetlub emptygdzieś w kodzie, jedynym sposobem na ograniczenie ich występowania jest prawidłowe zainicjowanie zmiennych. W zależności od sytuacji można to zrobić na różne sposoby:

Argumenty funkcji:

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

Nie ma potrzeby sprawdzania, czy są ustawione $barlub $bazsą ustawione w funkcji, ponieważ po prostu je ustawiasz, jedyne, o co musisz się martwić, to to, czy ich wartość wyniesie truelub false(lub cokolwiek innego).

Zwykłe zmienne w dowolnym miejscu:

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

Zainicjuj swoje zmienne u góry bloku kodu, w którym zamierzasz ich użyć. To rozwiązuje !issetproblem, zapewnia, że ​​zmienne zawsze mają znaną wartość domyślną, daje czytelnikowi wyobrażenie o tym, na czym będzie działać poniższy kod, a tym samym służy również jako rodzaj samodokumentacji.

Tablice:

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

Tak samo jak powyżej, inicjalizujesz tablicę wartościami domyślnymi i nadpisujesz je rzeczywistymi wartościami.

W pozostałych przypadkach, powiedzmy, szablon, w którym wyprowadzasz wartości, które mogą, ale nie muszą być ustawione przez kontroler, wystarczy sprawdzić:

<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>

Jeśli regularnie używasz array_key_exists, powinieneś ocenić, do czego go używasz. To robi różnicę tylko tutaj:

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

Jak wspomniano powyżej, jeśli poprawnie inicjalizujesz zmienne, nie musisz sprawdzać, czy klucz istnieje, czy nie, ponieważ wiesz, że tak. Jeśli otrzymujesz tablicę z zewnętrznego źródła, wartość najprawdopodobniej nie będzie nulljednak '', 0, '0', falselub coś podobnego, to znaczy wartość można ocenić z issetlub empty, w zależności od intencji. Jeśli regularnie ustawiasz klucz tablicy na nulli chcesz, aby miał jakiekolwiek znaczenie, ale falsenp. Jeśli w powyższym przykładzie różne wyniki isseti array_key_existswpływają na logikę programu, powinieneś zadać sobie pytanie, dlaczego. Samo istnienie zmiennej nie powinno być ważne, tylko jej wartość powinna mieć znaczenie. Jeśli klucz to true/ falseflaga, użyjtruealbo falsenie null. Jedynym wyjątkiem byłyby biblioteki innych firm, które chcą nullcoś znaczyć, ale ponieważ nulljest to tak trudne do wykrycia w PHP, nie znalazłem jeszcze żadnej biblioteki, która to robi.

zamrozić
źródło
4
To prawda, ale większość nieudanych prób dostępu to próby if ($array["xyz"])zamiast isset()lub array_key_exists()które uważam za nieco uzasadnione, na pewno nie problemy strukturalne (popraw mnie, jeśli się mylę). Dodawanie array_key_exists()po prostu wygląda dla mnie na straszną stratę.
Pekka,
9
Nie przychodzi mi do głowy żaden przypadek, w którym użyłbym array_key_existszamiast prostego isset($array['key'])lub !empty($array['key']). Jasne, oba dodają 7 lub 8 znaków do twojego kodu, ale raczej nie nazwałbym tego problemem. Pomaga również w wyjaśnieniu kodu: if (isset($array['key']))oznacza, że ​​ta zmienna jest rzeczywiście opcjonalna i może być nieobecna, podczas gdy if ($array['key'])oznacza po prostu „jeśli prawda”. Jeśli otrzymasz powiadomienie o tym drugim, wiesz, że twoja logika jest gdzieś schrzaniona.
deceze
6
Uważam, że różnica między isset () a array_key_exists () polega na tym, że ta ostatnia zwróci wartość true, jeśli wartość wynosi NULL. isset () nie będzie.
Htbaa
1
To prawda, ale nie przychodzi mi do głowy rozsądny przypadek użycia, w którym muszę odróżnić nieistniejącą zmienną od ustawionego klucza, którego wartość jest równa zeru. Jeśli wartość ma wartość FALSE, rozróżnienie powinno być bez różnicy. :)
deceze
1
Klucze tablic są z pewnością bardziej irytujące niż niezdefiniowane zmienne. Ale jeśli nie masz pewności, czy tablica zawiera klucz, czy nie, oznacza to , że nie zdefiniowałeś tablicy samodzielnie lub pobierasz ją ze źródła, nad którym nie masz kontroli. Żaden scenariusz nie powinien zdarzać się zbyt często; a jeśli tak się stanie, masz wszelkie powody, aby sprawdzić, czy tablica zawiera to, co myślisz, że robi. To środek bezpieczeństwa IMO.
kijin
37

Po prostu napisz do tego funkcję. Coś jak:

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;
    }
}

którego możesz użyć jako

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

Zrób to samo dla trywialnej rzeczy jak get_number(), get_boolean(), get_array()i tak dalej.

BalusC
źródło
5
Wygląda to dobrze, a także sprawdza magic_quotes. Miły!
Pekka,
Świetna funkcja! Wielkie dzięki za udostępnienie.
Mike Moore
3
Zauważ, że $ _POST ['coś'] może zwrócić tablicę, np. Dane wejściowe z <input name="something[]" />. Spowodowałoby to błąd (ponieważ przycinania nie można zastosować do tablic) przy użyciu powyższego kodu, w tym przypadku należy użyć is_stringi ewentualnie strval. Nie jest to zwykły przypadek, w którym należy użyć get_arrayktóregoś z nich, ponieważ dane wejściowe użytkownika (złośliwe) mogą być cokolwiek, a parser danych wejściowych użytkownika i tak nigdy nie powinien generować błędów.
Ciantic,
1
Używam tego samego rodzaju funkcji, ale zdefiniowanej jako: function get_value (& $ item, $ default = NULL) {return isset ($ item)? $ item: $ default; } Zaletą tej funkcji jest to, że można ją wywołać za pomocą tablic, zmiennych i obiektów. Wadą jest to, że element $ item zostaje później zainicjalizowany (do null), jeśli tak nie jest.
Mat
Powinieneś globalnie wyłączyć magiczne cudzysłowy, zamiast zajmować się nimi w jednej funkcji. W Internecie jest wiele źródeł wyjaśniających magiczne cytaty.
Kayla
13

Uważam, że jednym z najlepszych sposobów radzenia sobie z tym problemem jest dostęp do wartości tablic GET i POST (COOKIE, SESSION itp.) Za pośrednictwem klasy.

Utwórz klasę dla każdej z tych tablic i zadeklaruj __geti __setmetody ( przeciążenie ). __getakceptuje jeden argument, który będzie nazwą wartości. Ta metoda powinna sprawdzić tę wartość w odpowiedniej tablicy globalnej, używając isset()lub empty()i zwrócić wartość, jeśli istnieje, lub null(lub inną wartość domyślną) w inny sposób.

Następnie możesz bezpiecznie uzyskać dostęp do wartości tablic w ten sposób: $POST->usernamei wykonać dowolną walidację, jeśli jest to konieczne, bez użycia żadnejisset() s lub empty()s. Jeśli usernamenie istnieje w odpowiedniej tablicy globalnej, nullzostanie zwrócony, więc nie zostaną wygenerowane żadne ostrzeżenia ani powiadomienia.

Jamol
źródło
1
To świetny pomysł i coś, dla czego jestem gotowy, aby zrestrukturyzować kod. +1
Pekka,
Niestety nie będziesz w stanie uczynić tych instancji superglobalnymi, chyba że przypiszesz je do $ _GET lub $ _POST, co byłoby dość brzydkie. Ale możesz oczywiście skorzystać z zajęć statycznych ...
ThiefMaster
1
Nie możesz używać metod pobierających i ustawiających w „klasach statycznych”. a pisanie jednej klasy na zmienną jest złą praktyką, ponieważ implikuje powielanie kodu, co jest złe. Nie sądzę, żeby to rozwiązanie było najbardziej adekwatne.
Mat
Publiczny statyczny element członkowski klasy zachowuje się jak superglobalny, tj .: HTTP :: $ POST-> nazwa_użytkownika, gdzie tworzysz instancję HTTP :: $ POST w pewnym momencie przed jego użyciem, tj. Klasa HTTP {public static $ POST = array (); ...}; HTTP :: $ POST = new someClass ($ _ POST); ...
velcrow
6

Nie mam nic przeciwko używaniu array_key_exists()funkcji. W rzeczywistości wolę używać tej konkretnej funkcji, zamiast polegać na funkcjach hakerskich, które mogą zmienić swoje zachowanie w przyszłości, jak emptyiisset (przekreślono, aby uniknąć podatności ).


Używam jednak prostej funkcji, która jest przydatna w tym i kilku innych sytuacjach związanych z indeksami tablic :

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;
}

Załóżmy, że masz następujące tablice:

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

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

Jak uzyskać „wartość” z tablic? Prosty:

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');

Mamy już uwzględnione tablice jedno i wielowymiarowe, co jeszcze możemy zrobić?


Weźmy na przykład następujący fragment kodu:

$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';
}

Dość nudne, prawda? Oto inne podejście wykorzystujące Value()funkcję:

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

Jako dodatkowy przykład weźmy RealIP()funkcję do testu:

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

Schludnie, co? ;)

Alix Axel
źródło
6
„Poleganie na funkcjach hakerskich, które mogą zmienić ich zachowanie w przyszłości” ?! Przepraszam, ale to chyba najbardziej absurdalna rzecz, jaką słyszałem przez cały tydzień. Przede wszystkim, isseti emptykonstrukcje językowe , a nie funkcje. Po drugie, jeśli jakiekolwiek podstawowe funkcje biblioteczne / konstrukcje językowe zmienią swoje zachowanie, możesz zostać wkręcony lub nie. A jeśli array_key_existszmieni swoje zachowanie? Odpowiedź brzmi: nie będzie, o ile używasz go zgodnie z dokumentacją. I issetjest udokumentowany jako dokładnie taki używany. Funkcje w najgorszym przypadku są przestarzałe w stosunku do wersji głównej lub dwóch. Zespół NIH jest zły!
deceze
Przepraszam deceze, ale przede wszystkim hack jest pisany kursywą na wypadek, gdybyś nie zauważył. =) Po drugie, masz na myśli, że nie należy polegać na array_key_exists()sprawdzaniu, czy klucz istnieje w tablicy ?! array_key_exists()został stworzony właśnie w tym celu , raczej polegam na nim w tym celu niż isset()iw szczególności empty()którego oficjalny opis brzmi: „określ, czy zmienna jest pusta”, nie wspomina o tym, czy rzeczywiście istnieje. Twój komentarz i głos przeciwny są jednym z najbardziej absurdalnych, jakich byłem świadkiem przez cały miesiąc .
Alix Axel,
3
Mówię isseti emptynie są bardziej ani mniej wiarygodni niż array_key_existsi potrafią wykonać dokładnie to samo zadanie. Twój drugi, rozwlekły przykład może być napisany tak, jak $domain = isset($domain['host']) ? $domain['host'] : 'N/A';tylko z podstawowymi funkcjami języka, bez dodatkowych wywołań funkcji lub deklaracji potrzebnych (zauważ, że niekoniecznie zalecam użycie operatora trójskładnikowego; o)). W przypadku zwykłych zmiennych skalarnych nadal będziesz musiał używać issetlub empty, i możesz ich używać do tablic dokładnie w ten sam sposób. „Niezawodność” to zły powód, aby tego nie robić.
deceze
1
Przedstawiłeś swój punkt widzenia, chociaż nie zgadzam się z większością tego, co powiedziałeś. Myślę, że źle to zrozumiałeś w przypadkach ponad 90%, na przykład cały czas używam wartości „0” w ukrytych polach formularzy. Mimo to uważam, że rozwiązanie, które podałem, nie zasługuje na głosowanie przeciw i może być przydatne dla Pekki.
Alix Axel,
2
Chociaż @deceze ma rację co do funkcji niestandardowych - zwykle zajmuję to samo stanowisko - podejście value () wygląda na tyle interesująco, że przyjrzę się temu. Myślę, że odpowiedź i dalsze działania umożliwią każdemu, kto się później na to natknie, podjęcie własnego zdania. +1.
Pekka,
3

Jestem tu z tobą. Ale projektanci PHP popełnili o wiele gorsze błędy. Oprócz zdefiniowania funkcji niestandardowej dla dowolnego odczytu wartości, nie można tego obejść.

vava
źródło
1
isset () rzeczy. Domyślne zerowanie wszystkiego pozwoliłoby zaoszczędzić wiele kłopotów.
vava
2
A co to za „wszystko”? Wydawałoby się stratą dla PHP, gdyby musiał wyobrazić sobie każdą możliwą nazwę zmiennej i ustawić każdą na NULL, aby leniwy programista mógł uniknąć wpisywania 5 znaków.
Lotus Notes
5
@Byron, spójrz, to naprawdę proste, robi to wiele innych języków, Ruby i Perl jako kilka przykładów. VM wie, czy zmienna była używana wcześniej, czy nie, prawda? Zawsze może zwrócić wartość null zamiast zakończyć się niepowodzeniem z komunikatem o błędzie lub bez niego. I nie chodzi tu o kiepskie 5 znaków, chodzi o pisanie, params["width"] = params["width"] || 5aby ustawić wartości domyślne zamiast tego całego nonsensu z isset()połączeniami.
wawa
3
Przepraszam za wskrzeszenie starego wątku. Dwa z najgorszych błędów PHP to register_globalsi magic_quotes. W porównaniu z tymi problemami niezainicjalizowane zmienne wyglądają prawie nieszkodliwie.
staticsan
3

Używam tych funkcji

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

Przykłady

$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";
}
Jan Turoň
źródło
2
Ja też tego używam, ale pamiętaj, że w niektórych przypadkach twoje zmienne zostaną zainicjalizowane automatycznie: np. Load ($ array ['FOO']) utworzy klucz FOO w $ array.
Mat
2

Witamy w operatorze łączenia wartości null (PHP> = 7.0.1):

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

PHP mówi:

Operator koalescencji zerowej (??) został dodany jako cukier syntaktyczny w typowym przypadku konieczności użycia trójskładnika w połączeniu z isset (). Zwraca swój pierwszy operand, jeśli istnieje i nie ma wartości NULL; w przeciwnym razie zwraca swój drugi operand.

Alexandre Thebaldi
źródło
1

Utwórz funkcję, która zwraca, falsejeśli nie jest ustawiona, a jeśli jest określona, falsejeśli jest pusta. Jeśli poprawne, zwraca zmienną. Możesz dodać więcej opcji, jak widać w poniższym kodzie:

<?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"];
}
?>
Smoczy ogień
źródło
0

Oprogramowanie nie działa magicznie dzięki łasce Boga. Jeśli spodziewasz się czegoś, czego brakuje, musisz odpowiednio sobie z tym poradzić.

Jeśli go zignorujesz, prawdopodobnie tworzysz luki w zabezpieczeniach w swoich aplikacjach. W językach statycznych dostęp do niezdefiniowanej zmiennej jest po prostu niemożliwy. Nie skompiluje ani nie zawiesi twojej aplikacji, jeśli jest zerowa.

Ponadto sprawia, że ​​aplikacja jest nie do utrzymania i oszalejesz, gdy wydarzy się coś nieoczekiwanego. Surowość językowa jest koniecznością, a PHP z założenia jest błędne w wielu aspektach. Jeśli nie będziesz tego świadomy, staniesz się złym programistą.

KNOOPX
źródło
Doskonale zdaję sobie sprawę z wad PHP. Jak zaznaczyłem w pytaniu, mówię o remoncie starszych projektów.
Pekka
Zgoda. Będąc wieloletnim programistą PHP, jest mi dość trudno zapuszczać się w nowe języki, takie jak Java, gdzie trzeba wszystko deklarować.
Dzhuneyt,
0

Nie jestem pewien, jaka jest twoja definicja czytelności, ale właściwe użycie bloków empty (), isset () i try / throw / catch jest bardzo ważne dla całego procesu.

Jeśli Twoje E_NOTICE pochodzi z $ _GET lub $ _POST, to należy je sprawdzić pod kątem funkcji empty () wraz ze wszystkimi innymi kontrolami bezpieczeństwa, które powinny przejść te dane.

Jeśli pochodzi z zewnętrznych źródeł lub bibliotek, powinien być opakowany w try / catch.

Jeśli pochodzi z bazy danych, należy sprawdzić $ db_num_rows () lub jej odpowiednik.

Jeśli pochodzi ze zmiennych wewnętrznych, należy je odpowiednio zainicjalizować. Często tego typu powiadomienia pochodzą z przypisania nowej zmiennej do wartości zwracanej przez funkcję, która zwraca FALSE w przypadku błędu. Powinny być opakowane w test, który w przypadku niepowodzenia może albo przypisać zmiennej akceptowalną wartość domyślną, którą może obsłużyć kod, albo zgłosić wyjątek, który może obsłużyć kod.

Te rzeczy wydłużają kod, dodają dodatkowe bloki i dodają dodatkowe testy, ale nie zgadzam się z tobą, ponieważ myślę, że zdecydowanie dodają dodatkową wartość.

Mlutz
źródło
-2

A co z używaniem @operatora?

Na przykład:

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

Możesz powiedzieć, że jest to złe, ponieważ nie masz kontroli nad tym, co dzieje się "wewnątrz" $ foo (jeśli było to na przykład wywołanie funkcji, które zawiera błąd PHP), ale jeśli używasz tej techniki tylko dla zmiennych, jest to równoważne z:

if(isset($foo) && $foo) { /* ... */ }
Mata
źródło
if(isset($foo))właściwie wystarczy. Zwróci, TRUEjeśli wynikiem wyrażenia jest TRUE.
Dzhuneyt,
2
@ ColorWP.com zwróci również wartość true, jeśli wartość wyrażenia ma wartość false.
Jon Hulka
Powinieneś używać parametru @ (aby zignorować powiadomienie) tylko w kodzie, który tak naprawdę nie jest w dalszym rozwoju, lub w kodzie jednorazowym lub szybkiej poprawce w istniejących projektach, którego nie chcesz nikomu pokazywać. Ale jest to powszechne obejście dla szybkiego włamania.
rubo77