Controle do Progresso de UPLOAD no PHP

Artigo que apresenta os conceitos sobre a elaboração de um controle de processo de upload de arquivos em PHP.

Introdução

É comum que sistemas de informação possuam um mecanismo de upload para algum propósito. Normalmente para enviar uma foto do perfil, anexar documentos, etc. O problema é que alguns sistemas esperam receber arquivos relativamente grandes para um tráfego (quase) imediato na Internet e, por este motivo, é útil informar para o usuário sobre o progresso do upload dos arquivos que ele está enviando para o servidor.

Uma das "soluções" mais comuns era a exibição de uma imagem de carregamento (normalmente aquela rodinha girando ou uma barrinha se mexendo), que dava a ideia de que o sistema estava trabalhando e que o usuário precisava esperar. O problema dessa solução é que o usuário não era informado sobre quanto do arquivo foi enviado e, consequentemente, se faltava muito ou pouco para aguardar. Pensando nisso, foram implementadas alguns mecanismos alternativos, baseados em applet, flash e/ou iframes. Todos muito funcionais, mas normalmente pouco elegantes por utilizarem recursos não nativos do PHP.

Felizmente, no PHP 5.4 foi incluído um recurso para controlar o progresso das requisições de Upload. A versão 5.4 ainda está em fase beta, mas o manual do PHP já apresenta uma prévia de como será o funcionamento desse novo recurso: [Controle de Progresso de Upload]. O recurso prevê, inclusive, o cancelamento do upload durante o processo. Neste artigo veremos como este recurso funcionará.


Funcionamento

O funcionamento deste recurso normalmente se dará em conjunto com requisições Ajax. O processo será basicamente este:

  1. O cliente preenche/submete um formulário que tem campo de upload e um campo hidden (com nome reservado e valor aleatório, que chamarei de UploadID) indicando que aquele formulário terá o upload controlado. Suponha que os dados serão enviados para o arquivo "recebe.php".
  2. O script "recebe.php" verifica que o upload será controlado, registra isso no PHP e continua a execução.
  3. Enquanto isso, o navegador do cliente realiza requisições assíncronas do tipo POST passando o mesmo UploadID através de um campo com nome reservado. Suponha que seja para um arquivo "progresso.php".
  4. O servidor recebe as requisições assíncronas em paralelo, e o script "progresso.php" verifica que o campo de nome reservado foi informado, então preenche a variável $_SESSION[ Prefixo + UploadID ] com várias informações. O script pode analisar os dados desta variável e gerar um XML (ou pacote JSON ou algum outro mecanismo de transferência de dados) e encaminhá-lo como resposta à requisição assíncrona.
  5. A requisição assíncrona do navegador recebe a resposta do servidor com os dados do progresso e mostra isso de alguma forma para o usuário. Pode ser uma barra que cresce até 100%, um texto indicando qual é o progresso ou algo do tipo.

Os dados do progresso guardados em sessão são estes:

$_SESSION[$prefixo.$uploadid] = array(
 "start_time" => 1234567890,   // Time da requisição
 "content_length" => 57343257, // Tamanho do pacote POST
 "bytes_processed" => 453489,  // Quantidade de bytes recebida e processada
 "done" => false,              // Flag indicando que acabou (com sucesso ou não)
 "files" => array(             // Array com dados do andamento de cada arquivo de upload

  // Primeiro arquivo de upload
  0 => array(
   "field_name" => "file1",       // Name usado no input
   "name" => "foo.bar",           // Nome original do arquivo
   "tmp_name" => "/tmp/phpxxx",   // Nome do arquivo temporário no servidor
   "error" => 0,                  // Código do erro ocorrido com o upload do arquivo
   "done" => true,                // Flag indicando que o upload do arquivo acabou
   "start_time" => 1234567890,    // Time de quando o arquivo começou a ser carregado
   "bytes_processed" => 57343250, // Quantidade de bytes recebida e processada
  ),

  // Outros arquivos de upload com a mesma estrutura
  1 => ...
 )
);

O percentual concluído poderá ser calculado usando content_length e bytes_processed.


Configurações

Para tudo isso funcionar, é preciso configurar algumas diretivas do php.ini:

  • session.upload_progress.enabled - Indica se o controle de controle de upload estará ativo ou não.
  • session.upload_progress.cleanup - Indica se os dados do progresso devem ser apagados assim que todos arquivos forem lidos.
  • session.upload_progress.prefix - Prefixo usado nas chaves reservadas nas sessões.
  • session.upload_progress.name - Nome reservado para o campo de input hidden e para o campo das requisições assíncronas.
  • session.upload_progress.freq - Frequência com que os dados da sessão serão atualizados (em bytes ou em porcentagem).
  • session.upload_progress.min_freq - Frequência mínima com que os dados serão atualizados (em segundos).

Observação: para gerar o input hidden, ou recuperar os dados da sessão, será necessário usar a função ini_get para obter o valor das diretivas, conforme o exemplo abaixo:

$nome_reservado = ini_get('session.upload_progress.name');
$prefixo = ini_get('session.upload_progress.prefix');
$uploadid = $_POST[$nome_reservado];
$key = $prefixo.$uploadid;
echo $_SESSION[$key]['bytes_processed'];

4 comentários

Everton disse...

Excelente post. Sempre procurei soluções que pudessem substituir os preloaders do Flash e similares, mas concordo com o Alan, é um pouco trabalhoso de fazer funcionar. Seria possível postar um exemplo funcionando?
Abraços!

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

Olá, Alan e Everton

Como vocês pediram, criei um post apenas com os scripts de exemplo do novo recurso:
http://rubsphp.blogspot.com/2011/11/implementando-uma-barra-de-progresso-de.html

Abraço

Renan Mazo disse...

Caro Rubens,
Muito obrigado pela iniciativa de publicar essa enorme quantia de informações! Seu blog tem um conteúdo incrível.
Meus parabéns!