Workflow-Signale
Workflows können mithilfe von Signalen pausieren und auf externe Eingaben warten. Die Builder-Methoden wait_for_signal() und wait_for_signals() fügen Schritte ein, die den Workflow blockieren, bis deine Anwendung, eine zuständige Person oder ein anderes System das erwartete Signal über Queuety::signal() sendet.
Für Abläufe mit menschlicher Beteiligung enthält Queuety außerdem:
wait_for_approval()für eine Approval-Nutzlast mit Namensraumwait_for_input()für strukturierte menschliche Eingabenwait_for_decision()für ausdrückliche Genehmigungs- oder Ablehnungsergebnisse
Ein ausführlicheres Beispiel von Anfang bis Ende, das Agentenläufe mit menschlichen Freigabeschritten verbindet, findest du unter Agenten-Orchestrierung.
Die Methode ->wait_for_signal()
Füge einem Workflow einen Signalschritt hinzu:
use Queuety\Queuety;
$workflow_id = Queuety::workflow( 'content_approval' )
->then( DraftContentHandler::class )
->wait_for_signal( 'approved' )
->then( PublishContentHandler::class )
->dispatch( [ 'post_id' => 99 ] );
Wenn der Workflow den Schritt wait_for_signal erreicht, erhält er den Status waiting_for_signal und pausiert. Während des Wartens laufen keine Jobs und es werden keine Ressourcen verbraucht.
Standardmäßig wird eine einzelne Signalnutzlast mit dem obersten Workflow-Zustand zusammengeführt. Wenn du die Nutzlast stattdessen in einem eigenen Namensraum ablegen möchtest, übergib ein result_key:
$workflow_id = Queuety::workflow( 'content_approval' )
->then( DraftContentHandler::class )
->wait_for_signal( 'approved', 'approval' )
->then( PublishContentHandler::class )
->dispatch( [ 'post_id' => 99 ] );
Nach Eingang des Signals enthält $state['approval'] das Array der Signalnutzlasten.
Auf mehrere Signale warten
Verwende wait_for_signals(), wenn der Workflow durch eines von mehreren möglichen Signalen fortgesetzt werden soll oder wenn er alle erforderlichen Signale sammeln muss, bevor er weiterläuft.
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::Allwartet, bis jedes konfigurierte Signal registriert wurde.WaitMode::Anysetzt den Workflow fort, sobald das erste konfigurierte Signal eintrifft.
Mit einem result_key speichert ein Multi-Signal-Warteschritt ein assoziatives Array, dessen Schlüssel die Signalnamen sind:
[
'editor_approved' => [ 'by' => 'editor@example.com' ],
'legal_approved' => [ 'by' => 'legal@example.com' ],
]
Ohne ein result_key werden die Nutzlasten der passenden Signale in der Reihenfolge, in der der Schritt konfiguriert wurde, mit dem obersten Zustand zusammengeführt.
Abgleich und Zuordnung
Signal-Warteschritte können irrelevante Ereignisse ignorieren, indem sie Nutzlastinhalte abgleichen oder anhand eines gemeinsamen Schlüssels zuordnen.
$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' ] );
In diesem Beispiel wird das Warten nur dann beendet, wenn:
- der Signalname
review_readylautet - die Nutzlast
source: editorialenthält task_idder Nutzlast mit$state['task_id']übereinstimmt
Das ist nützlich, wenn mehrere externe Systeme oder Agenten Ereignisse mit denselben Namen veröffentlichen, aber nur eine Nutzlast zum aktuellen Workflow-Lauf gehört.
Helfer für menschliche Freigaben und Eingaben
wait_for_approval() und wait_for_input() sind praktische Wrapper für Signal-Warteschritte. Sie verhalten sich wie wait_for_signal(), verwenden aber standardmäßig einen Ergebnisschlüssel mit Namensraum.
$workflow_id = Queuety::workflow( 'blog_review' )
->then( GenerateDraftHandler::class )
->wait_for_approval()
->wait_for_input()
->then( HandleDecisionHandler::class )
->dispatch();
Nach dem Signal approval enthält $state['approval'] die Approval-Nutzlast. Nach dem Signal input enthält $state['input'] die Eingabenutzlast.
Verwende wait_for_decision(), wenn ein einzelner Schritt entweder bei einer Genehmigung oder einer Ablehnung fortgesetzt werden soll:
$workflow_id = Queuety::workflow( 'legal_review' )
->then( DraftContractHandler::class )
->wait_for_decision( result_key: 'review' )
->then( HandleReviewDecisionHandler::class )
->dispatch();
Nach Eingang des Signals enthält $state['review']:
[
'outcome' => 'approved', // or 'rejected'
'signal' => 'approved', // or 'rejected'
'data' => [ 'reviewer' => 'ops@example.com' ],
]
Ein Signal senden
Sende mit Queuety::signal() ein Signal an einen Workflow:
Queuety::signal( $workflow_id, 'approved' );
Wenn das Signal dem entspricht, auf das der Workflow wartet, wird der Workflow fortgesetzt und geht zum nächsten Schritt über.
Für Abläufe mit menschlicher Beteiligung bietet die Fassade außerdem semantische Helfer:
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' ] );
Signaldaten
Du kannst zusammen mit dem Signal ein Daten-Array übergeben. Die Daten werden mit dem Workflow-Zustand zusammengeführt und stehen dadurch den folgenden Schritten zur Verfügung:
Queuety::signal( $workflow_id, 'approved', [
'approved_by' => 'admin@example.com',
'approved_at' => time(),
] );
Im nächsten Schritt:
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 [];
}
}
Schlüssel, die mit _ (Unterstrich) beginnen, sind für die interne Verwendung reserviert und werden nicht mit dem Zustand zusammengeführt.
Bereits vorhandene Signale
Signale können gesendet werden, bevor der Workflow den Warteschritt erreicht. Wenn ein Signal eintrifft, wird es unabhängig vom aktuellen Status des Workflows in der Tabelle queuety_signals gespeichert. Erreicht der Workflow später den Schritt wait_for_signal, sucht er nach bereits vorhandenen Signalen und fährt sofort fort, falls er eines findet.
Das bedeutet, dass die Reihenfolge keine Rolle spielt:
// 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.
Workflow-Status während des Wartens auf ein Signal
Während der Workflow auf ein Signal wartet, lautet sein Status 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, ...
Anwendungsfall: menschliche Freigabe
Erstelle einen Freigabe-Workflow, in dem eine prüfende Person Inhalte genehmigen muss, bevor sie veröffentlicht werden:
$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 sendet eine E-Mail- oder Slack-Nachricht mit Links zum Genehmigen oder Ablehnen. Diese Links rufen deinen Controller auf, der das Signal sendet:
// In your approval controller
Queuety::approve_workflow(
$workflow_id,
[
'reviewer' => $current_user->user_email,
'reviewed_at' => time(),
],
'approved'
);
Der Schritt HandleDecisionHandler liest $state['review']['outcome'] und veröffentlicht oder archiviert den Entwurf.
Anwendungsfall: externer API-Callback
Warte auf einen Webhook von einem externen Dienst:
$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 ] );
Wenn der externe Dienst einen Request an deinen Webhook-Endpunkt sendet:
// 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'],
] );
Mehrere Signal-Sperren in einem Workflow
Ein Workflow kann an mehreren Stellen auf Signale warten:
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 ] );
Jeder Signal-Warteschritt ist unabhängig. Du kannst wait_for_signal(), wait_for_signals(), wait_for_approval(), wait_for_input() und wait_for_decision() im selben Workflow kombinieren.