Implementando uma Barra de Progresso de Upload com PHP

Artigo que apresenta o código PHP e JavaScript necessário para montar um controle de progresso de upload de arquivos, através de uma barra de progresso.

Introdução

No post passado, comentei que está previsto para o PHP 5.4.0 um novo recurso, que permitirá nativamente controlar o progresso do upload de arquivos. A pedidos, vou postar um exemplo de código que utiliza o novo recurso.

O principal código é em JavaScript. Você pode optar por usar jQuery ou algum framework próprio. Aqui no post, usei JavaScript/DOM puro, apenas para ilustrar.

Para montar a barra de progresso no HTML, utilizei a tag <progress> do HTML 5.

Arquivos


Arquivo index.php

<?php
session_start();

$upload_progress_name = ini_get('session.upload_progress.name');
$rand = md5(__FILE__);

echo <<<HTML
<!DOCTYPE html>
<html>
<head>
<title>Teste</title>
<meta name="php.upload_progress_name" content="{$upload_progress_name}" />
<script type="text/javascript" src="script.js"></script>
<link rel="stylesheet" type="text/css" href="estilos.css" media="screen" />
</head>
<body>
<form id="form_upload" method="post" action="recebe.php" enctype="multipart/form-data">
<input type="hidden" name="{$upload_progress_name}" value="{$rand}" />
<div>
<label for="arquivo">Arquivo:</label>
<input type="file" name="arquivo" id="arquivo" />
</div>
<input type="submit" value="Enviar" />
</form>
</body>
</html>
HTML;
exit(0);

Arquivo recebe.php

<?php

// Simulando um atraso
sleep(3);

session_start();

echo '<pre>';
var_dump($_POST);
echo '</pre>';

echo '<hr />';

echo '<pre>';
var_dump($_FILES);
echo '</pre>';

echo '<hr />';

echo '<pre>';
var_dump($_SESSION);
echo '</pre>';

exit(0);

Arquivo progresso.php

<?php
session_start();

$prefix = ini_get('session.upload_progress.prefix');
$sufix  = $_POST[ini_get('session.upload_progress.name')];
$key = $prefix.$sufix;

if (isset($_SESSION[$key])) {
    $content_length = $_SESSION[$key]['content_length'];
    $bytes_processed = $_SESSION[$key]['bytes_processed'];
    $done = $_SESSION[$key]['done'] ? 1 : 0;
    $percent = intval($bytes_processed * 100 / $content_length);
} else {
    $content_length = 0;
    $bytes_processed = 0;
    $done = 0;
    $percent = 0;
}

// Se terminou o upload: liberar memoria da sessao
if ($done) {
    unset($_SESSION[$key]);
}

header('Content-Type: text/xml; charset=UTF-8');
echo <<<XML
<?xml version="1.0" encoding="UTF-8" ?>
<progress>
<content_length>{$content_length}</content_length>
<bytes_processed>{$bytes_processed}</bytes_processed>
<percent>{$percent}</percent>
<done>{$done}</done>
</progress>
XML;
exit(0);

Arquivo script.js

if (window.addEventListener) {
    window.addEventListener("load", prepare_upload_forms, false);
}


/**
 * Funcao para preparar os formularios para o controle de progresso de upload.
 * @return void
 */
function prepare_upload_forms() {
    var upload_progress_name = get_upload_progress_name();
    if (!upload_progress_name) {
        return;
    }

    var len = document.forms.length;
    for (var i = 0; i < len; i++) {
        var form = document.forms.item(i);
        var input = get_upload_progress(form, upload_progress_name);
        if (input) {
            if (form.addEventListener) {
                form.addEventListener("submit", open_progress_bar, false);
            }
        }
    }
}

/**
 * Obtem o nome usado pelo PHP para controle de progresso
 * @return string
 */
function get_upload_progress_name() {
    var vt_meta = document.getElementsByTagName("meta");
    var len = vt_meta.length;
    for (var i = 0; i < len; i++) {
        var meta = vt_meta.item(i);
        if (meta.getAttribute("name") == "php.upload_progress_name") {
            return meta.getAttribute("content");
        }
    }
    return false;
}

/**
 * Obtem o input que define que o formulario tera o progresso de upload controlado.
 * @param FORM form Formulario.
 * @param string upload_progress_name Nome reservado para indicar controle de progresso pelo PHP.
 * @return INPUT || bool
 */
function get_upload_progress(form, upload_progress_name) {
    var vt_input = form.getElementsByTagName("input");
    var len = vt_input.length;
    for (var i = 0; i < len; i++) {
        var input = vt_input.item(i);
        if (input.getAttribute("name") == upload_progress_name) {
            return input;
        }
    }
    return false;
}

/**
 * Instancia um XML HTTP Request.
 * @return XMLHttpRequest || bool
 */
