您的位置:首页 > 编程语言 > PHP开发

php中foreach使用&引用后的异常分析及处理办法

2018-02-27 16:31 609 查看
可能在PHP编码中使用&引用变量或者对象或者方法的人不多,但是&引用可以让你的代码变的简单而且节省资源消耗。在这篇文章中我们重点讨论的是foreach中使用&时出现的异常以及解决办法。

$exp = [
[
'name' => 'test1',
'age' => 15,
'extension' => 'a:3:{s:4:"nose";s:4:"long";s:5:"mouth";s:3:"big";s:3:"eye";s:5:"small";}'
],
[
'name' => 'test2',
'age' => 25,
'extension' => 'a:3:{s:4:"nose";s:5:"long2";s:5:"mouth";s:4:"big2";s:3:"eye";s:6:"small2";}'
],
[
'name' => 'test4',
'age' => 18,
'extension' => 'a:3:{s:4:"nose";s:5:"long2";s:5:"mouth";s:4:"big2";s:3:"eye";s:6:"small2";}'
],
[
'name' => 'test3',
'age' => 20,
'extension' => 'a:3:{s:4:"nose";s:5:"long3";s:5:"mouth";s:4:"big3";s:3:"eye";s:6:"small3";}'
],
];

foreach ($exp as &$v) {
$extension = @unserialize($v['extension']);
$v['nose'] = $extension['nose'] ?? "";
$v['mouth'] = $extension['mouth'] ?? "";
$v['eye'] = $extension['eye'] ?? "";
}

$newExp = [];
foreach ($exp as $v) {
if ($v['mouth'] == "big3"){
$newExp[] = $v;
}
}
dump($newExp);
exit;


这部分代码的功能描述如下:

1.将exp中的扩展字段混入到exp中
2.如果exp中mouth为big3则赋值给新数组newExp
3.输出newExp


从简单的表象来分析貌似以上逻辑并没有错,而且我们预测输出的结果应该为

...
0 => array:6 [▼
"name" => "test3"
"age" => 20
"extension" => "a:3:{s:4:"nose";s:5:"long3";s:5:"mouth";s:4:"big3";s:3:"eye";s:6:"small3";}"
"nose" => "long3"
"mouth" => "big3"
"eye" => "small3"
]
...
但是结果并不是我们所预测的那样,程序输出的结果为:
[]
这是为什么呢,我们来逐一分析


foreach引用引发的异常

第一个foreach是以下的代码块

foreach ($exp as &$v) {
$extension = @unserialize($v['extension']);
$v['nose'] = $extension['nose'] ?? "";
$v['mouth'] = $extension['mouth'] ?? "";
$v['eye'] = $extension['eye'] ?? "";
}


,在该代码块中使用了&v。因为我们这一步要做的事情是处理数组本身的数据所以使用引用对于内存消耗较少。在程序执行中v作为子元素的一个引用,所以我们直接修改$v就可以修改exp相应的子元素的值。那么当循环结束时v作为子元素的一个引用,所以我们直接修改$v就可以修改exp相应的子元素的值。那么当循环结束时v应该就是exp最后一个元素的引用。

那么当我们修改$v的值应该exp的最后一个元素会变化。而且还有一个非常重要的问题就是foreach中使用了引用后引用在foreach结束后任然是存在的。也就是在以上的foreach之外$v依旧引用exp最后一个元素

在foreach后$v是否还存在

...
foreach ($exp as &$v) { $extension = @unserialize($v['extension']); $v['nose'] = $extension['nose'] ?? ""; $v['mouth'] = $extension['mouth'] ?? ""; $v['eye'] = $extension['eye'] ?? ""; }
dump($v);
输出结果为:
array:6 [▼
"name" => "test3"
"age" => 20
"extension" => "a:3:{s:4:"nose";s:5:"long3";s:5:"mouth";s:4:"big3";s:3:"eye";s:6:"small3";}"
"nose" => "long3"
"mouth" => "big3"
"eye" => "small3"
]


第二个循环分析

$newExp = [];
foreach ($exp as $v) {
if ($v['mouth'] == "big3"){
$newExp[] = $v;
}
}
dump($newExp);


在这儿我们是做了一个常规的循环来循环exp而且在该循环中我们使用的是变量并没有使用引用。差别就是
$v
&$v
请仔细看。

在这个循环中其实$v依旧是exp最后一个元素的引用。那么在循环中其实每次都是奖exp当前(current)的值赋值给$v因为引用关系最终改变的是exp最后一个元素的值。
那么在foreach中exp最后子元素的值一直是变的。演变过程如下


//为了篇幅简略表示

//第一次循环exp变为:也就是第一个元素赋值给了最后一个元素
[
[
'name' => 'test1',
...
],
[
'name' => 'test2',
...
],
[
'name' => 'test4',
...
],
[
'name' => 'test1',
...
],
]

//第二次循环exp变为:也就是第二个元素赋值给了最后一个元素
[
[
'name' => 'test1',
...
],
[
'name' => 'test2',
...
],
[
'name' => 'test4',
...
],
[
'name' => 'test2',
...
],
]

//第三次循环exp变为:也就是第三个元素赋值给了最后一个元素
[
[
'name' => 'test1',
...
],
[
'name' => 'test2',
...
],
[
'name' => 'test4',
...
],
[
'name' => 'test4',
...
],
]

//第四次循环exp变为:也就是第四个元素赋值给了最后一个元素 循环完毕
[
[
'name' => 'test1',
...
],
[
'name' => 'test2',
...
],
[
'name' => 'test4',
...
],
[
'name' => 'test4',
...
],
]


原因分析

从上可以看出虽然本来exp的最后一个元素复合if条件中的
$v['mouth'] == "big3"
,但是在循环最后一个元素时其本身已经变成了第三个元素,所以mouth=big3的元素不存在了。这个流程有点儿绕,多看几遍就能看得懂。当然你也可以看看PHP的zend引擎中关于foreach的实现以及查看VLD中间代码,例如简单循环的VLD

number of ops:  16
compiled vars:  !0 = $arr, !1 = $key, !2 = $row
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
2     0  >   INIT_ARRAY                                       ~0      1
1      ADD_ARRAY_ELEMENT                                ~0      2
2      ADD_ARRAY_ELEMENT                                ~0      3
3      ADD_ARRAY_ELEMENT                                ~0      4
4      ADD_ARRAY_ELEMENT                                ~0      5
5      ASSIGN                                                   !0, ~0
4     6    > FE_RESET                                         $2      !0, ->14
7  > > FE_FETCH                                         $3      $2, ->14
8  >   ZEND_OP_DATA                                     ~5
9      ASSIGN                                                   !2, $3
10      ASSIGN                                                   !1, ~5
5    11      ECHO                                                     !1
12      ECHO                                                     !2
6    13    > JMP                                                      ->7
14  >   SWITCH_FREE                                              $2
7    15    > RETURN                                                   1


总结



关注NS技术圈获得更多精彩分享以及免费视频
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息