読者です 読者をやめる 読者になる 読者になる

localdisk

PHP とか Java とか Web とか好きなことを書きます。

Laravel の unique ルールとソフトデリート

先月中旬から東京に出張行ってるんですが、通勤のストレス*1から部屋(くっそ狭いマンスリーマンション)で勉強する気力が沸かなかったのですが、1ヶ月弱経ちまして少しは回復したのでまずはブログでリハビリ。

フォーラムに投稿された質問

メールアドレスを他の人と被らないよう、バリデーションを「users,mail_address」としました。
usersテーブル自体はソフトデリートを有効にしています。 この場合、削除したユーザのメールアドレスと、新しく登録するユーザのメールアドレスが同じ場合、 バリデーションに引っ掛かって登録することができません。
どうにかソフトデリートとユニークなメールアドレスを両立させる方法はないでしょうか。

[解決済み] バリデーションで「unique」を指定した際のソフトデリートとの連携について - laravel.jp

Laravel の Validator からはモデルがソフトデリート(論理削除を簡単に実装できる機能)を使ってるかどうかわからないので unique ルールを適用したら削除したモデルがひっかかって辛いという話です。

再現するとこんな感じです。

<?php

// マイグレーションファイル(抜粋)
<?php

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

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function(Blueprint $table)
        {
            $table->increments('id');
            $table->string('email');
            $table->softDeletes();
            $table->timestamps();
        });
    }
}
// シードファイル(抜粋)
class UserTableSeeder extends Seeder
{

    public function run()
    {
        // Model の $unguarded を true にすると楽
        // これ豆知識な
        User::unguard();
        User::create([
            'email' => 'test@example.com'
        ]);
    }

}
Route::get('unique', function()
{
    User::destory(1); // 削除した!
    $email = 'test@example.com';
    $v     = Validator::make(['email' => $email], ['email' => 'unique:users']);
    // 削除したけどバリデーションはエラー!
    var_dump($v->fails());
});

ソフトデリートの場合、削除されるとカラムの deleted_at に日付を入れるだけなので unique で発行される SQL ではダメなわけです。ちなみにこんなSQLが発行されます。

select count(*) as aggregate from "users" where "email" = ?

上記 SQL の結果が 0 の場合 unique バリデーションは通過します。

解決方法

クエリーへWHERE節として追加される条件を追加することも可能です。

<?php
'email' => 'unique:users,email_address,NULL,id,account_id,1'
v4.2:バリデーション

なのでこんな感じで書いて上げればOK.

<?php
Validator::make(['email' => $email], ['email' => 'unique:users,email,NULL,id,deleted_at,NULL']);

こうするとこんなSQLが発行されます。

select count(*) as aggregate from "users" where "email" = ? and "deleted_at" is null

where 節が付加されているのがわかりますね。

*1:なんか抜け毛が増えた気がする