Upload

46. Classi per l'upload di file

In questo capitolo vedremo un esempio più complesso del precedente, ma che ci fornirà un'interfaccia più intuitiva e sicura per gestire l'upload dei file sui nostri server.

Per questo scopo ho creato due classi a cui vi consiglio più di applicare i concetti di ereditarietà e polimorfismo per creare poi le vostre classi per una libreria personalizzata più adeguata alle esigenze della vostra applicazione.

Prima di introdurre il codice della libreria è necessario sapere che esiste uno specifico tipo MIME per ogni tipo di file inviato.

Come abbiamo visto nel capitolo precedente, una volta inviato il file questo è accessibile attraverso l'array globale $_FILES[], in particolare con la chiave "type" è possibile accedere alla stringa che descrive che tipo di file è stato inviato.

Se "type" è uguale a "image/jpeg" abbiamo ricevuto una jpeg, se è uguale a "audio/x-mpeg-3" abbiamo ricevuto un file mp3 e così via ...
Una lista dettagliata dei tipi di file con i rispettivi MIME.

Iniziamo dalla classe "ErroreFile" per una gestione mirata delle eccezioni, che si appoggerà alle costanti globali degli errori di upload che abbiamo già visto nel capitolo precedente.

Nella libreria che segue ho definito inoltre altre costanti per gestire delle eccezioni non previste dal set di costanti globali.

Lo scopo principale della classe che segue è quello di fornire un set di messaggi d'errore tradotti in italiano, attraverso un array statico. Derivando da questa classe potrete quindi fornire dei messaggi in altre lingue o messaggi di errore aggiuntivi per gestire eccezioni particolari, personalizzate per le vostre applicazioni.

ErroreFile.php
<?php

	define("UPLOAD_ERR_MOVE", 7);
	define("UPLOAD_ERR_NO_FORM", 8);
	define("UPLOAD_ERR_NO_TYPE", 9);
	define("UPLOAD_ERR_UNKNOWN", 10);

	class ErroreFile extends Exception
	{
		private $errore;
		static $tipo = array(
			UPLOAD_ERR_OK => "File inviato con successo",
			UPLOAD_ERR_INI_SIZE => "Dimensione del file troppo grande",
			UPLOAD_ERR_FORM_SIZE => "Dimensione del file troppo grande",
			UPLOAD_ERR_PARTIAL => "File ricevuto parzialmente",
			UPLOAD_ERR_NO_FILE => "Nessun file ricevuto",
			UPLOAD_ERR_NO_TMP_DIR => "Cartella temporanea non trovata",
			UPLOAD_ERR_MOVE => "Impossibile archiviare il file",
			UPLOAD_ERR_NO_FORM => "Nessun file specificato",
			UPLOAD_ERR_NO_TYPE => "Formato file non consentito",
			UPLOAD_ERR_UNKNOWN => "Errore sconosciuto"

		);

		public function ErroreFile($err_no)
		{ $this->errore = (int) $err_no; }

		public function messaggio()
		{
			foreach (ErroreFile::$tipo as $chiave => $valore)
				if ($this->errore == $chiave)
					return $valore;

			return ErroreFile::$tipo[UPLOAD_ERR_UNKNOWN];
		}
	}

?>

Ho aggiunto 4 costanti per descrivere altri tipi di eccezioni che potrebbero verificarsi, facendo attenzione a non utilizzare i codici già presi dalle altre costanti predefinite, ossia 0, 1, 2, 3, 4 e 6 (non chiedetemi che fine ha fatto il 5).

Vediamo ora la classe File che si occuperà di gestire il file inviato e di controllare che sia compatibile con le nostre esigenze.


Classe File

A inizio sorgente dichiarerò anche qui un set di costanti globali, per non dover ricordare il tipo MIME dei file che consideriamo validi per la nostra applicazione. Per il mio esempio ho scelto questi :

  • GIF - "image/gif"
  • JPEG - "image/jpeg"
  • PNG - "image/png"
  • PSD - "image/psd"
  • BMP - "image/bmp"
  • TIFF - "image/tiff"
  • FLASH - "application/x-shockwave-flash"

