
# For-Each dinâmico

Use `->for_each()` quando o fluxo de trabalho não souber a quantidade de ramificações no momento da criação. Queuety lê um array do estado do fluxo de trabalho, despacha um trabalho de ramificação para cada item e avança somente quando a condição de conclusão configurada é atendida.

## O método `->for_each()`

```php
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 ] );
```

A etapa planejadora grava `pages` no estado do fluxo de trabalho. A etapa for-each lê esse array e despacha um trabalho de ramificação para cada item.

## Manipuladores de ramificação

Os manipuladores de ramificação for-each implementam `Contracts\ForEachHandler`:

```php
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',
        ];
    }
}
```

Em `config()`, `backoff` recebe um nome de estratégia: `exponential`, `linear` ou `fixed`. Uma lista de atrasos por tentativa nesse local é descartada atualmente, causando novas tentativas imediatas ([#16](https://github.com/inline0/queuety/issues/16)); em vez disso, defina os atrasos por tentativa em `backoff` da definição da etapa.

`$state` contém o estado completo do fluxo de trabalho, `$item` é o item individual do for-each e `$index` é sua posição baseada em zero.

## Modos de conclusão

`ForEachMode::All`

A etapa só é concluída depois que todas as ramificações são bem-sucedidas. Qualquer falha permanente em uma ramificação faz a etapa for-each falhar.

`ForEachMode::FirstSuccess`

A etapa é concluída assim que a primeira ramificação tem sucesso. As ramificações ativas restantes são marcadas como substituídas.

`ForEachMode::Quorum`

A etapa é concluída quando pelo menos `quorum` ramificações têm sucesso. Se as ramificações restantes já não puderem atingir o quórum, o fluxo de trabalho falha.

## Redutores

Por padrão, Queuety armazena um agregado estruturado em `result_key`:

- `mode`
- `quorum`
- `total`
- `succeeded`
- `failed`
- `settled`
- `winner`
- `results`
- `failures`

Para criar um resumo personalizado, passe uma classe redutora que implemente `Contracts\ForEachReducer`. O agregado continua armazenado em `result_key`, e as chaves retornadas pelo redutor são mescladas ao estado do fluxo de trabalho ao lado dele:

```php
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,
        ];
    }
}
```

A saída do redutor é mesclada ao estado do fluxo de trabalho como qualquer outra saída de etapa. Ele também pode retornar `_next_step` para ramificar após a conclusão do for-each.

## Comportamento de novas tentativas

As ramificações for-each fazem novas tentativas de forma independente, de acordo com `config()` do manipulador da ramificação. Se a etapa for-each falhar e você chamar `retry_workflow()`, Queuety expande a etapa for-each novamente e coloca em fila apenas as ramificações ainda ausentes ou com falha. Os resultados das ramificações bem-sucedidas permanecem no estado do fluxo de trabalho.

## Escolher entre `parallel()` e `for_each()`

Use `parallel()` quando a lista de ramificações estiver fixa no código.

Use `for_each()` quando a lista de ramificações for descoberta em tempo de execução a partir do estado do fluxo de trabalho e você precisar de modos de conclusão como primeira ramificação bem-sucedida ou quórum.
