貌似,被 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 条回复
lizheming
2014-05-14 18:07:49 +08:00
@raincious 29楼不是说了很清楚么.. isset($str['check'])的中文意思是检查$str 数组/字符串 内是否存在 'check' 键,isset($str['check'][0])的意思是检查 $str['check'] 这个 数组/字符串 内是否存在 0 键.. 所以$str['check']会先转换,因为只有得到 $str['check']是什么才能确定是否有0键... isset($str['f']['f'])其实也是一样的,检测$str['f']中是否有 'f' 键,先转换$str['f']所以会有报错,但是'f'是字符串所以直接返回false了。
raincious
2014-05-14 19:39:54 +08:00
@lizheming

你以为我不明白你29楼在说什么?

> isset($str['check'][0])的意思是检查 $str['check'] 这个 数组/字符串 内是否存在 0 键.. 所以$str['check']会先转换

(事实上你也说过好多次了。)

就这个问题,更准确的说,我40楼是在问“为什么要转换?”,为什么不发现'check'这个数组是非法下标之后直接return false?

好了,我觉得你已经清楚我12后之后一直在说什么了。
lizheming
2014-05-14 20:09:58 +08:00
@raincious 我真没觉得你明白我29楼在说什么,为什么要转换我不是说了么“不把$string['check']转换过来的话 isset()怎么知道是从哪个 数组/字符串 里面isset呢?”,而且我41楼又重复说了一次“因为只有得到 $str['check']是什么才能确定是否有0键”...
比如说
$a = array( array=>(1,2) );
var_dump( isset($a[0][3]) );

isset()是先获取到了 $a[0] 为 array(1,2) 之后然后再在这里面检查 3 这个键是否存在。这样说应该好理解了吧╮(╯▽╰)╭...

p.s.其实我也不想要重复说啊..因为真的很累啊....但是...真的就只有这点东西嘛...
raincious
2014-05-14 21:11:22 +08:00
@lizheming

哈哈。其实,我搞清楚这个问题之后,一直是在抱怨,可能我用了疑问句,所以让你误解了。我觉得我再不说清楚咱们俩都得进入死循环了。

因为我知道这个问题就算我在这里问也是搞不清楚的,因为要认真的阅读过源代码才会了解。所以我希望是在PHP新闻组上得到解答,或者被忽略。

不过你说的这个“因为只有得到 $str['check']是什么才能确定是否有0键”,没错啊,但是不一定要先估值转换啊,它可以直接因为$str['check']这个根本不合法而直接返回false啊对不对?



这个问题呢,就像是我写了个很垃圾的函数,叫做,嗯……比如说get_file_contents,嗯,get_file_contents。它是用来打开文件的,但是这个函数有个很讨厌的功能,就是如果这个文件不存在并且目标文件的文件名是以PHP结尾的话,他会给你建立一个文件,然后写入“PHP是最好的语言”这个字符串,然后返回。

有一天,你用到了这个函数,用来它来获得一个文件,比如叫“任何文件”。但是这个文件可能存在也可能不存在。问题是这个文件的文件名不是由你控制的(而是由其他程序员以及配置控制的)。

你搞清楚了get_file_contents的机制知道有这个问题。于是为了安全,你效验了文件名,写下了如下代码:

if (文件名的结尾(文件名变量) != 'PHP') {
return get_file_contents(文件名变量);
} elseif (文件存在(文件名变量)) {
return 另一个更加麻烦或者因为某些原因不建议使用的函数(文件名变量);
}

然后你开始了抱怨,是啊,如果get_file_contents没有判断文件是不是以PHP结尾这样糟糕的特性该多好,那时候你就可以直接return get_file_contents(文件名变量);了不是么?

于是你抱怨上了论坛。询问get_file_contents为什么会这样?

这个时候另一个人看到了你的问题,会答到:

这是因为get_file_contents会判断文件名结尾,然后调用另一个函数put_file_contents来写入“PHP是最好的语言”。

然后你说,为什么需要这样?难道返回一个false不是更好么?

