Laravel同一字段设置两个where条件查询结果异常咨询
查询创建时间距当前30~365天数据的问题排查
问题背景
需要查询所有创建时间距当前日期为30~365天前的数据,编写的两段代码均无法返回预期结果。
测试基础信息
- 测试基准日期:2022-06-10
- 测试表结构与样本数据:
id created_at 1 2022-05-09 2 2021-06-08
- 预期返回规则:
- id=2的记录距基准日超过365天,不应返回
- id=1的记录距基准日超过30天但不足365天,为目标返回结果
异常代码与现象
测试代码1
$today = Carbon::now(); $doubtfulLoan = Loan::select('*') ->where(function($query) use ($today) { return $query ->where('created_at', '<', $today->subDays(30)->endOfDay()) ->where('created_at', '>=', $today->subDays(365)->endOfDay()); }) ->get();
- 运行结果:返回空数组
- 补充测试现象:
- 注释第二个where子句时,返回id=1、id=2两条记录
- 注释第一个where子句时,仅返回id=1的记录
- 两个条件同时生效时返回空数组
测试代码2
$today = Carbon::now(); $doubtfulLoan = Loan::select('*') ->where([ ['created_at', '<', $today->subDays(30)->endOfDay()], ['created_at', '>=', $today->subDays(365)->endOfDay()] ])->get();
- 运行结果:返回全部两条记录,不符合预期
问题根因
Carbon默认是可变对象,所有日期加减、时间修改类方法都会原地修改当前实例并返回自身。两段代码出错的核心原因都是复用同一个$today实例多次调用修改方法,导致查询条件的时间边界被意外篡改:
- 代码1中,第一次
subDays(30)先将$today修改为30天前的时间,后续subDays(365)是在已经偏移30天的基础上再减365天,实际总共偏移了395天;且传入查询构造器的是同一个对象引用,两个条件最终拿到的时间值逻辑互斥,因此返回空数组。 - 代码2中,数组参数在传入查询方法前会完成全部求值,两次
subDays同样修改了同一个$today实例,导致时间边界偏移,最终把超过365天的记录也纳入了返回结果。
修复方案
核心原则是保证两个查询条件的时间边界独立计算,不要在同一个Carbon实例上连续做修改操作。
方案1:提前定义独立的时间边界变量
调用修改方法前用copy()复制新实例,避免互相干扰:
$now = Carbon::now(); // 早于该时间 = 距当前超过30天 $threshold30 = $now->copy()->subDays(30)->endOfDay(); // 晚于等于该时间 = 距当前不足365天 $threshold365 = $now->copy()->subDays(365)->endOfDay(); $doubtfulLoan = Loan::query() ->where('created_at', '<', $threshold30) ->where('created_at', '>=', $threshold365) ->get();
方案2:链式调用时实时复制实例
如果不想提前定义变量,每次调用修改方法前都链式调用copy()生成新实例:
$today = Carbon::now(); $doubtfulLoan = Loan::query() ->where('created_at', '<', $today->copy()->subDays(30)->endOfDay()) ->where('created_at', '>=', $today->copy()->subDays(365)->endOfDay()) ->get();
方案3:使用不可变Carbon类
Carbon 2.0+提供了不可变类CarbonImmutable,所有修改类方法都会自动返回新实例,不会出现原地修改的问题,数组写法也可以直接正常使用:
$today = CarbonImmutable::now(); $doubtfulLoan = Loan::query() ->where([ ['created_at', '<', $today->subDays(30)->endOfDay()], ['created_at', '>=', $today->subDays(365)->endOfDay()] ])->get();
内容的提问来源于stack exchange,提问作者Amrita Stha
相关产品推荐
相关产品推荐

