コンテンツへスキップ

繰り返し

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は無限に実行され続ける代わりに実行を失敗させます。