工作流信号
工作流可以使用信号暂停并等待外部输入。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 回调
等待外部服务发送 Webhook:
$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 ] );
外部服务向你的 Webhook 端点发送请求时:
// 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()。