본문으로 건너뛰기

워크플로 신호

워크플로는 신호를 사용해 외부 입력을 기다리며 일시 중지할 수 있습니다. wait_for_signal() 및 wait_for_signals() 빌더 메서드는 애플리케이션, 운영자 또는 다른 시스템이 Queuety::signal()을 통해 예상된 신호를 보낼 때까지 워크플로를 차단하는 단계를 추가합니다.

사람이 참여하는 워크플로의 경우 Queuety에는 다음 기능도 포함됩니다:

  • 네임스페이스가 지정된 승인 페이로드를 위한 wait_for_approval()
  • 구조화된 사용자 입력을 위한 wait_for_input()
  • 명시적인 승인/거부 결과를 위한 wait_for_decision()

에이전트 실행과 사람의 승인 단계를 결합한 더 큰 엔드투엔드 예제는 에이전트 오케스트레이션을 참조하세요.

->wait_for_signal() 메서드

워크플로에 신호 대기 단계를 추가합니다:

PHP
use Queuety\Queuety;

$workflow_id = Queuety::workflow( 'content_approval' )
    ->then( DraftContentHandler::class )
    ->wait_for_signal( 'approved' )
    ->then( PublishContentHandler::class )
    ->dispatch( [ 'post_id' => 99 ] );

워크플로가 wait_for_signal 단계에 도달하면 상태가 waiting_for_signal로 설정되고 일시 중지됩니다. 대기 중에는 작업이 실행되지 않고 리소스도 소비되지 않습니다.

기본적으로 단일 신호 페이로드는 최상위 워크플로 상태에 병합됩니다. 대신 페이로드에 네임스페이스를 지정하려면 result_key을 전달하세요:

PHP
$workflow_id = Queuety::workflow( 'content_approval' )
    ->then( DraftContentHandler::class )
    ->wait_for_signal( 'approved', 'approval' )
    ->then( PublishContentHandler::class )
    ->dispatch( [ 'post_id' => 99 ] );

신호가 도착한 뒤에는 $state['approval']에 신호 페이로드 배열이 들어 있습니다.

여러 신호 대기

여러 신호 중 하나가 도착하면 워크플로를 재개하거나, 계속 진행하기 전에 필요한 모든 신호를 수집해야 하는 경우 wait_for_signals()을 사용하세요.

PHP
use Queuety\Enums\WaitMode;

$workflow_id = Queuety::workflow( 'moderation_gate' )
    ->then( SubmitForReviewHandler::class )
    ->wait_for_signals(
        [ 'editor_approved', 'legal_approved' ],
        WaitMode::All,
        'approvals'
    )
    ->then( PublishContentHandler::class )
    ->dispatch();
  • WaitMode::All은 설정된 모든 신호가 기록될 때까지 기다립니다.
  • WaitMode::Any은 설정된 신호 중 하나가 도착하면 즉시 재개됩니다.

result_key을 사용하면 다중 신호 대기에서 신호 이름을 키로 하는 연관 배열을 저장합니다:

PHP
[
    'editor_approved' => [ 'by' => 'editor@example.com' ],
    'legal_approved' => [ 'by' => 'legal@example.com' ],
]

result_key을 사용하지 않으면 일치한 신호 페이로드가 단계에 설정된 순서대로 최상위 상태에 병합됩니다.

일치 및 상관관계

신호 대기는 페이로드 내용을 일치시키거나 공유 키를 기준으로 상관관계를 확인해 관련 없는 이벤트를 무시할 수 있습니다.

PHP
$workflow_id = Queuety::workflow( 'review_gate' )
    ->wait_for_signal(
        name: 'review_ready',
        result_key: 'review',
        match_payload: [ 'source' => 'editorial' ],
        correlation_key: 'task_id',
    )
    ->dispatch( [ 'task_id' => 'task-42' ] );

이 예제에서는 다음 조건을 충족할 때만 대기가 재개됩니다:

  • 신호 이름이 review_ready입니다.
  • 페이로드에 source: editorial이 포함되어 있습니다.
  • 페이로드의 task_id가 $state['task_id']과 일치합니다.

이 기능은 여러 외부 시스템이나 에이전트가 같은 이벤트 이름을 게시하지만 페이로드 하나만 현재 워크플로 실행에 해당할 때 유용합니다.

사람의 승인 및 입력 도우미

wait_for_approval() 및 wait_for_input()은 신호 대기를 편리하게 감싼 래퍼입니다. wait_for_signal()처럼 동작하지만 기본 결과 키에 네임스페이스가 지정됩니다.

PHP
$workflow_id = Queuety::workflow( 'blog_review' )
    ->then( GenerateDraftHandler::class )
    ->wait_for_approval()
    ->wait_for_input()
    ->then( HandleDecisionHandler::class )
    ->dispatch();

approval 신호가 도착한 뒤에는 $state['approval']에 승인 페이로드가 들어 있습니다. input 신호가 도착한 뒤에는 $state['input']에 입력 페이로드가 들어 있습니다.

승인 또는 거부 중 하나로 단일 단계를 재개하려면 wait_for_decision()을 사용하세요:

PHP
$workflow_id = Queuety::workflow( 'legal_review' )
    ->then( DraftContractHandler::class )
    ->wait_for_decision( result_key: 'review' )
    ->then( HandleReviewDecisionHandler::class )
    ->dispatch();

신호가 도착한 뒤 $state['review']에는 다음 내용이 들어 있습니다:

PHP
[
    'outcome' => 'approved', // or 'rejected'
    'signal'  => 'approved', // or 'rejected'
    'data'    => [ 'reviewer' => 'ops@example.com' ],
]

신호 보내기

Queuety::signal()을 사용해 워크플로에 신호를 보냅니다:

