Manejar archivos en PHP.

Resumen.

Llegados a este punto ya tenemos un conocimiento asentado de los conceptos mas básicos y elementales de PHP, así que este capitulo lo vamos a dedicar a una de las tareas mas “peligrosas” para nuestro sitio Web. Manejar archivos no es solamente subir archivos a nuestro servidor, es mucho mas que eso, ya que PHP nos permite generar documentos de texto, archivos .pdf, archivos sitemap .xml…, por poner solo unos ejemplos.

Artículo.

Si hay una tarea que se hace absolutamente imprescindible para cualquier sitio Web es la posibilidad de subir archivos a nuestro sitio Web. Desde un foro para publicar nuestro avatar, hasta publicar unas fotos de nuestro viejo coche que queremos vender en una Web de anuncios clasificados, los posibilidades son casi infinitas. Y como no, los riesgos de que nos metan un virus, también.

Dicho esto, comenzamos, y lo haremos con lo mas obvio, el formulario, que es HTML puro.


<form method="POST" action="procesa_archivo.php" enctype="multipart/form-data">

<p>Archivo: <input type="file" name="foto" size="25"></p>

<input type="submit" value="Publicar">

</form>


Como podemos ver, el formulario es como otro cualquiera, puro HTML básico, pero hay dos particularidades, la primera el atributo enctype="multipart/form-data", si nuestro objetivo es subir un archivos, sea de la clase que sea, a nuestro servidor, tiene que estar presente. La segunda particularidad es el tipo de campo, type="file", que nos permitirá agregar archivos de uno en uno a nuestro servidor. Puntualizo lo de “de uno en uno” por que con una pequeña modificación este formulario podría enviar mas de un archivo, pero eso no nos ataña en este momento, no corramos antes de empezar a caminar.

Como podemos recordar de capítulos anteriores, los datos se pueden recibir mediante $_POST['campo'], pero para recibir un archivo precisaremos de $_FILES['archivo']. Como podemos ver, es prácticamente idéntico, solo cambia POST por FILES. Al igual que el método POST nos devolvía un array con todos los valores del formulario, el FILE nos devuelve un array con varias propiedades del archivo que vamos a publicar. Mas concretamente, el array $_FILES['foto'] nos puede arrojar un resultado como este:


Array
(
[name] => DSC02155.JPG
[type] => image/jpeg
[tmp_name] => C:\xampp\tmp\php8FD0.tmp
[error] => 0
[size] => 1362881
)



Como vemos el array contiene 5 elementos:

El nombre del archivo en nuestro equipo [name].
El tipo de archivo [type].
La ubicación temporal de nuestro archivo en el servidor [tmp_name].
Si se ha producido un error [error].
El tamaño de nuestro archivo en bytes [size].

De estos 5 elementos, el error es el que menos veo usada en diferentes publicaciones sobre este mismo asunto, y a mi modo de ver, es sumamente importante.

Dicho esto, y con estos datos, vamos a ver que tenemos que hacer para publicar un archivo, que por el array, podemos ver que ha sido una foto típica, la que sacamos cada día con nuestro móvil.

Como es obvio empezamos por recibir el archivo, lo cual lo haremos así:


$foto = $_FILES['foto'];

$nombre_archivo = $_FILES['foto']['name'];
$tipo_archivo = $_FILES['foto']['type'];
$vol_archivo = $_FILES['foto']['size'];
$temporal = $_FILES['foto']['tmp_name'];
$error = $_FILES['foto']['error'];


La variable $foto es el array que contiene todos los datos del archivo, seguidamente desgranamos este array para tener disponible cada elemento en una variable, que por su nombre, podemos saber a que hace referencia. Y como no, asignamos una ubicación donde almacenar nuestras fotos mediante la variable

$carpeta = 'mis_fotos/';

Tras esto, y lo primero primerísimo de todo es validar el tipo de archivo, esto es inexcusable. Aunque para esto vamos a tener que hacer unas cositas antes.

Lo primero que vamos ha hacer es asignar un valor máximo para el tamaño de los archivos que queremos que se puedan publicar, obviamente, el tamaño que determinemos estará en función del objetivo (no es lo mismo un avatar para un foro que fotos sobre nuestras vacaciones, obvio, no??). y lo haremos de la siguiente forma:


$carpeta = 'mis_fotos/';
$un_mega = 1024 * 1024; // 1 mega 1024*1024 Bytes
$vol_max = 1.5 * $un_mega;


Con el objetivo de clarificar al máximo posible de donde provienen los datos hemos creado una variable, $un_mega, y otra $vol_max. La primera hace referencia al número de bytes que tiene un megabyte (1 megabyte = 1024 kilobytes y 1 kilobyte son 1024 bytes). Y como queremos fijar el tamaño máximo de los archivos en 1.5 megabytes, en la variable $vol_max multiplicamos 1.5 * $un_mega.

