foreach ($array as $value) 对每个元素运行一次代码块,$value 被设置为当前元素。要同时拿到键,就写 foreach ($array as $key => $value)。
foreach 适用于任何数组,不管它的键是什么,你也不需要管理计数器或检查长度。它是你在 PHP 中最常写的循环。
遍历键和值
在 $key => $value 中,变量名由你自己决定:$name => $age、$id => $user、$k => $v。在列表中,键是位置 0, 1, 2。在关联数组中,键是你定义的字符串键。
最后一个循环展示了一个容易让人意外的细节:看起来像整数的字符串键,比如 "1",会被存储为整数 1。"01" 不是规范的整数形式,所以它仍然是字符串。
在 foreach 中获取索引
在列表中,键就是索引,所以 foreach ($items as $i => $item) 就够了。关联数组没有数字位置,所以要自己维护一个计数器。要对第一个或最后一个元素做特殊处理,就把键和 array_key_first() 或 array_key_last() 比较。
最后一个循环在名字之间输出逗号,但最后一个名字后面不输出。单就这件事而言,implode(', ', array_keys($scores)) 更简短;array_key_last() 的判断适用于最后一轮需要不同逻辑的情况,而不只是换一个分隔符。
在 foreach 中修改数组的值(按引用)
$value 是一个副本。给它赋值不会改变数组中的任何内容。要修改数组,要么用 &$value 按引用遍历,要么通过键写回。
两种方法都可以。通过键的写法在循环结束后不会留下隐藏的状态,所以很多团队更喜欢它。如果是要生成一个新数组而不是修改原数组,array_map() 也是一种选择。
一个相关的问题:如果在循环中添加元素会怎样?普通的 foreach 遍历的是循环开始时的数组,所以在循环中写 $prices[] = ... 不会增加循环的轮数。按引用的循环遍历的是实时的数组,会访问到新增的元素。
按引用 foreach 时 unset() 的陷阱
foreach ($arr as &$v) 结束后,变量 $v 仍然是对数组最后一个元素的引用。如果之后在普通循环中再次使用 $v 这个名字,每一轮都会写进最后那个元素。运行下面的代码,看看最后一项:
第二个循环依次把 A、B,最后是最后一个元素自身的值赋给 $letters[2],而到最后一轮时,这个位置已经是 B 了。修复只需要一行:在每个按引用的 foreach 之后紧接着写 unset($v);。
foreach 中的 break 和 continue
break 退出循环,continue 跳到下一个元素。一种常见用法是查找:一直循环到找到需要的东西,然后 break,这样就不会再检查数组的其余部分。
在嵌套循环中,break 2 会同时退出内层循环和它外面的一层,continue 2 则跳到外层循环的下一轮。
嵌套数组和在 foreach 中使用 list()
一行行数据就是数组里的数组。可以先遍历各行,再遍历每一行;也可以在 foreach 那一行直接用数组解构取出字段:列表用 [$a, $b],关联数组的行用 ['key' => $var]。list($a, $b) 是 [$a, $b] 的旧写法。
解构页面详细介绍了 [...] = 语法,包括跳过元素和嵌套模式。
用 foreach 生成 HTML 列表或表格
在模板中,使用冒号形式的 foreach (...): 加 endforeach;,能让夹在标签之间的循环更易读。这段代码生成一个表格,表头是链接:在“页面”标签页中点击其中一个,同一个脚本会带着设置好的 $_GET['sort'] 再运行一次,各行就会按新的顺序排列。
这段代码中有两个值得借鉴的习惯:URL 中的 sort 值在使用前会先和允许的列名列表比对;输出到 HTML 中、可能含有用户文本的每个值,都经过了 htmlspecialchars() 处理。
对 null、空数组或对象使用 foreach
空数组没问题:循环体一次也不会运行。null、字符串或数字就不行了:PHP 会输出一条警告并跳过循环。对于 $tags = null; foreach ($tags as $tag),它会输出:
Warning: foreach() argument must be of type array|object, null given in /var/www/html/index.php on line 4
这种情况最常出现在有时会缺失的数据上,比如解码后的 JSON 中的可选字段。用 ?? 回退到空数组:
foreach ($data['tags'] ?? [] as $tag) {
echo $tag;
}
那条信息中的 object 是真的:对对象使用 foreach 会访问它在循环所在位置可见的属性(在类的外部就是公有属性),以属性名作为键。实现了 Iterator 或 IteratorAggregate 的类,以及生成器,会自行决定 foreach 能看到什么。
foreach、for 与 array_map 的区别
| 用法 | 适用场景 |
|---|---|
foreach | 需要每个元素,带或不带键。适用于任何数组,无论是列表还是关联数组。 |
for | 需要计数(1 到 100、每隔一个元素)或按索引访问相邻元素($a[$i - 1])。见 for 循环。 |
array_map() | 通过转换每个元素生成一个新数组,常配合 fn 写成一行。 |
array_filter() | 只保留通过某个检测的元素。 |
一个快速的判断方法:如果循环体只是为每个元素计算一个新值,array_map(fn($x) => $x * 2, $a) 一行就能表达。如果它有副作用(输出、保存、同时统计多个东西)或者需要 break,就写 foreach。
常见问题
PHP foreach 循环中怎么同时获取键和值?
用 => 的写法:foreach ($array as $key => $value) { echo "$key: $value"; }。在普通列表中,键就是索引(0、1、2);在关联数组中,键是字符串键,比如 'name'。
PHP foreach 循环中怎么获取索引?
对于列表,键本身就是索引:foreach ($items as $i => $item)。对于关联数组,自己维护一个计数器:在循环前写 $i = 0;,在每轮末尾写 $i++。array_key_first() 和 array_key_last() 可以告诉你哪个键是第一个或最后一个。
PHP 中怎么在 foreach 里修改数组的值?
用 & 按引用遍历:foreach ($prices as &$price) { $price *= 2; } unset($price);。或者通过键写回:foreach ($prices as $key => $price) { $prices[$key] = $price * 2; }。直接写 $price = ... 只会修改一个副本。
为什么 PHP 按引用的 foreach 会让最后一个元素重复?
foreach ($arr as &$v) 结束后,$v 仍然是对最后一个元素的引用。接下来的 foreach ($arr as $v) 会把每个值都写进最后那个位置,结果它最后重复了倒数第二个值。在按引用的循环之后立即调用 unset($v);。
PHP 中怎么跳出 foreach 循环?
break; 立即停止循环,continue; 跳到下一个元素。在嵌套循环中,break 2; 会同时退出内层和外层循环。