메인 콘텐츠로 건너뛰기

tap()이란

tap()은 “값을 사용하면서, 그 값을 그대로 반환하는” 헬퍼입니다. 부수 효과를 끼워 넣고 싶을 때 사용합니다. Illuminate\Support\helpers.php의 구현은 단순합니다.
function tap($value, $callback = null)
{
    if (is_null($callback)) {
        return new HigherOrderTapProxy($value);
    }

    $callback($value);

    return $value;
}
tap()은 콜백의 반환값을 무시합니다. 반환값은 항상 원래의 값입니다.

기본적인 사용법

가장 기본적인 사용 방법은 값을 받아 처리한 다음 그대로 반환하는 형태입니다.
use App\Models\User;

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

// update() の戻り値に関係なく $user が返る
콜백을 생략하면 HigherOrderTapProxy가 반환되므로, 메서드 호출을 그대로 이어서 연결할 수 있습니다.
$updatedUser = tap($user)->update([
    'name' => $name,
    'email' => $email,
]);

// update() の戻り値ではなく元の User インスタンスが返る

자주 쓰이는 활용 지점

디버그 출력 끼워 넣기

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

체인 도중에 로그나 이벤트 송신 삽입하기

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

반환값은 바꾸지 않고 부수 효과만 실행하기

$response = tap($service->handle($request), function ($response) {
    \Illuminate\Support\Facades\Cache::increment('service_handle_success_total');
});
반환값을 가공하고 싶다면 with()나 일반적인 변수 할당을 사용합니다. tap()은 부수 효과에 한정해서 사용하면 가독성이 좋아집니다.

Tappable 트레이트란

Illuminate\Support\Traits\Tappableuse하면, 클래스에 tap() 메서드를 추가할 수 있습니다.
trait Tappable
{
    public function tap($callback = null)
    {
        return tap($this, $callback);
    }
}
즉 인스턴스 메서드 버전의 tap()을 제공할 뿐입니다.
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 도중에 부수 효과를 끼워 넣고 싶을 때 편리합니다. MacroableConditionable을 조합하면, Laravel다운 확장 가능한 빌더를 만들 수 있습니다.
1

유창한 클래스를 만들기

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;
    }
}
2

Macroable·Conditionable·Tappable을 조합해 사용하기

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();
관련 페이지:

Laravel 코어 내에서의 사용 예

Laravel 본체에서도 tap() / Tappable은 실제로 사용되고 있습니다.
  • Illuminate\Routing\RouterMacroableTappable을 함께 사용합니다
  • Router::respondWithRoute()에서는 콜백 없는 tap($route)->bind(...)를 사용하여, bind() 실행 후에도 원래의 $route를 반환합니다
  • Router::prepareResponse()에서는 응답 변환 후에 tap(..., fn (...) => event(...))으로 이벤트를 발생시킵니다
  • Illuminate\Testing\Fluent\Concerns\Has에서는 ->tap(...)->first(...)->etc()라는 체인으로 어서션 보조를 구현하고 있습니다
// 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));
});
tap()은 단독으로는 작은 헬퍼이지만, Macroable·Conditionable과 함께 사용하면 Laravel에서 흔히 볼 수 있는 읽기 쉬운 메서드 체인을 만들기 쉬워집니다.

다음 단계

Macroable 트레이트

기존 클래스에 고유 메서드를 추가하는 설계를 배웁니다.

Conditionable 트레이트

when() / unless()를 통한 조건 분기 체인을 배웁니다.
마지막 수정일 2026년 7월 13일