The job class holds the order id and the attempt. The controller already has enough to do: authorise, validate, redirect. Dispatching the job is a decision about the order, so it belongs with the rest of that decision.
Queues in production · Lesson 1
Dispatch from actions, not controllers
Video
01 One action owns the hand-off
The action receives the order and the attempt key. The controller does not mention the job class.
class PayOrder
{
public function __invoke(Order $order, string $attempt): void
{
MarkOrderPaid::dispatch($order->id, $attempt);
}
}class Example
{
public function store(PayOrderRequest $request, PayOrder $pay): RedirectResponse
{
$order = Order::query()->create([
'number' => $request->validated('number'),
'status' => 'pending',
]);
$pay($order, (string) Str::uuid());
return redirect()->route('orders.show', $order);
}
}A later test can swap the action, or call it from a command, without copying the dispatch line into a second controller. The job class stays the thing that knows how to mark the order paid.
02 Refuse before a second job is queued
An order that is already paid should not gain another job from a second click. The action can see the row, because the request has just loaded it. Returning here skips the dispatch. The controller still does not mention the job class.
class Example
{
public function __invoke(Order $order, string $attempt): void
{
if ($order->status === 'paid') {
return;
}
MarkOrderPaid::dispatch($order->id, $attempt);
}
}This check is the request’s decision. The job’s own check, in Make the job safe to run twice, is the worker’s decision. A retry happens after the request has finished, so the action is no longer on the stack. Both checks stay. One of them is not a substitute for the other.
Recap
You can dispatch from the action that decided to take payment, not from the controller.
- The controller authorises, validates, and redirects.
- A second click can return before a second job is queued.
- The job still checks again, because a retry happens after the action has gone.
Check yourself · 3 questions
Question 1 of 3The action returns early because the order is paid. A retry of the job still runs. Why is the action's check not enough?