如何将Eloquent API Resource与Spatie QueryBuilder结合使用?
问题描述
我在查阅QueryBuilder文档及相关博客时,看到了关于使用Eloquent API Resources的提及,但只有引用没有详细说明。我想知道的是,如何为QueryBuilder返回的主模型使用资源集合,而不是只在资源内部的关联关系中使用。
举个例子,我现在用QueryBuilder返回WorkHours模型的代码如下:
return Inertia::render('Users/WorkHours/Index', [ 'workHours' => QueryBuilder::for(WorkHours::class) ->where('user_id', $user->id) ->with('area') ->allowedIncludes(['area']) ->allowedSorts('date') ->defaultSort('-date') ->allowedFilters([ AllowedFilter::callback('year', function(Builder $query, $value) { $thirdSaturdayPrevious = Carbon::parse('Third Saturday of April ' . $value-1)->toDateString(); $thirdSaturdayThis = Carbon::parse('Third Saturday of April ' . $value)->toDateString(); $query->whereBetween('date', [$thirdSaturdayPrevious, $thirdSaturdayThis]); }) ]) ->paginate(20) ->withQueryString(), 'user' => new UserResource($user), 'areas' => Area::with('approvers')->get() ]);
在使用QueryBuilder之前,我是这样处理的:
'workHours' => WorkHoursResource::collection(WorkHours::where('user_id', $user->id) ->where('approved', true) ->with(['approver', 'area', 'user'])->get()),
我会在资源里做一些格式化操作,比如把布尔值转换成"Yes"或"No"。但现在我找不到合适的方式把WorkHoursResource应用到QueryBuilder返回的WorkHours记录上——直接用WorkHoursResource::collection()包裹会返回所有分页元数据,在查询的不同位置尝试也没用。请问这种场景下能不能为主模型使用资源?
解决方案
当然可以为主模型使用Eloquent API Resources,核心是要正确处理QueryBuilder返回的分页实例,而不是直接包裹整个分页对象。以下两种方法都能实现需求:
方法1:手动转换分页结果的items
先获取QueryBuilder的分页实例,然后将其中的items用资源集合处理,同时保留分页的元数据(页码、每页数量、总条数等):
$workHoursPaginated = QueryBuilder::for(WorkHours::class) ->where('user_id', $user->id) ->with('area') ->allowedIncludes(['area']) ->allowedSorts('date') ->defaultSort('-date') ->allowedFilters([ AllowedFilter::callback('year', function(Builder $query, $value) { $thirdSaturdayPrevious = Carbon::parse('Third Saturday of April ' . $value-1)->toDateString(); $thirdSaturdayThis = Carbon::parse('Third Saturday of April ' . $value)->toDateString(); $query->whereBetween('date', [$thirdSaturdayPrevious, $thirdSaturdayThis]); }) ]) ->paginate(20) ->withQueryString(); // 将分页的items转换为资源集合 $workHoursPaginated->setCollection(WorkHoursResource::collection($workHoursPaginated->getCollection())); return Inertia::render('Users/WorkHours/Index', [ 'workHours' => $workHoursPaginated, 'user' => new UserResource($user), 'areas' => Area::with('approvers')->get() ]);
方法2:自定义分页资源(推荐)
创建专门的分页资源类,统一处理分页数据和资源转换,代码更整洁易维护:
- 生成分页资源类:
php artisan make:resource WorkHoursPaginatedResource
- 在
WorkHoursPaginatedResource中定义结构:
<?php namespace App\Http\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; class WorkHoursPaginatedResource extends ResourceCollection { public function toArray($request) { return [ 'data' => WorkHoursResource::collection($this->collection), 'meta' => [ 'current_page' => $this->currentPage(), 'last_page' => $this->lastPage(), 'per_page' => $this->perPage(), 'total' => $this->total(), 'query' => $request->query(), ], 'links' => [ 'first' => $this->url(1), 'last' => $this->url($this->lastPage()), 'prev' => $this->previousPageUrl(), 'next' => $this->nextPageUrl(), ], ]; } }
- 在控制器中使用:
return Inertia::render('Users/WorkHours/Index', [ 'workHours' => new WorkHoursPaginatedResource( QueryBuilder::for(WorkHours::class) ->where('user_id', $user->id) ->with('area') ->allowedIncludes(['area']) ->allowedSorts('date') ->defaultSort('-date') ->allowedFilters([ AllowedFilter::callback('year', function(Builder $query, $value) { $thirdSaturdayPrevious = Carbon::parse('Third Saturday of April ' . $value-1)->toDateString(); $thirdSaturdayThis = Carbon::parse('Third Saturday of April ' . $value)->toDateString(); $query->whereBetween('date', [$thirdSaturdayPrevious, $thirdSaturdayThis]); }) ]) ->paginate(20) ->withQueryString() ), 'user' => new UserResource($user), 'areas' => Area::with('approvers')->get() ]);
这两种方式都能让主模型的每条记录经过WorkHoursResource的格式化处理,同时保留分页所需的元数据和链接,完全满足你的需求。
内容的提问来源于stack exchange,提问作者wonder95
相关产品推荐
相关产品推荐

