
# Ripetizioni

I flussi di lavoro Queuety sono catene ordinate di passaggi. Usate un passaggio di controllo della ripetizione per tornare a un passaggio precedente con nome, invece di scrivere manualmente la logica `_next_step`.

Scegliete la forma più adatta al flusso di lavoro:

- un `state_key` pubblico quando la condizione è semplice
- un `condition_class` quando la ripetizione richiede una logica più articolata
- `max_iterations` quando la ripetizione deve avere un limite massimo

Entrambi i metodi puntano a un passaggio precedente con nome, così la ripetizione resta serializzabile, riproducibile e protetta dallo stesso runtime del flusso di lavoro di ogni altro passaggio.

La condizione di ripetizione viene valutata sullo stato pubblico persistente del flusso di lavoro dopo il completamento del passaggio precedente, così ogni iterazione resta durevole anche dopo il riavvio dei worker.

## `repeat_until()` con una chiave di stato

```php
Queuety::workflow( 'approval_revision_loop' )
    ->max_transitions( 12 )
    ->then( DraftBriefStep::class, 'draft' )
    ->wait_for_decision( result_key: 'review' )
    ->then( NormalizeReviewOutcomeStep::class, 'review_outcome' )
    ->repeat_until( 'draft', 'review_approved', true, 'repeat_draft_until_approved' )
    ->then( PublishBriefStep::class )
    ->dispatch( [ 'brief_id' => 42 ] );
```

`NormalizeReviewOutcomeStep` può appiattire i dati della decisione in una semplice chiave pubblica:

```php
class NormalizeReviewOutcomeStep implements \Queuety\Step {
    public function handle( array $state ): array {
        return [
            'review_approved' => 'approved' === ( $state['review']['outcome'] ?? null ),
        ];
    }

    public function config(): array {
        return [];
    }
}
```

Se `review_approved` non è ancora `true`, il passaggio di ripetizione torna a `draft`. Quando diventa `true`, il flusso di lavoro prosegue con `PublishBriefStep`.

I segnali vengono memorizzati, non consumati. Al secondo passaggio per `wait_for_decision()`, il segnale `rejected` precedente corrisponde ancora e l'attesa termina subito, quindi il ciclo continuerebbe all'infinito finché `max_transitions()` non lo interrompe. Assegnate a ogni iterazione una chiave distinta: fate in modo che `DraftBriefStep` restituisca un `review_round` incrementale, passate `correlation_key: 'review_round'` a `wait_for_decision()` e chiedete al revisore di inviare lo stesso `review_round` nel payload del segnale. L'attesa termina quindi solo per un segnale il cui `review_round` corrisponde al valore attuale dello stato.

## `repeat_until()` con una classe di condizione

```php
use App\Workflows\ReviewApprovedCondition;

Queuety::workflow( 'approval_revision_loop' )
    ->then( DraftBriefStep::class, 'draft' )
    ->wait_for_decision( result_key: 'review' )
    ->repeat_until(
        target_step: 'draft',
        condition_class: ReviewApprovedCondition::class,
        max_iterations: 5,
        name: 'repeat_draft_until_approved',
    )
    ->then( PublishBriefStep::class )
    ->dispatch( [ 'brief_id' => 42 ] );
```

```php
namespace App\Workflows;

use Queuety\Contracts\RepeatCondition;

final class ReviewApprovedCondition implements RepeatCondition {
    public function matches( array $state ): bool {
        return 'approved' === ( $state['review']['outcome'] ?? null );
    }
}
```

Le classi di condizione ricevono lo stato pubblico del flusso di lavoro, così il predicato resta serializzabile e ispezionabile senza dipendere da closure.

## `repeat_while()`

```php
Queuety::workflow( 'poll_remote_status' )
    ->max_transitions( 20 )
    ->then( PollRemoteStatusStep::class, 'poll' )
    ->delay( seconds: 30 )
    ->repeat_while( 'poll', 'should_poll_again', true, 'repeat_poll' )
    ->then( FinalizeImportStep::class )
    ->dispatch( [ 'import_id' => 99 ] );
```

Questo schema è utile quando un passaggio scrive un semplice valore booleano, ad esempio `should_poll_again`, `has_more_pages` o `needs_revision`.

## Perché le ripetizioni sono separate da `_next_step`

Potete comunque gestire le diramazioni manualmente restituendo `_next_step` da un passaggio normale. Le funzioni di supporto per la ripetizione servono per il caso più specifico in cui:

- la destinazione è un passaggio precedente
- la condizione deriva dallo stato pubblico del flusso di lavoro
- volete che la logica della ripetizione resti visibile nella definizione stessa del flusso di lavoro

In questo modo è più facile esaminare le ripetizioni nelle esportazioni, nei registri degli eventi e nella documentazione, rispetto a nascondere i collegamenti all'indietro in codice arbitrario dei passaggi.

## Proteggere le ripetizioni

Usate `max_iterations` quando una specifica ripetizione deve avere un limite massimo locale:

```php
Queuety::workflow( 'poll_remote_status' )
    ->then( PollRemoteStatusStep::class, 'poll' )
    ->repeat_while( 'poll', 'should_poll_again', true, max_iterations: 8 )
    ->dispatch();
```

Usate `max_transitions()` quando l'intero flusso di lavoro richiede un interruttore di emergenza più ampio:

```php
Queuety::workflow( 'safe_revision_loop' )
    ->max_transitions( 10 )
    ->then( DraftBriefStep::class, 'draft' )
    ->wait_for_decision( result_key: 'review' )
    ->then( NormalizeReviewOutcomeStep::class )
    ->repeat_until( 'draft', 'review_approved', true )
    ->dispatch();
```

Se una ripetizione supera `max_iterations` o il flusso di lavoro supera `max_transitions()`, Queuety interrompe l'esecuzione invece di lasciare che il ciclo continui all'infinito.

## Documentazione correlata

- [Diramazioni condizionali](https://queuety.inline0.dev/it/docs/workflows/conditional)
- [Segnali](https://queuety.inline0.dev/it/docs/workflows/signals)
- [Protezioni](https://queuety.inline0.dev/it/docs/workflows/guardrails)
- [PHP API](https://queuety.inline0.dev/it/docs/api)
