什么是 Conditionable trait
Illuminate\Support\Traits\Conditionable trait 为对象添加了 when() 与 unless() 方法。它的特点是可以在方法链中按条件分支执行处理。
实际源码位于 src/Illuminate/Conditionable/Traits/Conditionable.php,通过别名 Illuminate\Support\Traits\Conditionable 引用。
QueryBuilder、EloquentBuilder、Mail、Notification 等 Laravel 中许多类都使用了该 trait。
基本用法
when() — 条件为真时执行
use Illuminate\Support\Collection;
$results = collect([1, 2, 3, 4, 5])
->when(true, function (Collection $collection) {
return $collection->filter(fn ($n) => $n > 2);
});
// [3, 4, 5]
当第 1 个参数的值为真时,第 2 个参数的回调会被执行;为假时,则执行第 3 个参数的回调(默认回调)。
$results = collect([1, 2, 3, 4, 5])
->when(false, function (Collection $collection) {
return $collection->filter(fn ($n) => $n > 2);
}, function (Collection $collection) {
return $collection->filter(fn ($n) => $n < 3);
});
// [1, 2]
unless() — 条件为假时执行
unless() 是 when() 的相反版本。条件为假时执行回调。
$results = collect([1, 2, 3, 4, 5])
->unless(false, function (Collection $collection) {
return $collection->take(3);
});
// [1, 2, 3]
方法链得以延续的原因
当回调返回值为 null 时,将返回 $this(使用该 trait 的对象)。当回调返回非 null 值时,就使用该返回值。
// 因为返回的是 $this,所以链式调用可以继续
$query = User::query()
->when($request->has('active'), function ($query) {
$query->where('active', true); // 返回 void / null
})
->when($request->filled('name'), function ($query) use ($request) {
$query->where('name', 'like', "%{$request->name}%");
})
->orderBy('created_at', 'desc');
源码中的实现如下:
if ($value) {
return $callback($this, $value) ?? $this;
} elseif ($default) {
return $default($this, $value) ?? $this;
}
return $this;
如果回调返回了明确的值,该值会传递给链的下一步;否则(返回 null)返回的是 $this。
无参数调用 — HigherOrderWhenProxy
不传参数调用 when() 时会返回 HigherOrderWhenProxy。这样可以稍后再设置条件。
$query = User::query()
->when()->isActive() // 将 isActive() 作为条件求值
->where('role', 'admin');
传入 1 个参数调用时,会返回一个持有该值作为条件的代理。
// 1 个参数:仅传入条件获得代理
$proxy = collect([1, 2, 3])->when($request->has('filter'));
// $proxy->methodName() 会在条件为真时才调用 methodName()
传入闭包作为值
若第 1 个参数是闭包,则以该闭包的返回值作为条件。
$results = User::query()
->when(
fn ($query) => $request->filled('role'),
fn ($query) => $query->where('role', $request->role)
)
->get();
由此可以将条件的求值逻辑拆分到回调中。
在 QueryBuilder 中的典型模式
使用 when() 构建动态查询是最常见的用途。
public function index(Request $request)
{
$users = User::query()
->when($request->filled('search'), function ($query) use ($request) {
$query->where('name', 'like', "%{$request->search}%")
->orWhere('email', 'like', "%{$request->search}%");
})
->when($request->filled('role'), fn ($q) => $q->where('role', $request->role))
->when($request->boolean('verified'), fn ($q) => $q->whereNotNull('email_verified_at'))
->when(
$request->filled('sort'),
fn ($q) => $q->orderBy($request->sort, $request->get('direction', 'asc')),
fn ($q) => $q->latest()
)
->paginate();
return UserResource::collection($users);
}
在自定义类中使用 Conditionable
只需 use 该 trait,就能获得 when() / unless() 能力。
namespace App\Services;
use Illuminate\Support\Traits\Conditionable;
class ReportBuilder
{
use Conditionable;
protected array $filters = [];
protected bool $includeArchived = false;
protected ?string $groupBy = null;
public function withArchived(): static
{
$this->includeArchived = true;
return $this;
}
public function groupBy(string $column): static
{
$this->groupBy = $column;
return $this;
}
public function addFilter(string $column, mixed $value): static
{
$this->filters[$column] = $value;
return $this;
}
public function build(): \Illuminate\Database\Eloquent\Builder
{
return Report::query()
->when($this->includeArchived, fn ($q) => $q->withTrashed())
->when($this->groupBy, fn ($q) => $q->groupBy($this->groupBy))
->when($this->filters, function ($q) {
foreach ($this->filters as $column => $value) {
$q->where($column, $value);
}
});
}
}
// 使用示例
$query = (new ReportBuilder)
->when($request->boolean('archived'), fn ($b) => $b->withArchived())
->when($request->filled('group'), fn ($b) => $b->groupBy($request->group))
->addFilter('status', 'published')
->build();
在 Mail、Notification、Response 中的应用
when() 也可用于邮件、通知和响应的构建。
use Illuminate\Mail\Mailable;
use Illuminate\Support\Traits\Conditionable;
class OrderConfirmation extends Mailable
{
public function build(): static
{
return $this
->subject('您的订单已受理')
->view('emails.order.confirmation')
->when($this->order->hasDiscount(), function (Mailable $mail) {
$mail->attach(storage_path('discounts/coupon.pdf'));
})
->when(app()->environment('production'), function (Mailable $mail) {
$mail->bcc('[email protected]');
});
}
}
与 tap() 的区别
tap() 与 when() 看似相似,但目的不同。
| tap() | when() |
|---|
| 目的 | 副作用(日志、调试) | 条件分支 |
| 返回值 | 始终为 $this | 视条件返回 $this 或回调的返回值 |
| 条件 | 无 | 有 |
// tap:用于副作用,返回值始终是 $this
$user = User::find($id)
->tap(fn ($user) => Log::info("User {$user->id} loaded"));
// when:用于条件分支
$user = User::query()
->when($isAdmin, fn ($q) => $q->where('role', 'admin'))
->first();
只是想在链式调用中做一些调试或副作用的话,使用 tap()。要根据条件切换处理逻辑,则使用 when() / unless()。
下一步
集合的高阶消息
了解 $collection->map->method() 这类语法的机制与实用用法。