【问题标题】:How to avoid isset() and empty()如何避免 isset() 和 empty()
【发布时间】:2010-12-29 22:50:04
【问题描述】:

我有几个较旧的应用程序在 E_NOTICE 错误级别上运行时会抛出大量“xyz is undefined”和“undefined offset”消息,因为没有使用isset() 和 consorts 显式检查变量的存在。

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

但是,我不喜欢将数百个 isset() empty()array_key_exists() s 对我的代码造成的影响。它变得臃肿,变得不那么可读,没有任何价值或意义。

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

【问题讨论】:

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

标签: php error-reporting


【解决方案1】:

只需为此编写一个函数。比如:

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() 等琐碎的事情也这样做。

【讨论】:

  • 这看起来不错,并且还做了 magic_quotes 检查。不错!
  • 很棒的功能!非常感谢分享。
  • 注意 $_POST['something'] 可能返回数组,例如输入<input name="something[]" />。使用上面的代码这会导致错误(因为修剪不能应用于数组),在这种情况下应该使用is_string 并且可能使用strval。这不仅仅是一个应该使用get_array 的情况,因为用户输入(恶意)可能是任何东西,用户输入解析器无论如何都不应该抛出错误。
  • 我使用相同类型的函数,但定义如下: function get_value(&$item, $default = NULL) { return isset($item) ? $项目:$默认;这个函数的优点是可以用数组、变量和对象调用它。缺点是 $item 如果没有被初始化(为 null)。
  • 您应该全局关闭魔术引号,而不是在 1 个函数中处理它们。互联网上有很多解释魔术引号的资源。
【解决方案2】:

对于那些感兴趣的人,我已将此主题扩展为一篇小文章,它以更好的结构形式提供了以下信息:The Definitive Guide To PHP's isset And empty


恕我直言,您不仅应该考虑让应用“兼容 E_NOTICE”,还应该重新构建整个应用。在您的代码中有 数百 个点经常尝试使用不存在的变量,这听起来像是一个结构相当糟糕的程序。尝试访问不存在的变量永远不会发生,其他语言在编译时会对此犹豫不决。 PHP 允许你这样做并不意味着你应该这样做。

这些警告是为了帮助你,而不是为了惹恼你。如果您收到警告“您正在尝试使用不存在的东西!”,您的反应应该是“糟糕,我的错,让我尽快解决这个问题。”您还打算如何区分“未定义的正常工作的变量”可能导致严重错误的真正错误代码?这也是为什么你总是,总是,使用错误报告 turned to 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 或类似的值,即您可以评估的值issetempty,取决于您的意图。如果您经常将数组键设置为null 并希望它表示除false 之外的任何内容,即如果在上面的示例中issetarray_key_exists 的不同结果对您的程序逻辑产生影响,您应该问自己为什么。变量的存在不应该是重要的,只有它的值才是重要的。如果密钥是true/false 标志,则使用truefalse,而不是null。唯一的例外是希望null 具有某种含义的第三方库,但由于null 在PHP 中很难检测到,所以我还没有找到任何可以做到这一点的库。

【讨论】:

  • 是的,但大多数失败的访问尝试都与if ($array["xyz"]) 类似,而不是isset()array_key_exists(),我觉得这有点合理,当然不是结构性问题(如果我弄错了,请纠正我)。添加array_key_exists() 对我来说简直是一种可怕的浪费。
  • 我想不出任何我会使用array_key_exists 而不是简单的isset($array['key'])!empty($array['key']) 的情况。当然,两者都会在您的代码中添加 7 或 8 个字符,但我几乎不会称其为问题。它还有助于澄清您的代码:if (isset($array['key'])) 表示此变量确实是可选的并且可能不存在,而 if ($array['key']) 仅表示“如果为真”。如果你收到后一个通知,你就知道你的逻辑在某个地方搞砸了。
  • 我相信 isset() 和 array_key_exists() 之间的区别在于,如果值为 NULL,后者将返回 true。 isset() 不会。
  • 没错,但我想不出一个理智的用例,我需要区分一个不存在的变量和一个值为空的集合键。如果该值评估为 FALSE,则区别应该没有区别。 :)
  • 数组键肯定比未定义的变量更烦人。但是如果你不确定一个数组是否包含一个键,这意味着要么你没有自己定义数组或者你从源中提取它你无法控制。这两种情况都不应该经常发生;如果发生这种情况,您有充分的理由检查数组是否包含您认为的内容。这是 IMO 的一项安全措施。