这时候那人又说,你得读文件啊,文件必须读过才知道是不是有内容啊,这都发生在put_file_contents之后。



所以你看,我们的方向是不一样的。我期待的讨论是,是不是底层上有什么结构会导致我的设想无法成立,另外是不是我的设想就不对呢?接着我还可能会问“那么为什么不能实现我的设想,因为那样更加直觉正确”,最后可能真的能促成这件事(虽然,这,简直微乎其微,大PHP怎么会为这点小事而改动)。


我期待的结果可能是比如这样的讨论“$string对于编译器来说是一个T_VARIABLE,除此之外编译器对其一无所知。而isset发生与编译阶段,于是它只知道$string的类型,而不知道$string其中内容的类型”。当然前面是扯淡,PHP会解析这些东西,然后得到所有TOKEN的必要信息,包括它是什么以及他里面是什么(何况或许根本就没有“里面”这东西,只是地址引用)。

所以,嗯,咱们继续等那边啥情况吧,或许我真的被忽略了。

不过就我自己这种情况来看,isset($string['check'][1]) + is_string($string['check'])工作良好。但或许我应该改成!empty($string['check']) + is_string($string['check'])。因为我发现他们都是通过zend_do_isset_or_isempty来实现的,只是作为第一个参数type,被设定成ZEND_ISEMPTY或者ZEND_ISSET而已。

而这个type也就是在检查zend_is_function_or_method_call以及设定op编译扩展信息的时候用了下。(当然我还没完整看过代码,还是闭嘴的好。)


好了不吧唧了,我继续做项目去了。也感谢你一直聊这个话题。
lizheming
2014-05-14 21:51:31 +08:00
@raincious 写了这么长完全不知道你要吐槽什么。”直接因为$str['check']这个根本不合法而直接返回false啊“这个不在我的模式范围内所以我不多做讨论。举的例子也是bug,文件名分明要转成大写再做判断好么,而且函数名就说明了人家就是用来获取文件内容的,非得拿人家来做判断文件是否存在是不是稍微过分了点?...
其实我早就明白你是在说要PHP为什么这么做,而我一直在强调的是我在说的是PHP怎么这么做,详情见23,25,32,35,37楼...我说到邮件组和其他人这部分的话的意思都是说我只是在解释PHP怎么这么做的,不明白为什么这么做。
问题在于每次我这么说完之后你就把我扯到怎么做这个问题上,等我把怎么做这个问题解释清楚之后你又给我扯回到为什么这么做上头去……WTF!!!!
raincious
2014-05-14 23:29:43 +08:00
@lizheming


> 写了这么长完全不知道你要吐槽什么。”直接因为$str['check']这个根本不合法而直接返回false啊“这个不在我的模式范围内所以我不多做讨论。

这是我14楼以来一直在抱怨的啊,我就是想如果能那样该多好阿,简直直接乌托邦了啊。现在都46楼了中间这么多你干啥去了?我们在进行什么欢乐的娱乐活动么亲?PHP原理猜猜猜?看似是的呢。



> “函数名就说明了人家就是用来获取文件内容的”

isset函数名说明了这是用来检测某个变量是否存在的,那么解析这个变量的时候给我埋这个坑是不是也是Bug?



另外你要知道,其实,你想要解释的那些Test1 ~ Test 4已经说明白了。

比如你27楼str['f']['f'],只是做了Test1 + Test 3而已。


当然,我原先的认知也不是对的,我一开始是认为,因为'check'这个索引找不到,所以才会是false,而不是isset检查过了索引,因为是非法的所以返回了false。


我只想知道为什么不能想我说的那样实现isset,让Test 1和Test 3一起false。然而这个问题我是一直没有得到答案的。

至于死循环是这样的:我一直想问为何不能这样 => 你没有答案 => 你解释PHP怎么这么做 => 我:艹我没问这个问题啊 => 回到1。这是很正常的轮询结构不是么o_o。


其实我已经不想接着讨论这个问题了,所以才发上面那个帖子。老实说吧,我觉得在这里讨论不出任何有价值的信息了,因为……你也不知道源代码说了什么啊,根本不知道应该告诉我什么。再着么下去估计就得吵起来了不是么,就此打住我们还是好朋友,一包薯片都能分着吃的那种:我一包,。


