在Laravel 8的数据库查询构造器使用中,闭包查询是非常常见的写法,比如我们需要在查询条件中用到闭包外部定义的变量,就需要掌握正确的访问方式,否则很容易触发变量作用域相关的错误。

为什么闭包内无法直接访问外部变量
PHP的闭包函数默认不会自动继承父作用域的变量,这是语言本身的作用域规则决定的。在Laravel的查询构造器中,闭包通常作为查询条件的回调传入,比如where方法的闭包参数,此时如果直接在闭包内使用外部定义的变量,就会出现变量未定义的报错。
比如下面这段错误的代码:
<?php
$status = 1;
$users = DB::table('users')->where(function ($query) {
// 这里直接访问$status会报错,因为闭包没有继承该变量
$query->where('status', $status);
})->get();
使用use关键字传递外部变量
PHP提供了use关键字,可以让闭包继承父作用域的变量,这是Laravel闭包查询中访问外部变量最常用的方法。
我们修改上面的错误代码,通过use传递$status变量:
<?php
$status = 1;
$minAge = 18;
$users = DB::table('users')->where(function ($query) use ($status, $minAge) {
// 现在可以正常访问use传递进来的变量
$query->where('status', $status)
->where('age', '>=', $minAge);
})->get();
需要注意,use传递的是变量的当前值,如果外部变量后续发生了修改,闭包内使用的还是传递时的值。如果需要闭包内使用外部变量的最新值,可以传递变量的引用,示例如下:
<?php
$status = 1;
$users = DB::table('users')->where(function ($query) use (&$status) {
// 这里使用的是$status的最新值,如果外部修改了$status,这里会同步变化
$query->where('status', $status);
})->get();
// 修改外部变量
$status = 2;
通过请求对象获取外部参数
如果外部变量是来自HTTP请求的参数,也可以直接在闭包内通过请求对象获取,不需要额外传递。Laravel的请求对象可以通过request()辅助函数或者注入Request类来获取。
示例代码如下:
<?php
use IlluminateHttpRequest;
use IlluminateSupportFacadesDB;
// 方式一:使用request()辅助函数
$users = DB::table('users')->where(function ($query) {
$status = request()->input('status', 1);
$query->where('status', $status);
})->get();
// 方式二:注入Request对象
$users = DB::table('users')->where(function ($query) {
// 假设已经在方法参数中注入了$request变量
$status = $request->input('status', 1);
$query->where('status', $status);
})->get();
常见注意事项
- 使用use传递变量时,变量需要在闭包定义之前就已经定义,否则还是会报错。
- 如果传递的是对象,use传递的是对象的引用,修改对象内部的属性会影响外部的对象,这一点和传递普通变量的引用不同。
- 不要在闭包内修改通过use传递的普通变量(非引用传递),这不会影响外部变量的值,还容易造成逻辑混乱。
不同场景的选择建议
如果是已经定义的局部变量,优先使用use关键字传递,代码逻辑更清晰;如果是来自请求的参数,直接在闭包内通过请求对象获取会更方便,减少参数传递的层级。
下面是一个完整的控制器方法示例,综合使用了两种方式:
<?php
namespace AppHttpControllers;
use IlluminateSupportFacadesDB;
use IlluminateHttpRequest;
class UserController extends Controller
{
public function index(Request $request)
{
// 外部定义的固定变量
$validStatus = [1, 2];
// 从请求获取的参数
$age = $request->input('age', 0);
$users = DB::table('users')->where(function ($query) use ($validStatus, $request) {
$query->whereIn('status', $validStatus)
->where('age', '>=', $request->input('age', 0));
})->get();
return view('user.index', ['users' => $users]);
}
}