PHP while e do-while
Até agora, cada linha do seu programa rodava uma vez, de cima para baixo. Com um laço (ou "loop", ou "repetição"), um pedaço de código roda várias vezes. O mais simples de todos é o while ("enquanto"): repita isto enquanto uma condição for verdadeira. Nesta aula você vai conhecê-lo, junto com o irmão dele, o do-while, e aprender a reconhecer (e parar) um laço que nunca termina.
- Como o
whilefunciona: testar, repetir, testar de novo. - As três peças de um laço com contador: início, condição e mudança.
- Laços que não sabem quantas voltas vão dar: juntar dinheiro até a meta, atender uma fila.
- O
do-while, que roda pelo menos uma vez, e quando usá-lo. - O que é um laço infinito, como pará-lo (Ctrl+C) e o limite de tempo do PHP.
- A sintaxe alternativa
while (…): … endwhile;dentro do HTML.
Você vai usar comparações (<, <=, !==…) e o if, que são a base das condições. Os exemplos rodam na pasta ~/site da Ana.
A ideia antes do código: lavar a louça
Depois do almoço, a Ana segue uma regra simples: enquanto houver louça na pia, ela pega um prato e lava. Ela não sabe de antemão quantos pratos são; só olha a pia antes de cada um. Quando olha e a pia está vazia, para.
Repare na ordem: primeiro ela olha (a condição), depois lava (o corpo do laço), depois olha de novo. Se a pia já estiver vazia logo no começo, ela não lava nada. É exatamente assim que o while funciona.
O while mais simples: contar até 5
<?php
$i = 1;
while ($i <= 5) {
echo "Volta $i\n";
$i++;
}
echo "Fim. \$i vale $i\n";
- $i = 1;
- Início: cria o contador antes do laço. (
$ié um nome tradicional para contadores, de "índice".) - while
- "Enquanto". Começa o laço.
- ($i <= 5)
- A condição, entre parênteses: "
$ié menor ou igual a 5?". Testada antes de cada volta. Se for verdadeira, o corpo roda; se for falsa, o laço termina. - { … }
- O corpo: o código repetido.
- $i++;
- A mudança: soma 1 ao contador (veja Operadores de Atribuição). Sem ela,
$ificaria em 1 para sempre.
$ php contador.php Volta 1 Volta 2 Volta 3 Volta 4 Volta 5 Fim. $i vale 6
Seguindo o PHP passo a passo:
$ivale 1.1 <= 5? Sim: mostra "Volta 1",$ivira 2.2 <= 5? Sim: "Volta 2",$ivira 3. E assim por diante…5 <= 5? Sim (é "menor ou igual"): "Volta 5",$ivira 6.6 <= 5? Não. O laço termina e o programa segue para a linha depois da}.
Por isso $i termina valendo 6: foi o valor que fez a condição falhar. Cada repetição do corpo se chama uma volta (ou iteração).
Missão: usando while, mostre 10 8 6 4 2 0 (de 10 até 0, descendo de 2 em 2, separados por espaço). Dica: a condição é $n >= 0 e a mudança é $n -= 2. Se a tela começar a encher sem parar, você criou um laço infinito: confira a mudança.
<?php $n = 10; // escreva o while aqui echo "\n";
<?php
$n = 10;
while ($n >= 0) {
echo $n, " ";
$n -= 2;
}
echo "\n";
Quando você não sabe quantas voltas serão
Para contar de 1 a 5, existe um laço mais prático, o for (próxima aula). O while brilha quando o número de voltas depende do que acontece dentro do laço. A Ana quer juntar R$ 1.000: deposita R$ 150 por mês, e a poupança rende 1% ao mês. Em quantos meses ela chega lá?
<?php
$saldo = 0;
$meta = 1000;
$meses = 0;
while ($saldo < $meta) {
$saldo = $saldo + 150; // depósito do mês
$saldo = $saldo * 1.01; // rendimento de 1%
$meses++;
echo "Mês $meses: R$ ", number_format($saldo, 2, ",", "."), "\n";
}
echo "A Ana chegou à meta em $meses meses.\n";
$ php poupanca.php Mês 1: R$ 151,50 Mês 2: R$ 304,52 Mês 3: R$ 459,06 Mês 4: R$ 615,15 Mês 5: R$ 772,80 Mês 6: R$ 932,03 Mês 7: R$ 1.092,85 A Ana chegou à meta em 7 meses.
Ninguém escreveu "7" no programa: o laço descobriu sozinho. A condição $saldo < $meta é testada todo mês, e o que a faz ficar falsa é o próprio $saldo crescendo lá dentro. (O number_format mostra o dinheiro no formato brasileiro; veja Formatar Números.)
Experimente: mude a meta para 5000 e o depósito para 300. Quantos meses agora? Depois coloque uma meta já batida ($saldo = 2000; no começo) e veja que o corpo não roda nenhuma vez.
<?php
$saldo = 0;
$meta = 1000;
$meses = 0;
while ($saldo < $meta) {
$saldo = $saldo + 150; // depósito do mês
$saldo = $saldo * 1.01; // rendimento de 1%
$meses++;
echo "Mês $meses: R$ ", number_format($saldo, 2, ",", "."), "\n";
}
echo "A Ana chegou à meta em $meses meses.\n";
A fila de atendimento
Outro caso clássico: tratar itens de uma lista até ela acabar. array_shift tira o primeiro elemento de um array e o devolve, como chamar o primeiro da fila:
<?php
$fila = ["Ana", "Bruno", "Carla"];
while (count($fila) > 0) {
$cliente = array_shift($fila);
echo "Atendendo $cliente. Ainda na fila: ", count($fila), "\n";
}
echo "Fila vazia!\n";
$ php fila.php Atendendo Ana. Ainda na fila: 2 Atendendo Bruno. Ainda na fila: 1 Atendendo Carla. Ainda na fila: 0 Fila vazia!
Aqui a "mudança" que leva ao fim do laço é o array_shift: cada volta tira uma pessoa, até count($fila) (o tamanho da fila) chegar a zero. O mesmo padrão aparece ao ler um arquivo linha por linha, até o fim (veja Ler e Escrever Arquivos).
do-while: primeiro faz, depois pergunta
O while testa antes de cada volta. Se a condição já for falsa no começo, o corpo nunca roda. O do-while ("faça… enquanto") inverte a ordem: roda o corpo primeiro e só depois testa. Por isso ele sempre roda pelo menos uma vez.
<?php
$saldo = 2000;
$meta = 1000;
while ($saldo < $meta) {
echo "Guardando dinheiro...\n";
$saldo += 150;
}
echo "Fim (saldo: $saldo)\n";
do {
echo "Guardando dinheiro (do-while)...\n";
$saldo += 150;
} while ($saldo < $meta);
echo "Fim (saldo: $saldo)\n";
- do {
- "Faça". Abre o corpo, sem condição nenhuma ainda.
- } while ($saldo < $meta);
- A condição vem no fim, depois da chave. Atenção ao ponto e vírgula no final: aqui ele é obrigatório.
$ php nunca.php Fim (saldo: 2000) « o while não rodou nenhuma vez Guardando dinheiro (do-while)... « o do-while rodou uma vez antes de testar Fim (saldo: 2150)
Com o saldo já acima da meta, o while olhou a condição, viu que era falsa e pulou o corpo. O do-while rodou o corpo, só então olhou a condição, e parou.
O uso típico: perguntar até a resposta servir
O do-while é perfeito quando você precisa fazer algo antes de ter o que testar. Para conferir uma senha, primeiro é preciso pedir a senha. A Ana fez um programa de terminal que dá três chances (ler o teclado com fgets(STDIN) é assunto de PHP na Linha de Comando):
<?php
$tentativas = 0;
do {
echo "Senha: ";
$senha = trim(fgets(STDIN));
$tentativas++;
} while ($senha !== "1234" && $tentativas < 3);
if ($senha === "1234") {
echo "Acesso liberado após $tentativas tentativa(s).\n";
} else {
echo "Bloqueado: 3 tentativas erradas.\n";
}
| Trecho | O que faz |
|---|---|
fgets(STDIN) | Lê uma linha digitada no teclado. |
trim(…) | Tira o Enter (a quebra de linha) do fim do que foi digitado. |
$senha !== "1234" && $tentativas < 3 | Repete enquanto a senha estiver errada e ainda houver chances (veja Operadores Lógicos). Duas formas de sair: acertar, ou gastar as 3 chances. |
Para testar sem digitar, a Ana mandou as respostas pelo printf, uma por linha. As perguntas "Senha:" saem coladas porque as respostas não aparecem na tela, só são lidas:
$ printf "abc\n1234\n" | php senha.php Senha: Senha: Acesso liberado após 2 tentativa(s). $ printf "a\nb\nc\n" | php senha.php Senha: Senha: Senha: Bloqueado: 3 tentativas erradas.
Com o while comum, a primeira pergunta teria de ficar repetida antes do laço (ou a variável $senha nem existiria no primeiro teste). A missão no fim da aula mostra esse problema.
while ou do-while?
while | do-while | |
|---|---|---|
| Quando testa | Antes de cada volta | Depois de cada volta |
| Mínimo de voltas | 0 | 1 |
| Ponto e vírgula no fim | Não | Sim: } while (…); |
| Use quando… | Pode não ser preciso fazer nada (fila vazia, meta já batida). É o caso mais comum. | Precisa fazer uma vez para ter o que testar (pedir uma resposta, mostrar um menu). |
O laço infinito
Se a condição nunca ficar falsa, o laço nunca termina. É o erro mais comum com while, e quase sempre a causa é a mesma: esquecer a linha que muda a variável da condição.
<?php
$i = 1;
while ($i <= 5) {
echo "Volta $i\n";
}
$ php esqueceu.php | head -4 Volta 1 Volta 1 Volta 1 Volta 1 « o head mostrou 4 linhas e encerrou; sem ele, isto nunca para
Sem o $i++, $i vale 1 para sempre, e 1 <= 5 é sempre verdade. No terminal, sem o | head, a tela se encheria de "Volta 1" sem parar.
No terminal, segure Ctrl e aperte C: o programa é interrompido e o prompt volta. Isso vale para qualquer programa, não só para o PHP. Num laço infinito que não escreve nada, o terminal parece "travado", sem mostrar nada; o Ctrl+C resolve do mesmo jeito.
O limite de tempo do PHP
Num site, o PHP não deixa uma página rodar para sempre: depois de 30 segundos (o padrão da configuração max_execution_time, "tempo máximo de execução"), ele interrompe o script com um erro fatal. No terminal, esse limite vem desligado, mas dá para ligá-lo com -d. A Ana testou um laço infinito que não escreve nada, com limite de 1 segundo:
<?php
$i = 1;
$total = 0;
while ($i <= 5) {
$total += $i;
}
echo "Total: $total\n";
$ php -d max_execution_time=1 silencioso.php
Fatal error: Maximum execution time of 1 second exceeded in /home/ana/site/silencioso.php on line 5
Stack trace:
#0 {main}
"Tempo máximo de execução de 1 segundo excedido". Num site, você veria a mesma mensagem com 30 seconds, depois de esperar meio minuto com a página carregando. Se aparecer, procure um laço cuja condição nunca fica falsa.
Laços infinitos de propósito
Às vezes você quer um laço sem fim na condição, com a saída no meio do corpo: while (true) { … if (…) break; … }. O break ("quebrar") sai do laço na hora. Esse padrão, junto com o continue, está em break e continue.
A sintaxe alternativa: while (…): … endwhile;
Dentro do HTML (veja PHP Dentro do HTML), as chaves somem no meio das tags. Por isso existe uma forma com dois-pontos e endwhile; ("fim do while"):
<?php $i = 1; ?>
<ul>
<?php while ($i <= 3): ?>
<li>Item <?= $i ?></li>
<?php $i++; endwhile; ?>
</ul>
$ php alt.php
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
O : substitui o {, e o endwhile; substitui o }. O mesmo existe para for (endfor), foreach (endforeach) e if (endif). Na prática, para gerar listas no HTML, o foreach e o for são mais usados que o while.
Quando dá errado
$ php esqueceu.php | head -4 Volta 1 Volta 1 Volta 1 Volta 1
O que aconteceu: a condição depende de $i, mas nada dentro do laço muda $i.
Como consertar: interrompa com Ctrl+C e acrescente a mudança ($i++;) dentro do corpo. Regra: toda variável da condição precisa mudar dentro do laço, na direção que leva a condição a ficar falsa.
while
<?php
$i = 1;
while ($i <= 5); {
echo "Volta $i\n";
$i++;
}
$ timeout 2 php pv.php $ echo $? 124 « 124: o timeout teve de parar o PHP depois de 2 segundos
O que aconteceu: o ; logo depois de while (…) é um corpo vazio. O PHP fica repetindo "nada" enquanto $i <= 5, e o bloco { … } de baixo (que tem o $i++) nunca é alcançado. Não aparece erro, nem saída: o programa só fica preso. (A Ana usou o comando timeout 2 para o terminal desistir sozinho; sem ele, seria Ctrl+C.)
Como consertar: apague o ;. No while, nada vai entre o ) e a {. (O ponto e vírgula depois do while só existe no fim do do-while.)
$ php nunca.php Fim (saldo: 2000) …
O que aconteceu: a condição já era falsa no primeiro teste. Pode ser o certo (não havia o que fazer) ou um erro no valor inicial ou na comparação (> no lugar de <).
Como consertar: confira os valores antes do laço com var_dump (veja var_dump e print_r). Se o corpo precisa rodar pelo menos uma vez, use do-while.
Warning: Undefined variable na condição
$ printf "abc\n1234\n" | php senha0.php Warning: Undefined variable $senha in /home/ana/site/senha0.php on line 4 Senha: Senha: Acesso liberado após 2 tentativa(s).
O que aconteceu: um while ($senha !== "1234" …) testa $senha antes de ela ser criada (ela só nasce dentro do corpo). O programa até funcionou, porque a variável inexistente vale null, que é diferente de "1234", mas o aviso mostra que o código está torto.
Como consertar: crie a variável antes do laço ($senha = "";) ou, melhor, use do-while, que pergunta antes de testar.
Maximum execution time of 30 seconds exceeded
O que aconteceu: na web, um script rodou por mais de 30 segundos, quase sempre por causa de um laço infinito (ou de um laço com dados demais). Veja o limite de tempo do PHP, acima.
Como consertar: procure o laço cuja condição nunca fica falsa. Aumentar o limite (set_time_limit) só faz sentido para tarefas longas de verdade, nunca para esconder um laço infinito.
Referência
| Forma | O que faz |
|---|---|
while (condição) { … } | Testa; se verdadeira, roda o corpo e testa de novo. Pode rodar 0 vezes. |
do { … } while (condição); | Roda o corpo, depois testa. Roda pelo menos 1 vez. Ponto e vírgula no fim. |
while (condição): … endwhile; | Sintaxe alternativa, para templates HTML. |
while (true) { … break; … } | Laço com a saída no meio (veja break e continue). |
| Ctrl+C | Interrompe um programa preso no terminal. |
max_execution_time | Tempo máximo de um script: 30 s na web, sem limite no terminal. php -d max_execution_time=N muda para N segundos. |
Exercícios
1. A condição
Complete para repetir enquanto $i for menor que 10:
2. A peça que falta
Este laço nunca termina. Complete a linha que falta dentro dele:
3. O fim do do-while
Complete a última linha de um do-while que repete enquanto $opcao for diferente de "sair":
4. Quantas vezes "oi" aparece? $x = 10; while ($x < 5) { echo "oi"; $x++; }
5. E agora? $x = 10; do { echo "oi"; $x++; } while ($x < 5);
6. Seu programa de terminal ficou preso, sem mostrar nada. O que fazer primeiro?
Missão final
Missão: este programa de senha usa while e testa $senha antes de ela existir: rode e veja o Warning. Transforme o laço num do-while (o do { no começo, o } while (…); no fim) para o aviso sumir. As respostas digitadas já estão prontas na caixa: abc e depois 1234.
<?php
$tentativas = 0;
while ($senha !== "1234" && $tentativas < 3) {
echo "Senha: ";
$senha = trim(fgets(STDIN));
$tentativas++;
}
if ($senha === "1234") {
echo "Acesso liberado após $tentativas tentativa(s).\n";
} else {
echo "Bloqueado: 3 tentativas erradas.\n";
}
<?php
$tentativas = 0;
do {
echo "Senha: ";
$senha = trim(fgets(STDIN));
$tentativas++;
} while ($senha !== "1234" && $tentativas < 3);
if ($senha === "1234") {
echo "Acesso liberado após $tentativas tentativa(s).\n";
} else {
echo "Bloqueado: 3 tentativas erradas.\n";
}
Resumo
while (condição) { … }repete o corpo enquanto a condição for verdadeira, testando antes de cada volta. Pode não rodar nenhuma vez.- Com contador, são três peças: o início antes do laço, a condição no
whilee a mudança dentro do corpo. - O
whileé ideal quando você não sabe quantas voltas serão: até bater a meta, até a fila acabar. do { … } while (condição);testa depois: roda pelo menos uma vez. Ideal para pedir algo e repetir enquanto não servir.- Laço infinito: a condição nunca fica falsa (quase sempre faltou a mudança). Pare com Ctrl+C; na web, o PHP para em 30 segundos com
Maximum execution time … exceeded. - No HTML, existe
while (…): … endwhile;.