php artisan make:job SendOrderConfirmation writes a class that implements ShouldQueue and uses Queueable. The constructor runs in the request; handle() runs in the worker and may type-hint services for the container to inject:
class SendOrderConfirmation implements ShouldQueue
{
use Queueable;
public function __construct(public Order $order) {}
public function handle(): void
{
$email = $this->order->customer->email;
usleep(300_000); // stands in for the SMTP round trip (Section 5.17)
Log::info("confirmation for order {$this->order->id} sent to {$email}");
}
}Queueable bundles Dispatchable, InteractsWithQueue (attempts(), release(), fail()), the bus settings and SerializesModels. In tinker:
use App\Jobs\SendOrderConfirmation;
use App\Models\Order;
$order = Order::find(3);
$t = hrtime(true);
SendOrderConfirmation::dispatchSync($order);
printf("dispatchSync: %.0f ms\n", (hrtime(true) - $t) / 1e6);
$t = hrtime(true);
SendOrderConfirmation::dispatch($order);
printf("dispatch: %.0f ms\n", (hrtime(true) - $t) / 1e6);
$payload = DB::table('jobs')->latest('id')->value('payload');
preg_match('/ModelIdentifier.*?"id";i:\d+/', json_decode($payload)->data->command, $m);
echo str_replace("\0", '', $m[0]), "\n";Output
dispatchSync: 322 ms
dispatch: 18 ms
ModelIdentifier":5:{s:5:"class";s:16:"App\Models\Order";s:2:"id";i:3Queuing cut the caller's cost from 322 ms to an 18 ms insert. The payload holds only a ModelIdentifier, so the worker reloads a fresh order; a deleted one throws ModelNotFoundException unless the class has #[DeleteWhenMissingModels]. Pass models, IDs and scalars, never closures or open handles. queue:work --stop-when-empty then ran it in 338 ms and exited.