PHP foreach

O foreach ("para cada") é o laço que você mais vai usar em PHP. Ele percorre um array item por item, do primeiro ao último, sem você precisar contar posições nem saber o tamanho da lista. Lista de compras, produtos de uma loja, linhas que vieram do banco de dados: quase tudo em PHP é "para cada item, faça isto".

Você vai aprender
  • foreach ($lista as $item): passar por todos os valores.
  • foreach ($lista as $chave => $valor): valores e chaves (índices ou nomes).
  • Por que mudar $item não muda o array, e as duas formas de mudar: pela chave e por referência (&$item).
  • O bug clássico da referência depois do laço, demonstrado passo a passo, e como evitá-lo.
  • foreach com [$nome, $nota] (o list()) para desmontar cada item.
  • A sintaxe alternativa foreach (…): … endforeach; dentro do HTML.

Você vai precisar de arrays indexados e associativos (se ainda não leu, tudo bem: os exemplos explicam o necessário). Antes desta aula vêm while e for. Os exemplos rodam na pasta ~/site da Ana.


A ideia antes do código: conferir a sacola de compras

A Ana chegou do mercado e vai tirar as compras da sacola. Ela não conta "item 1, item 2, item 3"; ela simplesmente pega cada coisa que está lá, uma de cada vez, até a sacola ficar vazia. Em cada mão, por um momento, está um item: o pão, depois o leite, depois o café.

O foreach faz isso com um array. A cada volta, ele coloca o próximo item numa variável (a "mão") e roda o bloco de código. Quando não há mais itens, o laço termina sozinho. Não existe condição para escrever nem contador para esquecer de aumentar: por isso o foreach quase nunca vira laço infinito.


A forma básica: foreach ($lista as $item)

<?php
$compras = ["pão", "leite", "café", "ovos"];

foreach ($compras as $item) {
    echo "- $item\n";
}
echo "Fim da lista.\n";
foreach
"Para cada". Começa o laço.
$compras
O array a percorrer. Vem antes do as.
as
"Como". Liga o array ao nome que cada item vai receber.
$item
A variável que, a cada volta, guarda um item. O nome é você quem escolhe: $item, $produto, $x… Costuma-se usar o singular do nome do array ($compras → $compra, $alunos → $aluno).
{ … }
O corpo: roda uma vez para cada item.
$ php compras.php
- pão
- leite
- café
- ovos
Fim da lista.

Volta a volta: na 1ª, $item vale "pão"; na 2ª, "leite"; na 3ª, "café"; na 4ª, "ovos". Não há 5ª: o array acabou, e o programa segue para o echo "Fim da lista.".

Se o array estiver vazio, o corpo não roda nenhuma vez, sem erro nenhum. Por isso é comum testar antes, para avisar o usuário:

<?php
$compras = [];
foreach ($compras as $item) {
    echo "- $item\n";
}
if ($compras === []) {
    echo "A lista está vazia.\n";
}
$ php vazio.php
A lista está vazia.

Experimente: acrescente "manteiga" ao array e rode de novo: o laço se adapta sozinho. Depois troque o echo por echo strtoupper($item), "\n"; para mostrar cada item em maiúsculas.

<?php
$compras = ["pão", "leite", "café", "ovos"];

foreach ($compras as $item) {
    echo "- $item\n";
}
echo "Fim da lista.\n";

Chave e valor: as $chave => $valor

Todo array tem chaves: num array comum, os índices 0, 1, 2…; num array associativo, os nomes que você deu. Para receber a chave também, escreva duas variáveis separadas por => (a mesma "setinha" usada para criar o array):

<?php
$precos = ["pão" => 8.50, "leite" => 5.20, "café" => 18.90];

foreach ($precos as $produto => $preco) {
    echo "$produto custa R$ ", number_format($preco, 2, ",", "."), "\n";
}
$produto
A chave da volta atual: "pão", depois "leite", depois "café".
=>
Separa a variável da chave da variável do valor. A ordem é sempre chave => valor.
$preco
O valor: 8.50, 5.20, 18.90.
number_format(…)
Formata o preço no jeito brasileiro (veja Formatar Números).
$ php precos.php
pão custa R$ 8,50
leite custa R$ 5,20
café custa R$ 18,90

