> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# tap() 辅助函数与 Tappable trait

> 讲解 Laravel 的 tap() 辅助函数与 Illuminate\Support\Traits\Tappable trait 的用法与实现模式。

## 什么是 tap()

`tap()` 是「使用值的同时把这个值原样返回」的辅助函数。适合插入副作用的场景。

`Illuminate\Support\helpers.php` 的实现很简单。

```php theme={null}
function tap($value, $callback = null)
{
    if (is_null($callback)) {
        return new HigherOrderTapProxy($value);
    }

    $callback($value);

    return $value;
}
```

```mermaid theme={null}
flowchart TD
    A["调用 tap(value, callback)"] --> B{"callback 是 null?"}
    B -- 是 --> C["返回 HigherOrderTapProxy"]
    B -- 否 --> D["执行 callback(value)"]
    D --> E["返回原始 value"]
```

<Info>
  `tap()` 会忽略回调的返回值，其返回值始终是原始值。
</Info>

## 基本用法

最基本的用法是接收一个值、处理后再原样返回。

```php theme={null}
use App\Models\User;

$user = tap(User::query()->latest()->firstOrFail(), function (User $model) {
    $model->update(['last_seen_at' => now()]);
});

// 无论 update() 返回什么，最终返回的都是 $user
```

省略回调时会返回 `HigherOrderTapProxy`，可以直接链式调用方法。

```php theme={null}
$updatedUser = tap($user)->update([
    'name' => $name,
    'email' => $email,
]);

// 返回原始 User 实例，而非 update() 的返回值
```

## 典型使用场景

### 插入调试输出

```php theme={null}
$result = tap($query->get(), function ($users) {
    logger()->debug('Fetched users', ['count' => $users->count()]);
});
```

### 在链式调用中插入日志或事件

```php theme={null}
$order = tap(Order::create($payload), function (Order $order) {
    event(new OrderCreated($order));
    logger()->info('Order created', ['id' => $order->id]);
});
```

### 不改变返回值只做副作用

```php theme={null}
$response = tap($service->handle($request), function ($response) {
    \Illuminate\Support\Facades\Cache::increment('service_handle_success_total');
});
```

<Tip>
  想加工返回值时使用 `with()` 或普通的变量赋值。将 `tap()` 限定于副作用会更易读。
</Tip>

## 什么是 Tappable trait

在类中 `use` `Illuminate\Support\Traits\Tappable` 可以添加 `tap()` 方法。

```php theme={null}
trait Tappable
{
    public function tap($callback = null)
    {
        return tap($this, $callback);
    }
}
```

也就是提供了实例方法版的 `tap()`。

```php theme={null}
use Illuminate\Support\Traits\Tappable;

class ReportBuilder
{
    use Tappable;
}

$builder = new ReportBuilder();

$builder = $builder->tap(function (ReportBuilder $instance) {
    logger()->debug('builder initialized');
});
```

## 在包开发中的应用

`Tappable` 在 Fluent API 中间需要插入副作用时非常方便。结合 `Macroable` 与 `Conditionable`，可以构建颇有 Laravel 味道的可扩展 Builder。

<Steps>
  <Step title="创建流畅 API 的类">
    ```php theme={null}
    use Illuminate\Support\Traits\Conditionable;
    use Illuminate\Support\Traits\Macroable;
    use Illuminate\Support\Traits\Tappable;

    class QueryPresetBuilder
    {
        use Macroable;
        use Conditionable;
        use Tappable;

        protected array $filters = [];

        public function where(string $key, mixed $value): static
        {
            $this->filters[$key] = $value;

            return $this;
        }

        public function toArray(): array
        {
            return $this->filters;
        }
    }
    ```
  </Step>

  <Step title="组合使用 Macroable、Conditionable、Tappable">
    ```php theme={null}
    QueryPresetBuilder::macro('forActiveUsers', function () {
        /** @var QueryPresetBuilder $this */
        return $this->where('active', true);
    });

    $filters = (new QueryPresetBuilder)
        ->forActiveUsers()
        ->when($request->filled('role'), fn ($builder) => $builder->where('role', $request->role))
        ->tap(fn ($builder) => logger()->debug('current filters', $builder->toArray()))
        ->toArray();
    ```
  </Step>
</Steps>

相关页面：

* [Macroable trait](/zh/advanced/macroable)
* [Conditionable trait](/zh/advanced/conditionable)
* [VOICEVOX for Laravel](/zh/packages/laravel-voicevox) — 通过 `tap()` 调整 `TalkAudioQuery` 或 `SongAudioQuery` 后送入 `generate()` 的实用示例

## Laravel 核心中的使用示例

Laravel 本体中也在真实地使用 `tap()` / `Tappable`。

* `Illuminate\Routing\Router` 同时使用了 `Macroable` 与 `Tappable`
* `Router::respondWithRoute()` 使用无回调的 `tap($route)->bind(...)`，`bind()` 执行后仍返回原始的 `$route`
* `Router::prepareResponse()` 在响应转换后使用 `tap(..., fn (...) => event(...))` 触发事件
* `Illuminate\Testing\Fluent\Concerns\Has` 中用 `->tap(...)->first(...)->etc()` 这样的链式实现断言辅助

```php theme={null}
// 摘自 Illuminate\Routing\Router
$route = tap($this->routes->getByName($name))->bind($this->currentRequest);

return tap(static::toResponse($request, $response), function ($response) use ($request) {
    $this->events->dispatch(new ResponsePrepared($request, $response));
});
```

<Info>
  `tap()` 单独看是一个小巧辅助函数，与 `Macroable`、`Conditionable` 结合，能非常便利地写出 Laravel 常见的易读方法链。
</Info>

## 下一步

<Columns cols={2}>
  <Card title="Macroable trait" icon="puzzle-piece" href="/zh/advanced/macroable">
    学习向既有类添加自定义方法的设计。
  </Card>

  <Card title="Conditionable trait" icon="git-branch" href="/zh/advanced/conditionable">
    学习基于 `when()` / `unless()` 的条件分支链。
  </Card>
</Columns>


## Related topics

- [InteractsWithData trait](/zh/advanced/interacts-with-data.md)
- [辅助函数](/zh/helpers.md)
- [Dumpable trait](/zh/advanced/dumpable.md)
- [Support Contracts（Arrayable / Jsonable / Htmlable / Responsable）](/zh/advanced/support-contracts.md)
- [集合](/zh/collections.md)
