> ## 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() helper 與 Tappable trait

> 說明 Laravel 的 tap() helper 與 Illuminate\Support\Traits\Tappable trait 的用法及實作模式。

## 什麼是 tap()

`tap()` 是「使用值同時將該值原封不動回傳」的 helper。用於希望插入副作用時。

`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()` 會忽略 callback 的回傳值。回傳值恆為原始值。
</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 都會回傳
```

若省略 callback，會回傳 `HigherOrderTapProxy`，因此可直接串接方法呼叫。

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

// 回傳的不是 update() 的回傳值，而是原始的 User 實例
```

## 常見用途

### 插入除錯輸出

```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="建立 Fluent 類別">
    ```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-TW/advanced/macroable)
* [Conditionable trait](/zh-TW/advanced/conditionable)
* [VOICEVOX for Laravel](/zh-TW/packages/laravel-voicevox) — 以 `tap()` 調整 `TalkAudioQuery` 或 `SongAudioQuery` 後再送入 `generate()` 的實務範例

## Laravel Core 內的使用範例

Laravel 本體中，`tap()` / `Tappable` 也實際被使用。

* `Illuminate\Routing\Router` 併用 `Macroable` 與 `Tappable`
* `Router::respondWithRoute()` 中使用不帶 callback 的 `tap($route)->bind(...)`，於 `bind()` 執行後仍回傳原始的 `$route`
* `Router::prepareResponse()` 於 Response 轉換後以 `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()` 單獨看是小巧的 helper，但結合 `Macroable`、`Conditionable` 後，較容易寫出 Laravel 中常見的易讀方法鏈。
</Info>

## 下一步

<Columns cols={2}>
  <Card title="Macroable trait" icon="puzzle-piece" href="/zh-TW/advanced/macroable">
    學習為既有類別加入自訂方法的設計。
  </Card>

  <Card title="Conditionable trait" icon="git-branch" href="/zh-TW/advanced/conditionable">
    學習以 `when()` / `unless()` 的條件分支鏈。
  </Card>
</Columns>


## Related topics

- [InteractsWithData trait](/zh-TW/advanced/interacts-with-data.md)
- [Native 模式:Talk(文字語音合成)- VOICEVOX for Laravel](/zh-TW/packages/laravel-voicevox/native-talk.md)
- [Client 模式:Talk(文字語音合成)- VOICEVOX for Laravel](/zh-TW/packages/laravel-voicevox/client-talk.md)
- [Client 模式:Song(歌聲語音合成)- VOICEVOX for Laravel](/zh-TW/packages/laravel-voicevox/client-song.md)
- [VOICEVOX for Laravel](/zh-TW/packages/laravel-voicevox/index.md)
