← PHP para aplicações reais
11 / 11 · 50 MIN

CLI: falhas, opções e ciclo de vida

Transforma scripts em jobs com contratos claros para argumentos, limpeza, tentativas e resultados observáveis.

A linha de comandos é uma fronteira

Um job chamado por um scheduler recebe texto, não intenções. Define as opções aceites, os valores exigidos e a política para argumentos inesperados antes de produzir efeitos. Em getopt, uma opção sem valor pode existir com valor false; testa a presença da chave em vez da verdade do valor. A análise termina no primeiro argumento que não é opção, pelo que colocar um ficheiro antes de --dry-run pode impedir que a opção seja lida. Um programa que só aceita opções nomeadas deve rejeitar os argumentos restantes. Uma variável de ambiente com texto 0 também não está ausente: getenv devolve false quando não encontra a variável.

Separar falha, registo e código de saída

O scheduler decide o próximo passo a partir de um contrato de saída. Registar uma exceção e terminar com zero pode permitir que uma tarefa dependente avance apesar da falha. Faz a função principal devolver um código e termina o processo depois de ela concluir a limpeza. No fixture executado em PHP 8.4.4, exit dentro do try termina o processo sem executar o finally associado. Isso é uma observação do runtime testado, não uma promessa de execução em PHP 8.5. finally também não protege contra falha da máquina. Recursos que sobrevivem ao processo precisam de uma estratégia própria de recuperação e identificação do proprietário.

Handlers temporários têm de ser restaurados

Alguns avisos PHP não são exceções por defeito. Um handler pode converter classes escolhidas em ErrorException, mas essa política deve ser explícita e limitada ao bloco que a necessita. Guarda a fronteira com try e finally e usa restore_error_handler para repor a pilha anterior. Voltar a chamar set_error_handler com o handler antigo acrescenta uma entrada à pilha em vez de desfazer a instalação. Num worker prolongado, uma fuga desse estado pode afetar a tarefa seguinte. Não transformes todos os avisos em retries: um erro de entrada ou uma incompatibilidade de contrato pede correção. Regista a classe e o contexto sem incluir segredos nos logs.

Limites de repetição e estado por tarefa

Três tentativas totais significam a primeira execução e, no máximo, duas repetições. Distingue esse limite de três retries adicionais. Classifica as falhas transitórias elegíveis, define o prazo total e considera o que o scheduler já repete para evitar multiplicar tentativas entre camadas. Reinicia acumuladores, parâmetros e contexto por tarefa num processo prolongado. Para medir duração local, usa uma diferença de relógio monotónico, como hrtime, e converte as unidades declaradas; o valor absoluto não é um instante de calendário. Um flock local coordena participantes que respeitam o mesmo bloqueio e as garantias do sistema de ficheiros. Não o apresentes como eleição universal entre máquinas ou como substituto de idempotência.

Laboratório: uma opção e uma saída controlada

O programa abaixo aceita apenas --dry-run e não executa escritas de negócio. Com essa opção, deve imprimir dry-run seguido de cleanup e terminar com zero. Sem a opção, devolve código 2 depois de executar a limpeza. Com um argumento posicional antes da opção, rejeita a invocação antes de iniciar o trabalho. O subconjunto de sintaxe é deliberadamente pequeno: aplicações maiores podem usar um parser completo com um contrato documentado. O teste executa as três variantes num processo separado, porque exit terminaria o processo que o chamasse. Observa em conjunto stdout, stderr e código de saída; nenhuma dessas evidências isolada descreve todo o resultado.

Resumo para passagem à produção

Um runbook deve indicar uma invocação válida, o significado dos códigos de saída, o limite de tentativas e os sinais de recuperação. Inclui um exemplo de dry-run e uma entrada rejeitada para que o operador confirme o comportamento antes da janela. Explica quais os recursos locais libertados no fluxo controlado e quais precisam de reconciliação após interrupção. Se o job chama uma base de dados, liga o estado do scheduler ao resultado durável sem confundir confirmação perdida com falha garantida. Relaciona esta aula com streams, propriedade de recursos e transações. A passagem a produção exige ainda testar o runtime, extensões, permissões e scheduler reais, além destes exemplos locais.

<?php
declare(strict_types=1);
function main(array $args): int {
    // This laboratory deliberately accepts only this exact option syntax.
    foreach (array_slice($args, 1) as $arg) {
        if ($arg !== '--dry-run') {
            return 2;
        }
    }
    $options = getopt('', ['dry-run'], $rest);
    if ($options === false || $rest < count($args)) {
        return 2;
    }
    try {
        if (!array_key_exists('dry-run', $options)) {
            return 2;
        }
        echo 'dry-run';
        return 0;
    } finally {
        echo '|cleanup';
    }
}
exit(main($argv));
NA PRÁTICA

Uma opção colocada depois de um argumento inválido é rejeitada antes do trabalho, em vez de ser ignorada e provocar escrita.

Armadilhas comuns

Não usar verdade do valor para testar presença de uma flag, esconder falhas com código zero ou terminar antes da limpeza controlada.

Tópicos relacionados: Transações e falhas parciais em PDO · JSON, contratos e falhas explícitas · Streams, CSV e importações controladas

Leva esta ideia contigo

Um job fiável valida entradas, conserva o estado por tarefa e comunica um resultado compatível com os seus efeitos conhecidos.

Criar conta

Referência: PHP manual: function.getopt · PHP 8.5 reference; DR PHP 2026.3; new fixtures executed on PHP 8.4.4 / SQLite 3.51.2