在PHP中testingvariables存在的最佳方法; isset()显然是坏的

isset()文档 :

 isset() will return FALSE if testing a variable that has been set to NULL. 

基本上, isset()不会检查variables是否被设置,而是被设置为NULL而不是NULL

鉴于此,实际检查variables存在的最佳方法是什么? 我尝试了这样的:

 if(isset($v) || @is_null($v)) 

(当$v没有被设置时@需要避免警告),但is_null()isset()有类似的问题:它在未设置的variables上返回TRUE 。 这似乎也是:

 @($v === NULL) 

工作方式与@is_null($v)完全一样,所以也是一样。

我们应该如何可靠地检查PHP中是否存在variables?


编辑:没有设置的variables和被设置为NULLvariables之间的PHP显然是有区别的:

 <?php $a = array('b' => NULL); var_dump($a); 

PHP显示$a['b']存在,并且有一个NULL值。 如果你添加:

 var_dump(isset($a['b'])); var_dump(isset($a['c'])); 

你可以看到我用isset()函数讨论的歧义。 下面是这三个var_dump()s

 array(1) { ["b"]=> NULL } bool(false) bool(false) 

进一步编辑:两件事。

一,用例。 将数组转换为SQL UPDATE语句的数据,其中数组的键是表的列,数组的值是要应用于每列的值。 任何表的列可以保存一个NULL值,通过在数组中传递NULL值来表示。 您需要一种方法来区分不存在的数组键和数组的值设置为NULL ; 这是不更新列的值并将列的值更新为NULL的区别。

其次, Zoredache的回答 array_key_exists()可以正常工作,对于我上面的用例和任何全局variables:

 <?php $a = NULL; var_dump(array_key_exists('a', $GLOBALS)); var_dump(array_key_exists('b', $GLOBALS)); 

输出:

 bool(true) bool(false) 

既然这样可以正确地处理任何地方,我可以看到variables之间不存在任何不明确的地方,variables被设置为NULL我打电话给array_key_exists()在PHP的官方最简单的方法来真正检查是否存在一个variables

