貌似,被 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。
3466 次点击
所在节点    PHP
55 条回复
lizheming
2014-05-13 20:44:42 +08:00
...你确定认真看了我复制的那段话了么?PHP手册上已经说了,会把非整型key转换成整形,同时有一个E_NOTICE的报错。isset()没有报错是因为 isset()有特殊操作,在知道这是一个字符内的非整型key的时候就直接返回false了,具体你可以看手册 http://cn2.php.net/manual/zh/function.isset.php 更新说明里头明确写了:5.4.0 检查字符的非数字偏移量将会返回 FALSE。

以及5楼的URL已经打不开了,就不吐槽这神奇的情况了。以及,真的还是好好看看手册吧……
raincious
2014-05-13 21:36:57 +08:00
@lizheming

* Sigh *

20楼


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

所以你不需要重新说明一遍,真的,12楼的时候我已经把出现这个现象的原因都解释了。

我发这个帖子的目的不是为了让别人挑我用法上的刺。因为本身这样用就是错误的,我也只是意外的填错了参数才发现了这个问题罢了(这是我第二遍说了亲)。如果你真的有空,那么去看看我的代码,做做Review,我真的太感激了。但是这段代码,至少5楼链接里的具体那一行是没有问题的。(当然,至于为什么会有throw、return、break这样奇葩的组合,那真是我很奇葩的原因。而我会在未来某个版本删除掉那些。)



> 以及5楼的URL已经打不开了,就不吐槽这神奇的情况了。以及,真的还是好好看看手册吧……

Github是经常被墙的。难道你认为是我神奇的从Git历史记录里删掉了那个文件来遮丑么?这框架里面有2000多个文件啊,删掉任何一个都会导致问题,我不会那么蠢的。其次,我犯过很多很小的错误,而且他们都还在历史记录里,你甚至能看到修复记录,我不是喜欢删掉罪证然后把他们当作没发生过的家伙。



我现在的想法,是如果有人也觉得这样比较奇葩的的话,我就鼓足勇气去PHP邮件组跟那群真正设计语言的人吧啦下,看看他们能不能思考下这个问题。因为这样也太怪异了。不是么?

不过现在我担心的问题是,当我说“这样设计不太好吧”的时候,他们也会说,“哦,那不是问题,是你用错了,你看我们的手册要怎么用再来吧唧”。就跟……这个帖子的情况一样。

当然,你觉得不怪异可能是你已经接受了这种世界设定,就像他们当初给丢出magic quotes时一样。



我的观点也已经说过了,14楼。
lizheming
2014-05-13 21:56:59 +08:00
@raincious …汗...您已经开启开喷模式了么...

1.我刚才访问5楼的地址的时候是显示Github的404页面的,并不是被墙的问题,墙没被墙我还是能看的出来的:),不过现在正常了。以及我刚才说这句话并没有别的意思,只是单纯的陈述一个事实而已,你真的想太多了。
2.我并没有说这种做法很好,当然也没有说这种做法不好。我认为,你要说一个东西好不好,首先你得会用这个东西,然后你得理解这个东西,人家为什么会这么做,这个不好的地方可能是基于什么样的考虑,然后你才能觉得人家这样做不好。你连人家为什么这样做都不知道就开始说人家的做法很差劲你觉得说的过去么?很不才,我楼上的评论正是在一直疏通这个代码是怎么运行的。如果你早已经明白了的话,您就当我在放屁吧,如果你不明白,就当我在帮您补充点知识以备你在官方邮件组里头和人讨论的时候有点资本。
3.最后欢迎你去邮件组里头和官方讨论一下,也希望您能把讨论结果贴于此方便给大家答疑解惑。

以上。
raincious
2014-05-13 22:11:56 +08:00
@lizheming

1) 不,我只是借这个机会证明我是个诚实的人,哦呵呵。404我真不知道了,当然我刚才在Commit,不知道是不是Git在更新本地的数据。

2) 其实我看过那些内容了。也说了check到最后会变成0,所以……我真的已经看了。你可以看到剩下的我就开始不解为什么会这样了。

我的反应可能和你不一样,我猜测了原因(因为PHP的源代码真难看懂),因为数组下标和字符串下标在C里使用了不同的实现,所以导致处理方式上进行了不同的转换。isset是一种特殊的语法结构,他尝试在不操作目标的情况下对目标进检查(爬一下变量表嘛),然后再转换索引准备爬表的时候,使用了“绝顶聪明”的方法。

好吧,我真的不知道是怎么具体实现的。但是这样的设定,分明就是在造坑啊不是么。我是说,为什么isset($string['check'])能正确的搞出false,isset($string['check'][0])就得先雅蠛蝶的转换下变成$string[0][0]然后搞成true呢?因该也还是的设定false才对吧。 // 请作为用户视角来看待这个问题

3) 我已经不知道咋讨论了,就像22楼说的,说不定人家说“这是你不会用”。不过,等有空还是去吧唧下,不管有没有效果什么的。

