Ga naar de inhoud

Workflowsignalen

Workflows kunnen pauzeren en wachten op externe invoer via signalen. De buildermethoden wait_for_signal() en wait_for_signals() voegen stappen toe die de workflow blokkeren totdat uw applicatie, een operator of een ander systeem het verwachte signaal via Queuety::signal() verstuurt.

Voor processen met een mens in de lus biedt Queuety ook:

  • wait_for_approval() voor een goedkeuringspayload met naamruimte
  • wait_for_input() voor gestructureerde menselijke invoer
  • wait_for_decision() voor expliciete goedkeurings- of afwijzingsresultaten

Zie voor een uitgebreider end-to-endvoorbeeld waarin agentuitvoeringen worden gecombineerd met menselijke controlepunten Agentorkestratie.

De methode ->wait_for_signal()

Voeg een stap toe die op een signaal wacht aan een workflow:

PHP
use Queuety\Queuety;

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

Wanneer de workflow de stap wait_for_signal bereikt, verandert de status in waiting_for_signal en wordt de workflow gepauzeerd. Tijdens het wachten worden geen taken uitgevoerd en geen resources verbruikt.

Standaard wordt één signaalpayload samengevoegd met de workflowstatus op het hoogste niveau. Als u de payload liever een naamruimte geeft, geeft u een result_key mee:

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

Na ontvangst van het signaal bevat $state['approval'] de array met signaalpayloads.

Op meerdere signalen wachten

Gebruik wait_for_signals() wanneer de workflow moet worden hervat door een van meerdere mogelijke signalen, of wanneer alle vereiste signalen moeten zijn verzameld voordat de workflow doorgaat.

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 wacht totdat elk ingesteld signaal is geregistreerd.
  • WaitMode::Any wordt hervat zodra het eerste ingestelde signaal binnenkomt.

Met een result_key slaat wachten op meerdere signalen een associatieve array op, gesorteerd op signaalnaam:

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

Zonder een result_key worden de payloads van overeenkomende signalen samengevoegd met de status op het hoogste niveau, in de volgorde waarin de stap is geconfigureerd.

Overeenkomsten en correlatie

Signaalwachten kunnen niet-gerelateerde gebeurtenissen negeren door de payloadinhoud te vergelijken of te correleren op basis van een gedeelde sleutel.

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' ] );

In dat voorbeeld wordt het wachten alleen hervat wanneer:

  • de signaalnaam review_ready is
  • de payload source: editorial bevat
  • de payloadwaarde task_id overeenkomt met $state['task_id']

Dit is handig wanneer meerdere externe systemen of agents dezelfde gebeurtenisnamen publiceren, maar slechts één payload bij de huidige workflowuitvoering hoort.

Hulpmethoden voor menselijke goedkeuring en invoer

wait_for_approval() en wait_for_input() zijn handige wrappers rond signaalwachten. Ze werken als wait_for_signal(), maar gebruiken standaard een resultaatssleutel met naamruimte.

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

Na het signaal approval bevat $state['approval'] de goedkeuringspayload. Na het signaal input bevat $state['input'] de invoerpayload.

Gebruik wait_for_decision() wanneer u wilt dat één stap wordt hervat na goedkeuring of afwijzing:

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

Na ontvangst van het signaal bevat $state['review'] het volgende:

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

Een signaal verzenden

Verzend een signaal naar een workflow met Queuety::signal():

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

Wanneer het signaal overeenkomt met het signaal waarop de workflow wacht, wordt de workflow hervat en gaat deze verder met de volgende stap.

Voor processen met een mens in de lus biedt de facade ook semantische hulpmethoden:

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' ] );

Signaalgegevens

U kunt een gegevensarray met het signaal meesturen. De gegevens worden samengevoegd met de workflowstatus, zodat ze beschikbaar zijn voor volgende stappen:

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

In de volgende stap:

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 [];
    }
}

Sleutels die beginnen met _ (underscore) zijn gereserveerd voor intern gebruik en worden niet samengevoegd met de status.

Reeds bestaande signalen

Signalen kunnen worden verzonden voordat de workflow de wachtstap bereikt. Wanneer een signaal binnenkomt, wordt het vastgelegd in de tabel queuety_signals, ongeacht de huidige status van de workflow. Wanneer de workflow later de stap wait_for_signal bereikt, controleert deze op eerder ontvangen signalen en gaat onmiddellijk verder als er een is gevonden.

Dit betekent dat de volgorde niet uitmaakt:

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 tijdens het wachten op een signaal

In afwachting van een signaal is de workflowstatus 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, ...

Gebruikssituatie: menselijke goedkeuring

Bouw een goedkeuringsworkflow waarin een menselijke beoordelaar content moet goedkeuren voordat deze live gaat:

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' ] );

De NotifyReviewerHandler stuurt een e-mail of Slack-bericht met links om goed te keuren of af te wijzen. Die links roepen je controller aan, die het signaal verstuurt:

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

De stap HandleDecisionHandler leest $state['review']['outcome'] en publiceert het concept of archiveert het.

Gebruikssituatie: externe API-callback

Wacht op een webhook van een externe service:

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 ] );

Wanneer de externe service een verzoek naar je webhook-endpoint stuurt:

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'],
] );

Meerdere signaalpoorten in één workflow

Een workflow kan op meerdere punten op signalen wachten:

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 ] );

Elke stap die op een signaal wacht, is onafhankelijk. Je kunt wait_for_signal(), wait_for_signals(), wait_for_approval(), wait_for_input() en wait_for_decision() combineren in dezelfde workflow.