> ## 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 트레이트

> Laravel의 tap() 헬퍼와 Illuminate\Support\Traits\Tappable 트레이트의 사용법과 구현 패턴을 해설합니다.

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

// 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 트레이트란

`Illuminate\Support\Traits\Tappable`을 `use`하면, 클래스에 `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다운 확장 가능한 빌더를 만들 수 있습니다.

<Steps>
  <Step title="유창한 클래스를 만들기">
    ```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 트레이트](/ko/advanced/macroable)
* [Conditionable 트레이트](/ko/advanced/conditionable)
* [VOICEVOX for Laravel](/ko/packages/laravel-voicevox) — `TalkAudioQuery`나 `SongAudioQuery`를 `tap()`으로 조정한 뒤 `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 트레이트" icon="puzzle-piece" href="/ko/advanced/macroable">
    기존 클래스에 고유 메서드를 추가하는 설계를 배웁니다.
  </Card>

  <Card title="Conditionable 트레이트" icon="git-branch" href="/ko/advanced/conditionable">
    `when()` / `unless()`를 통한 조건 분기 체인을 배웁니다.
  </Card>
</Columns>


## Related topics

- [InteractsWithData 트레이트](/ko/advanced/interacts-with-data.md)
- [Dumpable 트레이트](/ko/advanced/dumpable.md)
- [네이티브 모드: 토크 (텍스트 음성 합성) - VOICEVOX for Laravel](/ko/packages/laravel-voicevox/native-talk.md)
- [클라이언트 모드: 토크 (텍스트 음성 합성) - VOICEVOX for Laravel](/ko/packages/laravel-voicevox/client-talk.md)
- [클라이언트 모드: 송 (노래 음성 합성) - VOICEVOX for Laravel](/ko/packages/laravel-voicevox/client-song.md)
