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

如何将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:自定义分页资源(推荐)

创建专门的分页资源类,统一处理分页数据和资源转换,代码更整洁易维护:

  1. 生成分页资源类:
php artisan make:resource WorkHoursPaginatedResource
  1. 在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(),
            ],
        ];
    }
}
  1. 在控制器中使用:
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 17:32:02