
# Sinyal Alur Kerja

Alur kerja dapat dijeda dan menunggu input eksternal menggunakan sinyal. Metode builder `wait_for_signal()` dan `wait_for_signals()` menyisipkan langkah yang memblokir alur kerja hingga aplikasi Anda, operator, atau sistem lain mengirim sinyal yang diharapkan melalui `Queuety::signal()`.

Untuk alur kerja yang melibatkan manusia, Queuety juga mencakup:

- `wait_for_approval()` untuk payload persetujuan dengan namespace
- `wait_for_input()` untuk input manusia yang terstruktur
- `wait_for_decision()` untuk hasil setuju/tolak yang eksplisit

Untuk contoh menyeluruh yang lebih besar, yang menggabungkan eksekusi agen dengan gerbang tinjauan manusia, lihat [Orkestrasi Agen](https://queuety.inline0.dev/id/docs/workflows/agent-orchestration).

## Metode `->wait_for_signal()`

Tambahkan langkah penantian sinyal ke alur kerja:

```php
use Queuety\Queuety;

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

Saat alur kerja mencapai langkah `wait_for_signal`, statusnya diatur menjadi `waiting_for_signal` dan alur kerja dijeda. Tidak ada tugas yang berjalan dan tidak ada sumber daya yang digunakan selama menunggu.

Secara default, satu payload sinyal digabungkan ke status alur kerja tingkat teratas. Jika Anda ingin payload diberi namespace, berikan `result_key`:

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

Setelah sinyal tiba, `$state['approval']` berisi array payload sinyal.

## Menunggu beberapa sinyal

Gunakan `wait_for_signals()` saat alur kerja harus dilanjutkan setelah salah satu dari beberapa sinyal yang mungkin diterima, atau saat semua sinyal yang diperlukan harus dikumpulkan sebelum melanjutkan.

```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` menunggu hingga setiap sinyal yang dikonfigurasi tercatat.
- `WaitMode::Any` dilanjutkan segera setelah sinyal pertama yang dikonfigurasi tiba.

Dengan `result_key`, penantian multi-sinyal menyimpan array asosiatif dengan nama sinyal sebagai kuncinya:

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

Tanpa `result_key`, payload sinyal yang cocok digabungkan ke status tingkat teratas sesuai urutan konfigurasi langkah.

## Pencocokan dan korelasi

Penantian sinyal dapat mengabaikan peristiwa yang tidak terkait dengan mencocokkan konten payload atau mengorelasikannya berdasarkan kunci bersama.

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

Dalam contoh tersebut, penantian hanya dilanjutkan jika:

- nama sinyalnya adalah `review_ready`
- payload berisi `source: editorial`
- payload `task_id` cocok dengan `$state['task_id']`

Ini berguna ketika beberapa sistem eksternal atau agen menerbitkan nama peristiwa yang sama, tetapi hanya satu payload yang terkait dengan eksekusi alur kerja saat ini.

## Helper persetujuan dan input manusia

`wait_for_approval()` dan `wait_for_input()` adalah pembungkus praktis untuk penantian sinyal. Keduanya berperilaku seperti `wait_for_signal()`, tetapi secara default menggunakan kunci hasil dengan namespace.

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

Setelah sinyal `approval`, `$state['approval']` berisi payload persetujuan. Setelah sinyal `input`, `$state['input']` berisi payload input.

Gunakan `wait_for_decision()` jika Anda ingin satu langkah dilanjutkan saat menerima persetujuan atau penolakan:

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

Setelah sinyal tiba, `$state['review']` berisi:

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

## Mengirim sinyal

Kirim sinyal ke alur kerja menggunakan `Queuety::signal()`:

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

Saat sinyal cocok dengan sinyal yang sedang ditunggu alur kerja, alur kerja dilanjutkan dan berpindah ke langkah berikutnya.

Untuk alur kerja yang melibatkan manusia, facade juga menyediakan helper semantik:

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

Anda dapat mengirim array data bersama sinyal. Data tersebut digabungkan ke status alur kerja sehingga tersedia untuk langkah-langkah berikutnya:

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

Pada langkah berikutnya:

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

Kunci yang diawali dengan `_` (garis bawah) dicadangkan untuk penggunaan internal dan tidak digabungkan ke dalam status.

## Sinyal yang sudah ada

Sinyal dapat dikirim sebelum alur kerja mencapai langkah penantian. Saat sinyal tiba, sinyal dicatat di tabel `queuety_signals` tanpa memedulikan status alur kerja saat itu. Ketika alur kerja kemudian mencapai langkah `wait_for_signal`, alur kerja memeriksa sinyal yang sudah ada dan langsung melanjutkan jika menemukannya.

Artinya, urutannya tidak menjadi masalah:

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

## Status alur kerja selama menunggu sinyal

Saat menunggu sinyal, status alur kerja adalah `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, ...
```

## Kasus penggunaan: persetujuan manusia

Buat alur persetujuan ketika peninjau manusia harus menyetujui konten sebelum ditayangkan:

```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` mengirim email atau pesan Slack dengan tautan setujui/tolak. Tautan tersebut memanggil controller Anda, yang mengirim sinyal:

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

Langkah `HandleDecisionHandler` membaca `$state['review']['outcome']` lalu menerbitkan atau mengarsipkan draf.

## Kasus penggunaan: callback API eksternal

Tunggu webhook dari layanan eksternal:

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

Saat layanan eksternal mengirim permintaan ke endpoint webhook Anda:

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

## Beberapa gerbang sinyal dalam satu alur kerja

Alur kerja dapat menunggu sinyal di beberapa titik:

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

Setiap langkah penantian sinyal berdiri sendiri. Anda dapat menggabungkan `wait_for_signal()`, `wait_for_signals()`, `wait_for_approval()`, `wait_for_input()`, dan `wait_for_decision()` dalam alur kerja yang sama.
