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

在Yajra-Datatables中高效实现Laravel Policy关联预加载的方法

解决Laravel Policy结合Yajra-Datatables时关联重复加载的问题

问题场景

使用Yajra-Datatables渲染数据时,每一行调用$request->user->can('create', $row)触发Policy校验时,Policy中的$user->load('sections')会被逐行执行,导致sections关联被重复加载,造成性能浪费。

现有Policy代码

public function create(User $user)
{
    $user->load('sections');
    $sections = collect($user->sections->toArray())->pluck('name');
    
    return
        in_array($user->role_id,[3,4])
        && in_array('Produksi', $sections->toArray())
        && Session::get('productionPlans-branch') !== 'all'
        && Session::get('productionPlans-period') !== 'all';
}

Datatables调用代码

public function table(Request $request)
{
    $query = ProductionPlan::with(['branch','product.category','period'])->orderBy('created_at');

    return \Yajra\DataTables\DataTables::of($query)
        ->addIndexColumn()
        ->addColumn('action', function($row) use ($request) {
            if ($request->user()->can('create', $row)) {
                return '<a href="javascript:void(0)" onclick="show('. $row->id .')">Add</a>';
            }
        })
        ->rawColumns(['action'])
        ->make(true);
}

解决方案

方法一:提前预加载用户关联到请求对象

在Datatables方法执行前,一次性预加载当前用户的sections关联,避免Policy中重复加载:

  1. 在控制器方法开头添加预加载逻辑:
public function table(Request $request)
{
    // 提前预加载当前用户的sections关联,仅执行一次
    $request->user()->load('sections');

    $query = ProductionPlan::with(['branch','product.category','period'])->orderBy('created_at');

    return \Yajra\DataTables\DataTables::of($query)
        ->addIndexColumn()
        ->addColumn('action', function($row) use ($request) {
            if ($request->user()->can('create', $row)) {
                return '<a href="javascript:void(0)" onclick="show('. $row->id .')">Add</a>';
            }
        })
        ->rawColumns(['action'])
        ->make(true);
}
  1. 修改Policy代码,去掉重复加载逻辑:
public function create(User $user)
{
    // 直接使用已预加载的sections关联
    $sections = $user->sections->pluck('name');
    
    return
        in_array($user->role_id,[3,4])
        && $sections->contains('Produksi')
        && Session::get('productionPlans-branch') !== 'all'
        && Session::get('productionPlans-period') !== 'all';
}

这里用Collection的contains替代in_array,逻辑更简洁高效。

方法二:在User模型中实现一次性加载逻辑

给User模型添加属性方法,确保sections关联仅加载一次:

class User extends Authenticatable
{
    // ...

    public function getSectionsLoadedAttribute()
    {
        if (! $this->relationLoaded('sections')) {
            $this->load('sections');
        }
        return $this->sections;
    }
}

然后在Policy中直接调用这个属性:

public function create(User $user)
{
    $sections = $user->sections_loaded->pluck('name');
    
    return
        in_array($user->role_id,[3,4])
        && $sections->contains('Produksi')
        && Session::get('productionPlans-branch') !== 'all'
        && Session::get('productionPlans-period') !== 'all';
}

无论调用多少次,sections关联只会被加载一次。

修复Accessor的序列化问题

你之前尝试用Accessor时遇到的"Serialization of 'PDO' is not allowed"错误,是因为直接返回了查询构造器实例。正确的Accessor写法应该确保返回集合且仅加载一次:

class User extends Authenticatable
{
    // 不建议将sections加入$appends,避免不必要的序列化加载
    // protected $appends = ['sections'];

    public function getSectionsAttribute()
    {
        if (! array_key_exists('sections', $this->attributes)) {
            $this->attributes['sections'] = $this->sections()->get();
        }
        return $this->attributes['sections'];
    }
}

不过更推荐前两种方案,因为Accessor如果加入$appends会在每次序列化用户模型时触发,可能造成额外性能开销。


内容的提问来源于stack exchange,提问作者owf

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 23:20:18