PS。如果VLD不只能打印出Opcode,也能跟踪整个工作流,我想就不会有这么多解释不清的麻烦了。现在只能看到FETCH_DIM_IS拿了check(后来应该是0才对吧)出来然后给到ISSET_ISEMPTY_DIM_OBJ,却不能知道IS出来的东西到底给编译器搞成什么了。

其实我觉得,一直将输入数组当成DIM来用,次序也是从左往右的,ASSIGN的时候也知道目标数据类型了(是的,如果是数组的话会先INIT_ARRAY的亲),那,实现让isset遇到非数字下标直接return false能有多复杂呢。

好了。我还是安心去等PHP那边有没有响应又或者永远没有响应。你也安了吧,消消气,睡个好觉。
lizheming
2014-05-15 22:26:40 +08:00
好吧,我也来结下帖,看到你这个最后,我最后真的是想骂人了。
最开始我看您的帖子内容是“猜测xxxxx”并没有给出PHP是怎么在这个代码中工作的,然后我就上手册查了查帮你找到了一个确实的工作机理,然后你就说我的内容各种不对不对什么的...

现在可以确定的是,工作机理是我说的这样的这样,我的解释并没有什么问题。

然后在22楼和23楼的时候我已经明白了您是想要探讨更高深的东西,想要知道为什么这么做,并在25楼的时候明确告知关于“为什么这么做”这个问题的答案我不知道并试图停止解释工作机理这个过程。

结果您在26楼的时候又把我给扯进去了,之后我就一直在试图想说明“我说的是PHP的工作机理的一个合理解释,并且是一个可以自圆其说的解释,是没有问题的,以及我并不懂PHP的工作机理为什么是这样的,希望您在邮件组中能得到更完美的答案,并贴于此告知大家”,结果我也不知道您为什么就是各种吐槽我这不对那不对什么的..跟您扯这么多,我真的好忧桑好么!
raincious
2014-05-16 00:39:15 +08:00
@lizheming 怎么没有TL;DR,因为我真的不想R。

我已经把这个问题丢给老外了。结果收获丰硕。

