
# Dynaaminen for-each

Käytä `->for_each()`-toimintoa, kun haarojen määrää ei tiedetä työnkulun rakennusvaiheessa. Queuety lukee taulukon työnkulun tilasta, lähettää kustakin alkiosta haaratyön ja jatkaa vasta, kun määritetty valmistumisehto täyttyy.

## `->for_each()`-metodi

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

Suunnittelijavaihe kirjoittaa `pages`-arvon työnkulun tilaan. For-each-vaihe lukee tämän taulukon ja lähettää kustakin alkiosta haaratyön.

## Haarankäsittelijät

For-each-haarankäsittelijät toteuttavat toiminnon `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',
        ];
    }
}
```

Kohdassa `config()` `backoff` saa strategiaksi nimen `exponential`, `linear` tai `fixed`. Siinä annettu yrityskohtainen viiveiden luettelo pudotetaan tällä hetkellä pois, jolloin uudelleenyritykset tapahtuvat välittömästi ([#16](https://github.com/inline0/queuety/issues/16)); määritä yrityskohtaiset viiveet sen sijaan vaiheen määritelmän `backoff`-arvossa.

`$state` sisältää työnkulun koko tilan, `$item` on yksittäinen for-each-alkio ja `$index` sen nollasta alkava sijainti.

## Valmistumistilat

`ForEachMode::All`

Vaihe valmistuu vasta, kun kaikki haarat onnistuvat. Yksikin pysyvästi epäonnistunut haara saa for-each-vaiheen epäonnistumaan.

`ForEachMode::FirstSuccess`

Vaihe valmistuu heti, kun ensimmäinen haara onnistuu. Jäljellä olevat aktiiviset haarat merkitään syrjäytetyiksi.

`ForEachMode::Quorum`

Vaihe valmistuu, kun vähintään `quorum` haaraa onnistuu. Jos jäljellä olevat haarat eivät enää voi saavuttaa vaadittua päätösvaltaisuutta, työnkulku epäonnistuu.

## Reducerit

Oletuksena Queuety tallentaa rakenteisen koosteen avaimeen `result_key`:

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

Jos haluat mukautetun yhteenvedon, välitä reducer-luokka, joka toteuttaa `Contracts\ForEachReducer`. Kooste tallennetaan edelleen kohteeseen `result_key`, ja reducerin palauttamat avaimet yhdistetään työnkulun tilaan sen rinnalle:

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

Reducerin tulos yhdistetään työnkulun tilaan kuten minkä tahansa muun vaiheen tulos. Se voi myös palauttaa arvon `_next_step` haarautuakseen for-each-vaiheen päätyttyä.

## Uudelleenyritysten toiminta

For-each-haarat yrittävät uudelleen itsenäisesti haarakäsittelijän `config()`-asetuksen mukaisesti. Jos for-each-vaihe epäonnistuu ja kutsut toimintoa `retry_workflow()`, Queuety laajentaa for-each-vaiheen uudelleen ja lisää jonoon vain yhä puuttuvat tai epäonnistuneet haarat. Onnistuneiden haarojen tulokset säilyvät työnkulun tilassa.

## Valinta: `parallel()` vai `for_each()`

Käytä toimintoa `parallel()`, kun haaraluettelo on määritetty koodissa.

Käytä toimintoa `for_each()`, kun haaraluettelo selvitetään ajonaikaisesti työnkulun tilasta ja tarvitset valmistumistiloja, kuten ensimmäisen onnistumisen tai päätösvaltaisuuden.