【解决方案3】:

我和你在一起。但是 PHP 设计者犯了比这更严重的错误。没有为任何值读取定义自定义函数,没有任何方法可以解决它。

【讨论】:

  • isset() 的东西。默认情况下将所有内容设为 null 会省去很多麻烦。
  • 这“一切”是什么?对于 PHP 来说,必须想象每个可能的变量名并将每个变量名设置为 NULL 以便懒惰的开发人员可以避免输入 5 个字符,这似乎是一种浪费。
  • @Byron,看,这真的很简单,很多其他语言都这样做,Ruby 和 Perl 只是几个例子。 VM 知道之前是否使用过变量,不是吗?它总是可以返回 null 而不是在有或没有错误消息的情况下失败。这不是关于糟糕的 5 个字符,而是关于编写 params["width"] = params["width"] || 5 来设置默认值,而不是使用 isset() 调用的所有废话。
  • 很抱歉复活了一个旧线程。 PHP 最严重的两个错误是register_globalsmagic_quotes。相比之下,这些助长的问题使未初始化的变量看起来几乎无害。
【解决方案4】:

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

如果您的 E_NOTICE 来自 $_GET 或 $_POST,则应针对 empty() 以及该数据必须通过的所有其他安全检查对它们进行检查。

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

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

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

这些东西使代码更长,添加了额外的块,并添加了额外的测试,但我不同意你的观点,我认为它们肯定会增加额外的价值。

