はじめに
ユーザーを削除する方法として、データベースからデータを消してしまう「物理削除(HardDelete)」と、削除フラグを立てて処理をする「論理削除(SoftDelete)」があります。
ここでは、「論理削除」でユーザーを削除する手順をまとめます。
環境
XAMPP環境でLaravelが使えるように設定してあります。
- Windows10 Pro 64bit
- PHP 7.3.18
- Laravel 7.12.0
- MariaDB 10.1.32
また、Laravelプロジェクトは以下の手順で、ユーザー認証をユーザー名とパスワードで行えるようにしてあります。
- 【Laravel7でユーザー認証_1】基本のき
- 【Laravel7でユーザー認証_2】ユーザー認証を日本語化
- 【Laravel7でユーザー認証_3】ユーザー認証をメールアドレスからユーザー名に変更する
- 【Laravel7でユーザー認証_4】パスワード変更フォームを作成する
#実装
論理削除用のカラムを追加する
Laravelで用意されている $table->softDeletes();
を使って、users
テーブルにdeleted_at
という論理削除用のカラムを追加します。
$ php artisan make:migration add_softdeletes_to_users --table=users
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
- //
+ $table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
- //
+ $table->dropColumn('deleted_at');
});
}
$ php artisan migrate
実行すると、usersテーブルにdeleted_atというカラムが追加されます。
usersテーブルのunique制約を削除する
論理削除は、削除フラグを立てるだけでレコード自体は削除されません。
そのため、メールアドレス(ユーザー名)にunique制約がついていると、削除後に同じメールアドレス(ユーザー名)での登録ができなくなります。
このusersテーブルに設定されているunique制約を削除します。
$ php artisan make:migration drop_unique_to_users --table=users
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
- //
+ $table->dropUnique('users_email_unique');
+ $table->dropUnique('users_username_unique');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
- //
+ $table->unique('email', 'users_email_unique');
+ $table->unique('username', 'users_username_unique');
});
}
usersテーブルに複合unique制約を追加する
unique制約を外してしまったので、このままだと同じメールアドレス(ユーザー名)が登録できてしまいます。
▼現状
※山田太郎も山田商店も同じメールアドレス(ユーザー名)で登録できてしまう。
名前 | ユーザー名 | メールアドレス | deleted_at |
---|---|---|---|
山田太郎 | yamada | yamada@example.jp | NULL |
山田商店 | yamada | yamada@example.jp | NULL |
太郎 | taro | taro@example.com | NULL |
これだと山田商店でログインした際、山田太郎のデータを引っ張ってきてしまったり、パスワード再設定メールを発行した際に、希望のアカウントの再設定ができなくなる可能性があります。
「メールアドレス(ユーザー名)の重複は原則禁止、ただしdeleted_atに値が入っていたら許可」という仕様にするため、deleted_atとメールアドレス(ユーザー名)の複合unique制約を追加します。
▼複合unique制約で実現したい仕様
※山田商店は削除済アカウントなので、メールアドレス(ユーザー名)の重複が可能。
名前 | ユーザー名 | メールアドレス | deleted_at |
---|---|---|---|
山田太郎 | yamada | yamada@example.jp | NULL |
山田商店 | yamada | yamada@example.jp | 2000-01-01 00:00:00 |
太郎 | taro | taro@example.com | NULL |
$ php artisan make:migration add_unique_to_users --table=users
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
- //
+ $table->unique(['email', 'deleted_at'], 'users_email_deleted_at_unique');
+ $table->unique(['username', 'deleted_at'], 'users_username_deleted_at_unique');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
- //
+ $table->dropUnique('users_email_deleted_at_unique');
+ $table->dropUnique('users_username_deleted_at_unique');
});
}
$ php artisan migrate
実行すると、複合unique制約が作成されます。
Userモデルの変更
Userモデルで、SoftDeletesトレイトが使用できるよう設定します。
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
+ use Illuminate\Database\Eloquent\Model;
+ use Illuminate\Database\Eloquent\SoftDeletes;
class User extends Authenticatable
{
+ use Notifiable;
+ use SoftDeletes;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'username', 'password',
];
ユーザー登録時のバリデーション変更
ユーザー登録時のバリデーションチェックとして、複合unique制約でのチェックに変更します。
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
+ use Illuminate\Validation\Rule;
==(中略)==
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
- 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
+ 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')->whereNull('deleted_at'),],
'password' => ['required', 'string', 'min:8', 'confirmed'],
- 'username' => ['required', 'string', 'max:32', 'unique:users', 'regex:/^[a-zA-Z0-9-_]+$/'],
+ 'username' => ['required', 'string', 'max:32', Rule::unique('users', 'username')->whereNull('deleted_at'), 'regex:/^[a-zA-Z0-9-_]+$/'],
]);
}
##一旦確認
ここまでで、実際にユーザーの論理削除ができるかどうか、php artisan tinker
で検証してみます。
Laravel TinkerはコマンドラインでLaravelアプリケーション全体を操作できるコマンドです。
▼ユーザーIDが1のユーザーを削除
$ php artisan tinker
>>> $user = User::find(1)
ユーザーIDが1の情報が表示
>>> $user->delete()
>>> $user = User::find(1)
null と表示されていたら論理削除成功
データベース上では、deleted_atに日時が入っています。
▼削除したID1のユーザーを元に戻す
>>> User::onlyTrashed()->where('id',1)->restore()
>>> $user = User::find(1)
ユーザーIDが1の情報が表示
exit
と打ち込むか、Ctrl+C
でTinkerが終了します。
アカウント削除のボタンを実装する
deactiveというページにアカウント削除ボタンを実装します。
ログインパスワードの再入力や、間違いがないかどうかの承認チェックボックスなども必要に思いますが、今回はシンプルにアカウント削除ボタンを押せば削除完了という仕様で進めます。
###コントローラの作成
ユーザー削除用のコントローラを作成します。
Authディレクトリの中に「DeactiveController.php」というファイルを追加しました。
$ php artisan make:controller Auth/DeactiveController
###ルーティングの設定
以下の仕様で、ルーティングを設定します。
通常のアクセス(GET)の場合は、「Auth\DeactiveController」コントローラの「showDeactiveForm」メソッドを実行。
パスワードを変更の処理(POST)の場合は、「Auth\DeactiveController」コントローラの「deactive」メソッドを実行。
それぞれのルーティングには、「deactive.form」、「deactive」という名前を付けました。
Route::post('/password/change', 'Auth\ChangePasswordController@ChangePassword')->name('password.change');
+ Route::get('/deactive', 'Auth\DeactiveController@showDeactiveForm')->name('deactive.form');
+ Route::post('/deactive', 'Auth\DeactiveController@deactive')->name('deactive');
###viewの作成
viewは、auth の中に deactive.blade.php を作成しました。
formのアクション先は、ルーティングで付けた「deactive」という名前を使います。
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Deactive') }}</div>
<div class="card-body text-center">
<form method="POST" action="{{ route('deactive') }}">
@csrf
<h2>{{ __('This will deactivate your account.') }}</h2>
<p>{{ __('Press the Deactive button to continue the process.') }}</p>
<button type="submit" class="btn btn-primary">
{{ __('Deactive') }}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
###翻訳ファイルに追加
view内で使った翻訳文字をja.jsonに追加します。
- "Your password has been changed.": "パスワードが変更されました。"
+ "Your password has been changed.": "パスワードが変更されました。",
+ "Deactive": "アカウント削除",
+ "This will deactivate your account.": "アカウントを削除しようとしています。",
+ "Press the Deactive button to continue the process.": "アカウント削除の処理を続けるにはアカウント削除のボタンを押してください。"
}
###コントローラにメソッド追加
DeactiveController
に、ルーティングで指定したメソッドを追加します。
showDeactiveForm
メソッド内では、作成したview(auth/deactive.blade.php)が表示されるように指定します。
deactive
メソッドは、ユーザーを論理削除する処理を入れます。
削除処理後は、トップページにリダイレクトされるようにしました。
ログインしていない状態では表示させないようにAuthミドルウェアの指定を忘れずに…。
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
+ use Illuminate\Support\Facades\Auth;
+ use App\User;
class DeactiveController extends Controller
{
+ public function __construct()
+ {
+ $this->middleware('auth');
+ }
+
+ public function showDeactiveForm()
+ {
+ return view('auth\deactive');
+ }
+
+ public function deactive()
+ {
+ User::find(Auth::id())->delete();
+ return redirect('/');
+ }
}
これで実装は完了です。
アカウント削除のボタンを押すと、データベースのdelete_atに削除ボタンを押した日時が入っているのが確認できます。
蛇足
- 複合ユニーク制約で、論理削除したユーザーは重複を無視するように設計しましたが、論理削除だとデータベースにいつまでもデータが残ってしまいます。一定期間が経過したデータは削除するように、何らかの処理が必要そうです。
- Twitterの場合、アカウントを削除しても30日間は仮削除の状態になるので、 同じように、論理削除しても30日以内なら復活可能、30日経過したら物理削除 という処理をしてもいいのかなと思いました。