
# 批处理

批次让你能够分派一组任务，并将它们作为一个整体进行跟踪。你可以注册回调，以便在批次完成、任一任务失败或批次无论结果如何结束时触发。Queuety 会自动跟踪进度、失败和取消状态。

## 创建批次

使用 `Queuety::create_batch()` 启动 `BatchBuilder`。传入一个包含 `Contracts\Job` 实例的数组，然后调用 `dispatch()` 创建批次记录和所有任务。

```php
use Queuety\Queuety;

$batch = Queuety::create_batch( [
    new ImportUsersJob( $csv_chunk_1 ),
    new ImportUsersJob( $csv_chunk_2 ),
    new ImportUsersJob( $csv_chunk_3 ),
] )
    ->name( 'Import users' )
    ->on_queue( 'imports' )
    ->dispatch();

echo $batch->id; // The batch ID
```

`dispatch()` 会返回包含批次状态的 `Batch` 值对象。

你也可以传入处理程序和载荷数组，而不是任务实例：

```php
$batch = Queuety::create_batch( [
    [ 'handler' => 'import_users', 'payload' => [ 'chunk' => 1 ] ],
    [ 'handler' => 'import_users', 'payload' => [ 'chunk' => 2 ] ],
] )->dispatch();
```

## 回调

注册处理程序类，以便在批次生命周期的不同阶段调用。每个处理程序类都必须有一个 `handle( Batch $batch )` 方法。

```php
$batch = Queuety::create_batch( $jobs )
    ->name( 'Import users' )
    ->then( ImportCompleteHandler::class )
    ->catch( ImportFailedHandler::class )
    ->finally( ImportCleanupHandler::class )
    ->dispatch();
```

| 方法 | 触发时机 |
|---|---|
| `then( $handler_class )` | 所有任务均成功完成（或已设置 `allow_failures()`） |
| `catch( $handler_class )` | 批次中的第一个任务失败时 |
| `finally( $handler_class )` | 批次结束时，无论成功还是失败 |

### 编写回调处理程序

```php
use Queuety\Batch;

class ImportCompleteHandler {
    public function handle( Batch $batch ): void {
        // All users imported. Send a notification.
        error_log( "Batch {$batch->id} completed: {$batch->total_jobs} jobs processed." );
    }
}

class ImportFailedHandler {
    public function handle( Batch $batch ): void {
        error_log( "Batch {$batch->id} had a failure. Failed jobs: {$batch->failed_jobs}" );
    }
}

class ImportCleanupHandler {
    public function handle( Batch $batch ): void {
        // Runs regardless of outcome. Clean up temp files, etc.
    }
}
```

## 允许失败

默认情况下，只有批次中的所有任务均成功时才会触发 `then` 回调。调用 `allow_failures()` 后，即使部分任务失败，也会触发 `then` 回调。

```php
$batch = Queuety::create_batch( $jobs )
    ->then( ReportHandler::class )
    ->allow_failures()
    ->dispatch();
```

## 进度跟踪

`Batch` 值对象提供用于跟踪进度的属性和方法。

```php
$batch = Queuety::find_batch( $batch_id );

$batch->progress();      // 0-100 percentage
$batch->finished();      // true if all jobs are done
$batch->has_failures();  // true if any job failed
$batch->cancelled();     // true if the batch was cancelled

$batch->total_jobs;      // Total number of jobs
$batch->pending_jobs;    // Jobs still pending
$batch->failed_jobs;     // Number of failed jobs
$batch->failed_job_ids;  // Array of failed job IDs
```

| 方法/属性 | 类型 | 说明 |
|---|---|---|
| `progress()` | `int` | 完成百分比，范围为 0 到 100 |
| `finished()` | `bool` | 批次是否已结束（所有任务均已完成或失败） |
| `has_failures()` | `bool` | 批次中是否有任务失败 |
| `cancelled()` | `bool` | 批次是否已取消 |
| `$total_jobs` | `int` | 批次中的任务总数 |
| `$pending_jobs` | `int` | 仍在等待处理的任务数 |
| `$failed_jobs` | `int` | 失败的任务数 |
| `$failed_job_ids` | `array` | 失败任务的 ID |
| `$name` | `string\|null` | 可选的批次名称 |
| `$created_at` | `DateTimeImmutable` | 批次的创建时间 |
| `$finished_at` | `DateTimeImmutable\|null` | 批次的结束时间 |
| `$cancelled_at` | `DateTimeImmutable\|null` | 批次的取消时间 |

## 取消

通过 `BatchManager` 取消批次。取消批次会设置 `cancelled_at` 和 `finished_at`，并触发 `finally` 回调。

```php
Queuety::batch_manager()->cancel( $batch_id );
```

取消操作会将批次标记为已取消并结束，同时触发其 `finally` 回调。取消批次不会影响其中的任务：已取消批次中等待处理的任务仍会运行，但其结果不会再更新批次计数器，也不会触发 `then`、`catch` 或 `finally`。如果任务不应运行，请自行删除。

## 查找批次

按 ID 查找批次：

```php
$batch = Queuety::find_batch( $batch_id );

if ( null === $batch ) {
    // Batch not found.
}
```

## 清理旧批次

删除已结束且超过指定天数的批次：

```php
$deleted = Queuety::batch_manager()->prune( 30 ); // Delete batches finished > 30 days ago
```

只有带有非空 `finished_at` 时间戳的批次才会被删除。活动中的批次永远不会被清理。
