
# Dynamický cyklus for-each

Použijte `->for_each()`, když při sestavování workflow není znám počet větví. Queuety načte pole ze stavu workflow, spustí úlohu větve pro každou položku a pokračuje, až je splněna nastavená podmínka dokončení.

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

Krok plánovače zapíše `pages` do stavu workflow. Krok cyklu for-each toto pole načte a spustí úlohu větve pro každou položku.

## Obslužné rutiny větví

Obslužné rutiny větví cyklu for-each implementují `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',
        ];
    }
}
```

V `config()` přijímá `backoff` název strategie: `exponential`, `linear` nebo `fixed`. Seznam prodlev mezi pokusy se nyní zahazuje a vede k okamžitým opakováním ([#16](https://github.com/inline0/queuety/issues/16)); prodlevy mezi pokusy místo toho nastavte v `backoff` definice kroku.

`$state` obsahuje úplný stav workflow, `$item` je jednotlivá položka cyklu for-each a `$index` je její pozice počítaná od nuly.

## Režimy dokončení

`ForEachMode::All`

Krok skončí až po úspěchu všech větví. Trvalé selhání kterékoli větve způsobí selhání cyklu for-each.

`ForEachMode::FirstSuccess`

Krok skončí, jakmile uspěje první větev. Zbývající aktivní větve se označí jako překonané.

`ForEachMode::Quorum`

Krok skončí, jakmile uspěje alespoň `quorum` větví. Pokud zbývající větve už nemohou splnit kvórum, workflow selže.

## Reduktory

Ve výchozím nastavení Queuety ukládá strukturovaný souhrn pod klíčem `result_key`:

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

Chcete-li vlastní souhrn, předejte třídu reduktoru implementující `Contracts\ForEachReducer`. Souhrn se stále ukládá pod klíčem `result_key` a klíče vrácené reduktorem se vedle něj sloučí se stavem workflow:

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

Výstup reduktoru se stejně jako výstup jiného kroku sloučí se stavem workflow. Může také vrátit `_next_step` pro větvení po skončení cyklu for-each.

## Opakování při chybě

Větve cyklu for-each opakují pokusy nezávisle podle `config()` obslužné rutiny větve. Pokud krok cyklu for-each selže a zavoláte `retry_workflow()`, Queuety cyklus znovu rozbalí a do fronty zařadí pouze chybějící nebo neúspěšné větve. Výsledky úspěšných větví zůstanou ve stavu workflow.

## Volba mezi `parallel()` a `for_each()`

Použijte `parallel()`, pokud je seznam větví pevně daný v kódu.

Použijte `for_each()`, pokud se seznam větví zjišťuje za běhu ze stavu workflow a potřebujete režim dokončení, například při prvním úspěchu nebo při dosažení kvóra.