function get_xhr() {
    return new XMLHttpRequest();
}

/**
 * Abre a caixa de progresso do formulario de upload.
 */
function open_progress_bar(event) {    
    var div = document.createElement("div");
    div.className = "progress";
    {
        var label = document.createElement("span");
        label.appendChild(document.createTextNode("Progresso:"));

        var progress = document.createElement("progress");
        progress.id = "upload_progress";
        progress.setAttribute("value", "0");
        progress.setAttribute("max", "100");
        progress.appendChild(document.createTextNode("0/0 (0%)"));

        div.appendChild(label);
        div.appendChild(progress);
    }
    this.appendChild(div);

    var upload_progress_name = get_upload_progress_name();
    var input = get_upload_progress(this, upload_progress_name)
    var upload_id = input.getAttribute("value");

    window.setTimeout("update_progress('" + upload_id + "')", 100);
    return true;
}

/**
 * Realiza uma requisicao assincrona para consultar o andamento do Upload.
 * @param string upload_id
 */
function update_progress(upload_id) {
    var url = window.location.href;
    var vt = url.split('/');
    vt.pop();
    url = vt.join('/') + '/progresso.php';

    var upload_progress_name = get_upload_progress_name();
    var dados = encodeURIComponent(upload_progress_name) + "=" + encodeURIComponent(upload_id);

    var xhr = get_xhr();
    xhr.upload_id = upload_id;
    xhr.open("POST", url, false);
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    xhr.setRequestHeader("Content-Length", dados.length);
    xhr.setRequestHeader("Connection", "close");
    xhr.onreadystatechange = ready_state_change;
    xhr.send(dados);
}

/**
 * Evento quando muda o estado da requisicao Ajax
 */
function ready_state_change() {
    if (this.readyState == 4) {
        if (this.status == 200) {
            var xml = this.responseXML;
            var percent = xml.getElementsByTagName("percent").item(0).textContent;
            var content_length = xml.getElementsByTagName("content_length").item(0).textContent;
            var bytes_processed = xml.getElementsByTagName("bytes_processed").item(0).textContent;
            var done = xml.getElementsByTagName("done").item(0).textContent;

            var progress = document.getElementById("upload_progress");
            progress.setAttribute("value", percent);

            var texto_antigo = progress.firstChild
            var texto_novo = document.createTextNode(bytes_processed + "/" + content_length + " (" + percent + "%)");

            progress.replaceChild(texto_novo, texto_antigo);

            if (done == "0") {
                window.setTimeout("update_progress('" + this.upload_id+ "')", 1000);
            }
        }
    }
    return true;
}

Arquivo estilos.css

.progress {
  background-color: #303030;
  border: 1px outset #303030;
  color: #EEEEEE;
  display: block;
  width: 400px;
  padding: 5px;
  position: fixed;
  left: 0;
  top: 0;
}

Observações

Para este script funcionar, é preciso que o PHP esteja configurado adequadamente e, se você estiver rodando o script localmente, também vai precisar de um arquivo bem grande para ver a barra ser preenchida aos poucos. Eu testei com um arquivo de 1.7GB e usei as seguintes configurações no php.ini:

file_uploads = On
upload_max_filesize = 2000M
max_file_uploads = 20
post_max_size = 2000M
session.upload_progress.enabled = On
session.upload_progress.cleanup = Off
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
session.upload_progress.freq =  "1%"
session.upload_progress.min_freq = "1"

Desabilitei session.upload_progress.cleanup pois o próprio script de progresso limpa a sessão quando chega a 100% (veja o código acima). Você pode optar por fazer um garbage collector personalizado. Por exemplo, sempre apagar as posições da sessão que foram iniciadas a mais de 10 minutos, por exemplo.

37 comentários

Anônimo disse...

Comigo não funcionou...
Mas esse exemplo funcionou blz:
http://blog.thiagobelem.net/upload-de-arquivos-com-php/
Eu só ainda não sei como fazer o PROGRESSO, mas logo logo eu chego lá

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

Olá, Anônimo

Como eu coloquei no post, este recurso está disponível apenas para o PHP 5.4, que foi lançado oficialmente ontem. Você experimentou o código nesta versão?

O objetivo do post não era mostrar como fazer o upload de arquivos via PHP e sim mostrar como implementar uma barra de progresso do upload, que é algo complementar.

Até

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

Olá, Anônimo

Agradeço pela crítica, embora ela soe um tanto ofensiva. Caso ela tenha sido motivada por alguma informação incorreta no post, seria muito legal se você postasse isso nos comentários e eu corrigiria sem problemas.

Ou então, se tiver sugestões de como melhorar o blog, também aceito.

André Brum disse...

Olá Rubens,

Bacana seu blog. Parabéns!
Você acaso conhece alguma script de barra de progresso para inserção de dados no mysql via php?

