Subida de archivos en PHP
Aprende a gestionar la subida de archivos en PHP con $_FILES y move_uploaded_file() de forma segura y eficiente.
La subida de archivos es un requisito habitual en el desarrollo web. Ya sea una foto de perfil, un documento PDF o una importación CSV, permitir que los usuarios envíen archivos a tu servidor es una función central de la mayoría de las aplicaciones. En PHP esto se gestiona con la función move_uploaded_file() junto con el array superglobal $_FILES.
Este capítulo cubre el flujo completo de subida: configurar el formulario HTML, leer los metadatos del archivo subido desde $_FILES, validarlo de forma segura y moverlo a su destino final. También se explican los códigos de error de subida y las opciones de configuración de PHP que controlan los límites de subida.
Cómo funciona la subida de archivos en PHP
La subida de un archivo ocurre en tres etapas:
- El navegador envía el archivo en una solicitud POST de tipo
multipart/form-data. - PHP recibe el archivo y lo escribe en una ubicación temporal en disco, y expone sus detalles en el array
$_FILES. - Tu script valida el archivo y lo mueve desde la ubicación temporal a una permanente con
move_uploaded_file().
El archivo temporal se elimina automáticamente al finalizar la solicitud si no lo mueves, por lo que debes procesarlo durante la misma solicitud.
El superglobal $_FILES
Cuando se sube un archivo, la información sobre él se almacena en el array superglobal $_FILES. Para un campo de formulario llamado userfile, el array contiene las siguientes claves:
$_FILES['userfile']['name']- El nombre original del archivo subido.$_FILES['userfile']['type']- El tipo MIME del archivo subido.$_FILES['userfile']['size']- El tamaño del archivo subido en bytes.$_FILES['userfile']['tmp_name']- La ubicación temporal del archivo subido en el servidor.$_FILES['userfile']['error']- Un código de error que indica si hubo algún problema durante la subida (véanse los códigos de error a continuación).
Paso 1: El formulario HTML
El atributo enctype del formulario debe establecerse en multipart/form-data, y el método debe ser POST. Sin multipart/form-data, el navegador envía solo el nombre del archivo, no su contenido, y $_FILES estará vacío.
<form action="upload.php" method="POST" enctype="multipart/form-data">
<input type="file" name="userfile">
<input type="submit" value="Upload">
</form>Paso 2: Validar la subida
Nunca confíes en un archivo subido. Antes de moverlo, comprueba tres cosas: que la subida se realizó correctamente, que el tamaño está dentro de tu límite y que el archivo es realmente del tipo que esperas.
Comprobar el código de error
Inspecciona siempre $_FILES['userfile']['error'] primero. PHP define constantes con nombre para los posibles valores:
| Constante | Valor | Significado |
|---|---|---|
UPLOAD_ERR_OK | 0 | Sin error, el archivo se subió correctamente. |
UPLOAD_ERR_INI_SIZE | 1 | El archivo supera upload_max_filesize en php.ini. |
UPLOAD_ERR_FORM_SIZE | 2 | El archivo supera el campo MAX_FILE_SIZE del formulario. |
UPLOAD_ERR_PARTIAL | 3 | El archivo solo se subió parcialmente. |
UPLOAD_ERR_NO_FILE | 4 | No se subió ningún archivo. |
UPLOAD_ERR_NO_TMP_DIR | 6 | Falta una carpeta temporal. |
UPLOAD_ERR_CANT_WRITE | 7 | No se pudo escribir el archivo en disco. |
Validar tipo y tamaño de forma segura
No confíes en $_FILES['userfile']['type']. Ese valor lo proporciona el navegador y un atacante puede falsificarlo trivialmente. En su lugar, detecta el tipo MIME real a partir del contenido del archivo con la extensión finfo, y aplica tú mismo un límite de tamaño:
$file = $_FILES['userfile'];
// 1. Did the upload succeed?
if ($file['error'] !== UPLOAD_ERR_OK) {
exit("Upload failed with error code " . $file['error']);
}
// 2. Enforce a maximum size (2 MB here).
$maxBytes = 2 * 1024 * 1024;
if ($file['size'] > $maxBytes) {
exit("File is too large.");
}
// 3. Detect the real MIME type, not the client-supplied one.
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
$allowed = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
];
if (!isset($allowed[$mime])) {
exit("Only JPEG, PNG, and GIF images are allowed.");
}Paso 3: Mover el archivo a su destino final
Una vez validado el archivo, muévelo con move_uploaded_file(). Acepta dos argumentos: la ruta temporal ($_FILES['userfile']['tmp_name']) y la ruta de destino. Usar esta función en lugar de copy() o rename() es importante — verifica que el archivo fue una subida HTTP genuina, lo que impide que un atacante engañe a tu script para mover un archivo arbitrario del servidor.
Genera el nombre final del archivo tú mismo en lugar de confiar en el nombre original. Esto evita ataques de traversal de directorios (un nombre como ../../config.php) y la sobreescritura de archivos existentes:
$targetDir = "uploads/";
// Build a safe, unique file name; never trust the client's name.
$extension = $allowed[$mime];
$safeName = bin2hex(random_bytes(8)) . "." . $extension;
$targetFile = $targetDir . $safeName;
if (move_uploaded_file($file['tmp_name'], $targetFile)) {
echo "The file was uploaded as " . $safeName;
} else {
echo "There was an error saving the file.";
}Para más detalles sobre esta función y su complemento is_uploaded_file(), consulta los capítulos de referencia move_uploaded_file() e is_uploaded_file().
Configuración que afecta a las subidas
Algunas directivas de php.ini limitan silenciosamente lo que tu script puede recibir. Si las subidas de archivos grandes fallan aunque tu código sea correcto, comprueba estas:
file_uploads— debe estar enOnpara que las subidas funcionen.upload_max_filesize— el tamaño máximo individual de archivo que PHP aceptará (por defecto2M).post_max_size— el tamaño máximo total del cuerpo POST; debe ser mayor queupload_max_filesize.max_file_uploads— el número máximo de archivos en una sola solicitud.
Conclusión
La subida de archivos en PHP es una parte fundamental del desarrollo web. Con el array superglobal $_FILES y la función move_uploaded_file(), puedes gestionar subidas en pocas líneas. Lo difícil es hacerlo de forma segura: comprueba siempre el código de error de subida, aplica un límite de tamaño, detecta el tipo MIME real con finfo en lugar de confiar en $_FILES[...]['type'], y genera tu propio nombre de archivo para que el cliente nunca pueda controlar dónde aterriza el archivo. Para procesar el archivo guardado posteriormente, consulta gestión de archivos en PHP y validación de formularios.