
# Boucle dynamique sur chaque élément

Utilisez `->for_each()` lorsque le nombre de branches du flux de travail est inconnu au moment de la création. Queuety lit un tableau dans l’état du flux, répartit une tâche de branche par élément et ne poursuit que lorsque la condition d’achèvement configurée est remplie.

## La méthode `->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 ] );
```

L’étape du planificateur écrit `pages` dans l’état du flux de travail. L’étape de boucle lit ce tableau et répartit une tâche de branche par élément.

## Gestionnaires de branche

Les gestionnaires de branche de la boucle implémentent `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',
        ];
    }
}
```

Dans `config()`, `backoff` prend un nom de stratégie : `exponential`, `linear` ou `fixed`. Une liste de délais entre tentatives y est actuellement ignorée et les relances sont immédiates ([#16](https://github.com/inline0/queuety/issues/16)) ; définissez plutôt les délais entre tentatives dans `backoff` de la définition de l’étape.

`$state` contient l’état complet du flux de travail, `$item` est l’élément individuel de la boucle et `$index` sa position à partir de zéro.

## Modes d’achèvement

`ForEachMode::All`

L’étape ne se termine que lorsque toutes les branches ont réussi. L’échec définitif d’une branche fait échouer la boucle.

`ForEachMode::FirstSuccess`

L’étape se termine dès qu’une première branche réussit. Les branches actives restantes sont mises en attente comme étant supplantées.

`ForEachMode::Quorum`

L’étape se termine lorsqu’au moins `quorum` branches ont réussi. Si les branches restantes ne peuvent plus atteindre le quorum, le flux de travail échoue.

## Réducteurs

Par défaut, Queuety stocke un agrégat structuré sous `result_key` :

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

Pour créer un récapitulatif personnalisé, transmettez une classe de réducteur qui implémente `Contracts\ForEachReducer`. L’agrégat reste stocké sous `result_key`, et les clés renvoyées par le réducteur sont fusionnées dans l’état du flux de travail à côté de celui-ci :

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

La sortie du réducteur est fusionnée dans l’état du flux de travail comme toute autre sortie d’étape. Elle peut aussi renvoyer `_next_step` pour créer une branche après la fin de la boucle.

## Comportement des relances

Les branches de la boucle sont relancées indépendamment selon `config()` du gestionnaire de branche. Si l’étape de boucle échoue et que vous appelez `retry_workflow()`, Queuety développe à nouveau l’étape et remet en file d’attente uniquement les branches encore manquantes ou en échec. Les résultats des branches réussies restent dans l’état du flux de travail.

## Choisir entre `parallel()` et `for_each()`

Utilisez `parallel()` lorsque la liste des branches est définie dans le code.

Utilisez `for_each()` lorsque la liste des branches est découverte à l’exécution à partir de l’état du flux de travail et que vous avez besoin de modes d’achèvement comme la première réussite ou le quorum.
