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

  1. Preparando el método upload

  2. Extrayendo información del archivo

  3. Validando el tipo de archivo

  4. Validando el tamaño del archivo

  5. Creando el nombre único del archivo

  6. Moviendo el archivo al directorio

  7. Manejando errores

  8. Código completo

  9. Explicación detallada

  10. 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
<?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:

php
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?

VariableDescripciónEjemplo
$fileTmpUbicación temporal del archivo en el servidor/tmp/phpXyZ123
$filenameNombre original del archivovideo.mp4
$fileSizeTamaño del archivo en bytes1234567
$errorsCódigo de error (0 = sin errores)0
$mimeTipo MIME del archivovideo/mp4

📁 2. Extrayendo información del archivo

Obteniendo la extensión

La extensión del archivo nos ayudará a generar un nombre único:

php
// 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:

php
$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:

php
// Obtener el directorio padre (dos niveles arriba)
$parentDirectory = dirname(dirname(dirname(__FILE__)));

Explicación:

  • __FILE__ es una constante que contiene la ruta del archivo actual

  • dirname() obtiene el directorio padre

  • Lo llamamos 3 veces para subir 3 niveles en la jerarquía

Ejemplo de rutas:

text
__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

php
$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

php
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:

php
$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):

php
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?

UnidadBytes
1 KB1,024 bytes
1 MB1,048,576 bytes
20 MB20,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

php
$folder = 'files/';
$file = $folder . md5(time() . mt_rand()) . '.' . $ext;

Desglose del nombre

ParteCódigoEjemplo
Carpeta$folderfiles/
Hashmd5(time() . mt_rand())7f3a4b2c1d
Extensión.$ext.mp4
Resultadofiles/7f3a4b2c1d.mp4

Explicación de las funciones

FunciónDescripciónEjemplo
time()Devuelve el timestamp actual1698765432
mt_rand()Genera un número aleatorio123456789
md5()Calcula el hash MD5 de un string7f3a4b2c1d8e9f

📤 6. Moviendo el archivo al directorio

Creando la carpeta 'files'

Primero, crea la carpeta en la raíz del proyecto:

text
whisper/
├── backend/
├── files/          # ¡Nueva carpeta!
├── frontend/
└── index.php

Moviendo el archivo

php
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:

php
move_uploaded_file(origen, destino);

Características importantes:

  • ✅ Verifica que el archivo fue subido mediante POST

  • ✅ Es más seguro que copy() o rename()

  • ✅ Retorna true si tiene éxito, false si falla

Retornando el nombre del archivo

php
return $file; // Devuelve la ruta del archivo subido

❌ 7. Manejando errores

Propiedad de error

php
public $error; // Almacena el mensaje de error

Estableciendo errores

php
// 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

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):

php
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
<?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
<?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
<?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)

html
<!-- 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

text
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ónCódigoMensaje de error
Tipo MIMEin_array($mime, $allowedMedia)"Formato de archivo inválido"
Tamaño$fileSize <= 20000000"El archivo es demasiado grande"
Subidamove_uploaded_file()"Error al mover el archivo"

Formatos soportados

FormatoTipo MIMEExtensión
MP4video/mp4.mp4
MPEGvideo/mpeg.mpeg
MP3audio/mpeg.mp3
MP3audio/mpeg3.mp3
WAVaudio/wav.wav

🧪 10. Probando la aplicación

Paso 1: Crear la carpeta 'files'

text
whisper/
├── files/          # ¡Crear esta carpeta!
├── backend/
├── frontend/
└── index.php

En Windows (CMD):

cmd
cd C:\xampp\htdocs\whisper
mkdir files

En Linux/Mac:

bash
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:

bash
chmod 755 files
chmod 777 files  # O 755 si es más restrictivo

Paso 3: Probar la subida

  1. Abrir la aplicación:

    text
    http://localhost/whisper/
  2. Seleccionar un archivo válido:

    • Elige un archivo MP4, MP3 o WAV

    • Asegúrate de que sea menor a 20 MB

  3. Verificar el resultado:

    • ✅ Éxito: Verás el nombre del archivo subido

    • ❌ Error: Verás un mensaje de error en rojo

  4. 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

text
Éxito: ¡Archivo subido correctamente! ID: files/7f3a4b2c1d8e9f.mp4

Ejemplo de errores

text
Error: Formato de archivo inválido. Solo se permiten: MP4, MPEG, MP3, WAV
text
Error: El archivo es demasiado grande. Máximo permitido: 20 MB
text
Error: Error al mover el archivo al servidor

📊 11. Resumen de funciones PHP usadas

FunciónPropósitoEjemplo
basename()Obtiene el nombre del archivobasename('/ruta/video.mp4')video.mp4
pathinfo()Obtiene información del archivopathinfo('video.mp4', PATHINFO_EXTENSION)mp4
strtolower()Convierte a minúsculasstrtolower('MP4')mp4
dirname()Obtiene el directorio padredirname('/ruta/archivo.php')/ruta
in_array()Busca en un arrayin_array('mp4', ['mp4','mp3'])true
md5()Calcula hash MD5md5('texto')5f4dcc3b5aa7...
time()Devuelve timestamptime()1698765432
mt_rand()Número aleatoriomt_rand()123456789
move_uploaded_file()Mueve archivo subidomove_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:

  1. Guardar información en la base de datos

  2. Crear la clase Database para manejar la conexión

  3. Insertar registros con la información del archivo

  4. Mostrar la lista de archivos subidos

Avance del código de la próxima lección

php
// 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 20000000 a un número mayor

  • Recuerda 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 $allowedMedia

  • Ejemplo: '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:

php
$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:

php
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:

php
// 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

Entradas más populares de este blog

Cómo usar Whisper para sacar el texto de un video

Tutorial 18: Creando la Página de Visualización de Archivos Recientes

Tutorial 11: ¿Qué es Whisper AI y Cómo Funciona?