Valeu!

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

Vivendo a Palavra: é possível criar uma barra de progresso sim. Eu não conheço algum pronto.

A idéia é a seguinte: você vai requisitar a ação via Ajax e montar uma barra de progresso dinamicamente com javascript. O servidor recebe a requisição e inicia o processo, guardando seu percentual concluído como 0%. Depois, você realiza requisições Ajax pelo cliente periodicamente para saber o percentual concluído e atualiza dinamicamente a barra de progresso. O servidor recebe estas requisições e devolve o progresso. Quando chegar a 100%, você sabe que terminou.

E o controle de tudo isso no servidor pode ser feito em BD ou em arquivo temporário, para cada requisição.

Talvez eu faça um post sobre isso no futuro.

André Brum disse...

Valeu Rubens,

Estou buscando uma solução para implementar em um projeto. Insiro muitos registros na hora da instalação e é interessante posicionar o usuário do andamento do processo.

Fica na Paz!

HDL disse...

Rubens eu ja tenho um script para upload de arquivos, mas eu custumo fazer uploads de arquivos bem pesados para o meu site,portanto eu gostaria de saber se há como implementar uma barra de progresso nele.

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

Olá, HDL.

Creio que seja perfeitamente possível, caso você já esteja utilizando o PHP 5.4.

Conforme mostrado do post, as mudanças necessárias num script convencional de upload não são conflitantes.

Para mais detalhes, veja o post que apresenta os conceitos desse novo recurso: http://rubsphp.blogspot.com.br/2011/11/controle-do-progresso-de-upload.html

Josue disse...

Rubens acho que com um diagrama ficaria mais fácil de compreender como realmente funciona o fluxo, seria bem legal um diagrama na postagem.

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

Olá, Josue.

No post anterior expliquei como funciona. Neste post, apenas coloquei a implementação.

http://rubsphp.blogspot.com.br/2011/11/controle-do-progresso-de-upload.html

Anônimo disse...

Olá Rubens,

eu realizei todo o fluxo, configurei o php. Mas no script

$key = ini_get("session.upload_progress.prefix") . $_POST[ini_get("session.upload_progress.name")];
var_dump($_SESSION[$key]);

ele me retorna null, não sei o que fazer! Você pode me ajudar?

Igor.

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

Olá, Igor.

Certifique-se de que esteja utilizando o PHP 5.4 ou superior.

Se estiver, dê um dump na $_SESSION para ver se está guardando o progresso em algum índice.

Anônimo disse...

Oi Rubens,

é superior ao 5.4, não está guardando nada na $_SESSION

Igor.

Anônimo disse...

O valor retornado da SESSION é null, vc sabe o que é? Pode me ajudar.

Obrigado,

Igor.

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

Então confira se a diretiva de configuração session.upload_progress.enabled esteja habilitada e que você está gerando o campo hidden no formulário com o nome correto.

A documentação oficial está no link: http://php.net/manual/en/session.upload-progress.php

Dê uma olhada se seguiu tudo corretamente.

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

Olá, tudo sobre tecnologia.

Neste exemplo, coloquei a barra de progresso em um DIV fixo no canto superior direito, com a largura de 400px.

Para ajustar, basta editar o arquivo CSS ao seu gosto.

anderson disse...

Brother, é o seguinte.
Sou novo nesse ramo de PHP e JavaScript, mais consigo entender algumas coisas básica, me deparei com um problema... " Criei um formulário de Upload, envia os arquivos para a pasta especificada, só que quero que me avise por e-mail que eu recebi um arquivo no FTP entende, só que não consegui."
Me ajuda ai.

By: Anderson - Bahia

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

Olá, Anderson
Para enviar um e-mail, você pode usar a função "mail" do PHP.
http://php.net/manual/en/function.mail.php

Ou pode usar uma classe que tenha recursos adicionais, como enviar e-mail com anexo, etc. Por exemplo: phpmailer

Para isso, você também precisa configurar o servidor de envio de e-mail (smtp).

anderson disse...

Vlw, mais axo que vc não entendeu.
é o Seguinte. Fiz esse Código Upload em PHP.

Código

$baixar = 'ftp/';

$baixar_arquivo = $baixar . $_FILES['arquivo']['name'];


if (move_uploaded_file($_FILES['arquivo']['tmp_name'], $baixar_arquivo)){

echo "Arquivo Enviado";}

