Přejít k obsahu

Signály workflow

Workflow mohou pomocí signálů pozastavit běh a čekat na externí vstup. Metody nástroje pro sestavení wait_for_signal() a wait_for_signals() vloží kroky, které blokují workflow, dokud vaše aplikace, operátor nebo jiný systém neodešle očekávaný signál prostřednictvím Queuety::signal().

Pro postupy s lidskou kontrolou zahrnuje Queuety také:

  • wait_for_approval() pro schvalovací data s jmenným prostorem
  • wait_for_input() pro strukturované lidské zadání
  • wait_for_decision() pro výslovné výsledky schválení nebo zamítnutí

Rozsáhlejší příklad od začátku do konce, který kombinuje běhy agentů s kontrolními body pro člověka, najdete v části Orchestrace agentů.

Metoda ->wait_for_signal()

Přidejte do workflow krok čekající na signál:

PHP
use Queuety\Queuety;

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

Když workflow dosáhne kroku wait_for_signal, změní stav na waiting_for_signal a pozastaví se. Během čekání neběží žádné úlohy a nespotřebovávají se žádné prostředky.

Ve výchozím nastavení se data jednoho signálu sloučí do stavu workflow nejvyšší úrovně. Chcete-li data místo toho umístit do jmenného prostoru, předejte result_key:

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

Po doručení signálu obsahuje $state['approval'] pole dat signálu.

Čekání na více signálů

Použijte wait_for_signals(), když se má workflow odblokovat při přijetí některého z několika možných signálů nebo když musí před pokračováním shromáždit všechny požadované signály.

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 čeká, dokud nejsou zaznamenány všechny nastavené signály.
  • WaitMode::Any pokračuje, jakmile dorazí první nastavený signál.

Při použití result_key ukládá čekání na více signálů asociativní pole s klíči podle názvů signálů:

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

Bez result_key se data odpovídajících signálů sloučí do stavu nejvyšší úrovně v pořadí, v jakém byl krok nastaven.

Párování a korelace

Čekání na signál může ignorovat nesouvisející události porovnáním obsahu dat nebo korelací podle sdíleného klíče.

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

V tomto příkladu se čekání ukončí pouze tehdy, když:

  • název signálu je review_ready
  • data obsahují source: editorial
  • hodnota task_id v datech odpovídá $state['task_id']

To se hodí, když několik externích systémů nebo agentů publikuje názvy stejných událostí, ale pouze jedna datová část patří k aktuálnímu běhu workflow.

Pomocné funkce pro schválení a zadávání vstupů

wait_for_approval() a wait_for_input() jsou pohodlné obaly čekání na signály. Chovají se jako wait_for_signal(), ale jako výchozí používají klíč výsledku s jmenným prostorem.

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

Po signálu approval obsahuje $state['approval'] data schválení. Po signálu input obsahuje $state['input'] data vstupu.

Použijte wait_for_decision(), když chcete, aby se jeden krok obnovil po schválení i zamítnutí:

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

Po přijetí signálu obsahuje $state['review']:

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

Odesílání signálu

Signál workflow odešlete pomocí Queuety::signal():

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

Když signál odpovídá tomu, na který workflow čeká, workflow se obnoví a přejde k dalšímu kroku.

Pro procesy s lidským zásahem nabízí fasáda také sémantické pomocné funkce:

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

Data signálu

Se signálem můžete předat datové pole. Data se sloučí se stavem workflow a budou dostupná v následujících krocích:

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

V dalším kroku:

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

Klíče začínající na _ (podtržítko) jsou vyhrazeny pro interní použití a do stavu se neslučují.

Signály odeslané předem

Signály lze odeslat ještě předtím, než workflow dosáhne kroku čekání. Po přijetí signálu se zaznamená do tabulky queuety_signals bez ohledu na aktuální stav workflow. Když workflow později dosáhne kroku wait_for_signal, zkontroluje, zda již nebyly odeslány nějaké signály, a pokud ano, ihned pokračuje.

Na pořadí tedy nezáleží:

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.

Stav workflow při čekání na signál

Při čekání na signál má workflow stav 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, ...

Příklad použití: schválení člověkem

Vytvořte proces schvalování, ve kterém musí kontrolující osoba schválit obsah před jeho zveřejněním:

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

Krok NotifyReviewerHandler odešle e-mail nebo zprávu Slack s odkazy pro schválení nebo zamítnutí. Tyto odkazy zavolají váš řadič, který odešle signál:

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

Krok HandleDecisionHandler přečte $state['review']['outcome'] a návrh buď zveřejní, nebo archivuje.

Příklad použití: externí zpětné volání API

Počkejte na webhook z externí služby:

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

Když externí služba odešle požadavek na váš koncový bod webhooku:

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

Více bran signálů v jednom workflow

Workflow může čekat na signály na několika místech:

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

Každý krok čekající na signál je nezávislý. V jednom workflow můžete kombinovat wait_for_signal(), wait_for_signals(), wait_for_approval(), wait_for_input() a wait_for_decision().