【Laravel】Model::where()のような書き方ができる理由

2023/02/12に公開

よくmodel::whereのようなコードを見かけるが、Modelのドキュメントにはwhereは載っていないです。
ではなぜ正常に実行することができるのか。

https://laravel.com/api/8.x/Illuminate/Database/Eloquent/Model.html

laravel8のコードリーディングを軽くしていきたいと思います。
https://github.com/laravel/framework/tree/8.x

まず
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Eloquent/Model.php
でwhereメソッドの定義がされていないことを確認する。

https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Eloquent/Model.php#L2142-L2145
ここでマジックメソッドの __callStaticが定義されている。
定義されていないクラスメソッドを実行しようとすると呼び出される。
modelのインスタンスを生成して、whereを実行しようとしていますが、whereは定義されていないので、
__callに処理が移ります。
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Eloquent/Model.php#L2122-L2133

調べた結果whereは以下の分岐を通ることになります。
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Eloquent/Model.php#L2132

Traitsに定義されているforwardCallToを読み進めていきます。
まず引数に渡しているのが、
$this->newQueryとメソッド名とそのパラメータ。
newQueryメソッドは
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Eloquent/Model.php#L1324-L1332
\Illuminate\Database\Eloquent\Builderを生成してくれています。

生成した\Illuminate\Database\Eloquent\Builderインスタンスに対して
whereを実行します。

https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Eloquent/Builder.php


https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Eloquent/Builder.php#L274-L285
にwhereが定義されていました!

>where('id','=','1')

を指定して、elseを通るとします。
queryプロパティには
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Eloquent/Builder.php#L33-L38
\Illuminate\Database\Query\Builder がインスタンス生成時に代入されています。
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Eloquent/Builder.php#L133-L136

つまり最終的に
\Illuminate\Database\Query\Builder のwhereメソッドが実行されます。
https://github.com/laravel/framework/blob/d3ff07dbb343b7aaaef2cc623cc32516d8551a68/src/Illuminate/Database/Query/Builder.php#L699

またfirst等でクエリ結果を取得する際のことを考えます。
\Illuminate\Database\Eloquent\Builderで以下のtraitがuseされています。
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Eloquent/Builder.php#L29

Illuminate\Database\Concerns\BuildsQueries (trait)でのfirstの定義
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Concerns/BuildsQueries.php#L286-L295

\Illuminate\Database\Eloquent\Builderでtakeは定義されていないので、
__callマジックメソッドが実行される。
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Eloquent/Builder.php#L1660

\Illuminate\Database\Query\Builderのtakeが実行され、
\Illuminate\Database\Eloquent\Builderの\Illuminate\Database\Query\Builderインスタンスが更新される。ここで取得したデータのレコード数が1になる。
https://github.com/laravel/framework/blob/d3ff07dbb343b7aaaef2cc623cc32516d8551a68/src/Illuminate/Database/Query/Builder.php#L2146-L2154

\Illuminate\Database\Eloquent\Builderでgetは定義されており、
1つのModalが入った\Illuminate\Database\Eloquent\Collectionインスタンスを返してくれる。
https://github.com/laravel/framework/blob/d3ff07dbb343b7aaaef2cc623cc32516d8551a68/src/Illuminate/Database/Eloquent/Builder.php#L596-L614

\Illuminate\Database\Eloquent\CollectionはIlluminate\Support\Collectionを拡張して作られているため、fisrtを使用してコレクションの最初の要素を取得しています。

上記のようなことから
例えば

Player::where('id','=','1')->first()

のような簡単なコードでも、上記につらつら書いたような動作をlaravelが内部でやってくれています。

このコードリーディングを通して得た理解としては、以下のような感じ。
Illuminate/Database/Eloquent/Model

Illuminate/Database/Eloquent/Builder
(\Illuminate\Database\Query\Builder をラッピングしている、プロパティとして持っている)

Modelには
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Eloquent/Model.php#L1314-L1322
のように明示的にIlluminate/Database/Eloquent/Builderを生成してくれるクラスメソッドがあるので 、

個人的には

Player::where('id','=','1')->first()

と書くよりも、

Player::query()->where('id','=','1')->first()

と書く方が好きです。

Discussion