Criar arquivos com nomes únicos em diretórios

Artigo que mostra como utilizar o PHP para criar um arquivo com o nome único dentro de um diretório.

Em sistemas que envolvem upload de um número indefinido de arquivos, é possível que o sistema armazene o arquivo no próprio Banco de Dados ou que ele armazene em algum diretório reservado para arquivos submetidos.

O primeiro caso normalmente não exige que os arquivos tenham nomes únicos (exceto por alguma restrição do sistema). No segundo caso, independente da forma como os diretórios são organizados, é necessário garantir que um arquivo tenha um nome único dentro de um diretório.

A estratégia mais indicada para este segundo caso é criar diretórios diferentes para grupos de arquivos diferentes. Por exemplo, se o upload é feito por usuários que possuem uma identificação única (um ID), você pode usar este ID para criar um diretório exclusivo do usuário (usa-se a função do PHP mkdir para criá-lo). Dentro do diretório do usuário, para garantir que o nome do arquivo seja único, é possível adotar algumas estratégias:

  • Usar o próprio nome enviado pelo usuário, e não permitir submeter um arquivo cujo nome já consta no diretório.
  • Nome auto-incrementável (normalmente com algum prefixo seguido de um número que é controlado pelo programador).
  • Nome único (gerado por algum mecanismo que garanta o nome único do arquivo no diretório).

O nome auto-incrementável consiste em definir um padrão de nome (por exemplo: "foto_%d.jpg", onde %d é um número). No momento que uma nova foto é submetida, o sistema verifica qual é a última foto cadastrada (através de uma varredura do diretório e extração da informação sobre o último número utilizando expressão regular). Exemplo:

...

// Obter conteudo do diretorio do usuario
$arquivos = scandir($diretorio_usuario);

// Variavel que guarda o ultimo numero encontrado no diretorio
$ultimo = 0;

foreach ($arquivos as $arquivo) {
    if ($arquivo == '.' || $arquivo == '..') {
        continue;
    }

    // Se o nome do arquivo esta no padrao
    if (preg_match('/^foto_(\d+)\.jpg$/', $arquivo, $matches)) {

        // Extrair o numero do arquivo
        $numero = (int)$matches[1];

        // Guardar o maior numero
        $ultimo = max($numero, $ultimo);
    }
}

// Determinar o proximo numero a ser usado
$proximo = $ultimo + 1;

// Montar o nome do proximo arquivo submetido
$novo_arquivo = $diretorio_usuario.'/'.sprintf('foto_%d.jpg', $proximo);

// Salvar o arquivo em $novo_arquivo
move_uploaded_file($origem, $novo_arquivo);

Esta abordagem é relativamente segura se considerarmos que o mesmo usuário não pode acessar o sistema de forma concorrente. Logo, não existe risco do usuário ler o diretório enquanto um arquivo é criado ali, ocasionando uma "leitura suja".

A segunda estratégia é utilizar uma função que consiga criar um nome único em um diretório com segurança. Em PHP existe a função tempnam, que cria um arquivo com nome único em um diretório e retorna o nome do arquivo gerado. Esta abordagem é útil quando se tem apenas um diretório com arquivos de diferentes usuários.

Além das funções apresentadas, pode ser útil conhecer file_exists para determinar se um arquivo/diretório já existe.

Para casos mais específicos onde pretende-se criar um arquivo temporário, é possível usar a função tmpfile que cria um arquivo com nome único em algum diretório específico para arquivos temporários, e retorna um handle do arquivo (assim como a função fopen, por exemplo). Porém, ao fechar o arquivo (usar fclose sobre o handle), ele é automaticamente excluído. Este recurso pode ser usado para realizar operações em disco (por utilizarem muita memória, por exemplo).

2 comentários

anacris disse...

Muito legal o artigo, Rubens, você é sempre eficiente e muito didático!
Mas me conta: como o banco de dados incorporaria um pdf? permitiria fazer buscas dentro do arquivo pdf incorporado no banco, usando comandos do mysql?
bj
acris

Rubens Takiguti Ribeiro (autor do blog) disse...

Oi Ana,

Existe um tipo de campo em bancos de dados chamado "BLOB" (http://dev.mysql.com/doc/refman/5.1/en/blob.html), que consegue armazenar dados binários de arquivos. É similar a um campo TEXT, mas especial para guardar arquivos.

Guardando um PDF em BD, não é possível consultar dados diretamente dele pois as informações dentro de um PDF ficam codificadas num formato especial do PDF e, normalmente, compactadas também. Como o MySQL não tem recursos para descompactar estas informações, muito menos entender a estrutura do documento, não pode consultar dados de lá.

Uma alternativa para isso, é que antes de jogar o arquivo PDF no BD, seja usada uma ferramenta (por exemplo, em PHP) que extrai o texto do documento PDF e aí você armazena a informação nas duas formas no BD (em PDF e em texto puro). Assim podem ser feitas buscas usando o campo de texto.