PHP para Iniciantes: Estruturas de Controle - CONTINUE

"Continue a nadar" é uma frase que marcou toda uma geração e que remete a sensação de loops infinitos, certo? Mas se você partir desse ponto de vista, vai acabar tendo uma ideia errada. Na programação, continue é um verbo executado dentro de um laço e não é exatamente um "Ok, continue o que está fazendo" (se fosse o caso, bastaria não colocar instrução alguma...), está mais para "Pera, deu ruim, encerra aqui e continue do próximo laço".

Como assim, Kiko?

Calma, vamos do começo.

Uma reflexão sobre semântica

O verbo continue afeta as mesmas estruturas que o break: while, do-while, for, foreach e switch. No entanto, peço que evite utilizar em switch, especificamente, pois não faz sentido algum e a partir do PHP 7.3 já está gerando Warning - o que significa que em algum momento será um erro, oficialmente falando.

Ok, Kiko, eu nem vi switch ainda e você já tá falando o que não usar?

Hehehehe, sim... Faz parte, né?

Voltando a reflexão, vamos relembrar a semântica simplificada do for:

  • PARA (configuração dos loops) FAÇA (ação)

Certo? Nesse cenário, o continue seria um verbo acionado dentro da ação. Por tanto, sua reflexão precisa ser a nível de unicidade de um laço. Então imagine que você tem algum critério para processar cada laço, mas que esse critério não deve interromper todo o fluxo, apenas ignorar os laços que não forem aceitos.

Isso não é a mesma coisa que o break, Kiko?

Não! Vai ficar mais claro a seguir. Mas o ponto é que o break ENCERRA os laços, enquanto o continue apenas salta para a próxima iteração.

Voltando ao cenário, geralmente os critérios são feitos em if e, sendo o caso, nós temos a semântica SE (isso) ENTÃO (aquilo). Aqui, aquilo = continue. Com isso, a semântica do continue se soma à semântica do if: SE (isso) ENTÃO CONTINUE PARA A PRÓXIMA ITERAÇÃO.

continue = CONTINUE PARA A PRÓXIMA ITERAÇÃO? Orra, Kiko... Não tinha algo mais curto não?

Se o cenário fosse uma fila de atendimento, seria CHAME O PRÓXIMO. Tudo depende do contexto.

A sintaxe

Bem, agora que você já entendeu a semântica, você pode aplicar o continue do mesmo jeito que o break, exceto que o inteiro colocado posteriormente representa qual dos laços encadeados você quer afetar.

Como assim, Kiko?

Bem, você lembra da sintaxe break <número>? Também temos continue <número>. Mas na minha explicação do break, eu comentei que era a quantidade de laços a encerrar, porque é basicamente o que ele faz. Já o continue, não. Apenas um laço recebe o comando continue, o número que você informa serve para identificar a profundidade onde o comando tem de afetar.

Vamos para o exemplo? Imagine que temos uma sequência numérica e, a partir dela, queremos extrair os números pares. Tem algumas formas de se fazer isso... A melhor é com array_filter, mas não vem ao caso. A ideia aqui é usar uma estrutura onde seja possível aplicar o continue, ou seja, while / do-while / for / foreach. Escolhi o último, porque é o mais simples de ler. Vamos lá?

<?php

$numeros = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

function ehPar(int $numero): bool
{
    return ($numero % 2) === 0; // resto da divisão por 2 é estritamente igual a 0
}

$pares = []; // lista de pares a popular

foreach ($numeros as $numero) {
    if (! ehPar($numero)) { // se não for par
        continue; // continue para a próxima iteração
    }
    // se for par
    $pares[] = $numero; // coloque na lista de pares
}

var_dump($pares); // array(0, 2, 4, 6, 8)

Ué, Kiko, não era continue <número>?

Sim! Mas se você não informar nada, será o equivalente a continue 1, assim como no break. Nesse caso, como só tinha um loop, era essa minha intenção mesmo.

Então você pode dar algum exemplo real onde o continue afeta algum laço acima, Kiko?

Er... Ok, deixe-me pensar um pouco.

Thinking.......

Ok, tive uma ideia. Imagine que, por algum motivo, você tem uma lista de dados completamente bagunçados. É uma estrutura similar a uma matriz (array dentro de array), só que as colunas estão desordenadas. Você precisa percorrer todo o array para coletar os dados cujo primeiro valor inteiro é par. Isso vai gerar alguns continue, vamos ver? Bom, acho que o ideal é fazer em partes.

A entrada

$bagunca = [
    ['K', 1.83, 0, 'Kiko'],
    [3, 'Y', 1.73, 'Medalha'],
    ['derBrian', 8, 'V', 1.69],
];

A saída esperada

$resultado = [
    ['K', 1.83, 0, 'Kiko'],
    ['derBrian', 8, 'V', 1.69],
];

Os laços

Bem, Antes de qualquer coisa, nós precisamos percorrer o array, certo? Começamos com um primeiro foreach:

foreach ($bagunca as $item) {
    // a mágica vai acontecer aqui
    // onde $item é um array, por exemplo, ['K', 1.83, 0, 'Kiko']
}

Então, precisaremos percorrer o array $item para encontrar a primeira "propriedade" que é inteira. Para validar, usaremos a função is_int():

