
# Dynamische For-Each-Schritte

Verwende `->for_each()`, wenn die Anzahl der Zweige zum Erstellungszeitpunkt des Workflows noch unbekannt ist. Queuety liest ein Array aus dem Workflow-Status, stellt pro Element einen Zweig-Job ein und fährt erst fort, wenn die konfigurierte Abschlussbedingung erfüllt ist.

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

Der Planungsschritt schreibt `pages` in den Workflow-Status. Der For-Each-Schritt liest dieses Array und stellt pro Element einen Zweig-Job ein.

## Handler für Zweige

Handler für For-Each-Zweige implementieren `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',
        ];
    }
}
```

In `config()` übernimmt `backoff` einen Strategienamen: `exponential`, `linear` oder `fixed`. Eine Liste mit Verzögerungen pro Versuch wird dort derzeit verworfen, sodass Wiederholungen sofort erfolgen ([#16](https://github.com/inline0/queuety/issues/16)). Lege Verzögerungen pro Versuch stattdessen in `backoff` der Schrittdefinition fest.

`$state` enthält den vollständigen Workflow-Status, `$item` ist das einzelne For-Each-Element und `$index` seine nullbasierte Position.

## Abschlussmodi

`ForEachMode::All`

Der Schritt wird erst abgeschlossen, wenn alle Zweige erfolgreich sind. Jeder dauerhafte Fehler in einem Zweig lässt den For-Each-Schritt fehlschlagen.

`ForEachMode::FirstSuccess`

Der Schritt wird abgeschlossen, sobald der erste Zweig erfolgreich ist. Die übrigen aktiven Zweige werden als überholt markiert.

`ForEachMode::Quorum`

Der Schritt wird abgeschlossen, sobald mindestens `quorum` Zweige erfolgreich sind. Können die übrigen Zweige das Quorum nicht mehr erreichen, schlägt der Workflow fehl.

## Reduzierer

Standardmäßig speichert Queuety unter `result_key` ein strukturiertes Gesamtergebnis:

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

Wenn du eine benutzerdefinierte Zusammenfassung möchtest, übergib eine Reducer-Klasse, die `Contracts\ForEachReducer` implementiert. Das Aggregat wird weiterhin unter `result_key` gespeichert, und die vom Reducer zurückgegebenen Schlüssel werden daneben mit dem Workflow-Status zusammengeführt:

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

Die Ausgabe des Reducers wird wie die Ausgabe jedes anderen Schritts mit dem Workflow-Status zusammengeführt. Er kann auch `_next_step` zurückgeben, um nach Abschluss der For-each-Ausführung zu verzweigen.

## Wiederholungsverhalten

For-each-Zweige werden gemäß dem `config()` des Zweighandlers unabhängig voneinander wiederholt. Wenn der For-each-Schritt fehlschlägt und du `retry_workflow()` aufrufst, entfaltet Queuety den For-each-Schritt erneut und reiht nur Zweige ein, die noch fehlen oder fehlgeschlagen sind. Erfolgreiche Zweigergebnisse bleiben im Workflow-Status erhalten.

## `parallel()` und `for_each()` im Vergleich

Verwende `parallel()`, wenn die Zweigliste im Code festgelegt ist.

Verwende `for_each()`, wenn die Zweigliste zur Laufzeit anhand des Workflow-Status ermittelt wird und du Abschlussmodi wie erster Erfolg oder Quorum benötigst.