具体其实已经被解释的很清楚了: http://stackoverflow.com/questions/6588016/php-5-4s-simplified-string-offset-reading 我很满意,但是还在等PHP里的家伙能不能给个回复,所以没有Append。其他的我也不想说什么了。
lizheming
2014-05-16 10:30:55 +08:00
@raincious ...不懂您第一句的DR和R是什么意思(´Д`),zend的源码也看不懂,好忧桑..
raincious
2014-05-16 10:58:29 +08:00
@lizheming 好吧,你一直在帮我,我还是挺感激的。所以我吧问题梳理下,然后就能意识到咱们两个(至少是我)个笨瓜在讨论什么。

原始问题是原帖;答案是:'check'会估值成0。于是原始问题就解决了,答案在8楼。于是推导出了 $string['check'][0] => $string[0][0] => 't'[0] => 't' => isset == true

于是出现了20楼的问题,对待var_dump和isset方式不一致。你的解答:isset()没有报错是因为 isset()有特殊操作。(简而言之,“对待var_dump和isset方式不一致”,但这就是我的结论啊)。这个问题可以Pass了。我说12楼其实是让你看“是的。应该是有特殊的处理。”,因为我一再确认(Conform)那个不一致。

然后就是你27楼丢出的问题,我28回到“根据上面的来说,$str['f']['f'],或许只是变成$str[0]['f']了”。我没说我是对的你是错的,我是推断你是对的。这也是正确的答案。

当然,过程是这样的:$str['f']['f']最后是变成了$str[0]['f']然后交给了isset,isset看到string,于是返回了false。

正确的原因是这样的:

isset($string['check'])能返回false因为它有代码段来专门进行这项检查,扫描到'check'是string直接返回了false。
所以,这也就不难理解为什么isset($string['check'][0])会是true,因为那段检查没有检查$string['check']。他只检查了[0],然后对前面的进行了转换(根PHP 5.3之前一样),而不是你猜测的无法判断数据类型的原因,具体的原因是SO的那个链接(TL;DR:为了……优化)。

所以可以理解为PHP还是在5.3的模式下运行,只是isset那边多了这项检查罢了。于是乎上面都说通了。


于是乎,(你真的不用回答下面的问题的,因为你回答不了,对吧?),我艹,多爬个检查会死么。isset改改让他也爬下$string['check'][0]里的'check'不就没这问题了么。 // 其实我后面一直在说这个,但是我用了疑问句“为什么不能因为$string['check'][0]的而false,非得$string['check']呢”,这或许造成了你的理解错误嗯。

另外,我46楼在说,其实PHP拿到那个isset的输入参数之后,(我猜)是直接拿它当数组用的,因为可以看到FETCH_DIM_IS,然后ISSET_ISEMPTY_DIM_OBJ,所以我觉得上面我的吐槽是略有道理的呢,除非我猜错了……
lizheming
2014-05-16 15:41:15 +08:00
8楼的答案最终给出的也没有明确,也是用或许回答的,而且还转到了我的楼上来了。而且我回答的时候是针对你7楼说的“跟这个没关系”来回答的,就是想强调,其实是有关系的。

20楼的时候我还没明白您是在问Why这种高层次问题,我是在22楼你“吐槽”我的时候我才明白的,再此我先为我的低智商道歉一下。

从你以上的回答中我并没有感觉到你认为我说的是一个对的答案,而且回答中的示例跟我想的步骤也有不同,所以我在回复中一再进行“疏导”,并且强调了如果您明白了的话就当我是在放屁好了。结果您还在和我讨论...我以为你还没明白..我当然就只能陪着您讨论下去了..另外说一句,32楼我说的“isset()的问题”是说isset()为什么这样做的问题,我以为你能明白的……

你说的原因不就是我讲的啊..我并没有说没法判断数据类型(我哪里有表达这个意思啊(´Д`))..我只是想强调isset()只是转换了前面的一部分获取到最终一个数组后检查最后一个key是否存在,但是因为key是字符串的,就直接返回false了啊... 果然对我的回答的理解不一致才导致了我们这么累么....

是的,你这句话让我出奇的愤怒了,什么叫做没办法回答?(╯‵□′)╯︵┻━┻ 我这就回答给你看:

1.首先要强申一下isset()只是判断一个变量是否存在的,不管什么时候也不会报错的,比如 http://3v4l.org/Ao6GI 很抱歉之前没有认识到这一点,给我的解释上造成了一些误会。只有在取值 var_dump()或者echo的时候才会报错。

2. 关于为啥会报错什么的,没必要去查源码了,laruence大大在博客上已经讲得很清楚了,http://www.laruence.com/2011/12/19/2409.html。总结来说就是为了兼容。

3.关于怎么去isset()的过程就是我讲得那样,本回复的第四段也是在说这个。你的最后一次回复基本没有说错,除了这句”isset($string['check'])能返回false因为它有代码段来专门进行这项检查,扫描到'check'是string直接返回了false。“是不完全正确的。isset()就是判断最后一个而已..

4.如果你真的想要搞清楚PHP为啥怎么运作的话可以试试vld这个扩展http://pecl.php.net/package/vld,可以逐步输出PHP的运行过程,能了解PHP的运行过程。

好啦,我发现我现在连为什么也搞清楚了,你搞没搞清楚就随意吧。如果你要回答的话请别@我了,我是一看有@不回就不舒服斯基的人,这样可能就没完没了了T_T,亲我们就再也不见了吧....真蛋疼...
raincious
2014-05-16 16:31:35 +08:00
@lizheming 搞不懂你在说什么了。

> 你说的原因不就是我讲的啊..我并没有说没法判断数据类型
> 所以$str['check']会先转换,因为只有得到 $str['check']是什么才能确定是否有0键(41)

那是我理解错了?

回答在这里: http://news.php.net/php.general/323332

