LaravelでDBへの新規追加と更新を共通化する方法

1 min読了の目安(約1600字TECH技術記事

背景

LaravelでCRUD処理を実装しているときに、
追加と更新処理は処理的にはほとんど同じなのにそれぞれコントローラとビューを用意するのを、いつも"なんか面倒"と思っていました。

  • 新規追加のフォームから、新規追加実行
  • 既存データ更新フォームから、更新処理実行

上記2つの処理フローにおいて、
LaravelにはupdateOrCreateという、データがあれば更新、なければ新規追加というメソッドもありますし、コードがほとんど重複になります。
なんとかスマートに実装できないか調査して実装しました。

コード

前提

  • Laravelの命名規則にそったItemモデルを定義し、item_nameのカラムを作ります

route

// 新規追加&更新フォーム用
Route::get('/item/input/{item?}', 'ItemController@input')->name('input.item');

// 新規追加実行&更新実行
Route::post('/item/save/{item?}', 'ItemController@save')->name('save.item');

実行部分のroute定義のキモは、モデルルートで{item?}のように、「?」で定義し任意パラメータにしているところです。
このようにすうることで、新規追加のときはURLにIDが不要となります。

controller

// 入力フォーム
public function input(Item $item)
{
    return view('input.item', compact('item'));
}

// 追加or更新処理
public function save(Request $requestItem,Item $item)
{
    Item::updateOrCreate(['id' => $item->id], $requestItem);
    return redirect()->route('item.complete');
}

コントロールの引数である、Item $itemは、routeでItemモデルのIDを指定されて場合は、
指定されたレコードのオブジェクトが取得できます。
指定されなかった場合は、Laravelのインジェクションの機能により、Itemモデルのインスタンスが取得されます。

追加or更新処理で、['id' => $item->id]を指定しています。
updateOrCreateはID=NULLが指定された場合は新規追加の挙動をします。

view

{{Form::open(['method'=>'post','route' => ['item.save',['item' => $item->id]]])}}

<input type="text"
name="item_name"
value="{{old('item_name',$item->item_name)}}">

<button type="submit">投稿する</button>

{{ Form::close() }}

{{old('item_name',$item->item_name)}}

という記述があります。
フォームのデフォルト値として$item->item_nameを指定することで、更新処理のときはモデルオブジェクトの値がセットされ、新規追加のときはnull=空白となります。
バリデーションエラーで引っかかった場合は、入力値が保持されます。

追加と更新でバリデーションのルールが変わったり、新規追加の時には特殊な処理を挟まないと行けない時以外はこのパターンで実装できると思います。