else {echo "Arquivo não enviado";

FIM

E agora preciso implementar o código para enviar o email de aviso que recebir um arquivo no meu FTP.
Ajuda ai.

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

Olá, Anderson

Acho que entendi o que você quer: enviar um e-mail automaticamente quando fizer o upload. Sugeri duas formas: com a função mail (do próprio PHP) ou baixando alguma biblioteca que faça esse serviço, como a classe phpmailer.

Você só precisa estudar as duas formas e usar uma delas dentro de seu "if". Já coloquei o link da função "mail", que explica como usá-la.

Para usar o phpmailer, vou te passar o link do blog do thiago belem, que tem um post sobre o assunto:
http://blog.thiagobelem.net/enviar-e-mails-pelo-php-usando-o-phpmailer/

Novamente, recomendo que leia sobre servidor de envio de e-mail (dê uma pesquisada sobre smtp, postfix, etc).

Anônimo disse...

Olá, muito bom o seu post, é o seguinte eu sou um pouco leigo no assunto, estou aprendendo, tenho um site que se faz upload, não consigo emplementar uma barra de progresso enquanto o arquivo do usuário é enviado para o servidor, são todos esses códigos que vc colocou mesmo ou só parte dele? Agradeço!!!

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

Olá, Anônimo

Infelizmente precisa de todos estes arquivos. Basta copiar e colar cada um com o respectivo nome. Mas lembre-se que precisa ser no PHP versão 5.4 ou posterior. Para saber qual você está usando, basta fazer um script que exiba o valor da constante PHP_VERSION:

echo PHP_VERSION;

Anônimo disse...

Rubens, bom dia. Estou tentando implementar uma rotina da barra de progresso com banco de dados postgress e não esta dando certo, você teria alguma coisa desse jeito, uso o banco de dados para enviar os dados de quem enviou o arquivo e algumas outras informações.

Grato,

Dário

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

Olá, Dário

O controle sobre a barra de progresso independe do recurso que você usa para armazenar os arquivos. Não faz diferença se você guarda em PostgreSQL, MySQL, ou apenas copia os arquivos para um outro diretório. O controle de progresso de upload diz respeito à quantidade de bytes que foram transferidos dos arquivos da máquina do cliente (navegador) para o servidor.

Sugiro que, inicialmente, experimente copiar o conteúdo dos arquivos mostrados neste artigo, ajustar as configurações do PHP e testar para ver se funciona. Caso funcione, basta adaptá-los para a sua realidade, ou seja, alterar principalmente o arquivo "recebe.php" para salvar os dados onde você deseja.

Anônimo disse...

Olá quando testo o código no "xampp" aparece um erro de sintaxe -> Parse error: syntax error, unexpected 'var' (T_VAR)
exatamente na linha contendo -> var upload_progress_name = get_upload_progress_name();
saberia oque pode ser agradeço a ajuda abraço!!!

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

Olá, Anônimo

O arquivo que contém esse trecho de código é JavaScript e não PHP. E essa mensagem que você mostrou é um erro de sintaxe de código PHP. Portanto, você deve tá tentando executar um JavaScript com PHP, que não dá certo. Talvez você salvou o arquivo com extensão ".php" ou então tenha colocado o trecho de código junto com o código PHP, mas são coisas separadas.

Anônimo disse...

Obrigado pela ajuda Rubens você estava absolutamente certo. Como ainda estou aprendendo a programar cometo esses erros bobos, caso tenha alguma dica pra que eu possa melhorar, na faculdade eu só aprendi a manipular as sintaxes fazendo exercícios nunca fiz projetos ou nada do tipo agora que comecei a fazer um estágio que percebo que é bem diferente. Meu chefe pediu pra eu fazer uma barra de upload pro enviar arquivos pro banco de dados. Consegui graças ao seu Blog mais uma vez agradeço pela ajuda!

Anônimo disse...

Amigo gostei do seu código eu ainda estou aprendendo sobre PHP, se puder me ajudar com algumas uma dúvidas, eu gostaria de saber qual parte do código específica para qual pasta esta sendo enviada os arquivos upados e sem tem a possibilidade de selecionar mais de um arquivo para upload se tem em qual parte do código ele entraria. Também gostaria de saber se os três códigos em PHP(index, recebe, e progresso) podem estar no mesmo documento. Grato!

Unknown disse...

Antes de mais nada, PHP é algo novo para mim, então surgiu uma dúvida (desculpe caso seja algo tolo).
Salvei todas as páginas (conforme artigo), no lugar de sleep(3) na página recebe.php, inseri o meu código de upload do arquivo, porém a barra de progresso fica estática.
Sabe o que pode estar acontecendo? Creio que o erro esteja no arquivo progresso.php.
Agradeço desde já.

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

Olá, Rafael
Além dos arquivos do artigo, você também precisa configurar o PHP adequadamente (veja no final do artigo).

Provavelmente sua diretiva file_uploads não está ativa.

Eugênio Márcio disse...

boa tarde, gostei muito do seu código, porém não aparece a contagem do percentual, exibe sim uma barra horizontal, que já é alguma coisa, porém, estática, sem andamento de carregamento.