如何避免isset()和empty()


98

我有几个较旧的应用程序,它们在E_NOTICE错误级别上运行时会抛出很多“ xyz未定义”和“未定义偏移”消息,因为没有使用显式检查变量的存在。 isset()和。

我正在考虑通过它们使它们与E_NOTICE兼容,因为有关丢失变量或偏移量的通知可能会节省生命,可能会获得一些较小的性能改进,并且总体而言,这是一种更清洁的方法。

但是,我不喜欢什么造成数以百计的isset() empty()array_key_exists() S ^确实给我的代码。它变得肿,可读性降低,而没有获得价值或意义上的任何东西。

如何在不进行过多变量检查的情况下构造代码,同时又能与E_NOTICE兼容?


6
我完全同意。这就是为什么我非常喜欢Zend Framework,那里的请求模块非常好。如果我正在开发一些小型应用程序,则通常会使用魔术方法__set和__get编写一些简单的请求类,这些方法的工作方式类似于ZF的请求。这样,我就避免了代码中所有isset和empty的出现。这样,您只需要在数组上迭代之前在数组上使用if(count($ arr)> 0)以及在几个关键位置使用if(null!== $ variable)。看起来更干净。
理查德·诺普

Answers:


128

对于感兴趣的人,我已将该主题扩展为一小篇文章,以结构更好的形式提供了以下信息:PHP的isset和空的权威指南


恕我直言,您不仅应该考虑使应用程序“与E_NOTICE兼容”,还应该对整个事情进行重组。有数百个在您的代码中点经常尝试使用不存在的变量听起来像一个结构很差的程序。尝试访问不存在的变量永远不会发生,其他语言在编译时会对此表示沮丧。PHP允许您这样做的事实并不意味着您应该这样做。

这些警告可以帮助您,而不会惹恼您。如果收到警告“您正在尝试使用不存在的东西!” ,您的反应应该是“糟糕,我的糟糕,让我尽快解决该问题。” 您还如何区分“在未定义的情况下正常工作的变量”可能导致严重错误的诚实错误代码之间的区别?这也是为什么您始终,始终使用错误报告进行开发的原因。关闭错误报告仅适用于生产环境,从而避免信息泄漏并即使面对错误代码也能提供更好的用户体验。转为11的情况下进行开发并不断插入代码直到没有一个NOTICE


详细说明:

您将始终需要在代码中issetempty代码中的某个位置,减少它们出现的唯一方法是正确初始化变量。根据情况,有不同的方法可以执行此操作:

函数参数:

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

有没有必要检查是否$bar$baz设置里面的功能,因为你设置它们,你需要担心的是,如果他们的价值评估为truefalse(或任何其他)。

任何地方的常规变量:

$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或。唯一的例外是希望表示某些含义的第三方库,但是由于很难在PHP中检测到,因此我尚未找到任何可以做到这一点的库。false,不是nullnullnull


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'])仅表示“如果为真”。如果您收到关于后者的通知,则您知道自己的逻辑已被搞砸了。
deceze

6
我相信isset()和array_key_exists()之间的区别在于,如果值为NULL,后者将返回true。isset()不会。
Htbaa

1
是的,但是我想不出一个合理的用例,在该用例中,我需要区分一个不存在的变量和一个集合键,其值是null。如果该值评估为FALSE,则区别应该没有区别。:)
deceze

1
数组键肯定比未定义的变量更令人讨厌。但是,如果你不知道数组是否包含一个键或没有,这意味着要么你没有自己定义数组或者你从一个来源,你不用管拉。两种情况都不应该经常发生。并且如果发生这种情况,您有充分的理由检查数组是否包含您认为的内容。这是IMO的安全措施。
kijin 2010年

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
这看起来不错,并且也进行了magic_quotes检查。真好!
Pekka

功能强大!非常感谢分享。
Mike Moore 2010年

3
注意$ _POST ['something']可能返回数组,例如带有的输入<input name="something[]" />。使用上述代码会导致错误(因为无法将修整应用于数组),在这种情况下,应使用is_string并且可能使用strval。这不仅是一种不应该使用get_array两者的情况,因为用户输入(恶意)可能是任何东西,并且用户输入解析器无论如何都不应抛出错误。
Ciantic

1
我使用相同类型的函数,但定义如下:function get_value(&$ item,$ default = NULL){return isset($ item)?$ item:$ default; }此函数的优点是可以使用数组,变量和对象来调用它。缺点是$ item如果没有初始化,则会在之后初始化(为null)。
2012年

您应该全局关闭魔术引号,而不是在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 2010年

1
您不能在“静态类”上使用getter和setter方法。为每个变量编写一个类是不好的做法,因为这意味着代码重复,这是不好的。我认为这种解决方案不是最合适的。
2012年

类的公共静态成员的行为类似于超全局变量,即:HTTP :: $ POST-> username,在该实例中,您可以在使用HTTP之前将其实例化。类HTTP {public static $ POST = array(); ...}; HTTP :: $ POST = new someClass($ _ POST); ...
velcrow

6

