はじめに
本記事は、下記記事にて構築した環境で開発しております。
また、本記事はLaravel公式ドキュメントを参考にしています。ぜひそちらをご覧になりながら、開発を進めていただければと思います。
本記事のゴール
Laravel12でモデルクラスを作成すること
とてもざっくり言うと、新たなテーブルを作成することが本記事のゴールとなります。
ちなみに、ここで作成するモデルを用いてログイン認証を行うことを見据えています。
今回はその準備にもなっておりますので、最低限よりは少し過剰な実装となります。
デフォルトのログイン認証
Laravelは、普通にプロジェクトを作成するとUser
モデルが作成されます。デフォルトでは、このモデルを使ってログイン認証が行われます。
しかし、開発する規模が大きくなっていけば、以下のような壁が出てきます。
- 自分で定義したモデルでもログイン認証をしたい
- 何なら自作モデルをデフォルトの認証モデルにしたい
本記事ではこれらを行うことを見据えて実装していきます。
モデルの作成・マイグレーション
なにはともあれ、モデルを作成します。
モデルの設計
今回は以下のような属性を持つモデルを考えます。
カラム名 | 型 | 備考 |
---|---|---|
id | BigInteger | 主キー |
user_code | string | ログインIDとして利用,自動生成,ユニーク制約有り |
user_name | string | 表示ユーザ名,ユーザが自由に設定可 |
password | string | パスワード,ハッシュ化して保持 |
string | メールアドレス | |
email_verified_at | timestamp | メール認証日時の保持 |
テーブル名(モデル名)はMember
とでもしましょう。
これに則ってモデルを作ります。
モデルの作成
今回は、以下のコマンドを実行します。
php artisan make:model Member -mscrR
コマンド実行後、以下のファイルが作成されていることを確認してください。
app/Http/Controllers/MemberController.php
app/Http/Requests/StoreMemberRequest.php
app/Http/Requests/UpdateMemberRequest.php
app/Models/Member.php
database/migrations/yyyy_mm_dd_xxxxxx_create_members_table
また、コントローラーの中には、以下の7つのメソッドが定義されていると思います。
- index
- create
- store
- show
- edit
- update
- destroy
リソースコントローラに対するお馴染み7つのメソッドですね。
テーブル構造が決まったところで、モデルとなるテーブルをDBに作成したり、モデルの設定を行ったりしていきます。マイグレーションファイルを作成してテーブルを定義したり、モデルファイルを作成してモデルを定義したり…という工程です。
- マイグレーションファイルの作成/マイグレーション実行
php artisan make:migration create_hoge_table
- モデルファイルの作成/モデルファイルの編集
php artisan make:model Hoge
このように、それぞれのファイルを都度作成して記述していくことが可能です。今回のように新たなモデルを作るときに利用する他、マイグレーションファイルに関しては既存のテーブルのカラム追加・削除・変更を行うような場合にも利用します。結局のところ、テーブル定義に関する操作はマイグレーションを通して行うのです。
しかし今回は、モデルもテーブルも、どちらも定義しなければいけません。だったら、マイグレーションファイルとモデルファイルは一緒に作れたほうが、直感的な気がします。
さらに、先を見据えると、このMember
モデルはCRUD(作成/閲覧/更新/削除)機能が実装されるとも考えられます。こういった基本機能に関しては、設計段階で必要かどうか分かることでしょう。こういった機能を作るとき、Ruby on Railsだとイメージしやすいのですが、リソース(resources)を利用するのがとても効率的だと思います。
Laravelでは、モデルファイルを作成するときに、これらを一括して作成・定義することができるのです。詳細は公式ドキュメントを御覧ください。
これを行っているのが、今回実行するコマンドです。モデルファイル作成時にオプションをつけることで、他の関連ファイル作成や最低限の記述をしてくれます。
// -m: マイグレーションファイルの作成, -s: シーディングファイルの作成
// -c: コントローラの作成, -r: resourcesクラスの作成, -R: リクエストファイルの作成
php artisan make:model Member -mscrR
今回はマイグレーションファイルに加えて、シーディングファイルとコントローラ・リクエストファイルも同時に作成しています。ニュアンスとしては以下のとおりです。
- シーディングファイル:テーブルに最初から登録しておくデータを作成する
- コントローラ:あるURLにアクセスされたときの処理をする
- リクエスト:フォームから飛んできたデータに処理を加える
今は無視しちゃってよいです。とりあえず。
これらがあって、いよいよフレームワークっぽい挙動が出来上がってきます。
モデルの設定
作成したモデルに対して、入力やキャスティングに関する規約を設定していきます。
class Member extends Model
{
protected $fillable = [
'user_name',
'password',
'email'
];
protected $hidden = [
'password'
];
public function casts(): array
{
return [
'password' => 'hashed',
'email_verified_at' => 'datetime'
];
}
}
これらにより、以下の規約がモデルに追加できます。
- 一括挿入できるカラム
- シリアライズ時に隠すカラム
- キャスティングするカラムとキャスト先の型
$fillable
レコード作成時に一括挿入できるカラムを制限するための配列です。
モデルは、テーブル作成時にデータを挿入するだけでなく、フォームによる新規作成や更新がされることもあります。このとき、やろうと思えば「フォームに入力欄はないけど、データ送ってやろ」ということだってできます。
そういった予期しないカラムへのデータ登録を防ぐための配列となります。
ちなみにレコード作成時に一括挿入を禁止するカラムの設定は$guarded
となります。
公式では「全てのカラムを一括挿入OKにするときは、これを空の配列として設定してね」と言っています。基本的には$fillable
で設定した方が直感的ですし、推奨っぽいですね。
では、「一括挿入しちゃダメなカラムにはどうするのか?」という話ですが、デフォルト値設定 or カラムへの直接代入の2つの方法が考えられます。
- デフォルト値設定
後述のマイグレーションファイル設定時にデフォルト値設定をする。 - カラムへの直接代入
$model_object->column = 'value'
のようにモデルオブジェクトに対して直接代入
データをプログラム側で自動生成するような場合は、後者の対応が必要になります。今回のMember
モデルでは、user_code
をその対象としています。
$hidden
シリアライズ文字列に入れないカラムを設定するための配列です。
モデルオブジェクトをサーバから送信するようなとき、JSONや文字列として送ることになります。その中にパスワード等の「見えちゃマズいデータ」が入っていたら洒落になりません。これを防ぐための配列となります。
function casts()
モデルの属性⇔実際にDBに格納される値の変換を行うカラムと変換方法を設定するためのメソッドです。正直、私はあまり良い説明が思いつかないです。すみません。
パスワードを入力するとき、私達は単なる文字列として入力し送信します。これをそのままDBへ登録するのは危ない、というのは現代における常識です。そのため、モデル属性として送られてきたパスワードをハッシュ化してDBへ登録するのが定石となります。このハッシュ化をDB登録時に勝手に行ってくれるようにするため'password' => 'hashed'
という記述が役立つのです。
日時に関してのフォームを送信すると、内部的には文字列として扱われることもあります。これをDBに特に苦がなく登録できるようにするため、また、DBから取り出した日時データを適切にdatetime型として取り扱うため'email_verified_at' => 'datetime'
という記述が役立つのです。
実際にやってくれる変換についても公式が挙げてくれているので、実際に作るときはこれを見ながら、試しながらやってみると良いと思います。
マイグレーション
DBにモデルの実体を作ります。作成されたマイグレーションファイルのupメソッド
の記述を追加します。
public function up(): void
{
Schema::create('members', function (Blueprint $table) {
$table->id();
$table->string('user_code')->unique()->nullable(false);
$table->string('user_name')->nullable(false);
$table->string('password')->nullable(false);
$table->string('email')->nullable(false);
$table->timestamp('email_verified_at')->nullable(true);
$table->timestamps();
});
}
マイグレーションファイルの記述ができたら、マイグレーションを行います。テーブルの作成です。
php artisan migrate
マイグレーションファイルでは、カラムの追加だけでなく、各カラムに対して型や制約を設定できます。
カラムの型設定(ごく一部)
-
string(column_name)
:文字列型のカラムcolumn_name
を作成 -
timestamp(column_name)
:操作が行われた日時を保持するカラムcolumn_name
を作成 -
integer(column_name)
:整数型のカラムcolumn_name
を作成
カラムの制約設定(ごく一部)
-
unique()
:カラムにユニーク制約を追加 -
nullable(bool)
:カラムに非null制約を付けるかどうか設定(false→null NG) -
default(value)
:カラムのデフォルト値を設定
シードデータの作成
テーブルにデフォルトで置くレコードを作成します。シーディングファイルに追記していきます。
/* add writing */
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\DB;
class MemberSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
/* add writing */
DB::table('members')->insert([
'user_code' => Str::random(3) . random_int(100000, 999999),
'user_name' => 'Taro',
'password' => Hash::make('password'),
'email' => 'test@example.com'
]);
/* add writing end */
}
}
また、同ディレクトリにあるDatabaseSeeder.php
のrunメソッド
の内容はごっそり削除してしまって構いません。
<?php
namespace Database\Seeders;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
$this->call([
MemberSeeder::class,
]);
}
}
そしてシーディングを行います。
php artisan db:seed
これでmembers
テーブルにシードデータが投入されました。
シードデータ
「モデル作ったしテストしたいけど、そもそもデータがないのでテストができない」を回避することができます。
デフォルトで作られているDatabaseSeeder.php
php artisan db:seed
を実行すると、内部的にはコレが動きます。そのため、これ以外のシーディングファイル(シードクラス)を呼び出すためにcallメソッド
が必要だったのです。
$this->call([SeedClass::class, ...]);
メソッドの引数となる配列内に呼び出したいシードクラスを連ねることで、そのクラスのシーディングが実行されます。
モデルごとのシーディング記述
insertメソッド
を用いて、テーブルに直接レコードを挿入します。
以下2点に注意してください。
- DBファザードのインポートが必要
- テーブル名の選択が必要
use Illuminate\Support\Facades\DB;
...
...
public function run(): void
{
DB::table('table_name')->insert([...]);
}
「なぜデフォルトで書いてくれてないんだ!」という声が聞こえてきそうですが、それは恐らく、後述の方法との選択肢を残すためだと思われます。DBにレコードを直接挿入する場合は、忘れず記述しましょう。
これ以外のやり方だと、モデルクラスを利用してモデル作成を行う方法があります。
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
use App\Models\Member; // add writing
use Illuminate\Database\Seeder;
class MemberSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
Member::create([
'user_code' => Str::random(3) . random_int(100000, 999999),
'user_name' => 'Jiro',
'password' => 'password',
'email' => 'test@example.com'
]);
}
}
この場合、AppディレクトリからModelクラスのインポートが必要になります。
ちなみに、シーディング実行中のみ全てのカラムが「一括挿入可能」となります(公式ドキュメント参照)。そのため、createメソッド
に対して、一括挿入を許可した記憶のないカラムuser_code
を含めた全カラムにデータを挿入した状態で実行することが可能です。
Factoryについて
ここまでのシーディング記述では、どうしても「1件ずつレコードを登録していく」という方式のため、大量のシードデータ投入には向いていません。
これを効率的に行うための機能・クラスがFactory
になります。これにより、大量のシードデータを生成することが可能になります。
なお、本記事では1人さえデータが入っていればログイン認証やメール認証のテストもできるため、割愛します。ご了承ください。気になる方・必要な方は、まず公式ドキュメントを読んでみてください。
テーブルの完全作り直し
php artisan migrate:fresh
このコマンドにより、全テーブルの削除→全テーブルの作成が行われます。必要に応じて、--seed
オプションをつけてください。これを追加すると、シーディングまで実行してくれます。
場合によっては、「今までのテーブルを全部まっさらにしたい」と考えるときがあるかもしれません。そんなとき、ただマイグレーションするだけではNothing to migrate
や、場合によってはエラーが出力されることもあります。
細かく言えば、ロールバックするといった安全なやり方があります。そのため、「本当に全テーブルをまっさらにしても良い」という場合に限って、覚悟を決めて実行する価値はあると思います。
こちらも公式ドキュメントがオプションを説明してくれているので、ご一読ください。
最後に
本記事では、自作モデルの作成を行いました。このモデルのCRUD実装やログイン認証も見据えているため、少し過剰な実装となりました。必要なときに、必要な部分をピックアップしながら読み返していただければと思います。
また。今回は各セクションで公式ドキュメントへのリンクを示しております。本記事のような浅い説明だけでなく、ぜひそちらも読みながら、深い理解と共に実装していただければと思います。