Un'occhiata veloce agli attributi e ai metodi della classe File :

  • private $file - L'array che conterrà solo il file specificato
  • private $cartella - La cartella remota dove andrà memorizzato il file inviato
  • public function File() - Il costruttore della classe che prenderà in input il nome del file da gestire e la cartella di destinazione
  • public function tipo() - Metodo che prende un numero di parametri variabile, ossia tutti i tipi di file che consideriamo validi
  • public function dimensione() - La dimensione massima accettata per il file inviato. Prende due parametri, il primo la dimensione massima e il secondo l'unità di misura per valutare la dimensione, ossia "byte", "kb" o "mb" (default "byte")
  • public function sposta() - Sposta il file nella cartella di destinazione
  • public function immagine() - Restituisce la stringa già formattata in HTML per visualizzare l'immagine inviata
    Prima di usare verificare che il file sia un'immagine con il metodo tipo()
  • public function __get() - Restituisce il valore dell'attributo specificato
  • public function __toString() - Restituisce il nome originale del file
File.php
<?php

	require_once("ErroreFile.php");

	// MIME Types
	define("GIF", "image/gif");
	define("JPEG", "image/jpeg");
	define("PNG", "image/png");
	define("PSD", "image/psd");
	define("BMP", "image/bmp");
	define("TIFF", "image/tiff");
	define("FLASH", "application/x-shockwave-flash"); // Filmato Adobe Flash

	class File
	{
		private $file;
		private $cartella;

		public function File($nome, $cartella = "/uploads/")
		{
			if (isset($_FILES[$nome]))
			{
				$this->file = $_FILES[$nome];
				$this->cartella = $cartella;
			}
			else
				throw new ErroreFile(UPLOAD_ERR_NO_FORM);
		}

		public function tipo()
		{
			if (!func_num_args()) throw new ErroreFile(UPLOAD_ERR_NO_TYPE);

			$tipi = func_get_args();

			foreach ($tipi as $chiave => $valore)
				if ($this->file["type"] == $valore)
					return true;

			throw new ErroreFile(UPLOAD_ERR_NO_TYPE);
		}

		public function dimensione($dim, $unita = "byte")
		{
			if (strcasecmp($unita, "byte") == 0) $dim = (int) $dim;
			else if (strcasecmp($unita, "kb") == 0) $dim = (int) $dim * 1024;
			else if (strcasecmp($unita, "mb") == 0) $dim = (int) $dim * 1024 * 1024;
			else $dim = 0;

			if ($this->file["size"] > $dim) throw new ErroreFile(UPLOAD_ERR_FORM_SIZE);
		}

		public function sposta()
		{
			$ok = @move_uploaded_file($this->file["tmp_name"], $this->cartella . $this->file["name"]);

			if (!$ok) throw new ErroreFile(UPLOAD_ERR_MOVE);
		}

		public function immagine($dir, $alt = NULL)
		{
			$dimensioni = getimagesize($dir . $this->file["name"]);
			$dimensioni = $dimensioni[3]; // height="yyy" width="xxx"

			
			$imgtag = "<img src=\"$dir" . $this->file["name"] . "\" ";
			$imgtag .= "$dimensioni ";
			$imgtag .= (strlen($alt) ? "alt=\"$alt\"" : "") . " />"; // Imposta l'attributo ALT per l'immagine

			return $imgtag;
		}

		public function __get($attributo)
		{
			if (array_key_exists($attributo, $this->file))
				return $this->file[$attributo];

			return false;
		}

		public function __toString()
		{ return $this->file["name"]; }
	}

?>

Per l'esempio ho utilizzato alcune funzioni native che non avevo ancora spiegato, di seguito i nomi e la relativa documentazione :

A seguire la pagina upload.php dell'esempio precedente, riscritta per l'uso delle nuove classi.

upload.php

<?php

	require_once("File.php");

	try
	{
		$file = new File("immagine", "C:/Appserv/www/test/uploads/");
		$file->tipo(JPEG, PNG); // Accettate solo immagini in formato JPEG o PNG
		$file->dimensione(100, "kb"); // Solo immagini più piccole di 100Kb
		$file->sposta(); // Sposta il file inviato nella cartella di destinazione

		echo "Nome file : $file<br /><br />";
		echo $file->immagine("uploads/", "Immagine inviata"); // Visualizza l'immagine appena inviata
	}
	catch (ErroreFile $e)
	{ echo $e->messaggio(); }
	catch (Exception $e)
	{ echo $e->getMessage(); }

?>

Il risultato ottenuto inviando un'immagine che soddisfa i parametri dell'esempio.

45. Upload di file

