質問: Item::where('aaa', 'bbb')->orderBy('updated_by', 'desc')->paginate(20);` とりあえず上記コードが動くことは確認しました。 が、`orderBy('myPrice', 'asc')`にすると(当然ながら)動きません。 Controller側でmyPriceと同じ条件分岐をすればいい話なのですが、出来れば同じ処理を2つ書きたくありません。 `Item::where('aaa', 'bbb')->get()`したデータを元に配列作成、それをソートする方法もあると思いますが、 そうした場合paginateが使えないのでは?と思っています。 何か良い方法があればお教え頂けると助かります。
回答: scopeを使ってはどうでしょう?本来は独自のwhere句を書くための構文ですが、$queryを受け取れるのでこういった使い方もできるかもしれません(未検証)。 ``` class Item extends Model { // roleによって使用するカラム名を切り分ける private function myPriceColumnName() { if (\Auth::User()->role == 'admin') { return 'master_price'; } else { return 'my_price'; } } public function myPrice () { $column = $this->myPriceColumnName(); return $this->$column; // カラム名を変数を使って指定 } public function scopeOrderByMyPrice($query) { return $query->orderBy($this->myPriceColumnName(), 'asc'); // scope処理でorderByを付ける } } ``` ``` # 使い方 $itemList = Item::where('aaa', 'bbb') ->orderByMyPrice() // scopeXXXという名前のメソッドはクエリビルダでこうやって呼べる ->paginate(20); ``` ### 仕様変更後 正直仕様が複雑すぎて、PHP側の処理とSQL側の処理を共通化して書くとか無理です...同じ動作をするように力技でSQLを書くしかないかと思います。 全然動作確認してないので、このまま正しく動くかどうかはわかりませんが...joinとかDB::rawのサブクエリを使えばどうにかできるかと思います。 ``` class Item extends Model { public function myPrice () { if (\Auth::User()->role == 'admin') { $result = $this->master_price; } else { $data = $this->prices()->where('user_id', \Auth::User()->id)->first(); $result = (is_null($data) ? $this->master_price : $data->price); } return $result; } public function prices() { return $this->hasMany(Price::class); } public function scopeOrderByMyPrice($query) { if (\Auth::User()->role == 'admin') { $query ->select( 'items.*', // itemsテーブルのカラム全てと 'items.master_price AS my_price' // master_priceをmy_priceとして追加 ); } else { $query ->join(\DB::raw('(SELECT item_id, price FROM prices WHERE user_id = :user_id) AS prices', ['user_id' => \Auth::User()->id]), 'prices.item_id', 'items.id') // pricesからuser_idの一致するレコードを引き出し、itemsのidと紐付ける ->groupBy('prices.item_id') // pricesのitem_idでグルーピングすることでpricesの結合を1件に絞り込む ->select( 'items.*', // itemsテーブルのカラム全てと 'COALESCE(prices.price, items.master_price) AS my_price' // joinしたpricesのpriceがあればpriceを、なければmaster_priceをmy_priceとして追加 ); } return $query->orderBy('my_price', 'asc'); // my_price順でソート } } ``` 使い方は仕様変更前と同じです。Model内の実装だけが変わります。