PHP
Linux
CentOS
laravel5
laravel5.2

Laravel5のバリデーションの重複チェックで自分自身は除外する方法

More than 1 year has passed since last update.

動作環境

  • Laravel 5.2.31
  • CentOS 6.8
  • PHP 5.6.22
  • PostgreSQL 9.5.3

はじめに

Laravel5では次のようにすることで、値の重複チェックをすることができます。

$rules = ['name' => 'required|unique:items,name'];

これは、データベース上のitemsテーブルの中のnameカラムを参照し、
既に同一の値の入ったレコードがある場合はエラーを表示し、登録を抑制するといったことができます。

何が問題か

この重複チェック、自分自身も弾いてしまいます。
例えば /items/1/edit に name 1 とか入っていれば、
当然ながらデータベースにも name 1 とかあるはず。

そうなると、name 1 → name 1 へ値を更新しようとするので重複チェックに引っかかり、エラーになります。
登録するにはデータベースにはまだ登録されていない別の値に変えなきゃいけないわけです。
つまりこういうこと

データベースに name 1,2 という値があるとする

/items/1/edit で name 1 から name 1 へ変更 → 不可(できるようにしたい)
/items/1/edit で name 1 から name 2 へ変更 → 不可(OK)
/items/1/edit で name 1 から name 3 へ変更 → 可(OK)
/items/2/edit で name 2 から name 1 へ変更 → 不可(OK)
/items/2/edit で name 2 から name 2 へ変更 → 不可(できるようにしたい)
/items/2/edit で name 2 から name 3 へ変更 → 可(OK)
/items/create で name 1 を登録 → 不可(OK)
/items/create で name 2 を登録 → 不可(OK)
/items/create で name 3 を登録 → 可(OK)

これって不便ですよね。
自分自身は重複チェックから外す、ということがしたいはず。
次のようなコードで実現が可能です、が…

$rules = ['name' => 'required|unique:items,name'. $request -> name .',name',];

// もし app/Http/Requests/CustomRequest.php とかにバリデーションを書いている場合は次のように書くと動くはず

return ['name' => 'required|unique:items,name'. $this -> name .',name',];

これだと次のような挙動になります。

/items/1/edit で name 1 から name 1 へ変更 → 可(OK)
/items/1/edit で name 1 から name 2 へ変更 → 可(NG)
/items/1/edit で name 1 から name 3 へ変更 → 可(OK)
/items/2/edit で name 2 から name 1 へ変更 → 可(NG)
/items/2/edit で name 2 から name 2 へ変更 → 可(OK)
/items/2/edit で name 2 から name 3 へ変更 → 可(OK)
/items/create で name 1 を登録 → 不可(OK)
/items/create で name 2 を登録 → 不可(OK)
/items/create で name 3 を登録 → 可(OK)

自分自身を重複チェックから除外することになるので、既にレコードに値があっても通ってしまいます。

なので、自分自身の重複は許可するが、既存の値への変更は不可にする、という書き方が必要です。
それでいろいろ悩んだ結果、次のようになりました。

実装

Controller側「ItemsController.php」

<?php // ファイルパス app/Http/Controllers/Admin/ItemsController.php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Item; // Modelのパス app/Item.php

use Illuminate\Http\Request;
use App\Http\Requests;

class ItemsController extends Controller
{

    public function create()
    {
        $item = new \App\Item();
        return view('.admin.items.create',['item' => $item]);
    }

    public function store(Request $request)
    {
        $item = new \App\Item();

        $rules = ['name' => 'required|unique:items,name'];
        $this -> validate($request, $rules);

        $item -> name = $request -> name; 
        $item -> save();
        return redirect('/admin/items');
    }

    public function edit($id)
    {
        $item = Item::find($id);
        return view('.admin.items.edit',['item' => $item]);
    }

    public function update(Request $request, $id)
    {
        $item = Item::find($id);

        $rules = ['name' => 'required|unique:items,name,'. $item -> id .',id',];
        $this -> validate($request, $rules);

        $item -> name = $request -> name; 
        $item -> save();
        return redirect('/admin/items');
    }
}

重複チェックをするが、データベース上のIDに紐づいているnameは除外する、といった設定。
ID 1 name 1 なら除外、 ID 2 name 1 は重複チェック、 ID 1 name 2 は重複チェックみたいなかんじ。
これで期待動作を満たしてくれるはずです。

なお、app/Http/Requests/CustomRequest.php とかに書いている場合は次のように書けばいけると思います。

<?php // ファイルパス app/Http/Requests/CustomRequest.php

namespace App\Http\Requests;

use App\Http\Requests\Request;

use App\Item; // Modelのパス app/Item.php

class CustomRequest extends Request
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */

    public function rules()
    {

        if ($this -> id) { // 編集画面の時
            $unique = 'unique:items,name,' . $this -> id . ',id';
        } else { // 新規登録画面の時
            $unique = 'unique:items,name';
        }

        return ['name' => 'required|' . $unique];
    }

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }
}

あとはそれをControllerで読み込ませればうまくいくと思います。

Controller側「ItemsController.php」

<?php // ファイルパス app/Http/Controllers/Admin/ItemsController.php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Item; // Modelのパス app/Item.php

use Illuminate\Http\Request;
use App\Http\Requests;

use App\Http\Requests\CustomRequest; // カスタムバリデーションファイルのパス app/Http/Requests/CustomRequest.php

class ItemsController extends Controller
{

    public function create()
    {
        $item = new \App\Item();
        return view('.admin.items.create',['item' => $item]);
    }

    public function store(CustomRequest $request) // Request -> CustomRequest
    {
        $item = new \App\Item();
        $item -> name = $request -> name; 
        $item -> save();
        return redirect('/admin/items');
    }

    public function edit($id)
    {
        $item = Item::find($id);
        return view('.admin.items.edit',['item' => $item]);
    }

    public function update(CustomRequest $request, $id) // Request -> CustomRequest
    {
        $item = Item::find($id);
        $item -> name = $request -> name; 
        $item -> save();
        return redirect('/admin/items');
    }
}