A manipulação de arquivos é uma habilidade essencial para qualquer desenvolvedor PHP. Seja para ler configurações, gravar logs, processar dados de entrada ou gerar relatórios, o PHP oferece um conjunto robusto de funções para lidar com o sistema de arquivos. Nesta aula, vamos explorar desde as funções mais tradicionais, como fopen, fread e fwrite, até alternativas mais concisas como file_get_contents, e entender o conceito de streams, que é a base de toda a manipulação de arquivos no PHP. Além disso, veremos como lidar com erros de forma elegante, evitando que falhas de leitura ou escrita interrompam a aplicação de maneira abrupta.

Vamos começar com uma visão geral das funções que você usará no dia a dia e, em seguida, mergulharemos em exemplos práticos que ilustram cada conceito. Ao final, você estará apto a escolher a abordagem mais adequada para cada situação e a escrever código robusto e eficiente.

fopen, fread, fwrite

As funções fopen, fread e fwrite fazem parte do conjunto clássico de funções de arquivo do PHP, baseadas na API de arquivos da linguagem C. Elas oferecem controle fino sobre a leitura e escrita, permitindo que você gerencie o ponteiro de arquivo e leia ou escreva quantidades específicas de dados.

A função fopen abre um arquivo ou URL e retorna um recurso (resource) que será usado nas operações subsequentes. Ela recebe dois parâmetros: o caminho do arquivo e o modo de abertura. O modo define se você vai ler, escrever, acrescentar, etc. Alguns modos comuns são: 'r' (somente leitura, ponteiro no início), 'w' (somente escrita, truncando o arquivo se existir), 'a' (somente escrita, posicionando o ponteiro no final para acrescentar dados), 'r+' (leitura e escrita, ponteiro no início) e 'a+' (leitura e escrita, ponteiro no final). É importante lembrar que ao usar modos que escrevem, o arquivo é criado se não existir (exceto no modo 'x', que falha se o arquivo já existir).

Depois de aberto, podemos ler dados com fread, que recebe o recurso e o número de bytes a ler, retornando a string lida. Para ler o arquivo inteiro, você pode usar filesize() para obter o tamanho em bytes e ler exatamente esse número, mas cuidado com arquivos muito grandes que podem sobrecarregar a memória. Já a escrita é feita com fwrite, que recebe o recurso e a string a ser escrita, retornando o número de bytes escritos ou false em caso de erro.

// Abrir arquivo para escrita (trunca se existir)
$arquivo = fopen('exemplo.txt', 'w');
if ($arquivo) {
    $conteudo = "Olá, mundo!\n";
    fwrite($arquivo, $conteudo);
    // Escrever mais conteúdo
    fwrite($arquivo, "Segunda linha.\n");
    fclose($arquivo); // sempre feche o recurso
} else {
    echo "Não foi possível abrir o arquivo.";
}

// Abrir para leitura
$arquivo = fopen('exemplo.txt', 'r');
if ($arquivo) {
    $tamanho = filesize('exemplo.txt');
    $conteudo = fread($arquivo, $tamanho);
    echo $conteudo;
    fclose($arquivo);
}
?>

Uma boa prática é verificar se a abertura foi bem-sucedida antes de prosseguir, pois fopen retorna false em caso de erro. Além disso, sempre feche o arquivo com fclose() para liberar recursos, embora o PHP feche automaticamente ao final da execução do script, é uma boa prática explícita.

file_get_contents

Para simplificar a leitura de arquivos, o PHP oferece file_get_contents(), que lê todo o arquivo para uma string em uma única chamada. Essa função é ideal para arquivos pequenos e médios, pois é mais concisa e dispensa a necessidade de gerenciar o ponteiro. Ela também funciona com URLs, permitindo obter conteúdo de páginas web, desde que a configuração allow_url_fopen esteja habilitada.

O uso básico é simples: $conteudo = file_get_contents('caminho/do/arquivo.txt');. Mas a função também aceita um segundo parâmetro que permite usar contextos (veremos mais adiante) e um terceiro para especificar o deslocamento (offset) e o limite de leitura. Em versões mais recentes do PHP, você pode usar o parâmetro $offset e $length para ler apenas uma parte do arquivo, o que é útil para arquivos grandes.