El siguiente condicional


if ($vol_archivo <= $un_mega){
$kb = round($vol_archivo / 1024, 2);
$info_vol = "$kb Kb";
}else{
$mb = round($vol_archivo / $un_mega, 2);
$info_vol = "$mb MB";
}


solo tiene por objetivo aportar información al usuario, lo que hace es mostrar el volumen del archivo en kilobytes (Kb) si es menor que un megabyte, y si es mayor que 1 megabyte, entonces lo expresa en megabytes (MB). La función round(valor, decimales) toma un valor y lo expresa con un determinado número de decimales, 2 en este caso.

Y entrando ya en lo importante, vamos a determinar el tipo de archivos que queremos permitir que suban a nuestro servidor. Esto lo haremos mediante un array que exprese el tipo de archivo (capturado por la variable $tipo_archivo) y la extensión del archivo, que la precisaremos para renombrar ese archivo. El array será el siguiente:


$ar_tipos_ok = array(
'image/jpeg'=>'.jpg',
'image/png'=>'.png',
'image/gif'=>'.gif'
);
@$ext = $ar_tipos_ok[$tipo_archivo];


En este array podemos ver las extensiones de archivos de imagen que probablemente sean las mas utilizadas, si quisieras permitir otro tipo, solo tendrías que ver el valor que para ese tipo te muestra el array $fotos en el elemento [type] y agregarlo al array.

Lo siguiente que tenemos que hacer es determinar si el tipo de archivo enviado, contenido en la variable $tipo_archivo, esta dentro del array, y en función de ello, emitir una respuesta. Esto lo haremos de la siguiente manera:


$tipos_validos = array_key_exists($tipo_archivo, $ar_tipos_ok);
if(strlen($tipos_validos) > 0){
$tipo_ok = 1; // Tipo valido
}else{
$tipo_ok = 0; // Tipo NO valido
}


¿Y que hemos hecho?

Como puede observarse, en el array $ar_tipos_ok la clave o key es el tipo de archivo ($tipo_archivo), y lo que hacemos mediante la función array_key_exists es comprobar si el tipo de archivo recogido se encuentra dentro del array, si se encuentra, el tipo es válido, de lo contrario, no es válido. Pero esta función, array_key_exists, tiene una respuesta de tipo boleana (TRUE ó FALSE) lo que quiere decir, que si es TRUE, devuelve un valor numérico de key del array, pero si es falso, no emite respuesta en pantalla. Por tanto, lo que hacemos es contar el número de caracteres mediante la función strlen() que cuenta el número de caracteres. Así, solo si la respuesta es TRUE, su valor será mayor que 0 (cero). Cuando la respuesta es mayor que cero, la variable $tipo_ok toma valor 1, en caso contrario, toma cero.

Y por fin, llegados a este punto, ya tenemos los datos precisos para hacer una validación de archivos impecable. Ahora recurrimos a un condicional y si los datos son válidos, subimos el archivo al servidor, o simplemente, lo rechazamos e informamos al usuario que su archivo no es válido.


if($error == 0 AND $tipo_ok == 1 AND $vol_archivo <= $vol_max){
// El archivo es valido y se publica
}else{
// El archive no es valido, y se informa al usuario
}


1. Veamos el caso en que el archive es valido y se publica.

Para publicar un archivo en nuestro servidor y que este sea accesible lo primero que debe tener es un nombre compatible con los protocolos de Internet, es decir, no puede por ejemplo tener espacios en blanco, tildes u otros caracteres especiales (mi fotopláya.jpg NO es un nombre valido), esto es fundamental. Lo segundo, y esto ya es un criterio propio, el nombre del archivo debe aportar una información sobre el archivo, así, el nuevo nombre de nuestra foto podría hacerse de la siguiente forma:


$new_nombre = date('y').date('m').date('d').'_'.date('H').'-'.mt_rand(100,999).$ext;


La variable $new_nombre genera el nuevo nombre del archivo y se compone de lo siguiente: el año, el mes y el día expresado con dos dígitos y capturado mediante la función date(), un guión bajo de separación, seguido de un aleatorio generado mediante la función mt_rand(menor,mayor) y por ultimo e imprescindible, la extensión, extraída del array de archivos permitidos $ar_tipos_ok. De esta forma obtenemos un nombre de archivo como por ejemplo este 221024_23-871.jpg.

En el nombre del archivo podemos ver que se ha generado el 24 de octubre de 2022 a las 23 horas, y el 871, simplemente trata de diferenciar este, de otros archivos subidos a esa misma hora. Por supuesto, este no es el único criterio para nombrar archivos y ni siquiera tiene que ser válido para la mayoría de los casos, simplemente es un mero ejemplo de una nomenclatura que aporte información sobre el archivo.

