LaravelでのDBレコード取得と暗黙の結合について【ルートモデル結合】【モデル結合ルート】

今回の記事

Laravelでのデータ取得の基本に触れながら、暗黙の結合について触れていきます。ルートモデル結合モデル結合ルートとも呼ばれる技術になります。

暗黙の結合は便利な機能ながら、これを前提として実装されているLaravel入門の記事をみました。
もちろん便利機能なので活用することは大切だとは思うのですが、DBレコードの基礎的な部分についての理解がないまま利用していると、何が起こっているか分からないと思います。また、暗黙の結合を利用しない実装が必要になることもあるので、今回はデータ取得に焦点を置いて記事を書いていこうと思います。

データ取得のためのModelメソッド

LaravelではDB関連の処理をModelクラスを用いて行います。
その中でもデータ取得のメソッドとして覚えておくべきメソッドを3つ紹介します。

get()

一番オーソドックスなデータ取得メソッドとなります。
「覚えておくべきメソッドを3つ」と先ほど記述しましたが、極論これだけで実装は済むのでこれを覚えておけばOKと言っても差し支えないと私は考えます。

単純にデータが取得できるメソッドですが、特徴としては「1件であっても複数レコードの取得であってもN件の取得」として結果が返ってくるという部分になります。

Modelクラスは基本的に1レコードに対して1インスタンスが取得されます。
getメソッドは複数のレコードを取得することを前提としているため、複数のインスタンスが結果として返ってきます。この複数のインスタンスを扱うために返却のデータ型は「Collection」という配列の応用版のようなデータ型で返却されます。
これは結果が1件であっても同じです。複数のデータを扱うためのCollectionの中に1件のインスタンスが格納されている形式で結果が返ります。主キーでの条件設定など、確実に1件しかレコードが返却されない場合はでもCollectionになってしまいますので、後述の1件取得のメソッドを使用したほうが効率の良いソースコードになります。

first()

こちらもデータ取得を行うメソッドですが、firstの名前の通り「最初にヒットした1件」のみを返却します。
そのため、結果のデータ型がModelクラスとなります。1件のみ返却するためModelクラスのインスタンスで返却することが出来るわけですね。

そのため、条件指定などを行って1件の取得に限られる場合に関してはfirstで取得すると効率の良いソースコードが書けます。
取得結果が1件であることが決まっているのにgetで取得した場合はcollectionからインスタンスを取り出す記述が必要になってしまいますからね。

find(値)

最後にfindになります。こちらもデータを1件取得するために利用しますが、主キー(Pkey)を指定して取得するために使用されます。Laravelでは主キーを「id」カラムにすることが基本なので、idを指定したレコード取得に使われると覚えてしまって問題ありません。引数が取得対象の値になるため、「find(1)」という記述の場合は「id=1」のレコードが取得されます。

主キーでのデータ取得は結果が1件になるので、こちらも結果はModelクラスのインスタンスとなります。

データ取得の際によく一緒に使われるメソッド

続いて、getやfirstと一緒に利用されることの多いメソッドを紹介します。

where(カラム名, 比較条件, 値)

こちらはデータ取得の条件を指定するために使用します。SQLのwhere句に相当します。複数続けて書いた場合には「and」条件として処理されます。
今回は説明を省いてしまうのですが、「or」条件がなかなか使いにくいものになっているのでor条件でのデータ取得をする際は記述に注意しましょう。

使い方は引数に「検索に使用するカラム名」「値の比較条件」「検索に使用する値」を設定します。例えば

where('level', '=', 10)

と記述した場合には「levelカラムの登録値が10」という条件がデータ取得条件として設定されます。もちろん

where('level', '>', 10)

のように記述すれば「10よりも大きい」といった指定も可能です。複数の条件や取得メソッドを組み合わせると

where('level', '>', 10)->where('level', '<', 20)->get();
where('level', '>', 10)->where('level', '<', 20)->first();

のような記述になります。

whereメソッドを重ねているので「levelが10より大きく、かつ20未満」のレコードが取得対象になります。

条件は全く一緒ですが、getとfirstで取得メソッドが異なります。getの場合は条件に合致する全てのレコード、firstの場合は最初に合致したレコード1件の取得になります。「最初に合致」の「最初」の決まり方についてはこの後のメソッドで説明します。

orderBy(カラム名, 並び順) ※並び順はasc(昇順)かdesc(降順)

