Introdução: "Erro Fatal" e 2 gigabytes de logs
Imagine: você é um desenvolvedor, e um administrador de sistemas chega até você com um pendrive. Nele, há um arquivo de log do servidor web dos últimos seis meses. O tamanho? Modestos 2.5 gigabytes. "Preciso encontrar todos os endereços IP de onde houve tentativas de brute force", ele diz e vai tomar café.
Você abre a IDE e, mecanicamente, escreve:
$lines = file('huge_log.txt');foreach ($lines as $line) { // processando...}Aperta "Executar"... e vê uma tela branca no navegador, e nos logs de erro — Fatal Error: Allowed memory size exhausted. Familiar? Esta é a armadilha clássica em que quase todos os desenvolvedores PHP já caíram.
A pergunta "How to read a large file line by line?" no Stack Overflow acumulou centenas de milhares de visualizações por um bom motivo. Este é um daqueles problemas onde a solução "ingênua" mata o servidor, e a abordagem correta salva o projeto. Hoje, vamos analisar como ler arquivos enormes em PHP sem exceder o limite de memória e mantendo alto desempenho. Vamos lá!
Por que file() e file_get_contents() são o mal para grandes dados
Vamos logo desfazer o mito principal. As funções file() e file_get_contents() são funções gananciosas. Elas carregam o arquivo inteiro na memória RAM de uma só vez. Se o seu arquivo pesa 2 GB e o limite memory_limit no php.ini está definido para 128 MB (valor padrão para muitas hospedagens), você certamente receberá um erro.
Além disso, o PHP armazena strings em codificação, o que pode aumentar o consumo de memória em 2 a 4 vezes em relação ao tamanho do arquivo no disco. Ou seja, 2 GB de logs podem "consumir" de 6 a 8 GB de RAM!
Conclusão: se o tamanho do arquivo exceder 10-20% do seu memory_limit, esqueça o file(). Precisamos de uma abordagem de fluxo.
A maneira correta: fgets() e leitura por fluxo
O método mais confiável e eficiente é usar fopen() em conjunto com fgets(). Essas funções não leem o arquivo inteiro, mas uma linha de cada vez. Imagine que você está bebendo suco com um canudo — você não bebe o copo inteiro de um só gole, mas vai sugando aos poucos. A leitura por fluxo funciona da mesma forma.
Aqui está um exemplo básico, mas já funcional:
<?php$handle = fopen('huge_log.txt', 'r');if ($handle) { while (($line = fgets($handle)) !== false) { // Processa uma linha echo $line; } fclose($handle);}?>O que acontece aqui? fopen() abre o arquivo e retorna um ponteiro (resource). fgets() lê uma linha de cada vez até o caractere de nova linha (). A memória é alocada apenas para a linha atual, não para o arquivo inteiro. Após o processamento, a linha é destruída pelo coletor de lixo (ou sobrescrita).
Nuances importantes de desempenho
- Tamanho do buffer:
fgets()lê até 8192 bytes por padrão. Se as linhas forem mais longas, a função fará várias iterações. Para a grande maioria dos logs, isso é suficiente. - Verificação de false: Sempre use a comparação estrita
!== false. Por quê? Porque uma linha pode ser vazia ("") ou conter "0", ewhile ($line = fgets($handle))pode parar prematuramente. - Fechamento do arquivo: Sempre chame
fclose(). Sim, o PHP fará isso sozinho ao final do script, mas se você estiver processando o arquivo em um loop ou em um script em segundo plano, descritores não fechados são um vazamento de recursos.
Super-carregamento: Geradores (yield) para reutilização
O gerenciamento manual de fopen/fclose é bom, mas se você precisar ler diferentes arquivos em diferentes partes do projeto, o código começará a se repetir. É aí que entram os geradores — um dos recursos mais elegantes do PHP.
Um gerador permite criar uma função que