Table of Contents
You dispatch a Laravel job. It runs fine locally. You push to production, and suddenly: CallbackNotFound errors everywhere.
The culprit? A closure inside your job that references $this->someService.
The Problem
Here’s what breaks:
class ProcessOrder implements ShouldQueue
{
use SerializesModels;
public function __construct(
private PaymentService $paymentService
) {}
public function handle()
{
$orders = Order::pending()->get();
// This closure references $this
$orders->each(function ($order) {
// π₯ BOOM after serialization
$this->paymentService->charge($order);
});
}
}
When Laravel serializes the job for the queue, closures can’t capture $this properly. The callback becomes unresolvable after deserialization, and your job dies silently or throws cryptic errors.
The Fix
Stop referencing $this inside closures. Use app() to resolve the service fresh from the container:
class ProcessOrder implements ShouldQueue
{
use SerializesModels;
public function handle()
{
$orders = Order::pending()->get();
// β
Resolve fresh from container
$orders->each(function ($order) {
app(PaymentService::class)->charge($order);
});
}
}
Now the closure is self-contained. Every time it runs, it pulls the service from the container. No serialization issues, no broken callbacks.
Why It Works
Queue jobs get serialized to JSON and stored (database, Redis, SQS, etc.). When the worker picks up the job:
- Laravel deserializes the job class
- Runs your
handle()method - Executes any closures inside
But PHP can’t serialize object references inside closures. Using app() defers service resolution until the closure actually runs β after deserialization.
Bonus: Same Rule for Collection Transforms
This isn’t just a queue problem. Any time you serialize data with closures (caching collections, API responses, etc.), the same rule applies:
// β Breaks if serialized
$cached = $items->map(fn($item) => $this->transformer->format($item));
// β
Safe
$cached = $items->map(fn($item) => app(Transformer::class)->format($item));
The Takeaway
Never use $this-> inside closures in queue jobs. Resolve services from the container with app() or pass them as closure parameters instead.
Your queue workers will thank you.

Leave a Reply