워크플로 신호
워크플로는 신호를 사용해 외부 입력을 기다리며 일시 중지할 수 있습니다. wait_for_signal() 및 wait_for_signals() 빌더 메서드는 애플리케이션, 운영자 또는 다른 시스템이 Queuety::signal()을 통해 예상된 신호를 보낼 때까지 워크플로를 차단하는 단계를 추가합니다.
사람이 참여하는 워크플로의 경우 Queuety에는 다음 기능도 포함됩니다:
- 네임스페이스가 지정된 승인 페이로드를 위한
wait_for_approval() - 구조화된 사용자 입력을 위한
wait_for_input() - 명시적인 승인/거부 결과를 위한
wait_for_decision()
에이전트 실행과 사람의 승인 단계를 결합한 더 큰 엔드투엔드 예제는 에이전트 오케스트레이션을 참조하세요.
->wait_for_signal() 메서드
워크플로에 신호 대기 단계를 추가합니다:
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을 전달하세요:
$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()을 사용하세요.
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을 사용하면 다중 신호 대기에서 신호 이름을 키로 하는 연관 배열을 저장합니다:
[
'editor_approved' => [ 'by' => 'editor@example.com' ],
'legal_approved' => [ 'by' => 'legal@example.com' ],
]
result_key을 사용하지 않으면 일치한 신호 페이로드가 단계에 설정된 순서대로 최상위 상태에 병합됩니다.
일치 및 상관관계
신호 대기는 페이로드 내용을 일치시키거나 공유 키를 기준으로 상관관계를 확인해 관련 없는 이벤트를 무시할 수 있습니다.
$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()처럼 동작하지만 기본 결과 키에 네임스페이스가 지정됩니다.
$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()을 사용하세요:
$workflow_id = Queuety::workflow( 'legal_review' )
->then( DraftContractHandler::class )
->wait_for_decision( result_key: 'review' )
->then( HandleReviewDecisionHandler::class )
->dispatch();
신호가 도착한 뒤 $state['review']에는 다음 내용이 들어 있습니다:
[
'outcome' => 'approved', // or 'rejected'
'signal' => 'approved', // or 'rejected'
'data' => [ 'reviewer' => 'ops@example.com' ],
]
신호 보내기
Queuety::signal()을 사용해 워크플로에 신호를 보냅니다:
Queuety::signal( $workflow_id, 'approved' );
신호가 워크플로가 기다리는 신호와 일치하면 워크플로가 재개되고 다음 단계로 진행합니다.
사람이 참여하는 워크플로의 경우 파사드에는 의미에 맞는 도우미 메서드도 제공됩니다:
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' ] );
신호 데이터
신호와 함께 데이터 배열을 전달할 수 있습니다. 데이터는 워크플로 상태에 병합되어 후속 단계에서 사용할 수 있습니다:
Queuety::signal( $workflow_id, 'approved', [
'approved_by' => 'admin@example.com',
'approved_at' => time(),
] );
다음 단계에서는:
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 단계에 도달하면 기존 신호를 확인하고 발견된 경우 즉시 계속 진행합니다.
따라서 순서는 중요하지 않습니다:
// 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)입니다:
$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, ...
사용 사례: 사람의 승인
사람 검토자가 콘텐츠 공개 전에 승인해야 하는 승인 워크플로를 구성합니다:
$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 메시지를 보냅니다. 이 링크는 컨트롤러를 호출하고, 컨트롤러는 신호를 보냅니다:
// In your approval controller
Queuety::approve_workflow(
$workflow_id,
[
'reviewer' => $current_user->user_email,
'reviewed_at' => time(),
],
'approved'
);
HandleDecisionHandler 단계는 $state['review']['outcome']을 읽고 초안을 게시하거나 보관합니다.
사용 사례: 외부 API 콜백
외부 서비스의 웹훅을 기다립니다:
$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 ] );
외부 서비스가 웹훅 엔드포인트로 요청을 보내면:
// 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'],
] );
한 워크플로의 여러 신호 게이트
워크플로는 여러 지점에서 신호를 기다릴 수 있습니다:
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()를 함께 사용할 수 있습니다.