什么是 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 中间需要插入副作用时非常方便。结合 Macroable 与 Conditionable,可以构建颇有 Laravel 味道的可扩展 Builder。
创建流畅 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 ;
}
}
组合使用 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 同时使用了 Macroable 与 Tappable
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 trait 学习向既有类添加自定义方法的设计。
Conditionable trait 学习基于 when() / unless() 的条件分支链。