Num array comum, a chave é a posição. Útil para numerar uma lista (somando 1, porque as posições começam em 0):

<?php
$compras = ["pão", "leite", "café"];

foreach ($compras as $i => $item) {
    echo ($i + 1), ". $item\n";
}
$ php indices.php
1. pão
2. leite
3. café
O foreach segue a ordem em que os itens foram colocados

Não é a ordem dos números das chaves. Se você criar $lista[5], depois $lista[1], depois $lista[3], o foreach mostra 5, 1, 3:

$ php ordem.php
5 => cinco
1 => um
3 => três

Para outra ordem, ordene o array antes (veja Ordenar Arrays).

Missão: o estoque da lojinha diz quantas unidades há de cada produto. O programa deveria escrever Comprar leite e Comprar ovos (os que estão em zero), mas só tem a quantidade, não o nome. Mude o foreach para receber também a chave, em $produto.

<?php
$estoque = ["pão" => 12, "leite" => 0, "café" => 3, "ovos" => 0];

foreach ($estoque as $quantidade) {
    if ($quantidade === 0) {
        echo "Comprar $produto\n";
    }
}
<?php
$estoque = ["pão" => 12, "leite" => 0, "café" => 3, "ovos" => 0];

foreach ($estoque as $produto => $quantidade) {
    if ($quantidade === 0) {
        echo "Comprar $produto\n";
    }
}

Mudar os itens do array

A Ana quer dobrar todos os preços. A primeira tentativa parece óbvia, mas não funciona:

<?php
$precos = [10, 20, 30];

foreach ($precos as $preco) {
    $preco = $preco * 2;
}
print_r($precos);
$ php copia.php
Array
(
    [0] => 10
    [1] => 20
    [2] => 30
)                    « nada mudou

A cada volta, $preco recebe uma cópia do item. Dobrar a cópia não mexe no array, como anotar um número num papel e rabiscar o papel: o original continua igual. Há duas formas de mudar o array de verdade.

Forma 1: escrever no array pela chave

<?php
$precos = [10, 20, 30];

foreach ($precos as $i => $preco) {
    $precos[$i] = $preco * 2;
}
print_r($precos);
$ php chave.php
Array
(
    [0] => 20
    [1] => 40
    [2] => 60
)

$precos[$i] = … escreve direto na posição $i do array original. É a forma mais segura e fácil de ler.

Forma 2: por referência, &$preco

Um & antes da variável do item faz o foreach entregar o próprio item, e não uma cópia. A variável vira um "apelido" (uma referência) para aquela posição do array: mudar $preco é mudar o array.

<?php
$precos = [10, 20, 30];

foreach ($precos as &$preco) {
    $preco = $preco * 2;
}
print_r($precos);
$ php referencia.php
Array
(
    [0] => 20
    [1] => 40
    [2] => 60
)

Funciona, e é mais curto. Mas tem uma armadilha famosa.

O bug clássico: a referência que sobra depois do laço

Você viu em Escopo que a variável do foreach continua existindo depois do laço. Com &, ela continua sendo um apelido para o último item do array. Se, mais adiante, você usar o mesmo nome num segundo foreach comum, cada volta desse segundo laço escreve no último item. Veja a Ana colocando as frutas com inicial maiúscula e depois listando:

<?php
$frutas = ["banana", "maçã", "uva"];

foreach ($frutas as &$fruta) {
    $fruta = ucfirst($fruta);
}

foreach ($frutas as $fruta) {
    echo $fruta, "\n";
}
print_r($frutas);
$ php bug.php
Banana
Maçã
Maçã                  « cadê a Uva?
Array
(
    [0] => Banana
    [1] => Maçã
    [2] => Maçã       « o array foi estragado
)

