尝试概述各种讨论和答案:
这个问题没有一个单一的答案可以替代所有isset
可以使用的方式。有些用例是由其他功能解决的,而另一些则不能经受审查,或者具有超出代码标准的可疑价值。其他用例远没有被“破坏”或“不一致”,而是证明了为什么isset
的反应null
而是是逻辑行为。
实际用例(包含解决方案)
1.阵列键
数组可以像对待的变量集合,与unset
和isset
对待他们,好像他们是。但是,由于可以对它们进行迭代,计数等,因此缺失值与值为null
。
在这种情况下,答案是使用array_key_exists()
代替isset()
。
由于这需要将数组作为函数参数进行检查,因此如果数组本身不存在,PHP仍然会发出“ notices”。在某些情况下,可以有效地辩称每个维度都应该首先初始化,因此该通知正在起作用。在其他情况下,“递归” array_key_exists
函数会依次检查数组的每个维度,从而避免了这种情况,但基本上与相同@array_key_exists
。它也与null
值的处理有关。
2.对象属性
在传统的“面向对象程序设计”理论中,封装和多态性是对象的关键属性。在基于类的OOP实现像PHP的,封装的属性声明为类定义的一部分,并给予访问级别(public
,protected
,或private
)。
但是,PHP还允许您动态地向对象添加属性,就像对数组的键一样,有些人stdClass
在类似的方法中使用无类对象(从技术上讲,是内置实例,没有方法或私有功能)。关联数组的方式。这导致函数可能想知道某个特定属性是否已添加到提供给它的对象的情况。
与数组键一样,该语言中也包含一种用于检查对象属性的解决方案,这种语言称为property_exists
。
不合理的用例,并进行讨论
3. register_globals
和其他污染全局名称空间
该register_globals
功能将变量添加到了全局范围,该变量的名称由HTTP请求的各个方面(GET和POST参数以及cookie)确定。这可能导致错误的代码和不安全的代码,这就是为什么自2000年8月发布的PHP 4.2开始默认禁用它,并于2012年3月发布的PHP 5.4中将其完全删除的原因。但是,可能某些系统仍在启用或仿真此功能的情况下运行。也可以使用global
关键字或$GLOBALS
数组以其他方式“污染”全局名称空间。
首先,由于GET,POST和cookie值将始终为字符串(仍从返回),因此register_globals
其自身不太可能意外地产生null
变量,并且会话中的变量应完全在程序员的控制之下。''
true
isset
其次,如果变量null
覆盖了先前的某些初始化,则仅会污染该值。null
仅当其他地方的代码在两种状态之间进行区分时,“覆盖”未初始化的变量才是有问题的,因此,就其本身而言,这种可能性是反对进行这种区分的理由。
4. get_defined_vars
和compact
PHP中很少使用的一些函数(例如get_defined_vars
和compact
)允许您将变量名视为数组中的键。对于全局变量,超全局数组$GLOBALS
允许类似的访问,并且更为常见。如果未在相关范围内定义变量,则这些访问方法的行为将有所不同。
一旦决定使用这些机制之一将一组变量视为数组,就可以对它进行所有操作,就如同对任何普通数组一样。因此,请参见1。
仅用于预测这些函数将如何运行的功能(例如“在返回的数组中是否存在键'foo' get_defined_vars
?”)是多余的,因为您可以简单地运行该函数并找出没有不良影响的函数。
4a。变量($$foo
)
尽管与将一组变量转换为关联数组的函数不太相同,但是大多数情况下使用“变量变量”(“分配给基于该另一个变量命名的变量”)可以并且应该改为使用关联数组。
从根本上说,变量名是程序员赋予值的标签。如果要在运行时确定它,它实际上不是标签,而是某些键值存储中的键。实际上,通过不使用数组,您将失去计数,迭代等功能。在键值存储区的“外部”有一个变量也变得不可能,因为它可能被覆盖$$foo
。
一旦更改为使用关联数组,该代码将适用于解决方案1。$foo->$property_name
可以使用解决方案2 解决间接对象属性访问(例如)。
5. isset
比打字容易得多array_key_exists
我不确定这是否真的相关,但是是的,PHP的函数名称有时会很冗长且不一致。显然,史前版本的PHP使用函数名的长度作为哈希键,因此Rasmus故意像htmlspecialchars
这样编造函数名,以使它们具有不寻常的字符数。
不过,至少我们不写Java,是吗?;)
6.未初始化的变量具有类型
关于变量基础的手册页包含以下语句:
未初始化变量的类型默认值取决于使用它们的上下文
我不确定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
,有一种可能性,即echo
不会甚至达到了,虽然some_test
返回true
。程序员的意图是检测$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
-用作此处的终端值。假定从函数返回此值是非法的,或者将其强制转换为null
,并且将其作为函数参数传递可能也是如此。这会使这个非常特殊的情况稍微简单一些,但是一旦您决定重新构造代码(例如,将内部循环放入一个单独的函数中),它将变得毫无用处。如果常量可以在函数之间传递,则不能保证some_function
不会返回该常量,因此它将不再用作通用终端值。
在这种情况下,用于检测未初始化变量的参数归结为该特殊常数的参数:如果将替换为unset($result)
,并且与区别对待,则将$result = null
引入一个“值”,因为$result
该值不能被传递,只能被由特定的内置功能检测。
思想实验二:作业计数器
关于最后一个if
问题的另一种思考方式是“是否有任务分配给$result
?” 与其将其视为的特殊值,不如将其$result
视为关于变量的“元数据” ,有点像Perl的“变量污点”。因此,而不是isset
你可以叫它has_been_assigned_to
,而非unset
,reset_assignment_state
。
但是,如果是这样,为什么要停在布尔值上呢?如果您想知道测试通过了多少次怎么办?您可以简单地将元数据扩展为整数并具有get_assignment_count
and reset_assignment_count
...
显然,添加这样的功能将在语言的复杂性和性能之间进行权衡,因此需要仔细权衡其预期的有用性。与very_null
常数一样,它仅在非常狭窄的情况下才有用,并且同样会抵抗重构。
希望明确的问题是,为什么PHP运行时引擎应该事先假设您想跟踪这些事情,而不是让您使用常规代码来明确地做到这一点。