17. Creating a method to save generated files into database
17. Creación de un método para guardar archivos generados en la base de datos
¡Hola y bienvenido de nuevo!
En esta lección vamos a crear un método crucial para persistir nuestros datos. Hasta ahora convertimos audio a texto, pero esos resultados se pierden al refrescar la página. Ahora vamos a guardarlos en la base de datos para poder consultarlos después, mostrarlos en una lista y permitir traducciones.
🎯 ¿Qué vamos a crear?
Vamos a crear el método save() que:
Recibe el archivo, contenido y tipo
Prepara una consulta SQL segura
Inserta los datos en la tabla files
Retorna el ID generado para redireccionar al usuario
🔍 Estructura de la base de datos
Tabla files en MySQL:
sql
CREATE TABLE files (
id INT AUTO_INCREMENT PRIMARY KEY,
fileUrl VARCHAR(255) NOT NULL, -- Ruta del archivo (ej: files/abc123.mp3)
content TEXT, -- Texto transcrito
type ENUM('audio', 'video') NOT NULL, -- Tipo de archivo
translated TEXT NULL, -- Para traducciones futuras
language VARCHAR(50) NULL, -- Idioma de traducción
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Visualización de la tabla:
text
┌─────┬──────────────────────┬────────────────────────────┬────────┬────────────┬──────────┬─────────────────────┐
│ id │ fileUrl │ content │ type │ translated │ language │ created_at │
├─────┼──────────────────────┼────────────────────────────┼────────┼────────────┼──────────┼─────────────────────┤
│ 1 │ files/a1b2c3.mp3 │ Hola, esto es una prueba │ audio │ NULL │ NULL │ 2024-01-15 10:30:00 │
│ 2 │ files/x9y8z7.mp4 │ Buenos días a todos │ video │ NULL │ NULL │ 2024-01-15 11:15:00 │
└─────┴──────────────────────┴────────────────────────────┴────────┴────────────┴──────────┴─────────────────────┘
🛠️ Vamos a crear el método save()
php
/**
* Guarda el archivo procesado en la base de datos
*
* @param string $file Ruta del archivo (ej: 'files/abc123.mp3')
* @param string $content Texto transcrito del archivo
* @param string $type Tipo de archivo ('audio' o 'video')
* @return int ID del registro insertado
*/
public function save($file, $content, $type)
{
// 1. Preparamos la consulta SQL con placeholders
$stmt = $this->DB->prepare("
INSERT INTO `files`
(`fileUrl`, `content`, `type`)
VALUES (:fileUrl, :content, :type)
");
// 2. Vinculamos los parámetros (evita SQL injection)
$stmt->bindParam(":fileUrl", $file, PDO::PARAM_STR);
$stmt->bindParam(":content", $content, PDO::PARAM_STR);
$stmt->bindParam(":type", $type, PDO::PARAM_STR);
// 3. Ejecutamos la consulta
$stmt->execute();
// 4. Retornamos el ID generado
return $this->DB->lastInsertId();
}
📝 Explicación detallada del método
1. ¿Por qué usar PDO?
PDO (PHP Data Objects) es una interfaz para acceder a bases de datos en PHP. Es más seguro que funciones antiguas como mysql_query().
Ventajas:
✅ Prevención de SQL Injection
✅ Portabilidad entre diferentes bases de datos
✅ Mejor manejo de errores
✅ Soporte para transacciones
2. Placeholders y bindParam
php
// MAL (vulnerable a SQL injection):
$query = "INSERT INTO files VALUES ('$file', '$content', '$type')";
// BIEN (seguro con placeholders):
$query = "INSERT INTO files VALUES (:fileUrl, :content, :type)";
$stmt->bindParam(":fileUrl", $file, PDO::PARAM_STR);
Analogía:
Placeholders son como formularios con espacios en blanco.
bindParam es como escribir con tinta indeleble en esos espacios.
Así, aunque alguien malintencionado ponga código dañino, solo se guardará como texto normal.
3. Tipos de datos PDO:
PDO::PARAM_STR → Para strings (texto)
PDO::PARAM_INT → Para números enteros
PDO::PARAM_BOOL → Para booleanos
PDO::PARAM_NULL → Para valores NULL
4. lastInsertId()
Retorna el último ID autoincremental generado
Útil para redireccionar al registro recién creado
Solo funciona con tablas que tienen columna autoincremental
🔧 Implementación en index.php
php
// En index.php, después de procesar el archivo:
if($_SERVER['REQUEST_METHOD'] === "POST"){
if(isset($_FILES['file'])){
if(!empty($_FILES['file']['name'][0])){
// 1. Subir archivo físicamente
$file = $whisperObj->upload($_FILES['file']);
if($file){
// 2. Configurar para ASR
$whisperObj->dataType = 'ASR';
$whisperObj->file = $file;
// 3. Convertir audio a texto
$text = $whisperObj->convert();
// 4. Determinar tipo de archivo
$fileType = $_FILES['file']['type'];
$type = '';
if(strpos($fileType, 'audio/') === 0){
$type = 'audio';
} else if(strpos($fileType, 'video/') === 0){
$type = 'video';
}
// 5. GUARDAR EN BASE DE DATOS (¡NUEVO!)
$fileID = $whisperObj->save($file, $text['text'], $type);
// 6. Redireccionar a la vista del archivo
header("location: view.php?file={$fileID}");
}
}
}
}
🔍 Explicando strpos() para determinar el tipo
php
// strpos() busca una cadena dentro de otra
// Retorna la posición (0, 1, 2...) o FALSE si no encuentra
$fileType = "audio/mp3";
// Busca "audio/" al inicio del string
$posicion = strpos($fileType, 'audio/'); // Retorna 0
// Si es 0, significa que "audio/" está al inicio
if($posicion === 0){
$type = 'audio';
}
// Tipos comunes de MIME:
// audio/mpeg, audio/wav, audio/mp3 → "audio"
// video/mp4, video/mpeg, video/quicktime → "video"
Ejemplos:
"audio/mp3" → strpos() retorna 0 → es audio
"video/mp4" → strpos() retorna 0 → es video
"application/pdf" → strpos() retorna FALSE → no es ni audio ni video
🎨 Diagrama del flujo completo
text
┌─────────────────────────────────────────────────────────────┐
│ FLUJO COMPLETO: SUBIR → PROCESAR → GUARDAR │
├─────────────────────────────────────────────────────────────┤
│ 1. USUARIO sube archivo.mp3 │
│ ↓ │
│ 2. upload() guarda en: files/a1b2c3.mp3 │
│ ↓ │
│ 3. Configurar: │
│ • dataType = 'ASR' │
│ • file = 'files/a1b2c3.mp3' │
│ ↓ │
│ 4. convert() envía a OpenAI │
│ ↓ │
│ 5. OpenAI responde: {"text": "Hola mundo"} │
│ ↓ │
│ 6. Determinar tipo: strpos("audio/mp3", "audio/") → 0 │
│ → type = 'audio' │
│ ↓ │
│ 7. save() inserta en BD: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ TABLA files │ │
│ │ id: 25 (auto) │ │
│ │ fileUrl: 'files/a1b2c3.mp3' │ │
│ │ content: 'Hola mundo' │ │
│ │ type: 'audio' │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 8. Retorna ID: 25 │
│ ↓ │
│ 9. Redirecciona a: view.php?file=25 │
└─────────────────────────────────────────────────────────────┘
💡 La importancia de guardar en base de datos
¿Por qué no solo guardar el texto en un archivo?
Casos de uso futuros:
Historial → Ver todos los archivos procesados
Búsqueda → Encontrar archivos por contenido
Estadísticas → Cuántos audios vs videos
Traducciones → Guardar múltiples versiones
Compartir → Enlaces específicos a cada archivo
🔧 Ejemplo práctico paso a paso
Paso 1: Subir archivo "entrevista.mp3"
php
// Archivo subido:
$_FILES['file'] = [
'name' => 'entrevista.mp3',
'type' => 'audio/mpeg',
'tmp_name' => '/tmp/phpABC123',
'size' => 1048576 // 1MB
];
// Después de upload():
$file = 'files/d4e5f678.mp3'; // Nombre encriptado
Paso 2: Procesar con OpenAI
php
$text = $whisperObj->convert();
// $text = ['text' => 'Buenos días, hoy hablaremos sobre IA.']
Paso 3: Determinar tipo
php
$fileType = 'audio/mpeg';
if(strpos($fileType, 'audio/') === 0){
$type = 'audio'; // ¡Sí es audio!
}
Paso 4: Guardar en BD
sql
-- Consulta ejecutada:
INSERT INTO files (fileUrl, content, type)
VALUES ('files/d4e5f678.mp3', 'Buenos días, hoy hablaremos sobre IA.', 'audio')
-- Resultado:
-- id: 42 (generado automáticamente)
Paso 5: Redireccionar
php
header("location: view.php?file=42");
// Ahora el usuario ve su archivo procesado
⚠️ Consideraciones de seguridad importantes
1. SQL Injection
php
// NUNCA HACER ESTO (¡PELIGROSO!):
$sql = "INSERT INTO files VALUES ('{$_POST['file']}')";
// Un atacante podría poner: audio.mp3'); DROP TABLE files; --
// SIEMPRE HACER ESTO (SEGURO):
$stmt = $pdo->prepare("INSERT INTO files VALUES (:file)");
$stmt->bindParam(':file', $_POST['file'], PDO::PARAM_STR);
2. Validación de tipos MIME
php
// Verificar realmente el tipo de archivo
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES['file']['tmp_name']);
finfo_close($finfo);
// Comparar con lista permitida
$allowed = ['audio/mpeg', 'audio/wav', 'video/mp4'];
if(!in_array($mime, $allowed)){
die("Tipo de archivo no permitido");
}
📊 Esquema de relaciones futuras
text
┌─────────────────────────────────────────────────────┐
│ BASE DE DATOS COMPLETA │
├─────────────────────────────────────────────────────┤
│ TABLA files │
│ ┌─────────┬──────────────┬──────────────────────┐ │
│ │ id: PK │ fileUrl │ content │ │
│ ├─────────┼──────────────┼──────────────────────┤ │
│ │ 1 │ files/a.mp3 │ "Texto original" │ │
│ │ 2 │ files/b.mp4 │ "Otro texto" │ │
│ └─────────┴──────────────┴──────────────────────┘ │
│ │ │
│ └─────┬────────────────────────────────────┘
│ │
│ ┌─────────▼────────────────────────────────────┐
│ │ TABLA translations │
│ │ ┌─────┬─────────┬─────────────┬──────────┐ │
│ │ │ id │ file_id │ language │ content │ │
│ │ ├─────┼─────────┼─────────────┼──────────┤ │
│ │ │ 1 │ 1 │ English │ "Text" │ │
│ │ │ 2 │ 1 │ French │ "Texte" │ │
│ │ └─────┴─────────┴─────────────┴──────────┘ │
│ └──────────────────────────────────────────────┘
│
│ PK = Primary Key (Clave primaria)
│ FK = Foreign Key (Clave foránea - futuro)
└─────────────────────────────────────────────────────┘
🎯 Puntos Clave para Recordar
PDO::prepare() → Prepara consultas SQL de forma segura
Placeholders (:nombre) → Marcadores para valores variables
bindParam() → Vincula valores a placeholders (evita SQL injection)
strpos() → Encuentra subcadenas para determinar tipos MIME
lastInsertId() → Obtiene el ID del último registro insertado
header("location: ...") → Redirecciona al usuario
📋 Cuestionario de Repaso
Pregunta 1:
¿Cuál es la principal ventaja de usar placeholders (:fileUrl) en consultas SQL?
a) Hace que el código se ejecute más rápido
b) Previene ataques de SQL injection
c) Permite usar menos código
d) Hace que las consultas sean más legibles
Pregunta 2:
¿Qué retorna strpos("audio/mp3", "audio/")?
a) TRUE
b) FALSE
c) 0 (porque "audio/" está al inicio)
d) 6
Pregunta 3:
¿Qué hace el método lastInsertId()?
a) Cuenta cuántos registros hay en la tabla
b) Retorna el último ID autoincremental generado
c) Obtiene el ID del usuario actual
d) Busca el registro más antiguo
Pregunta 4:
¿Por qué necesitamos determinar si un archivo es "audio" o "video"?
a) Para cobrar diferente por cada tipo
b) Para mostrar iconos diferentes en la interfaz
c) Para guardar correctamente en la columna type de la BD
d) Para usar diferentes APIs de OpenAI
Pregunta 5:
¿Qué significa PDO::PARAM_STR en bindParam()?
a) Que el valor es un número
b) Que el valor es una cadena de texto
c) Que el valor es una fecha
d) Que el valor es opcional
📝 Respuestas del Cuestionario
b) Los placeholders previenen SQL injection porque separan los datos del código SQL. Los valores se tratan como datos, no como parte del código ejecutable.
c) strpos() retorna la posición donde encuentra la subcadena. Como "audio/" está al inicio de "audio/mp3", retorna 0 (las posiciones empiezan en 0).
b) lastInsertId() retorna el valor del último ID autoincremental generado por una consulta INSERT, lo que nos permite redireccionar al registro recién creado.
c) Necesitamos determinar el tipo para guardarlo correctamente en la base de datos y poder filtrar, organizar y mostrar los archivos apropiadamente.
b) PDO::PARAM_STR indica que el parámetro es de tipo string (cadena de texto). Esto ayuda a PDO a tratar el valor apropiadamente y añade una capa extra de seguridad.
🚀 Lo que viene después
¡Excelente! Ahora tenemos:
✅ Subida de archivos
✅ Procesamiento con IA
✅ Guardado en base de datos
✅ Redirección automática
En la próxima lección vamos a:
Crear view.php para mostrar archivos individuales
Mostrar el reproductor de audio/video
Exponer el texto transcrito
Preparar la interfaz para traducciones
✨ ¡Gran trabajo! Tu aplicación ahora guarda historial completo de todos los procesamientos. Los usuarios pueden volver a ver sus archivos en cualquier momento.
Comentarios
Publicar un comentario