spatie/laravel-long-running-tasks
Monitor externally executed long-running tasks in Laravel (e.g., AWS Rekognition) by polling for status. Define tasks with a check() method returning ContinueChecking or StopChecking, store metadata, and run checks on a configurable interval until completion.
Installation:
composer require spatie/laravel-long-running-tasks
Publish the config (optional):
php artisan vendor:publish --provider="Spatie\LongRunningTasks\LongRunningTasksServiceProvider"
Define a Task Model:
Extend Spatie\LongRunningTasks\LongRunningTask and implement getTaskId() and getStatus():
use Spatie\LongRunningTasks\LongRunningTask;
class MyExternalTask extends LongRunningTask
{
public function getTaskId(): string
{
return 'external-task-id-123';
}
public function getStatus(): string
{
return $this->externalService->getStatus();
}
}
Run the Poller: Add a cron job to poll task statuses (e.g., every minute):
* * * * * php artisan long-running:tasks:check
Trigger a Task: Start a task and return immediately:
$task = MyExternalTask::create([
'data' => ['key' => 'value'],
]);
$task->start();
// Start a Rekognition job
$task = new RekognitionTask($imageId);
$task->start();
// Return immediately; the task will poll for completion
return response()->json(['task_id' => $task->id]);
Task Creation:
$task = MyTask::create(['input' => $data]);
$task->start(); // Triggers initial poll
Polling Logic:
Override getStatus() to query the external service:
public function getStatus(): string
{
$response = Http::get("https://api.example.com/status/{$this->getTaskId()}");
return $response->json()['status'];
}
Result Handling:
Implement handleResult() for post-completion actions:
public function handleResult(TaskResult $result)
{
if ($result === TaskResult::SUCCESS) {
$this->processSuccess();
}
}
Queue Integration:
Use Laravel queues to defer polling (e.g., dispatch(new CheckTaskJob($task))).
class CheckTaskJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue;
public function handle()
{
$task = MyTask::find($this->taskId);
$task->checkStatus();
}
}
Webhooks + Polling: Combine with webhooks for efficiency:
public function getStatus(): string
{
if ($this->isWebhookReceived()) {
return TaskResult::SUCCESS;
}
return $this->pollExternalService();
}
Retry Logic:
Use shouldRetry() to control retries:
public function shouldRetry(): bool
{
return $this->attempts < 5 && $this->getStatus() === 'PENDING';
}
Infinite Polling:
shouldRetry(), tasks may poll indefinitely.shouldRetry() to limit attempts.Race Conditions:
public function checkStatus()
{
DB::transaction(function () {
$this->updateStatus();
});
}
Stale Data:
lastCheckedAt field and validate freshness:
public function getStatus(): string
{
if ($this->lastCheckedAt->diffInMinutes() > 5) {
throw new \RuntimeException('Stale data');
}
return $this->externalService->getStatus();
}
Log Polling: Enable debug mode in config:
'debug' => env('LONG_RUNNING_TASKS_DEBUG', false),
Logs will show polling attempts and status changes.
Manual Checks: Use Artisan to force-check a task:
php artisan long-running:tasks:check --task=1
Custom Statuses:
Extend TaskResult enum or add a customStatus field:
public function getStatus(): string
{
return match ($this->externalStatus) {
'PROCESSING' => TaskResult::IN_PROGRESS,
'FAILED' => TaskResult::FAILURE,
default => TaskResult::SUCCESS,
};
}
Task Timeouts:
Add a timeoutAt field and cancel tasks automatically:
public function checkStatus()
{
if (now()->gt($this->timeoutAt)) {
$this->markAsFailed('Timeout');
}
}
Event Listeners:
Listen for task events (e.g., TaskStarted, TaskCompleted):
public function boot()
{
LongRunningTask::started(fn ($task) => Log::info("Task started: {$task->id}"));
}
Polling Interval:
Adjust polling_interval in config (default: 60 seconds).
'polling_interval' => env('LONG_RUNNING_TASKS_POLLING_INTERVAL', 60),
Queue Connection: Specify a queue for polling jobs:
'queue_connection' => env('LONG_RUNNING_TASKS_QUEUE', 'default'),
How can I help you explore Laravel packages today?