貌似,被 isset 坑了又。

2014-05-12 22:19:37 +08:00
 raincious
故事是这样的:

https://gist.github.com/raincious/682ba152dbd74537e323

Test1 你猜他会echo出什么?

原先我猜是not found,可是竟然是found。



猜测是不是调用isset的时候,PHP默默的跳过了非法的offset,然后使用了正确的数字索引……PHP啊亲,能别这么聪明么?

版本5.5.12。
3434 次点击
所在节点    PHP
55 条回复
yinxingren
2014-05-12 22:30:20 +08:00
Test1 怎么也不会输出found把。
测试了下
PHP 5.3 not found / found / found 抛异常
php 5.4 found / found/ not found 抛异常 且输出了string(1) "t"
php 5.5 found / found / not found 未抛异常 且输出了 string(1) "t"

翻PHP官网去了
raincious
2014-05-12 22:37:51 +08:00
@yinxingren 我又加了个测试(Test4),来测试一个不存在的数字索引。

现在我这里是found / found / not found / not found / Illegal string offset 'check' 之后string 't' (length=1)。

其实我想问的是,这是Bug么……
soli
2014-05-12 22:38:05 +08:00
从来没有正确使用过 isset 。。。
z4213489
2014-05-12 22:46:08 +08:00
为什么要这么用isset呢?
raincious
2014-05-12 22:53:11 +08:00
@z4213489 不是这样用。

本来我有个isset是用来检测数组的,具体是这句:

https://github.com/raincious/facula/blob/30bf00ac0f4408149801745f8e7b1040ca239ca5/src/Facula/Unit/SimpleORM/ORM.php#L520

注意这里用了[0]来检测$jMVal['Field'](应该是字符串)是不是空的。而$jMVal原本应该是数组。

但是我用这个模块的时候,意外的填错了参数,让原本应该是数组的$jMVal变成了字符串,然后报了Illegal string offset。我只是意外isset没拦截到这个问题罢了。

总之,现在加入了is_array来检测$jMVal是不是数组类型了,我的问题算是解决了。剩下的就是isset为什么这么奇怪的问题了。
lizheming
2014-05-12 23:10:27 +08:00
PHP官方手册是这么说的:

Warning:用超出字符串长度的下标写入将会拉长该字符串并以空格填充。非整数类型下标会被转换成整数。非法下标类型会产生一个 E_NOTICE 级别错误。用负数下标写入字符串时会产生一个 E_NOTICE 级别错误,用负数下标读取字符串时返回空字符串。写入时只用到了赋值字符串的第一个字符。用空字符串赋值则赋给的值是 NULL 字符。

http://www.php.net/manual/zh/language.types.string.php
raincious
2014-05-12 23:17:27 +08:00
@lizheming 不,这其实跟那个没关系。要关注的是Example #10里的那些isset,因为是在检测字符串是否被设置。

当然那些都是单维度索引的isset,但是用了多维度的索引就出问题了,比如Test1和Test3的区别。
vibbow
2014-05-13 11:03:51 +08:00
<?php
$string = 'abcdefghijkl';

var_dump(isset($string['check']));
var_dump($string['check']);

var_dump(isset($string['check'][0]));
var_dump($string['check'][0]);

echo "===================\r\n";

$arr = array(
array('a', 'b', 'c'),
array('d', 'e', 'f')
);

var_dump(isset($arr['check']));
var_dump($arr['check']);

var_dump(isset($arr['check'][0]));
var_dump($arr['check'][0]);
?>

运行结果:
C:\Users\vibbow\Desktop>php test.php
bool(false)
string(1) "a"
bool(true)
string(1) "a"
===================
bool(false)
NULL
bool(false)
NULL


应该是在把String当Array访问时,PHP有特殊的处理。

或许如 #6 所说的,虽然 $string['check'] 不存在,但是你访问的时候,下标的string会被强行转换成int (你这本身就是个string,所以PHP做这种强行转换是合理的,因为String类型的Array不可能有string类型的下标)

