
# 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](https://queuety.inline0.dev/nl/docs/workflows/agent-orchestration).

## 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.