// dentro do foreach de cima
foreach ($item as $valor) {
    if (is_int($valor)) {
        // o que acontece?
    }
    // e se não for?
}

Aqui temos duas possibilidades: tratar com break ao encontrar (if positivo) ou com continue ao não encontrar (if negativo). Como a aula é sobre continue, já sabe o que escolhi:

foreach($item as $valor) {
    if (! is_int($valor)) {
        continue;
    }
    // ok, mas o que acontece quando é int?
}

Já temos um exemplo, uhuuu... Mas ainda falta validar se o inteiro é par. O que acontece se não for? Bem, aquele $item torna-se inválido. Nesse caso, precisamos saltar para o próximo $item, que é gerado no loop de fora......... Ok, tá virando bagunça. Vamos juntar o que temos até agora:

foreach ($bagunca as $item) {
    foreach ($item as $valor) {
        if (! is_int($valor)) {
            continue;
        }
        // alguma mágica aqui...
    }
    // e esperamos que, ao chegar aqui, todas as validações passaram e precisamos incluir esse $item em algum lugar
}

Para validar se $valor é par, usaremos a expressão ($valor % 2) === 0, que checa se é divisível por 2. Mas como a ideia é validar com continue, precisamos negar essa sentença, procurando pelos ímpares. Ou seja, basta trocar o === por !==:

foreach ($bagunca as $item) {
    foreach ($item as $valor) {
        if (! is_int($valor)) {
            continue;
        }
        if (($valor % 2) !== 0) {
            continue 2; // ou seja, afete o 2° foreach de dentro para fora, que é o foreach($bagunca)
        }
        // e se chegou até aqui, é porque validou tudo! ou seja, vamos encerrar esse loop
        break;
    }
    // TODO
}

E agora? Já validamos tudo:

  • se não for int, continua buscando o inteiro. Nesse cenário, é garantido que ao menos um dos dados em $item é inteiro, então não se preocupe com a possibilidade de não vir um;
  • se não for par, ignoramos aquele $item e passamos para o próximo;
  • se for int e par, então o $item passou nos critérios e deve ser armazenado em... $resultado! Variável que ainda não criamos. Vamos criar logo?
<?php
$bagunca = [
    ['K', 1.83, 0, 'Kiko'],
    [3, 'Y', 1.73, 'Medalha'],
    ['derBrian', 8, 'V', 1.69],
];

$resultado = [];
foreach ($bagunca as $item) {
    foreach ($item as $valor) {
        if (! is_int($valor)) {
            continue;
        }
        if (($valor % 2) !== 0) {
            continue 2;
        }
        break;
    }
    $resultado[] = $item;
}

var_dump($resultado); // array(['K', 1.83, 0, 'Kiko'], ['derBrian', 8, 'V', 1.69])

Como seria esse código com break, Kiko?

Bem mais simples, mas mais difícil de ler:

<?php
$bagunca = [
    ['K', 1.83, 0, 'Kiko'],
    [3, 'Y', 1.73, 'Medalha'],
    ['derBrian', 8, 'V', 1.69],
];

$resultado = [];
foreach ($bagunca as $item) {
    foreach ($item as $valor) {
        if (is_int($valor) && ($valor % 2) === 0) { // se é inteiro && é par
            $resultado[] = $item; // então adiciona o item
            break; // e fim de papo, encerra esse loop e começa o outro logo rapá
        }
    }
}

var_dump($resultado); // array(['K', 1.83, 0, 'Kiko'], ['derBrian', 8, 'V', 1.69])

Nesse cenário, como não tem mais nenhum código depois do foreach ($item), daria para trocar o break por um continue 2, mas o princípio não é esse. O break aplicado ali ainda permite executar algum código depois do foreach de dentro encerrar, sabe? O continue 2 não daria essa oportunidade.

Além disso, apesar de um único if ser bonito, isso não é exatamente uma forma interessante de programar. Na visão do produto, você juntou duas regras em uma. Ao precisar dar uma manutenção, você precisará tomar cuidado pra sua alteração não afetar o comportamento de outra regra, sabe?

Quando é só duas regras é fácil ler. Eu já vi código com quase 10 regras em um if só. Na real, eu já fiz isso e precisei dar manutenção depois... Não façam isso. Vai por mim.

Tá bom, Kiko... Mas só por curiosidade, como seria com array_filter?

Olha só... Gostei
<?php

$bagunca = [
    ['K', 1.83, 0, 'Kiko'],
    [3, 'Y', 1.73, 'Medalha'],
    ['derBrian', 8, 'V', 1.69],
];

$resultado = array_filter($bagunca, function($item) {
    $inteiros = array_filter($item, 'is_int'); // pega somente os valores inteiros
    $primeiroInteiro = array_pop($inteiros);
    return ($primeiroInteiro % 2) === 0; // se for par, entra no array_filter. se não, é ignorado
});

var_dump($resultado); // array(['K', 1.83, 0, 'Kiko'], ['derBrian', 8, 'V', 1.69])

Enfim, por hoje é só! Deu para entender as possibilidades de uso de um continue? Espero que sim! E se curtiu, comenta e compartilha! No próximo artigo vamos falar sobre o tão mencionado switch. Dá pra viver sem, mas é bom conhecer! Vai perder?

Inté!!