4) 我觉得强类型语言真省心啊有没有,比如Python啊,Go啊什么的。
lizheming
2014-05-13 23:24:08 +08:00
@raincious 你说的这个isset()的行为并不是不能理解,不过我懒得说了,怕你又要喷我陷入人家的模式里头了。以及你的评论并没有让我觉得你明白了人家的代码是怎么运作的,只是一个劲的觉得很奇怪,希望你能在邮件组里头得到一个完美的解释吧。最后,我个人还是比较喜欢弱类型语言的。以上!
raincious
2014-05-13 23:38:11 +08:00
@lizheming 我没说你陷入人家的模式啊,我只是说你接受了这个模式。当然,如果PHP那边觉得这样挺好,我也会接受这种模式,现在我已经学到了这个坑了,并且以后也会避免,只是可能我会多一个操作,比如去骂PHP是最好的语言。

(好在我聪明伶俐的在很久之前就在用户输入效验的模块里面加入了is_string判断,所以除了内部调用比如参数或配置填错了之外没有副作用。所以面对这问题我还是很轻松的。)

如果你有原理性的解释来解释为何会发生这种情况请但说无妨啊。不过你先看我12楼的对不对呢,如果 不对&&你愿意 的话帮我De个Bug。
lizheming
2014-05-14 10:45:55 +08:00
@raincious 我一直都是在针对你的7楼的回答,以及你在14楼的回答中说不知道为什么。我的回答就是在帮你找官方的原因,仅此而已:)。isset()这个我认为应该是只判断最后一个key是否存在的,比如 isset($str['f']['f']) 的中文理解可能是 "$str['f']"数组中是否存在'f'这个键值,所以先数组了前面一部分,然后对'f'这个键进行了检测,因为是字符串型的非整型key所以会直接返回false,具体的实例可以看这里: http://3v4l.org/A7Bqb
raincious
2014-05-14 11:32:39 +08:00
@lizheming

哦,14楼可能我让你理解错了。我想说的其实是不知道为什么这帮家伙会做出这样的决定。

总之,在看不懂源代码的情况下,咱们只能论断这两种可能了。要么就是'f'被估值成了0,要么就是PHP只估值了最后一个Key。

当然或许是我没搞清楚你的意思,但是我测试$string['check'][1]的时候,得到的是not found的结果,所以其实貌似不是只估值了最后一个。而还是$string[0][1]这样的,然后't'[1]不存在。如果只估值最后一个结果也就是[1]的话,那么应该得到的是h。

根据上面的来说,$str['f']['f'],或许只是变成$str[0]['f']了(如果这才是你的正确意思的话),所以这是有可能的。

总之,我发了个帖在PHP的新闻组里,然后有个家伙让我直接用array_key_exists代替。除此之外还没有更详细的回复。

http://news.php.net/php.general/323298

然后我就借机抱怨了下
http://news.php.net/php.general/323307

不知道会不会有什么进展。

总之我现在有两个方案来搞定这个问题了。