意思是说 “对待var_dump和isset方式不一致”。而不是“因为只有得到 $str['check']是什么才能确定是否有0键”(所以才将'check'估值成0)。 // 这就是我想知道的。

PHP一开始就知道$str的类型,因为变量的初始化方式不同。String类型是不可能有数组的,所以根本不需要去找$str['check']就能知道这是个字符串。

46楼我已经说了,“FETCH_DIM_IS拿了check(后来应该是0才对吧)出来然后给到ISSET_ISEMPTY_DIM_OBJ”。'check' -> '0'这样一层一层取出的(所以没必要只检测最后一个)。

之所以只拿最后一个,是因为SO那个帖子说的“I think it's not a problem at all. "b" makes sense because "abs"[1] -> "b" and "b"[0] -> "b".”。

好了,我要知道的信息已经完全了,完结。
lizheming
2014-05-16 16:45:17 +08:00
@raincious 你居然还@我了..真是不回不舒服斯基啊…囧... 没有什么一致不一致的,var_dump和isset的思路都是一样的,多为数组都得逐级获取才能得到值。但是isset只是判断值是否存在而已,如果硬要说特别就只有这一点特别而已,邮件上也是这么说的。而且这段”So, $string['check'] is effectively $string[0], which is "t"; so
$string['check'][0] is $string[0][0], which is set and evaluates
to "t".“就是我讲的思路(你引号中说的不是的那部分)。没错,是一层一层的取出的,但是取到最后面就只有最后一个key了啊..跟我说的判断最后一个key有什么区别么(´Д`)..
你倒数第二段讲的不也还是我说么一级一级的获取然后去顶最后那个key是否有(´Д`)
raincious
2014-05-16 17:30:38 +08:00
@lizheming 我……跟你是一个星球的。

> 但是isset只是判断值是否存在而已,如果硬要说特别就只有这一点特别而已
好了,你成功被我摆直(歪)了。我很满意,哇哈哈。

> 就是我讲的思路

差不多,但其实我是在纠结你前面说的一句,就是:

> 所以$str['check']会先转换,因为只有得到 $str['check']是什么才能确定是否有0键

然后,老外告诉我,这是isset特有的检查之后

> 就这个问题,更准确的说,我40楼是在问“为什么要转换?”,为什么不发现'check'这个数组是非法下标之后直接return false?

这个问题就解决了。因为原先就要转换,isset只是“好心”的false了一个test。

然后加上12楼推导出来的结果再加上“isset和var_dump不一致”,你6楼的帖子就变得可以理解了。(因为我一直在问$string['check'][0]为什么和$string['check']不一样,而6楼的帖子解释了$string['check']会被isset判定为false掉,但是没说$string['check'][0](就是说,其实isset在处理$string['check'][0]的时候还是按照$string[0][0]来处理的),你看36楼)


> 跟我说的判断最后一个key有什么区别么

不……我只是不理解为什么只判断最后一个。后来SO上的帖子给了我答案。就是这样。

而且貌似,我没说你错了啊,我还确认了下说“根据上面的来说,$str['f']['f'],或许只是变成$str[0]['f']了(如果这才是你的正确意思的话),所以这是有可能的。”,可能你后面理解错了,觉得我说你说错了。


好了,咱们俩说话好费劲,只是因为互相理解错了。

另外你有没有觉得V2EX讨论这样的话题好费劲啊,不停的报楼号什么的。

感谢你一直陪我吧唧。
lizheming
2014-05-16 17:47:38 +08:00
@raincious 为啥是被你掰的..我51楼的第一点不就是这么说的么o(╯□╰)o……肯定是会转换的啊,一级一级的获取在获取 $str['check'] 这个的时候就会被转换成 $str[0] 获取啊..有什么问题?是的,我的意思就是变成了 $str[0]['f']
嗯,是好费劲..还要自己去找楼号,就不能给楼号来个更简单的锚点然后自动解析锚点么OwQ。。
好了大概误会解除了...我也谢谢你,查文档我也学习到了好多OwQ

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

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

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

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

© 2021 V2EX