はじめに
みなさん、Laravel捗っていますか?
僕が日々Laravelのコードを書いている上で困っている点の1つが、Eloquentの**$fillable指定です。
基本的に$guardedを使う方が良いと思っているのでそれについて簡単にまとめます。
もし「$fillable**を使うとこういう点で便利だよ!」というのがあればコメントで教えてくださると助かります。
$fillable と $guarded
$fillable
所謂ホワイトリストです。$fillableに指定したカラムのみ、create()やfill()、**update()**で値が代入されます。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $fillable = [
'name',
'sex'
];
}
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
class UsersController extends Controller
{
public function update(Request $request, User $user)
{
// $fillableに指定したもの以外は入らない
$user->update($request->all());
return redirect()->route('user.edit', $user);
}
}
$guarded
所謂ブラックリストです。$guardedに指定したカラムのみ、create()やfill()、**update()**で値が代入されません。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $guarded = [
'id',
'email',
'password'
];
}
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
class UsersController extends Controller
{
public function update(Request $request, User $user)
{
// $guardedに指定していないものは全て入り得る
$user->update($request->all());
return redirect()->route('user.edit', $user);
}
}
Pros & Cons
$fillable
Pros
- 指定したもの以外は入り得ない
Cons
- カラムが増えるたびに追加する必要がある
- 特に横に長いテーブルだとツラい ← これで困ったので今回このPOSTを書きました
$guarded
Pros
- 基本的に指定は増えない
- id や email、 password などだけ
Cons
- 指定していないものは全て入り得る
上記を踏まえた上で $guarded を選んだ理由
そもそも、Modelに指定する**$fillableや$guarded**は、Model・DB単位で予期せぬ代入が起こると困るものを書くべきだと考えました。というのも、複数代入されることを期待しているデータは、モデルごとにあるのではなく、アクションごとにあります。
$fillableに指定した全てのカラムが、一度のリクエストで代入されるとは限らず、例えばページごとに、複数代入されるカラムが違う可能性もあります。すると、$fillableには入れているが、このアクションで代入されることは考えていないデータが悪意あるユーザーによって渡されるかもしれません。
しかし、$guardedでもこれは起こり得ます。
ではどのようにするか
フィルタリングの役割分担をします。具体的に言うと、
-
Model・DB単位で書き換えられると困るものや、個別の更新ロジックがあるものはModelの**$guarded**に指定する
- idやemailはuniqueなどの制約がかかっているでしょうし、passwordも専用の変更アクションがあるでしょう
- アクションごとに書き換えられるべきもののみを、FormRequest側で抽出する
- **$request->all()**を使わない
こうすることで、指定していないものは全て入り得る現象を回避できます。
コード
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $guarded = [
'id',
'email',
'password'
];
}
<?php
namespace App\Http\Controllers;
use App\User;
use Illuminate\Http\Requests\User\UpdateUserRequest;
class UsersController extends Controller
{
public function update(UpdateUserRequest $request, User $user)
{
// UpdateUserRequest@userAttributesで必要なものだけ返す
$user->update($request->userAttributes());
return redirect()->route('user.edit', $user);
}
}
<?php
use Illuminate\Foundation\Http\FormRequest;
class UpdateUserRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|string',
'sex' => 'required|in:1,2,3',
];
}
public function userAttributes()
{
return $this->only([
'name',
'sex'
]);
}
}
こうすることで、いくつかの利点が得られます。
- 予期せぬ代入を防げる
- View側を見なくても、どのカラムが更新されるか分かる
- バリデーションなどもFormRequestに書くので、バリデーションチェックする値と、代入される値が同じクラスに存在していて保守性が上がる
もちろんFormRequest側をこのようにした上で**$fillableを指定しても構いませんが、先述した通り、カラムが増えるたびに追加する必要があることと、役割分担があいまいになってしまうため、$guarded**を使うようにしました。
(もともと横長のテーブルが根本的原因であるのかもしれませんが……)
まとめ
- **$model->fill($request->all())**を止めよう
- ModelとFormRequestのそれぞれで期待したデータ以外が入らないようにしよう
- $fillableでも問題はないが、テーブルが横長になるほどツラくなる