Tutorial 8: Implementando la Subida de Archivos con la Clase Whisper
Tutorial 8: Implementando la Subida de Archivos con la Clase Whisper
¡Hola y bienvenido de nuevo!
En esta lección vamos a implementar la funcionalidad completa de subida de archivos en nuestra clase Whisper. Vamos a validar el archivo, moverlo a una carpeta segura y manejar los errores adecuadamente. ¡Vamos a ello!
📋 Contenido del Tutorial
Preparando el método upload
Extrayendo información del archivo
Validando el tipo de archivo
Validando el tamaño del archivo
Creando el nombre único del archivo
Moviendo el archivo al directorio
Manejando errores
Código completo
Explicación detallada
Próximos pasos
🚀 1. Preparando el método upload
Estructura inicial
Vamos a modificar nuestro método upload() para que realmente procese y suba el archivo:
<?php class Whisper { public $error; // Propiedad para almacenar errores public function upload($file) { // Aquí irá toda la lógica de subida } } ?>
Variables que necesitamos
Vamos a extraer toda la información del archivo:
public function upload($file) { // Información del archivo subido $fileTmp = $file['tmp_name']; // Ruta 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 }
¿Qué es cada variable?
| Variable | Descripción | Ejemplo |
|---|---|---|
$fileTmp | Ubicación temporal del archivo en el servidor | /tmp/phpXyZ123 |
$filename | Nombre original del archivo | video.mp4 |
$fileSize | Tamaño del archivo en bytes | 1234567 |
$errors | Código de error (0 = sin errores) | 0 |
$mime | Tipo MIME del archivo | video/mp4 |
📁 2. Extrayendo información del archivo
Obteniendo la extensión
La extensión del archivo nos ayudará a generar un nombre único:
// Obtener la extensión del archivo $ext = pathinfo($filename, PATHINFO_EXTENSION); $ext = strtolower($ext); // Convertir a minúsculas
¿Qué hace pathinfo()?
pathinfo() es una función de PHP que devuelve información sobre una ruta de archivo:
$path = '/mi/carpeta/video.mp4'; $info = pathinfo($path); // Resultado: // $info['dirname'] = '/mi/carpeta' // $info['basename'] = 'video.mp4' // $info['extension'] = 'mp4' // $info['filename'] = 'video'
Obteniendo el directorio raíz
Necesitamos saber dónde está la raíz de nuestro proyecto:
// Obtener el directorio padre (dos niveles arriba) $parentDirectory = dirname(dirname(dirname(__FILE__)));
Explicación:
__FILE__es una constante que contiene la ruta del archivo actualdirname()obtiene el directorio padreLo llamamos 3 veces para subir 3 niveles en la jerarquía
Ejemplo de rutas:
__FILE__ = C:\xampp\htdocs\whisper\backend\classes\Whisper.php dirname(__FILE__) = C:\xampp\htdocs\whisper\backend\classes dirname(dirname(__FILE__)) = C:\xampp\htdocs\whisper\backend dirname(dirname(dirname(__FILE__))) = C:\xampp\htdocs\whisper
✅ 3. Validando el tipo de archivo
Lista de tipos permitidos
$allowedMedia = [ 'video/mp4', // Videos MP4 'video/mpeg', // Videos MPEG 'audio/mpeg', // Audios MP3 'audio/mpeg3', // Audios MP3 (variante) 'audio/wav' // Audios WAV ];
Verificando si el tipo está permitido
if (in_array($mime, $allowedMedia)) { // El tipo de archivo está permitido // Continuar con el proceso } else { // El tipo de archivo NO está permitido $this->error = "Formato de archivo inválido"; }
¿Qué hace in_array()?
in_array() busca un valor en un array:
$frutas = ['manzana', 'pera', 'naranja']; in_array('manzana', $frutas); // true in_array('sandia', $frutas); // false
📏 4. Validando el tamaño del archivo
Límite de tamaño (20 MB)
Vamos a limitar los archivos a 20 MB (20,000,000 bytes):
if ($fileSize <= 20000000) { // El tamaño está dentro del límite // Continuar con el proceso } else { // El archivo es demasiado grande $this->error = "El archivo es demasiado grande (máximo 20 MB)"; }
¿Por qué 20,000,000 bytes?
| Unidad | Bytes |
|---|---|
| 1 KB | 1,024 bytes |
| 1 MB | 1,048,576 bytes |
| 20 MB | 20,971,520 bytes |
| 20 MB (redondeado) | 20,000,000 bytes |
🔐 5. Creando el nombre único del archivo
¿Por qué un nombre único?
Seguridad: Evita que los usuarios sobrescriban archivos
Organización: Mantiene los archivos ordenados
Prevención: Evita conflictos con nombres duplicados
Generando el nombre
$folder = 'files/'; $file = $folder . md5(time() . mt_rand()) . '.' . $ext;
Desglose del nombre
| Parte | Código | Ejemplo |
|---|---|---|
| Carpeta | $folder | files/ |
| Hash | md5(time() . mt_rand()) | 7f3a4b2c1d |
| Extensión | .$ext | .mp4 |
| Resultado | files/7f3a4b2c1d.mp4 |
Explicación de las funciones
| Función | Descripción | Ejemplo |
|---|---|---|
time() | Devuelve el timestamp actual | 1698765432 |
mt_rand() | Genera un número aleatorio | 123456789 |
md5() | Calcula el hash MD5 de un string | 7f3a4b2c1d8e9f |
📤 6. Moviendo el archivo al directorio
Creando la carpeta 'files'
Primero, crea la carpeta en la raíz del proyecto:
whisper/ ├── backend/ ├── files/ # ¡Nueva carpeta! ├── frontend/ └── index.php
Moviendo el archivo
move_uploaded_file($fileTmp, $parentDirectory . '/' . $file);
¿Qué hace move_uploaded_file()?
Esta función mueve el archivo de la ubicación temporal a la ubicación definitiva:
move_uploaded_file(origen, destino);
Características importantes:
✅ Verifica que el archivo fue subido mediante POST
✅ Es más seguro que
copy()orename()✅ Retorna
truesi tiene éxito,falsesi falla
Retornando el nombre del archivo
return $file; // Devuelve la ruta del archivo subido
❌ 7. Manejando errores
Propiedad de error
public $error; // Almacena el mensaje de error
Estableciendo errores
// Error: Formato inválido $this->error = "Formato de archivo inválido"; // Error: Archivo demasiado grande $this->error = "El archivo es demasiado grande (máximo 20 MB)";
Usando la propiedad en index.php
$file = $whisperObj->upload($_FILES['file']); if ($file) { // Éxito - mostrar información var_dump($file); } else { // Error - mostrar mensaje $error = $whisperObj->error; }
¿Qué es $this?
$this es una referencia al objeto actual (la instancia de la clase):
class Ejemplo { public $nombre; public function setNombre($nombre) { $this->nombre = $nombre; // $this se refiere al objeto actual } } $obj = new Ejemplo(); $obj->setNombre('Juan'); // $this es $obj en este momento
💻 8. Código completo
Archivo: backend/classes/Whisper.php
<?php /** * Clase Whisper * * Maneja la subida, validación y procesamiento de archivos */ class Whisper { /** * @var string Almacena mensajes de error */ public $error; /** * Sube y procesa un archivo * * @param array $file Arreglo $_FILES con la información del archivo * @return string|false Ruta del archivo subido o false en caso de error */ public function upload($file) { // 1. Extraer información del archivo $fileTmp = $file['tmp_name']; // Ruta 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 la extensión del archivo $ext = pathinfo($filename, PATHINFO_EXTENSION); $ext = strtolower($ext); // 3. Obtener el directorio raíz del proyecto $parentDirectory = dirname(dirname(dirname(__FILE__))); // 4. Definir los tipos de archivo permitidos $allowedMedia = [ 'video/mp4', // Video MP4 'video/mpeg', // Video MPEG 'audio/mpeg', // Audio MP3 'audio/mpeg3', // Audio MP3 (variante) 'audio/wav' // Audio WAV ]; // 5. Validar el tipo de archivo if (!in_array($mime, $allowedMedia)) { $this->error = "Formato de archivo inválido. Solo se permiten: MP4, MPEG, MP3, WAV"; return false; } // 6. Validar el tamaño del archivo (máximo 20 MB) if ($fileSize > 20000000) { $this->error = "El archivo es demasiado grande. Máximo permitido: 20 MB"; return false; } // 7. Crear el nombre único para el archivo $folder = 'files/'; $uniqueName = md5(time() . mt_rand()); $file = $folder . $uniqueName . '.' . $ext; // 8. Mover el archivo al directorio de destino $destination = $parentDirectory . '/' . $file; if (move_uploaded_file($fileTmp, $destination)) { // Éxito: retornar la ruta del archivo return $file; } else { // Error al mover el archivo $this->error = "Error al mover el archivo al servidor"; return false; } } } ?>
Archivo: backend/init.php
<?php /** * Archivo de inicialización * Carga las clases y configuraciones necesarias */ // Cargar la clase Whisper require_once 'classes/Whisper.php'; // Crear instancia de la clase Whisper $whisperObj = new Whisper(); // Aquí se cargarán más clases en el futuro // require_once 'classes/Database.php'; // $db = new Database(); ?>
Archivo: index.php (sección PHP)
<?php // Cargar la configuración inicial include 'backend/init.php'; // Variable para mensajes de error $error = null; $success = null; // Verificar si la solicitud es POST if ($_SERVER['REQUEST_METHOD'] === "POST") { // Verificar si el archivo está presente if (isset($_FILES['file'])) { // Verificar que no esté vacío if (!empty($_FILES['file']['name'])) { // Procesar el archivo con la clase Whisper $file = $whisperObj->upload($_FILES['file']); if ($file) { // Éxito: el archivo se subió correctamente $success = "¡Archivo subido correctamente! ID: " . $file; // Aquí redirigiremos a la página de visualización // header("Location: view.php?file=" . $file); // exit; } else { // Error: obtener el mensaje de error $error = $whisperObj->error; } } else { $error = "Por favor selecciona un archivo para convertir a texto"; } } else { $error = "Por favor selecciona un archivo para convertir a texto"; } } ?>
Archivo: index.php (HTML - sección de errores actualizada)
<!-- ERROR DIV --> <?php if(isset($error)): ?> <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"> <strong class="font-bold">Error:</strong> <span class="block sm:inline"><?php echo $error; ?></span> </div> <?php endif; ?> <!-- SUCCESS DIV --> <?php if(isset($success)): ?> <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative"> <strong class="font-bold">Éxito:</strong> <span class="block sm:inline"><?php echo $success; ?></span> </div> <?php endif; ?>
📖 9. Explicación detallada
Flujo del proceso de subida
1. Usuario selecciona archivo ↓ 2. JavaScript envía el formulario ↓ 3. PHP recibe la solicitud POST ↓ 4. Se llama a $whisperObj->upload($_FILES['file']) ↓ 5. Extrae información del archivo: - tmp_name (ubicación temporal) - name (nombre original) - size (tamaño) - type (tipo MIME) ↓ 6. Obtiene la extensión del archivo ↓ 7. Valida el tipo MIME (¿está permitido?) ↓ 8. Valida el tamaño (¿es menor a 20 MB?) ↓ 9. Genera un nombre único (MD5 + timestamp) ↓ 10. Mueve el archivo a la carpeta 'files/' ↓ 11. Retorna la ruta del archivo o false
Validaciones implementadas
| Validación | Código | Mensaje de error |
|---|---|---|
| Tipo MIME | in_array($mime, $allowedMedia) | "Formato de archivo inválido" |
| Tamaño | $fileSize <= 20000000 | "El archivo es demasiado grande" |
| Subida | move_uploaded_file() | "Error al mover el archivo" |
Formatos soportados
| Formato | Tipo MIME | Extensión |
|---|---|---|
| MP4 | video/mp4 | .mp4 |
| MPEG | video/mpeg | .mpeg |
| MP3 | audio/mpeg | .mp3 |
| MP3 | audio/mpeg3 | .mp3 |
| WAV | audio/wav | .wav |
🧪 10. Probando la aplicación
Paso 1: Crear la carpeta 'files'
whisper/ ├── files/ # ¡Crear esta carpeta! ├── backend/ ├── frontend/ └── index.php
En Windows (CMD):
cd C:\xampp\htdocs\whisper mkdir files
En Linux/Mac:
cd /opt/lampp/htdocs/whisper mkdir files
Paso 2: Dar permisos a la carpeta
En Windows:
La carpeta ya tiene permisos por defecto
En Linux/Mac:
chmod 755 files chmod 777 files # O 755 si es más restrictivo
Paso 3: Probar la subida
Abrir la aplicación:
http://localhost/whisper/
Seleccionar un archivo válido:
Elige un archivo MP4, MP3 o WAV
Asegúrate de que sea menor a 20 MB
Verificar el resultado:
✅ Éxito: Verás el nombre del archivo subido
❌ Error: Verás un mensaje de error en rojo
Verificar la carpeta 'files':
Ve a
C:\xampp\htdocs\whisper\files\Deberías ver un archivo con nombre similar a:
7f3a4b2c1d.mp4
Ejemplo de resultado exitoso
Éxito: ¡Archivo subido correctamente! ID: files/7f3a4b2c1d8e9f.mp4
Ejemplo de errores
Error: Formato de archivo inválido. Solo se permiten: MP4, MPEG, MP3, WAV
Error: El archivo es demasiado grande. Máximo permitido: 20 MB
Error: Error al mover el archivo al servidor
📊 11. Resumen de funciones PHP usadas
| Función | Propósito | Ejemplo |
|---|---|---|
basename() | Obtiene el nombre del archivo | basename('/ruta/video.mp4') → video.mp4 |
pathinfo() | Obtiene información del archivo | pathinfo('video.mp4', PATHINFO_EXTENSION) → mp4 |
strtolower() | Convierte a minúsculas | strtolower('MP4') → mp4 |
dirname() | Obtiene el directorio padre | dirname('/ruta/archivo.php') → /ruta |
in_array() | Busca en un array | in_array('mp4', ['mp4','mp3']) → true |
md5() | Calcula hash MD5 | md5('texto') → 5f4dcc3b5aa7... |
time() | Devuelve timestamp | time() → 1698765432 |
mt_rand() | Número aleatorio | mt_rand() → 123456789 |
move_uploaded_file() | Mueve archivo subido | move_uploaded_file($tmp, $dest) |
🎯 12. Próximos pasos
Lo que hemos logrado
✅ Método upload completamente funcional
✅ Validación de tipo MIME
✅ Validación de tamaño (20 MB)
✅ Generación de nombres únicos
✅ Movimiento del archivo a la carpeta 'files'
✅ Manejo de errores básico
✅ Mensajes de éxito/error en la interfaz
Lo que viene en la próxima lección
En la siguiente lección vamos a:
Guardar información en la base de datos
Crear la clase Database para manejar la conexión
Insertar registros con la información del archivo
Mostrar la lista de archivos subidos
Avance del código de la próxima lección
// En Whisper.php public function saveToDatabase($fileInfo) { // Guardar en la base de datos // INSERT INTO transcriptions (filename, path, type, size) } // En index.php if ($file) { // Guardar en la base de datos $whisperObj->saveToDatabase($file, $_FILES['file']); // Redirigir a la vista }
❓ Preguntas frecuentes
¿Por qué usar MD5 para el nombre del archivo?
Para generar nombres únicos y evitar conflictos
Es seguro y rápido de calcular
¿Qué pasa si subo un archivo con el mismo nombre?
No importa, porque generamos un nombre único
El nombre original se guarda en la base de datos
¿Cómo puedo aumentar el límite de tamaño?
Cambia
20000000a un número mayorRecuerda que también debes configurar PHP (
upload_max_filesize)
¿Qué pasa si la carpeta 'files' no existe?
PHP mostrará un error
Asegúrate de crearla con los permisos adecuados
¿Cómo puedo agregar más formatos permitidos?
Añade el tipo MIME al array
$allowedMediaEjemplo:
'video/quicktime'para MOV
¿Por qué usamos dirname() tres veces?
Para subir desde
backend/classes/hasta la raíz del proyecto__FILE__está en la clase, necesitamos la raíz
🎓 Ejercicio práctico
Ejercicio 1: Agregar más formatos
Agrega soporte para formatos adicionales:
$allowedMedia = [ 'video/mp4', 'video/mpeg', 'video/quicktime', // MOV 'video/x-msvideo', // AVI 'audio/mpeg', 'audio/mpeg3', 'audio/wav', 'audio/ogg' // OGG ];
Ejercicio 2: Limitar el tamaño
Cambia el límite a 50 MB:
if ($fileSize > 50000000) { $this->error = "El archivo es demasiado grande. Máximo permitido: 50 MB"; return false; }
Ejercicio 3: Agregar una carpeta por usuario
Modifica la carpeta de destino para que incluya el usuario:
// En lugar de 'files/' $folder = 'files/' . date('Y-m-d') . '/'; // Carpeta por fecha
¡Excelente trabajo! Ahora tenemos un método de subida de archivos completamente funcional con validaciones y manejo de errores. En la próxima lección, conectaremos esto con la base de datos
Comentarios
Publicar un comentario