Una vez tenemos el nombre, ya solo queda asignarlo al nuevo archivo, y lo hacemos de la siguiente forma:


rename($nombre_archivo, $new_nombre);


Esta función es muy simple, el nombre del archivo recibido del dispositivo del usuario, le asignamos un nuevo nombre.

Lo siguiente que hacemos es comprobar si tenemos el directorio o carpeta donde vamos a guardar los archivos, esto lo hacemos con la función is_dir(), si el directorio no existe, lo creamos con la función mkdir() y con la función chmod() le asignamos permisos de lectura, escritura y ejecución.


if(is_dir($carpeta)){
// El directorio existe, no hacemos nada
}else{
// El directorio NO existe, lo creamos
mkdir($carpeta);
chmod($carpeta, 0777);
}



Con esto, ya lo tenemos todo listo, ahora mediante la función move_uploaded_file() enviamos el archivo temporal almacenado en el servidor al directorio especificado en la variable $carpeta.


move_uploaded_file($temporal, $carpeta.$new_nombre);


Seguidamente, mostramos la imagen en pantalla con un sencillo HTML que contiene la ruta de la imagen en PHP.


echo "
<img src=\"$carpeta$new_nombre\" width=\"250\">
$new_nombre
";


Advertir que, la ante las comillas es una barra de escape para que esas comillas no sean interpretadas como parte del “echo”, en este caso, como HTML.

La línea


echo "$info_vol <br />";


nos da el volumen del archivo que se ha subido al servidor.

2. Caso en que el archivo no es válido.

Este caso es muy sencillo, simplemente informamos de la no subida del archivo y los motivo por que esto se ha producido.


echo "<p>Por favor, adjunte una foto valida.</p>
$info_vol |
Maximo ".round($vol_max / $un_mega, 2)." MB";


Por supuesto, se podría aportar mas información, como la relativa al tipo de archivo, y ahora esta en tu mano aportarla.

Y con todo esto, el código completo de recepción de archivos es el siguiente:


<?php

$foto = $_FILES['foto'];

$nombre_archivo = $_FILES['foto']['name'];
$tipo_archivo = $_FILES['foto']['type'];
$vol_archivo = $_FILES['foto']['size'];
$temporal = $_FILES['foto']['tmp_name'];
$error = $_FILES['foto']['error'];

$carpeta = 'mis_fotos/'; // Directorio que contendra los archivos

$un_mega = 1024 * 1024; // 1 mega 1024*1024 Bytes
$vol_max = 1.5 * $un_mega;

if ($vol_archivo <= $un_mega){
$kb = round($vol_archivo / 1024, 2);
$info_vol = "$kb Kb";
}else{
$mb = round($vol_archivo / $un_mega, 2);
$info_vol = "$mb MB";
}
// Array que contiene los tipos de archivos permitidos y su extension
$ar_tipos_ok = array(
'image/jpeg'=>'.jpg',
'image/png'=>'.png',
'image/gif'=>'.gif'
);
@$ext = $ar_tipos_ok[$tipo_archivo];

$tipos_validos = array_key_exists($tipo_archivo, $ar_tipos_ok);
if(strlen($tipos_validos) > 0){
$tipo_ok = 1; // Tipo valido
}else{
$tipo_ok = 0; // Tipo NO valido
}

if($error == 0 AND $tipo_ok == 1 AND $vol_archivo <= $vol_max){

$new_nombre = date('y').date('m').date('d').'_'.date('H').'-'.mt_rand(100,999).$ext;
@rename($nombre_archivo, $new_nombre);

// Comprobamos si el directorio existe, si NO existe, lo creamos
if(is_dir($carpeta)){
// El directorio existe, no hacemos nada
}else{
// El directorio NO existe, lo creamos
mkdir($carpeta);
chmod($carpeta, 0777);
}

// Enviamos nuestro archivo a la carpeta de destino
move_uploaded_file($temporal, $carpeta.$new_nombre);

// Mostramos el archivo desde su ubicacion
echo "
<img src=\"$carpeta$new_nombre\" width=\"250\">
$new_nombre
";
echo "$info_vol <br />";
}else{
echo "<p>Por favor, adjunte una foto valida.</p>
$info_vol |
Maximo ".round($vol_max / $un_mega, 2)." MB";
}

echo '<pre>';
print_r($foto);
echo '</pre>';
?>


Y con esto, finalizamos este curso de PHP, considero que este ultimo capitulo (a menos que se me ocurra otro), si bien trata un aspecto muy importante de cualquier sitio Web, debe ser considerado también como un ejercicio de todo lo aprendido en este curso, que si bien, todo es muy básico y elemental, estos conceptos tan básicos y elementales son fundamentales a la hora de adentrarse en este magnifico lenguaje de programación de alto nivel.
Tags: $_FILES || validación || renombrado || tipos || extensión

Comentarios.

Sin comentarios, publica el tuyo.