Premessa
Lo scopo della pillola è mostrare un modesto esempio su come sia possibile implementare una progress bar assieme ad altre informazioni tramite APC e l'utilizzo della libreria client jQuery.
Tuttavia è possibile implementare quanto descritto anche tramite JavaScript "nudo e crudo" o qualunque altra libreria più o meno nota.
Questa pillola non tratterà alcuna problematica diversa da quella specificata nel topic per cui se non conoscete affatto JavaScript, jQuery o PHP5 consiglio di approfondire ogni argomento in questo stesso sito (pillole/articoli/guide) prima di proseguire.

---FILMATO DEMO----------------------------------------
http://www.youtube.com/watch?v=aXZ3WYdB5sQ
--------------------------------------------------------------

APC - Alternative PHP Cache
Questa estensione, in particolare la direttiva rfc1867, è sfruttabile a partire da PHP 5.2 e superiore.
Il vero scopo di tale libreria è ben diverso da quello proposto in questa pillola ma allo stesso tempo la direttiva in esame, la rfc1867, è presente solo in APC.
Per installare APC potete seguire i suggerimenti presenti nel sito php.net sotto la voce installazione librerie PECL.
Per gli utenti PAMPA o per i più esperti con l'ambiente Windows (Apache e PHP installati manualmente) è sufficiente aggiungere almeno queste due linee all'interno del file php.ini
codice:
extension=php_apc.dll
apc.rfc1867 = On
sotto la voce Dynamic Extensions.
La direttiva rfc1867 è infatti disabilitata di default poichè il tracking di un upload non è una procedura threadsafe, pertanto consiglio di essere documentati a sufficienza a riguardo prima di scegliere di utilizzare questa procedura in produzione.


jQuery
Una delle tante librerie client disponibili, una delle più riuscite, se non altro per il successo dimostrato dall'utilizzo da parte di numerosi sviluppatori più o meno esperti.
Sebbene l'idea iniziale era quella di fare un plugin per jQuery ho preferito creare un file più semplice da anlizzare, eliminando problematiche di scope, riferimenti this ed altro ancora.
Il codice client proposto è comunque facilmente implementabile in un eventuale plugin nonchè totalmente compatibile con altre librerie, qualunque esse siano, purchè non "annientino" la variabile globale jQuery.


Upload e problematiche annesse
L'aggiunta di files sul server è una problematica comune affrontabile in tanti modi.
Uno dei più usabili ed accattivanti è sicuramente quello di poter mostrare al cliente/utente lo stato di upload del file.
Così facendo si ovvia al problema dello "stress da attesa", impegnando occhi e mente a seguire quanto ancora manchi assicurandolo virtualmente che qualcosa stia accadendo.
Il pensiero "ma quanto ci mette / a che punto è ?" viene soppiantato dall'eppur si muove e l'invidia suscitata da almeno un paio d'anni da parte del mondo ActionScript ha "causato" lo sviluppo di alternative differenti, basate sullo stesso ActionScript oppure su librerie server dedicate.


Upload, quale futuro ?
Sebbene io stesso stia dedicando in questo istante del tempo per aggiungere ad un normale form di invio file caratteristiche impensabili fino a pochi anni fa, l'unico modo realmente portabile ed affidabile per risolvere il problema è affidarsi al client.
Per creare una progress bar come quella che sto per mostrare è infatti necessario stressare più del necessario il server, richiamandolo molteplici volte e costringendolo a monitorare "variabili" che avrebbe volentieri ignorato prima dell'avvento Ajax.
L'unica tecnologia attualmente riconosciuta come molto valida, affidabile nonchè ampiamente compatibile e capace di spostare la problematica sul client piuttosto che sul protocollo HTTP e l'interazione col server, è il player di Adobe, tramite il quale si sono creati moduli portabili e di semplice implementazione come SWFUpload.
Ciò nonostante le prossime release di JavaScript dovrebbero permettere di utilizzare metodologie analoghe a quelle ActionScript ... per cui se avete pazienza aspettate ancora un pò prima di proseguire e scegliere la strada del polling client / server ...