Nenhum aviso, e a uva sumiu do array. Para ver o que aconteceu, a Ana colocou um var_dump entre os laços e mostrou o array a cada volta do segundo:

<?php
$frutas = ["banana", "maçã", "uva"];

foreach ($frutas as &$fruta) {
    $fruta = ucfirst($fruta);
}
var_dump($frutas);

foreach ($frutas as $fruta) {
    echo "Agora \$fruta = $fruta; o array está: ", implode(", ", $frutas), "\n";
}
$ php bug-passo.php
array(3) {
  [0]=>
  string(6) "Banana"
  [1]=>
  string(6) "Maçã"
  [2]=>
  &string(3) "Uva"          « o &: a posição 2 ainda está ligada a $fruta
}
Agora $fruta = Banana; o array está: Banana, Maçã, Banana
Agora $fruta = Maçã; o array está: Banana, Maçã, Maçã
Agora $fruta = Maçã; o array está: Banana, Maçã, Maçã
  1. Depois do primeiro laço, $fruta é um apelido para $frutas[2]. O var_dump mostra isso com o & antes de string(3) "Uva".
  2. 1ª volta do segundo laço: o PHP coloca "Banana" em $fruta… que é $frutas[2]. A uva foi sobrescrita: o array agora é Banana, Maçã, Banana.
  3. 2ª volta: "Maçã" vai para $fruta, ou seja, para $frutas[2]: Banana, Maçã, Maçã.
  4. 3ª volta: o item da posição 2 agora é "Maçã", que é copiado para… ele mesmo. Por isso "Maçã" aparece duas vezes.

A correção é uma linha: unset($fruta); logo depois do laço com &. O unset ("desfazer") apaga o apelido, sem mexer no array:

<?php
$frutas = ["banana", "maçã", "uva"];

foreach ($frutas as &$fruta) {
    $fruta = ucfirst($fruta);
}
unset($fruta);

foreach ($frutas as $fruta) {
    echo $fruta, "\n";
}
print_r($frutas);
$ php bug2.php
Banana
Maçã
Uva
Array
(
    [0] => Banana
    [1] => Maçã
    [2] => Uva
)
Regra prática

Usou foreach (… as &$x)? Escreva unset($x); na linha seguinte ao }, sempre. Ou prefira a Forma 1 ($array[$chave] = …), que não deixa nada para trás. Muitos programadores experientes simplesmente evitam o & no foreach.

Qual usar?

Você quer…Use
Só ler os itensforeach ($lista as $item)
Ler também as chavesforeach ($lista as $chave => $valor)
Mudar os itens do próprio array$lista[$chave] = … dentro do laço (Forma 1), ou &$item + unset($item)
Criar um array novo transformado, sem mexer no originalarray_map (veja Funções Anônimas) ou um foreach que preenche um array novo
Contar de 1 a 10, ou pular de 2 em 2for
Repetir enquanto algo for verdade (sem lista)while

Desmontar cada item: foreach com [$a, $b]

Muitas vezes cada item do array é ele mesmo um pequeno array: um aluno com nome e nota, uma linha de uma planilha. Em vez de escrever $aluno[0] e $aluno[1], você pode desmontar o item direto no foreach, com colchetes no lugar da variável:

<?php
$alunos = [
    ["Ana", 9.5],
    ["Bruno", 5.5],
    ["Carla", 8.5],
];

foreach ($alunos as [$nome, $nota]) {
    echo "$nome tirou $nota\n";
}

foreach ($alunos as list($nome, $nota)) {
    echo "$nome: ", $nota >= 7 ? "aprovado" : "reprovado", "\n";
}

