LaravelはPHPフレームワークの中でも非常に人気があります。その中で、Eloquent ORM(Object-Relational Mapping)の特徴の一つとして「マスアサインメント」という概念があります。これを効率的かつ安全に使用するための便利な機能が「fillable」です。この記事では、Laravelのfillableの使い方を詳細に解説し、安全なデータ管理を実現するためのベストプラクティスをご紹介します。
マスアサインメントとは?
マスアサインメント(Mass Assignment)とは、複数の属性を一度に設定するための機能です。通常、Eloquentモデルを使って一つ一つのプロパティを設定することができますが、リクエストから受け取った大量のデータを一回でモデルに割り当てると、冗長性を減らし、コードをきれいに保つことができます。
// マスアサインメントの例
$user = User::create([
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => Hash::make('password'),
]);
この機能は便利ですが、思わぬセキュリティホールを生む可能性もあるため、注意が必要です。
fillableプロパティ
マスアサインメントは非常に便利ですが、ユーザーからの入力をそのままモデルに割り当てると、許可されていないフィールドまでもが変更されてしまう可能性があります。そこで登場するのがfillableプロパティです。fillableプロパティは、マスアサインメントを許可するフィールドを指定するために使用されます。
class User extends Model
{
protected $fillable = ['name', 'email', 'password'];
}
ここで$fillableに指定されていない属性は、マスアサインメントによって変更されることはありません。この設定により、セキュリティが向上し、安全なデータ操作が可能になります。
fillableの利点
-
セキュリティ: 不正なデータが誤って入力されないようにすることで、アプリケーションの安全性を高めます。
-
コードの可読性: 明示的に更新可能なフィールドを示すことで、コードを読む人がどのフィールドが重要であるかをすぐに理解できます。
-
保守性: 強制的に許可・不許可を設定できるため、プロジェクトの拡大にともなって保守が容易になります。
fillableとguardedの違い
fillableの代わりに使用されるguardedプロパティも理解しておきましょう。guardedは、マスアサインメントの対象から除外するフィールドを指定する配列です。
class User extends Model
{
protected $guarded = ['is_admin'];
}
fillableはホワイトリスト方式で、guardedはブラックリスト方式です。どちらを使うかは、プロジェクトの要件や個人の好みに依りますが、基本はfillableが推奨されます。なぜなら、新しいフィールドを追加したときにセキュリティ上のリスクが少ないためです。
実践的な使用例
ユーザー登録時のデータ保存
ユーザー登録の際、多くのデータを一度に取り扱う必要があります。ここでの代表的な例を紹介します。
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
class UserController extends Controller
{
public function store(Request $request)
{
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
]);
$user = User::create([
'name' => $validatedData['name'],
'email' => $validatedData['email'],
'password' => Hash::make($validatedData['password']),
]);
// 他の処理...
return response()->json($user, 201);
}
}
この例では、fillableを活用して公平なセキュリティレイヤーが提供され、開発者が変更可能なフィールドを完全にコントロールできるようにしています。
fillableを設定していないとどうなるか(MassAssignmentException)
$fillableにも$guardedにも指定していないプロパティをcreate()やfill()で一括代入しようとすると、Laravelは次のようなエラーを出します。
Illuminate\Database\Eloquent\MassAssignmentException: Add [is_admin] to fillable property to allow mass assignment on [App\Models\User].
これは「そのフィールドを一括代入してよいかどうかをモデル側で明示していない」ために発生するエラーです。対処法は次の3つです。
$fillableに該当フィールドを追加する(推奨・ホワイトリスト方式)$guardedから該当フィールドを除外する- 一括代入せず
$model->is_admin = true; $model->save();のように個別に代入する
$guarded = [] は原則使わない
protected $guarded = [];と設定すると「すべてのフィールドを一括代入可能」にできますが、リクエストの生データをそのままcreate()に渡している場合、is_adminやroleのような本来ユーザーが変更できてはいけないフィールドまで書き換えられてしまう危険があります。開発中の一時的な検証以外では避け、$fillableで許可フィールドを明示するのが安全です。
fill() とforceFill() の違い
create()以外にも、既存モデルへ一括代入するfill()があります。$fillableの制限を守った上で複数属性をまとめて設定したい場合に使います。
$user = User::find(1);
$user->fill(['name' => 'Jane Doe', 'email' => 'jane@example.com']);
$user->save();
バッチ処理やシーダーなど、信頼できるデータソースから$fillableの制限を無視して強制的に代入したい場合はforceFill()を使います。ユーザー入力を直接forceFill()に渡すのは$guarded = []と同様の危険があるため避けてください。
// $fillableの制限を無視して代入する(信頼できるデータのみに使用)
$user->forceFill(['is_admin' => true])->save();
FAQ
fillableを設定しないとどうなりますか?
1件も$fillable/$guardedを設定しないと、Eloquentはデフォルトで全フィールドをガードするため、create()やfill()での一括代入時にMassAssignmentExceptionが発生します。個別に$model->name = '...'のように代入すれば問題は起きません。
fillableとguardedは併用できますか?
併用できません。どちらか一方のみをモデルに定義します。基本的には安全性の高いfillable(ホワイトリスト方式)を使うことが推奨されます。
リレーション先のモデルにもfillableは必要ですか?
はい。User::create([...])とPost::create([...])はそれぞれ別モデルなので、一括代入するモデルごとに$fillableを定義する必要があります。
一般的な注意点
- 必要な検証: ビジネスロジック上で変更が必要なフィールドに対しては常にバリデーションを行いましょう。
-
fillableのレビュー: プロジェクトが進化するにつれ、
fillableプロパティのレビューを行い、必要に応じて更新します。 - 他のモデルとの関係: 関連するモデルの属性変更が必要な場合もあるため、リレーションを考慮した設計を心がけましょう。
まとめ
Laravelのfillableは安全かつ効率的なデータ操作を実現する重要なツールです。プロジェクトの成長や要求の変化に柔軟に対応するため、正しく使用する方法を理解して実践しましょう。これにより、セキュアで保守が容易なアプリケーションを構築することができます。フィールドの管理に気を配り、必要なフィールドのみにマスアサインメントを許可することで、より強固なアプリケーションを作り上げましょう。
関連記事
- Laravelモデル入門:基本操作からEloquentの活用まで徹底解説 — モデル定義やEloquentの基本操作を先に押さえたい方はこちら
- Laravel Eloquentでデータ更新を効果的に行う方法とベストプラクティス — update()やfill()を使ったデータ更新の実践例

コメント