在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中重复加载:
- 在控制器方法开头添加预加载逻辑:
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); }
- 修改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
相关产品推荐
相关产品推荐

