Trabalhando com arquivos: leitura, escrita e permissões
1. Fundamentos da manipulação de arquivos em PHP
1.1. O sistema de arquivos no PHP: funções nativas vs. wrappers de fluxo
O PHP oferece duas abordagens principais para manipular arquivos: funções nativas do sistema de arquivos e wrappers de fluxo (streams). As funções nativas como fopen, fread e fwrite operam diretamente no sistema de arquivos local, enquanto os wrappers de stream permitem acessar recursos como memória, compressão ou até URLs remotas de forma transparente.
// Função nativa
$handle = fopen('arquivo.txt', 'r');
$conteudo = fread($handle, 1024);
fclose($handle);
// Wrapper de stream
$stream = fopen('php://memory', 'r+');
fwrite($stream, 'dados temporários');
rewind($stream);
echo stream_get_contents($stream);
1.2. Modos de abertura de arquivos (fopen)
O segundo parâmetro de fopen define o modo de acesso. Os modos mais comuns são:
'r'— leitura apenas, ponteiro no início'r+'— leitura e escrita, ponteiro no início'w'— escrita apenas, cria ou sobrescreve'w+'— leitura e escrita, cria ou sobrescreve'a'— escrita apenas, append (ponteiro no final)'a+'— leitura e escrita, append'x'— escrita exclusiva, falha se o arquivo existir'b'— modo binário (combinado com outros, ex.:'rb')
// Leitura simples
$arquivo = fopen('dados.txt', 'r');
// Escrita com sobrescrita
$arquivo = fopen('novo.txt', 'w');
// Append (adicionar ao final)
$arquivo = fopen('log.txt', 'a');
// Modo binário para arquivos não-texto
$imagem = fopen('foto.jpg', 'rb');
1.3. Verificação de existência e estado do arquivo
Antes de qualquer operação, é prudente verificar se o arquivo existe e se as permissões são adequadas.
$caminho = '/tmp/meu_arquivo.txt';
if (file_exists($caminho)) {
if (is_file($caminho)) {
if (is_readable($caminho)) {
echo "Arquivo existe e pode ser lido.";
}
if (is_writable($caminho)) {
echo "Arquivo existe e pode ser escrito.";
}
}
} else {
echo "Arquivo não encontrado.";
}
2. Leitura de arquivos: estratégias e boas práticas
2.1. Leitura completa: file_get_contents, file e readfile
Para arquivos pequenos (até alguns MB), a leitura completa é a abordagem mais simples e eficiente.
// Lê todo o conteúdo como string
$conteudo = file_get_contents('config.php');
// Lê como array de linhas
$linhas = file('usuarios.csv');
foreach ($linhas as $linha) {
echo htmlspecialchars($linha) . "<br>";
}
// Envia o conteúdo diretamente para a saída (útil para downloads)
readfile('relatorio.pdf');
2.2. Leitura linha a linha com fgets e fgetcsv para arquivos grandes
Arquivos grandes (logs, CSVs enormes) devem ser processados linha a linha para evitar consumo excessivo de memória.
$handle = fopen('logs_servidor.txt', 'r');
if ($handle) {
while (($linha = fgets($handle)) !== false) {
// Processa cada linha
if (str_contains($linha, 'ERROR')) {
echo $linha;
}
}
fclose($handle);
}
// Para arquivos CSV
$csv = fopen('clientes.csv', 'r');
while (($dados = fgetcsv($csv, 1000, ',')) !== false) {
echo "Nome: {$dados[0]}, Email: {$dados[1]}<br>";
}
fclose($csv);
2.3. Leitura em partes com fread e controle de buffer
Para arquivos binários ou quando você precisa controlar exatamente quantos bytes ler.
$handle = fopen('video.mp4', 'rb');
$bufferSize = 8192; // 8KB
while (!feof($handle)) {
$chunk = fread($handle, $bufferSize);
// Processa o chunk (ex.: enviar para download)
echo $chunk;
ob_flush();
flush();
}
fclose($handle);
3. Escrita e criação de arquivos
3.1. Escrita simples: file_put_contents e fwrite
file_put_contents é a função mais direta para escrever arquivos pequenos.
// Escreve (ou sobrescreve) um arquivo
file_put_contents('saida.txt', "Conteúdo do arquivo\n");
// Com flags: FILE_APPEND para adicionar ao final
file_put_contents('log.txt', "Novo log entry\n", FILE_APPEND);
// Usando fwrite para mais controle
$handle = fopen('personalizado.txt', 'w');
fwrite($handle, "Linha 1\n");
fwrite($handle, "Linha 2\n");
fclose($handle);
3.2. Escrita incremental e append com bloqueio de arquivo (flock)
Em ambientes concorrentes, use flock para evitar corrupção de dados.
$handle = fopen('contador.txt', 'c+');
if (flock($handle, LOCK_EX)) { // Bloqueio exclusivo
$contador = (int) fread($handle, 1024);
$contador++;
ftruncate($handle, 0);
rewind($handle);
fwrite($handle, (string) $contador);
flock($handle, LOCK_UN); // Libera bloqueio
}
fclose($handle);
3.3. Criação de diretórios e arquivos temporários
// Criar diretórios recursivamente
mkdir('/tmp/app/logs/2024', 0755, true);
// Arquivo temporário com nome único
$tmp = tmpfile();
fwrite($tmp, 'dados temporários');
$meta = stream_get_meta_data($tmp);
echo "Arquivo temporário: " . $meta['uri'];
// Ou obter caminho do diretório temporário
$caminho = sys_get_temp_dir() . '/meu_arquivo_' . uniqid() . '.tmp';
file_put_contents($caminho, 'conteúdo');
4. Manipulação avançada com ponteiros e streams
4.1. Controle de ponteiro: fseek, ftell, rewind
$handle = fopen('arquivo.txt', 'r+');
echo "Posição atual: " . ftell($handle) . "\n"; // 0
fseek($handle, 10); // Move para o byte 10
echo "Nova posição: " . ftell($handle) . "\n";
rewind($handle); // Volta ao início
echo "Após rewind: " . ftell($handle) . "\n"; // 0
fclose($handle);
4.2. Trabalhando com wrappers de stream
Os wrappers php://memory e php://temp são úteis para processar dados sem criar arquivos físicos.
// Usando memória para dados temporários
$stream = fopen('php://memory', 'r+');
fwrite($stream, serialize(['nome' => 'João', 'idade' => 30]));
rewind($stream);
$dados = unserialize(stream_get_contents($stream));
fclose($stream);
// php://temp usa memória até um limite, depois usa disco
$stream = fopen('php://temp/maxmemory:1048576', 'r+');
// Aplicando filtros (ex.: compressão)
stream_filter_append($stream, 'zlib.deflate', STREAM_FILTER_WRITE);
fwrite($stream, 'dados a serem comprimidos');
4.3. Leitura e escrita de arquivos remotos via wrappers HTTP/FTP
// Ler conteúdo de uma URL (requer allow_url_fopen ativo)
$conteudo = file_get_contents('https://api.exemplo.com/dados.json');
// Upload via FTP
$ftp = fopen('ftp://usuario:senha@servidor/pasta/arquivo.txt', 'w');
fwrite($ftp, "conteúdo do arquivo remoto");
fclose($ftp);
5. Gerenciamento de permissões e metadados
5.1. Alteração de permissões com chmod
// Modo octal (0755 = rwxr-xr-x)
chmod('/var/www/html/config.php', 0644);
// Verificar permissões atuais
$perms = fileperms('arquivo.txt');
if ($perms & 0x004) {
echo "Proprietário pode ler";
}
5.2. Propriedade de arquivos: chown, chgrp
// Alterar proprietário (requer privilégios de root)
chown('arquivo.txt', 'www-data');
chgrp('arquivo.txt', 'www-data');
// Em ambientes compartilhados, essas funções podem falhar
5.3. Metadados: fileatime, filemtime, filesize e stat
$caminho = 'documento.pdf';
echo "Último acesso: " . date('d/m/Y H:i:s', fileatime($caminho)) . "\n";
echo "Última modificação: " . date('d/m/Y H:i:s', filemtime($caminho)) . "\n";
echo "Tamanho: " . filesize($caminho) . " bytes\n";
// Informações completas com stat
$info = stat($caminho);
echo "Inode: " . $info['ino'] . "\n";
echo "Permissões: " . decoct($info['mode']) . "\n";
6. Tratamento de erros e exceções na manipulação de arquivos
6.1. Uso de try-catch com exceções
function lerArquivoSeguro(string $caminho): string {
if (!file_exists($caminho)) {
throw new \RuntimeException("Arquivo não encontrado: $caminho");
}
$conteudo = @file_get_contents($caminho);
if ($conteudo === false) {
throw new \RuntimeException("Erro ao ler o arquivo: $caminho");
}
return $conteudo;
}
try {
$dados = lerArquivoSeguro('/etc/config.php');
} catch (\RuntimeException $e) {
error_log("Falha na leitura: " . $e->getMessage());
// Fallback para valores padrão
$dados = '{}';
}
6.2. Verificações defensivas antes de operações de I/O
function escreverSeguro(string $caminho, string $conteudo): bool {
$dir = dirname($caminho);
// Verifica se o diretório existe e é gravável
if (!is_dir($dir)) {
if (!mkdir($dir, 0755, true)) {
return false;
}
}
if (!is_writable($dir)) {
return false;
}
// Escrita atômica: escreve em arquivo temporário e renomeia
$tmp = tempnam($dir, 'tmp_');
if (file_put_contents($tmp, $conteudo) === false) {
return false;
}
return rename($tmp, $caminho);
}
6.3. Logging de erros e fallback
function processarUpload(array $arquivo): ?string {
try {
if ($arquivo['error'] !== UPLOAD_ERR_OK) {
throw new \RuntimeException("Erro no upload: código {$arquivo['error']}");
}
$destino = '/uploads/' . basename($arquivo['name']);
if (!move_uploaded_file($arquivo['tmp_name'], $destino)) {
throw new \RuntimeException("Falha ao mover arquivo");
}
return $destino;
} catch (\RuntimeException $e) {
error_log("Upload falhou: " . $e->getMessage());
return null;
}
}
7. Casos práticos e segurança
7.1. Upload seguro de arquivos
function uploadSeguro(array $arquivo): string {
$extensoesPermitidas = ['jpg', 'png', 'pdf', 'txt'];
$tamanhoMaximo = 5 * 1024 * 1024; // 5MB
$extensao = strtolower(pathinfo($arquivo['name'], PATHINFO_EXTENSION));
if (!in_array($extensao, $extensoesPermitidas)) {
throw new \InvalidArgumentException("Extensão não permitida: $extensao");
}
if ($arquivo['size'] > $tamanhoMaximo) {
throw new \InvalidArgumentException("Arquivo muito grande");
}
// Validar tipo MIME real
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $arquivo['tmp_name']);
finfo_close($finfo);
if (!in_array($mime, ['image/jpeg', 'image/png', 'application/pdf', 'text/plain'])) {
throw new \InvalidArgumentException("Tipo MIME inválido: $mime");
}
// Gerar nome único para evitar colisões
$nomeUnico = uniqid() . '_' . bin2hex(random_bytes(8)) . '.' . $extensao;
$destino = __DIR__ . '/uploads/' . $nomeUnico;
if (!move_uploaded_file($arquivo['tmp_name'], $destino)) {
throw new \RuntimeException("Falha ao salvar arquivo");
}
return $destino;
}
7.2. Prevenção de path traversal e injeção de caminhos
function caminhoSeguro(string $base, string $caminhoUsuario): string {
// Remove qualquer componente de diretório pai
$caminhoLimpo = basename($caminhoUsuario);
// Resolve caminho absoluto
$caminhoReal = realpath($base . '/' . $caminhoLimpo);
// Verifica se está dentro do diretório base
$baseReal = realpath($base);
if ($caminhoReal === false || strpos($caminhoReal, $baseReal) !== 0) {
throw new \InvalidArgumentException("Caminho inválido ou fora do diretório permitido");
}
return $caminhoReal;
}
// Uso
try {
$arquivo = caminhoSeguro('/var/www/uploads', $_GET['arquivo']);
readfile($arquivo);
} catch (\InvalidArgumentException $e) {
http_response_code(403);
echo "Acesso negado";
}
7.3. Limpeza de arquivos temporários e boas práticas de concorrência
function processarLoteComLock(string $arquivo, callable $processador): void {
$handle = fopen($arquivo, 'c+');
if (!flock($handle, LOCK_EX)) {
throw new \RuntimeException("Não foi possível obter bloqueio");
}
try {
$conteudo = stream_get_contents($handle);
$resultado = $processador($conteudo);
ftruncate($handle, 0);
rewind($handle);
fwrite($handle, $resultado);
} finally {
flock($handle, LOCK_UN);
fclose($handle);
}
}
// Limpeza automática de arquivos temporários
register_shutdown_function(function () {
$tmpDir = sys_get_temp_dir();
$padrao = $tmpDir . '/tmp_*';
foreach (glob($padrao) as $arquivo) {
if (filemtime($arquivo) < time() - 3600) { // Mais de 1 hora
unlink($arquivo);
}
}
});
Referências
- PHP Manual: Filesystem Functions — Documentação oficial completa de todas as funções de sistema de arquivos no PHP.
- PHP Manual: Stream Wrappers — Lista completa de wrappers de stream suportados pelo PHP, incluindo
php://memory,php://tempe wrappers HTTP/FTP. - PHP The Right Way: File Handling — Guia de boas práticas para manipulação de arquivos em PHP, com exemplos de segurança e eficiência.
- [OWASP: File Upload Cheat Sheet](https://cheats
atsheetseries/AJAX_Security_Cheat_Sheet) — Referência essencial para implementar upload seguro de arquivos e prevenir vulnerabilidades comuns.
- PHP Security Guide: Filesystem Security — Aborda riscos de path traversal, permissões inadequadas e outras ameaças relacionadas ao sistema de arquivos.
Conclusão
A manipulação de arquivos em PHP é uma habilidade fundamental para qualquer desenvolvedor web. Desde operações simples como leitura e escrita até tarefas complexas como upload seguro e gerenciamento de concorrência, dominar as funções nativas e os wrappers de stream permite construir aplicações robustas e seguras.
Ao longo deste artigo, exploramos:
- Fundamentos: modos de abertura, verificação de existência e estado dos arquivos.
- Leitura: estratégias para arquivos pequenos e grandes, com foco em desempenho e uso de memória.
- Escrita: criação incremental, append, bloqueio de arquivo e manipulação de diretórios.
- Streams e ponteiros: controle fino sobre o fluxo de dados e uso de wrappers especiais.
- Permissões e metadados: alteração de permissões, propriedade e consulta de informações do arquivo.
- Tratamento de erros: uso de exceções, verificações defensivas e logging para operações seguras.
- Segurança prática: upload seguro, prevenção de path traversal e limpeza de arquivos temporários.
Lembre-se sempre de aplicar verificações defensivas, validar entradas do usuário e utilizar bloqueios de arquivo em operações concorrentes. Com essas práticas, você estará preparado para lidar com qualquer desafio de I/O no ecossistema PHP.