本文参考 http://www.laruence.com/2009/04/24/695.html
在数组中,数组的键key可以是integer或者string,许多人在使用非数字键的时候,习惯不给键加引号,比如:
<?php $a = array("a"=>"aa","b"=>"bb"); echo $a[a]; //没加引号 $a["a"] ?>
这段代码可以执行,但是会报NOTICE,Notice: Use of undefined constant a – assumed ‘a’ 。但是许多人在php.ini配置文件中设置了 error_reporting = E_ALL & ~E_NOTICE ,导致看不到这个NOTICE。
不加引号情况下,不谈代码NOTICE带来的性能问题,那我们来看看会带来什么不可预期的错误。
先看opcode序列,来看看两段代码执行的操作。
加引号的情况:
未加引号情况:不加引号的情况下,多了一步 FETCH_CONSTANT操作,也就是查找常量。
我们可以看出,其实根据NOTICE提示也可以得到, PHP会把没加引号的键当做常量去获取,如果找不到,抛出NOTICE,然后在根据常量名生成一个字符串,然后继续再作为数组的键。
这个时候就会导致一个不可预期的问题,就是如果你define定义了一个同名常量,会出现什么呢:
<?php $array = array('a'=>"aaa" , "b"=>"bbb" , "c"=>"ccc"); define("define_key" , "a"); //定义常量 define_key ,值为 a echo $array[define_key]; // 输出 aaa</div> $array[define_key] = "change";</div> echo $array["a"]; // 输出 "change" ?>
数组非数字键一定要加引号!
但是还没结束,又会遇到一个问题,当我们在字符串中引入数组的时候,此时加上引号则会语法报错,比如:
<?php $array = array('a'=>"aaa" , "b"=>"bbb" , "c"=>"ccc"); $str = "variable value is $array['a']"; // 会报错 // 语法错误 // Parse error: syntax error, unexpected T_ENCAPSED_AND_WHITESPACE, expecting T_STRING or T_VARIABLE or T_NUM_STRING $str1 = "variable value is {$array['a']}"; // 这样就不会报错了 $str2 = "variable value is $array[a]"; //在字符串中引入非数字键,不加引号也不会报错 ?>
———————————————————————————————————————-
来, 我们一起来看看:
good.php: <?php $array = array(); $i = 0; while(++$i < 1000){ $array['good'] = 2; } ?> bad.php: <?php $array = array(); $i = 0; while(++$i < 1000){ $array[good] = 2; } ?>
分别看运行时间(多次平均时间):
加引号的:
加引号的:
$ time php -f good.php real 0m0.013s user 0m0.005s sys 0m0.007s
不加引号的:
$ time php -f bad.php PHP Notice: Use of undefined constant bad - assumed 'bad' in /home/huixinchen/tmp/bad.php on line (此处省略999行NOTICE) real 0m0.100s user 0m0.020s sys 0m0.029s
看看,差别有多大?
哦, 或许我们应该模拟一下那些”幸运的”人们的情况, 去掉花费在记录NOTICE的开销, 看看~
哦, 或许我们应该模拟一下那些”幸运的”人们的情况, 去掉花费在记录NOTICE的开销, 看看~
$ time php -f bad.php real 0m0.037s user 0m0.018s sys 0m0.018s
我们可以看出, 基本上, 使用引号,和不使用引号的效率损失在3倍以上
那么, 这些效率损失到哪里去了呢?
我们分别看下, 俩个文件生成的OPCODE序列:
good.php :
filename: /home/huixinchen/tmp/good.php compiled vars: !0 = $array, !1 = $i line # op fetch ext return operands ------------------------------------------------------------------------------- 2 0 INIT_ARRAY ~0 1 ASSIGN !0, ~0 3 2 ASSIGN !1, 0 4 3 PRE_INC $3 !1 4 IS_SMALLER ~4 $3, 1000 5 JMPZ ~4, ->9 5 6 ZEND_ASSIGN_DIM !0, 'good' 7 ZEND_OP_DATA 2, $6 6 8 JMP ->3 8 9 RETURN 1 10* ZEND_HANDLE_EXCEPTION
bad.php :
filename: /home/huixinchen/tmp/bad.php compiled vars: !0 = $array, !1 = $i line # op fetch ext return operands ------------------------------------------------------------------------------- 2 0 INIT_ARRAY ~0 1 ASSIGN !0, ~0 3 2 ASSIGN !1, 0 4 3 PRE_INC $3 !1 4 IS_SMALLER ~4 $3, 1000 5 JMPZ ~4, ->10 5 6 FETCH_CONSTANT ~5 'bad' 7 ZEND_ASSIGN_DIM !0, ~5 8 ZEND_OP_DATA 2, $7 6 9 JMP ->3 8 10 RETURN 1 11* ZEND_HANDLE_EXCEPTION
我们可以看出(其实,根据NOTICE的提示也知道), PHP会把没有引号引起来的键名当作是常量去获取, 当找不到的时候, 抛出一个NOTICE, 然后再根据”常量明”生成一个字符串, 然后再讲这个字符串做为键名继续~
聪明的你一定会想到, 可能会出现如下不可预期的错误:
define('key_name' , 'laruence'); .... //省略很多行代码 $array[key_name] = 2; //变成了 $array['laruence'] = 2;
明白了么? 数组中的非数字键的键名一定要有引号啊~
哦, 还记得有人会说, 那在字符串变量替换的时候, 写引号会导致错误,
恩, 标准写法:
$string = "variable value is {$array['key']}"
我很赞同:”be lazy”, 但是, lazy也是应该有原则的.
最后, 好的代码,不应该通过关闭error_reporting来伪装.
附注, FETCH_CONSTANT OPCODE中找不到常量的相关逻辑:
.... if (!zend_get_constant(opline->op2.u.constant.value.str.val, opline->op2.u.constant.value.str.len, &EX_T(opline->result.u.var).tmp_var TSRMLS_CC)) { zend_error(E_NOTICE, "Use of undefined constant %s - assumed '%s'", opline->op2.u.constant.value.str.val, opline->op2.u.constant.value.str.val); EX_T(opline->result.u.var).tmp_var = opline->op2.u.constant;//获取"常量"名字符串 zval_copy_ctor(&EX_T(opline->result.u.var).tmp_var);//分配空间,生成字符串 } ....
转载请注明:jinglingshu的博客 » php数组非数字键名加引号的必要性!