可能在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則賦值給新數組newExp3.輸出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應該就是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: 16compiled vars: !0 = $arr, !1 = $key, !2 = $rowline # * 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