要么就老老实实地用着复杂组合。if (isset($val) && array_key_exists('key', $val) &&
is_string($val['key']) && !empty($val['key'])) {这些。

要么就利用上面的诡计if (isset($val['key'][1]) && is_string($val['key'])) {,这样一样能正确检测,但是数据不但必须不能是空的,还得多于2个字符。

当然还有一个做法是 isset($val['key'], $val['key'][0])。

我决定就这么纠结着用了。一边等邮件列表里的结果,虽然貌似看来不会有啥结果。

另外感谢你一直回复,我散点分给你哈。
lizheming
2014-05-14 12:01:19 +08:00
@raincious
..已经完全不懂你在说什么了..我的理解是
$string = 'this';
var_dump( isset($string['check'][1] );
这句话中 $string['check'] 因为isset只判断最后一个键 && $string是字符串 && 'check'是一个非整型键 被强制转换成了 $string[0] 也就是 't',然后实际上执行的就是 isset( 't'[1] )了,这里报false不是很正常的么,总共就一个字符串哪里来的1?

你说的两种可能就是一个思路上的东西,并不存在两种的情况...不把$string['check']转换过来的话 isset()怎么知道是从哪个 数组/字符串 里面isset呢?
lazyphp
2014-05-14 12:03:23 +08:00
isset 是一个坑。有时候用来检测 东西是否 赋值了,很容易就出现意外 结果。
于是乎,我现在的程序都是用empty, !empty 去判断了。
raincious
2014-05-14 13:44:13 +08:00
@lizheming 哦,很抱歉,我没给你看邮件原文,很明显这造成了你理解的问题,这是 Topic: http://news.php.net/php.general/323297

事实上这一系列的帖子或许旨在讨论一个问题:“如何使用isset来快速检测当前设定的数组是否为合法的结构”。当然,我觉得目前这个说明是足够的。

isset($string['check'][1]) && is_string($string['check']) 的目的在于,一旦$string是一个字符串,那么这个表达式将准确的返回false。

因为无论isset的行为如何或者'check'到底有没有估值成0,只有当$string['check']是一个字符串(isset中包括$string是否存在,以及$string['check']是否存在的检查),且至少有两个字符的时候,才会是true。

当输入是如下结构的时候:

$string => array(
'check' => 'whatever',
);

表达式将返回true;

而当结构如下的时候

$string = 'whatever';

表达式将返回false。

而且根据我12楼的推导,这样的检查将会是准确的,因为如果$string['check']真的会被估值为$string[0]。那么$string[0][1]总是不存在的,(相反$string[0][0]总是存在的,哪怕$string[0][0][0]...[0][0][0][0][0])。

所以其实我所有的问题都已经解决了。现在我希望能在PHP邮件列表上讨论下关于isset的细节。因为我想要的结果是isset($string['check'][0]) and isset($string['check'])都将返回false。也就是说isset不要对这个'check'进行估值,而应该先检查下数据类型,如果使用了不正确的下标则应该返回false而不是估值成0。

以上。
lizheming
2014-05-14 14:42:43 +08:00
@raincious 关于isset()的问题我我帮助不了你太多了,我只是来理清楚为什么出现这种结果的。更高一层的东西还得由其他人解决,希望你能得到个好结果:)
raincious
2014-05-14 16:04:01 +08:00
@lizheming

关于isset为什么会出现这种情形。我12楼已经推导了(我已经说了好多次了),那是正确的结论,请不要继续纠结。

关于你那个$str['f']['f']还是得继续研究下。不过我想我们都应该意识到猜测是没有意义的,必须去阅读PHP的源代码来找到答案。

如果PHP新闻组那边有什么有价值的更新,或许我会在这里Append。
lizheming
2014-05-14 16:10:34 +08:00
@raincious 12楼说的是PHP怎么"运行"isset()的,并没有说明isset()为什么要这么去做,关于怎么"运行"在5楼的时候我就已经名表了:),以及坐等append。
lizheming
2014-05-14 16:10:44 +08:00
@raincious 12楼说的是PHP怎么"运行"isset()的,并没有说明isset()为什么要这么去做,关于怎么"运行"在5楼的时候我就已经明白了:),以及坐等append。
raincious
2014-05-14 16:26:15 +08:00
@lizheming 我……不是已经给你分了么……

另外其实,文档里的解释是不全面的,因为根据var_dump(isset($str['1x']));在5.4下会返回bool(false)的事实,不应该得出Test1为Found的结果。

我们讨论的是,为什么isset($str['check'])是false,而isset($str['check'][0])则会是true(Test 1和Test 3)。

说了这么多……我想你应该明白了……

以上。


嗯……为什么我们在说这个?我觉得应该聊聊更有意义的话题,比如PHP为什么是最好的语言。
lizheming
2014-05-14 16:37:27 +08:00
@raincious 原来你的感谢加分是这个意思?我一直以为是“另外感谢你一直回复,我散点分给你哈。”这个意思呢…… 5.4以下就不在我的讨论范围了,因为手册上明显的说着是>=5.4加入的。如果只是讨论为什么会有这个结果我想我已经说的很清楚了,如果是讨论PHP为什么要这么做的话我也不清楚,坐等你把官方的消息反馈给我:)。
我也不太想说下去了,思路我已经捋清楚了还在这里饶口舌真是累。
raincious
2014-05-14 16:45:40 +08:00
@lizheming 所以我觉得我们一直在纠结一个乌虚有的问题。

因为你的帖过档案之后,7楼我已经说过了,跟这个没有关系。简单一点来说,我在意的是单维和多维数组得到的结论不相符的问题。但可能我没解释清楚(抱歉,我还在一边做原型设计,然后实现,还要处理框架变更,所以抱歉,精力有限,做出了“你可能懂的”假设。)

isset($string['check']); // Test 3
isset($string['check'][0]); // Test 1

* Sigh *,这两个例子我贴上去之后,竟然没有也感到很奇怪的。

难道没有发现一个检查到了'check'之后false,一个估值成了0然后true这两种结论然后觉得很奇怪的。
lizheming
2014-05-14 16:52:57 +08:00
@raincious 如果是基于官方手册给出的说明”检查字符的非数字偏移量将会返回 FALSE“,我觉得是可以理解的,我的理解在29楼...
raincious
2014-05-14 17:07:05 +08:00
@lizheming 那么Test 1为什么没有返回false,它也是非法下标啊(又没说具体是第几个)? 是因为有第二条规则,即非数字偏移量会被估值转然后换成0(PHP 5.3的做法)。

其实我的意思就是,PHP应该在检测到第一个下标是非数字偏移量的时候,就return false;而他这么做很明显就是为了照顾isset($str['1'])。

我觉得应该烧死那帮用isset($str['1'])的人。

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

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

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

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

© 2021 V2EX