spatie/laravel-long-running-tasks
Monitor and poll external long-running jobs (e.g., AWS Rekognition) in Laravel. Define a task with a check() method that runs on a configurable interval, store meta/context, and automatically reschedule until it reports completion.
Installation:
composer require spatie/laravel-long-running-tasks
php artisan vendor:publish --tag="long-running-tasks-migrations"
php artisan vendor:publish --tag="long-running-tasks-config"
php artisan migrate
Ensure your Laravel queues are configured (e.g., database, Redis, SQS).
First Task Creation:
Create a task class extending Spatie\LongRunningTask:
namespace App\Tasks;
use Spatie\LongRunningTasks\LongRunningTask;
use Spatie\LongRunningTasks\Enums\TaskResult;
use Spatie\LongRunningTasks\Models\LongRunningTaskLogItem;
class ProcessExternalData extends LongRunningTask
{
public function check(LongRunningTaskLogItem $logItem): TaskResult
{
$data = $logItem->meta['data'];
$isDone = $this->isProcessingComplete($data);
return $isDone ? TaskResult::StopChecking : TaskResult::ContinueChecking;
}
private function isProcessingComplete(array $data): bool
{
// Your logic to check completion
return false;
}
}
Start the Task:
ProcessExternalData::make()
->meta(['data' => $yourData])
->start();
config/long-running-tasks.php for default settings (queue, frequency, strategies).long_running_task_log_items to inspect task statuses.app/Jobs for custom job implementations if needed.Polling an External API:
class PollExternalApi extends LongRunningTask
{
public function check(LongRunningTaskLogItem $logItem): TaskResult
{
$response = Http::get($logItem->meta['api_url']);
$status = $response->json()['status'];
if ($status === 'completed') {
return TaskResult::StopChecking;
}
return TaskResult::ContinueChecking;
}
}
// Start with metadata
PollExternalApi::make()
->meta(['api_url' => 'https://api.example.com/task/123'])
->checkFrequencyInSeconds(15) // Custom interval
->start();
Task Definition:
Extend LongRunningTask and implement the check() method. Use TaskResult to control task lifecycle.
class MyTask extends LongRunningTask {
public function check(LongRunningTaskLogItem $logItem): TaskResult {
// Business logic here
return $this->shouldContinue ? TaskResult::ContinueChecking : TaskResult::StopChecking;
}
}
Task Initialization: Use fluent methods to configure task behavior before starting:
MyTask::make()
->meta(['key' => 'value']) // Pass metadata
->checkFrequencyInSeconds(30) // Override default frequency
->checkStrategy(StandardBackoffCheckStrategy::class) // Custom strategy
->onQueue('high-priority') // Custom queue
->keepCheckingForInSeconds(3600) // Timeout
->start();
Error Handling:
Implement onFail() to handle exceptions gracefully:
public function onFail(LongRunningTaskLogItem $logItem, Exception $exception): ?TaskResult {
Log::error("Task failed: " . $exception->getMessage());
return TaskResult::ContinueChecking; // Retry or not
}
Monitoring:
Query the LongRunningTaskLogItem model to track task statuses:
$pendingTasks = LongRunningTaskLogItem::where('status', 'pending')->get();
$failedTasks = LongRunningTaskLogItem::where('status', 'failed')->with('latestException')->get();
Queue Workers: Ensure your queue worker (php artisan queue:work) is running to process tasks.
Event Listeners: Listen to task events (e.g., TaskStarted, TaskCompleted) for notifications or side effects:
LongRunningTaskLogItem::created(function ($logItem) {
event(new TaskStarted($logItem));
});
Custom Models: Extend LongRunningTaskLogItem for additional fields or logic:
class CustomTaskLog extends LongRunningTaskLogItem {
protected $casts = [
'custom_field' => 'string',
];
}
Update the config to use your custom model:
'log_model' => App\Models\CustomTaskLog::class,
Testing:
Use LongRunningTaskLogItem factories or mock the check() method in unit tests:
$task = MyTask::make()->meta(['test' => true]);
$this->assertEquals(TaskResult::StopChecking, $task->check($logItem));
Queue Stuck Jobs:
running status, check for deadlocks or infinite loops in check().php artisan queue:failed to inspect failed jobs and retry manually.Timeouts:
didNotComplete may indicate:
keepCheckingForInSeconds).checkFrequencyInSeconds (e.g., too high for real-time needs).stop_checking_at to debug stalled tasks.Metadata Handling:
meta is serializable (avoid passing objects or resources).Strategy Misuse:
StandardBackoffCheckStrategy and ExponentialBackoffCheckStrategy can lead to long delays if not monitored. Use for non-critical tasks.// Default: 10s, 60s, 120s, 300s, 600s
$task->checkStrategy(ExponentialBackoffCheckStrategy::class);
Concurrency Issues:
check() by using transactions or optimistic locking if modifying shared resources.Log Task Execution:
Add logging in check() to trace progress:
Log::info("Task run #{$logItem->run_count}: Processing {$logItem->meta['item_id']}");
Inspect Log Items: Use Tinker to debug:
php artisan tinker
>>> $logItem = \App\Models\LongRunningTaskLogItem::find(1);
>>> $logItem->latest_exception; // Check last error
>>> $logItem->meta; // Verify metadata
Queue Monitoring:
queue:listen to observe job processing.failed_jobs table for exceptions.Custom Exceptions:
Throw descriptive exceptions in check() to aid debugging:
throw new \RuntimeException("Failed to fetch data for ID: {$logItem->meta['id']}");
Custom Strategies:
Create a new strategy by implementing Spatie\LongRunningTasks\Contracts\CheckStrategy:
class CustomStrategy implements CheckStrategy {
public function getCheckFrequencyInSeconds(LongRunningTaskLogItem $logItem): int {
return $logItem->run_count > 3 ? 60 : 10;
}
}
Pre/Post Hooks: Use model events to add behavior:
LongRunningTaskLogItem::created(function ($logItem) {
// Send notification when task starts
});
LongRunningTaskLogItem::updated(function ($logItem) {
if ($logItem->isDirty('status') && $logItem->status === 'completed') {
// Clean up resources
}
});
Bulk Operations: Process multiple tasks in parallel by starting them with unique metadata:
foreach ($items as $item) {
ProcessItemTask::make()
->meta(['item_id' => $item->id])
->onQueue('bulk-processing')
->start();
}
Retry Logic:
Implement custom retry logic in onFail():
public function onFail(LongRunningTaskLogItem $logItem, Exception $exception): ?TaskResult {
if ($logItem->attempt < 3 && $exception instanceof \HttpException) {
return TaskResult::ContinueChecking; // Retry up to 3 times
}
return null; // Give up
}
Performance Optimization:
check() to reduce overhead.checkFrequencyInSeconds wisely: shorter intervals increase load but improve responsiveness.How can I help you explore Laravel packages today?