Динамический цикл For-Each
Используйте ->for_each(), если во время построения рабочего процесса количество ветвей неизвестно. Queuety считывает массив из состояния рабочего процесса, запускает отдельное задание ветви для каждого элемента и продолжает выполнение только после выполнения заданного условия завершения.
Метод ->for_each()
use Queuety\Contracts\ForEachHandler;
use Queuety\Contracts\ForEachReducer;
use Queuety\Enums\ForEachMode;
use Queuety\Queuety;
Queuety::workflow( 'index_site' )
->then( PlanPagesStep::class )
->for_each(
items_key: 'pages',
handler_class: IndexPageStep::class,
result_key: 'page_results',
mode: ForEachMode::Quorum,
quorum: 2,
reducer_class: IndexSummaryReducer::class,
name: 'index_pages',
)
->then( PublishIndexStep::class )
->dispatch( [ 'site_id' => 42 ] );
Шаг планировщика записывает pages в состояние рабочего процесса. Шаг цикла for-each считывает этот массив и запускает отдельное задание ветви для каждого элемента.
Обработчики ветвей
Обработчики ветвей цикла for-each реализуют Contracts\ForEachHandler:
use Queuety\Contracts\ForEachHandler;
final class IndexPageStep implements ForEachHandler {
public function handle_item( array $state, mixed $item, int $index ): array {
$page = is_array( $item ) ? $item : [ 'url' => (string) $item ];
return [
'url' => $page['url'],
'title' => fetch_title( $page['url'] ),
'index' => $index,
];
}
public function config(): array {
return [
'max_attempts' => 3,
'backoff' => 'exponential',
];
}
}
В config() параметр backoff принимает имя стратегии: exponential, linear или fixed. Список задержек для отдельных попыток в этом месте сейчас отбрасывается, поэтому повторные попытки выполняются без задержки (#16); задавайте задержки для отдельных попыток в backoff определения шага.
$state содержит полное состояние рабочего процесса, $item — отдельный элемент цикла for-each, а $index — его позицию, начиная с нуля.
Режимы завершения
ForEachMode::All
Шаг завершается, только когда успешно выполнены все ветви. Любой окончательный сбой ветви приводит к сбою цикла for-each.
ForEachMode::FirstSuccess
Шаг завершается, как только успешно выполнена первая ветвь. Остальные активные ветви помечаются как вытесненные.
ForEachMode::Quorum
Шаг завершается, когда успешно выполнено не менее quorum ветвей. Если оставшиеся ветви уже не могут обеспечить кворум, рабочий процесс завершается с ошибкой.
Редукторы
По умолчанию Queuety сохраняет структурированный агрегат в result_key:
modequorumtotalsucceededfailedsettledwinnerresultsfailures
Чтобы создать собственную сводку, передайте класс редуктора, реализующий Contracts\ForEachReducer. Агрегат по-прежнему сохраняется в result_key, а возвращенные редуктором ключи объединяются с состоянием рабочего процесса рядом с ним:
use Queuety\Contracts\ForEachReducer;
final class IndexSummaryReducer implements ForEachReducer {
public function reduce( array $state, array $for_each ): array {
return [
'indexed_count' => count( $for_each['results'] ),
'winning_url' => $for_each['winner']['output']['url'] ?? null,
];
}
}
Результат редуктора объединяется с состоянием рабочего процесса так же, как результат любого другого шага. Он также может возвращать _next_step для перехода к ветвлению после завершения цикла for-each.
Повторные попытки
Для ветвей for-each повторные попытки выполняются независимо согласно config() обработчика ветви. Если шаг for-each завершается с ошибкой и вы вызываете retry_workflow(), Queuety повторно разворачивает шаг for-each и ставит в очередь только отсутствующие или завершившиеся с ошибкой ветви. Результаты успешно выполненных ветвей остаются в состоянии рабочего процесса.
Выбор между parallel() и for_each()
Используйте parallel(), если список ветвей фиксирован в коде.
Используйте for_each(), если список ветвей определяется во время выполнения по состоянию рабочего процесса и нужны режимы завершения, например успех первой ветви или кворум.