Джобы
Джоба (Job) — самый частый вид фоновой задачи: разовая операция, запускаемая
«выстрелил и забыл» из сервиса. Контроллер отвечает клиенту сразу, а тяжёлая работа
уходит в отдельный процесс.
Что такое джоба и зачем
Джоба — единица фоновой работы, выполняемая вне запроса.
Проблема. Некоторые операции нельзя делать синхронно: отправка письма, генерация отчёта, создание папки инстанса. Держать под них запрос открытым — плохой UX и риск таймаута.
Решение. Оформите работу как Job и запустите её из сервиса через dispatch()
— она форкнется в фон, а запрос вернёт ответ немедленно. Об этом и раздел; общая
модель процессов — на странице Фоновые задачи.
Определение
Скаффолд флагом -J:
php call make -J .InstanceCreate # → main/InstanceCreateJob.phpДжоба реализует resolution() — тело задачи; зависимости приходят через
#[Autowired], логгер доступен как $this->logger:
<?php
namespace Main;
use Flytachi\Winter\DI\Attribute\Autowired;
use Flytachi\Winter\K2\Stereotype\Job;
class InstanceCreateJob extends Job
{
#[Autowired] private InstanceRepository $repo;
public function resolution(mixed $data = null): void
{
if (! is_string($data)) {
$this->logger->error('bad payload', ['data' => $data]);
return;
}
$instance = $this->repo->findById($data);
// ... создать папку, переключить статус CREATED → ACTIVE
}
}Запуск из сервиса
Типичный приём — dispatch() сразу после записи в БД. Запрос не ждёт джобу:
public function create(CreateInstanceDto $dto): Instance
{
$instance = Instance::fromDto($dto);
$instance->id = $this->repo->insert($instance);
InstanceCreateJob::dispatch($instance->id); // фон, запрос уже свободен
return $instance;
}Передача данных
Аргумент dispatch($data) уходит в потомок сериализованным. Передавайте
идентификаторы и простые данные, а тяжёлые объекты подгружайте в resolution()
из репозитория:
InstanceCreateJob::dispatch($instance->id); // хорошо — id
InstanceCreateJob::dispatch(['id' => $id, 'retry' => 0]); // хорошо — простой массивDI в потомке, но не ресурсы
Форкнутый потомок пересоздаёт зависимости через контейнер (#[Autowired]
работает), но живые соединения родителя не наследует — джоба сама достаёт данные
из репозитория. Логи потомка идут в канал cli.
Идемпотентность
Потомок форкается «выстрелил и забыл»: родитель не видит его исключений (они
логируются как critical, потомок завершается сам). Поэтому пишите джобы
идемпотентно — безопасно к повтору и частичному выполнению:
public function resolution(mixed $data = null): void
{
$instance = $this->repo->findById($data);
if ($instance === null || $instance->status !== Status::CREATED->value) {
return; // уже обработано или удалено — выходим тихо
}
// ... выполнить шаг и перевести статус
}Дальше
- Фоновые задачи — Process, Daemon, WebSocket
- CLI → thread — ручной запуск джоб
- Сервисы — откуда обычно вызывается
dispatch()