
# Herhalingen

Queuety-workflows bestaan uit geordende stapketens. Gebruik een herhaalbesturingsstap om terug te keren naar een eerder benoemde stap, in plaats van zelf `_next_step`-logica te schrijven.

Kies de vorm die bij de workflow past:

- een openbare `state_key` wanneer de voorwaarde eenvoudig is
- een `condition_class` wanneer de herhaling complexere logica vereist
- `max_iterations` wanneer de herhaling zelf een harde stop nodig heeft

Beide methoden richten zich op een eerder benoemde stap, zodat de herhaling serialiseerbaar en opnieuw afspeelbaar blijft en wordt bewaakt door dezelfde workflowruntime als elke andere stap.

De herhaalvoorwaarde wordt geëvalueerd op basis van de persistente openbare workflowstatus nadat de voorgaande stap is voltooid. Zo blijft elke iteratie behouden bij herstarts van de worker.

## `repeat_until()` met een statussleutel

```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 de beslissingspayload omzetten in een eenvoudige openbare sleutel:

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

Als `review_approved` nog steeds niet `true` is, springt de herhaalstap terug naar `draft`. Zodra het `true` wordt, gaat de workflow verder naar `PublishBriefStep`.

Signalen worden opgeslagen, niet verbruikt. Bij de tweede keer dat `wait_for_decision()` wordt doorlopen, komt het eerdere signaal `rejected` nog steeds overeen en wordt het wachten onmiddellijk beëindigd. De lus zou dus blijven draaien totdat `max_transitions()` deze stopt. Geef elke ronde een eigen sleutel: laat `DraftBriefStep` een oplopende `review_round` retourneren, geef `correlation_key: 'review_round'` door aan `wait_for_decision()` en laat de beoordelaar dezelfde `review_round` meesturen in de signaalpayload. Alleen een signaal waarvan `review_round` overeenkomt met de huidige statuswaarde beëindigt dan het wachten.

## `repeat_until()` met een voorwaardeklasse

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

Voorwaardeklassen ontvangen de openbare workflowstatus. Zo blijft de predicaatfunctie serialiseerbaar en inspecteerbaar zonder afhankelijk te zijn van 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 ] );
```

Dit patroon is handig wanneer een stap een eenvoudige booleaanse waarde schrijft, zoals `should_poll_again`, `has_more_pages` of `needs_revision`.

## Waarom herhalingen losstaan van `_next_step`

U kunt nog steeds handmatig vertakken door `_next_step` vanuit een normale stap te retourneren. De herhaalhulpmethoden zijn bedoeld voor het beperktere geval waarin:

- het doel een eerdere stap is
- de voorwaarde afkomstig is uit de openbare workflowstatus
- u de herhaallogica zichtbaar wilt houden in de workflowdefinitie zelf

Daardoor zijn herhalingen makkelijker te inspecteren in exports, gebeurtenislogboeken en documentatie dan wanneer u de terugkoppeling verbergt in willekeurige stapcode.

## Herhalingen bewaken

Gebruik `max_iterations` wanneer een specifieke herhaling een lokale harde stop nodig heeft:

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

Gebruik `max_transitions()` wanneer de hele workflow een bredere noodstop nodig heeft:

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

Als een herhaling `max_iterations` overschrijdt, of de workflow `max_transitions()` overschrijdt, mislukt Queuety en wordt de uitvoering beëindigd in plaats van oneindig door te draaien.

## Gerelateerde documentatie

- [Voorwaardelijke vertakkingen](https://queuety.inline0.dev/nl/docs/workflows/conditional)
- [Signalen](https://queuety.inline0.dev/nl/docs/workflows/signals)
- [Beveiligingen](https://queuety.inline0.dev/nl/docs/workflows/guardrails)
- [PHP API](https://queuety.inline0.dev/nl/docs/api)
