
# Opakování

Queuety workflow jsou uspořádané řetězce kroků. Pomocí řídicího kroku opakování se vrátíte k dřívějšímu pojmenovanému kroku, místo abyste ručně psali logiku `_next_step`.

Zvolte podobu, která odpovídá vašemu workflow:

- veřejné `state_key`, pokud je podmínka jednoduchá
- `condition_class`, pokud opakování vyžaduje složitější logiku
- `max_iterations`, pokud opakování potřebuje pevný limit

Obě metody cílí na dřívější pojmenovaný krok, takže opakování zůstává serializovatelné, přehratelné a podléhá stejnému běhovému prostředí workflow jako každý jiný krok.

Podmínka opakování se vyhodnocuje z uloženého veřejného stavu workflow po dokončení předchozího kroku, takže každá iterace zůstává trvalá i po restartu workeru.

## `repeat_until()` se stavovým klíčem

```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` může převést data pro rozhodnutí na jednoduchý veřejný klíč:

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

Pokud `review_approved` stále není `true`, krok opakování se vrátí na `draft`. Jakmile se změní na `true`, workflow přejde na `PublishBriefStep`.

Signály se ukládají, nespotřebovávají. Při druhém průchodu krokem `wait_for_decision()` se dřívější signál `rejected` stále shoduje a čekání se ihned vyřídí, takže by se smyčka opakovala, dokud by ji nezastavilo `max_transitions()`. Každému kolu přiřaďte vlastní klíč: nechte `DraftBriefStep` vrátit inkrementující se `review_round`, předejte `correlation_key: 'review_round'` do `wait_for_decision()` a nechte kontrolora poslat ve výsledném signálu stejné `review_round`. Čekání pak vyřídí pouze signál, jehož `review_round` odpovídá aktuální hodnotě stavu.

## `repeat_until()` s třídou podmínky

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

Třídy podmínek přijímají veřejný stav workflow, takže predikát zůstává serializovatelný a kontrolovatelný bez závislosti na uzávěrech.

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

Tento vzor se hodí, když krok zapisuje jednoduchou booleovskou hodnotu, například `should_poll_again`, `has_more_pages` nebo `needs_revision`.

## Proč jsou opakování oddělená od `_next_step`

Větvení můžete stále řídit ručně tak, že z běžného kroku vrátíte `_next_step`. Pomocné funkce pro opakování jsou určeny pro užší případy, kdy:

- cílem je dřívější krok
- podmínka vychází z veřejného stavu workflow
- chcete, aby logika opakování zůstala viditelná přímo v definici workflow

Díky tomu lze opakování snáze kontrolovat v exportech, protokolech událostí a dokumentaci, než když je zpětná vazba skrytá v libovolném kódu kroku.

## Omezení opakování

Použijte `max_iterations`, když potřebujete pevný limit pro konkrétní opakování:

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

Použijte `max_transitions()`, když celé workflow potřebuje širší pojistku:

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

Pokud opakování překročí `max_iterations` nebo workflow překročí `max_transitions()`, Queuety běh ukončí chybou, místo aby se nechalo nekonečně opakovat.

## Související dokumentace

- [Podmíněné větvení](https://queuety.inline0.dev/cs/docs/workflows/conditional)
- [Signály](https://queuety.inline0.dev/cs/docs/workflows/signals)
- [Ochranné prvky](https://queuety.inline0.dev/cs/docs/workflows/guardrails)
- [PHP API](https://queuety.inline0.dev/cs/docs/api)
