
# Dynamický cyklus For-Each

Použite `->for_each()`, keď pri zostavovaní workflowu ešte nepoznáte počet vetiev. Queuety načíta z poľa stavu workflowu pole položiek, pre každú odošle úlohu vetvy a pokračuje až po splnení nastavenej podmienky dokončenia.

## Metóda `->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 ] );
```

Plánovací krok zapíše `pages` do stavu workflowu. Krok for-each načíta toto pole a pre každú položku odošle úlohu vetvy.

## Obslužné rutiny vetiev

Obslužné rutiny vetiev 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()` prijíma `backoff` názov stratégie: `exponential`, `linear` alebo `fixed`. Zoznam oneskorení pre jednotlivé pokusy sa tam momentálne zahodí a opakovania sa vykonajú okamžite ([#16](https://github.com/inline0/queuety/issues/16)); oneskorenia pre jednotlivé pokusy nastavte v `backoff` definície kroku.

`$state` obsahuje celý stav workflowu, `$item` označuje jednotlivú položku cyklu for-each a `$index` jej pozíciu počítanú od nuly.

## Režimy dokončenia

`ForEachMode::All`

Krok sa dokončí až po úspechu všetkých vetiev. Akákoľvek trvalá chyba vetvy spôsobí zlyhanie kroku for-each.

`ForEachMode::FirstSuccess`

Krok sa dokončí hneď po úspechu prvej vetvy. Zostávajúce aktívne vetvy sa označia ako nahradené a presunú do archívu.

`ForEachMode::Quorum`

Krok sa dokončí, keď uspeje aspoň `quorum` vetiev. Ak zostávajúce vetvy už nedokážu splniť kvórum, workflow zlyhá.

## Reduktory

Predvolene Queuety ukladá štruktúrovaný súhrn pod kľúčom `result_key`:

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

Ak chcete vlastný súhrn, odovzdajte triedu reduktora implementujúcu `Contracts\ForEachReducer`. Súhrn sa stále ukladá pod kľúčom `result_key` a kľúče vrátené reduktorom sa vedľa neho zlúčia do stavu workflowu:

```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 reduktora sa zlúči do stavu workflowu rovnako ako výstup každého iného kroku. Môže tiež vrátiť `_next_step`, aby sa po dokončení cyklu for-each spustila ďalšia vetva.

## Správanie pri opakovaní

Vetvy cyklu for-each sa opakujú samostatne podľa `config()` obslužnej rutiny vetvy. Ak krok for-each zlyhá a zavoláte `retry_workflow()`, Queuety znova rozšíri krok for-each a do frontu zaradí iba chýbajúce alebo neúspešné vetvy. Výsledky úspešných vetiev zostanú v stave workflowu.

## Výber medzi `parallel()` a `for_each()`

Použite `parallel()`, keď je zoznam vetiev pevne daný v kóde.

Použite `for_each()`, keď sa zoznam vetiev zisťuje počas behu zo stavu workflowu a potrebujete režim dokončenia, napríklad prvý úspech alebo kvórum.