我不介意使用该array_key_exists()功能。其实,我更喜欢使用这个特定的功能,而不是依靠黑客可能会改变他们的行为在未来的功能emptyisset(strikedthrough为了避免敏感性)。


但是,我确实使用了一个简单的函数,在处理数组索引时以及其他一些情况下,它很方便:

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

我们已经覆盖了uni和多维数组,我们还可以做什么?


以下面的代码为例:

$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
“依靠将来可能会改变其行为的黑客功能”?!抱歉,但这是我整周听到的最荒谬的事情。首先,issetempty语言结构,不是函数。其次,如果任何核心库函数/语言构造改变了它们的行为,您可能会或可能不会被搞砸。如果array_key_exists改变其行为怎么办?答案是,只要您按照记录使用它,它就不会。并isset据记录就是如此使用。在一个或两个主要发行版本中不建议使用最坏的功能。NIH综合征很严重!
deceze

对不起deceze,但首先的黑客斜体如果你没有注意到。=)其次,你的意思是一个不应该依赖array_key_exists()检查,如果一个键存在?!array_key_exists()正好创造了那个,我宁愿依靠它用于此目的不是isset()和特制empty()其官方描述是:“判断一个变量是否是空的”,如果它确实存在不提及任何东西。您的评论和不赞成票是我一个月以来目睹的最荒谬的事情之一。
Alix Axel

3
我的意思是issetempty它的可靠性与之差不多,array_key_exists并且可以完成完全相同的工作。您的第二个冗长的示例可以编写为$domain = isset($domain['host']) ? $domain['host'] : 'N/A';仅具有核心语言功能,而无需额外的函数调用或声明(请注意,虽然我不一定要使用三元运算符; o)。对于普通的标量变量,您仍然需要使用issetempty,并且可以将它们完全相同地用于数组。“可靠性”是不这样做的不好原因。
deceze

1
您的观点是正确的,尽管我不同意您所说的大部分内容。我认为您在90%以上的案例中都弄错了,例如,我一直在表单的隐藏字段中使用“ 0”的值。仍然我相信我提供的解决方案不应该被否决,并且可能对Pekka 有用
Alix Axel

2
尽管@deceze对自定义函数有一点看法-我通常采取相同的立场-value()方法看起来很有趣,我将对其进行研究。我认为答案和后续行动将使以后偶然发现该问题的每个人都能下定决心。+1。
佩卡

3

我和你在一起 但是PHP设计师犯的错误要严重得多。缺少为任何值读取定义自定义函数的方法,没有任何解决方法。


1
isset()的东西。默认情况下,将所有内容设置为null可以节省很多麻烦。
vava

2
什么是“一切”?对于PHP来说,想象每个可能的变量名并将每个变量名设置为NULL似乎是一种浪费,因为懒惰的开发人员可以避免键入5个字符。
Lotus Notes

5
@Byron,看,这真的很简单,很多其他语言都可以做到这一点,Ruby和Perl只是少数示例。VM知道之前是否使用过变量,不是吗?它总是可以返回null,而不是失败,无论是否出现错误消息。这不是关于糟糕的5个字符,而是关于params["width"] = params["width"] || 5设置默认值的编写,而不是所有关于isset()电话的废话。
vava 2010年

3
很抱歉,要恢复旧线程。PHP中最严重的两个错误是register_globalsmagic_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'])将在$ array中创建FOO键。
2012年

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的不足。正如我在问题中指出的那样,我说的是旧项目的大修。
佩卡

同意 作为一名长期的PHP开发人员,我很难涉足需要声明所有内容的Java之类的新语言。
Dzhuneyt 2012年

0

我不确定您对可读性的定义是什么,但是正确使用empty(),isset()和try / throw / catch块对整个过程非常重要。

如果您的E_NOTICE来自$ _GET或$ _POST,则应将它们与empty()进行检查,同时还要检查该数据必须通过的所有其他安全检查。

如果来自外部提要或库,则应将其包装在try / catch中。

如果它来自数据库,则应检查$ db_num_rows()或其等效项。

如果来自内部变量,则应正确初始化它们。通常,这些类型的通知来自为函数的返回分配新变量,该函数在失败时返回FALSE。应当将它们包装在测试中,以便在发生故障时可以为变量分配代码可以处理的可接受的默认值,或者引发代码可以处理的异常。

这些事情使代码更长,增加了额外的块并增加了额外的测试,但是我不同意我的看法,因为我认为它们绝对可以增加额外的价值。


-2

那使用@运算符呢?

例如:

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

您可能会说这很不好,因为您无法控制$ foo“内部”发生的事情(例如,如果它是一个函数调用,其中包含PHP错误),但是如果仅将此技术用于变量,则等效于:

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

if(isset($foo))实际上就足够了。TRUE如果表达式的计算结果为,它将返回TRUE
Dzhuneyt 2012年

2
@ ColorWP.com如果表达式的计算结果为false,它也会返回true。
乔恩·赫尔卡

您只应在不打算进一步开发的代码上使用@参数(忽略通知),或者在不希望向其他人展示的一次性代码或现有项目的快速修复程序上使用@参数。但这是快速破解的常见解决方法。
rubo77
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.