如何在Fractal中按关联原始资源对资源集合排序?
实现按最近X天评论数排序并返回统计数的Laravel Fractal方案
我刚好做过类似的需求,给你一步步拆解实现:
1. 先在Post模型里添加动态统计评论数的逻辑
我们需要能够根据传入的天数,计算每个帖子在该时间段内的评论数。可以在Post模型里定义一个作用域,直接在查询时拼接统计字段:
// app/Models/Post.php use Illuminate\Database\Eloquent\Model; class Post extends Model { public function comments() { return $this->hasMany(Comment::class); } // 定义作用域,添加最近X天评论数的统计字段 public function scopeWithRecentCommentCount($query, $days) { return $query->addSelect([ 'recent_comment_count' => Comment::selectRaw('count(*)') ->whereColumn('post_id', 'posts.id') ->where('created_at', '>=', now()->subDays($days)) ]); } }
2. 处理PostTransformer中的include解析
接下来在你的PostTransformer里,解析comment_count:since_days(7)这种带参数的include请求,提取天数后把统计数加入响应:
// app/Transformers/PostTransformer.php use League\Fractal\TransformerAbstract; class PostTransformer extends TransformerAbstract { protected $availableIncludes = ['recent_comment_count']; protected $days = 7; // 默认天数 public function transform(Post $post) { return [ 'id' => $post->id, 'title' => $post->title, 'content' => $post->content, // 其他基础字段... ]; } // 返回最近评论数的原始值 public function includeRecentCommentCount(Post $post) { return $this->primitive($post->recent_comment_count); } // 重写include解析逻辑,提取since_days参数 public function parseIncludes($includes) { $parsed = []; foreach ($includes as $include) { if (strpos($include, ':') !== false) { list($name, $params) = explode(':', $include, 2); // 匹配since_days(数字)格式的参数 if (preg_match('/since_days\((\d+)\)/', $params, $matches)) { $this->days = (int)$matches[1]; $parsed[] = 'recent_comment_count'; } } else { $parsed[] = $include; } } return parent::parseIncludes($parsed); } }
3. 控制器中处理请求参数(include和sort)
在PostsController里,需要解析请求的include和sort参数,构建带统计和排序的查询:
// app/Http/Controllers/Api/PostsController.php use App\Models\Post; use App\Transformers\PostTransformer; use League\Fractal\Manager; use League\Fractal\Resource\Collection; class PostsController extends Controller { public function index() { $fractal = new Manager(); $request = request(); $days = 7; // 默认天数 $sortField = 'id'; $sortDirection = 'asc'; // 处理include参数,同时提取天数 if ($request->has('include')) { $fractal->parseIncludes($request->get('include')); // 从include参数里提取天数 if (preg_match('/comment_count:since_days\((\d+)\)/', $request->get('include'), $matches)) { $days = (int)$matches[1]; } } // 处理sort参数,切换为统计字段排序 if ($request->has('sort')) { list($field, $direction) = explode(':', $request->get('sort'), 2); if ($field === 'comment_count') { $sortField = 'recent_comment_count'; $sortDirection = strtolower($direction) === 'desc' ? 'desc' : 'asc'; } } // 构建查询:添加统计字段 + 排序 $posts = Post::withRecentCommentCount($days) ->orderBy($sortField, $sortDirection) ->get(); // 转换为Fractal资源并返回 $resource = new Collection($posts, new PostTransformer()); $resource->setMeta([ 'stats_period_days' => $days, 'sort_field' => $sortField, 'sort_direction' => $sortDirection ]); return response()->json($fractal->createData($resource)->toArray()); } }
4. 测试API请求
现在你可以直接测试目标API了:
GET /api/posts?include=comment_count:since_days(7)&sort=comment_count:desc
响应会包含每个帖子的recent_comment_count字段,并且帖子按最近7天的评论数降序排列。
优化小提示
- 给comments表的
post_id和created_at字段添加联合索引,能大幅提升统计查询的性能; - 可以把参数解析逻辑封装成自定义Request类,让控制器代码更简洁;
- 如果用了Laravel的Fractal集成包,可通过依赖注入直接在Transformer里获取请求实例,简化参数传递。
内容的提问来源于stack exchange,提问作者Horrorente
相关产品推荐
相关产品推荐

