⏳ Procesos de Larga Duración con Server-Side Events (SSE)
El framework Ragnos incluye una implementación para manejar procesos de larga duración en el servidor, permitiendo enviar actualizaciones de progreso en tiempo real al navegador del usuario utilizando Server-Side Events (SSE).
Esta funcionalidad es ideal para tareas que exceden el tiempo de espera habitual de una petición HTTP o donde el usuario necesita retroalimentación visual del avance (barras de progreso).
Límites de tiempo en PHP
Aunque SSE permite mantener la conexión abierta, tu servidor web o configuración de PHP (max_execution_time) podría matar el proceso si dura demasiado. Asegúrate de configurar set_time_limit(0) en tu lógica si esperas procesos muy largos.
Arquitectura
El sistema se basa en dos componentes principales:
App\ThirdParty\Ragnos\Controllers\RProcessController: Una clase base para los controladores que ejecutarán procesos.process_helper.php: Un helper que contiene las funciones para gestionar el flujo de eventos SSE (cabeceras, envío de datos, control de bufer, etc.).
Flujo de Trabajo
- El usuario accede a una acción del controlador que renderiza la vista de progreso (método
showProgress). - La vista conecta mediante JavaScript (
EventSource) al métodostartdel mismo controlador. - El método
startejecuta la lógica pesada, enviando eventos de progreso periódicamente mediante las funciones del helper. - Al finalizar, el servidor envía un evento de finalización y cierra la conexión.
Clase RProcessController
Para crear un nuevo proceso, debes crear un controlador que extienda de RProcessController.
Ubicación: App\Controllers\ o cualquier subcarpeta, siempre que el namespace sea correcto.
Métodos Principales
__construct(): Carga automáticamente elprocess_helper.showProgress(): Muestra la interfaz de usuario con la barra de progreso. Calcula automáticamente la URL del endpointstartbasándose en el nombre de la clase.start(): Debe ser sobreescrito. Aquí reside la lógica de tu proceso larga duración.
Funciones del Helper (process_helper)
Estas funciones están disponibles automáticamente dentro de tu controlador extendido.
processStart($title = 'Processing...')
Inicializa el proceso SSE.
- Limpia buffers de salida y establece las cabeceras
text/event-stream. - Deshabilita límites de tiempo (
set_time_limit(0)) y memoria en PHP para evitar timeouts. - Envía el título del proceso al cliente.
setProgress($percentage)
Actualiza la barra de progreso al porcentaje indicado.
$percentage: Número entero (0-100).
setProgressText($text)
Actualiza el texto descriptivo debajo de la barra de progreso. Útil para informar al usuario qué se está haciendo exactamente (ej: "Procesando fila 45...").
setProgressOf($currentStep, $total)
Una función de utilidad que calcula el porcentaje automáticamente y llama a setProgress.
$currentStep: El paso actual (iterador).$total: El total de pasos a realizar.
endProcess($additionalData = null)
Finaliza el proceso.
- Calcula el tiempo total de ejecución.
- Envía el evento de finalización al cliente con datos opcionales.
- Termina la ejecución del script (
exit).
Aplicaciones Comunes
Esta funcionalidad es útil para:
- Importaciones Masivas: Carga de archivos Excel/CSV con miles de registros a la base de datos.
- Generación de Reportes: Creación de PDFs complejos o excels grandes.
- Mantenimiento: Tareas de limpieza, recálculo de precios, regeneración de caché o imágenes.
- Envío de Correos Masivos: Envío de newsletters donde se requiere feedback de cada bloque enviado.
Ejemplos de Implementación
Ejemplo 1: Recálculo de Precios (Básico)
Este ejemplo simula un proceso que itera sobre 100 elementos.
<?php
namespace App\Controllers;
use App\ThirdParty\Ragnos\Controllers\RProcessController;
class RecalculoPrecios extends RProcessController
{
// Método principal que ejecuta la lógica
public function start()
{
// 1. Iniciar el proceso
processStart('Recalculando precios del catálogo');
$totalProductos = 100;
for ($i = 1; $i <= $totalProductos; $i++) {
// Simulamos trabajo pesado
usleep(100000); // 0.1 segundos
// 2. Actualizar progreso (calcula % automáticamente)
setProgressOf($i, $totalProductos);
// 3. Actualizar texto informativo
setProgressText("Actualizando producto ID: #{$i}");
}
// 4. Finalizar
endProcess([
'mensaje' => 'Se han actualizado los precios correctamente',
'total' => $totalProductos
]);
}
}
Ejemplo 2: Importación de Usuarios (Con validación)
Un ejemplo más complejo simulando una importación donde se valida información.
<?php
namespace App\Controllers;
use App\ThirdParty\Ragnos\Controllers\RProcessController;
class ImportarUsuarios extends RProcessController
{
public function start()
{
processStart('Importando Base de Datos de Usuarios');
// Simular obtención de datos (ej. leer un CSV)
$usuarios = $this->obtenerDatosSimulados(500);
$total = count($usuarios);
$importados = 0;
$errores = 0;
foreach ($usuarios as $index => $usuario) {
$paso = $index + 1;
// Lógica de negocio simulada
if ($this->guardarUsuario($usuario)) {
$importados++;
setProgressText("Usuario {$usuario['nombre']} importado.");
} else {
$errores++;
setProgressText("Error importando {$usuario['nombre']}.");
}
// Actualizar barra de progreso cada 5 registros para no saturar la red
// en procesos muy rápidos
if ($paso % 5 === 0 || $paso === $total) {
setProgressOf($paso, $total);
}
// Simular tiempo de base de datos
usleep(20000);
}
endProcess([
'resultado' => "Importación finalizada. Éxito: {$importados}, Errores: {$errores}"
]);
}
private function obtenerDatosSimulados($qty) { /* ... */ return array_fill(0, $qty, ['nombre' => 'User']); }
private function guardarUsuario($user) { return rand(0, 10) > 1; }
}
Ejemplo 3: Generación de Copia de Seguridad
<?php
namespace App\Controllers\Admin;
use App\ThirdParty\Ragnos\Controllers\RProcessController;
class BackupSystem extends RProcessController
{
public function start()
{
processStart('Generando Backup del Sistema');
$steps = [
'Comprimiendo imágenes...',
'Exportando base de datos...',
'Generando archivos de log...',
'Empaquetando ZIP final...'
];
$totalSteps = count($steps);
foreach ($steps as $index => $stepName) {
setProgressText($stepName);
// Simular tarea larga
sleep(2);
setProgressOf($index + 1, $totalSteps);
}
endProcess(['file' => 'backup_2023.zip']);
}
}