
# 工作流取消

可以通过编程方式或经由 CLI 取消工作流。工作流取消后，Queuety 会将其状态设为 `cancelled`，将所有待处理或正在处理的任务移入封存，并运行可选的清理处理程序。

## 注册清理处理程序

在工作流构建器上使用 `on_cancel()` 注册一个在工作流取消时运行的类。处理程序类必须实现 `handle( array $state ): void` 方法。

```php
use Queuety\Queuety;

Queuety::workflow( 'import_data' )
    ->then( FetchDataHandler::class )
    ->then( TransformDataHandler::class )
    ->then( LoadDataHandler::class )
    ->on_cancel( ImportCleanupHandler::class )
    ->dispatch( [ 'source_url' => $url ] );
```

清理处理程序会接收公开的工作流状态（所有名称不以 `_` 开头的键）：

```php
class ImportCleanupHandler {

    public function handle( array $state ): void {
        // Clean up any partial imports.
        if ( isset( $state['temp_table'] ) ) {
            global $wpdb;
            $wpdb->query( "DROP TABLE IF EXISTS {$state['temp_table']}" );
        }

        // Notify the user.
        wp_mail(
            $state['notify_email'] ?? '',
            'Import cancelled',
            'Your data import was cancelled.'
        );
    }
}
```

## 通过 PHP 取消

使用工作流 ID 调用 `Queuety::cancel_workflow()`：

```php
Queuety::cancel_workflow( $workflow_id );
```

此方法会：

1. 锁定工作流行
2. 将工作流状态设为 `cancelled`
3. 将该工作流的所有待处理和正在处理的任务移入封存，并附上消息 "Workflow cancelled"
4. 记录 `workflow_cancelled` 事件并提交
5. 按相反顺序运行已完成步骤的补偿处理程序（如果通过 `compensate_with()` 注册过）
6. 运行 `on_cancel` 清理处理程序（如果已注册）

如果工作流已处于 `completed` 或 `cancelled` 状态，就会抛出 `RuntimeException`。

## 通过 CLI 取消

```bash
wp queuety workflow cancel <id>
```

| 选项 | 说明 |
|---|---|
| `<id>` | 要取消的工作流 ID（必填） |

```bash
wp queuety workflow cancel 42
```

## 取消时会发生什么

工作流取消时：

- **状态** 更改为 `cancelled`
- **步骤补偿处理程序** 按完成顺序的逆序运行（如果你通过 `compensate_with()` 添加了它们）
- **清理处理程序** 在工作流行标记为 `cancelled` 后运行
- 该工作流的 **待处理任务** 会立即移入封存
- 该工作流的 **正在处理的任务** 会移入封存；正在执行的处理程序完成后，其输出会被丢弃，工作流也不会继续推进
- 先前步骤的 **已完成任务** 保持原样
- 记录包含事件 `workflow_cancelled` 的 **日志条目**

## 已取消与失败

| 方面 | 失败 | 已取消 |
|---|---|---|
| 触发条件 | 某个步骤耗尽重试次数 | 显式调用 `cancel_workflow()` |
| 状态 | `failed` | `cancelled` |
| 能否重试 | 可以，通过 `retry_workflow()` 重试，除非工作流已经完成补偿 | 不可以 |
| 清理处理程序 | 不会调用 | 如果通过 `on_cancel()` 注册，则会调用 |
| 日志事件 | `workflow_failed` | `workflow_cancelled` |

## 示例：取消前进行确认

一种常见模式是在取消前检查工作流状态：

```php
$status = Queuety::workflow_status( $workflow_id );

if ( null === $status ) {
    throw new \RuntimeException( 'Workflow not found.' );
}

if ( 'running' === $status->status->value || 'paused' === $status->status->value ) {
    Queuety::cancel_workflow( $workflow_id );
}
```
