尝试概述各种讨论和答案:
这个问题没有单一的答案可以替代isset 的所有使用方式。 一些用例由其他功能解决,而另一些则经不起推敲,或者除了代码高尔夫之外,还有可疑的价值。远非“破碎”或“不一致”,其他用例说明了为什么isset 对null 的反应是合乎逻辑的行为。
真实用例(带有解决方案)
1。数组键
可以将数组视为变量的集合,unset 和 isset 将它们视为变量的集合。但是,由于它们可以被迭代、计数等,缺失值与值为null 的值不同。
在这种情况下,答案是使用array_key_exists() 而不是isset()。
由于这是将要检查的数组作为函数参数,因此如果数组本身不存在,PHP 仍会发出“通知”。在某些情况下,可以有效地争论每个维度应该首先被初始化,所以通知正在完成它的工作。对于其他情况,依次检查数组的每个维度的“递归”array_key_exists 函数可以避免这种情况,但基本上与@array_key_exists 相同。它也与null 值的处理有些相切。
2。对象属性
在传统的“面向对象编程”理论中,封装和多态是对象的关键属性;在像 PHP 这样的基于类的 OOP 实现中,封装的属性被声明为类定义的一部分,并被赋予访问级别(public、protected 或 private)。
然而,PHP 还允许您动态地向对象添加属性,就像您将键添加到数组中一样,并且有些人使用无类对象(技术上,内置 stdClass 的实例,它没有方法或私有功能)以与关联数组类似的方式。这会导致函数可能想知道某个特定属性是否已添加到给它的对象中。
与数组键一样,语言中包含一个检查对象属性的解决方案,称为property_exists。
不合理的用例,有讨论
3。 register_globals,以及其他对全局命名空间的污染
register_globals 功能将变量添加到全局范围,其名称由 HTTP 请求的各个方面(GET 和 POST 参数以及 cookie)确定。这可能会导致错误和不安全的代码,这就是为什么它在PHP 4.2, released Aug 2000 之后默认被禁用并在PHP 5.4, released Mar 2012 中完全删除。但是,某些系统可能在启用或模拟此功能的情况下仍在运行。也可以使用global 关键字或$GLOBALS 数组以其他方式“污染”全局命名空间。
首先,register_globals 本身不太可能意外生成 null 变量,因为 GET、POST 和 cookie 值将始终是字符串('' 仍然从 isset 返回 true),并且会话中的变量应该完全在程序员的控制之下。
其次,只有当这覆盖了一些先前的初始化时,才会对值为 null 的变量造成污染。仅当其他地方的代码区分这两种状态时,使用null“覆盖”未初始化的变量才会有问题,因此这种可能性本身就是一个论点反对做出这样的区分。
4。 get_defined_vars 和 compact
PHP 中一些很少使用的函数,例如get_defined_vars 和compact,允许您将变量名视为数组中的键。对于全局变量,the super-global array $GLOBALS 允许类似的访问,并且更常见。如果变量未在相关范围内定义,这些访问方法的行为会有所不同。
一旦您决定使用其中一种机制将一组变量视为一个数组,您就可以对它执行所有与对任何普通数组相同的操作。因此,请参见 1。
仅用于预测这些函数将如何运行的功能(例如,“get_defined_vars 返回的数组中是否会有键 'foo'?”)是多余的,因为您可以简单地运行该函数并找到没有任何不良影响。
4a。变量变量($$foo)
虽然与将一组变量转换为关联数组的函数不太一样,但大多数使用"variable variables"(“分配给基于此其他变量的变量”)的情况可以并且应该更改为使用关联代替数组。
从根本上说,变量名是程序员赋予值的标签;如果您在运行时确定它,它实际上并不是一个标签,而是某个键值存储中的一个键。更实际的是,如果不使用数组,您将失去计数、迭代等的能力;也可能无法在键值存储“外部”拥有一个变量,因为它可能会被$$foo 覆盖。
一旦更改为使用关联数组,代码将适用于解决方案 1。间接对象属性访问(例如 $foo->$property_name)可以通过解决方案 2 解决。
5。 isset 比 array_key_exists 更容易输入
我不确定这是否真的相关,但是是的,PHP 的函数名称有时会非常冗长且不一致。显然,史前版本的 PHP 使用函数名称的长度作为哈希键,因此 Rasmus 故意编造函数名称,如 htmlspecialchars,这样它们的字符数就会不寻常......
不过,至少我们不是在编写 Java,是吗? ;)
6。未初始化的变量有一个类型
manual page on variable basics 包含以下声明:
未初始化的变量具有其类型的默认值,具体取决于使用它们的上下文
我不确定 Zend 引擎中是否存在“未初始化但已知类型”的概念,或者这是否对声明的解读过多。
很明显,这对它们的行为没有实际影响,因为该页面上描述的未初始化变量的行为与值为 null 的变量的行为相同。举一个例子,这段代码中的$a 和$b 最终都将是整数42:
unset($a);
$a += 42;
$b = null;
$b += 42;
(第一个会引发关于未声明变量的通知,试图让您编写更好的代码,但它不会对代码的实际运行方式产生任何影响。)
99。检测函数是否运行
(把这个放在最后,因为它比其他的要长得多。也许我稍后会编辑它......)
考虑以下代码:
$test_value = 'hello';
foreach ( $list_of_things as $thing ) {
if ( some_test($thing, $test_value) ) {
$result = some_function($thing);
}
}
if ( isset($result) ) {
echo 'The test passed at least once!';
}
如果some_function 可以返回null,则即使some_test 返回true,也可能无法到达echo。程序员的意图是检测 $result 何时从未设置,但 PHP 不允许他们这样做。
但是,这种方法还有其他问题,如果您添加外部循环,这些问题就会变得清晰:
foreach ( $list_of_tests as $test_value ) {
// something's missing here...
foreach ( $list_of_things as $thing ) {
if ( some_test($thing, $test_value) ) {
$result = some_function($thing);
}
}
if ( isset($result) ) {
echo 'The test passed at least once!';
}
}
因为$result 从未显式初始化,所以它会在第一个测试通过时获取一个值,因此无法判断后续测试是否通过。 当变量未正确初始化时,这实际上是一个极其常见的错误。
要解决此问题,我们需要在我评论说缺少某些内容的那一行做一些事情。最明显的解决方案是将$result 设置为some_function 永远无法返回的“终端值”;如果这是null,那么其余代码将正常工作。如果由于some_function 具有极其不可预测的返回类型(这本身可能是一个不好的迹象)而没有自然的终端值候选者,那么一个额外的布尔值,例如$found,可以改用。
思想实验一:very_null常量
理论上,PHP 可以提供一个特殊的常量 - 以及 null - 在这里用作终端值;据推测,从函数返回 this 是非法的,或者它会被强制转换为null,同样的情况也可能适用于将它作为函数参数传入。这将使这个非常具体的情况稍微简单一些,但是一旦您决定重构代码 - 例如,将内部循环放入单独的函数中 - 它就会变得无用。如果常量可以在函数之间传递,则不能保证some_function 不会返回它,因此它不再用作通用终结值。
在这种情况下,用于检测未初始化变量的参数归结为该特殊常量的参数:如果您将注释替换为 unset($result),并将其处理方式与 $result = null 不同,则您正在为 @ 引入一个“值” 987654395@不能传,只能通过特定的内置函数检测到。
思想实验二:赋值计数器
另一种思考最后一个if 所问问题的方式是“有什么分配给$result 的吗?”与其将其视为 $result 的特殊值,不如将其视为变量的“元数据”关于,有点像 Perl 的“变量污染”。因此,您可以将其称为has_been_assigned_to,而不是isset,而不是unset、reset_assignment_state。
但如果是这样,为什么要停在布尔值上?如果您想知道多少次测试通过了怎么办?您可以简单地将元数据扩展为整数并拥有get_assignment_count 和reset_assignment_count...
显然,添加这样的功能会在语言的复杂性和性能方面进行权衡,因此需要仔细权衡其预期用途。与very_null 常量一样,它仅在非常狭窄的情况下才有用,并且同样可以抵抗重构。
希望显而易见的问题是,为什么 PHP 运行时引擎应该提前假设您想要跟踪这些事情,而不是让您使用普通代码明确地进行。