
# Opakovania

Queuety sú usporiadané reťazce krokov. Pomocou riadiaceho kroku opakovania sa môžete vrátiť k predchádzajúcemu pomenovanému kroku namiesto ručného písania logiky `_next_step`.

Vyberte formu, ktorá zodpovedá pracovnému postupu:

- verejné `state_key`, ak je podmienka jednoduchá
- `condition_class`, ak opakovanie vyžaduje zložitejšiu logiku
- `max_iterations`, ak opakovanie potrebuje pevný limit

Obe metódy smerujú na predchádzajúci pomenovaný krok, takže opakovanie zostáva serializovateľné, prehrateľné a podlieha rovnakému modulu runtime pracovného postupu ako všetky ostatné kroky.

Podmienka opakovania sa vyhodnotí z uloženého verejného stavu pracovného postupu po dokončení predchádzajúceho kroku, takže každé opakovanie zostáva trvalé aj po reštarte pracovníka.

## `repeat_until()` s kľúčom stavu

```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 zjednodušiť údaje o rozhodnutí na jednoduchý verejný kľúč:

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

Ak `review_approved` stále nie je `true`, krok opakovania sa vráti na `draft`. Keď sa zmení na `true`, pracovný postup pokračuje krokom `PublishBriefStep`.

Signály sa ukladajú, nespotrebúvajú sa. Pri druhom prechode cez `wait_for_decision()` sa stále zhoduje predchádzajúci signál `rejected` a čakanie sa okamžite skončí, takže slučka by sa opakovala, kým ju nezastaví `max_transitions()`. Každému kolu priraďte vlastný kľúč: nech `DraftBriefStep` vráti rastúce `review_round`, odovzdajte `correlation_key: 'review_round'` funkcii `wait_for_decision()` a nech kontrolór odošle rovnaké `review_round` v údajoch signálu. Čakanie sa potom ukončí iba vtedy, keď sa `review_round` signálu rovná aktuálnej hodnote stavu.

## `repeat_until()` s triedou podmienky

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

Triedy podmienok dostávajú verejný stav pracovného postupu, takže predikát zostáva serializovateľný a kontrolovateľný bez závislosti od uzáverov.

## `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 je užitočný, keď krok zapíše jednoduchú booleovskú hodnotu, napríklad `should_poll_again`, `has_more_pages` alebo `needs_revision`.

## Prečo sú opakovania oddelené od `_next_step`

Vetviť môžete aj ručne tak, že z bežného kroku vrátite `_next_step`. Pomocné funkcie na opakovanie slúžia na užší prípad, keď:

- cieľom je predchádzajúci krok
- podmienka vychádza z verejného stavu pracovného postupu
- chcete, aby logika opakovania zostala viditeľná priamo v definícii pracovného postupu

Vďaka tomu sa opakovania ľahšie kontrolujú v exportoch, protokoloch udalostí a dokumentácii než pri skrytí spätného prepojenia v ľubovoľnom kóde kroku.

## Ochrana pred nekonečným opakovaním

Ak jedno konkrétne opakovanie potrebuje miestny pevný limit, použite `max_iterations`:

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

Ak celý pracovný postup potrebuje širšiu poistku, použite `max_transitions()`:

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

Ak opakovanie prekročí `max_iterations` alebo pracovný postup prekročí `max_transitions()`, Queuety zlyhaním ukončí beh, aby sa nikdy neopakoval donekonečna.

## Súvisiaca dokumentácia

- [Podmienené vetvenie](https://queuety.inline0.dev/sk/docs/workflows/conditional)
- [Signály](https://queuety.inline0.dev/sk/docs/workflows/signals)
- [Ochranné opatrenia](https://queuety.inline0.dev/sk/docs/workflows/guardrails)
- [PHP API](https://queuety.inline0.dev/sk/docs/api)
