
# Repetări

Queuety fluxurile de lucru sunt lanțuri ordonate de pași. Folosiți un pas de control al repetării pentru a reveni la un pas anterior cu nume, în loc să scrieți manual logica `_next_step`.

Alegeți forma potrivită pentru fluxul de lucru:

- un `state_key` public când condiția este simplă
- o `condition_class` când repetarea necesită o logică mai complexă
- `max_iterations` când repetarea în sine are nevoie de o limită strictă

Ambele metode vizează un pas anterior cu nume, astfel încât repetarea rămâne serializabilă, reexecutabilă și protejată de același motor de fluxuri de lucru ca orice alt pas.

Condiția de repetare este evaluată pe baza stării publice persistate a fluxului de lucru după încheierea pasului precedent, astfel încât fiecare iterație rezistă la repornirea lucrătorului.

## `repeat_until()` cu o cheie de stare

```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` poate aplatiza datele deciziei într-o cheie publică simplă:

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

Dacă `review_approved` încă nu este `true`, pasul de repetare revine la `draft`. Când devine `true`, fluxul de lucru trece la `PublishBriefStep`.

Semnalele sunt stocate, nu consumate. La a doua trecere prin `wait_for_decision()`, semnalul `rejected` anterior se potrivește în continuare, iar așteptarea se încheie imediat, astfel încât bucla s-ar repeta la nesfârșit până când `max_transitions()` o oprește. Dați fiecărei runde propria cheie: faceți ca `DraftBriefStep` să returneze un `review_round` incrementat, transmiteți `correlation_key: 'review_round'` către `wait_for_decision()` și solicitați evaluatorului să trimită aceeași `review_round` în datele utile ale semnalului. Așteptarea se încheie apoi numai dacă `review_round` semnalului este egală cu valoarea curentă a stării.

## `repeat_until()` cu o clasă de condiție

```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 );
    }
}
```

Clasele de condiție primesc starea publică a fluxului de lucru, astfel încât predicatul rămâne serializabil și inspectabil fără a depinde de închideri.

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

Acest tipar este util când un pas scrie o valoare booleană simplă, cum ar fi `should_poll_again`, `has_more_pages` sau `needs_revision`.

## De ce repetările sunt separate de `_next_step`

Puteți ramifica în continuare manual returnând `_next_step` dintr-un pas obișnuit. Funcțiile auxiliare de repetare sunt pentru cazul mai restrâns în care:

- ținta este un pas anterior
- condiția provine din starea publică a fluxului de lucru
- doriți ca logica de repetare să rămână vizibilă chiar în definiția fluxului de lucru

Astfel, repetările sunt mai ușor de inspectat în exporturi, jurnalele de evenimente și documentație decât dacă ascundeți bucla de revenire în cod arbitrar al unui pas.

## Protejarea repetărilor

Folosiți `max_iterations` când o anumită repetare are nevoie de o limită strictă locală:

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

Folosiți `max_transitions()` când întregul flux de lucru are nevoie de un mecanism general de oprire de urgență:

```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();
```

Dacă o repetare depășește `max_iterations` sau fluxul de lucru depășește `max_transitions()`, Queuety oprește execuția cu eroare, în loc să o lase să se repete la nesfârșit.

## Documentație asociată

- [Ramificare condiționată](https://queuety.inline0.dev/ro/docs/workflows/conditional)
- [Semnale](https://queuety.inline0.dev/ro/docs/workflows/signals)
- [Măsuri de protecție](https://queuety.inline0.dev/ro/docs/workflows/guardrails)
- [PHP API](https://queuety.inline0.dev/ro/docs/api)
