記錄一次因為對PHP作用域理解不夠導致的小坑。
自測需求的時候發現有一塊地方數據很奇怪,要么該寫的沒有寫入、要么數據被寫入雙份。剝離業務后的代碼大概如下:
<?php
$arr = [
['is_checked'=>false,'k'=>1],
['is_checked'=>false,'k'=>2],
];
foreach ($arr as &$item) {
if ($item['k']==1) {
$item['is_checked'] = true;
}
}
echo '<pre>';
foreach ($arr as $item) {
if ($item['is_checked']) {
print_r($item);
}
}
我預想中 上面的代碼應該是只打印arr里的第一條記錄,也就是['is_checked'=>true,'k'=>1]
,然而實際運行發現打印的是這樣的:
Array
(
[is_checked] => 1
[k] => 1
)
Array
(
[is_checked] => 1
[k] => 1
)
居然打印了兩條記錄,而且兩條的k都是1。
斷點調試的時候也發現,運行到第二個foreach里的時候 arr確實變成了這樣的數組:
[
['is_checked'=>true,'k'=>1],
['is_checked'=>true,'k'=>1],
]
仔細看代碼,前面foreach的時候,循環里的變量是用的item,而且是取引用,后面的foreach也是item。我之前是認為這倆item的作用域是不重合的,也就是認為第一個foreach的作用域只在foreach代碼塊里(這點可能是受了golang變量作用域的影響)
然而從結果來看,兩個item應該是一樣的,也就是第二個循環里的item還是前一個循環里的item,而前一個循環里的item是對數組里元素取的引用,也就是說,第一個循環結束后,item還是指向$arr
的第二個元素。第二個foreach開始的時候,$arr
的第一個元素的值被賦給item,這樣$arr
的第二個元素就被第一個元素覆蓋了,所以產生了上面的結果。
來一段代碼驗證下:
<?php
$arr1 = [1,2,3,4];
foreach ($arr1 as &$item) {
//do nothing
}
$arr2 = ['a','b','c','d'];
echo '<pre>';
foreach ($arr2 as $item) {
print_r($arr1);
}
輸出結果:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => a
)
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => b
)
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => c
)
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => d
)
這里應該算是比較基礎的點吧。但是因為對作用域范圍不夠敏感,踩了個坑還排查了半天(實際業務代碼較多,開始沒想到是這里的問題)。
說下這里要注意的點吧
foreach
時候的循環變量盡量不要用同一個變量,尤其是涉及到取引用的- 循環變量取引用的,退出循環后,最好是unset掉,防止后面不小心改掉了該數據