$produtos = [
    ["nome" => "bolo", "preco" => 25],
    ["nome" => "pudim", "preco" => 12],
];
foreach ($produtos as ["nome" => $n, "preco" => $p]) {
    echo "$n: R$ $p\n";
}
TrechoO que faz
as [$nome, $nota]A cada volta, a posição 0 do item vai para $nome e a posição 1 para $nota.
as list($nome, $nota)A mesma coisa, na forma antiga, com list(). Você vai encontrar as duas.
as ["nome" => $n, "preco" => $p]Com arrays associativos: a chave "nome" vai para $n, a "preco" para $p.
$ php lista.php
Ana tirou 9.5
Bruno tirou 5.5
Carla tirou 8.5
Ana: aprovado
Bruno: reprovado
Carla: aprovado
bolo: R$ 25
pudim: R$ 12

Esse "desmontar" se chama desestruturação e funciona fora do foreach também; a aula completa é Desestruturação (list).


A sintaxe alternativa no HTML: foreach (…): … endforeach;

Quando o PHP está misturado com HTML (veja PHP Dentro do HTML), as chaves { } ficam difíceis de achar no meio das tags. Para isso existe uma forma alternativa: dois-pontos no lugar do { e endforeach; ("fim do foreach") no lugar do }.

<?php foreach ($compras as $item): ?>
Abre o laço. Repare no : no fim, antes do ?>.
<li><?= htmlspecialchars($item) ?></li>
HTML comum, repetido uma vez por item. O <?= é o atalho de <?php echo, e o htmlspecialchars protege a página contra textos com <, > e aspas (veja Escapar a Saída).
<?php endforeach; ?>
Fecha o laço. Muito mais fácil de achar do que um <?php } ?> perdido.

Experimente: veja a lista na aba Resultado e o HTML gerado na aba Código-fonte HTML: repare que o café <forte> virou café &lt;forte&gt;. Acrescente itens ao array; depois tire o htmlspecialchars e veja o <forte> sumir da página (o navegador achou que era uma tag).

<?php
$compras = ["pão", "leite", "café <forte>"];
?>
<!doctype html>
<html lang="pt-BR">
<head><meta charset="utf-8"><title>Compras</title></head>
<body>
<h1>Lista de compras</h1>
<ul>
<?php foreach ($compras as $item): ?>
    <li><?= htmlspecialchars($item) ?></li>
<?php endforeach; ?>
</ul>
</body>
</html>

O HTML que o PHP real gerou para esse código:

$ php web.php
<!doctype html>
<html lang="pt-BR">
<head><meta charset="utf-8"><title>Compras</title></head>
<body>
<h1>Lista de compras</h1>
<ul>
    <li>pão</li>
    <li>leite</li>
    <li>café &lt;forte&gt;</li>
</ul>
</body>
</html>

As outras estruturas têm a mesma forma alternativa: if: … endif;, for: … endfor;, while: … endwhile;, switch: … endswitch;. Use-as nos arquivos de HTML ("templates"); no código PHP puro, prefira as chaves.


Quando dá errado

foreach() argument must be of type array|object, null given
<?php
$compras = null;

foreach ($compras as $item) {
    echo "- $item\n";
}
echo "Fim\n";
$ php naoarray.php

Warning: foreach() argument must be of type array|object, null given in /home/ana/site/naoarray.php on line 4
Fim

O que significa: "o argumento do foreach precisa ser do tipo array ou objeto; foi dado null". O foreach só percorre arrays (e objetos, no Nível 3). Acontece muito quando uma variável que deveria ser um array veio vazia de um formulário, de um arquivo ou do banco, ou tem o nome escrito errado (aí aparece antes um Undefined variable).

Como consertar: descubra por que a variável não é um array (var_dump($compras);). Se ela puder faltar de verdade, use um array vazio como reserva: foreach ($compras ?? [] as $item) (veja ??).

string given: tentou percorrer um texto
$ php texto.php          « $palavra = "café"; foreach ($palavra as $letra) …

Warning: foreach() argument must be of type array|object, string given in /home/ana/site/texto.php on line 3

O que significa: um texto não é um array de letras para o foreach. Nada é repetido.

Como consertar: transforme o texto num array antes: mb_str_split("café") devolve ["c", "a", "f", "é"] (com acentos certos; veja Acentos e mbstring); para separar palavras, explode(" ", $frase).