(我能想到的另一种情况是类属性,对于其property_exists() ,根据它的文档 ,类似于array_key_exists() ,它正确地区分未被设置和被设置为NULL

如果你正在检查的variables在全局范围内,你可以这样做:

 array_key_exists('v', $GLOBALS) 

试图概述各种讨论和答案:

这个问题没有一个单一的答案可以取代isset可以使用的所有方法。 有些使用案例是由其他职能来解决的,而有些使用案例则不经过审查,或者是高于代码高尔夫的可疑价值。 其他用例远非“破”或“不一致”,这说明为什么issetnull的反应是合乎逻辑的行为。

真正的使用案例(与解决scheme)

1.数组键

数组可以像variables的集合一样对待,未unset ,并将它们视为它们。 但是,由于它们可以被迭代,计数等,所以缺失的值与值为null

在这种情况下的答案是使用array_key_exists()而不是isset()

由于这需要将数组作为函数参数进行检查,如果数组本身不存在,PHP仍然会引发“通知”。 在某些情况下,可以有把握地认为,每一个维度都应该首先被初始化,所以通知正在完成其工作。 对于其他情况,一个“recursion的” array_key_exists函数会依次检查数组的每个维度,这样可以避免这种情况,但基本上和@array_key_exists是一样的。 对于null值的处理也是有一些切合的。

2.对象属性

在传统的“面向对象编程”理论中,封装和多态是对象的关键属性, 在像PHP这样的基于类的OOP实现中,封装的属性被声明为类定义的一部分,并赋予访问级别( publicprotectedprivate )。

然而,PHP也允许你dynamic地添加属性到一个对象,就像你键入一个数组,一些人使用类无对象(技术上,内置stdClass实例,它没有方法或私有function)在一个类似的方式来关联数组。 这导致了一个情况,一个函数可能想知道一个特定的属性是否已经添加到给定的对象。

与数组键一样, 用于检查对象属性的解决scheme也包含在语言中,足够合理地称为property_exists

不合理的用例和讨论

3. register_globals和其他污染的全局命名空间

register_globalsfunction将variables添加到全局范围,这些variables的名称由HTTP请求(GET和POST参数和cookie)的方面确定。 这可能会导致错误和不安全的代码,这就是为什么从2000年8月发布的PHP 4.2开始,在2012年3月发布的PHP 5.4中完全删除的原因。 但是,某些系统仍可能在启用或模拟此function的情况下运行。 也可以使用global关键字或$GLOBALS数组以其他方式“污染”全局名称空间。

首先, register_globals本身不可能意外地产生一个nullvariables,因为GET,POST和cookie的值将始终是string( ''仍然从isset返回true ),并且会话中的variables应完全由程序员控制。

其次,如果重写了一些以前的初始化,那么对于null值的variables的污染只是一个问题。 如果在其他地方的代码区分这两个状态,用“ null ”覆盖未初始化的variables只会是有问题的,所以这种可能性是反对做这种区分的一个论据。

4. get_defined_varscompact

PHP中一些很less使用的函数(如get_defined_varscompact )允许您将variables名称视为数组中的键。 对于全局variables, 超全局数组$GLOBALS允许类似的访问,更常见。 如果在相关范围内未定义variables,这些访问方法的行为将会有所不同。

一旦你决定使用这些机制之一将一组variables当作一个数组来处理,你可以像在任何普通数组上一样对它进行相同的操作。 因此,见1。

仅仅为了预测这些函数将如何运作而存在的function(例如,“在get_defined_vars返回的数组中是否会有一个关键'foo'?”)是多余的,因为您可以简单地运行该函数并找出没有不良影响。

4A。 variablesvariables( $$foo

虽然与将一组variables转换为关联数组的function不完全相同,但大多数情况下使用“variablesvariables” (“分配给基于此其他variables命名的variables”)可以并应该更改为使用关联数组。

一个variables的名字,从根本上说就是程序员赋予一个值的标签; 如果你在运行时确定它,这不是一个真正的标签,而是一些关键价值商店的关键。 更实际的是,通过不使用数组,你正在失去计数,迭代等的能力。 因为它可能被$$foo覆盖,所以也可能变得不可能在键值存储“外部”。

一旦更改为使用关联数组,代码将适用于解决scheme1.可以使用解决scheme2来解决间接对象属性访问(例如$foo->$property_name )。

5. issetarray_key_exists更容易input

我不确定这真的是相关的,但是,PHP的函数名称可能会相当冗长和有时不一致。 显然,PHP的预历史版本使用一个函数名称的长度作为散列键,所以Rasmus故意制作了像htmlspecialchars这样的函数名,这样他们将有不寻常的字符数量。

不过,至less我们不是在写Java,呃? ;)

6.未初始化的variables有一个types

variables基础的手册页包含以下声明:

未初始化的variables具有其types的默认值,具体取决于使用它们的上下文

我不确定在Zend引擎中是否存在“未初始化但已知types”的概念,或者这个语句是否读得太多。

显而易见的是,它们的行为没有实际的区别,因为该页面上描述的未初始化variables的行为与其值为null的variables的行为是相同的。 举一个例子,这个代码中的$a$b将以整数42结尾:

 unset($a); $a += 42; $b = null; $b += 42; 

(第一个会提出一个关于未声明的variables的通知,试图让你编写更好的代码,但是代码的运行方式并没有什么不同。)

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 ,也有可能不会达到回显。 程序员的意图是检测何时从未设置$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不会被显式初始化,所以在第一次testing通过时它将会有一个值,从而无法判断后续testing是否通过。 当variables没有正确初始化时,这实际上是一个非常常见的错误。

为了解决这个问题,我们需要做一些我曾经评论过的东西。 最明显的解决scheme是将$result设置$result some_function永远不会返回的“terminal值” 如果这是null ,那么其余的代码将正常工作。 如果由于some_function具有非常不可预测的返回types(这本身可能是一个坏签名),那么terminal值没有自然的候选值,那么可以使用额外的布尔值,例如$found

思想实验一: very_null常数

PHP在理论上可以提供一个特殊的常量 – 以及null – 在这里用作terminal值; 大概是从函数返回这个是非法的,否则将被强制为null ,同样可能适用于将它作为函数parameter passing。 这会使这个非常特殊的情况稍微简单一些,但是一旦你决定重新分解代码 – 例如,把内部循环放到一个单独的函数中 – 就没用了。 如果常量可以在函数之间传递,则不能保证some_function不会返回它,所以它不再有用作为通用terminal值。

在这种情况下,用于检测未初始化variables的参数归结为该特殊常量的参数:如果用unset($result)replace该注释,并将其与$result = null区别对待,那么您将为$result引入一个“值”无法传递的$result ,只能通过特定的内置函数来检测。

思考实验二:分配计数器

思考最后一个问题的另一种方法是“有什么东西做了$result的分配? 而不是把它看作$result的特殊值,你可以把它看作是有关variables的“元数据”,有点像Perl的“可变隐性”。 所以,你可以把它has_been_assigned_to ,而不是unset reset_assignment_state

但是,如果是这样,为什么停在布尔值? 如果你想知道testing通过了多less次,怎么办? 你可以简单地扩展你的元数据为一个整数,并有get_assignment_countreset_assignment_count

显然,增加这样一个特征将会在语言的复杂性和performance上有所取舍,所以需要仔细权衡它的预期用处。 如同一个very_null不变的常数,只有在非常狭窄的情况下才会有用,并且同样会抵制重新分解。

这个希望显而易见的问题是为什么PHP运行时引擎应该事先假设你想要跟踪这些事情,而不是让你明确地使用普通代码来做。

有时候,我会试图弄清楚在给定的情况下使用哪种比较操作。 isset()仅适用于未初始化或显式空值。 传递/分配null是确保逻辑比较按预期工作的好方法。

尽pipe如此,还是有点难以想象,下面是一个简单的matrix,比较不同的值如何被不同的操作评估:

 | | ===null | is_null | isset | empty | if/else | ternary | count>0 | | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | | $a; | true | true | | true | | | | | null | true | true | | true | | | | | [] | | | true | true | | | | | 0 | | | true | true | | | true | | "" | | | true | true | | | true | | 1 | | | true | | true | true | true | | -1 | | | true | | true | true | true | | " " | | | true | | true | true | true | | "str" | | | true | | true | true | true | | [0,1] | | | true | | true | true | true | | new Class | | | true | | true | true | true | 

为了适应表格,我压缩了一下标签:

  • $a; 指的是一个声明但未分配的variables
  • 第一列中的其他内容都是指定的值,如:
    • $a = null;
    • $a = [];
    • $a = 0;
  • 列是指比较操作,如:
    • $a === null
    • isset($a)
    • empty($a)
    • $a ? true : false

所有结果都是布尔值,打印为false ,省略false

你可以自己运行testing,检查这个要点:
https://gist.github.com/mfdj/8165967

你可以使用紧凑的语言结构来testing一个nullvariables的存在。 不存在的variables将不会在结果中出现,而空值将显示。

 $x = null; $y = 'y'; $r = compact('x', 'y', 'z'); print_r($r); // Output: // Array ( // [x] => // [y] => y // ) 

以你的例子为例:

 if (compact('v')) { // True if $v exists, even when null. // False on var $v; without assignment and when $v does not exist. } 

当然,对于全局范围内的variables,你也可以使用array_key_exists()。

顺便说一句,我会避免像瘟疫那样存在一个不存在的variables和variables具有空值之间的语义差异的情况。 PHP和大多数其他语言只是不觉得有。

解释NULL,逻辑思考

我想所有这一切的明显答案是…不要初始化你的variables为NULL,把它们作为与他们想要成为的东西相关的东西。

正确处理NULL

NULL应该被视为“不存在的值”,这是NULL的含义。 该variables不能被归类为PHP,因为它没有被告知它试图成为什么types的实体。 它可能不存在,所以PHP只是说“好吧,这不是因为毫无意义,NULL是我这样说的。”

一个论据

现在我们来争论。 “但是NULL就像是在说”0“或”FALSE“或”“。

错误的,0-FALSE-''仍然被归类为空值,但是它们被指定为某种types的值或预定的问题答案。 FALSE是是或否的答案, 是对某人提交的标题的回答, 0是对数量或时间等的回答。它们被设置为某种types的回答/结果,使得它们被设定为有效。

NULL只是没有答案,它没有告诉我们是或否,它没有告诉我们时间,它没有告诉我们一个空白的string被提交。 这是理解NULL的基本逻辑。

概要

这不是要制造古怪的function来解决问题,只是改变你的大脑看起来为NULL的方式。 如果它是NULL,假设它没有设置任何东西。 如果你是预先定义的variables,然后预先定义为0,FALSE或“”,取决于你打算使用的types。

随意引用这个。 这是我的逻辑头顶:)

对象属性可以通过property_exists检查是否存在

从unit testing的例子:

 function testPropertiesExist() { $sl =& $this->system_log; $props = array('log_id', 'type', 'message', 'username', 'ip_address', 'date_added'); foreach($props as $prop) { $this->assertTrue(property_exists($sl, $prop), "Property <{$prop}> exists"); } } 

作为greatbigmassive关于NULL意味着什么的讨论的补充,考虑“variables的存在”实际上是什么意思。

在许多语言中,你必须在使用之前明确地声明每个variables ; 这可能决定了它的types,但更重要的是它声明了它的范围 。 一个variables“存在”在其范围内的任何地方,而不是在它之外 – 是一个整体function,或一个单一的“块”。

在它的范围内,一个variables赋予程序员select的标签一些含义 。 在范围之外,这个标签是没有意义的(不pipe你在不同的范围使用相同的标签基本上是不相关的)。

在PHP中,variables不需要声明 – 只要你需要它们就会生效。 当您第一次写入variables时,PHP会为该variables分配一个内存条目。 如果您从当前没有条目的variables中读取数据,则PHP会将该variables的值设为NULL

但是,如果您使用一个variables而不是“初始化”它,自动代码质量检测器通常会警告您。 首先,这有助于检测拼写错误,例如分配给$thingId但从$thingId读取; 但是第二,它迫使你考虑variables具有的意义范围,就像声明一样。

任何关心variables“是否存在”的代码都是该variables范围的一部分 – 无论它是否已经被初始化,作为程序员的你已经给出了该代码的含义。 既然你在使用它,它在某种意义上必须是“存在的”,如果存在的话,它必须有一个隐含的价值; 在PHP中,隐式值为null

由于PHP的工作方式,可以编写代码来处理现有variables的命名空间,而不是作为您赋予意义的标签的范围,而是作为某种键 – 值存储。 例如,你可以像这样运行代码: $var = $_GET['var_name']; $$var = $_GET['var_value']; $var = $_GET['var_name']; $$var = $_GET['var_value'];只是因为你可以,并不意味着这是一个好主意。

事实certificate,PHP有更好的表示键值存储的方法,称为关联数组。 虽然数组的值可以像variables一样对待,但是您也可以对整个数组执行操作。 如果你有一个关联数组,你可以使用array_key_exists()来testing它是否包含一个键。

你也可以用类似的方式使用对象,dynamic地设置属性,在这种情况下,你可以用完全相同的方式使用property_exists() 。 当然, 如果你定义了一个类,你可以声明它拥有哪个属性 – 你甚至可以在publicprivateprotected范围之间进行select。

尽pipe在尚未初始化(或者已经显式地unset() )的variables(与数组键或对象属性相反)和值为null的variables之间存在技术差异,差异是有意义的是以一种他们不打算使用的方式使用variables。

isset检查variables是否设置,如果是,它的是不是NULL。 后一部分(在我看来)不在这个function的范围之内。 没有像样的解决方法来确定variables是否为NULL, 因为它没有被设置,或者因为它被显式地设置为NULL

这是一个可能的解决scheme:

 $e1 = error_get_last(); $isNULL = is_null(@$x); $e2 = error_get_last(); $isNOTSET = $e1 != $e2; echo sprintf("isNOTSET: %d, isNULL: %d", $isNOTSET, $isNULL); // Sample output: // when $x is not set: isNOTSET: 1, isNULL: 1 // when $x = NULL: isNOTSET: 0, isNULL: 1 // when $x = false: isNOTSET: 0, isNULL: 0 

其他解决方法是探测get_defined_vars()的输出:

 $vars = get_defined_vars(); $isNOTSET = !array_key_exists("x", $vars); $isNULL = $isNOTSET ? true : is_null($x); echo sprintf("isNOTSET: %d, isNULL: %d", $isNOTSET, $isNULL); // Sample output: // when $x is not set: isNOTSET: 1, isNULL: 1 // when $x = NULL: isNOTSET: 0, isNULL: 1 // when $x = false: isNOTSET: 0, isNULL: 0 

我不同意你关于NULL的推理 ,并且说你需要改变你关于NULL的思想是很奇怪的。

我认为isset()没有正确devise,isset()应该告诉你,如果variables已经设置,它应该不关心variables的实际值。

如果你正在检查从数据库返回的值,并且其中一个列有NULL值,那么你仍然想知道它是否存在,即使值是NULL … nope在这里不信任isset()。

同样

 $a = array ('test' => 1, 'hello' => NULL); var_dump(isset($a['test'])); // TRUE var_dump(isset($a['foo'])); // FALSE var_dump(isset($a['hello'])); // FALSE 

isset()应该被devise成像这样工作:

 if(isset($var) && $var===NULL){.... 

这样我们把它留给程序员去检查types,而不要让isset()假设它不在那里,因为这个值是NULL – 它只是愚蠢的devise

I'm going to add a quick two cents to this. One reason this issue is confusing is because this scenario seems to return the same result with error reporting not on full:

 $a = null; var_dump($a); // NULL var_dump($b); // NULL 

You could assume from this result that the difference between $a = null and not defining $b at all is nothing.

Crank error reporting up:

 NULL Notice: Undefined variable: b in xxx on line n NULL 

Note: it threw an undefined variable error, but the output value of var_dump is still NULL .

PHP obviously does have an internal ability to distinguish between a null variable and an undefined variable. It seems to me that there should be a built in function to check for this.

I think the accepted answer is good for the most part, but if I was going to implement it I would write a wrapper for it. As previously mentioned in this answer , I have to agree that I haven't actually encountered a situation where this has been a problem. I seem to almost always end up in a scenario where my variables are either set and defined, or they aren't (undefined, unset, null, blank, etc). Not to say that a situation like this won't occur in future, but as it seems to be quite a unique issue I'm not surprised that the PHP devs haven't bothered to put this in.

If I run the following:

 echo '<?php echo $foo; ?>' | php 

我收到一个错误:

 PHP Notice: Undefined variable: foo in /home/altern8/- on line 1 

If I run the following:

 echo '<?php if ( isset($foo) ) { echo $foo; } ?>' | php 

I do not get the error.

If I have a variable that should be set, I usually do something like the following.

 $foo = isset($foo) ? $foo : null; 

要么

 if ( ! isset($foo) ) $foo = null; 

That way, later in the script, I can safely use $foo and know that it "is set", and that it defaults to null. Later I can if ( is_null($foo) ) { /* ... */ } if I need to and know for certain that the variable exists, even if it is null.

The full isset documentation reads a little more than just what was initially pasted. Yes, it returns false for a variable that was previously set but is now null, but it also returns false if a variable has not yet been set (ever) and for any variable that has been marked as unset. It also notes that the NULL byte ("\0") is not considered null and will return true.

Determine whether a variable is set.

If a variable has been unset with unset(), it will no longer be set. isset() will return FALSE if testing a variable that has been set to NULL. Also note that a NULL byte ("\0") is not equivalent to the PHP NULL constant.

尝试使用

 unset($v) 

It seems the only time a variable is not set is when it is specifically unset($v). It sounds like your meaning of 'existence' is different than PHP's definition. NULL is certainly existing, it is NULL.

I have to say in all my years of PHP programming, I have never encountered a problem with isset() returning false on a null variable. OTOH, I have encountered problems with isset() failing on a null array entry – but array_key_exists() works correctly in that case.

For some comparison, Icon explicitly defines an unused variable as returning &null so you use the is-null test in Icon to also check for an unset variable. This does make things easier. On the other hand, Visual BASIC has multiple states for a variable that doesn't have a value (Null, Empty, Nothing, …), and you often have to check for more than one of them. This is known to be a source of bugs.

According to the PHP Manual for the empty() function, "Determine whether a variable is considered to be empty. A variable is considered empty IF IT DOES NOT EXIST or if its value equals FALSE. empty() does not generate a warning if the variable does not exist." (My emphasis.) That means the empty() function should qualify as the "best way to test a variable's existence in PHP", per the title Question.

However, this is not good enough, because the empty() function can be fooled by a variable that does exist and is set to NULL.

I'm interrupting my earlier answer to present something better, because it is less cumbersome than my original answer (which follows this interruption, for comparing).

  function undef($dnc) //do not care what we receive { $inf=ob_get_contents(); //get the content of the buffer ob_end_clean(); //stop buffering outputs, and empty the buffer if($inf>"") //if test associated with the call to this function had an output { if(false!==strpos($inf, "Undef"); //if the word "Undefined" was part of the output return true; //tested variable is undefined } return false; //tested variable is not undefined } 

Two simple lines of code can use the above function to reveal if a variable is undefined:

  ob_start(); //pass all output messages (including errors) to a buffer if(undef($testvar===null)) //in this case the variable being tested is $testvar 

You can follow those two lines with anything appropriate, such as this example:

  echo("variable is undefined"); else echo("variable exists, holding some value"); 

I wanted to put the call to ob_start() and the ($testvar===null) inside the function, and simply pass the variable to the function, but it doesn't work. Even if you try to use "pass by reference" of the variable to the function, the variable BECOMES defined, and then the function can never detect that it previously had been undefined. What is presented here is a compromise between what I wanted to do, and what actually works.

The preceding implies that there is another way to always avoid running into the "Undefined variable" error message. (The assumption here is, preventing such a message is why you want to test to see if a variable is undefined.)

  function inst(&$v) { return; } //receive any variable passed by reference; instantiates the undefined 

Just call that function before doing something to your $testvar:

  inst($testvar); //The function doesn't affect any value of any already-existing variable 

The newly-instantiated variable's value is set to null, of course!

(Interruption ends)

So, after some studying and experimenting, here is something guaranteed to work:

  function myHndlr($en, $es, $ef, $el) { global $er; $er = (substr($es, 0, 18) == "Undefined variable"); return; } $er = false; if(empty($testvar)) { set_error_handler("myHndlr"); ($testvar === null); restore_error_handler(); } if($er) // will be 1 (true) if the tested variable was not defined. { ; //do whatever you think is appropriate to the undefined variable } 

The explanation: A variable $er is initialized to a default value of "no error". A "handler function" is defined. If the $testvar (the variable we want to know whether or not is undefined) passes the preliminary empty() function test, then we do the more thorough test. We call the set_error_handler() function to use the previously-defined handler function. Then we do a simple identity-comparison involving $testvar, WHICH IF UNDEFINED WILL TRIGGER AN ERROR. The handler function captures the error and specifically tests to see if the reason for the error is the fact that the variable is undefined. The result is placed in the error-information variable $er, which we can later test to do whatever we want as a result of knowing for sure whether or not $testvar was defined. Because we only need the handler function for this limited purpose, we restore the original error-handling function. The "myHndlr" function only needs to be declared once; the other code can be copied to whatever places are appropriate, for $testvar or any other variable we want to test this way.

I think the only full solution is to report notices with

 error_reporting(E_ALL); // Enables E_NOTICE 

But you will have to fix all the notices generated by undefined variables, constants, array keys, class properties amongst others. Once you have done that you won't have to worry about the difference between null and not declared variables, and the ambiguity dissappears.

Enabling notice reporting might not be a good alternative in all situations, but there are good reasons to enable it:

Why should I fix E_NOTICE errors?

In my case was more than a year working in a proyect without it, but was used to be careful about declaring variables, so it was fast to transition.

THE only way to know if a variable is defined in current scope ( $GLOBALS is not trustworthy) is array_key_exists( 'var_name', get_defined_vars() ) .

I prefer using not empty as the best method to check for the existence of a variable that a) exists, and b) is not null.

 if (!empty($variable)) do_something();