【讨论】:

    【解决方案5】:

    我不介意使用array_key_exists() 函数。事实上,我更喜欢使用 这个特定功能 而不是依赖 hack 功能,这些功能可能会在未来改变它们的行为比如@987654324 @ 和 isset(删除线以避免 susceptibilities)。


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

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

    作为一个额外的例子,take the RealIP() function 用于测试:

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

    整洁,对吧? ;)

    【讨论】:

    • “依赖可能在未来改变其行为的黑客功能”?!对不起,但这是我整个星期听到的最荒谬的事情。首先,issetempty语言结构,而不是函数。其次,如果任何核心库函数/语言结构改变了它们的行为,你可能会也可能不会被搞砸。如果array_key_exists 改变了它的行为怎么办?答案是它不会,只要您按照记录使用它。并且isset 被记录为完全如此使用。最坏情况下的函数在一个或两个主要版本上被弃用。 NIH 综合症很糟糕!
    • 我很抱歉 deceze,但首先 hackitalics 表示,以防您没有注意到。 =) 其次,您的意思是不应该依赖array_key_exists() 来检查 array 中是否存在 key?! array_key_exists() 正是为此而创建的,我宁愿依赖它而不是 isset() 尤其是 empty(),其官方描述是:“确定变量是否为空”,不是如果它确实存在,它会提到任何东西。您的评论和否决票是我在整个 中目睹的最荒谬的评论之一。
    • 我是说issetempty 并不比array_key_exists 更可靠或更不可靠,并且可以完成完全相同的工作。您的第二个冗长的示例可以写为$domain = isset($domain['host']) ? $domain['host'] : 'N/A';,仅具有核心语言功能,不需要额外的函数调用或声明(请注意,我不一定提倡使用三元运算符;o))。对于普通的标量变量,您仍然需要使用issetempty,并且可以以完全相同的方式将它们用于数组。 “可靠性”是不这样做的一个坏理由。
    • 你的观点是正确的,虽然我不同意你所说的大部分内容。我认为您在 90% 以上的情况下弄错了,例如,我一直在表单的隐藏字段中使用“0”的值。我仍然相信我提供的解决方案不值得被否决并且很可能对 Pekka 有一些用处
    • 虽然@deceze 对自定义函数有意见——我通常采取相同的立场——但 value() 方法看起来很有趣,我将对其进行研究。我认为答案和后续行动将使后来偶然发现它的每个人都能做出自己的决定。 +1。
    【解决方案6】:

    软件不会在上帝的恩典下神奇地运行。如果您期望缺少某些东西,则需要正确处理它。

    如果您忽略它,您可能会在您的应用程序中创建安全漏洞。在静态语言中访问未定义的变量是不可能的。如果它为 null,它不会简单地编译或崩溃您的应用程序。

    此外,它会使您的应用程序无法维护,并且当意外发生时您会发疯。语言严格性是必须的,而 PHP 在设计上在很多方面都是错误的。如果你不知道,它会让你成为一个糟糕的程序员。

    【讨论】:

    • 我很清楚 PHP 的不足之处。正如我在问题中所指出的,我说的是对旧项目的大修。
    • 同意。作为一名长期的 PHP 开发人员,我很难冒险涉足 Java 等需要声明所有内容的新语言。
    【解决方案7】:

    我认为解决此问题的最佳方法之一是通过类访问 GET 和 POST(COOKIE、SESSION 等)数组的值。

    为每个数组创建一个类并声明__get__set 方法(overloading)。 __get 接受一个参数,该参数将是一个值的名称。此方法应在相应的全局数组中检查该值,使用isset()empty(),如果存在则返回该值,否则返回null(或其他一些默认值)。

    之后,您可以放心地以这种方式访问​​数组值:$POST-&gt;username 并在需要时进行任何验证,而无需使用任何 isset()s 或 empty()s。如果对应的全局数组中不存在username,则返回null,因此不会产生警告或通知。

    【讨论】:

    • 这是个好主意,我已经准备好为之重构代码。 +1
    • 不幸的是,除非您将它们分配给 $_GET 或 $_POST,否则您将无法使这些实例具有超全局性,这将非常难看。但是你当然可以使用静态类...
    • 您不能在“静态类”上使用 getter 和 setter。并且为每个变量编写一个类是不好的做法,因为它意味着代码重复,这是不好的。我不认为这个解决方案是最合适的。
    • 一个类的公共静态成员就像一个超全局的,即:HTTP::$POST->username,你可以在它使用之前的某个时刻实例化 HTTP::$POST,即。 HTTP 类 { public static $POST = array();...}; HTTP::$POST = new someClass($_POST);...
    【解决方案8】:

    我使用这些功能

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

    【讨论】:

    • 我也使用这个,但请记住,在某些情况下,您的变量会自动初始化:例如 load($array['FOO']) 会在 $array 中创建一个 FOO 键。
    【解决方案9】:

    如何使用@ 运算符?

    例如:

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

    你可能会说这很糟糕,因为你无法控制“内部”发生的事情 $foo(例如,如果它是一个包含 PHP 错误的函数调用),但如果你只对变量使用这种技术,这是相当于:

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

    【讨论】:

    • if(isset($foo)) 其实就够了。如果表达式的计算结果为TRUE,它将返回TRUE
    • @ColorWP.com 如果表达式的计算结果为假,它也会返回真。
    • 您应该只在没有真正进一步开发的代码上使用@参数(忽略通知),或者一次性代码或现有项目的快速修复,您不'不想给其他人看。但这是快速破解的常见解决方法。
    【解决方案10】:

    创建一个函数,如果未设置,则返回 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"];
    }
    ?>
    

    【讨论】:

      【解决方案11】:

      欢迎来到null coalescing operator(PHP >= 7.0.1):

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

      PHP 说:

      已添加空合并运算符 (??) 作为语法糖,用于需要将三元组与 isset() 结合使用的常见情况。如果存在且不为 NULL,则返回其第一个操作数;否则返回第二个操作数。

      【讨论】:

        猜你喜欢
        • 2011-11-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-14
        相关资源
        最近更新 更多