跳转到主要内容

什么是 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,
]);

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

典型使用场景

插入调试输出

$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 trait

在类中 use Illuminate\Support\Traits\Tappable 可以添加 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 味道的可扩展 Builder。
1

创建流畅 API 的类

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\Router 同时使用了 MacroableTappable
  • 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() 单独看是一个小巧辅助函数,与 MacroableConditionable 结合,能非常便利地写出 Laravel 常见的易读方法链。

下一步

Macroable trait

学习向既有类添加自定义方法的设计。

Conditionable trait

学习基于 when() / unless() 的条件分支链。
最后修改于 2026年7月13日