Dia 4 — Repetição: PHP e MySQL
for e while
for e while
O for repete um bloco um número controlado de vezes: inicializa o contador, testa a condição e atualiza o contador. O while repete enquanto a condição for verdadeira, sem se preocupar com quantas vezes.
for
for ($i = 1; $i <= 5; $i++) { echo $i; }
As três partes vao separadas por ponto e virgula: inicializacao, condição e incremento. Qualquer uma pode ser vazia — e o que transforma o laco em for (;;), a forma infinita.
A condição e testada antes de cada volta, então um for cuja condição já começa falsa não executa nada. Esse detalhe e a diferença entre for e do-while.
for ($i = 0; $i < 5; $i++) {} // 5 voltas: 0, 1, 2, 3, 4 for ($i = 1; $i <= 5; $i++) {} // 5 voltas: 1, 2, 3, 4, 5
while
$tentativas = 3; while ($tentativas > 0) { echo $tentativas; $tentativas--; }
O while so tem condição. O corpo pode não rodar nenhuma vez: e o que acontece quando a condição já comeca falsa, como while ($x < 5) com $x = 10.
O while e a escolha quando o número de voltas não se sabe de antemao — repetir até o usuario acertar, processar fila, ler arquivo linha a linha.
Acumulador
O contador e um acumulador: uma variável que guarda o total e e atualizada a cada volta. Ela precisa ser iniciada antes do laco, senao o PHP avisa "undefined variable" na primeira volta.
$total = 0; foreach ($precos as $preco) { $total += $preco; }
O operador += encurta $total = $total + $preco. O erro classico e declarar o acumulador dentro do laco: ele volta a zero a cada volta e o total final e sempre o ultimo valor.
A mesma lógica vale para while, com $contador++ ou $contador += 2 para pular de dois em dois.
Exemplo
<?php declare(strict_types=1); // for: inicializacao; condicao; incremento. As tres partes vao separadas por // ponto e virgula e podem ser vazias. echo "1. for contando de 1 a 5\n"; for ($i = 1; $i <= 5; $i++) { echo " volta $i\n"; } // O contador pode comecar em zero e a condicao pode ser <. echo "\n2. O mesmo laco comecando em zero\n"; $soma = 0; for ($i = 0; $i < 5; $i++) { $soma += $i; } echo " soma de 0 a 4 = $soma\n"; // while: so a condicao. O corpo pode nao rodar nenhuma vez. echo "\n3. while testa a condicao antes de executar\n"; $tentativas = 3; while ($tentativas > 0) { echo " tentativa restando: $tentativas\n"; $tentativas--; } echo " o corpo rodou " . (3 - $tentativas) . " vezes\n"; // while com condicao inicialmente falsa nao entra no corpo. $contador = 10; while ($contador < 5) { echo " isto nunca aparece\n"; } echo " while com $contador < 5 nao executou nada\n"; // As tres partes do for sao opcionais. Sem condicao, o laco e infinito. echo "\n4. Partes do for opcionais\n"; for (;;) { echo " primeira volta\n"; break; } $lista = ['a', 'b']; for ($i = 0, $n = count($lista); $i < $n; $i++) { echo ' indice ' . $i . ': ' . $lista[$i] . "\n"; } // Acumulador: a variavel que guarda o total precisa existir antes do laco. echo "\n5. Acumulador precisa ser inicializado fora\n"; $total = 0; $itens = [12.50, 8.00, 30.25, 4.99]; foreach ($itens as $preco) { $total += $preco; } echo ' total dos ' . count($itens) . " itens: $total\n"; $aprovados = 0; $notas = [7.5, 5.0, 9.9, 3.2, 6.8]; foreach ($notas as $nota) { if ($nota >= 6.0) { $aprovados++; } } echo " notas aprovadas: $aprovados de " . count($notas) . "\n"; // while tambem acumula. echo "\n6. while como acumulador\n"; $linha = 5; $espaco = 1; while ($espaco <= $linha) { echo str_repeat('* ', $espaco) . "\n"; $espaco++; }
Saída real
1. for contando de 1 a 5 volta 1 volta 2 volta 3 volta 4 volta 5 2. O mesmo laco comecando em zero soma de 0 a 4 = 10 3. while testa a condicao antes de executar tentativa restando: 3 tentativa restando: 2 tentativa restando: 1 o corpo rodou 3 vezes while com 10 < 5 nao executou nada 4. Partes do for opcionais primeira volta indice 0: a indice 1: b 5. Acumulador precisa ser inicializado fora total dos 4 itens: 55.74 notas aprovadas: 3 de 5 6. while como acumulador * * * * * * * * * * * * * * *
do-while e laços infinitos
do-while e laços infinitos
O do-while executa o corpo antes de testar a condição, então roda pelo menos uma vez. Quando a repeticao depende de algo externo — uma resposta do usuario, uma leitura de arquivo — essa e a forma correta, porque evita um teste vazio no comeco.
A diferença que importa
$i = 10; while ($i < 5) { echo 'nunca'; } // zero execucoes $i = 10; do { echo 'uma vez'; } while ($i < 5); // uma execucao
Com while, a condição falsa impede a entrada. Com do-while, o corpo já rodou antes do primeiro teste. Por isso a condição precisa mudar dentro do corpo; caso contrario o laco nunca termina.
$confirmacao = 'N'; do { echo "digite S: "; $confirmacao = 'S'; // o corpo precisa mudar a condicao } while ($confirmacao !== 'S');
while (true) e for (;;)
São a mesma coisa: laco sem condição de parada, que so sai pelo break. O while (true) e o mais comum e mais legivel.
$processados = 0; while (true) { $processados++; if ($processados >= 4) { break; } echo "item $processados"; }
O break costuma ficar dentro de um if, e essa e a saída mais usada na pratica. As outras saídas de um laco são o proprio return, quando o laco esta dentro de uma função, e exit ou die, que encerram o script inteiro.
Condição composta
Quando a parada depende de duas regras, junte com && e ||. O && e avaliado primeiro, então o grupo mais restritivo vem entre parenteses:
while ($indice < count($fila) && $fila[$indice] !== '') { $processados[] = $fila[$indice]; $indice++; }
O laco que nunca termina acontece quando a condição depende de uma variável que o corpo nunca altera, ou quando o corpo altera para um valor que ainda satisfaz a condição. A defesa pratica e o limite de seguranca — um if com contador maximo — porque um laco infinito em página web trava o servidor, não so o navegador.
Exemplo
<?php declare(strict_types=1); // do-while testa a condicao DEPOIS de executar: o corpo roda pelo menos uma vez. echo "1. do-while executa uma vez mesmo com condicao ja falsa\n"; $tentativas = 0; do { echo " corpo executado, tentativas = $tentativas\n"; $tentativas++; } while ($tentativas < 3); echo " total de execucoes: $tentativas\n"; // A condicao precisa virar falsa. Aqui a confirmacao muda dentro do corpo. $confirmacao = 'N'; do { echo " 'Digite S para confirmar' recebido: '$confirmacao'\n"; $confirmacao = 'S'; } while ($confirmacao !== 'S'); echo " (o corpo rodou uma vez porque a confirmacao ja era 'S')\n"; // while nao faz isso: com condicao falsa o corpo nao roda nenhuma vez. echo "\n2. while e do-while com a mesma condicao inicial\n"; $i = 10; while ($i < 5) { echo " isto nunca aparece\n"; } echo " while: zero execucoes\n"; $i = 10; do { echo " do-while: uma execucao com i = $i\n"; } while ($i < 5); // while Infinito: e um while sem condicao de parada. O break e a saida. echo "\n3. while (true) com break: a forma mais comum de laco infinito\n"; $processados = 0; while (true) { $processados++; if ($processados >= 4) { break; } echo " item $processados processado\n"; } echo " saiu do laco apos $processados itens\n"; // for (;;) tambem e infinito e as vezes deixa a intencao mais clara. echo "\n4. for (;;) e a mesma coisa\n"; $tentativas = 0; for (;;) { $tentativas++; if ($tentativas > 2) { break; } echo ' tentativa ' . $tentativas . "\n"; } echo " total de tentativas: $tentativas\n"; // A condicao de parada precisa depender do estado. Se ela nunca virar // falsa, o laco so para com break, exit ou die. echo "\n5. Laco que nunca termina: condicao sempre verdadeira\n"; $repeticoes = 0; $limite = 3; while ($repeticoes < $limite || $limite < 0) { $repeticoes++; if ($repeticoes >= $limite) { $limite = -1; // continua verdadeiro de proposito } if ($repeticoes > 6) { break; // rede de seguranca } } echo " laco sem condicao de parada real; break de seguranca em $repeticoes voltas\n"; // Condicao composta com && e ||: e assim que se simula "enquanto houver // item E nao deu erro". echo "\n6. Condicao composta com && e ||\n"; $fila = ['ana', 'bruno', 'cecilia', '', 'diego']; $indice = 0; $processados = []; while ($indice < count($fila) && ($fila[$indice] !== '' || $indice < 1)) { $processados[] = $fila[$indice]; $indice++; } echo ' processados: ' . implode(', ', $processados) . "\n"; // break dentro de um if dentro do laco e o padrao de "sai quando acabou". echo "\n7. break dentro de if: padrao para procurar em colecao\n"; $alunos = [ ['nome' => 'Ana', 'nota' => 8.0], ['nome' => 'Bruno', 'nota' => 4.5], ['nome' => 'Cecilia', 'nota' => 9.5], ]; foreach ($alunos as $aluno) { if ($aluno['nota'] < 5.0) { echo " reprovado encontrado: {$aluno['nome']}\n"; break; } echo " aprovado: {$aluno['nome']}\n"; }
Saída real
1. do-while executa uma vez mesmo com condicao ja falsa corpo executado, tentativas = 0 corpo executado, tentativas = 1 corpo executado, tentativas = 2 total de execucoes: 3 'Digite S para confirmar' recebido: 'N' (o corpo rodou uma vez porque a confirmacao ja era 'S') 2. while e do-while com a mesma condicao inicial while: zero execucoes do-while: uma execucao com i = 10 3. while (true) com break: a forma mais comum de laco infinito item 1 processado item 2 processado item 3 processado saiu do laco apos 4 itens 4. for (;;) e a mesma coisa tentativa 1 tentativa 2 total de tentativas: 3 5. Laco que nunca termina: condicao sempre verdadeira laco sem condicao de parada real; break de seguranca em 7 voltas 6. Condicao composta com && e || processados: ana, bruno, cecilia 7. break dentro de if: padrao para procurar em colecao aprovado: Ana reprovado encontrado: Bruno
foreach
foreach
O foreach percorre um array sem se preocupar com índice nem com condição de parada. Ele funciona em lista indexada, em dicionario e em objeto, sempre com a mesma forma.
As três formas
foreach ($frutas as $fruta) {} // so os valores foreach ($aluno as $campo => $valor) {} // chave e valor foreach ($tabela as [$nome, $nota]) {} // desestrutura a linha
A forma com chave e valor e a unica que funciona bem em array associativo, onde o índice não e sequencial:
$aluno = ['nome' => 'Maria', 'idade' => 17]; foreach ($aluno as $campo => $valor) { echo "$campo = $valor"; }
Em array indexado a chave e o índice, e comeca em 0. Com o list(), o par índice-valor e separado em duas variáveis na mesma volta, sem precisar de =>:
foreach ($tabela as [$nome, $nota]) { echo "$nome tirou $nota"; }
Dentro do laco, key() devolve a chave atual e current() devolve o valor — util quando o valor vem de uma expressao e não de uma variável simples.
Ordem e alteracao
O foreach percorre na ordem de insercao das chaves, e não em ordem alfabetica nem de valor. Para ordenar, use sort(), ksort() ou usort() (aula do dia 6).
Sem o &, mudar a variável do valor dentro do laco não surte efeito no array original: o PHP entrega uma copia do valor. Para alterar o array, o & faz a variável apontar para o elemento:
foreach ($numeros as &$numero) { $numero *= 10; } unset($numero); // obrigatorio
O unset() depois do &
Depois de um foreach com &, a referência continua apontando para o ultimo elemento. Um foreach posterior sobre outro array pode então alterar o primeiro sem querer, porque as duas voltas compartilham a mesma variável.
foreach ($primeiro as &$x) { $x *= 2; } unset($x); // limpa a referencia foreach ($segundo as $y) { $y += 100; } // agora nao toca no primeiro
O unset() imediatamente após o laco com & e o que separa o código que funciona do código que corrompe dados em producao.
Exemplo
<?php declare(strict_types=1); // foreach percorre valores; na forma com chave, percorre chave => valor. $frutas = ['maçã', 'banana', 'uva']; echo "1. foreach so nos valores\n"; foreach ($frutas as $fruta) { echo " $fruta\n"; } echo "\n2. foreach com chave e valor\n"; foreach ($frutas as $indice => $fruta) { echo " [$indice] $fruta\n"; } // A chave de um array indexado comeca em 0 e anda de 1 em 1. echo "\n3. As chaves de um array indexado\n"; foreach ($frutas as $chave => $valor) { echo " chave $chave e do tipo " . gettype($chave) . "\n"; } // Array associativo: a chave e o que voce deu de nome. echo "\n4. foreach em array associativo\n"; $aluno = [ 'nome' => 'Maria', 'idade' => 17, 'cidade' => 'Recife', ]; foreach ($aluno as $campo => $valor) { echo " $campo = $valor\n"; } // A ordem do foreach e a ordem de insercao das chaves. echo "\n5. A ordem e a de insercao\n"; $ordem = ['z' => 1, 'a' => 2, 'm' => 3]; foreach ($ordem as $k => $v) { echo " $k => $v\n"; } // Alternativa com list(), util quando a chave tambem interessa. echo "\n6. list() traz indice e valor de uma vez\n"; foreach ($frutas as $indice => $fruta) { [$indice, $fruta] = [$indice, $fruta]; echo " indice $indice, fruta $fruta\n"; } $tabela = [ ['Ana', 8.5], ['Bruno', 6.0], ['Cecilia', 9.5], ]; foreach ($tabela as [$nome, $nota]) { echo " $nome tirou $nota\n"; } // key() dentro do laco devolve a chave atual; current() o valor. echo "\n7. key() e current() leem a posicao atual\n"; $letras = ['a' => 'vogal', 'b' => 'consoante']; foreach ($letras as $chave => $tipo) { echo ' key() = ' . key($letras) . ', current() = ' . current($letras) . "\n"; } // Sem o &, mudar $valor dentro do laco nao altera o array original. echo "\n8. Alterar o valor dentro do laco sem & nao surte efeito\n"; $cores = ['azul', 'verde']; foreach ($cores as $cor) { $cor = strtoupper($cor); } echo ' depois do foreach: ' . implode(', ', $cores) . " (continua minusculo)\n"; echo "\n9. Com & o valor e alterado no proprio array\n"; $numeros = [1, 2, 3]; foreach ($numeros as &$numero) { $numero *= 10; } unset($numero); // obrigatorio: limpa a referencia do ultimo elemento echo ' depois do foreach: ' . implode(', ', $numeros) . "\n"; // Sem o unset, a referencia continua viva e o proximo foreach corrompe dados. echo "\n10. Por que o unset() depois do & e obrigatorio\n"; $primeiro = [1, 2]; $segundo = [5, 6]; foreach ($primeiro as &$x) { $x *= 2; } echo ' primeiro: ' . implode(', ', $primeiro) . "\n"; foreach ($segundo as $y) { $y += 100; } echo ' segundo: ' . implode(', ', $segundo) . " (intacto, sem referencia pendente)\n"; // foreach funciona igual em qualquer iteravel: array e objeto. echo "\n11. foreach em lista de listas e em dicionario aninhado\n"; foreach (['nota' => 8.5, 'falta' => 0] as $chave => $valor) { echo " $chave: $valor\n"; }
Saída real
1. foreach so nos valores maçã banana uva 2. foreach com chave e valor [0] maçã [1] banana [2] uva 3. As chaves de um array indexado chave 0 e do tipo integer chave 1 e do tipo integer chave 2 e do tipo integer 4. foreach em array associativo nome = Maria idade = 17 cidade = Recife 5. A ordem e a de insercao z => 1 a => 2 m => 3 6. list() traz indice e valor de uma vez indice 0, fruta maçã indice 1, fruta banana indice 2, fruta uva Ana tirou 8.5 Bruno tirou 6 Cecilia tirou 9.5 7. key() e current() leem a posicao atual key() = a, current() = vogal key() = a, current() = vogal 8. Alterar o valor dentro do laco sem & nao surte efeito depois do foreach: azul, verde (continua minusculo) 9. Com & o valor e alterado no proprio array depois do foreach: 10, 20, 30 10. Por que o unset() depois do & e obrigatorio primeiro: 2, 4 segundo: 5, 6 (intacto, sem referencia pendente) 11. foreach em lista de listas e em dicionario aninhado nota: 8.5 falta: 0
break, continue e laços aninhados
break, continue e laços aninhados
O break sai do laco; o continue pula o resto da volta e comeca a proxima. Em laco aninhado, os dois agem no laco mais interno — e o número depois do operador escolhe quantos niveis sair ou pular.
break e continue
| Instrução | Efeito |
|---|---|
break | sai do laco mais interno |
continue | pula para a proxima volta do laco mais interno |
break 2 | sai de dois lacos |
continue 2 | pula a volta do laco externo |
O continue e o filtro de lista: descarta o item e segue.
foreach ($numeros as $n) { if ($n % 2 === 0) { continue; // pula o par } echo $n; }
O break e a busca: para assim que achar o que procura.
foreach ($alunos as $aluno) { if ($aluno['nota'] < 5.0) { break; // achou o primeiro reprovado } }
break 2 e continue 2
Sem número, break sai so do laco interno e o externo continua. Com 2, alcança o laco de fora.
foreach ($a as $x) { foreach ($b as $y) { if ($y === 'erro') { break 2; // sai dos dois lacos } } }
O continue 2 tem efeito diferente: ele não sai do laco externo, apenas pula o resto da volta dele e avanca para o proximo valor do externo.
foreach ($a as $x) { foreach ($b as $y) { if ($y === 2) { continue 2; // pula para o proximo $x } echo "$x x $y"; } }
O número conta os niveis de laco, e não as repeticoes: break 3 exige três lacos aninhados.
Rotulo com goto
O goto salta para um rotulo na mesma função. E a forma de sair de varios lacos sem repeating break 2, e o unico caso em que e mais legivel que o break numerado.
for ($i = 1; $i <= 3; $i++) { for ($j = 1; $j <= 3; $j++) { if ($i * $j > 4) { goto fim; } } } fim: echo 'saiu pelo rotulo';
O rotulo e um identificador seguido de : e precisa terminar em ; quando e um comando simples. O goto não pula para dentro de laco nem de função, e so funciona dentro da mesma função — por isso ele quase nunca substitui um break 2 bem colocado.
Exemplo
<?php declare(strict_types=1); // break sai do laco mais interno. continue pula para a proxima volta. $numeros = [1, 2, 3, 4, 5, 6, 7, 8]; echo "1. break interrompe o laco\n"; foreach ($numeros as $n) { if ($n === 4) { echo " break no $n: o 4 e os seguintes nao entram\n"; break; } echo " processando $n\n"; } echo "\n2. continue pula para a proxima iteracao\n"; foreach ($numeros as $n) { if ($n % 2 === 0) { continue; } echo " apenas impares: $n\n"; } echo "\n3. continue no fim do corpo equivale a nada, e sautavel\n"; foreach ($numeros as $n) { if ($n < 3) { continue; } echo " $n\n"; } // continue 2 e break 2 dizem a quantos lacos a ordem se aplica. echo "\n4. break 2 e continue 2 em laco aninhado\n"; foreach ([1, 2, 3] as $a) { foreach (['x', 'y', 'z'] as $b) { if ($b === 'y') { break 2; // sai dos DOIS lacos de uma vez } echo " par $a$b (o 'y' nao aparece)\n"; } } echo "\n5. continue 2 pula a volta do laco de fora\n"; foreach ([1, 2, 3] as $a) { foreach ([1, 2, 3] as $b) { if ($b === 2) { continue 2; // pula o resto do laco interno e vai ao proximo $a } echo " $a x $b\n"; } } // break sem numero sai so do laco onde esta. echo "\n6. break comum em laco aninhado sai so do interno\n"; foreach ([1, 2] as $a) { foreach (['x', 'y'] as $b) { if ($b === 'x') { break; } echo " interno: $a$b\n"; } echo " volta ao laco externo com a = $a\n"; } // Rotulo: goto apontando para um rotulo dentro do laco. echo "\n7. Rotulo com goto para interromper lacos aninhados\n"; for ($i = 1; $i <= 3; $i++) { for ($j = 1; $j <= 3; $j++) { if ($i * $j > 4) { goto fim; } echo " i=$i j=$j\n"; } } fim: echo " saiu pelo rotulo 'fim'\n"; // break 3 alcança tres niveis de aninhamento. echo "\n8. break 3 em tres niveis\n"; $niveis = 0; foreach ([1, 2, 3] as $a) { foreach ([1, 2, 3] as $b) { foreach ([1, 2, 3] as $c) { $niveis++; if ($niveis === 5) { break 3; } } } } echo " pares visitados: $niveis (o quinto dispara o break 3)\n"; // O par break/continue em filtro de entrada. echo "\n9. Filtro com continue e validacao com break\n"; $entradas = ['ana', '', 'bruno', '0', 'cecilia']; foreach ($entradas as $entrada) { if (trim($entrada) === '') { echo " descartado: campo em branco\n"; continue; } if (strlen($entrada) < 3) { echo " descartado: $entrada tem menos de 3 letras\n"; continue; } echo " aceito: $entrada\n"; }
Saída real
1. break interrompe o laco processando 1 processando 2 processando 3 break no 4: o 4 e os seguintes nao entram 2. continue pula para a proxima iteracao apenas impares: 1 apenas impares: 3 apenas impares: 5 apenas impares: 7 3. continue no fim do corpo equivale a nada, e sautavel 3 4 5 6 7 8 4. break 2 e continue 2 em laco aninhado par 1x (o 'y' nao aparece) 5. continue 2 pula a volta do laco de fora 1 x 1 2 x 1 3 x 1 6. break comum em laco aninhado sai so do interno volta ao laco externo com a = 1 volta ao laco externo com a = 2 7. Rotulo com goto para interromper lacos aninhados i=1 j=1 i=1 j=2 i=1 j=3 i=2 j=1 i=2 j=2 saiu pelo rotulo 'fim' 8. break 3 em tres niveis pares visitados: 5 (o quinto dispara o break 3) 9. Filtro com continue e validacao com break aceito: ana descartado: campo em branco aceito: bruno descartado: 0 tem menos de 3 letras aceito: cecilia