⏳ Long Running Processes with Server-Side Events (SSE)
Ragnos framework includes implementation for handling long-running processes on server, allowing real-time progress updates to user browser using Server-Side Events (SSE).
Ideal for tasks exceeding usual HTTP request timeout or where user needs visual feedback of progress (progress bars).
PHP Time Limits
Although SSE keeps connection open, web server or PHP config (max_execution_time) might kill process if too long. Ensure set_time_limit(0) in logic if expecting very long processes.
Architecture
System based on two main components:
App\ThirdParty\Ragnos\Controllers\RProcessController: Base class for controllers executing processes.process_helper.php: Helper containing functions managing SSE event flow (headers, sending data, buffer control, etc.).
Workflow
- User accesses controller action rendering progress view (method
showProgress). - View connects via JavaScript (
EventSource) tostartmethod of same controller. startmethod executes heavy logic, sending progress events periodically via helper functions.- Upon completion, server sends finish event and closes connection.
RProcessController Class
To create new process, must create controller extending RProcessController.
Location: App\Controllers\ or any subfolder, provided namespace correct.
Main Methods
__construct(): Automatically loadsprocess_helper.showProgress(): Shows UI with progress bar. Automatically calculates URL ofstartendpoint based on class name.start(): Must be overridden. Logic of long running process resides here.
Helper Functions (process_helper)
These functions available automatically inside extended controller.
processStart($title = 'Processing...')
Initializes SSE process.
- Clears output buffers and sets
text/event-streamheaders. - Disables time limits (
set_time_limit(0)) and memory in PHP to avoid timeouts. - Sends process title to client.
setProgress($percentage)
Updates progress bar to indicated percentage.
$percentage: Integer (0-100).
setProgressText($text)
Updates descriptive text below progress bar. Useful to inform user what exactly is being done (e.g. "Processing row 45...").
setProgressOf($currentStep, $total)
Utility function calculating percentage automatically and calling setProgress.
$currentStep: Current step (iterator).$total: Total steps to perform.
endProcess($additionalData = null)
Finalizes process.
- Calculates total execution time.
- Sends finish event to client with optional data.
- Ends script execution (
exit).
Common Applications
Useful for:
- Bulk Imports: Uploading Excel/CSV files with thousands of records to DB.
- Report Generation: Creating complex PDFs or large excels.
- Maintenance: Cleanup tasks, price recalculation, cache or image regeneration.
- Mass Emails: Sending newsletters requiring feedback per sent block.
Implementation Examples
Example 1: Price Recalculation (Basic)
Simulates process iterating over 100 items.
<?php
namespace App\Controllers;
use App\ThirdParty\Ragnos\Controllers\RProcessController;
class RecalculatePrices extends RProcessController
{
// Main method executing logic
public function start()
{
// 1. Start process
processStart('Recalculating catalog prices');
$totalProducts = 100;
for ($i = 1; $i <= $totalProducts; $i++) {
// Simulate heavy work
usleep(100000); // 0.1 seconds
// 2. Update progress (calculates % automatically)
setProgressOf($i, $totalProducts);
// 3. Update info text
setProgressText("Updating product ID: #{$i}");
}
// 4. Finish
endProcess([
'message' => 'Prices updated successfully',
'total' => $totalProducts
]);
}
}
Example 2: User Import (With Validation)
More complex example simulating import where info validated.
<?php
namespace App\Controllers;
use App\ThirdParty\Ragnos\Controllers\RProcessController;
class ImportUsers extends RProcessController
{
public function start()
{
processStart('Importing User Database');
// Simulate data fetching (e.g. read CSV)
$users = $this->getSimulatedData(500);
$total = count($users);
$imported = 0;
$errors = 0;
foreach ($users as $index => $user) {
$step = $index + 1;
// Simulated business logic
if ($this->saveUser($user)) {
$imported++;
setProgressText("User {$user['name']} imported.");
} else {
$errors++;
setProgressText("Error importing {$user['name']}.");
}
// Update progress bar every 5 records to not saturate network
// in very fast processes
if ($step % 5 === 0 || $step === $total) {
setProgressOf($step, $total);
}
// Simulate DB time
usleep(20000);
}
endProcess([
'result' => "Import finished. Success: {$imported}, Errors: {$errors}"
]);
}
private function getSimulatedData($qty) { /* ... */ return array_fill(0, $qty, ['name' => 'User']); }
private function saveUser($user) { return rand(0, 10) > 1; }
}
Example 3: Backup Generation
<?php
namespace App\Controllers\Admin;
use App\ThirdParty\Ragnos\Controllers\RProcessController;
class BackupSystem extends RProcessController
{
public function start()
{
processStart('Generating System Backup');
$steps = [
'Compressing images...',
'Exporting database...',
'Generating log files...',
'Packaging final ZIP...'
];
$totalSteps = count($steps);
foreach ($steps as $index => $stepName) {
setProgressText($stepName);
// Simulate long task
sleep(2);
setProgressOf($index + 1, $totalSteps);
}
endProcess(['file' => 'backup_2023.zip']);
}
}