Класс Job
Класс Job в Laravel - это абстракция, которая превращает обычный класс в задачу, которую можно отправить в очередь. Он определяет контракт для выполнения фоновой работы, предоставляя механизмы обработки, повторных попыток и обработки ошибок. В основе работы лежит метод handle, в котором пишется основная бизнес-логика. Класс также поддерживает атрибуты для настройки поведения: количество попыток, таймаут, задержка и другие. Использование Job позволяет перенести тяжёлые операции (отправка писем, работа с API, генерация отчётов) в фоновый режим, не блокируя работу веб-приложения.
Синтаксис
Для создания собственной задачи необходимо создать класс, который реализует интерфейс ShouldQueue. Обычно класс наследуется от базового абстрактного класса, но в Laravel принято использовать команду make:job, которая генерирует готовую заготовку. Основной метод, который должен быть определён в классе - это handle. Именно он вызывается при обработке задачи из очереди.
Пример
Создадим задачу для отправки уведомления пользователю после регистрации. Для этого сгенерируем класс через Artisan-команду и реализуем логику в методе handle:
<?php
namespace AppJobs;
use AppModelsUser;
use IlluminateContractsQueueShouldQueue;
use IlluminateFoundationBusDispatchable;
use IlluminateQueueInteractsWithQueue;
use IlluminateQueueSerializesModels;
use IlluminateSupportFacadesLog;
class SendWelcomeEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, SerializesModels;
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function handle()
{
// Логика отправки письма
Log::info("Sending welcome email to: " . $this->user->email);
// Здесь мог бы быть код отправки реального письма
}
}
?>
Теперь задачу можно поместить в очередь с помощью фасада dispatch. Это делается в любом месте приложения, например, в контроллере после регистрации пользователя:
<?php
namespace AppHttpControllersAuth;
use AppHttpControllersController;
use AppJobsSendWelcomeEmail;
use AppModelsUser;
use IlluminateSupportFacadesHash;
use IlluminateSupportFacadesLog;
class RegisteredUserController extends Controller
{
public function store()
{
// Создание пользователя
$user = User::create([
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => Hash::make('password'),
]);
// Отправка задачи в очередь
SendWelcomeEmail::dispatch($user);
Log::info('User registered and job dispatched');
return response()->json(['message' => 'User registered']);
}
}
?>
Результат выполнения кода (логи):
"User registered and job dispatched"
"Sending welcome email to: john@example.com"
Обратите внимание, что задача выполнится асинхронно, не задерживая ответ сервера.
Пример
Часто бывает необходимо указать количество попыток повторного выполнения задачи в случае сбоя. Для этого используется атрибут tries. Давайте создадим задачу, которая будет пытаться выполниться до 3 раз с интервалом 5 секунд между попытками:
<?php
namespace AppJobs;
use IlluminateContractsQueueShouldQueue;
use IlluminateFoundationBusDispatchable;
use IlluminateQueueInteractsWithQueue;
use IlluminateQueueSerializesModels;
use IlluminateSupportFacadesLog;
class ProcessPayment implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, SerializesModels;
public $tries = 3;
public $backoff = 5;
public function handle()
{
Log::info('Processing payment...');
// Имитация ошибки для демонстрации
throw new Exception('Payment gateway is temporarily unavailable');
}
public function failed(Throwable $exception)
{
Log::error('Payment processing failed: ' . $exception->getMessage());
}
}
?>
Теперь при выполнении задачи она будет повторно ставиться в очередь три раза с интервалом в 5 секунд. Если все попытки исчерпаны, будет вызван метод failed, где можно залогировать ошибку или уведомить администратора:
"Processing payment..."
"Payment processing failed: Payment gateway is temporarily unavailable"
"Processing payment..."
"Payment processing failed: Payment gateway is temporarily unavailable"
"Processing payment..."
"Payment processing failed: Payment gateway is temporarily unavailable"
В реальном приложении в методе handle выполнялась бы логика взаимодействия с платёжным шлюзом, а повторные попытки помогли бы справиться с временными сетевыми проблемами.
Пример
Laravel позволяет использовать middleware прямо в классе задачи. Это полезно для настройки ограничений, например, чтобы задачи не перекрывали друг друга. Воспользуемся встроенным middleware WithoutOverlapping, чтобы гарантировать, что задача с одним и тем же ключом не выполняется одновременно:
<?php
namespace AppJobs;
use AppModelsUser;
use IlluminateContractsQueueShouldQueue;
use IlluminateFoundationBusDispatchable;
use IlluminateQueueInteractsWithQueue;
use IlluminateQueueSerializesModels;
use IlluminateQueueMiddlewareWithoutOverlapping;
use IlluminateSupportFacadesCache;
use IlluminateSupportFacadesLog;
class UpdateUserCache implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, SerializesModels;
protected $userId;
public function __construct($userId)
{
$this->userId = $userId;
}
public function handle()
{
$user = User::find($this->userId);
Cache::put('user_' . $this->userId, $user, 3600);
Log::info('Cache updated for user: ' . $this->userId);
}
public function middleware()
{
return [
new WithoutOverlapping($this->userId)
];
}
}
?>
Теперь если несколько задач UpdateUserCache для одного и того же пользователя будут поставлены в очередь, вторая из них не запустится, пока не завершится первая. Это предотвращает состояние гонки и избыточное обновление кеша:
"Cache updated for user: 1"
"Cache updated for user: 2"
// Для user 1 вторая задача будет ждать, пока освободится замок
Благодаря этому приёму можно безопасно работать с ресурсами, которые не поддерживают конкурентный доступ.