Laravel中如何避免使用多层foreach循环为查询结果添加自定义属性
解决方案
方案1:查询阶段直接计算属性(性能最优,完全避免多层循环)
利用Laravel Eloquent支持的带条件关联聚合方法withSum,直接在数据库层面完成rate_zone_income的计算,不需要在PHP层面遍历多层数据,性能远高于手动循环,尤其适合数据量较大的场景。
修改后的查询代码如下:
$invoices = Invoice::when(!empty($request->customer_id), function ($q) use ($request) { return $q->where('customer_id', $request->customer_id); }) ->where('start_date', '>=', $request->start_date) ->whereDate('end_date', '<=', $request->end_date) ->when(!empty($request->rate_zone_id), function ($q) use ($request) { $rateZoneId = $request->rate_zone_id; // 带条件的sum计算,直接生成rate_zone_income属性 $q->withSum(['consignments_invoices.charges as rate_zone_income' => function($query) use ($rateZoneId) { $query->where('rate_zone_id', $rateZoneId); }], 'income'); // 保留原来的whereHas过滤条件 $q->whereHas('consignments_invoices.charges', function ($query) use ($rateZoneId) { $query->where('rate_zone_id', $rateZoneId); }); }) ->withCount(['consignments_invoices']) ->with(['customers:id,name', 'invoice_totals', 'consignments_invoices.charges']) ->get();
执行完上述查询后,每个Invoice对象会自动带上rate_zone_income属性,不需要后续任何多层循环处理。如果没有传入rate_zone_id,该属性默认为null,你也可以后续遍历一次集合统一设为空字符串,仅需一层循环即可,比原有的三层循环性能提升显著。
方案2:集合高阶方法简化循环(适配不改查询逻辑的场景)
如果你不方便修改原有查询逻辑,可以用Laravel集合的内置方法替代手写多层foreach,代码更简洁易读:
$invoices->each(function($invoice) use ($request) { $invoice->setAttribute('rate_zone_income', ''); if (!empty($request->rate_zone_id)) { $income = $invoice->consignments_invoices ->flatMap(fn($consignment) => $consignment->charges) ->where('rate_zone_id', $request->rate_zone_id) ->sum('income'); $invoice->setAttribute('rate_zone_income', $income); } });
这个方案本质还是遍历,但用框架内置方法代替手写嵌套循环,代码可维护性更高。
内容的提问来源于stack exchange,提问作者Bilal Arshad
相关产品推荐
相关产品推荐

