8. Creating method upload files to the server
Método para Subir Archivos al Servidor
📚 Tema del Tutorial:
"Subida Segura de Archivos en PHP: Validación, Procesamiento y Almacenamiento"
🎯 Objetivo de Aprendizaje
Al final de este tutorial entenderás:
Cómo extraer información de archivos subidos
Cómo validar tipos y tamaños de archivo
Cómo generar nombres seguros para archivos
Cómo mover archivos temporales a ubicación permanente
Cómo manejar errores en la subida de archivos
📂 Parte 1: Entendiendo el Flujo de Subida de Archivos
El Proceso de Subida - Analogía del Correo:
text
📦 PAQUETE POSTAL (Archivo subido)
├── 🏷️ Nombre: audio.mp3 (nombre original)
├── 📏 Tamaño: 5MB (peso del paquete)
├── 🚚 Temporal: Oficina de correos (ubicación temporal)
├── ✅ Tipo: Audio/MP3 (qué contiene)
└── 📍 Destino: Tu casa (carpeta files/)
El Viaje de un Archivo:
text
👤 USUARIO selecciona archivo
↓
🌐 NAVEGADOR envía al servidor
↓
🖥️ PHP recibe en ubicación TEMPORAL
↓
🔍 PHP verifica (tipo, tamaño, seguridad)
↓
📁 PHP mueve a ubicación PERMANENTE
↓
✅ Archivo listo para usar en tu aplicación
🛠️ Parte 2: Extrayendo Información del Archivo
Variables Clave de $_FILES:
Cuando un usuario sube un archivo, PHP crea un array con esta estructura:
php
$_FILES['file'] = [
'name' => 'video.mp4', // 🏷️ Nombre original
'type' => 'video/mp4', // 📄 Tipo MIME
'tmp_name' => '/tmp/php123', // 📦 Ubicación temporal
'error' => 0, // ✅ Código de error (0 = éxito)
'size' => 10485760 // 📏 Tamaño en bytes (10MB)
];
Extrayendo Cada Parte - Código Paso a Paso:
Paso 1: Ubicación temporal del archivo
php
$fileTmp = $file['tmp_name'];
// Ejemplo: '/tmp/phpX1Y2Z3'
// 📦 El paquete está en la oficina de correos temporal
Paso 2: Nombre original del archivo
php
$filename = basename($file['name']);
// basename('carpeta/video.mp4') = 'video.mp4'
// 🏷️ Solo el nombre, sin rutas de carpetas
Paso 3: Tamaño del archivo
php
$fileSize = $file['size'];
// Ejemplo: 10485760 (10MB en bytes)
// 📏 Cuánto pesa el archivo
Paso 4: Código de error
php
$errors = $file['error'];
// 0 = ✅ Éxito
// 1, 2, 3, 4, 6, 7, 8 = ❌ Diferentes tipos de errores
Paso 5: Tipo MIME del archivo
php
$mime = $file['type'];
// Ejemplo: 'video/mp4', 'audio/mpeg'
// 📄 Qué tipo de contenido es realmente
🔍 Parte 3: Obteniendo la Extensión del Archivo
¿Qué es una Extensión de Archivo?
La extensión son las letras después del punto:
text
🎵 audio.mp3 → .mp3
🎥 video.mp4 → .mp4
📸 foto.jpg → .jpg
Código para Obtener la Extensión:
Método 1: Usando pathinfo() (Recomendado)
php
// Extrae información del path del archivo
$ext = pathinfo($filename, PATHINFO_EXTENSION);
// 'video.mp4' → 'mp4'
// 'audio.mpeg3' → 'mpeg3'
Método 2: Convertir a minúsculas
php
$ext = strtolower($ext);
// 'MP4' → 'mp4'
// 'JPG' → 'jpg'
// Importante: PHP en Linux diferencia mayúsculas/minúsculas
Visualización del proceso:
text
📁 Archivo: 'MiVideo.MP4'
↓ pathinfo()
🔧 Extensión: 'MP4'
↓ strtolower()
🔧 Extensión: 'mp4' ✅ Lista para comparar
📁 Parte 4: Encontrando la Ruta Correcta - Directorios en PHP
El Problema de las Rutas Relativas:
Cuando trabajas con archivos subidos, necesitas saber dónde estás y dónde quieres guardar.
Obteniendo el Directorio Raíz del Proyecto:
Código explicado:
php
$parentDirectory = dirname(dirname(dirname(__FILE__)));
Desglose visual:
text
__FILE__ = /home/usuario/proyecto/backend/classes/Whisper.php
↓ dirname() (1 vez)
/home/usuario/proyecto/backend/classes/
↓ dirname() (2 veces)
/home/usuario/proyecto/backend/
↓ dirname() (3 veces)
/home/usuario/proyecto/ ← ¡DIRECTORIO RAÍZ!
Analogía del edificio:
text
🏢 EDIFICIO (Proyecto completo)
├── 🏠 PISO 3: /home/usuario/proyecto/backend/classes/
├── 🏠 PISO 2: /home/usuario/proyecto/backend/
├── 🏠 PISO 1: /home/usuario/proyecto/ ← ¡Salida!
└── 📁 MALETEROS: /home/usuario/proyecto/files/
¿Por qué necesitamos esto?
Para poder guardar archivos en una carpeta específica:
text
/home/usuario/proyecto/files/video_abc123.mp4
🛡️ Parte 5: Validación de Seguridad - Lo Permitido vs Lo Prohibido
Creando la Lista de Tipos Permitidos:
Código del array de tipos permitidos:
php
$allowedMedia = [
'video/mp4', // 🎥 Videos MP4
'video/mpeg', // 🎥 Videos MPEG
'audio/mpeg', // 🎵 Audio MP3
'audio/mpeg3', // 🎵 Audio MPEG3
'audio/wav' // 🎵 Audio WAV
];
Visualización de tipos MIME comunes:
text
🎥 VIDEO:
├── MP4: video/mp4
├── AVI: video/x-msvideo
└── MOV: video/quicktime
🎵 AUDIO:
├── MP3: audio/mpeg
├── WAV: audio/wav
└── OGG: audio/ogg
🖼️ IMAGEN:
├── JPG: image/jpeg
├── PNG: image/png
└── GIF: image/gif
Validando el Tipo de Archivo:
Código de validación:
php
if(in_array($mime, $allowedMedia)){
// ✅ Tipo permitido - Continuar
} else {
// ❌ Tipo NO permitido - Mostrar error
$this->error = "Formato de archivo inválido!";
}
¿Por qué validar por tipo MIME y no por extensión?
text
❌ Mala práctica: Validar solo por '.mp4'
(Un virus.exe renombrado a virus.mp4 pasaría)
✅ Buena práctica: Validar por 'video/mp4'
(PHP verifica el contenido real del archivo)
📏 Parte 6: Validando el Tamaño del Archivo
Entendiendo los Bytes - Conversiones:
text
1 Byte = 1 carácter
1 KB = 1,024 Bytes
1 MB = 1,024 KB = 1,048,576 Bytes
1 GB = 1,024 MB = 1,073,741,824 Bytes
Límite de 20MB en Diferentes Unidades:
php
20 MB = 20 * 1024 * 1024 = 20,971,520 bytes
Código de validación de tamaño:
php
if($fileSize <= 20971520){ // 20MB en bytes
// ✅ Tamaño aceptable
} else {
// ❌ Archivo demasiado grande
$this->error = "¡El archivo es muy grande!";
}
Mensajes de error útiles:
php
// Mostrar en MB para que el usuario entienda
$tamanoMB = round($fileSize / (1024 * 1024), 2);
$this->error = "El archivo tiene {$tamanoMB}MB. Máximo: 20MB";
🔐 Parte 7: Generando Nombres Seguros para Archivos
¿Por qué NO usar el nombre original?
Problemas con nombres originales:
text
❌ 'Mi Video.mp4' → Espacios causan problemas
❌ 'carnet.jpg' → Sobrescribe otros archivos
❌ '../../../virus.exe' → Inyección de rutas
❌ 'áéíóú.mp3' → Caracteres especiales
Solución: Nombres Aleatorios Únicos
Código para generar nombre seguro:
php
$folder = 'files/';
$file = $folder . md5(time() . mt_rand()) . '.' . $ext;
Desglose de la generación:
text
'files/' + md5('1678901234' + '54872') + '.mp4'
↓
'files/' + 'a1b2c3d4e5f6g7h8i9j0' + '.mp4'
↓
'files/a1b2c3d4e5f6g7h8i9j0.mp4' ✅
Explicación de las funciones:
time() = Segundos desde 1970 (siempre diferente)
mt_rand() = Número aleatorio (más diferencia)
md5() = Convierte a hash de 32 caracteres (seguro)
Ventajas de este método:
✅ Único (casi imposible repetir)
✅ Sin espacios ni caracteres especiales
✅ No revela información original
✅ Previene sobreescritura
🚚 Parte 8: Moviendo el Archivo a su Ubicación Final
La Diferencia Entre Temporal y Permanente:
text
📦 ARCHIVO TEMPORAL:
├── Ubicación: /tmp/phpX1Y2Z3
├── Duración: Hasta que termine el script
├── Propósito: Validación previa
└️️ Riesgo: Se borra automáticamente
🏠 ARCHIVO PERMANENTE:
├── Ubicación: /proyecto/files/abc123.mp4
├── Duración: Hasta que lo borres
├── Propósito: Uso en la aplicación
└️️ Seguridad: Tu responsabilidad
Función move_uploaded_file():
Código para mover el archivo:
php
move_uploaded_file($fileTmp, $parentDirectory . '/' . $file);
Parámetros explicados:
php
move_uploaded_file(
$origen, // 📦 '/tmp/phpX1Y2Z3' (archivo temporal)
$destino // 🏠 '/home/proyecto/files/abc123.mp4' (destino final)
);
Validación de seguridad automática:
La función move_uploaded_file() tiene seguridad integrada:
✅ Verifica que el archivo fue subido via HTTP POST
✅ Previene ataques de inyección de archivos
✅ Es más seguro que copy() o rename()
🧩 Parte 9: Código Completo del Método upload()
Versión Completa con Comentarios:
php
public function upload($file){
// 1️⃣ Extraer información del archivo
$fileTmp = $file['tmp_name']; // Ubicación temporal
$filename = basename($file['name']); // Nombre original
$fileSize = $file['size']; // Tamaño en bytes
$errors = $file['error']; // Código de error
$mime = $file['type']; // Tipo MIME
// 2️⃣ Obtener extensión del archivo
$ext = pathinfo($filename, PATHINFO_EXTENSION);
$ext = strtolower($ext);
// 3️⃣ Obtener directorio raíz del proyecto
$parentDirectory = dirname(dirname(dirname(__FILE__)));
// 4️⃣ Tipos de archivo permitidos
$allowedMedia = [
'video/mp4',
'video/mpeg',
'audio/mpeg',
'audio/mpeg3',
'audio/wav'
];
// 5️⃣ Validar tipo de archivo
if(in_array($mime, $allowedMedia)){
// 6️⃣ Validar tamaño (máximo 20MB)
if($fileSize <= 20971520){
// 7️⃣ Preparar carpeta destino
$folder = 'files/';
// 8️⃣ Generar nombre único seguro
$file = $folder . md5(time() . mt_rand()) . '.' . $ext;
// 9️⃣ Mover archivo a ubicación permanente
move_uploaded_file($fileTmp, $parentDirectory . '/'.$file);
// 🔟 Devolver ruta del archivo guardado
return $file;
} else {
$this->error = "¡El archivo es muy grande!";
}
} else {
$this->error = "Formato de archivo inválido!";
}
}
Diagrama de Flujo del Método:
text
[📤 Archivo subido]
↓
[1️⃣ Extraer información]
↓
[2️⃣ Obtener extensión]
↓
[3️⃣ Encontrar directorio raíz]
↓
[4️⃣ ¿Tipo permitido?]
│
├── NO → 🚨 Error: "Formato inválido"
│
└── SI → 5️⃣ ¿Tamaño ≤ 20MB?
│
├── NO → 🚨 Error: "Muy grande"
│
└── SI → 6️⃣ Generar nombre único
↓
7️⃣ Mover a carpeta files/
↓
8️⃣ ✅ Devolver ruta guardada
🎯 Parte 10: Probando y Depurando el Método
Prueba Paso a Paso:
1. Crear carpeta files/ en el directorio raíz:
text
📁 proyecto/
├── 📁 backend/
├── 📁 frontend/
├── 📁 files/ ← ¡CREAR ESTA CARPETA!
└── 📄 index.php
2. Verificar permisos de la carpeta:
bash
# En Linux/Mac, dar permisos de escritura
chmod 755 files/
3. Probar con diferentes archivos:
✅ video.mp4 (15MB)
✅ audio.mp3 (5MB)
❌ documento.pdf (formato no permitido)
❌ video_grande.mkv (25MB - demasiado grande)
Posibles Errores y Soluciones:
Error 1: "Warning: move_uploaded_file(): failed to open stream"
Causa: La carpeta files/ no existe o no tiene permisos
Solución: Crear carpeta y dar permisos
Error 2: "File is large!" aunque el archivo es pequeño
Causa: Límite en php.ini (upload_max_filesize)
Solución: Verificar configuración PHP
Error 3: Archivo no se mueve pero no da error
Causa: move_uploaded_file() devuelve false silenciosamente
Solución: Verificar ruta completa de destino
📊 Parte 11: Ejercicio Práctico - Sistema de Subida de Imágenes
Clase para Subir Imágenes:
php
<?php
class ImageUploader {
public $error;
public function upload($image){
// 1. Extraer información
$tmpName = $image['tmp_name'];
$originalName = basename($image['name']);
$size = $image['size'];
$type = $image['type'];
// 2. Obtener extensión
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
// 3. Tipos permitidos (solo imágenes)
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
if(!in_array($type, $allowedTypes)){
$this->error = "Solo se permiten imágenes JPG, PNG o GIF";
return false;
}
// 4. Tamaño máximo: 5MB
if($size > 5242880){
$this->error = "La imagen no debe superar 5MB";
return false;
}
// 5. Generar nombre único
$newName = 'uploads/' . uniqid() . '.' . $ext;
// 6. Mover archivo
if(move_uploaded_file($tmpName, $newName)){
return $newName; // ✅ Éxito
} else {
$this->error = "Error al subir la imagen";
return false;
}
}
}
?>
Uso de la clase:
php
<?php
require 'ImageUploader.php';
$uploader = new ImageUploader();
if($_SERVER['REQUEST_METHOD'] == 'POST'){
$resultado = $uploader->upload($_FILES['imagen']);
if($resultado){
echo "✅ Imagen subida: " . $resultado;
} else {
echo "❌ Error: " . $uploader->error;
}
}
?>
🛡️ Parte 12: Consideraciones de Seguridad Avanzadas
Protecciones Adicionales Recomendadas:
1. Validar contenido real del archivo:
php
// Verificar que un MP3 sea realmente audio
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$realType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if($realType != 'audio/mpeg'){
$this->error = "El archivo no es un MP3 válido";
}
2. Escanear archivos con antivirus:
php
// En servidores Linux
exec("clamscan " . escapeshellarg($file['tmp_name']), $output, $return);
if($return != 0){
$this->error = "Archivo potencialmente peligroso";
}
3. Limitar tipos de archivo por extensión también:
php
$allowedExtensions = ['mp4', 'mp3', 'wav', 'mpeg'];
if(!in_array($ext, $allowedExtensions)){
$this->error = "Extensión no permitida";
}
4. Sanitizar nombres de archivo:
php
// Eliminar caracteres peligrosos
$safeName = preg_replace('/[^a-zA-Z0-9\.\-_]/', '', $originalName);
❓ Parte 13: Cuestionario de Evaluación
Pregunta 1:
¿Por qué es importante usar move_uploaded_file() en lugar de copy()?
a) Porque es más rápido
b) Porque tiene validaciones de seguridad integradas ✅
c) Porque usa menos memoria
d) Porque es más nuevo
Explicación: move_uploaded_file() verifica que el archivo fue subido via HTTP POST, previniendo ataques.
Pregunta 2:
¿Qué hace la función basename('carpeta/subcarpeta/archivo.mp4')?
a) Devuelve 'carpeta/subcarpeta/archivo.mp4'
b) Devuelve 'archivo.mp4' ✅
c) Devuelve 'carpeta'
d) Devuelve 'mp4'
Pregunta 3:
En este código, ¿por qué usamos md5(time() . mt_rand())?
a) Para encriptar el archivo
b) Para generar un nombre único y seguro ✅
c) Para comprimir el archivo
d) Para validar el tipo de archivo
Pregunta 4:
¿Cuál es el tamaño máximo permitido en nuestro método upload?
a) 10MB
b) 20MB ✅
c) 50MB
d) 100MB
Pregunta 5:
¿Qué tipo de archivos permite nuestra aplicación?
a) Cualquier tipo de archivo
b) Solo documentos PDF
c) Videos MP4/MPEG y audios MP3/WAV ✅
d) Solo imágenes JPG y PNG
Pregunta 6:
¿Por qué convertimos la extensión a minúsculas con strtolower()?
a) Para ahorrar espacio de almacenamiento
b) Porque PHP en Linux diferencia mayúsculas/minúsculas ✅
c) Para hacer más rápido la validación
d) Porque se ve mejor
Pregunta 7:
¿Qué significa este código?
php
$parentDirectory = dirname(dirname(dirname(__FILE__)));
a) Obtiene la carpeta actual
b) Sube 3 niveles en la estructura de directorios ✅
c) Crea 3 carpetas nuevas
d) Elimina 3 niveles de directorios
Respuesta correcta: b) Sube desde la ubicación actual hasta el directorio raíz del proyecto.
🏆 Resumen Final - Reglas para Subida Segura
Las 7 Reglas de Oro:
🔒 Valida siempre en el servidor - Nunca confíes en validación JavaScript
📏 Establece límites claros - Tamaño máximo definido
📋 Restringe tipos permitidos - Solo lo que tu aplicación necesita
🔑 Genera nombres únicos - Previene sobrescritura y ataques
📁 Usa move_uploaded_file() - Seguridad integrada
🚨 Maneja errores apropiadamente - Informa al usuario claramente
📝 Registra actividad - Para depuración y seguridad
Checklist de tu Método upload():
✅ Extrae información del array $_FILES
✅ Obtiene extensión con pathinfo()
✅ Encuentra directorio raíz con dirname()
✅ Valida tipo MIME contra lista permitida
✅ Verifica tamaño (≤ 20MB)
✅ Genera nombre único con md5(time() . mt_rand())
✅ Crea carpeta files/ en directorio raíz
✅ Mueve archivo con move_uploaded_file()
✅ Devuelve ruta del archivo guardado
✅ Maneja errores con $this->error
🚀 Próximos Pasos - Lo que Viene
Mejoras para Implementar:
🗂️ Subida múltiple de archivos
📊 Barra de progreso en tiempo real
🖼️ Generación de miniaturas para videos
📁 Organización por carpetas (por fecha/usuario)
🔍 Búsqueda y gestión de archivos subidos
🔄 Reintentos automáticos en fallos
Código para subida múltiple:
php
public function uploadMultiple($files){
$resultados = [];
foreach($files['tmp_name'] as $key => $tmpName){
$archivo = [
'name' => $files['name'][$key],
'type' => $files['type'][$key],
'tmp_name' => $tmpName,
'error' => $files['error'][$key],
'size' => $files['size'][$key]
];
$resultados[] = $this->upload($archivo);
}
return $resultados;
}
📚 Recursos Adicionales
Configuraciones PHP importantes en php.ini:
ini
upload_max_filesize = 20M ; Tamaño máximo por archivo
post_max_size = 21M ; Tamaño máximo total POST
max_file_uploads = 20 ; Máximo de archivos simultáneos
file_uploads = On ; Permitir subida de archivos
Para seguir aprendiendo:
Subida con AJAX - Sin recargar página
Compresión automática - Reducir tamaño de imágenes/videos
Almacenamiento en la nube - AWS S3, Google Cloud Storage
Streaming de video - Reproducción mientras se sube
OCR de documentos - Extraer texto de PDFs e imágenes
¡Excelente trabajo! 🎉 Has creado un sistema seguro de subida de archivos. Esta es una de las funcionalidades más importantes (y peligrosas si se hace mal) en desarrollo web.
💡 Consejo final de seguridad:
Siempre trata los archivos subidos por usuarios como potencialmente peligrosos. Incluso con validación, un atacante creativo podría intentar explotar tu sistema. La regla de oro: desconfía, valida, sanitiza.
Comentarios
Publicar un comentario