【Laravel】Modelの::query()メソッドで快適なEloquent操作を

## はじめに
小ネタですが、LaravelのEloquentで地味に便利な機能の紹介です。
こんな感じで使われる`::query()`メソッドについてです。

```php
User::query()
    ->latest()
    ->limit(10)
    ->get();
```

## 前提
こちらの記事は以下のバージョンを想定しています。
- PHP・・・8.2
- Laravel・・・10.0

## ::query()を使うと何が嬉しいのか
`::query()`を使うと嬉しいポイントは以下の2つです。

1. コードの見通しがよくなる
1. エディタの補完が効く

### コードの見通しがよくなる
たとえばユーザーを取得するクエリを考えます。
- 通常のクエリ
```php
$users = User::when(request('name'), function ($query) {
    $query->where('name', 'like', '%' . request('name') . '%');
})->when(request('email'), function ($query) {
    $query->where('email', 'like', '%' . request('email') . '%');
})->get();
```

- `::query()`を使ったクエリ
```php
$users = User::query()
    ->when(request('name'), function ($query) {
        $query->where('name', 'like', '%' . request('name') . '%');
    })
    ->when(request('email'), function ($query) {
        $query->where('email', 'like', '%' . request('email') . '%');
    })
    ->get();
```

`::query()`を使った場合だと**クエリの制約が縦方向一つにまとめられて**見やすくなっているのが分かるかと思います。

このように条件が多くなったときにもその条件の位置が揃っていて分かりやすいです。
```php
$users = User::select('name', 'email')
    ->whereIn('address', request('address'))
    ->oldest()
    ->take(10)
    ->toArray()
```
```php
$users = User::query()
    ->select('name', 'email')
    ->whereIn('address', request('address'))
    ->oldest()
    ->take(10)
    ->toArray()
```

### エディタの補完が効く
Eloquentを使用してエディタの補完が効かない現象に見舞われたことのある方は少なくないと思います。
後述[余談](##余談queryを使う場合も使わない場合も呼び出されるのはilluminatedatabaseeloquentbuilder)でも触れていますが、`::query()`メソッドを使用することで新しく`\Illuminate\Database\Eloquent\Builder`が生成され、エディタの補完が効くようになります。

**※当方VS Codeを使用しています。**

- 通常だと補完が効かない😇
![ezgif-2-ee84596882.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884629/5c182020-fb12-e14d-06f1-063857b93c3b.gif)

- `::query()`を使えば補完が効く🎉
※VS Codeに[Laravel Extension Pack](https://marketplace.visualstudio.com/items?itemName=onecentlin.laravel-extension-pack)の拡張機能をインストールしています。
![ezgif-2-775bed1699.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884629/29403e03-a889-dbaf-48d5-c0e2e4a12c1f.gif)

## （余談）::query()を使う場合も使わない場合も呼び出されるのは\Illuminate\Database\Eloquent\Builder
`::query()`を使用すると`\Illuminate\Database\Eloquent\Builder`が呼び出されます。

```php:\Illuminate\Database\Eloquent\Model.php
...
/**
 * Begin querying the model.
 *
 * @return \Illuminate\Database\Eloquent\Builder
 */
public static function query()
{
    return (new static)->newQuery();
}
...
```

また、`::query()`を使わない場合は`\Illuminate\Database\Eloquent\Model.php`に定義されていないメソッドが呼び出されることになるので、`__call()`マジックメソッドによって`\Illuminate\Database\Eloquent\Builder`が呼び出されます。

:::note info
`__call()`は定義されていないメソッドが呼び出された時に呼び出されるphpのマジックメソッドです。
[メソッドのオーバーロード](https://www.php.net/manual/ja/language.oop5.overloading.php#object.call)
:::



以下の`forwardCallTo()`メソッドの第一引数に指定されている`newQuery()`で新しいBuilderを生成しています。
```php:\Illuminate\Database\Eloquent\Model.php
...
/**
 * Handle dynamic method calls into the model.
 *
 * @param  string  $method
 * @param  array  $parameters
 * @return mixed
 */
public function __call($method, $parameters)
{
    if (in_array($method, ['increment', 'decrement'])) {
        return $this->$method(...$parameters);
    }

    if ($resolver = (static::$relationResolvers[get_class($this)][$method] ?? null)) {
        return $resolver($this);
    }

    return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}
...
```

## まとめ
好みの域を出ない話題かもしれないですが、私は初めてこの記述を見て何これ？となったので知っておいてもよい内容かと思います。

## 最後に
#### GoQSystemでは一緒に働いてくれる仲間を募集中です！
ご興味がある方は以下リンクよりご確認ください。

https://goqsystem.recruit.site/?qiita=2279bb947ec4e7b103b2