La subida de archivos permite recibir en el servidor un fichero enviado mediante un formulario HTTP.
Subir archivos implica dos partes: el formulario HTML que envía el archivo y el script PHP que lo recibe, lo valida y lo mueve a su lugar definitivo.
El formulario HTML (el requisito olvidado)
Para subir archivos, la etiqueta <form> debe tener dos atributos obligatorios. Si olvidas uno, el archivo nunca llegará a PHP.
method="POST": Los archivos no viajan por GET.enctype="multipart/form-data": Indica al navegador que construya una petición capaz de incluir archivos.
<form action="upload.php" method="POST" enctype="multipart/form-data">
<label>Selecciona una imagen:</label>
<input type="file" name="fichero_usuario">
<button type="submit">Subir</button>
</form>La superglobal $_FILES
Cuando PHP recibe el archivo, no lo pone en $_POST. Lo pone en $_FILES. Es un array bidimensional con 5 datos clave por cada archivo subido:
Si tu input se llama name="fichero_usuario", tendrás:
| Índice | Descripción | Importancia |
|---|---|---|
$_FILES['fichero_usuario']['name'] | El nombre original (ej: foto.jpg). | Peligroso: El usuario puede manipularlo. |
$_FILES['fichero_usuario']['type'] | El tipo de archivo (ej: image/jpeg). | Peligroso: El navegador puede mentir. |
$_FILES['fichero_usuario']['tmp_name'] | Dónde lo guardó PHP temporalmente. | Es la ruta que usaremos para validarlo y moverlo. |
$_FILES['fichero_usuario']['error'] | Código de error (0 = Éxito). | Debes verificar que sea 0. |
$_FILES['fichero_usuario']['size'] | Tamaño en bytes. | Úsalo para validar límites. |
El proceso de seguridad (paso a paso)
Nunca muevas el archivo directamente. Sigue este checklist de seguridad:
Verificar Errores: Comprueba si ['error'] === UPLOAD_ERR_OK.
Validar Tamaño: No permitas que te llenen el disco duro.
Validar Tipo (MIME): Asegúrate de que es una imagen real y no un script .php disfrazado.
Renombrar el archivo: nunca guardes el archivo con el nombre original.
- Riesgo 1: Sobreescritura (dos usuarios suben
foto.jpg). - Riesgo 2: Hackeo (un usuario sube
hack.phpy luego intenta ejecutarlo). - Solución: Genera un nombre aleatorio con
random_bytes()y asigna la extensión a partir del MIME verificado.
Ejemplo completo y seguro
Este script implementa todas las barreras de seguridad mencionadas.
// Configuración
$directorioSubida = 'uploads/';
$maxSize = 2 * 1024 * 1024; // 2 MB
// Asegurarse de que se envió el archivo
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES['fichero_usuario'])) {
$archivo = $_FILES['fichero_usuario'];
// 1. Verificar errores de subida
if ($archivo['error'] !== UPLOAD_ERR_OK) {
die("Error al subir el archivo.");
}
// 2. Validar tamaño
if ($archivo['size'] > $maxSize) {
die("Error: El archivo es demasiado grande (Máx 2MB).");
}
// 3. Validar tipo de archivo (Seguridad Real)
// No confíes en $archivo['type'], usa finfo para ver el contenido real
$finfo = new finfo(FILEINFO_MIME_TYPE);
$tipoMime = $finfo->file($archivo['tmp_name']);
$formatosPermitidos = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'application/pdf' => 'pdf',
];
if (!isset($formatosPermitidos[$tipoMime])) {
die("Error: Formato de archivo no permitido.");
}
// 4. Generar nombre y extensión desde datos de confianza
$extension = $formatosPermitidos[$tipoMime];
$nombreNuevo = bin2hex(random_bytes(16)) . '.' . $extension;
$rutaDestino = $directorioSubida . $nombreNuevo;
// 5. Mover del temporal al definitivo
if (move_uploaded_file($archivo['tmp_name'], $rutaDestino)) {
echo "¡Éxito! Archivo guardado como: " . $nombreNuevo;
} else {
echo "Error al mover el archivo.";
}
}Guarda las subidas fuera del directorio público siempre que sea posible. Si tienen que ser públicas, configura el servidor para impedir la ejecución de scripts en esa carpeta y sirve los archivos con las cabeceras adecuadas.
Si intentas subir un archivo grande y falla sin error, revisa tu archivo php.ini. Las directivas upload_max_filesize y post_max_size suelen venir limitadas a 2MB por defecto.