PHP Progress bar

O PHP não possui nativamente nada que permita monitorar o upload de um arquivo. Hoje no mercado existem duas opções disponíveis para fazer isso:

  1. Um patch no PHP, que modifica sua funcionalidade
  2. Uma extensão em PECL.

Como meu servidor não me permite fazer o patch no PHP e eu particularmente acho muito chato ter que ficar aplicando patches no PHP toda vez que eu atualizo sua versão e etc… Aqui vai um rápido post explicando como instalar e usar a extensão PECL.

Para instalar é necessário executar o comando:

pecl install uploadprogress

Exemplos podem ser achados aqui:

Para os preguiçosos ou aqueles que precisem de uma ajudinha extra….

Aqui vai:

Presumo que se você chegou aqui, você ja deve ter instalado o componente no seu servidor e devidamente restartado o Apache (se você não fez isso ainda, então vai lá… eu espero aqui).

Vou usar as páginas acima por preguiça de escrever meu próprio código, mas eu vou explicar o que elas estão fazendo e porque.

Vamos à descrição das páginas:

index.php – é o form e onde tudo acontece. Aqui temos o Javascript de upload e o formulário.

info.php – é usado para buscar informações sobre o upload atual e retornar isso ao script

server.php – é o que efetivamente processa o upload. Ele é o que salva o arquivo e para o script de monitoramento.
Bom agora que sabemos o que cada um faz, vamos explicar em detalhes:

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<?php
$id = md5(microtime() . rand());
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<script>
			var UP = function() {
				/* private variables */
				var ifr = null;
				var startTime = null;
				var infoUpdated = 0;
				var writeStatus = function(text,color) {
					var statDiv = document.getElementById("status");
					if (color == 1 ) {
						statDiv.style.backgroundColor = "green";
					} else if (color == 2 ) {
						statDiv.style.backgroundColor = "orange";
					} else if (color == 3 ) {
						statDiv.style.backgroundColor = "red";
					} else {
						statDiv.style.backgroundColor = "white";
					}
					statDiv.innerHTML = text;
				};
				return {
					start: function() {
						ifr = document.getElementById("ifr");
						startTime = new Date();
						infoUpdated = 0;
						this.requestInfo();
					},
			stop: function(files) {
				if (typeof files == 'undefined' || files) {
					var secs = (new Date() - startTime)/1000;
					var statusText = "Upload succeeded, it took " + secs + " seconds. <br/> ";
					if (infoUpdated > 0) {
						writeStatus(statusText + "You had " + infoUpdated + " updates from the progress meter, looks like it's working fine",1);
					} else {
						statusText += "BUT there were no progress meter updates<br/> ";
						if (secs < 5) {
							writeStatus(statusText + "Your upload was maybe too short, try with a bigger file or a slower connection",2);
						} else {
							writeStatus(statusText + "Your upload should have taken long enough to have an progress update. Maybe it really does not work...",3);
						}
					}
				} else {
					writeStatus('PHP did not report any uploaded file, maybe it was too large, try a smaller one (max. <?php echo ini_get('upload_max_filesize');?>)',3);
				}
				startTime = null;
			},
			requestInfo: function() {
				ifr.src="info.php?ID=<?php echo $id;?>&"+new Date();
			},
			updateInfo: function(percent, estimatedSeconds) {
				if (startTime) {
					if (percent) {
						infoUpdated++;
						writeStatus("Download started since " + (new Date() - startTime)/1000 + " seconds. " + Math.floor(percent * 100) + "% done, " + estimatedSeconds + " seconds to go");
					} else {
						writeStatus("Download started since " + (new Date() - startTime)/1000 + " seconds. No progress info yet");
					}
				window.setTimeout("UP.requestInfo()",500);
				}
			}
		}
	}()
	</script>
	<title>php5.2 uploadprogress Meter - Simple Demo</title>
