零、前言
這陣子在進行手邊 PHP 專案的版本升級,從 PHP 5.x 升級到 PHP 7.x ,由於專案裡大量的使用了 while(list($key, $value) = each($items)) {}
這樣的語法,而在 PHP 7.2 之後,官方直接標注不建議使用,因此升級的過程中,順帶轉換為 foreach
結構,在過程中踩到一些 PHP 5.x 升級到 PHP 7.x 之後的特性雷,因此寫下這篇作為紀錄。
PS. 現行 PHP 7.2 當未關閉警告的情況下,會提醒 PHP: each - Manual 不建議使用¶:
1 2
| Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /xxxx.php on line xx
|
壹、測試資料
首先,測試資料的部分,全程使用底下的陣列內容。
1
| $items = [1, 2, 3, 4, 5, 6];
|
在 Migrating from PHP 5.6.x to PHP 7.0.x 的文件 PHP: Backward incompatible changes - Manual 中,提到 foreach no longer changes the internal array pointer
在 PHP 7 之後 foreach
不會再改變 array
的內部指標。如下驗證測試碼,current 可以取得目前 items 這個 array 的指標位置,當超出範圍則顯示為 boolean 的 false 否則為 integer:
1 2 3 4 5 6 7 8
| $items = [1, 2, 3, 4, 5, 6];
echo "PHP version: " . phpversion() . PHP_EOL; echo "----" . PHP_EOL;
foreach ($items as &$val) { var_dump(current($items)); }
|
1 2 3 4 5 6 7 8
| PHP version: 7.1.8 ---- int(1) int(1) int(1) int(1) int(1) int(1)
|
1 2 3 4 5 6 7 8
| PHP version: 5.6.30 ---- int(2) int(3) int(4) int(5) int(6) bool(false)
|
貳、單獨獨立使用
一、關於 foreach 實作
平常我們會如此的使用 foreach 做迴圈內容的操作,這邊為單純整個測試及紀錄,就不變動 array 的內容。
1 2 3 4 5 6 7 8 9
| $items = [1, 2, 3, 4, 5, 6];
echo "PHP version: " . phpversion() . PHP_EOL; echo "----" . PHP_EOL;
echo "foreach "; foreach ($items as $foreach_key => $foreach_value) { echo $foreach_value . " "; }
|
輸出:
1 2 3
| PHP version: 5.6.30 ---- foreach 1 2 3 4 5 6
|
二、關於 while each
1 2 3 4 5 6 7 8 9
| $items = [1, 2, 3, 4, 5, 6];
echo "PHP version: " . phpversion() . PHP_EOL; echo "----" . PHP_EOL;
echo "while each "; while(list($list_key, $list_value) = each($items)) { echo $list_value . " "; }
|
輸出:
1 2 3
| PHP version: 5.6.30 ---- while each 1 2 3 4 5 6
|
目前為止無論是 each 或是 foreach 的操作,不論是 PHP 5.6 或是 PHP 7.1 兩者的輸出內容是一致的。
參、當混合使用時
先 foreach 後使用 each 迴圈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| $items = [1, 2, 3, 4, 5, 6];
echo "PHP version: " . phpversion() . PHP_EOL;
echo "----" . PHP_EOL;
echo "foreach "; foreach ($items as $foreach_key => $foreach_value) { echo $foreach_value . " "; } echo PHP_EOL;
echo "----" . PHP_EOL;
echo "while each "; while(list($list_key, $list_value) = each($items)) { echo $list_value . " "; } echo PHP_EOL;
|
1 2 3 4 5
| PHP version: 7.1.8 ---- foreach 1 2 3 4 5 6 ---- while each 1 2 3 4 5 6
|
1 2 3 4 5
| PHP version: 5.6.30 ---- foreach 1 2 3 4 5 6 ---- while each
|
從上面的結果看到,PHP 5.6 的執行結果,在進行完 foreach
迴圈之後,再做一次同一陣列的 while each
迴圈,已經不會再印出任何內容。 each
迴圈在操作時,可以透過 PHP: current - Manual 來取得目前陣列的 pointer
指到 array 的哪一個 element,稍作修改,使上面的程式印出陣列目前所在的 pointer 位置。
先 foreach 後使用 each 迴圈 (增加顯示 pointer 位置):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| $items = [1, 2, 3, 4, 5, 6];
echo "PHP version: " . phpversion() . PHP_EOL;
echo "----" . PHP_EOL;
echo "foreach "; foreach ($items as $foreach_key => $foreach_value) { echo $foreach_value . " "; } echo PHP_EOL; echo "current pos = " . current($items) . PHP_EOL;
echo "----" . PHP_EOL;
echo "while each "; while(list($list_key, $list_value) = each($items)) { echo $list_value . " "; } echo PHP_EOL; echo "current pos = " . current($items) . PHP_EOL;
|
1 2 3 4 5 6 7
| PHP version: 7.1.8 ---- foreach 1 2 3 4 5 6 current pos = 1 ---- while each 1 2 3 4 5 6 current pos =
|
1 2 3 4 5 6 7
| PHP version: 5.6.30 ---- foreach 1 2 3 4 5 6 current pos = ---- while each current pos =
|
發現,在 PHP 7 版本中,pointer 並沒有轉變,而 PHP 5.x 版本則會隨著 foreach
操作,連帶變動到 each 使用的 pointer 指標。
先 foreach 後使用 each 迴圈 (foreach 到一半直接 break 掉):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| $items = [1, 2, 3, 4, 5, 6];
echo "PHP version: " . phpversion() . PHP_EOL;
echo "----" . PHP_EOL;
echo "foreach "; foreach ($items as $foreach_key => $foreach_value) { echo $foreach_value . " "; if ($foreach_value > 2) { break; } } echo PHP_EOL; echo "current pos = " . current($items) . PHP_EOL;
echo "----" . PHP_EOL;
echo "while each "; while(list($list_key, $list_value) = each($items)) { echo $list_value . " "; } echo PHP_EOL; echo "current pos = " . current($items) . PHP_EOL;
|
1 2 3 4 5 6 7
| PHP version: 7.1.8 ---- foreach 1 2 3 current pos = 1 ---- while each 1 2 3 4 5 6 current pos =
|
1 2 3 4 5 6 7
| PHP version: 5.6.30 ---- foreach 1 2 3 current pos = 4 ---- while each 4 5 6 current pos =
|
特性如同上一個小實驗,在 PHP 7 版本中,pointer 並沒有轉變,而 PHP 5 版本則會隨著 foreach
操作,連帶變動到 each 使用的 pointer 指標。
多次 each 搭配 foreach 操作時
先一次 each 操作,而後 foreach,接著使用 while each 迴圈,程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| $items = [1, 2, 3, 4, 5, 6];
echo "PHP version: " . phpversion() . PHP_EOL;
echo "----" . PHP_EOL;
echo "each "; list($each_key, $each_value) = each($items); echo $each_value; echo PHP_EOL . "current pos = " . current($items) . PHP_EOL;
echo "----" . PHP_EOL;
echo "foreach "; foreach ($items as $foreach_key => $foreach_value) { echo $foreach_value . " "; if ($foreach_value > 2) { break; } } echo PHP_EOL; echo "current pos = " . current($items) . PHP_EOL;
echo "----" . PHP_EOL;
echo "while each "; while(list($list_key, $list_value) = each($items)) { echo $list_value . " "; } echo PHP_EOL; echo "current pos = " . current($items) . PHP_EOL;
|
1 2 3 4 5 6 7 8 9 10
| PHP version: 7.1.8 ---- each 1 current pos = 2 ---- foreach 1 2 3 current pos = 2 ---- while each 2 3 4 5 6 current pos =
|
1 2 3 4 5 6 7 8 9 10
| PHP version: 5.6.30 ---- each 1 current pos = 2 ---- foreach 1 2 3 current pos = 4 ---- while each 4 5 6 current pos =
|
這邊是第二個需要特別注意的地方,在 PHP 7 版本中,因為不再操作 array 內部的 pointer,因此就算在 foreach 操作前做了一次 each 操作,也不影響 foreach 的初始值,且來到 while each 迴圈中時,會從陣列的第二個位置開始。但…在 PHP 5 中,foreach 會重置 pointer,而後在結束 foreach 迴圈離開後, while each 直接接 foreach 結束之後的 pointer 繼續進行。
總結
- PHP 5 年代,會因為 foreach 迴圈而重置 array 的 pointer 但 PHP 7 不會。
- 升級到 PHP 7 之前,如果之前有做多次 foreach 或 each 的操作,則要謹記 pointer 操作的問題,必要時,可以使用 reset() 進行 array 的 pointer 重置。
- 升級 PHP 至 PHP 7 版本,必須隨時注意,是否是有意義的在操作 array pointer,否則會因為 php array 操作特性改變,變得很難找出錯誤的原因。
- 當有使用
foreach by-reference
操作到 array 的內容時,那下一個地雷將會是這個:foreach by-reference has improved iteration behaviour ,在 PHP 5 時,會操作到的指標僅陣列的原始範圍,但在 PHP 7 開始,則會動態的調整需要走訪的陣列範圍,如官方的範例:
1 2 3 4 5
| $array = [0]; foreach ($array as &$val) { var_dump($val); $array[1] = 1; }
|