Signaux de workflow
Les workflows peuvent se mettre en pause et attendre une entrée externe au moyen de signaux. Les méthodes de génération wait_for_signal() et wait_for_signals() insèrent des étapes qui bloquent le workflow jusqu’à ce que votre application, un opérateur ou un autre système envoie le signal attendu via Queuety::signal().
Pour les workflows avec intervention humaine, Queuety inclut également :
wait_for_approval()pour une charge utile d’approbation avec espace de nomswait_for_input()pour une saisie humaine structuréewait_for_decision()pour des résultats explicites d’approbation ou de rejet
Pour un exemple complet plus détaillé combinant des exécutions d’agents et des validations humaines, consultez Orchestration d’agents.
La méthode ->wait_for_signal()
Ajoutez une étape d’attente de signal à un workflow :
use Queuety\Queuety;
$workflow_id = Queuety::workflow( 'content_approval' )
->then( DraftContentHandler::class )
->wait_for_signal( 'approved' )
->then( PublishContentHandler::class )
->dispatch( [ 'post_id' => 99 ] );
Lorsque le workflow atteint l’étape wait_for_signal, son statut devient waiting_for_signal et il se met en pause. Aucun travail n’est en cours et aucune ressource n’est consommée pendant l’attente.
Par défaut, la charge utile d’un signal unique est fusionnée dans l’état de premier niveau du workflow. Pour placer plutôt la charge utile dans un espace de noms, passez un result_key :
$workflow_id = Queuety::workflow( 'content_approval' )
->then( DraftContentHandler::class )
->wait_for_signal( 'approved', 'approval' )
->then( PublishContentHandler::class )
->dispatch( [ 'post_id' => 99 ] );
Après l’arrivée du signal, $state['approval'] contient le tableau des charges utiles des signaux.
Attendre plusieurs signaux
Utilisez wait_for_signals() lorsque le workflow doit se débloquer à la réception de plusieurs signaux possibles, ou lorsqu’il doit recueillir tous les signaux requis avant de continuer.
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::Allattend que tous les signaux configurés aient été enregistrés.WaitMode::Anyreprend dès l’arrivée du premier signal configuré.
Avec un result_key, une attente de plusieurs signaux stocke un tableau associatif indexé par nom de signal :
[
'editor_approved' => [ 'by' => 'editor@example.com' ],
'legal_approved' => [ 'by' => 'legal@example.com' ],
]
Sans result_key, les charges utiles des signaux correspondants sont fusionnées dans l’état de premier niveau, dans l’ordre de configuration de l’étape.
Correspondance et corrélation
Les attentes de signal peuvent ignorer les événements sans rapport en faisant correspondre le contenu de la charge utile ou en utilisant une clé commune pour la corrélation.
$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' ] );
Dans cet exemple, l’attente ne reprend que lorsque :
- le nom du signal est
review_ready - la charge utile contient
source: editorial - la valeur
task_idde la charge utile correspond à$state['task_id']
C’est utile lorsque plusieurs systèmes externes ou agents publient les mêmes noms d’événements, mais qu’une seule charge utile correspond à l’exécution du workflow en cours.
Aides à l’approbation et à la saisie humaines
wait_for_approval() et wait_for_input() sont des raccourcis autour des attentes de signal. Ils se comportent comme wait_for_signal(), mais utilisent par défaut une clé de résultat avec espace de noms.
$workflow_id = Queuety::workflow( 'blog_review' )
->then( GenerateDraftHandler::class )
->wait_for_approval()
->wait_for_input()
->then( HandleDecisionHandler::class )
->dispatch();
Après le signal approval, $state['approval'] contient la charge utile d’approbation. Après le signal input, $state['input'] contient la charge utile de saisie.
Utilisez wait_for_decision() lorsqu’une seule étape doit reprendre après une approbation ou un rejet :
$workflow_id = Queuety::workflow( 'legal_review' )
->then( DraftContractHandler::class )
->wait_for_decision( result_key: 'review' )
->then( HandleReviewDecisionHandler::class )
->dispatch();
Après l’arrivée du signal, $state['review'] contient :
[
'outcome' => 'approved', // or 'rejected'
'signal' => 'approved', // or 'rejected'
'data' => [ 'reviewer' => 'ops@example.com' ],
]
Envoyer un signal
Envoyez un signal à un workflow à l’aide de Queuety::signal() :
Queuety::signal( $workflow_id, 'approved' );
Lorsque le signal correspond à celui attendu par le workflow, celui-ci reprend et passe à l’étape suivante.
Pour les workflows avec intervention humaine, la façade propose également des aides sémantiques :
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' ] );
Données du signal
Vous pouvez transmettre un tableau de données avec le signal. Les données sont fusionnées dans l’état du workflow et deviennent accessibles aux étapes suivantes :
Queuety::signal( $workflow_id, 'approved', [
'approved_by' => 'admin@example.com',
'approved_at' => time(),
] );
À l’étape suivante :
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 [];
}
}
Les clés commençant par _ (trait de soulignement) sont réservées à un usage interne et ne sont pas fusionnées dans l’état.
Signaux préexistants
Les signaux peuvent être envoyés avant que le workflow n’atteigne l’étape d’attente. À l’arrivée d’un signal, il est enregistré dans la table queuety_signals, quel que soit le statut actuel du workflow. Lorsque celui-ci atteint ensuite l’étape wait_for_signal, il recherche les signaux préexistants et continue immédiatement s’il en trouve un.
L’ordre n’a donc pas d’importance :
// 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.
Statut du workflow pendant l’attente d’un signal
Pendant l’attente d’un signal, le statut du workflow est 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, ...
Cas d’usage : approbation humaine
Créez un workflow d’approbation où une personne chargée de la révision doit approuver le contenu avant sa publication :
$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' ] );
Le NotifyReviewerHandler envoie un e-mail ou un message Slack avec des liens d’approbation ou de rejet. Ces liens appellent votre contrôleur, qui envoie le signal :
// In your approval controller
Queuety::approve_workflow(
$workflow_id,
[
'reviewer' => $current_user->user_email,
'reviewed_at' => time(),
],
'approved'
);
L’étape HandleDecisionHandler lit $state['review']['outcome'] et publie le brouillon ou le met en archive.
Cas d’usage : rappel externe de API
Attendez un webhook d’un service externe :
$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 ] );
Lorsque le service externe envoie une requête à votre point de terminaison 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'],
] );
Plusieurs étapes d’attente de signal dans un workflow
Un workflow peut attendre des signaux à plusieurs étapes :
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 ] );
Chaque étape d’attente de signal est indépendante. Vous pouvez combiner wait_for_signal(), wait_for_signals(), wait_for_approval(), wait_for_input() et wait_for_decision() dans un même workflow.