La form per l'upload
Se state ancora leggendo siete dei testardi ... e siete quindi, in questo settore, i benvenuti.
La pagina per gestire l'upload è molto semplice:
codice:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
	<head>
		<title>PHP Upload with APC and jQuery</title>
		<meta http-equiv="content-type" content="text/html; charset=utf-8" />
		<script type="text/javascript" src="jquery-1.2.1.js"></script>
		<script type="text/javascript" src="apcquery.js"></script>
		<link rel="stylesheet" type="text/css" href="apcquery.css" />
		<style type="text/css">
		body {
			font-family: Verdana, Helvetica, sans-serif;
			font-size: 1em;
		}
		fieldset {
			border: 0;
		}
		fieldset legend {
			display: block;
			font-weight: bold;
			padding: 4px;
		}
		label {
			display: none;
		}
		input {
			border: 1px solid #DDD;
			background-color: #EEE;
			font-size: .8em;
		}
		div.apcquery {
			visibility: hidden;
		}
		</style>
	</head>
	<body>
		<div>
			<form enctype="multipart/form-data" method="post" action="APCQuery.class.php?redirect=index.html">
				<fieldset>
					<legend>Please choose a file</legend>
					<label for="user-file">your file</label>
					<input type="file" id="user-file" name="user-file" tabindex="1" />
					<input type="submit" value="Upload"  tabindex="2" />
					<div class="apcquery">
						<span class="info">Total</span><span class="total"></span>
						<span class="info">Sent</span><span class="loaded"></span>
						<span class="info">Rate</span><span class="rate"></span>
						<span class="percent"></span>
					</div>
				</fieldset>
			</form>
		</div>
	</body>
</html>
I files necessari sono la stessa libreria jQuery, in questo esempio la versione 1.2.1, il mio file JavaScript ed in fine il file CSS dedicato al mio file JavaScript.
Gli stili in linea con la pagina sono solo un esempio e possono essere modificati a piacere ma se volete far apparire il "contenitore di informazioni" vi consiglio di non cambiare l'ultima parte
codice:
div.apcquery {
	visibility: hidden;
}
Questo XHTML spero sia di facile comprensione, l'unico appunto da fare credo sia per l'action della form.
APCQuery.class.php è la classe PHP5 che si occuperà della gestione delle informazioni (mostrata poi assieme al resto) mentre la variabile $_GET di nome redirect serve, sempre alla classe APCQuery, a permettere che la form sia utilizzabile con o senza JavaScript.



La classe server
codice:
<?php
// (C) Andrea Giammarchi - 24/10/2007
class APCQuery{

	// proprietà privata, assegnata nel costruttore
	private	$output = '';
	
	// metodo pubblico statico, forza il browser ad ignorare eventuali versioni in cache della stessa pagina
	public static final function noCache(){
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
		header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
		header('Cache-Control: no-store, no-cache, must-revalidate');
		header('Cache-Control: post-check=0, pre-check=0', false);
		header('Pragma: no-cache');
	}
	
	// costruttore, richiede un parametro stringa con un file, in questo caso
	// quello che dovrà gestire l'upload e/o controlli e/o altro eventuale
	public final function __construct($requireFile){
	
		// se la richiesta è tramite Ajax ed esiste
		if(isset($_GET['APC_PK']))
			// si crea solo un output di tipo JSON
			$this->output = $this->createJSON();
		// se invece la richiesta è di tipo post, l'upload è appena terminato
		// si crea un output JavaScript al fine di semplificare le operazioni client
		elseif(isset($_POST['APC_UPLOAD_PROGRESS'])){
			// ma se il file per gestire l'upload chiude il programma
			// o fa altro è possibile cambiare il comportamento
			require $requireFile;
			// alrtrimenti si prosegue creando l'output JavaScript
			$this->output = $this->createScript();
		}
		// se non è stato richiesto niente ne in get ne in post, o meglio
		// non è presente il campo APC_UPLOAD_PROGRESS
		// ed è presente la variabile redirect, il client
		// non aveva JavaScript abilitato ...
		elseif(isset($_GET['redirect'])){
			// si fanno le operazioni di upload
			require $requireFile;
			// e si reindirizza il client alla pagina specificata
			header('Location: '.$_GET['redirect']);
		}
	}
	
