【Laravel5.7】LaravelでSELECT対象カラムはどこに書けばいいのか

一覧画面では代表的なカラムのみ取得、詳細画面では全カラムを持ってくる、みたいなことがしたい場合、SELECT句はどこに書けばいいのかという話。
私が知らないだけで、定石みたいなのが既に存在しているのであればごめんなさい。

調査対象はLaravel5.7です。

# 場所

## コントローラ

クエリビルダの[マニュアル](https://readouble.com/laravel/5.7/ja/queries.html)に書かれているのがこの方法。
なんといってもわかりやすい。

```php:コントローラ
class HogeController extends Controller
{
    /**
     * 一覧を取得する
     * @param \Illuminate\Http\Request
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function list(Request $request)
    {
		return Foo::select(['id', 'name'])->get();
    }

    /**
     * 詳細を取得する
     * @param \Illuminate\Http\Request
     * @return Foo
     */
    public function detail(Request $request)
    {
		return Foo::find($request->id);
    }
}
```

これで一覧画面では`SELECT id, name`が、詳細画面では`SELECT *`が発行されます。

一番楽で便利ではあるのですが、気になるのはコントローラにカラム名を直接書いてしまってるところです。

DBのカラム名を変更したりした場合、呼び出している全箇所で変更しないといけないので大変です。
どうせViewで使ってたりするから何れにせよ修正は必要なのですが、まあ対象箇所は減らしたいですよね。


## モデル

Eloquentの[マニュアル](https://readouble.com/laravel/5.7/ja/eloquent.html)で、直接的ではないものの解説されている方法です。



```php:コントローラ
class HogeController extends Controller
{
    /**
     * 一覧を取得する
     * @param \Illuminate\Http\Request
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function list(Request $request)
    {
		return Foo::list()->get();
    }
}
```

```php:モデル
class Foo extends Model
{
    /**
     * list()を呼んだらscopeList()が呼ばれる
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @return \Illuminate\Database\Eloquent\Builder $query
     */
    public static function scopeList($query)
    {
        return $query->select(['id', 'name']);
    }
}
```


モデルに`scopeHoge()`というメソッドを実装すると、コントローラから`hoge()`で呼び出せます。
第一引数には自動的にクエリビルダインスタンスが入ってくるので、そちらを操作してreturnしましょう。
それだけでメソッドチェーンに組み込めるようになります。
メソッドに引数を渡したい場合は、`scopeHoge()`に入れると`hoge()`の第二引数以降に入ってきます。

やってることはコントローラに書いたのと同じですが、モデルに書いたことで一カ所に集約できました。
これによってテーブル構造が変更になったとしても、この一カ所だけ修正すればよくなります。

欠点としては、メソッド名が異なるしIDEも追ってくれないので、仕組みを知らなければどこがどう動いてるのかわかりにくいことでしょうか。
というかLaravelはこんなのばっかりで、組み込みメソッドすら何があるのか未だにわからない。


## visibleプロパティ

シリアライズの[マニュアル](https://readouble.com/laravel/5.5/ja/eloquent-serialization.html)で解説されています。

```php:コントローラ
class HogeController extends Controller
{
    /**
     * 一覧を取得する
     * @param \Illuminate\Http\Request
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function list(Request $request)
    {
		return Foo::all();
    }

    /**
     * 詳細を取得する
     * @param \Illuminate\Http\Request
     * @return Foo
     */
    public function detail(Request $request)
    {
		return Foo::find($request->id)->setVisible([]);
    }
}
```

```php:モデル
class Foo extends Model
{
    protected $visible = ['id', 'name'];
}
```

モデルに`$visible`というプロパティをセットしておくと、結果セットを`toArray()`などシリアライズした際に、該当のカラムしか出力されなくなります。
全カラムを取得したい場合は`setVisible([])`と空配列をセットしてキャンセルできます。
APIなど、さくっとJSONを返したい場合に便利です。

ただし、あくまで出力から消しているだけで、SELECT対象は`*`のままなので負荷軽減には役立ちません。
また`$foo->detail`のように対象カラムを直接指定すると見ることもできます。

同じように`$hidden`プロパティも存在し、こちらは隠しておきたいプロパティを指定できます。

`$visible`も`$hidden`も使いやすいかといったらちょっと微妙ですね。


## フロントエンド

GraphQLなんかだとフロントエンドでSELECT対象を指定できるわけですが……そんな権限フロントエンドに渡したいか？
どうせ

```php:コントローラ
	const ALLOWED_SELECT_COLUMN = ['id', 'name', …];
```

こんなことを書く羽目になるんだ。

まあたしかにPOSTで`column=id,name`とか受け付けるようなコードを書いたことはあるけれども。

# まとめ

自分ならモデルに書くかな。


# おまけ

一番最初の一覧を取得するやつ、実は`return Foo::all(['id', 'name']);`と書けます。
この書き方[マニュアル](https://readouble.com/laravel/5.7/ja/eloquent.html)にも載ってないし使ってる人も見たことがない。