Esqueceu o as: unexpected variable "$item"
$ php semas.php          « foreach ($compras $item) {

Parse error: syntax error, unexpected variable "$item" in /home/ana/site/semas.php on line 3

O que significa: depois de $compras o PHP esperava a palavra as e encontrou outra variável.

Como consertar: foreach ($compras as $item). A ordem também importa: o array vem primeiro, o item depois do as.

O último item foi trocado pelo penúltimo (sem erro)
$ php bug.php
Banana
Maçã
Maçã

O que aconteceu: um foreach com &$fruta deixou a variável ligada ao último item, e um foreach seguinte com o mesmo nome escreveu nele. Veja o passo a passo acima.

Como consertar: unset($fruta); logo depois do laço com &.

Mudei $item e o array não mudou (sem erro)
$ php copia.php
Array
(
    [0] => 10
    [1] => 20
    [2] => 30
)

O que aconteceu: sem &, o item é uma cópia.

Como consertar: $precos[$i] = … com as $i => $preco, ou &$preco seguido de unset($preco).


Referência

FormaO que faz
foreach ($a as $v) { … }Uma volta para cada valor de $a, na ordem de inserção.
foreach ($a as $k => $v) { … }Recebe também a chave.
foreach ($a as &$v) { … } unset($v);Por referência: mudar $v muda o array. Sempre com unset depois.
foreach ($a as [$x, $y]) / list($x, $y)Desmonta cada item (array) em variáveis.
foreach ($a as ["k" => $x])Desmonta pelo nome da chave.
foreach ($a as $v): … endforeach;Sintaxe alternativa, para templates HTML.
foreach ($a ?? [] as $v)Não reclama se $a for null.
break / continueSair do laço / pular para o próximo item (veja break e continue).

Exercícios

1. A forma básica

Complete para mostrar cada nome do array $alunos:

foreach ($alunos ) { echo $aluno; }

2. Chave e valor

Complete para receber o nome do produto em $produto e o preço em $preco:

foreach ($precos as ) { … }

3. Depois da referência

Depois de foreach ($notas as &$nota) { $nota += 1; }, complete a linha que evita o bug clássico:

4. O que mostra? $a = [1, 2, 3]; foreach ($a as $x) { $x = 0; } echo implode(",", $a);

5. Em que ordem foreach percorre $a = []; $a["z"] = 1; $a["a"] = 2;?

6. Qual forma é a mais indicada dentro de um arquivo de HTML?

Missão final

Missão: a Ana deu 10% de desconto em todos os doces, mudando o próprio array com &$preco. Mas a torta aparece custando R$ 10,80, o mesmo preço do pudim! Rode, encontre o bug clássico desta aula e corrija para a torta sair por R$ 36,00.

<?php
$precos = ["bolo" => 25, "pudim" => 12, "torta" => 40];

foreach ($precos as &$preco) {
    $preco = $preco * 0.9;
}

foreach ($precos as $nome => $preco) {
    echo "$nome: R$ ", number_format($preco, 2, ",", "."), "\n";
}
<?php
$precos = ["bolo" => 25, "pudim" => 12, "torta" => 40];

foreach ($precos as &$preco) {
    $preco = $preco * 0.9;
}
unset($preco);

foreach ($precos as $nome => $preco) {
    echo "$nome: R$ ", number_format($preco, 2, ",", "."), "\n";
}

Resumo

  • foreach ($lista as $item) roda o corpo uma vez para cada item, na ordem em que foram colocados. Array vazio: nenhuma volta.
  • as $chave => $valor entrega também a chave (o índice ou o nome).
  • A variável do item é uma cópia. Para mudar o array, use $lista[$chave] = … ou &$item.
  • Depois de um foreach com &, a variável continua ligada ao último item: unset($item); sempre.
  • as [$a, $b] (ou list($a, $b)) desmonta cada item em variáveis.
  • No HTML, use foreach (…): … endforeach; e htmlspecialchars em cada valor.
  • foreach em algo que não é array: Warning: foreach() argument must be of type array|object.