	// restituisce il contenuto della variabile output
	// e non la variabile privata stessa
	// qualora si tenti di stamparare o convertire a stringa
	// un'istanza di questa classe
	public final function __toString(){
		return	$this->output;
	}
	
	// verifica tutte le voci APC_PK (progress_key) e per ognuna di queste crea l'informazione
	// per poi restituirla come stringa JSON
	protected final function createJSON(){
		$APC_UPLOAD_PROGRESS = is_array($_GET['APC_PK']) ? $_GET['APC_PK'] : array($_GET['APC_PK']);
		for($i = 0, $j = count($APC_UPLOAD_PROGRESS); $i < $j; $i++)
			$APC_UPLOAD_PROGRESS[$i] = apc_fetch('upload_'.$APC_UPLOAD_PROGRESS[$i]);
		return	json_encode($APC_UPLOAD_PROGRESS);
	}
	
	// assegna la variabile o l'array di variabli APC_UPLOAD_PROGRESS alla $_GET['APC_PK']
	// per poi restituire un tag capace di memorizzare una variabile facilmente raggiungibile dal client
	protected final function createScript(){
		$_GET['APC_PK'] = $_POST['APC_UPLOAD_PROGRESS'];
		return	'<script type="text/javascript">response='.$this->createJSON().';</script>';
	}
}

// un fake main per permettere a questo file di essere incluso altrove al fine di poter riutilizzare la classe
// APCQuery anche da altri programmi ... in poche parole un'emulazione in stile Python
if($_SERVER['DOCUMENT_ROOT'].$_SERVER['PHP_SELF'] === str_replace('\\', '/', __FILE__)){
	// headers per evitare il caching dei dati
	APCQuery::noCache();
	
	// uscita dal programma, exit stampa informazioni sotto forma di stringa
	// se viene specificato un parametro ... ergo in questo caso richiama 
	// in automatico il metodo toString dell'istanza
	exit(new APCQuery('upload.php'));
}
?>
La $_GET['APC_PK'], come la $_POST['APC_UPLOAD_PROGRESS'], è gestita dal client ... che verrà mostrato a breve


La pagina di upload
Questa pagina ha il compito di gestire le informazioni (files) che si vorrebbero upoadare.
Al suo interno è possibile verificare dimensione file ed altro ancora per far fallire o completare l'upload.
codice:
<?php
if(isset($_FILES['user-file']))
	@move_uploaded_file($_FILES['user-file']['tmp_name'], 'files/'.$_FILES['user-file']['name']);
?>

Il CSS client
Questo CSS è dedicato ai vari div con classe apcquery, dando per scontato che la loro struttura sia identica a quella mostrata nel file XHTML.
L'appunto degnodi nota è che a prescindere da colori, dimensioni, testo e tutto il resto, l'iframe con classe apcquery dovrebbe essere lasciato come è.
Il motivo è semplice, se si maschera l'iframe mettendolo in display: none oppure visibility: hidden, alcuni browsers (per lo più il solito Explorer) non lo accetteranno come target valido per una form.
In soldoni se si tenta di nascondere l'iframe il tutto potrebbe non funzionare.
Siccome in questo caso l'iframe serve solo come "canestro per buttare i files" (perdonate il pessimo paragone), tanto vale evitare di fargli cambiare il layout (position: absolute) e posizionarlo dove nessuno potrà mai vederlo (top e left negativi ... a meno 10000px).
codice:
div.apcquery {
	width: 280px;
	padding: 2px;
	margin-top: 8px;
	border: 4px solid #AAA;
	background-color: #FFF;
	color: #444;
	text-align: left;
	font-size: .8em;
}
div.apcquery .info,
div.apcquery .total,
div.apcquery .loaded,
div.apcquery .rate {
	display: block;
	float: left;
	overflow: hidden;
	margin-bottom: 2px;
}
div.apcquery .info {
	width: 60px;
}
div.apcquery .total,
div.apcquery .loaded,
div.apcquery .rate {
	text-align: right;
	width: 216px;
}
div.apcquery .percent {
	clear: left;
	display: block;
	background-color: #CCC;
	width: 100%;
	height: 16px;
}
iframe.apcquery {
	position: absolute;
	top: -10000px;
	left: -10000px;
}