
# Повторения

Queuety работните потоци представляват подредени вериги от стъпки. Използвайте контролна стъпка за повторение, за да се върнете към по-ранна именувана стъпка, вместо да пишете логика с `_next_step` на ръка.

Изберете формата, която съответства на работния поток:

- публичен `state_key`, когато условието е просто
- `condition_class`, когато повторението изисква по-сложна логика
- `max_iterations`, когато за самото повторение е нужна твърда горна граница

И двата метода целят по-ранна именувана стъпка, така че повторението остава сериализируемо, възпроизвеждаемо и защитено от същата среда за изпълнение на работни потоци като всяка друга стъпка.

Условието за повторение се проверява спрямо запазеното публично състояние на работния поток след приключването на предходната стъпка, така че всяка итерация остава устойчива при рестартиране на работника.

## `repeat_until()` с ключ на състоянието

```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` може да сведе данните за решение до прост публичен ключ:

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

Ако `review_approved` все още не е `true`, стъпката за повторение се връща към `draft`. След като стане `true`, работният поток продължава към `PublishBriefStep`.

Сигналите се съхраняват, а не се консумират. При второто преминаване през `wait_for_decision()` предходният сигнал `rejected` все още съвпада и изчакването приключва незабавно, така че цикълът ще се върти, докато `max_transitions()` не го спре. Задавайте отделен ключ за всеки кръг: накарайте `DraftBriefStep` да връща нарастваща `review_round`, подайте `correlation_key: 'review_round'` на `wait_for_decision()` и накарайте проверяващия да изпрати същото `review_round` в полезния товар на сигнала. Тогава изчакването приключва само при сигнал, чиято `review_round` съвпада с текущата стойност в състоянието.

## `repeat_until()` с клас на условието

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

Класовете на условията получават публичното състояние на работния поток, така че предикатът остава сериализируем и достъпен за проверка, без да разчита на затваряния.

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

Този модел е полезен, когато стъпка записва проста булева стойност като `should_poll_again`, `has_more_pages` или `needs_revision`.

## Защо повторенията са отделни от `_next_step`

Все още можете да управлявате разклоненията ръчно, като връщате `_next_step` от обичайна стъпка. Помощните средства за повторение са предназначени за по-ограничения случай, когато:

- целевата стъпка е по-ранна
- условието идва от публичното състояние на работния поток
- искате логиката за повторение да се вижда в самата дефиниция на работния поток

Така повторенията се преглеждат по-лесно в експорти, регистрационни файлове на събития и документация, вместо да се скрива връзката назад в произволен код на стъпка.

## Ограничаване на повторенията

Използвайте `max_iterations`, когато е необходима локална твърда горна граница за конкретно повторение:

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

Използвайте `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();
```

Ако повторението надхвърли `max_iterations` или работният поток надхвърли `max_transitions()`, Queuety прекратява изпълнението, вместо да му позволява да се върти безкрайно.

## Свързана документация

- [Условно разклоняване](https://queuety.inline0.dev/bg/docs/workflows/conditional)
- [Сигнали](https://queuety.inline0.dev/bg/docs/workflows/signals)
- [Защитни ограничения](https://queuety.inline0.dev/bg/docs/workflows/guardrails)
- [PHP API](https://queuety.inline0.dev/bg/docs/api)
