環境
- windows10
- DockerDesktop for Win 3.5.x
- Laravel 9.x
- PHP 8.1
この記事は何?
本記事では、Laravelのcreate、saveの使い分け、違いについて説明しています。
(4.x以降であれば、バージョンによる差異はそこまで無いかと思います。)
複数代入保護
複数代入
Laravel Eloquentでは、デフォルトで複数代入から保護されています。
複数代入は、ユーザーからの入力値とDB側のカラム定義を合わせることで、簡単な記述で入力値をDBへ一括で保存できるようにしようという考え方で、Laravel3.xまではよく利用されていたものです。
複数代入の脆弱性
curlやThunder ClientやPostmanなどのツールからであれば、ブラウザ(フォーム)を通さずにリクエストを送信することが可能です。つまり、複数代入は開発者にとって便利な考え方であると同時に、意図していないデータが送信されてくる危険性があります。
具体的には、公式ドキュメントでも例示されている以下のようなケースが挙げられます
たとえば、悪意のあるユーザーがHTTPリクエストを介して
is_admin
パラメータを送信し、それがモデルのcreate
メソッドに渡されて、ユーザーが自分自身を管理者に格上げする場合が考えられます。
https://readouble.com/laravel/9.x/ja/eloquent.html#mass-assignment
上記のような背景があり、Laravel Eloquentでは、バージョン4.xからデフォルトで複数代入から保護されるようになりました。(=複数代入を許可、ないし禁止するカラムを明示的に指定する必要がある)
具体的には、以下のようにどちらかの形式でモデルクラスにプロパティ(カラム)を設定しておく必要があります。
- $fillableで複数代入を許可する属性を設定(ホワイトリスト形式)
- $guardedで、複数代入を許可しない属性を設定(ブラックリスト形式)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* 複数代入可能な属性
*
* @var array
*/
protected $fillable = ['name'];
}
※公式ドキュメントより引用
複数代入を使用しない場合のデメリット
モデルクラスのインスタンス生成後、Httpリクエストの一つ一つの項目を対応するカラムに指定し、最後にsaveメソッドを呼び出すことで、複数代入を使用しなくてもデータベースにレコードを追加することは可能です。ただし、項目が多ければ多いほど、このモデルに属性をセットするという作業が増え、ソースコードが肥大化します。
そのため、値と列名を表すキーを含む連想配列を渡すことによって簡単にモデルの属性を埋めることができる、複数代入が重宝されています。
<?php
・・・
public function store(Request $request)
{
// リクエストのバリデーション処理…
$flight = new Flight;
$flight->name = $request->name;
・・・
・・・
・・・
$flight->save();
}
※公式ドキュメントの例より引用
https://readouble.com/laravel/9.x/en/eloquent.html#inserts
create、saveの使い分け、違い
Laravel Eloquentにおいて、複数代入を実現しつつDBへデータを新規追加する方法はざっくりと以下の2種類に分けられます。(厳密には他にも複数ありますが、今回は割愛)
- create
- fill(+ save)
create
モデルクラスから create メソッドを呼ぶことで、以下の一連の処理を一気に行ってくれます。
挿入したモデルインスタンスを取得できるので後続の処理や返り値として返したい場合はこちらを使用したほうが便利です。
処理内容
インスタンスの作成
↓
データの永続化
↓
永続後、インスタンスを返す
書き方
モデルクラス
// 該当のモデルクラスでfillableかguardedのどちらかを指定する必要あり
protected $fillable = ['recipe_name'];
// protected $guarded = [];
createメソッドの実行
create()の引数は配列型で渡す必要があります。また、一般的には$request->all()で配列としてHttpリクエストのデータを取得し、それらをcreateに渡すことで、たった一行でDBへの永続化が可能になります。
$recipe = App\Recipe::create(['recipe_name' => 'カレーライス']);
fill + save
createとは異なり、既存のモデルクラスのインスタンスに対して実行できます。(モデルクラスのインスタンスの新規作成は行いません)
そのため、すでにインスタンスが存在する場合や、newでインスタンスを新規作成し、そのインスタンスに対してfillメソッド複数代入(配列型)し、saveで保存するという流れになります。
書き方
モデルクラス
// createと同様、モデルクラスににfillableかguardedのどちらかを指定する必要あり
protected $fillable = ['recipe_name'];
// protected $guarded = [];
fillメソッドの実行
$recipe = new App\Recipe();
$recipe->fill(['recipe_name' => 'カレーライス']);
$recipe->save();
// or
// モデルファイルに設定しているプロパティとリクエストの内容が一致していれば、ワンライナーで書くことも可
$recipe->fill($request->all())->save();
また、saveは新規レコードの追加だけではなく、レコードの更新にも使用できます。
まとめ
いかがでしたでしょうか。Laravelを用いてDB にデータを保存する際、Eloquent (エロクエント)を使用することで効率よくコードを書くことができますが、前提として、セキュリティ面で理解しておく必要のある概念があります。複数代入を保護しつつ、効率的にデータベースを操作する方法について解説していますので、ぜひ学習に役立ててみてください。