L'invio del file avviene in modo analogo all'invio di dati classico che abbiamo già visto con i metodi POST e GET.

Bisogna sapere che l'invio di un qualsiasi dato corrisponde ad una determinata richiesta HTTP, ognuna per gestire un tipo di dato diverso, il che significa che se abbiamo una normale pagina HTML con all'interno 3 immagini, avremo 4 richieste HTTP da parte del browser come segue :

Richieste HTTP :

Content-Type: text/html
Content-Type: image/jpeg
Content-Type: image/jpeg
Content-Type: image/jpeg

Per questo motivo quando dobbiamo inviare un file sono necessari alcuni accorgimenti in più rispetto ad un normale dato, a partire da due principali modifiche nella form HTML di invio :

  • enctype - E' obbligatorio specificare questo attributo nella form HTML assegnandolo al valore "multipart/form-data"
  • file - E' necessario inserire un campo input del tipo file

Form HTML per l'invio di file

Vediamo la pagina HTML che si occuperà dell'invio del file :

<!-- invio_file.html -->
<html>
	<head>
		<title>Upload di file</title>

	</head>
	<body>
		<form enctype="multipart/form-data" action="upload.php" method="post">
			<input type="file" name="immagine" />

			<br />
			<input type="submit" value="Invia file" />
		</form>
	</body>

</html>

Come possiamo vedere nella pagina d'esempio soprastante, il codice è molto simile a quello delle form viste nei capitoli precedenti, con la presenza però di enctype e utilizzando file come tipo di input, che sarà trasformato dal browser in un campo vuoto con un bottone la cui etichetta è solitamente "Sfoglia", ma che viene automaticamente sostituita a seconda della lingua del browser senza bisogno di un nostro intervento.

Il file inviato sarà poi accessibile alla pagina inviata tramite l'array globale $_FILES[] con una lista di elementi predefinita :

  • $_FILES["name"] - Il nome del file inviato (es. "foto1.jpg")
  • $_FILES["type"] - Il tipo del file inviato in formato MIME (es. "image/jpeg")
  • $_FILES["tmp_name"] - Il nome del file temporaneo che risiede sul server (es. "/tmp/php8K45x")
    Una volta inviato il file, questo sarà memorizzato in una cartella temporanea del server, e sarà cancellato automaticamente a fine richiesta. Se quindi non gestite il file prima della fine dello script, perderete il file inviato.
  • $_FILES["error"] - Contiene un codice di errore corrispondente a un evento verificatosi durante l'invio del file
  • $_FILES["size"] - La dimensione in byte del file inviato

PHP ci mette a disposizione molte funzioni per gestire il file inviato, le due che ci torneranno più utili sono :


E' essenziale sapere inoltre i tipi di errore che possiamo gestire per poter creare uno script funzionale.

Come abbiamo visto prima, il codice di errore viene reso accessibile attraverso l'array globale $_FILES[] e il suo attributo "error", che conterrà un numero intero corrispondente all'evento verificato (Lista completa dei messaggi di errore).

Torniamo ora alla form HTML dell'esempio, che invierà il file (name="immagine") alla pagina upload.php utilizzando il metodo POST.


upload.php

Prima di costruire uno script più robusto e flessibile, vediamo uno script leggero che riceverà il file, lo sposterà in una cartella chiamata "uploads" e mostrerà infine l'immagine appena inviata :

<?php

	$immagine = $_FILES["immagine"];
	move_uploaded_file($immagine["tmp_name"], "C:/Appserv/www/test/uploads/" . $immagine["name"]);

?>

<img src="<?= "uploads/" . $immagine["name"] ?>" />

Ho assegnato l'elemento di $_FILES[] ad un'altra variabile solo per comodità, sono infatti equivalenti le due echo che seguono :

<?php

	$immagine = $_FILES["immagine"];

	echo "Il nome del file è inviato è " . $immagine["name"];
	echo "Il nome del file è inviato è " . $_FILES["immagine"]["name"];

?>

Lo script mostrato è fin troppo semplice e soggetto a molte complicazioni, non effettua infatti nessun controllo sulla dimensione nè sul tipo del file inviato, avremmo quindi potuto inviare un file di testo che lo script avrebbe comunque tentato di mostrarlo attraverso il tag html "img".

Nel prossimo capitolo vedremo invece un esempio più funzionale e robusto con la programmazione orientata agli oggetti.


Condividi contenuti