You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel中如何对已过滤关联记录用withSum?避免代码重复

问题描述

我的$user模型拥有名为earnings的关联关系,对应用户的收入交易记录。需要根据部分属性过滤earnings记录,但使用withSum时会统计所有关联记录,因此我不得不在获取earnings列表和计算earnings_sum_amount时重复编写相同的过滤子查询,代码冗余问题明显。

现有代码如下:

$users->with([
            'earnings' => function($query) use ($dateSpanArray, $brandModel) {

                /* Subquery is executing when one of 2 parameters are true */
                $query->when(($dateSpanArray || $brandModel) , function($query) use ($dateSpanArray, $brandModel) {

                    if($brandModel)
                    {
                        $query->whereIn('sender_id', $brandModel->wallets->pluck('id')->toArray());
                    }
                    if($dateSpanArray)
                    {
                        $query->whereBetween('created_at', [$dateSpanArray['from'],$dateSpanArray['to']]);
                    }
                })
                ->latest();
            }]
        )

->withSum([
            'earnings' => function($query) use ($dateSpanArray, $brandModel) {

                $query->when(($dateSpanArray || $brandModel) , function($query) use ($dateSpanArray, $brandModel) {

                    if($brandModel)
                    {
                        $query->whereIn('sender_id', $brandModel->wallets->pluck('id')->toArray());
                    }
                    if($dateSpanArray)
                    {
                        $query->whereBetween('created_at', [$dateSpanArray['from'],$dateSpanArray['to']]);
                    }
                })->select(DB::raw('COALESCE(SUM(amount), 0)'));
            }
        ], 'amount')
->get();

想请教是否存在更优写法,可避免此类代码重复?

优化方案

方法1:提取过滤逻辑为可复用闭包

把重复的过滤逻辑抽成独立闭包,在with和withSum中直接复用,减少代码复制:

// 定义可复用的过滤闭包
$earningsFilter = function($query) use ($dateSpanArray, $brandModel) {
    $query->when(($dateSpanArray || $brandModel), function($query) use ($dateSpanArray, $brandModel) {
        if($brandModel) {
            $query->whereIn('sender_id', $brandModel->wallets->pluck('id')->toArray());
        }
        if($dateSpanArray) {
            $query->whereBetween('created_at', [$dateSpanArray['from'], $dateSpanArray['to']]);
        }
    });
};

// 复用闭包执行查询
$users->with([
        'earnings' => function($query) use ($earningsFilter) {
            $earningsFilter($query);
            $query->latest();
        }
    ])
    ->withSum([
        'earnings' => function($query) use ($earningsFilter) {
            $earningsFilter($query);
            $query->select(DB::raw('COALESCE(SUM(amount), 0)'));
        }
    ], 'amount')
    ->get();

方法2:在Earnings模型定义本地作用域

如果该过滤逻辑会在多个场景使用,推荐在Earnings模型中定义本地作用域,实现全局复用:

在Earnings.php模型中添加作用域方法:

public function scopeFilterByBrandAndDate($query, $brandModel = null, $dateSpanArray = null)
{
    return $query->when(($dateSpanArray || $brandModel), function($query) use ($dateSpanArray, $brandModel) {
        if($brandModel) {
            $query->whereIn('sender_id', $brandModel->wallets->pluck('id')->toArray());
        }
        if($dateSpanArray) {
            $query->whereBetween('created_at', [$dateSpanArray['from'], $dateSpanArray['to']]);
        }
    });
}

之后查询代码可简化为:

$users->with([
        'earnings' => function($query) use ($dateSpanArray, $brandModel) {
            $query->filterByBrandAndDate($brandModel, $dateSpanArray)
                  ->latest();
        }
    ])
    ->withSum([
        'earnings' => function($query) use ($dateSpanArray, $brandModel) {
            $query->filterByBrandAndDate($brandModel, $dateSpanArray)
                  ->select(DB::raw('COALESCE(SUM(amount), 0)'));
        }
    ], 'amount')
    ->get();

方法3:自定义带过滤的关联(进阶)

若过滤逻辑和用户关联强绑定,可在User模型中定义一个带参数的关联方法:

在User.php模型中添加:

public function filteredEarnings($brandModel = null, $dateSpanArray = null)
{
    return $this->earnings()
                ->when(($dateSpanArray || $brandModel), function($query) use ($dateSpanArray, $brandModel) {
                    if($brandModel) {
                        $query->whereIn('sender_id', $brandModel->wallets->pluck('id')->toArray());
                    }
                    if($dateSpanArray) {
                        $query->whereBetween('created_at', [$dateSpanArray['from'], $dateSpanArray['to']]);
                    }
                });
}

查询时直接使用这个自定义关联:

$users->with([
        'filteredEarnings' => function($query) {
            $query->latest();
        }
    ])
    ->withSum([
        'filteredEarnings' => function($query) {
            $query->select(DB::raw('COALESCE(SUM(amount), 0)'));
        }
    ], 'amount')
    ->get();

注:此方法需确保调用关联时参数已正确传入,适合固定场景复用。


内容的提问来源于stack exchange,提问作者Domagoj Samardžić

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 10:20:36