foreach ($array as $value) chạy khối lệnh một lần cho mỗi phần tử, với $value là phần tử hiện tại. Để lấy cả khóa, hãy viết foreach ($array as $key => $value).
foreach chạy được với mọi mảng, bất kể khóa là gì, và bạn không bao giờ phải tự quản lý biến đếm hay kiểm tra độ dài. Đây là vòng lặp (loop) bạn sẽ viết nhiều nhất trong PHP.
Lặp qua khóa và giá trị
Trong $key => $value, bạn tự chọn tên: $name => $age, $id => $user, $k => $v. Với một danh sách, khóa là các vị trí 0, 1, 2. Với mảng kết hợp (associative array), khóa là các chuỗi do bạn định nghĩa.
Vòng lặp cuối cho thấy một chi tiết khiến nhiều người bất ngờ: khóa dạng chuỗi trông như một số nguyên, chẳng hạn "1", sẽ được lưu thành số nguyên 1. "01" không phải dạng chuẩn của số nguyên nên vẫn là chuỗi.
Lấy chỉ số trong foreach
Với một danh sách, khóa chính là chỉ số, nên chỉ cần foreach ($items as $i => $item). Mảng kết hợp không có vị trí dạng số, nên hãy giữ một biến đếm. Để xử lý riêng phần tử đầu hoặc cuối, so sánh khóa với array_key_first() hoặc array_key_last().
Vòng lặp cuối in dấu phẩy giữa các tên nhưng không in sau tên cuối cùng. Riêng việc này thì implode(', ', array_keys($scores)) ngắn hơn; phép kiểm tra array_key_last() dành cho khi lượt cuối cần logic khác, chứ không chỉ khác dấu phân cách.
Thay đổi giá trị mảng trong foreach (theo tham chiếu)
$value là một bản sao. Gán giá trị cho nó không thay đổi gì trong mảng. Để thay đổi mảng, hãy lặp theo tham chiếu với &$value, hoặc ghi ngược lại qua khóa.
Cả hai cách đều được. Cách dùng khóa không để lại trạng thái ẩn nào sau vòng lặp, vì vậy nhiều nhóm phát triển chuộng cách này. Nếu muốn tạo mảng mới thay vì sửa mảng cũ, array_map() là một lựa chọn khác.
Một câu hỏi liên quan: chuyện gì xảy ra nếu bạn thêm phần tử trong lúc lặp? foreach thông thường đi qua mảng ở trạng thái lúc vòng lặp bắt đầu, nên $prices[] = ... bên trong không tạo thêm lượt lặp. Vòng lặp theo tham chiếu đi qua mảng đang thay đổi và có ghé qua các phần tử mới.
Cái bẫy unset() với foreach theo tham chiếu
Sau foreach ($arr as &$v), biến $v vẫn là tham chiếu đến phần tử cuối của mảng. Nếu sau đó bạn dùng lại tên $v trong một vòng lặp thường, mỗi lượt sẽ ghi vào phần tử cuối đó. Chạy đoạn code này và nhìn vào phần tử cuối:
Vòng lặp thứ hai lần lượt gán A, rồi B, rồi chính giá trị của phần tử cuối vào $letters[2], và đến lượt cuối thì ô đó đã giữ B. Cách sửa chỉ là một dòng: đặt unset($v); ngay sau mỗi vòng foreach theo tham chiếu.
break và continue trong foreach
break rời khỏi vòng lặp, continue nhảy sang phần tử tiếp theo. Một mẫu thường gặp là tìm kiếm: lặp cho đến khi tìm thấy thứ cần tìm, rồi break để không phải kiểm tra phần còn lại của mảng.
Trong các vòng lặp lồng nhau, break 2 rời khỏi vòng lặp bên trong và cả vòng lặp bao quanh nó, còn continue 2 nhảy sang lượt tiếp theo của vòng lặp bên ngoài.
Mảng lồng nhau và foreach với list()
Các hàng dữ liệu là những mảng nằm trong một mảng. Hãy lặp qua các hàng rồi lặp qua từng hàng, hoặc lấy thẳng các trường ra ngay trên dòng foreach bằng destructuring mảng: [$a, $b] cho danh sách, ['key' => $var] cho các hàng dạng mảng kết hợp. list($a, $b) là cách viết cũ của [$a, $b].
Trang destructuring có thêm về cú pháp [...] =, bao gồm bỏ qua phần tử và mẫu lồng nhau.
Dựng danh sách hoặc bảng HTML với foreach
Trong template, dạng dấu hai chấm foreach (...): với endforeach; giúp vòng lặp dễ đọc khi nằm giữa các thẻ. Khối code này dựng một bảng, và tiêu đề cột là các liên kết: bấm vào một tiêu đề trong tab Trang, script sẽ chạy lại với $_GET['sort'] được gán giá trị, nên các hàng hiện ra theo thứ tự mới.
Hai thói quen đáng học từ khối code này: giá trị sort lấy từ URL được đối chiếu với danh sách các cột cho phép trước khi dùng, và mọi giá trị in vào HTML có thể chứa văn bản của người dùng đều đi qua htmlspecialchars().
foreach với null, mảng rỗng hoặc đối tượng
Mảng rỗng thì không sao: thân vòng lặp không bao giờ chạy. Còn null, một chuỗi hay một số thì không được: PHP in cảnh báo và bỏ qua vòng lặp. Với $tags = null; foreach ($tags as $tag), PHP in ra:
Warning: foreach() argument must be of type array|object, null given in /var/www/html/index.php on line 4
Chuyện này hay xảy ra nhất với dữ liệu lúc có lúc không, như một trường tùy chọn trong JSON đã giải mã. Hãy dùng ?? để thay bằng mảng rỗng:
foreach ($data['tags'] ?? [] as $tag) {
echo $tag;
}
Chữ object trong thông báo đó là thật: foreach trên một đối tượng sẽ đi qua các thuộc tính nhìn thấy được từ nơi vòng lặp chạy (bên ngoài lớp thì là các thuộc tính public), với tên thuộc tính làm khóa. Các lớp cài đặt Iterator hoặc IteratorAggregate, cũng như generator, tự quyết định foreach sẽ thấy gì.
foreach, for hay array_map
| Dùng | Khi nào |
|---|---|
foreach | Bạn cần mọi phần tử, có hoặc không kèm khóa. Chạy được với mọi mảng, dạng danh sách hay mảng kết hợp. |
for | Bạn cần đếm (từ 1 đến 100, cứ cách một phần tử) hoặc cần phần tử lân cận theo chỉ số ($a[$i - 1]). Xem vòng lặp for. |
array_map() | Bạn tạo mảng mới bằng cách biến đổi từng phần tử, thường gói gọn trong một dòng với fn. |
array_filter() | Bạn chỉ giữ lại các phần tử vượt qua một phép kiểm tra. |
Cách chọn nhanh: nếu thân vòng lặp chỉ tính một giá trị mới cho mỗi phần tử, array_map(fn($x) => $x * 2, $a) diễn đạt điều đó trong một dòng. Nếu có tác dụng phụ (in ra, lưu lại, đếm nhiều thứ cùng lúc) hoặc cần break, hãy viết foreach.
Câu hỏi thường gặp
Làm sao lấy khóa và giá trị trong vòng lặp foreach của PHP?
Dùng dạng =>: foreach ($array as $key => $value) { echo "$key: $value"; }. Với một danh sách thông thường, khóa chính là chỉ số (0, 1, 2); với mảng kết hợp, đó là khóa dạng chuỗi, chẳng hạn 'name'.
Làm sao lấy chỉ số trong vòng lặp foreach của PHP?
Với một danh sách, khóa đã là chỉ số: foreach ($items as $i => $item). Với mảng kết hợp, hãy tự giữ một biến đếm: đặt $i = 0; trước vòng lặp và $i++ ở cuối mỗi lượt. array_key_first() và array_key_last() cho bạn biết khóa nào đứng đầu hay cuối.
Làm sao thay đổi giá trị mảng bên trong foreach trong PHP?
Lặp theo tham chiếu với &: foreach ($prices as &$price) { $price *= 2; } unset($price);. Hoặc ghi lại qua khóa: foreach ($prices as $key => $price) { $prices[$key] = $price * 2; }. Một phép gán $price = ... thông thường chỉ đổi bản sao.
Vì sao foreach theo tham chiếu trong PHP lại nhân đôi phần tử cuối?
Sau foreach ($arr as &$v), $v vẫn là tham chiếu đến phần tử cuối. Một vòng foreach ($arr as $v) thứ hai sẽ ghi từng giá trị vào ô cuối đó, nên cuối cùng ô này lặp lại giá trị kế cuối. Hãy gọi unset($v); ngay sau vòng lặp tham chiếu.
Làm sao thoát khỏi vòng lặp foreach trong PHP?
break; dừng vòng lặp ngay lập tức, còn continue; nhảy sang phần tử tiếp theo. Trong các vòng lặp lồng nhau, break 2; rời khỏi cả vòng lặp bên trong lẫn bên ngoài.