
# Signali poteka dela

Poteki dela se lahko začasno ustavijo in počakajo na zunanji vnos s pomočjo signalov. Gradniški metodi `wait_for_signal()` in `wait_for_signals()` dodata korake, ki blokirajo potek dela, dokler vaša aplikacija, operater ali drug sistem prek `Queuety::signal()` ne pošlje pričakovanega signala.

Za poteke dela s človeškim posredovanjem Queuety vključuje tudi:

- `wait_for_approval()` za odobritvene podatke z imenskim prostorom
- `wait_for_input()` za strukturiran človeški vnos
- `wait_for_decision()` za izrecne izide odobritve/zavrnitve

Za obsežnejši primer od začetka do konca, ki združuje izvajanja agentov s človeškimi potrditvami, glejte [Orkestracija agentov](https://queuety.inline0.dev/sl/docs/workflows/agent-orchestration).

## Metoda `->wait_for_signal()`

V potek dela dodajte korak čakanja na signal:

```php
use Queuety\Queuety;

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

Ko potek dela doseže korak `wait_for_signal`, se njegovo stanje nastavi na `waiting_for_signal` in se začasno ustavi. Med čakanjem se opravila ne izvajajo in viri se ne porabljajo.

Privzeto se podatkovni niz enega signala združi v stanje poteka dela na najvišji ravni. Če želite podatkovni niz umestiti pod imenski prostor, podajte `result_key`:

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

Ko prispe signal, `$state['approval']` vsebuje polje podatkovnih nizov signalov.

## Čakanje na več signalov

Uporabite `wait_for_signals()`, kadar naj se potek dela odblokira ob enem od več možnih signalov ali kadar mora pred nadaljevanjem zbrati vse zahtevane signale.

```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` čaka, dokler niso zabeleženi vsi nastavljeni signali.
- `WaitMode::Any` nadaljuje takoj, ko prispe prvi nastavljeni signal.

Z `result_key` čakanje na več signalov shrani asociativno polje, indeksirano z imenom signala:

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

Brez `result_key` se ustrezni podatkovni nizi signalov združijo v stanje na najvišji ravni v vrstnem redu, v katerem so bili koraki nastavljeni.

## Ujemanje in povezovanje

Čakanja na signal lahko prezrejo nepovezane dogodke z ujemanjem vsebine podatkovnega niza ali povezovanjem prek skupnega ključa.

```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 tem primeru se čakanje nadaljuje le, ko:

- je ime signala `review_ready`
- podatkovni niz vsebuje `source: editorial`
- se vrednost `task_id` v podatkovnem nizu ujema z `$state['task_id']`

To je uporabno, kadar več zunanjih sistemov ali agentov objavlja dogodke z istimi imeni, vendar le en podatkovni niz pripada trenutnemu izvajanju poteka dela.

## Pomožne funkcije za človeško odobritev in vnos

`wait_for_approval()` in `wait_for_input()` sta priročna ovoja okoli čakanja na signal. Delujeta kot `wait_for_signal()`, vendar privzeto uporabljata rezultatni ključ z imenskim prostorom.

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

Po signalu `approval` vsebuje `$state['approval']` podatke o odobritvi. Po signalu `input` vsebuje `$state['input']` podatke o vnosu.

Uporabite `wait_for_decision()`, kadar želite, da en korak nadaljuje ob odobritvi ali zavrnitvi:

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

Ko prispe signal, `$state['review']` vsebuje:

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

## Pošiljanje signala

Pošljite signal poteku dela z uporabo `Queuety::signal()`:

```php
Queuety::signal( $workflow_id, 'approved' );
```

Ko se signal ujema s tistim, na katerega čaka potek dela, se ta nadaljuje in preide na naslednji korak.

Za poteke dela s človeškim posredovanjem fasada ponuja tudi semantične pomožne funkcije:

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

### Podatki signala

Signalu lahko posredujete polje s podatki. Ti se združijo s stanjem poteka dela in so na voljo naslednjim korakom:

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

V naslednjem koraku:

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

Ključi, ki se začnejo z `_` (podčrtajem), so rezervirani za interno uporabo in se ne združijo s stanjem.

## Že obstoječi signali

Signale je mogoče poslati, še preden potek dela doseže korak čakanja. Ko prispe signal, se zabeleži v tabeli `queuety_signals`, ne glede na trenutno stanje poteka dela. Ko ta pozneje doseže korak `wait_for_signal`, preveri, ali obstajajo že poslani signali, in takoj nadaljuje, če jih najde.

To pomeni, da vrstni red ni pomemben:

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

## Stanje poteka dela med čakanjem na signal

Med čakanjem na signal je stanje poteka dela `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, ...
```

## Primer uporabe: človeška odobritev

Sestavite potek dela za odobritev, v katerem mora človeški pregledovalec odobriti vsebino, preden postane javno dostopna:

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

`NotifyReviewerHandler` pošlje e-pošto ali sporočilo Slack s povezavama za odobritev ali zavrnitev. Povezavi pokličeta vaš krmilnik, ki pošlje signal:

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

Korak `HandleDecisionHandler` prebere `$state['review']['outcome']` ter osnutek objavi ali arhivira.

## Primer uporabe: povratni klic zunanjega API

Počakajte na webhook zunanje storitve:

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

Ko zunanja storitev pošlje zahtevo na vašo končno točko webhooka:

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

## Več vrat za signale v enem poteku dela

Potek dela lahko čaka na signale na več mestih:

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

Vsak korak čakanja na signal je neodvisen. V istem poteku dela lahko kombinirate `wait_for_signal()`, `wait_for_signals()`, `wait_for_approval()`, `wait_for_input()` in `wait_for_decision()`.
