
# Dinaminis kartojimas kiekvienam

Naudokite `->for_each()`, kai darbo eiga kūrimo metu nežino šakų skaičiaus. Queuety nuskaito masyvą iš darbo eigos būsenos, kiekvienam elementui išsiunčia vykdyti po vieną šakos užduotį ir pereina toliau tik tada, kai įvykdoma nustatyta užbaigimo sąlyga.

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

Planavimo žingsnis įrašo `pages` į darbo eigos būseną. Kartojimo kiekvienam žingsnis nuskaito tą masyvą ir kiekvienam elementui išsiunčia vykdyti po vieną šakos užduotį.

## Šakų apdorojimo funkcijos

Kartojimo kiekvienam šakų apdorojimo funkcijos įgyvendina `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',
        ];
    }
}
```

Sistemoje `config()` `backoff` priima strategijos pavadinimą: `exponential`, `linear` arba `fixed`. Ten pateiktas bandymų delsų sąrašas šiuo metu atmetamas, todėl bandoma iš karto dar kartą ([#16](https://github.com/inline0/queuety/issues/16)); kiekvieno bandymo delsas nurodykite žingsnio apibrėžtyje esančiame `backoff`.

`$state` apima visą darbo eigos būseną, `$item` yra atskiras kartojimo kiekvienam elementas, o `$index` – jo pozicija, skaičiuojant nuo nulio.

## Užbaigimo režimai

`ForEachMode::All`

Žingsnis užbaigiamas tik tada, kai sėkmingai baigiamos visos šakos. Dėl bet kurios negrįžtamai nesėkmingos šakos kartojimo kiekvienam žingsnis nepavyksta.

`ForEachMode::FirstSuccess`

Žingsnis užbaigiamas, kai tik sėkmingai baigiama pirmoji šaka. Likusios aktyvios šakos užkasamos kaip pakeistos kitomis.

`ForEachMode::Quorum`

Žingsnis užbaigiamas, kai sėkmingai baigia veikti bent `quorum` šakų. Jei likusios šakos nebegali įvykdyti kvorumo sąlygos, darbo eiga nepavyksta.

## Reduktoriai

Pagal numatytuosius nustatymus Queuety išsaugo struktūrizuotą apibendrinimą adresu `result_key`:

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

Jei norite tinkintos suvestinės, perduokite reduktoriaus klasę, įgyvendinančią `Contracts\ForEachReducer`. Apibendrinimas vis tiek išsaugomas adresu `result_key`, o grąžinti reduktoriaus raktai sujungiami su darbo eigos būsena šalia jo:

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

Reduktoriaus išvestis sujungiama su darbo eigos būsena kaip bet kurio kito žingsnio išvestis. Ji taip pat gali grąžinti `_next_step`, kad užbaigus kartojimą kiekvienam būtų galima šakotis.

## Pakartotinių bandymų veikimas

Kartojimo kiekvienam šakos bandomos dar kartą nepriklausomai, pagal šakos apdorojimo funkcijos `config()`. Jei kartojimo kiekvienam žingsnis nepavyksta ir iškviečiate `retry_workflow()`, Queuety iš naujo išplečia kartojimo kiekvienam žingsnį ir į eilę vėl įtraukia tik trūkstamas arba nepavykusias šakas. Sėkmingų šakų rezultatai lieka darbo eigos būsenoje.

## Kaip pasirinkti tarp `parallel()` ir `for_each()`

Naudokite `parallel()`, kai šakų sąrašas nustatytas kode.

Naudokite `for_each()`, kai šakų sąrašas aptinkamas vykdymo metu pagal darbo eigos būseną ir reikia užbaigimo režimų, tokių kaip pirmoji sėkminga šaka arba kvorumas.
