Implement JQHTML function cache ID system and fix bundle compilation Implement underscore prefix for system tables Fix JS syntax linter to support decorators and grant exception to Task system SPA: Update planning docs and wishlists with remaining features SPA: Document Navigation API abandonment and future enhancements Implement SPA browser integration with History API (Phase 1) Convert contacts view page to SPA action Convert clients pages to SPA actions and document conversion procedure SPA: Merge GET parameters and update documentation Implement SPA route URL generation in JavaScript and PHP Implement SPA bootstrap controller architecture Add SPA routing manual page (rsx:man spa) Add SPA routing documentation to CLAUDE.md Phase 4 Complete: Client-side SPA routing implementation Update get_routes() consumers for unified route structure Complete SPA Phase 3: PHP-side route type detection and is_spa flag Restore unified routes structure and Manifest_Query class Refactor route indexing and add SPA infrastructure Phase 3 Complete: SPA route registration in manifest Implement SPA Phase 2: Extract router code and test decorators Rename Jqhtml_Component to Component and complete SPA foundation setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
7.6 KiB
Executable File
7.6 KiB
Executable File
Task System
RSpade provides a unified task execution system supporting immediate CLI execution, queued async tasks, and scheduled cron-based tasks.
Creating Task Services
Task services must extend Rsx_Service_Abstract and use attributes to define tasks:
use App\RSpade\Core\Service\Rsx_Service_Abstract;
use App\RSpade\Core\Task\Task_Instance;
class My_Service extends Rsx_Service_Abstract
{
// Simple task (can be run via CLI or dispatched to queue)
#[Task('Description of what this task does')]
public static function my_task(Task_Instance $task, array $params = [])
{
$task->info('Task is running');
// Task logic here
return ['result' => 'success'];
}
// Scheduled task (runs automatically via cron)
#[Task('Cleanup old records')]
#[Schedule('0 2 * * *')] // Daily at 2 AM
public static function cleanup(Task_Instance $task, array $params = [])
{
$task->info('Cleanup running');
// Cleanup logic here
}
}
Requirements
- Class MUST extend
Rsx_Service_Abstract - Method MUST be
public static - Method MUST have signature:
(Task_Instance $task, array $params = []) - Method MUST have
#[Task('description')]attribute - Scheduled tasks also need
#[Schedule('cron_expression')]attribute
Important Notes on Attributes
- Attributes work via reflection - NO backing PHP classes needed
- The linter will remove
usestatements for attributes - this is INTENTIONAL and CORRECT - Use
#[Task]and#[Schedule], NOT#[Task_Attribute]or any other variation - Never create PHP classes for these attributes
Task_Instance Methods
The $task parameter provides logging and progress tracking:
public static function process_items(Task_Instance $task, array $params = [])
{
$items = Item_Model::all();
$total = count($items);
$task->info("Processing {$total} items");
foreach ($items as $index => $item) {
// Update progress
$task->progress($index + 1, $total);
// Log info
$task->info("Processing item {$item->id}");
// Log warnings
if ($item->status === 'pending') {
$task->warning("Item {$item->id} is still pending");
}
// Process item
$item->process();
}
$task->info("Completed processing {$total} items");
return ['processed' => $total];
}
Running Tasks
CLI Execution
# List all available tasks
php artisan rsx:task:list
# Run task immediately from CLI (synchronous)
php artisan rsx:task:run Service_Class method_name
# Run with parameters
php artisan rsx:task:run Service_Class method_name '{"param":"value"}'
Programmatic Dispatch
// Dispatch task to queue (async)
use App\RSpade\Core\Task\Task;
Task::dispatch('Service_Class', 'method_name', ['param' => 'value']);
// Dispatch with delay
Task::dispatch('Service_Class', 'method_name', ['param' => 'value'], 60); // 60 second delay
Scheduled Execution
Tasks with #[Schedule] attribute run automatically via cron.
Add to crontab:
* * * * * cd /var/www/html && php artisan rsx:task:process
This runs every minute and:
- Processes any queued tasks
- Runs scheduled tasks that are due
Cron Expression Syntax
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday)
│ │ │ │ │
│ │ │ │ │
* * * * *
Common patterns:
*/5 * * * *- Every 5 minutes*/30 * * * *- Every 30 minutes0 * * * *- Every hour0 2 * * *- Daily at 2 AM0 */6 * * *- Every 6 hours0 0 * * 0- Weekly on Sunday at midnight0 0 1 * *- Monthly on the 1st at midnight30 2 * * 1-5- Weekdays at 2:30 AM
Task Queue
Tasks dispatched via Task::dispatch() are stored in the database queue:
task_queue
├── id (bigint)
├── service_class (varchar)
├── method_name (varchar)
├── parameters (json)
├── status (enum: pending, processing, completed, failed)
├── attempts (int)
├── run_at (timestamp)
├── started_at (timestamp)
├── completed_at (timestamp)
├── error_message (text)
└── timestamps
Error Handling
Tasks that throw exceptions:
- Are marked as
failedin the queue - Error message is logged
- Can be retried manually via CLI
- Maximum 3 automatic retry attempts
public static function risky_task(Task_Instance $task, array $params = [])
{
try {
// Risky operation
$result = External_API::call();
} catch (Exception $e) {
$task->error("API call failed: " . $e->getMessage());
throw $e; // Re-throw to mark task as failed
}
return $result;
}
Best Practices
- Keep tasks idempotent - Safe to run multiple times
- Log progress for long-running tasks
- Return meaningful data for debugging
- Use transactions for database operations
- Set appropriate schedules to avoid overlap
- Handle exceptions gracefully
- Keep tasks focused - one task, one purpose
Common Task Patterns
Cleanup Task
#[Task('Clean old logs')]
#[Schedule('0 3 * * *')] // Daily at 3 AM
public static function cleanup_logs(Task_Instance $task, array $params = [])
{
$cutoff = now()->subDays(30);
$deleted = Log_Model::where('created_at', '<', $cutoff)->delete();
$task->info("Deleted {$deleted} old log entries");
return ['deleted' => $deleted];
}
Import Task
#[Task('Import data from CSV')]
public static function import_csv(Task_Instance $task, array $params = [])
{
$file_path = $params['file_path'] ?? null;
if (!$file_path || !file_exists($file_path)) {
throw new Exception("File not found: {$file_path}");
}
$handle = fopen($file_path, 'r');
$row_count = 0;
while (($data = fgetcsv($handle)) !== false) {
$row_count++;
$task->progress($row_count);
// Process row
Model::create([
'field1' => $data[0],
'field2' => $data[1],
]);
}
fclose($handle);
$task->info("Imported {$row_count} rows");
return ['imported' => $row_count];
}
Report Generation
#[Task('Generate monthly report')]
#[Schedule('0 0 1 * *')] // First day of month at midnight
public static function monthly_report(Task_Instance $task, array $params = [])
{
$start = now()->subMonth()->startOfMonth();
$end = now()->subMonth()->endOfMonth();
$task->info("Generating report for {$start->format('F Y')}");
$data = Sales_Model::whereBetween('created_at', [$start, $end])
->get();
$report = Report_Generator::create($data);
$report->save(storage_path("reports/monthly-{$start->format('Y-m')}.pdf"));
$task->info("Report generated successfully");
return [
'period' => $start->format('F Y'),
'records' => count($data),
];
}
Monitoring Tasks
View task history and status:
# View pending tasks
php artisan rsx:task:pending
# View failed tasks
php artisan rsx:task:failed
# Retry failed task
php artisan rsx:task:retry {task_id}
# Clear completed tasks older than 30 days
php artisan rsx:task:clear --days=30
Service Discovery
The manifest system automatically discovers all services extending Rsx_Service_Abstract in directories configured for scanning. Ensure your service directory is included in the manifest's scan_directories configuration.