このブログを開始してから、もうすでに1年以上。RawのSQLを書いてコードに埋め込む日常から、Eloquentを使用したORMのコードへと日常へと移行しています。Eloquentに関しても、ブログを書き始めた頃からは理解が深まり、洗練されたLaravelのコードを書けるようになってきたこの頃です。

1年前に書いた「マスアサインメントで一括取り込み」のトピックで、EloquentのModelのクラスの属性fillableguardedの話、1年の経験で学んだことを含めてここでもう一度説明します。

まず、話のお膳立てを。

DBテーブルmemberにおいて以下の項目があるとします。

+----------------+------------------+------+-----+---------------------+----------------+
| Field          | Type             | Null | Key | Default             | Extra          |
+----------------+------------------+------+-----+---------------------+----------------+
| member_id      | int(10) unsigned | NO   | PRI | NULL                | auto_increment |
| active_flag    | char(1)          | NO   |     | NULL                |                |
| name           | varchar(255)     | NO   |     | NULL                |                |
| email          | varchar(255)     | NO   | UNI | NULL                |                |
| password       | varchar(60)      | NO   |     | NULL                |                |
| memo           | varchar(100)     | YES  |     | NULL                |                |
| created_at     | timestamp        | NO   |     | 0000-00-00 00:00:00 |                |
| updated_at     | timestamp        | NO   |     | 0000-00-00 00:00:00 |                |
+----------------+------------------+------+-----+---------------------+----------------+

MemberのModelは以下のような定義になります。

...
class Member extends Model
{
    protected $table = 'member';
    protected $primaryKey = 'member_id';
    protected $fillable = ['name', 'email'];
    ...
}

$timestamps$incrementingの定義は必要ないです。両方ともデフォルトでtrueなので。

入力フォームは、

Laravel 2016-08-31 20-51-04

で、email, password, nameの項目を入力できます。

以下のコントローラで、入力フォームから入ってきた値は以下のコードでDBに保存できます。

...
class MemberController extends Controller
{
    ...
    public store(Request $request)
    {
        $member = new Member;
        $member->fill($request->all())->save();
        ...
    }
    ...
}

しかし、先ほどの$fillable定義により、DBに保存されるのは、emailとnameのみです。実行されるSQLは以下で、他の入力された値は無視されるので、passwordはDBはデフォルトの空のままです。つまり、$fillableは、DBに入力したい値をリストするホワイトリストです。ちなみに、created_at, updated_atの項目は、Eloquentにより自動的に保存時の日時を記録します。

逆に、DBに入れたくない項目をリストするなら、つまりブラックリストを定義したいなら、$fillableの代わりに、$guardedを使用します。

...
class Member extends Model
{
    protected $table = 'member';
    protected $primaryKey = 'member_id';
    protected $guarded = ['member_id', 'active_flag', 'password'];
    ...
}

以上がマスアサイメントの使用の仕方で、意図的あるいは間違って入力フォームから、DBへ保存されるのを防いでくれます。

$fillable$guardedの目的を理解したところで、2つ問題。

まず、

active_flagやpasswordなどマスアサインメントで相手にしない項目の値はどうやってDBに保存するのでしょう?

これは、通常のオブジェクトの値のアサインメントで行います。

...
    public store(Request $request)
    {
        $member = new Member;
        $member->active_flag = 'Y'; //デフォルト
        $member->password = bcrypt($request->password); //ハッシュ値に変換
        $member->fill($request->all())->save();
        ...
    }
...

次に、
入力画面によりDBに入れたい項目が違う場合は、どう$fillableを設定?

例えば、管理画面で会員の情報を編集する画面。そこでは、会員が有効か無効のフラッグ(active_flag)、そして会員に関するノート(memo)も付加情報としてDBに保存したいです。もちろん、裏で使用するのは、同じMemberのクラスなので、同じ$fillableは使えないですね。

1つは、先の値のアサイメント使用する方法。

    $member->active_flag = $request->active_flag;
    $member->memo = $request->memo;
    $member->fill($request->all())->save();

しかし、これではもっと項目が増えたら面倒です。

$fillableを使用するのではなく、先のように$guardedをMemberで定義して、以下のようにコントローラにおいて、独自の$fillableを使用します。

...
    public store(Request $request)
    {
     $fillable = ['email', 'name'];
        $input = array_only($request->all(), $fillable);
        $member = new Member;
        $member->active_flag = 'Y';
        $member->password = Hash::make($request->password);
        $member->fill($input)->save();
        ...
    }

    public edit(Member $member, Request $request)
    {
        $fillable = ['email', 'name', 'active_flag', 'memo'];
        $input = array_only($request->all(), $fillable);
        $member->fill($input)->save();
        ...
    }
...

array_onlyの関数は、Laravelのヘルパー関数です。

By khino