我有几个较旧的应用程序在E_NOTICE错误级别上运行时会抛出大量“xyz未定义”和“未定义的偏移量”消息,因为未使用isset()
和consorts显式检查变量的存在。
我正在考虑通过它们来使它们与E_NOTICE兼容,因为关于缺失变量或偏移的通知可以是救生员,可能会有一些小的性能改进,并且它总体上更清洁。
但是,我不喜欢会导致数百isset()
empty()
和array_key_exists()
对我的代码造成影响。它变得臃肿,变得不那么可读,没有在价值或意义上获得任何东西。
如何在没有过多变量检查的情况下构建代码,同时兼容E_NOTICE?
答案 0 :(得分:127)
对于那些感兴趣的人,我已将此主题扩展为一篇小文章,该文章以更好的结构化形式提供了以下信息:The Definitive Guide To PHP's isset And empty
恕我直言,你应该考虑的不只是使应用程序“E_NOTICE兼容”,而是重组整个事情。在代码中有数百个的点经常尝试使用不存在的变量听起来像一个结构相当糟糕的程序。尝试访问不存在的变量永远不应该发生,其他语言在编译时就不会这样做了。 PHP允许你这样做的事实并不意味着你应该这样做。
这些警告是帮助你的,而不是惹恼你。如果你收到警告“你正在尝试使用不存在的东西!”,你的反应应该是“哎呀,我的坏,让我尽快解决。” / em>你怎么能分辨“变量工作得很好的未定义”和诚实错误的代码可能导致严重错误之间的区别?这也是您始终始终使用错误报告turned to 11进行开发并继续插入代码,直到没有发出单NOTICE
为止的原因。关闭错误报告仅适用于生产环境,以避免信息泄漏,即使面对错误的代码也能提供更好的用户体验。
详细说明:
您的代码中的某处始终需要isset
或empty
,减少其发生的唯一方法是正确初始化变量。根据具体情况,有不同的方法:
函数参数:
function foo ($bar, $baz = null) { ... }
无需检查函数中是否设置了$bar
或$baz
,因为您只需设置它们,所有您需要担心的是它们的值是否为true
或{ {1}}(或其他任何东西)。
任何地方的常规变量:
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
在PHP中难以检测,我还没有找到任何执行此操作的库。
答案 1 :(得分: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()
等琐碎的事情做同样的事情。
答案 2 :(得分:13)
我认为解决这个问题的最佳方法之一是通过类访问GET和POST(COOKIE,SESSION等)数组的值。
为每个阵列创建一个类,并声明__get
和__set
方法(overloading)。 __get
接受一个参数,该参数将是值的名称。此方法应使用isset()
或empty()
在相应的全局数组中检查此值,如果存在则返回值,否则返回null
(或其他一些默认值)。
之后,您可以通过以下方式自信地访问数组值:$POST->username
并根据需要进行任何验证,而不使用任何isset()
或empty()
。如果相应的全局数组中不存在username
,则会返回null
,因此不会生成任何警告或通知。
答案 3 :(得分:6)
我不介意使用array_key_exists()
,实际上我更喜欢使用此特定功能而不是依赖 hack 可能会改变他们未来行为的函数,如(为了避免susceptibilities而罢工)。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 = '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')));
答案 4 :(得分:3)
我和你在一起。但PHP设计师犯了比这更糟糕的错误。如果没有为任何价值阅读定义自定义函数,那么就无法解决它。
答案 5 :(得分: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")) // really =, not ==
if($pass=POST("pass"))
if($login=="Admin" && $pass==...) {
// login and pass are not empty, login is "Admin" and pass is ...
$authorized = true;
...
}
答案 6 :(得分:2)
欢迎使用Null合并运算符(PHP&gt; = 7.0.1):
$field = $_GET['field'] ?? null;
PHP说:
对于需要与isset()一起使用三元组的常见情况,添加了空合并运算符(??)作为语法糖。它返回第一个操作数(如果存在且不为NULL);否则它返回第二个操作数。
答案 7 :(得分: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"];
}
?>
答案 8 :(得分:0)
我不确定你的可读性定义是什么,但正确使用empty(),isset()和try / throw / catch块对整个过程非常重要。如果您的E_NOTICE来自$ _GET或$ _POST,那么应该针对empty()以及该数据应该通过的所有其他安全检查进行检查。如果它来自外部提要或库,它应该包含在try / catch中。如果它来自数据库,则应检查$ db_num_rows()或它的等价物。如果它来自内部变量,则应正确初始化它们。通常,这些类型的通知来自于在失败时返回FALSE的函数返回的新变量,这些应该包含在测试中,如果发生故障,可以将变量赋值为可接受的默认值代码可以处理或抛出代码可以处理的异常。这些东西使代码更长,添加额外的块,并添加额外的测试,但我不同意你,因为我认为他们肯定会增加额外的价值。
答案 9 :(得分:-1)
软件不会神圣地运行,如果你期望丢失的东西你需要妥善处理它。如果你忽略它,你可能会在你的应用程序中创建安全漏洞。在静态语言中访问一个非定义的变量是不可能的,如果它是null,它不会简单地编译或崩溃你的应用程序。此外,您的应用程序无法维护,并且当意外情况发生时您会发疯。语言严谨是必须的,而且设计上的php在很多方面都是错误的。如果你不知道,它会让你成为一个糟糕的程序员。
答案 10 :(得分:-3)
使用@运算符怎么样? e.g:
if(@$foo) { /* do something */ }
你可能会说这很糟糕,因为你无法控制$ foo内部发生的事情(例如,如果它是一个包含PHP错误的函数调用)但是如果你只对变量使用这种技术,那么这是等价的到:
if(isset($foo) && $foo) { /* ... */ }