
# Upprepningar

Queuety arbetsflöden består av ordnade stegkedjor. Använd ett upprepningssteg för att återgå till ett tidigare namngivet steg i stället för att skriva `_next_step`-logik för hand.

Välj den form som passar arbetsflödet:

- en offentlig `state_key` när villkoret är enkelt
- en `condition_class` när upprepningen kräver mer avancerad logik
- `max_iterations` när själva upprepningen behöver en tydlig gräns

Båda metoderna riktar sig mot ett tidigare namngivet steg, så upprepningen förblir serialiserbar, återspelningsbar och skyddad av samma arbetsflödeskörmiljö som alla andra steg.

Upprepningsvillkoret utvärderas från det beständiga offentliga arbetsflödestillståndet när det föregående steget har slutförts, så varje iteration förblir beständig även efter omstarter av arbetare.

## `repeat_until()` med en tillståndsnyckel

```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 platta till beslutsnyttolasten till en enkel offentlig nyckel:

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

Om `review_approved` fortfarande inte är `true` hoppar upprepningssteget tillbaka till `draft`. När det blir `true` fortsätter arbetsflödet till `PublishBriefStep`.

Signaler lagras, de förbrukas inte. Vid den andra genomgången av `wait_for_decision()` matchar den tidigare `rejected`-signalen fortfarande och väntan avslutas omedelbart, så loopen skulle snurra tills `max_transitions()` stoppar den. Ge varje omgång en egen nyckel: låt `DraftBriefStep` returnera ett inkrementerande `review_round`, skicka `correlation_key: 'review_round'` till `wait_for_decision()` och låt granskaren skicka samma `review_round` i signalens nyttolast. Då avslutas väntan bara när en signal vars `review_round` motsvarar det aktuella tillståndsvärdet tas emot.

## `repeat_until()` med en villkorsklass

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

Villkorsklasser får det offentliga arbetsflödestillståndet, så predikatet förblir serialiserbart och granskningsbart utan att vara beroende av 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 ] );
```

Mönstret är användbart när ett steg skriver ett enkelt booleskt värde som `should_poll_again`, `has_more_pages` eller `needs_revision`.

## Varför upprepningar är separata från `_next_step`

Du kan fortfarande skapa grenar manuellt genom att returnera `_next_step` från ett vanligt steg. Hjälpfunktionerna för upprepning finns för det mer avgränsade fallet när:

- målet är ett tidigare steg
- villkoret kommer från det offentliga arbetsflödestillståndet
- du vill att upprepningslogiken ska vara synlig i själva arbetsflödesdefinitionen

Det gör upprepningar enklare att granska i exporter, händelseloggar och dokumentation än om återkopplingen göms i godtycklig stegspecifik kod.

## Begränsa upprepningar

Använd `max_iterations` när en viss upprepning behöver en lokal gräns:

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

Använd `max_transitions()` när hela arbetsflödet behöver en bredare nödbrytare:

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

Om en upprepning överskrider `max_iterations`, eller arbetsflödet överskrider `max_transitions()`, avbryter Queuety körningen i stället för att låta den snurra för alltid.

## Relaterad dokumentation

- [Villkorsstyrd förgrening](https://queuety.inline0.dev/sv/docs/workflows/conditional)
- [Signaler](https://queuety.inline0.dev/sv/docs/workflows/signals)
- [Skyddsräcken](https://queuety.inline0.dev/sv/docs/workflows/guardrails)
- [PHP API](https://queuety.inline0.dev/sv/docs/api)