続いてデータの並び順を指定するorderByメソッドになります。
第一引数に並び替えに使用するカラム名を指定し、第二引数に並び順を「asc(昇順)」か「desc(降順)」で指定します。未指定の場合は「asc(昇順)」となります。

特にfirstの時は1件目のみを取得するため、疑似的に取得する1件を指定できます。(複数件の取得が考えられる場合にfirstを利用したいパターンは少ないかもしれませんが。。。)

getにおいても新しいものを上に持ってきたい場合やその逆、最近更新された順に表示したい、名前順に表示したいなど様々な活用があります。

画面に機能におけるデータ取得について

世の中には多くのアプリケーション、システムが存在しており、画面の構成や機能は様々です。しかし、ざっくり行ってしまえば「データの一覧表示」と「詳細表示」を中心に画面が構成されています。これらの表示をどのように見せるかによって特徴的なアプリケーションや機能として提供されているわけですね。

一覧表示でのデータ取得

まずはデータの一覧表示について考えます。動画サイトやショッピングサイトでの検索結果画面などがこれに当たります。

「一覧」という名前の通り、複数のレコードの情報を一覧表示しています。そのため使用するメソッドはget()になります。
ここに様々な条件を加えてデータの検索を行うわけですね。
get()メソッドはcollectionでの取得になるため、一覧表示画面の実装では「foreach」などを利用してループ処理を作成し取得したすべてのレコードに対して同じ出力処理を実行することがよくある実装パターンになります。

詳細画面でのデータ取得

詳細画面については特定のレコードを指定して画面表示を行うことになります。

特に多い実装例としては一覧画面からレコードを1つ選択して詳細画面に遷移するような画面遷移になります。この際、詳細画面を開くには対象となる特定のレコードを識別する必要があります。どのレコードを取得するかの条件を受け取らないといけないため、レコードを識別できる主キーの値を渡すことがほとんどになります。

ルートパラメーターに主キーの値(Laravelではほとんどの場合idとなる)を仕込んで詳細画面を表示し、そのidの値でレコードを取得し画面を表示するような実装になります。

暗黙の結合(ルートモデル結合)(モデル結合ルート)について

続いて暗黙の結合についてです。
これは「便利機能」なので使わなくても問題ありません。主に上記の詳細画面で使用されます。

まずはルートパラメータの仕組みについて確認していきます。

// routes/web.php
Route::get('user/{user}', UserController::detail);

// UserController
public function detail($user){}

例えば上記の通り、ルーティングとコントローラーのメソッド定義が行われていたとします。URLには{user}としてパラメーターを仕込んでいるため、URLの該当部分を変数として扱うことができます。変数部分を値として受け取るために、対応するコントローラーのメソッドにパラメーター名と同じ名前で引数を定義するのを忘れないようにしましょう。

上記の例では「/user/1」や「/user/2」というURLでdetailメソッドへルーティングが可能になっています。また、userという名前でルートパラメータとコントローラーメソッドの引数を統一しているため、コントローラーメソッドではURLの「1」「2」といった値を$userという引数で受け取ることができます。
これを用いて先述の詳細画面でのデータ取得を行います。

さて、一方で暗黙結合の場合はどうなるのでしょうか。

// routes/web.php
Route::get('user/{user}', UserController::detail);

// UserController
public function detail(User $user){}

ソースとしては上記のようになります。
違いはたった一か所で「引数の前にクラス指定を行う」だけです。この時に指定するクラスは、取得したいModelクラスになります。

今回はUserというModelからデータを取得することを例にしてみました。
暗黙の結合を使用しない場合は、ルートパラメータから取得できるidを用いて

User::find($user);

のようにデータの取得処理をおこないます。
しかし、暗黙の結合を行う場合はこの取得処理の記述を省略することができます。

暗黙の結合を行っていない場合、この$userはただの文字列(URLから取得できるもに限るため)ですが、暗黙の結合を行うことで指定したModelクラスを経由してデータを取得し、対象のクラスのインスタンスを返却します。

これが暗黙の結合を用いた実装になります。データ取得処理の記述が不要となるのでコードは短くなりますね。

以上が一般的なデータの取得処理と暗黙の結合についてのお話になります。
非常に便利な機能である一方で「何が行われているか」が曖昧な理解のまま進んでしまう機能の1つでもあると思っています。データ取得処理が暗黙の結合だけでなんとかなるようなシステムはないと思いますので、一般的なデータ取得処理も理解しておきましょう。