PHP
Queuety::signal( $workflow_id, 'approved' );

신호가 워크플로가 기다리는 신호와 일치하면 워크플로가 재개되고 다음 단계로 진행합니다.

사람이 참여하는 워크플로의 경우 파사드에는 의미에 맞는 도우미 메서드도 제공됩니다:

PHP
Queuety::approve_workflow( $workflow_id, [ 'reviewer' => 'editor@example.com' ] );
Queuety::reject_workflow( $workflow_id, [ 'reason' => 'needs citations' ] );
Queuety::submit_workflow_input( $workflow_id, [ 'notes' => 'Ship after copy edit' ] );

신호 데이터

신호와 함께 데이터 배열을 전달할 수 있습니다. 데이터는 워크플로 상태에 병합되어 후속 단계에서 사용할 수 있습니다:

PHP
Queuety::signal( $workflow_id, 'approved', [
    'approved_by' => 'admin@example.com',
    'approved_at' => time(),
] );

다음 단계에서는:

PHP
class PublishContentHandler implements Step {
    public function handle( array $state ): array {
        // $state['approved_by'] is 'admin@example.com'
        // $state['approved_at'] is the timestamp
        // $state['post_id'] is 99
        wp_update_post( [
            'ID'          => $state['post_id'],
            'post_status' => 'publish',
        ] );
        return [ 'published' => true ];
    }

    public function config(): array {
        return [];
    }
}

_(밑줄)로 시작하는 키는 내부 용도로 예약되어 있으며 상태에 병합되지 않습니다.

기존 신호

워크플로가 대기 단계에 도달하기 전에 신호를 보낼 수 있습니다. 신호가 도착하면 워크플로의 현재 상태와 관계없이 queuety_signals 테이블에 기록됩니다. 이후 워크플로가 wait_for_signal 단계에 도달하면 기존 신호를 확인하고 발견된 경우 즉시 계속 진행합니다.

따라서 순서는 중요하지 않습니다:

PHP
// Dispatch workflow
$workflow_id = Queuety::workflow( 'order_fulfillment' )
    ->then( PrepareOrderHandler::class )
    ->wait_for_signal( 'payment_confirmed' )
    ->then( ShipOrderHandler::class )
    ->dispatch( [ 'order_id' => 123 ] );

// Signal arrives before the workflow reaches the wait step
Queuety::signal( $workflow_id, 'payment_confirmed', [ 'transaction_id' => 'txn_abc' ] );

// When the workflow finishes PrepareOrderHandler and reaches the wait step,
// it sees the pre-existing signal and continues immediately.

신호 대기 중 워크플로 상태

신호를 기다리는 동안 워크플로 상태는 WorkflowStatus::WaitingForSignal(waiting_for_signal)입니다:

PHP
$state = Queuety::workflow_status( $workflow_id );
echo $state->status->value; // 'waiting_for_signal'
echo $state->wait_type;     // 'signal'
echo $state->wait_mode;     // 'all' or 'any'
print_r( $state->waiting_for ); // ['approved']
print_r( $state->wait_details ); // matched/remaining, correlation info, result key, ...

사용 사례: 사람의 승인

사람 검토자가 콘텐츠 공개 전에 승인해야 하는 승인 워크플로를 구성합니다:

PHP
$workflow_id = Queuety::workflow( 'blog_review' )
    ->then( GenerateDraftHandler::class )
    ->then( NotifyReviewerHandler::class )
    ->wait_for_decision( result_key: 'review' )
    ->then( HandleDecisionHandler::class )
    ->dispatch( [ 'topic' => 'PHP middleware patterns' ] );

NotifyReviewerHandler은 승인/거부 링크가 포함된 이메일 또는 Slack 메시지를 보냅니다. 이 링크는 컨트롤러를 호출하고, 컨트롤러는 신호를 보냅니다:

PHP
// In your approval controller
Queuety::approve_workflow(
    $workflow_id,
    [
        'reviewer'    => $current_user->user_email,
        'reviewed_at' => time(),
    ],
    'approved'
);

HandleDecisionHandler 단계는 $state['review']['outcome']을 읽고 초안을 게시하거나 보관합니다.

사용 사례: 외부 API 콜백

외부 서비스의 웹훅을 기다립니다:

PHP
$workflow_id = Queuety::workflow( 'generate_video' )
    ->then( SubmitToRenderServiceHandler::class )
    ->wait_for_signal( 'render_complete' )
    ->then( DownloadVideoHandler::class )
    ->then( NotifyUserHandler::class )
    ->dispatch( [ 'template_id' => 'promo_v2', 'user_id' => 42 ] );

외부 서비스가 웹훅 엔드포인트로 요청을 보내면:

PHP
// In your webhook handler
$payload = json_decode( file_get_contents( 'php://input' ), true );
$workflow_id = (int) $payload['metadata']['workflow_id'];

Queuety::signal( $workflow_id, 'render_complete', [
    'video_url'  => $payload['output_url'],
    'duration'   => $payload['duration_seconds'],
] );

한 워크플로의 여러 신호 게이트

워크플로는 여러 지점에서 신호를 기다릴 수 있습니다:

PHP
Queuety::workflow( 'multi_approval' )
    ->then( CreateProposalHandler::class )
    ->wait_for_signal( 'manager_approval' )
    ->then( EscalateToDirectorHandler::class )
    ->wait_for_signal( 'director_approval' )
    ->then( ExecuteProposalHandler::class )
    ->dispatch( [ 'proposal_id' => 7 ] );

각 신호 대기 단계는 독립적입니다. 하나의 워크플로에서 wait_for_signal(), wait_for_signals(), wait_for_approval(), wait_for_input() 및 wait_for_decision()를 함께 사용할 수 있습니다.