Spatie QueryBuilder关联模型多字段同时排序失效如何解决
关联模型字段多规则排序失效问题
问题场景
现有基于spatie/laravel-query-builder编写的Member查询语句,需要实现关联User模型的name字段排序,初始代码直接在allowedSorts中声明关联字段无法生效:
$members = QueryBuilder::for(Member::class) ->allowedIncludes('user.name', 'association', 'affiliate') ->with('user') ->allowedFilters([ AllowedFilter::exact('association.id'), AllowedFilter::partial('zip'), AllowedFilter::exact('specialisations.id') ]) ->allowedSorts(['user.name', 'zip']) ->where('published', 1);
针对该问题,最初通过实现Sort接口编写自定义关联排序逻辑,调整后的查询构造代码如下:
$members = QueryBuilder::for(Member::class) ->with('user') ->allowedIncludes(['user', 'association', 'affiliate']) ->allowedFilters([ AllowedFilter::exact('association.id'), AllowedFilter::partial('zip'), AllowedFilter::exact('specialisations.id') ]) ->allowedSorts( AllowedSort::custom("user.name", new SortByRelation()), 'zip' ) ->where('published', 1);
对应自定义排序的实现代码:
public function __invoke(Builder $query, bool $descending, string $property) { [$relationName, $columnName] = explode(".", $property); $relation = $query->getRelation($relationName); $subQuery = $relation ->getQuery() ->select($columnName) ->whereColumn($relation->getQualifiedForeignKeyName(), $relation->getQualifiedOwnerKeyName()); $query->orderBy($subQuery, $descending ? "desc" : "asc"); }
现存问题
上述实现仅在单个排序字段场景下正常运行,传入多排序参数(如sort=zip,user.name)时,仅单个排序逻辑生效,无法返回符合多字段排序规则的结果。
问题原因与修复方案
核心问题
原自定义排序中调用$query->getRelation($relationName)获取关联实例的逻辑存在缺陷:该方法用于获取模型已经加载完成的关联数据,在查询构造阶段(未执行查询、未触发预加载时)无法稳定拿到正确的关联定义实例,多排序规则依次构造时会出现关联实例取值错误,导致后续排序子句构造异常。
另外原实现未处理一对多关联场景下子查询返回多行的问题,也未对字段名做全限定名声明,多表查询时容易出现字段歧义报错。
修复后的自定义排序实现
public function __invoke(Builder $query, bool $descending, string $property) { // 拆分关联名与排序字段,支持两层以内的关联.字段格式 [$relationName, $columnName] = explode(".", $property, 2); // 直接通过模型实例调用关联方法,拿到稳定的关联定义实例,不依赖已加载的关联数据 $relation = $query->getModel()->{$relationName}(); $relatedTable = $relation->getRelated()->getTable(); // 构造关联子查询,所有字段使用全限定名避免歧义,一对多场景加limit1避免子查询返回多行 $subQuery = $relation->getQuery() ->select("{$relatedTable}.{$columnName}") ->whereColumn( $relation->getQualifiedForeignKeyName(), $relation->getQualifiedOwnerKeyName() ) ->limit(1); // 追加排序规则,orderBy原生方法会按调用顺序追加排序,不会覆盖之前已构造的排序条件 $query->orderBy($subQuery, $descending ? 'desc' : 'asc'); }
注意事项
- 若需要适配多对多(
BelongsToMany)关联,需要单独判断关联类型,调整子查询中的关联条件,补上中间表的连接逻辑。 - 自定义排序逻辑中不要调用
reorder()方法,该方法会清空之前已经构造的所有排序规则,直接导致多字段排序失效。 - 多字段排序的执行顺序和请求参数中传入的字段顺序一致,例如
sort=zip,user.name会先按zip升序排,zip相同的条目再按关联user的name升序排。
内容的提问来源于stack exchange,提问作者fefe
相关产品推荐
相关产品推荐

