
# Powtórzenia

Queuety workflows to uporządkowane ciągi kroków. Użyj kroku sterującego powtórzeniem, aby wrócić do wcześniejszego nazwanego kroku, zamiast ręcznie pisać logikę `_next_step`.

Wybierz formę pasującą do workflow:

- publiczne `state_key`, gdy warunek jest prosty
- `condition_class`, gdy powtórzenie wymaga bardziej rozbudowanej logiki
- `max_iterations`, gdy samo powtórzenie wymaga twardego limitu

Obie metody wskazują wcześniejszy nazwany krok, dzięki czemu powtórzenie pozostaje serializowalne, możliwe do odtworzenia i objęte tym samym środowiskiem wykonawczym workflow co każdy inny krok.

Warunek powtórzenia jest sprawdzany na podstawie utrwalonego publicznego stanu workflow po zakończeniu poprzedzającego kroku, dzięki czemu każda iteracja jest odporna na ponowne uruchomienie workera.

## `repeat_until()` z kluczem stanu

```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` może spłaszczyć dane decyzyjne do prostego klucza publicznego:

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

Jeśli `review_approved` nadal nie jest `true`, krok powtórzenia wraca do `draft`. Gdy stanie się `true`, workflow przechodzi dalej do `PublishBriefStep`.

Sygnały są zapisywane, a nie zużywane. Przy drugim przejściu przez `wait_for_decision()` wcześniejszy sygnał `rejected` nadal pasuje, a oczekiwanie kończy się natychmiast, więc pętla działałaby bez końca, dopóki nie zatrzymałoby jej `max_transitions()`. Nadaj każdej rundzie własny klucz: niech `DraftBriefStep` zwraca rosnącą wartość `review_round`, przekaż `correlation_key: 'review_round'` do `wait_for_decision()` i poproś recenzenta o wysłanie tej samej wartości `review_round` w danych sygnału. Oczekiwanie kończy wtedy tylko sygnał, którego `review_round` jest równe bieżącej wartości stanu.

## `repeat_until()` z klasą warunku

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

Klasy warunków otrzymują publiczny stan workflow, dzięki czemu predykat pozostaje serializowalny i możliwy do sprawdzenia bez polegania na domknięciach.

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

Ten wzorzec przydaje się, gdy krok zapisuje prostą wartość logiczną, taką jak `should_poll_again`, `has_more_pages` lub `needs_revision`.

## Dlaczego powtórzenia są oddzielone od `_next_step`

Nadal możesz ręcznie rozgałęziać workflow, zwracając `_next_step` ze zwykłego kroku. Pomocnicze mechanizmy powtórzeń służą węższemu przypadkowi, gdy:

- celem jest wcześniejszy krok
- warunek pochodzi z publicznego stanu workflow
- chcesz, aby logika powtórzenia była widoczna bezpośrednio w definicji workflow

Dzięki temu powtórzenia łatwiej sprawdzać w eksportach, dziennikach zdarzeń i dokumentacji niż w przypadku ukrycia krawędzi powrotnej w dowolnym kodzie kroku.

## Zabezpieczanie powtórzeń

Użyj `max_iterations`, gdy konkretne powtórzenie wymaga własnego twardego limitu:

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

Użyj `max_transitions()`, gdy cały workflow wymaga szerszego wyłącznika awaryjnego:

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

Jeśli powtórzenie przekroczy `max_iterations` lub workflow przekroczy `max_transitions()`, Queuety zakończy przebieg niepowodzeniem, zamiast pozwolić mu działać bez końca.

## Powiązana dokumentacja

- [Rozgałęzianie warunkowe](https://queuety.inline0.dev/pl/docs/workflows/conditional)
- [Sygnały](https://queuety.inline0.dev/pl/docs/workflows/signals)
- [Zabezpieczenia](https://queuety.inline0.dev/pl/docs/workflows/guardrails)
- [PHP API](https://queuety.inline0.dev/pl/docs/api)
