■
Laravel Eloquentで取得したデータの状態を意識してなかった件
今までDBファサードでデータの取得をしてましたが、最近はすっかりEloquentでデータを取ったり入れたりするようになりました。が、チェーンメソッドをごりごり書く際に上手くデータが取れない時があって、なんで?ってなる事がしばしばでしたが、データの状態をちゃんと意識しないで感覚と経験則と実装時のTry&Error でなんとかしちゃってました。 でも、この辺をちゃんと意識すべきだなと。
経験のある人から見ると「今それ?」みたいな話かもしれませんが、つまりこれです。
まずお馴染みのModelがあったとします。
<?php namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model { protected $table = 'users'; // リレーションとか scope とかのメソッドが続く }
それを use App\User; して使って他のクラス内で使える様にする訳ですが、 ごく単純に使うならまずこうですよね。
$user = User::find(1);
DBなら、user tableの主キーとなる id=1 のレコード1件を抽出します。 あとこんな風にも使います。
$user = User::find(1)->get();
とにかく欲しい。これでデータ取得、みたいな時ですね。
また、ちょっと違った条件で呼び出したい際はこんな風に呼んだりもします。
User::where('community_id', 1);
DBで考えると users table の community_id カラムの値が 1の奴だけ、 つまりコミュニティ1に属するユーザーだけを複数あれば取得、みたいな感じですね。
当然こいつもgetして使う訳です。
User::where('community_id', 1)->get();
で、個人的によく使うのがこのパターンで、複雑な階層下のtableなんかを呼ぶ際は、外部キーをベースに値を呼び出して使う。なんていうのがある訳です。
User::where('community_id', 1) ->LeftJoin('comments', 'comments.user_id', '=', 'users.id') ->where('comments.active', 1) ->get();
こんな風にアクティブな該当ユーザーのコメントのみを出す、みたいなニュアンスでクエリビルダゴリゴリ書いて使う感じですね。(リレーションメソッド使えって話もありますが)
で、こんな感じでいろんな形式でデータを取得するわけですが、なんか上手くいかないなー。って場合があって、その原因がわかりました。
$user = User::find(1); dd(get_class($user));
ってクラス名を出力したら、最初に紹介した奴、それぞれ違うんですね。
$user = User::find(1); dd(get_class($user));
これは App\User class と出力されます。
つまり、これはまだ只のModelクラスのインスタンスにすぎません。
次にこれ、一件やっている事は上と同じですが、インスタンスされるクラスが異なるんですね。
$user = User::where('id', 1); dd(get_class($user));
Illuminate\Database\Eloquent\Builder class が出力されます
dd($user)
って出力すると量が多くてヤバイ奴です。
Laravelやってる人は経験あると思います。dd() の際うっかりすげえ量のオブジェクトが出力されてマシンがうなってなかなか画面表示されない。って奴ですね。
しかし、これらに ->get()
を付与するとさらにクラスが変わります。
$usr = User::find(1)->get(); dd(get_class($user));
Illuminate\Database\Eloquent\Collection class と出力
つまりコレクションクラス(配列っぽく便利に使えるアレ)のインスタンスとして出力されます。
$usr = User::where('id', 1)->get(); dd(get_class($user));
これも同様、 Illuminate\Database\Eloquent\Collection class と出力
コレクションクラスのインスタンスとして出力されます。
つまり ->get() を付けたら実体化してコレクションとして加工や出力に便利に使える状態に変換される訳ですね。
ところがややこしいのはこれ、 ->first()
です
ひとまずこの条件で絞り込んで、最初の1件だけを取得する。なんていうときに使います。私は曖昧なデータの取れ方をするのが嫌であまり使わないですが。
$user = User::where('created_at', '>', Carbon::toDay())->first(); dd(get_class($user));
これ App\User class と出力されます。
コレクションでもなくまだピュアなモデルなのかよ!って感じです。
でもよく考えたら、 find(1)
と同じ扱いですね。
でもこれらはとても、混乱しがちで間違いがちなんですよね。
first() もごりごりクエリビルダ書いた最後に付けることが良く多くて、取れるデータとして確定するニュアンスなので。 ->get()
と同じ認識でいましたが、インスタンスが異なる為、本質も異なるんですね。
なので、余談ですが first() の後には ->get() が付けられて、コレクションインスタンスに変換が出来ます。使い道が今一つわかりませんが。
$user = User::where('created_at', '>', Carbon::toDay())->first()->get(); dd(get_class($user));
Illuminate\Database\Eloquent\Collection と出力
あとこれ、当たり前ですが最後に ->toArray()
を付けるといずれも完全な配列となります。
User::find(1)->toArray(); User::find(1)->get()->toArray();
普通の配列データになる
私はこれで必要なIDの配列を取得して、別のModelを呼ぶ際のwhereIn
の条件にして使うことが多いです。TOP階層から5階層配下(鬼のようなDB構造...)のtableのリレーションでの値を出す必要があったりして、そんな場合に使います。
また、実際のアプリケーションで複雑な検索条件に対応する際の方法として、一度、単純なクエリ find() や where() でデータを呼び出して、get() を繋げないて$query
みたいな変数に入れ、それをさらに様々な処理を経由して条件追加をチェーンメソッドで繋げて、最後に ->get()
みたいな使い方をします。
以下のような感じ。
// よく使う例 public function getUser(Request $request) $query = User::find($request->id); if (!is_null($request->date)) { $query->where('date', $request->date); } if (!is_null($request->name)) { $query->where('name', 'like', "%{$request->name}%"); } return $query; }
で、この関数の呼び出し元で
public function index(Request $request) // 上の関数を呼ぶ $query = this->getUser($request); // この辺に $query->を使ってさらに色々処理を繋げたり加工したり // で、最後にこれをしたりしなかったりして $user = query->get(); // 出力 return view('index', [ 'user' => $user ]); }
みたいな感じ。
ですが、その際にこの辺のデータも持ち方の違いをきちんと意識してないと、なにがなんだかわかんなくなる。という事がありますので、現在のデータの状態を意識しつつ、クエリビルダを繋げる必要があるな。と感じた次第。
でも、この辺、有志のリファレンスサイトEloquent:利用の開始 5.8 Laravel見るとちゃんと書いてあるんですよね。
データの取得の方法論ばかり意識していて、これまではどのクラスのインスタンスの状態であるか?というのを意識してなかった。そういえばdd()で出す際になんか違うな―。位の認識しかしてないという…。
余談ですが、ここに意識が向かったきっかけですが、最近チームで実装をするようになり、後から私のコードを読む人の為に、わかりやすいコードを意識するようになり、メソッドにきちんとアノテーションを書くことも始めました。(最近かよ!)
とはいえ、けっこう俺流で書いてしまっていたのですが、これを書くことで起こった良い変化として、引数にはどんな型のデータが必要で、返り値のデータの型は何なのか?というのを意識的に統一して書くようになりました。
その際の返り値に、じゃあクラスのインスタンスとしては何を書くべきなの?って時に、あれ?ってなってこの記事に至ります。
しかし、返り値については、これまでは単純な配列や数値等、あと、Modelで書いたら、全部内容同じでしょ、位にしか認識してなかったので、今後は返り値が何のClassのインスタンスなのかをきちんと意識して実装できるようにします。