Laravel helper tap 助手函数完全解析
tap 函数的起源
tap 函数是一个高阶函数 higher-order function (HOF),在 Larvel 5.3 当中被引入
// 接收两个参数,`$value` 为需要处理的数据,`$callback` 为闭包 Closure
function tap($value, $callback)
{
$callback($value); // $callback 对 $value 进行处理
return $value; // 返回处理完毕的 $value
}
这里解释了一种用法
public function findArticleAndIncreaseView($id)
{
$article = Article::findOrFail($id);
$article->incrementViews();
return $article;
}
public function findArticleAndIncreaseView($id)
{
return tap(Article::findOrFail($id), function (Article $article) {
$article->incrementViews();
});
}
仔细分析一下原始代码步骤:
- 原始代码中表明这是一个会产生副作用的函数,直接对
$article
中的状态进行了修改,用例子中的话来说就是执行了一个增加文章的阅读量
的操作 - 声明了一个函数作用域内的变量
$article
使用 tap
之后的改进:
- 没有声明额外变量
- 引入
tap
函数,将三条语句包裹为一个代码体 - 对
fn(v);return v;
的过程进行了抽象
注意⚠️ tap
中的 $value
必须是一个引用类型,因为副作用只能作用于引用类型,也就是 PHP 中的 \stdClass
咋一看没什么鸟用,好像还搞复杂了?
如果看到这里你以为就这么简单那就错了,让我们重新回到源代码 tap v10.x
if (! function_exists('tap')) {
/**
* Call the given Closure with the given value then return the value.
*
* @param mixed $value
* @param callable|null $callback
* @return mixed
*/
function tap($value, $callback = null)
{
if (is_null($callback)) {
return new HigherOrderTapProxy($value);
}
$callback($value);
return $value;
}
}
可以看到,代码中对 $callback
进行了 is_null
检查,意思为:如果闭包函数为 null,也就是没有传递 $callback
,那么返回一个 new HigherOrderTapProxy($value);
这里叫它 tap 高阶函数代理
那么什么是 HigherOrderTapProxy
?HigherOrderTapProxy v10.x
namespace Illuminate\Support;
class HigherOrderTapProxy
{
/**
* The target being tapped.
*
* @var mixed
*/
public $target;
/**
* Create a new tap proxy instance.
*
* @param mixed $target
* @return void
*/
public function __construct($target)
{
$this->target = $target;
}
/**
* Dynamically pass method calls to the target.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
$this->target->{$method}(...$parameters);
return $this->target;
}
}
参考代码
// tap 拥有两种作用,一个是返回 $model
tap($model, $callback)
// 另一个是在忽略 callback 的时候,返回一个 `HigherOrderTapProxy` 实例
tap($model)
->foobar();
在我们不传递 $callback
到 tap
中的时候,$model
传递到了 HigherOrderTapProxy
这个类实例中的 $target
,也就是“被代理了”
HigherOrderTapProxy
中的 public __call(string $name, array $arguments): mixed
是 PHP 的类魔术方法,意思是当类实例调用一个不存在的实例方法的时候,会自动触发此方法,方法第一个参数接收调用的函数名,第二个参数接收调用的函数参数,如上函数名为 foobar
,参数为 空数组
这行代码 $this->target->{$method}(...$parameters);
对函数执行原有预期操作,没什么特别的
特别的地方在于 return $this->target;
我们返回的是被代理的 $model
而不是 $this->target->{$method}(...$parameters);
因为我们不需要 $model->foobar()
的返回值(这个返回值可能会返回布尔,或者返回数组,返回 Collection
等等非 $model
的值),我们要的只是 $model
比如
<?php
return $post->update([
'title' => $title,
'description' => $description,
]); // boolean
// 我们不要返回的 boolean,要 `$post`
return tap($post) // 先返回一个 HigherOrderTapProxy 实例
->update([
'title' => $title,
'description' => $description,
]); // 调用 HigherOrderTapProxy 实例不存在的方法 update,触发 __call 函数调用,最后返回委托对象 $post
PHP Magic Methods
https://freek.dev/694-laravels-tap-helper-function-explained
https://medium.com/@tanmaymishu/the-anatomy-of-laravels-tap-function-ea239c9846ab