</head>
<body>
	<form target="ifr2" action="server.php" enctype="multipart/form-data" method="post">
		<input value="<?php echo $id;?>" />
		<label>Select File:</label>
		<input /> (Max. File Size is <?php echo ini_get('upload_max_filesize');?>)<br />
		<label>Upload File:</label>
		<input value="Upload File" />
	</form>
 
	<div style="border: 1px black solid">Status</div>
	<div>The info during the upload will be displayed here:</div>
 
	<iframe src="info.php?ID=<?php echo $id;?>" width="500px" height="350px"></iframe>
 
	<div>The actual file upload happens here (and displays info, when it's finished):</div>
 
	<iframe width="500px" height="300px"></iframe>
 
</body>
</html>

Tá é um monte de código então vamos as partes simples primeiro:

O primeiro PHP:

$id = md5(microtime() . rand());

Aqui, basicamente eu seto o ID para um valor em tese único. É um random
seed bem básico. Você pode mudar isso pro que quiser, apenas garanta que
o ID vai ser algo meio que único.

O HTML:

<body>
	<form target="ifr2" action="server.php" enctype="multipart/form-data" method="post">
		<input value="<?php echo $id;?>" />
		<label>Select File:</label>
		<input /> (Max. File Size is <?php echo ini_get('upload_max_filesize');?>)<br />
		<label>Upload File:</label>
		<input value="Upload File" />
	</form>
 
	<div style="border: 1px black solid">Status</div>
	<div>The info during the upload will be displayed here:</div>
 
	<iframe src="info.php?ID=<?php echo $id;?>" width="500px" height="350px"></iframe>
 
	<div>The actual file upload happens here (and displays info, when it's finished):</div>
 
	<iframe width="500px" height="300px"></iframe>
 
</body>

Sem crise certo? Um form, com action pro server, multipart, post
e um target pro iframe2 (#ifr2). Um input hidden com um ID gerado pelo
PHP, um input file, um submit, alguns labels para sabermos o que é o que
visualmente, um div #status, um iframe #ifr, e um iframe #ifr2. Os
iframes são usados como target para o nosso script depois, mas eles
poderiam ser substituidos por chamadas ajax normais. Finalmente, o
script, onde mora toda a mágica do nosso brinquedo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
			var UP = function() {
				/* private variables */
				var ifr = null;
				var startTime = null;
				var infoUpdated = 0;
				var writeStatus = function(text,color) {
					var statDiv = document.getElementById("status");
					if (color == 1 ) {
						statDiv.style.backgroundColor = "green";
					} else if (color == 2 ) {
						statDiv.style.backgroundColor = "orange";
					} else if (color == 3 ) {
						statDiv.style.backgroundColor = "red";
					} else {
						statDiv.style.backgroundColor = "white";
					}
					statDiv.innerHTML = text;
				};
				return {
					start: function() {
						ifr = document.getElementById("ifr");
						startTime = new Date();
						infoUpdated = 0;
						this.requestInfo();
					},
			stop: function(files) {
				if (typeof files == 'undefined' || files) {
					var secs = (new Date() - startTime)/1000;
					var statusText = "Upload succeeded, it took " + secs + " seconds. <br/> ";
					if (infoUpdated > 0) {
						writeStatus(statusText + "You had " + infoUpdated + " updates from the progress meter, looks like it's working fine",1);
					} else {
						statusText += "BUT there were no progress meter updates<br/> ";
						if (secs < 5) {
							writeStatus(statusText + "Your upload was maybe too short, try with a bigger file or a slower connection",2);
						} else {
							writeStatus(statusText + "Your upload should have taken long enough to have an progress update. Maybe it really does not work...",3);
						}
					}
				} else {
					writeStatus('PHP did not report any uploaded file, maybe it was too large, try a smaller one (max. <?php echo ini_get('upload_max_filesize');?>)',3);
				}
				startTime = null;
			},
			requestInfo: function() {
				ifr.src="info.php?ID=<?php echo $id;?>&"+new Date();
			},
			updateInfo: function(percent, estimatedSeconds) {
				if (startTime) {
					if (percent) {
						infoUpdated++;
						writeStatus("Download started since " + (new Date() - startTime)/1000 + " seconds. " + Math.floor(percent * 100) + "% done, " + estimatedSeconds + " seconds to go");
					} else {
						writeStatus("Download started since " + (new Date() - startTime)/1000 + " seconds. No progress info yet");
					}
				window.setTimeout("UP.requestInfo()",500);
				}
			}
		}
	}()

Criamos um objeto Up que basicamente faz o seguinte: Quando é feito um submit,
rodamos a função start que inicializa um contador de tempo e roda a
função requestInfo. A função requestInfo, abre dentro do #ifr o php
info.php com o ID criado no topo da página. A página info.php gera um
script que chama a função updateInfo. Se o updateinfo, tiver um start,
nos atualizamos o div colocando que ja iniciou o upload mas ainda nao
temos info, se temos um percentual, atualizamos colocando quanto do
upload foi feito. Lembre-se que essas informações vem da página info.php

A função se chama novamente a cada meio segundo. Uma vez concluido o
upload (a página server.php acaba de carregar, é rodado outro script,
que para o relógio e conclui o upload. Vamos para uma rápida explicação
dos outros dois PHPs: info.php

<?php
$info = uploadprogress_get_info($_GET['ID']);
?>
<html>
	<head>
		<script>
		<?php
		if ($info !== null) {
			print "parent.UP.updateInfo(".$info['bytes_uploaded']/$info['bytes_total'].",".$info['est_sec'].")";
		} else {
			print "parent.UP.updateInfo()";
		}
		?>
		</script>
	</head>
	<body>
		<pre>
		<?php
		print date("c",time())."n";
		print $_GET['ID'] ."n";
		var_dump($info);
		?>


Lembre-se esta página acompanha o upload. No php, pegamos o
progresso do upload usando nosso módulo recém instalado, usando o ID
único criado.

$info = uploadprogress_get_info($_GET['ID']);

Depois, dentro de um bloco de script, rodamos o seguinte PHP:

if ($info !== null) {
	print "parent.UP.updateInfo(".$info['bytes_uploaded']/$info['bytes_total'].",".$info['est_sec'].")";
} else {
	print "parent.UP.updateInfo()";
}

que básicamente verifica se temos $info. Se nosso modulo nos retorna alguma informação, imprimimos na tela o updateInfo() com a porcentagem do upload concluída, e o tempo estimado de conclusão (cortesia do módulo instalado).

Caso não temos nenhuma info, apenas imprimimos o updateInfo() vazio.
No arquivo server.php:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<script>
		<?php
		if (count($_FILES) > 0) {
		echo "parent.UP.stop(true);";
		} else {
		echo "parent.UP.stop(false);";
		}
		?>
		</script>
		<title></title>
	</head>
 
	<body>
	File uploaded:
	<pre>
	<?php
	var_dump($_FILES);
	?>
	</body>
</html>

Basicamente concluímos o upload mostrando um VAR_DUMP do $_FILES.

Temos uma verificação que é o seguinte: Se temos files, paramos o script da página index, caso contrário, mantemos o script rodando.

Bom, é isso... acho que isso da pra um bom começo. Depois, quando estiver mais calmo, eu mostro essa mesma implementação de uma forma mais limpa, usando o jquery e chamadas de ajax, sem precisar desses parent.UP e afins...

Compartilhe:
  • Print
  • email
  • Add to favorites
  • Digg
  • StumbleUpon
  • Yahoo! Buzz
  • Google Bookmarks
  • FriendFeed
  • Slashdot
  • Tumblr
  • LinkedIn
  • del.icio.us
  • Google Buzz
  • Ping.fm
  • Facebook
  • Twitter

One Response to “PHP Progress bar”

  1. Wow, very informative article. Thanks a lot.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">

Back to top

Page optimized by WP Minify WordPress Plugin