
# Pengulangan

Queuety alur kerja adalah rangkaian langkah yang berurutan. Gunakan langkah kontrol pengulangan untuk kembali ke langkah bernama sebelumnya alih-alih menulis sendiri logika `_next_step`.

Pilih bentuk yang sesuai dengan alur kerja:

- `state_key` publik jika kondisinya sederhana
- `condition_class` jika pengulangan memerlukan logika yang lebih kompleks
- `max_iterations` jika pengulangan itu sendiri memerlukan batas penghentian tegas

Kedua metode menargetkan langkah bernama sebelumnya, sehingga pengulangan tetap dapat diserialisasi, diputar ulang, dan dijaga oleh runtime alur kerja yang sama seperti langkah lainnya.

Kondisi pengulangan dievaluasi dari status alur kerja publik yang tersimpan setelah langkah sebelumnya selesai, sehingga setiap iterasi tetap tahan lama meski worker dimulai ulang.

## `repeat_until()` dengan kunci status

```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` dapat menyederhanakan payload keputusan menjadi kunci publik:

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

Jika `review_approved` masih belum `true`, langkah pengulangan melompat kembali ke `draft`. Setelah nilainya menjadi `true`, alur kerja berlanjut ke `PublishBriefStep`.

Sinyal disimpan, bukan dikonsumsi. Pada putaran kedua melalui `wait_for_decision()`, sinyal `rejected` sebelumnya masih cocok dan penantian langsung selesai, sehingga loop akan berputar terus hingga `max_transitions()` menghentikannya. Beri setiap putaran kunci tersendiri: minta `DraftBriefStep` mengembalikan `review_round` yang bertambah, berikan `correlation_key: 'review_round'` ke `wait_for_decision()`, dan minta peninjau mengirim `review_round` yang sama dalam payload sinyal. Hanya sinyal dengan `review_round` yang sama dengan nilai status saat ini yang akan menyelesaikan penantian.

## `repeat_until()` dengan kelas kondisi

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

Kelas kondisi menerima status alur kerja publik, sehingga predikat dapat diserialisasi dan diperiksa tanpa bergantung pada closure.

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

Pola ini berguna ketika langkah menulis boolean sederhana seperti `should_poll_again`, `has_more_pages`, atau `needs_revision`.

## Mengapa pengulangan terpisah dari `_next_step`

Anda tetap dapat membuat percabangan secara manual dengan mengembalikan `_next_step` dari langkah biasa. Helper pengulangan tersedia untuk kasus yang lebih spesifik ketika:

- targetnya adalah langkah sebelumnya
- kondisinya berasal dari status alur kerja publik
- Anda ingin logika pengulangan tetap terlihat dalam definisi alur kerja itu sendiri

Dengan begitu, pengulangan lebih mudah diperiksa dalam ekspor, log peristiwa, dan dokumentasi dibandingkan menyembunyikan alur balik di dalam kode langkah sembarang.

## Membatasi pengulangan

Gunakan `max_iterations` saat satu pengulangan tertentu memerlukan batas penghentian tegas:

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

Gunakan `max_transitions()` saat seluruh alur kerja memerlukan sakelar pengaman yang lebih luas:

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

Jika pengulangan melampaui `max_iterations`, atau alur kerja melampaui `max_transitions()`, Queuety akan menggagalkan eksekusi alih-alih membiarkannya berputar selamanya.

## Dokumentasi terkait

- [Percabangan Bersyarat](https://queuety.inline0.dev/id/docs/workflows/conditional)
- [Sinyal](https://queuety.inline0.dev/id/docs/workflows/signals)
- [Batas Pengaman](https://queuety.inline0.dev/id/docs/workflows/guardrails)
- [PHP API](https://queuety.inline0.dev/id/docs/api)
