Files
rspade_system/app/RSpade/Core/Task/Task.php
root 9ebcc359ae Fix code quality violations and enhance ROUTE-EXISTS-01 rule
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>
2025-11-19 17:48:15 +00:00

345 lines
12 KiB
PHP
Executable File

<?php
/**
* CODING CONVENTION:
* This file follows the coding convention where variable_names and function_names
* use snake_case (underscore_wherever_possible).
*/
namespace App\RSpade\Core\Task;
use Exception;
use Illuminate\Support\Facades\DB;
use App\RSpade\Core\Manifest\Manifest;
use App\RSpade\Core\Service\Rsx_Service_Abstract;
use App\RSpade\Core\Task\Task_Instance;
use App\RSpade\Core\Task\Task_Status;
/**
* Task - Unified task execution system
*
* Handles background task execution:
* - Internal PHP task calls (internal method)
* - Future: Queue integration, scheduling, progress tracking
*/
class Task
{
/**
* Execute a task internally from PHP code
*
* Used for server-side code to invoke tasks without CLI overhead.
* This is useful for calling tasks from other tasks, background jobs, etc.
*
* @param string $rsx_service Service name (e.g., 'Seeder_Service')
* @param string $rsx_task Task/method name (e.g., 'seed_clients')
* @param array $params Parameters to pass to the task
* @return mixed The response from the task method
* @throws Exception
*/
public static function internal($rsx_service, $rsx_task, $params = [])
{
// Get manifest to find service
$manifest = Manifest::get_all();
$service_class = null;
$file_info = null;
// Search for service class in manifest
foreach ($manifest as $file_path => $info) {
// Skip non-PHP files or files without classes
if (!isset($info['class']) || !isset($info['fqcn'])) {
continue;
}
// Check if class name matches exactly (without namespace)
$class_basename = basename(str_replace('\\', '/', $info['fqcn']));
if ($class_basename === $rsx_service) {
$service_class = $info['fqcn'];
$file_info = $info;
break;
}
}
if (!$service_class) {
throw new Exception("Service class not found: {$rsx_service}");
}
// Check if class exists
if (!class_exists($service_class)) {
throw new Exception("Service class does not exist: {$service_class}");
}
// Check if it's a subclass of Rsx_Service_Abstract
if (!Manifest::php_is_subclass_of($service_class, Rsx_Service_Abstract::class)) {
throw new Exception("Service {$service_class} must extend Rsx_Service_Abstract");
}
// Check if method exists and has Task attribute
if (!isset($file_info['public_static_methods'][$rsx_task])) {
throw new Exception("Task {$rsx_task} not found in service {$service_class}");
}
$method_info = $file_info['public_static_methods'][$rsx_task];
$has_task = false;
// Check for Task attribute in method metadata
if (isset($method_info['attributes'])) {
foreach ($method_info['attributes'] as $attr_name => $attr_instances) {
if ($attr_name === 'Task' || str_ends_with($attr_name, '\\Task')) {
$has_task = true;
break;
}
}
}
if (!$has_task) {
throw new Exception("Method {$rsx_task} in service {$service_class} must have #[Task] attribute");
}
// Create task instance for immediate execution
$task_instance = new Task_Instance(
$service_class,
$rsx_task,
$params,
'default',
true // immediate execution
);
// Mark as started
$task_instance->mark_started();
try {
// Call pre_task() if exists
if (method_exists($service_class, 'pre_task')) {
$pre_result = $service_class::pre_task($task_instance, $params);
if ($pre_result !== null) {
// pre_task returned something, use that as response
$task_instance->mark_completed($pre_result);
return $pre_result;
}
}
// Call the actual task method
$response = $service_class::$rsx_task($task_instance, $params);
// Mark as completed
$task_instance->mark_completed($response);
// Filter response through JSON encode/decode to remove PHP objects
// (similar to Ajax behavior)
$filtered_response = json_decode(json_encode($response), true);
return $filtered_response;
} catch (Exception $e) {
// Mark as failed
$task_instance->mark_failed($e->getMessage());
throw $e;
}
}
/**
* Format task response for CLI output
* Wraps the response in a consistent format
*
* @param mixed $response Task return value
* @return array Formatted response
*/
public static function format_task_response($response): array
{
return [
'success' => true,
'result' => $response,
];
}
/**
* Dispatch a task to the queue for async execution
*
* Creates a database record for the task and returns the task ID.
* Task will be picked up and executed by the task processor (rsx:task:process).
*
* @param string $rsx_service Service name (e.g., 'Seeder_Service')
* @param string $rsx_task Task/method name (e.g., 'seed_clients')
* @param array $params Parameters to pass to the task
* @param array $options Optional task options:
* - 'queue' => Queue name (default: 'default')
* - 'scheduled_for' => Timestamp when task should run (default: now)
* - 'timeout' => Maximum execution time in seconds (default: from config)
* @return int Task ID
* @throws Exception
*/
public static function dispatch(string $rsx_service, string $rsx_task, array $params = [], array $options = []): int
{
// Get manifest to find service
$manifest = Manifest::get_all();
$service_class = null;
$file_info = null;
// Search for service class in manifest
foreach ($manifest as $file_path => $info) {
// Skip non-PHP files or files without classes
if (!isset($info['class']) || !isset($info['fqcn'])) {
continue;
}
// Check if class name matches exactly (without namespace)
$class_basename = basename(str_replace('\\', '/', $info['fqcn']));
if ($class_basename === $rsx_service) {
$service_class = $info['fqcn'];
$file_info = $info;
break;
}
}
if (!$service_class) {
throw new Exception("Service class not found: {$rsx_service}");
}
// Check if class exists
if (!class_exists($service_class)) {
throw new Exception("Service class does not exist: {$service_class}");
}
// Check if it's a subclass of Rsx_Service_Abstract
if (!Manifest::php_is_subclass_of($service_class, Rsx_Service_Abstract::class)) {
throw new Exception("Service {$service_class} must extend Rsx_Service_Abstract");
}
// Check if method exists and has Task attribute
if (!isset($file_info['public_static_methods'][$rsx_task])) {
throw new Exception("Task {$rsx_task} not found in service {$service_class}");
}
$method_info = $file_info['public_static_methods'][$rsx_task];
$has_task = false;
// Check for Task attribute in method metadata
if (isset($method_info['attributes'])) {
foreach ($method_info['attributes'] as $attr_name => $attr_instances) {
if ($attr_name === 'Task' || str_ends_with($attr_name, '\\Task')) {
$has_task = true;
break;
}
}
}
if (!$has_task) {
throw new Exception("Method {$rsx_task} in service {$service_class} must have #[Task] attribute");
}
// Create task instance
$instance = new Task_Instance(
$service_class,
$rsx_task,
$params,
$options['queue'] ?? 'default',
false // not immediate
);
// Create database record
$data = [
'class' => $service_class,
'method' => $rsx_task,
'queue' => $options['queue'] ?? 'default',
'status' => Task_Status::PENDING,
'params' => json_encode($params),
'scheduled_for' => $options['scheduled_for'] ?? now(),
'timeout' => $options['timeout'] ?? config('rsx.tasks.default_timeout'),
'created_at' => now(),
'updated_at' => now(),
];
$task_id = DB::table('_task_queue')->insertGetId($data);
return $task_id;
}
/**
* Get the status of a task
*
* Returns task information including status, logs, result, and error.
*
* @param int $task_id Task ID
* @return array|null Task status data or null if not found
*/
public static function status(int $task_id): ?array
{
$row = DB::table('_task_queue')->where('id', $task_id)->first();
if (!$row) {
return null;
}
return [
'id' => $row->id,
'class' => $row->class,
'method' => $row->method,
'queue' => $row->queue,
'status' => $row->status,
'params' => json_decode($row->params, true),
'result' => json_decode($row->result, true),
'logs' => $row->logs ? explode("\n", $row->logs) : [],
'error' => $row->error,
'scheduled_for' => $row->scheduled_for,
'started_at' => $row->started_at,
'completed_at' => $row->completed_at,
'created_at' => $row->created_at,
'updated_at' => $row->updated_at,
];
}
/**
* Get all scheduled tasks from manifest
*
* Scans the manifest for methods with #[Schedule] attribute
* and returns information about each scheduled task.
*
* @return array Array of scheduled task definitions
*/
public static function get_scheduled_tasks(): array
{
$manifest = Manifest::get_all();
$scheduled_tasks = [];
foreach ($manifest as $file_path => $info) {
// Skip non-PHP files or files without classes
if (!isset($info['class']) || !isset($info['fqcn'])) {
continue;
}
// Check if it's a service class
if (!isset($info['public_static_methods'])) {
continue;
}
foreach ($info['public_static_methods'] as $method_name => $method_info) {
// Check for Schedule attribute
if (!isset($method_info['attributes'])) {
continue;
}
foreach ($method_info['attributes'] as $attr_name => $attr_instances) {
if ($attr_name === 'Schedule' || str_ends_with($attr_name, '\\Schedule')) {
// Found a scheduled task
foreach ($attr_instances as $attr_instance) {
$cron_expression = $attr_instance[0] ?? null;
$queue = $attr_instance[1] ?? 'scheduled';
if ($cron_expression) {
$scheduled_tasks[] = [
'class' => $info['fqcn'],
'method' => $method_name,
'cron_expression' => $cron_expression,
'queue' => $queue,
];
}
}
}
}
}
}
return $scheduled_tasks;
}
}