
# Gentagelser

Queuety workflows består af ordnede trinsekvenser. Brug et gentagelseskontroltrin til at vende tilbage til et tidligere navngivet trin i stedet for selv at skrive `_next_step`-logik.

Vælg den form, der passer til workflowet:

- en offentlig `state_key`, når betingelsen er enkel
- en `condition_class`, når gentagelsen kræver mere omfattende logik
- `max_iterations`, når gentagelsen skal have en fast grænse

Begge metoder retter sig mod et tidligere navngivet trin, så gentagelsen forbliver serialiserbar, afspilbar og beskyttet af samme workflowruntime som alle andre trin.

Gentagelsesbetingelsen evalueres ud fra den gemte offentlige workflowtilstand, når det foregående trin er fuldført, så hver gentagelse forbliver holdbar på tværs af genstarter af workerprocessen.

## `repeat_until()` med en tilstandsnøgle

```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` kan samle beslutningsnyttelasten i en enkel offentlig nøgle:

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

Hvis `review_approved` stadig ikke er `true`, springer gentagelsestrinnet tilbage til `draft`. Når det bliver `true`, fortsætter workflowet til `PublishBriefStep`.

Signaler gemmes, de forbruges ikke. Ved anden gennemgang af `wait_for_decision()` matcher det tidligere `rejected`-signal stadig, og ventetiden afsluttes med det samme, så løkken ville køre i ring, indtil `max_transitions()` stopper den. Giv hver runde sin egen nøgle: Lad `DraftBriefStep` returnere en stigende `review_round`, send `correlation_key: 'review_round'` til `wait_for_decision()`, og lad bedømmeren sende samme `review_round` i signalnyttelasten. Så afslutter kun et signal, hvor `review_round` svarer til den aktuelle tilstandsværdi, ventetiden.

## `repeat_until()` med en betingelsesklasse

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

Betingelsesklasser modtager den offentlige workflowtilstand, så prædikatet forbliver serialiserbart og inspicerbart uden at være afhængigt af closures.

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

Dette mønster er nyttigt, når et trin skriver en enkel boolesk værdi som `should_poll_again`, `has_more_pages` eller `needs_revision`.

## Hvorfor gentagelser er adskilt fra `_next_step`

Du kan stadig forgrene manuelt ved at returnere `_next_step` fra et normalt trin. Gentagelsesfunktionerne findes til det mere afgrænsede tilfælde, hvor:

- målet er et tidligere trin
- betingelsen kommer fra den offentlige workflowtilstand
- du ønsker, at gentagelseslogikken skal være synlig i selve workflowdefinitionen

Det gør gentagelser nemmere at inspicere i eksporter, hændelseslogfiler og dokumentation end at skjule tilbagekanten i vilkårlig trinkode.

## Begrænsning af gentagelser

Brug `max_iterations`, når en bestemt gentagelse skal have en lokal fast grænse:

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

Brug `max_transitions()`, når hele workflowet skal have en bredere livlineafbryder:

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

Hvis en gentagelse overskrider `max_iterations`, eller workflowet overskrider `max_transitions()`, fejler Queuety kørslen i stedet for at lade den køre i ring for evigt.

## Relateret dokumentation

- [Betinget forgrening](https://queuety.inline0.dev/da/docs/workflows/conditional)
- [Signaler](https://queuety.inline0.dev/da/docs/workflows/signals)
- [Værn](https://queuety.inline0.dev/da/docs/workflows/guardrails)
- [PHP API](https://queuety.inline0.dev/da/docs/api)
