
# 繰り返し

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()`を2回目に通るときも、以前の`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/ja/docs/workflows/conditional)
- [シグナル](https://queuety.inline0.dev/ja/docs/workflows/signals)
- [ガードレール](https://queuety.inline0.dev/ja/docs/workflows/guardrails)
- [PHP API](https://queuety.inline0.dev/ja/docs/api)