Para escrita, existe a função complementar file_put_contents(), que grava uma string em um arquivo, criando-o se necessário. Ela também aceita flags como FILE_APPEND para adicionar conteúdo ao final em vez de sobrescrever.

// Ler um arquivo inteiro
$conteudo = file_get_contents('exemplo.txt');
echo $conteudo;

// Escrever em um arquivo (sobrescreve)
file_put_contents('novo.txt', "Conteúdo novo\n");

// Adicionar conteúdo ao final
file_put_contents('log.txt', "Linha de log\n", FILE_APPEND);

// Ler uma parte do arquivo (a partir do byte 5, 10 bytes)
$parte = file_get_contents('exemplo.txt', false, null, 5, 10);
echo $parte;
?>

Essas funções são mais fáceis de usar e menos propensas a erros, pois lidam automaticamente com a abertura e fechamento do arquivo. No entanto, para arquivos muito grandes, elas podem consumir muita memória, pois carregam todo o conteúdo em memória. Nesses casos, é preferível usar fopen e fread em um loop.

Streams

O conceito de streams é fundamental no PHP para unificar a manipulação de arquivos, redes e outros fluxos de dados. Um stream é um objeto que representa um fluxo de dados que pode ser lido ou escrito. O PHP fornece uma camada de abstração que permite acessar diferentes fontes (arquivos locais, URLs, memória, etc.) usando as mesmas funções de fopen, fread, etc. Por exemplo, você pode abrir uma URL com fopen('http://exemplo.com', 'r') e ler o conteúdo da mesma forma que um arquivo local.

Os streams são identificados por um esquema de URI, como file:// para arquivos locais, http:// para HTTP, php:// para fluxos especiais, entre outros. O prefixo php:// oferece fluxos internos como php://memory (armazenamento em memória), php://temp (armazenamento temporário), php://stdin, php://stdout e php://stderr. Esses fluxos são úteis para testes e para processamento de dados sem tocar o disco.

Além disso, você pode criar filtros de stream para modificar os dados enquanto são lidos ou escritos. Por exemplo, php://filter/read=string.toupper/resource=arquivo.txt leria o arquivo convertendo todo o conteúdo para maiúsculas. Para usar filtros de forma programática, você pode chamar stream_filter_append() em um recurso de stream.

// Abrir uma URL como stream
$stream = fopen('https://www.php.net', 'r');
if ($stream) {
    $conteudo = stream_get_contents($stream);
    echo substr($conteudo, 0, 100); // apenas os primeiros 100 caracteres
    fclose($stream);
}

// Usar php://memory para armazenar dados temporários
$stream = fopen('php://memory', 'r+');
fwrite($stream, "Dados em memória");
rewind($stream); // volta ao início
echo stream_get_contents($stream); // Dados em memória
fclose($stream);

// Aplicar filtro de stream
$stream = fopen('exemplo.txt', 'r');
stream_filter_append($stream, 'string.toupper');
echo stream_get_contents($stream); // conteúdo em maiúsculas
fclose($stream);
?>

Os streams são poderosos e permitem criar aplicações flexíveis, como um leitor de arquivos que pode ser usado tanto para arquivos locais quanto para URLs sem alterar o código. No entanto, para a maioria das tarefas simples, as funções de alto nível como file_get_contents são suficientes.

Tratamento de erros

Ao trabalhar com arquivos, muitos problemas podem ocorrer: arquivo não existe, permissões insuficientes, disco cheio, etc. O PHP oferece várias maneiras de lidar com esses erros. A abordagem mais clássica é verificar o retorno das funções. Por exemplo, fopen retorna false se não conseguir abrir o arquivo. Mas também é possível usar exceções ou a classe ErrorException para transformar avisos em exceções, tornando o código mais robusto.

Uma prática recomendada é usar o operador de controle de erro @ para suprimir avisos e depois verificar o resultado. Por exemplo, $arquivo = @fopen('nao_existe.txt', 'r'); retorna false sem emitir um aviso. No entanto, é melhor usar exceções, especialmente em aplicações modernas. Você pode criar um manipulador de erros que converta avisos em exceções usando set_error_handler(), ou usar a classe ErrorException.

Além disso, ao usar file_get_contents(), se ocorrer um erro, ela retorna false e emite um aviso. Para evitar que o aviso seja exibido ao usuário, você pode usar o @ ou configurar o error_reporting para não exibir avisos. Mas a melhor prática é verificar o retorno e lançar uma exceção manualmente.

