はじめに
しばらく前に「良いコード/悪いコードで学ぶ設計入門」を読んで大変刺激を受け、コード設計というものに意識を向けるようになりました。
実践の一環として、上記書籍にも書いてあった「NULLを返さない」を実践しようとしたところ、表題の件ではまったので書き残します。
※記載事項に誤りなどあれば、コメントでのご指摘・編集リクエストをいただけますと幸いです。
本題
以下2本のeloquentにおいて、取得できるレコードがなかった場合、何が返されるか?
// 1)
$result = User::select('id')->get();
// 2)
$result = User::select('id')->first();
結論
- 1の
get()
は空のコレクションを返す - 2の
first()
はNULLを返す
ちょっと細かく見ていく
環境情報
- php 8.1
- laravel 9.19
実際に実行してみると
レコード0件のテーブル"users"に対して、それぞれのメソッドを実行、
その返り値$resultを様々な判定文で判定してみる。実行結果はコメントとして記載した。
// 1)
$result = User::select('id')->get();
Log::info('value:' . json_encode($result)); // value:[]
Log::info(is_null($result) ? 'true' :'false'); // false
Log::info(isset($result) ? 'true' : 'false'); // true
Log::info(gettype($result)); //object
Log::info($result->isEmpty() ? 'true' : 'false'); //true
型の判定結果がオブジェクト、かつisEmpty()
が実行できているのでコレクションだと確認できる。
// 2)
$result = User::select('id')->first();
Log::info('value:' . json_encode($result)); // value:null
Log::info(is_null($result) ? 'true' :'false'); // true
Log::info(isset($result) ? 'true' : 'false'); // false
Log::info(gettype($result)); // NULL
Log::info($result->isEmpty() ? 'true' : 'false'); // ERROR: Call to a member function isEmpty() on null
こちらはわかりやすくNULLだと確認できる。
ドキュメント&実コードを見てみると
get()
Readoubleの記載は以下の通り、eloquentメソッドであること、返り値が明確に記載されている。
これまで見てきたように、allやgetのようなEloquentメソッドは、データベースから複数のレコードを取得します。ただし、これらのメソッドはプレーンなPHP配列を返しません。代わりに、Illuminate\Database\Eloquent\Collectionのインスタンスが返されます。
ソースコードは以下、返り値がeloquent配下のコレクションであることが明記されている。
/**
* Execute the query as a "select" statement.
*
* @param array|string $columns
* @return \Illuminate\Database\Eloquent\Collection|static[]
*/
public function get($columns = ['*'])
{
$builder = $this->applyScopes();
// If we actually found models we will also eager load any relationships that
// have been specified as needing to be eager loaded, which will solve the
// n+1 query issue for the developers to avoid running a lot of queries.
if (count($models = $builder->getModels($columns)) > 0) {
$models = $builder->eagerLoadRelations($models);
}
return $builder->getModel()->newCollection($models);
}
first()
ややこしかったのはこちらのほう。
Readoubleの上述のページには以下の記載がある。
取得結果があればモデルインスタンスを返すのは皆さんご存じのとおり。
特定のクエリに一致するすべてのレコードを取得することに加えて、find、first、またはfirstWhereメソッドを使用して単一のレコードを取得することもできます。モデルのコレクションを返す代わりに、これらのメソッドは単一のモデルインスタンスを返します。
しかし、get()
が定義されていた Eloquent\Builder.php にはfirst()
の定義が見当たらない...
そこでReadoubleで合わせて言及されていたfirstWhereの定義を見てみると...
別クラスのメソッドをを利用している。これかな?と確認。
/**
* Execute the query and get the first result.
*
* @param array|string $columns
* @return \Illuminate\Database\Eloquent\Model|object|static|null
*/
public function first($columns = ['*'])
{
return $this->take(1)->get($columns)->first();
}
ModelやNULLを返す...実行結果やドキュメントの記載と一致するので合っていそう。
(もし違うようであればご指摘いただければと思います)
おわりに
これまでget()
もNULLを返すものだと思い込んでおり、「NULLを返さないようにしなきゃ!」とis_null()
を大量に仕込んでしまいました...お恥ずかしい...
気になったら実際に試して確認してみることを習慣づけようと思います。