課題
リレーション先のカラムに表示順序が指定されていてそれを元にリレーション元にデータを並び替えたい(ソートしたい)ときがあります。
例えば、次のようなケースを考えてみます。
countires
id | name | dsp_order |
---|---|---|
1 | 日本 | 1 |
2 | アメリカ | 5 |
3 | 中国 | 3 |
4 | 韓国 | 4 |
5 | 台湾 | 2 |
users
id | name | country_id |
---|---|---|
1 | 張 | 5 |
2 | 木村 | 1 |
3 | クレア | 2 |
4 | 金 | 4 |
5 | 李 | 3 |
望ましい順序
1. 木村
2. 張
3. 李
4. 金
5. クレア
このとき発行するSQLは次のようになります。
SELECT users.name
FROM users
LEFT JOIN countries ON countries.id = users.country_id
ORDER BY countries.dsp_order
LaravelにはGlobalScopeと言って、そのモデルにアクセスするときに自動で付加するクエリをあらかじめ定義しておくことが可能な機能があります。Userモデルにアクセスしたときに上記のようなSQLを自動で発行させるにはどのようなGlobalScopeを書くといいでしょうか。
解決策
protected static function boot()
{
parent::boot();
static::addGlobalScope('order', function (Builder $builder) {
$builder->select('users.*')
->join('countries', 'contries.id', '=', 'users.id')
->orderBy('countries.dsp_order', 'asc')
});
}
select('自分のテーブル.*')
を先頭につけておくのがポイントです。さもないと、joinしたcountriesのカラムまで引き連れたデータが取れて、意図した挙動になりません。
追記(2021.05.24)
GlobalScopeに他のテーブルを結合するような処理を書いていると、何気なく書いたQueryが他のテーブルのカラム名と衝突したりして(nameとかそれこそdsp_orderとかnoとか)、思わぬバグの原因になるので、このような処理はGlobalScopeではなくLocalScopeに定義してあげて、適宜必要に応じて呼んであげるという運用にしたほうがいいかもしれません。