// Exemplo de verificação de erro com fopen
$arquivo = @fopen('inexistente.txt', 'r');
if ($arquivo === false) {
    throw new Exception("Não foi possível abrir o arquivo.");
}

// Exemplo com file_get_contents e exceção
$conteudo = @file_get_contents('inexistente.txt');
if ($conteudo === false) {
    throw new RuntimeException("Falha ao ler o arquivo.");
}

// Usando set_error_handler para converter avisos em exceções
function error_to_exception($severidade, $mensagem, $arquivo, $linha) {
    throw new ErrorException($mensagem, 0, $severidade, $arquivo, $linha);
}
set_error_handler('error_to_exception');

try {
    $conteudo = file_get_contents('arquivo.txt');
} catch (ErrorException $e) {
    echo "Erro ao ler arquivo: " . $e->getMessage();
}
?>

Também é importante considerar o uso de file_exists() e is_readable() antes de tentar ler um arquivo, para evitar erros desnecessários. No entanto, essas verificações podem introduzir condições de corrida (o arquivo pode ser removido entre a verificação e a leitura), então é preferível tentar a operação e tratar o erro.

Boas práticas e observações finais

  • Escolha a função certa: Para leitura simples, use file_get_contents(); para escrita, file_put_contents(). Para arquivos grandes ou quando precisar de controle fino, use fopen() e fread()/fwrite().
  • Feche sempre os recursos: Use fclose() ou deixe o PHP fechar, mas é uma boa prática explícita, especialmente em scripts longos.
  • Trate erros adequadamente: Não ignore avisos; use exceções ou verifique retornos para garantir que seu código não falhe silenciosamente.
  • Tenha cuidado com permissões: Certifique-se de que o servidor web tenha permissão de leitura/escrita nos diretórios onde você opera.
  • Evite expor caminhos sensíveis: Nunca confie em dados do usuário para construir caminhos de arquivos sem validação, para prevenir ataques de path traversal.

Referências

Exercícios

  1. Escreva um script que lê o conteúdo de um arquivo chamado 'dados.txt' e exibe na tela. Use file_get_contents() e verifique se o arquivo existe antes de tentar ler.

    ✓ Resposta:
    if (file_exists('dados.txt')) {
        $conteudo = file_get_contents('dados.txt');
        echo $conteudo;
    } else {
        echo "Arquivo não encontrado.";
    }
    ?>
  2. Crie um script que grava a string "Olá, mundo!" em um arquivo chamado 'mensagem.txt' usando fopen e fwrite. Depois, leia o conteúdo e exiba.

    ✓ Resposta:
    $arquivo = fopen('mensagem.txt', 'w');
    if ($arquivo) {
        fwrite($arquivo, "Olá, mundo!");
        fclose($arquivo);
    } else {
        die("Erro ao abrir arquivo.");
    }
    
    $conteudo = file_get_contents('mensagem.txt');
    echo $conteudo;
    ?>
  3. Utilize a função file_put_contents() para adicionar uma nova linha ao final de um arquivo chamado 'log.txt' sem sobrescrever o conteúdo existente.

    ✓ Resposta:
    file_put_contents('log.txt', "Nova linha de log\n", FILE_APPEND);
    ?>
  4. Abra uma URL (ex.: https://www.php.net) com fopen, leia todo o conteúdo com stream_get_contents() e imprima os primeiros 200 caracteres.

    ✓ Resposta:
    $stream = fopen('https://www.php.net', 'r');
    if ($stream) {
        $conteudo = stream_get_contents($stream);
        echo substr($conteudo, 0, 200);
        fclose($stream);
    } else {
        echo "Falha ao abrir URL.";
    }
    ?>
  5. Escreva um script que tenta ler um arquivo que não existe. Use um manipulador de erros para converter o aviso em uma exceção e capture a exceção para exibir uma mensagem amigável.

    ✓ Resposta:
    function error_to_exception($severidade, $mensagem, $arquivo, $linha) {
        throw new ErrorException($mensagem, 0, $severidade, $arquivo, $linha);
    }
    set_error_handler('error_to_exception');
    
    try {
        $conteudo = file_get_contents('nao_existe.txt');
        echo $conteudo;
    } catch (ErrorException $e) {
        echo "Erro ao ler arquivo: " . $e->getMessage();
    }
    ?>