Edited at

[Laravel]insertGetIdは使っちゃだめ!!saveを使おう


ORMデータ生成法

laravelのORMを使って、データを作成する方法は、主に3つ(insert、save、create)があります。


実験するための準備


テーブルの用意

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateApplesTable extends Migration
{
public function up()
{
Schema::create('apples', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->integer('weight');
$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('apples');
}
}


Modelの用意


Account.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Account extends Model
{
protected $table = 'accounts';
protected $fillable = [
'name'
];
protected $guarded = [
'weight'
];
}



bladeの用意

<form method="get" action="{{route('sample')}}">

<label>
名前<input type="text" name="name">
</label>
<label>
体重<input type="text" name="weight">
</label>
<button type="submit">保存</button>
</form>

viewイメージ

Screenshot_2.png


Controllerの用意


SampleController.php

<?php

namespace App\Http\Controllers;

use App\Account;
use Illuminate\Http\Request;

class SampleController extends Controller
{
public function sample(Request $request)
{
$account1 = new Account();
$account1Id = $account1->newQuery()->create($request->all())->id;

$account2 = new Account();
$account2->fill($request->all())->save();
$account2Id = $account2->id;

$account3 = new Account();
$account3Id = $account3->newQuery()->insertGetId($request->all());

dd("create[{$account1Id}]:save[{$account2Id}]:insert[{$account3Id}]");
}
}



Routeの用意


web.php

Route::get('/sample', 'SampleController@sample')->name('sample');



実行結果

Screenshot_3.png

それぞれIDを取得できています。


DB出力結果

mysql> select * from accounts;

+----+----------+--------+---------------------+---------------------+
| id | name | weight | created_at | updated_at |
+----+----------+--------+---------------------+---------------------+
| 1 | useruser | NULL | 2019-08-08 14:42:14 | 2019-08-08 14:42:14 |
| 2 | useruser | NULL | 2019-08-08 14:42:14 | 2019-08-08 14:42:14 |
| 3 | useruser | 80 | NULL | NULL |
+----+----------+--------+---------------------+---------------------+


各メソッド(save・create・insert)から、なにがいいのかを紐解いていく


Create


・メリット

複数カラム代入制限付き(Modelで定義したfillableとguardedの加護を受けられる)

・fillable

複数同時代入可能なカラムを指定する。

・guarded

複数同時代入不可なカラムを指定する。


・デメリット

今回のような、受け取った値(request->all())をそのまま入れられる状況(わたってくる値に余分なものがない)なら、記述を少なく抑えられる。しかし、例えば、form にinput type="text" name="hobby"のような、別テーブルに保存したいものが存在したとき、request->all()でそのまま代入すると、エラーになるので

create([

'name' => $request->input('name')
])

と指定することが予想できる。これはカラム数が増えれば、増えるほど記述量が多くなってしまう。また、たとえrequest->allを使える状況にあったとしても、他の処理で、request->all()を使ったcreateが使えるとは限らない。そのとき、こちらではcreateそっちではsaveを使っていると統一感が失われ、共通化できる可能性を消してしまうことにつながってしまう。これも記述量が増すことにつながってしまう。


Save


・メリット

複数代入制限付き(Createと同じ)

fillメソッドで受け取ったデータを、カラムごとのデータとしてセットしてくれる。

fillメソッドはとても優秀、たとえform にinput type="text" name="hobby"のような、別テーブルに保存したいものが存在したときでも、fillメソッドが必要ないものはカラムにデータとしてセットしないため、request->all()をそのまま使うことができる。これは共通化の可能性を残すことにつながる。またcreate同様fillableとguardedの加護も受けられるため、代入するときに制限をかけることができ、バグを抑えられることにつながる。

ただ、request->all()でそのまま代入したいなら、inputタグのname属性とテーブルのカラム名を合わせる必要がある。(これは全体的に統一感を与えてくれる)

もし違っていたら、カラムに合わせた連想配列を作って、与える必要がある。


・デメリット

他に比べ、特にないように感じる。

どうしてもカラム名をスネークケース、inputタグのname属性をキャメルケースなどのそれぞれにすごくこだわりがある場合は、結局createと同じようなことをする必要があるので、記述量が増し、可読性がさがってしまう。


Insert


・メリット

複数データを2次元連想配列で渡すと、複数レコード代入を一括でやってくれる。


・デメリット

複数カラム代入制限がつかない(Modelで定義したfillableとguardedの加護を受けられない)

DBの出力結果からわかるように、guardedを無視して、weightに値が代入されてしまっている。また、createとsaveはcreated_atとupdated_atにデータが自動代入されているが、insertを使うと、自分で指定して代入しないといけない。これはバグになる可能性が他の二つに比べ高まってしまう。


おすすめ

・普段はsave

・どうしても一括レコード代入の場合insert


insertGetIdを使わない方法・理由

Controller内の処理を見ると、

$account1   = new Account();

$account1Id = $account1->newQuery()->create($request->all())->id;

$account2 = new Account();
$account2->fill($request->all())->save();
$account2Id = $account2->id;

$account3 = new Account();
$account3Id = $account3->newQuery()->insertGetId($request->all());


まとめ

createでもsaveでも、modelからidを取得することができる。つまり、複数カラム代入制限がきかないInsertを単レコード代入処理のときに使うのは、自分から制限を効かないようにして、バグの発生する可能性をあげてしまうことにつながってしまう


参考にさせていただいた記事

LaravelのORMで初心者から職人へ

【Laravel】DBにデータを保存する方法。createとinsertの違いなど