【Laravel】 Modelを使用してCRUDや外部結合をしよう
Laravelにおいて、データベースにアクセスする際はModelの活用が必須です。
モデルを使ってデータを取り出したり、変更してみましょう。
テーブルをまだ作成していない場合、先にマイグレーションしてテーブルを作成する必要があります。
Laravelのドキュメントにはモデルではなく、Eloquent ORM
に分類されています。
モデルの作成
1つのモデルは1つのテーブルと紐づきます。
まずはartisan
でモデルを作成します。
php artisan make:model Note
そうすると、app/Models/
内にモデルが作成されます。
モデル名はテーブル名を単数形かつPascalCaseにしたものにするべきです。
最初から入っているuse HasFactory;
は、テストデータを入れるためのものです。不要であれば削除して大丈夫です。
テーブルとの関連付け
モデルを作っただけでは関連付けされません。紐付けを行う必要があります。基本的には、テーブル名だけ設定すれば動作します。
class Notes extends Model { /** * モデルに関連付けるテーブル名 * * @var string */ protected $table = 'notes'; }
主キーは、デフォルトで符号なしBigIntegerid
が使用されます。id
以外やAUTO INCREMENTしないなどの場合、次のように設定します。(incrementing
はpublic
であることに注意)
class Notes extends Model { protected $table = 'notes'; /** * 主キーのカラム名と型の設定 */ protected $primaryKey = 'note_id'; protected $keyType = 'string'; /** * 自動増分するかどうか */ public $incrementing = false; }
コントローラからモデルを使う
モデルを作ったので、早速コントローラから使用してみましょう。まずはモデルをuse
します。
namespace App\Http\Controllers; use App\Models\Notes; // ...
Create
use
したら、まずはデータを書き込む処理を作ってみます。
データを書き込む際は、まずはモデルのクラスをnew
し、フィールド名をクラスのメンバのように扱います。セットする際は代入するだけでセット可能です。
適当にセットしたら、最後に->save()
すると書き込み完了です。
class HelloController extends Controller { public function index () { // ... } public function putNote() { $note = new Note; $note->content = "hogefugahige"; $note->version = 2; $note->save(); } }

created_at
とupdated_at
は、デフォルトでは自動的に書き込まれます。
Read
データを読み込む際は、様々な読み込み方が可能です。
全件取得
ModelClass::all()
で取得できます。データは配列のように扱うことができます。
ビューに出力する例では、all()
で取得したらそのまま@foreach
できます。
class HelloController extends Controller { public function index() { $notes = Note::all(); return view('index', compact('notes')); } }
@foreach($notes as $note) <p>{{ $note->content }}</p> <p>{{ $note->created_at }}</p> @endforeach
<p>hogefugahige</p> <p>2022-03-08 14:16:06</p> <p>foobarbaz</p> <p>2022-03-08 14:18:32</p>
コントローラでは、配列のようにforeach
などを使用できます。
$notes = Note::all(); $n = count($notes); // 取得した件数 foreach($notes as $note) { $aaa = $note->created_at; $bbb = $note->content; }
このように取得したデータは、モデルインスタンスと呼ばれています。
select
特定のカラムだけ取り出す場合、select
を使用します。select
しなかった場合はすべてのカラムを取得します。
$notes = Note::where('created_at', '2022-03-08 14:16:06') ->select('content', 'id') ->get();
プライマリーキーで取得
where
でも取得できますが、プライマリーキーである場合はfind(id)
を利用した書き方が可能です。
// idが1のデータを取得 $note = Note::find(1);
where
where()
を使用することで、SQLのwhere
と同等の事が可能です。
第1引数にカラム、第2引数に演算子(> >= = <= < <> like
等)、第3引数に値を入れます。
// ->get() を忘れないようにする $notes = Note::where('created_at', '>', '2022-03-08 14:17:00')->get(); $notes = Note::where('content', 'like', '%hoge%')->get(); // % をエスケープする場合は \\% とする $notes = Note::where('content', 'like', '\\%hoge\\%%')->get(); // %hoge で始まるものを検索
Note::where('created_at', '2022-03-08 14:18:32')
のように第2引数を省略 (つまり実引数が2つ) した場合、演算子が=
であるものとして処理されます。
一定件数取得
一定件数だけ取得したい場合、first()
やlast()
、limit(件数)
を用います。first
やlast
で取得した場合、コレクション(配列のようなもの)ではなく普通に単体で取得されます。
// 最初の1件を取得 $notes = Note::all()->sortBy('created_at')->first(); $notes = Note::where('created_at', '>', '2022-03-08')->limit(1)->get();
all
ではlimit
を使うことができません。all
ではEloquent\Collection
であり、Builder
ではないためです。
where
やlatest
などはクエリを組み立てるためにBuilder
が使用されるため、limit
を用いることができます。
ソート
ソートする場合はsortBy(カラム名)
やsortByDesc(カラム名)
を使用します。
または、latest
やoldest
を用います。
$notes = Note::all()->sortBy('created_at');
Update
更新したい場合、項目を取得した後、データのCreateのような感覚で値を代入し、save()
します。
$note = Note::find(2); $note->content = "updated hogehoge"; $note->version = 3; $note->save();
複数更新
当てはまるもの全て更新したい場合、update
を使用します。
update
の引数には連想配列を入れ、キーにはカラム名を、値には更新後の値を入れます。save()
の実行は不要です。
Note::where('version', 2) ->update(['content' => 'aaabbbccc', 'version' => 100]);
Delete
削除する場合、取得したレコードで->delete()
を実行します。
$note = Note::find(3); $note->delete();
複数件削除
where
で取得したもの全部を削除するなどの場合、同じように最後に->delete()
を付けます。
Note::where('version', 100)->delete();
ソフトデリート
もしソフトデリートをしたい場合、モデル側にuse SoftDeletes;
を追加します。デフォルトではdeleted_at
カラムが使用されます。
先にdeleted_at
カラムを用意しておく必要があります。マイグレーションでは、$table->softDeletes()
を用いるのが早いです。
use Illuminate\Database\Eloquent\Model; // useの追加を忘れないようにする use Illuminate\Database\Eloquent\SoftDeletes; class Note extends Model { // ソフトデリートを行う use SoftDeletes; // ... }
データの削除方法は、ハードデリートと同様に->delete()
するだけです。
ソフトデリートしたデータの復元
取得したデータに->restore()
を使用します。しかし、ソフトデリートされたデータはそのままではデータの取得対象とならないため、withTrashed()
をつけて取得します。
Note::withTrashed()->where('version', 100)->restore();
ソフトデリートしたデータをハードデリート
ソフトデリートを使用している場合にレコード自体を削除したい場合、ソフトデリートしたデータに対してforceDelete()
を実行します。
Note::withTrashed()->where('version', 100)->forceDelete();
データの複製
取得したモデルインスタンスにreplicate()
します。複製したデータで適当に書き換える等したら、最後にsave()
します。
$note = Note::find(2); // replicateでデータ複製 (idはデフォルトでは自動で更新される) $newNote = $note->replicate(); // 適当にデータ書き換え $newNote->version = 100; $newNote->content = 'hogefuga foober'; // 保存 $newNote->save();
fillでデータ設定
1つ1つのカラムで代入を書くのが面倒という場合、fill
を用いて一括で代入することができます。fill
には連想配列で入れ、キーには更新したいカラム名を、値はその値を入れます。
fill
を用いる場合、対象となるカラムをホワイトリスト形式でモデルに書いておく必要があります。
class Note extends Model { protected $table = 'notes'; /** * fillを用いて代入するリストをホワイトリスト形式で書く */ protected $fillable = ['content', 'version']; }
$note = new Note; $note->fill([ 'version' => 300, 'content' => 'abcde xyz', ])->save();
モデルに処理を書く
例えば、特定の条件を満たすデータを取得するという場合、毎回コントローラやサービス層に書くのでしょうか。データの更新命令や取得の色々をコントローラが行うのは、コントローラにデータベースのデータに関する責任をもたせていることになります。
データの取扱に関する様々なルールは、Modelに書くべきです。ただ、取得したデータをアプリに合わせて色々加工したりするのは、サービス層がするべきです。Modelには、システムに依存しない汎用的な処理を書きます。例えば、特定の引数だけ取ってそのままデータを更新する、外部結合してソートして取得する、などです。
モデルのインスタンス (要はNote::find(1)
などで取得したもの) は、そのままそのモデルのクラスのインスタンスです。そのため、モデルのクラス内で$this->id
や$this->created_at
のようにカラムを指定してデータを取得できます。
class Note extends Model { // ... /** * 最も新しいnoteを取得 */ static function getLatestNote() { return Note::latest()->first(); } /** * Noteの本文を更新 */ function updateNoteContent($content) { $this->content = $content; $this->save(); } }
class HelloController extends Controller { public function index() { // staticなものは Class::Method で呼び出せる $note = Note::getLatestNote(); return view('index', compact('note')); } public function updateNote() { $note = Note::find(6); // staticでないものは、モデルインスタンスから呼び出せる $note->updateNoteContent('foobar abcde'); } }
結合
外部結合等をしてデータを取得したい場合はよくあります。その場合、hasMany
とbelongsTo
を主に用います。
hasMany
は親から子のレコードに対して、belongsTo
は子から親のレコードを取得します。
例えば次のようなテーブル構造だったとします。

この場合、note
から所属するgroup
を取得したい場合、$note->belongsTo(Group::class, 'foreign_key', 'owner_key')->get()
のようにします。
第2引数を省略した場合、外部キーのカラム名はメソッド名から推測されます。例えば、メソッド名がgroup
なら、外部キーのカラム名がgroup_id
であるものとして処理されます。
第3引数を省略した場合、カラム名はid
であると処理されます。
そのため、メソッド名はgetGroup
ではなく、group
のようにするべきです。
use App\Models\Group; class Note extends Model { protected $table = 'notes'; static function getNote($noteId) { return Note::find($noteId); } /** * そのNoteが属するGroupを取得 */ function group() { // noteの`group_id`を使って、groupsテーブルの対応するレコードを取得 return $this->belongsTo(Group::class); // belongsTo(Group::class, 'group_id'、'id') のように外部キーのカラム名を明示できる } }
class Group extends Model { protected $table = 'groups'; }
$note = Note::getNote(1); // $note->belongsTo(Group::class, 'group_id')->first() でも取得できる $group = $note->group()->first();
逆に、group
が持っているnote
を取得したい場合、hasMany
かhasOne
を用います。複数件取得するならhasMany
です。第2引数には対象テーブルの外部キーのカラム名を、第3引数には自身のテーブルの外部キーで参照されるカラム名を入れます。
第2引数を省略した場合、自身のテーブル名単数形+_id
であると処理されます。下の例ではgroups
テーブルなので、外部キーのカラム名はgroup_id
であると推論されます。
第3引数を省略した場合、カラム名はid
になります。
use App\Models\Note; class Group extends Model { protected $table = 'groups'; /** * このGroupに属する全てのNoteを取得 */ function notes() { return $this->hasMany(Note::class); // hasMany(Note::class, 'group_id', 'id') のようにキーを明示しても良い } }
$group = Group::find(2); $notes = $group->notes()->get();
タイムスタンプの有効無効
デフォルトでは、created_at
とupdated_at
を用いた自動的なタイムスタンプの記録が有効化されています。
もし使用しない場合は、
class Notes extends Model { public $timestamps = false; }
のようにします。
別のコネクションを用いる場合
app/config/database.php
には、データベースのコネクション情報が入っています。特に指定しない限り'default' => env('DB_CONNECTION', 'mysql'),
のところに書かれている設定が使用されますが、モデル単位で別のコネクションを使用したい場合、モデルに$connection
を追加します。
class Note extends Model { // app/config/database.php にあるコネクション名を指定 protected $connection = 'sqlite'; }
まとめ
LaravelのModelはデータベースのテーブルと直結しています。直接SQLを書くことはまず無く、モデルを利用して抽象化された書き方で書き込みます。
特に外部結合を簡単に書けるのは大きなメリットです。他にも多対多の関係も処理できたりしますが、まずはhasMany
とbelongsTo
の親子の向きを覚えて使えるようになりましょう。
モデルは単にORMを使用してSQLを書かずに済ませるものではありません。MVCやその他アーキテクチャにおけるモデルの役割を知って使うと、良い設計を行うことができます。
モデル操作ではトランザクションが必要不可欠なので、そちらの方法も確認しておくことを推奨します。