当你的array是个原生的array,可以看到,就不存在这种“BUG”了。
vibbow
2014-05-13 11:08:51 +08:00
以上结果在
PHP 5.4.28 @ Windows 7
PHP 5.5.9 @ Ubuntu 14.04
里表现态一致。
vibbow
2014-05-13 11:10:49 +08:00
再补充一个测试,虽然结果应该是显而易见的。
<?php
$string = 'abcdefghijkl';

var_dump(isset($string['check']));
var_dump($string['check']);

var_dump(isset($string['check'][1]));
var_dump($string['check'][1]);
?>

运行结果:
C:\Users\vibbow\Desktop>php test.php
bool(false)
string(1) "a"
bool(false)
string(0) ""
vibbow
2014-05-13 11:17:35 +08:00
对了,检测String类型(仅String类型)是否为空时,empty函数也是不靠谱的。
因为 "0" 会被认为是 空。
raincious
2014-05-13 11:29:29 +08:00
@vibbow 是的。应该是有特殊的处理。

Test1里面的情况看起来是这样的:

$string['check'][0] => $string[(int)'check'][0] => $string[0][0]

看来PHP在取完$string[0]的值之后得到了字符t,然后比较了't'[0],然后isset就通过了。所以造成了上面的问题。

看来以后做这类比较的时候得先判断下数据类型,以免造成问题(甚至可能是安全问题)。


而且还得注意取值的时候并没有做这类转换。
lizheming
2014-05-13 14:01:09 +08:00
@raincious keypoint就是我找的这段,String以Array形式访问的话不存在的key会被强制转换,这里是`interval('check')`就是0了。
raincious
2014-05-13 14:11:24 +08:00
@lizheming

嗯。

不明白为什么isset和取值会用两种方式。虽然在字符串上这样用多维下标不是正确的用法,但这样不规律的隐式转换感觉是欠考虑了啊,isset和取值应该都被隐式转换或都不被隐式转换才是正确的逻辑吧。

不明白那群家伙怎么想的,或许……是给忘了。
vibbow
2014-05-13 14:22:26 +08:00
@raincious 很规律啊
对于字符串当array用,强制把下标转换成integer
对于正常的array,就正常用啊。
vibbow
2014-05-13 14:24:05 +08:00
@raincious 还有,如果你打开了错误报告,用string当下标访问字符串,PHP会raise一个warning级别的错误的。
lizheming
2014-05-13 20:05:32 +08:00
@raincious 这个和isset完全没有关系啊骚年,在$string['check']这个的时候你已经就是不规范写法然后被PHP强制转换了啊,话说判断字符串是否为空难道不是应该直接 $string!="" 么...
raincious
2014-05-13 20:10:52 +08:00
@lizheming 你不觉得当遇到“不规范”的写法时,isset就应该因为找不到‘check’这个索引而失败么?没事“好心”转换integer才是有问题吧。
lizheming
2014-05-13 20:19:10 +08:00
@raincious 卧槽,v2ex的消息提醒终于给力了一回....代码跟isset真的没关系啊...在isset()之前'check'就已经被转换成0了...至于为什么会有这么神奇的情况我也不太清楚,具体还得去看一下源码才能知道..不过这个真的有心无力,既然手册已经明摆写出来了就这么用着吧,而且这代码是会有报错的,报错才是“不规范”的写法的提示吧……
最后的最后,我其实很纳闷为啥lz你会写出如此奇葩的代码出来...
raincious
2014-05-13 20:27:52 +08:00
@lizheming 什么叫奇葩的代码?上面已经说了怎么发现的这样的情况,你可以看到5楼。发这个帖子的时候我自己的Bug已经修复好了。只是奇怪这样的情况才来问。

另外“在isset()之前'check'就已经被转换成0了”,如果真的是这样的话,那么var_dump也应该是[0][0]才对,那么Illegal string offset就不会出现了。

所以是isset和var_dump对待$string['check']的方式不一致时才会出现这个问题。

代码我已经翻了,对我来说太乱了,已经迷失。正考虑是不是要去PHP邮件组上把这个问题再吧啦一遍。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/112241

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX