
# Dinamikus for-each

Akkor használja a `->for_each()` elemet, ha a munkafolyamat összeállításakor még nem ismert az ágak száma. A Queuety beolvassa a tömböt a munkafolyamat állapotából, elemenként egy ágfeladatot indít, és csak a beállított teljesülési feltétel teljesülésekor lép tovább.

## A `->for_each()` metódus

```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 tervezőlépés a `pages` értéket írja a munkafolyamat állapotába. A for-each lépés beolvassa ezt a tömböt, és elemenként egy ágfeladatot indít.

## Ágkezelők

A for-each ágkezelők a `Contracts\ForEachHandler` interfészt valósítják meg:

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

A `config()` elemben a `backoff` stratégianevet vár: `exponential`, `linear` vagy `fixed`. Az ott megadott próbálkozásonkénti késleltetések listája jelenleg elvész, és azonnali újrapróbálkozásra vált ([#16](https://github.com/inline0/queuety/issues/16)); a próbálkozásonkénti késleltetéseket inkább a lépésdefiníció `backoff` mezőjében adja meg.

A `$state` a teljes munkafolyamat-állapotot, a `$item` az egyes for-each elemet, a `$index` pedig annak nullától kezdődő pozícióját tartalmazza.

## Teljesülési módok

`ForEachMode::All`

A lépés csak akkor zárul le, ha minden ág sikeres. Bármely végleges ág hiba esetén a for-each lépés is hibára fut.

`ForEachMode::FirstSuccess`

A lépés az első sikeres ág után azonnal lezárul. A többi aktív ág felülírtként kerül félre.

`ForEachMode::Quorum`

A lépés akkor zárul le, ha legalább `quorum` ág sikeres. Ha a fennmaradó ágak már nem teljesíthetik a kvórumot, a munkafolyamat hibára fut.

## Reducerek

Alapértelmezés szerint a Queuety strukturált összesítést tárol a `result_key` alatt:

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

Egyéni összesítéshez adjon át a `Contracts\ForEachReducer` interfészt megvalósító reducer osztályt. Az összesítés továbbra is a `result_key` alatt tárolódik, a reducer által visszaadott kulcsok pedig mellé kerülnek a munkafolyamat állapotába:

```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 reducer kimenete ugyanúgy olvad össze a munkafolyamat állapotával, mint bármely más lépéskimenet. A for-each lezárulta után az elágazáshoz `_next_step` értéket is visszaadhat.

## Újrapróbálkozási viselkedés

A for-each ágak egymástól függetlenül próbálkoznak újra az ágkezelő `config()` beállítása szerint. Ha a for-each lépés meghiúsul, és meghívja a `retry_workflow()` elemet, a Queuety újra kibontja a for-each lépést, és csak a még hiányzó vagy sikertelen ágakat állítja ismét sorba. A sikeres ágak eredményei a munkafolyamat állapotában maradnak.

## Választás a `parallel()` és a `for_each()` között

Akkor használja a `parallel()` elemet, ha az áglista rögzített a kódban.

Akkor használja a `for_each()` elemet, ha az áglista futásidőben, a munkafolyamat állapotából derül ki, és olyan teljesülési módokra van szüksége, mint az első siker vagy a kvórum.
