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?


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.


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.


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.


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


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 emptysą konstrukcje 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ść.


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

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.


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"];
}
?>

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


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ść.


-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) { /* ... */ }

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.