$allowedColumns 원 테이블 허용 정렬 컬럼 * @param array, foreign_key: string, column: string}> $relatedMap 정렬 키 → 관계 스펙 * @param Model $parent 정렬 대상 원 모델 (상관 조건의 좌변) * @param string $defaultColumn 기본 정렬 컬럼 * @param string $defaultDirection 기본 정렬 방향 * @param string $columnKey 정렬 컬럼이 담긴 필터 키 * @param string $directionKey 정렬 방향이 담긴 필터 키 * @return array 정렬 스펙 배열 */ protected function resolveSortSpecWithRelated( array $filters, array $allowedColumns, array $relatedMap, Model $parent, string $defaultColumn, string $defaultDirection = 'desc', string $columnKey = 'sort_by', string $directionKey = 'sort_order', ): array { $requested = $filters[$columnKey] ?? null; $requested = is_string($requested) ? trim($requested) : ''; if ($requested === '' || ! isset($relatedMap[$requested])) { return $this->resolveSortSpec( $filters, $allowedColumns, $defaultColumn, $defaultDirection, columnKey: $columnKey, directionKey: $directionKey, ); } $direction = $this->normalizeSortDirection($filters[$directionKey] ?? null, $defaultDirection); return [[ 'column' => $this->relatedSortSubquery($relatedMap[$requested], $parent, $direction), 'direction' => $direction, ]]; } /** * 관계 테이블에서 정렬 기준값 한 건을 뽑는 상관 서브쿼리를 만듭니다. * * 정렬 방향과 같은 방향으로 관계 행을 정렬해 첫 건만 취한다 — * `desc` 면 가장 늦은 값, `asc` 면 가장 이른 값이 기준이 된다. * * @param array{model: class-string, foreign_key: string, column: string} $spec 관계 스펙 * @param Model $parent 원 모델 * @param string $direction 정렬 방향 (asc|desc) * @return Builder 상관 서브쿼리 */ protected function relatedSortSubquery(array $spec, Model $parent, string $direction): Builder { /** @var Model $related */ $related = new $spec['model']; return $related->newQuery() ->select($related->qualifyColumn($spec['column'])) ->whereColumn($related->qualifyColumn($spec['foreign_key']), $parent->qualifyColumn($parent->getKeyName())) ->orderBy($related->qualifyColumn($spec['column']), $direction) ->limit(1); } }