RitoLabo

Laravelの認証機能でユーザ情報の論理削除(ソフトデリート)を実装する

  • 公開:
  • 更新:
  • カテゴリ: PHP Laravel
  • タグ: PHP,Laravel,migration,5.5,5.4,5.3,Auth,EloquentORM,5.6

ユーザ管理機能については、デフォルトでは「ログイン」「ユーザ登録」「パスワードリセット」の機能がありますが、要件によってはもっと多くの機能と情報が必要になる場合も多々あります。

今回からユーザ情報に関する機能の拡張・追加を行いますが、中でも管理機能で良く使われる「論理削除(ソフトデリート)」「最終ログイン」「ロール(権限)機能」を追加していきます。

今回はLaravel認証機能の拡張第2弾として、ユーザ情報の論理削除(ソフトデリート)機能を実装します。

アジェンダ
  1. 開発環境
  2. ユーザの論理削除(ソフトデリート)について
  3. 論理削除用のカラム追加
  4. モデル側の設定
  5. 論理削除のアクション
  6. 論理削除されたユーザの取得と復活処理

開発環境

今回の開発環境は以下の通りです。

  • Linux CentOS 7
  • Apache 2.4
  • MySQL 5.7
  • PHP 7.1/7.2
  • Laravel

Laravelのバージョンに関しては5.6/5.5/5.4/5.3にて動作確認済みです。

また、laravelのソースディレクトリを「laravel/」とします。

基本的な認証機能は導入済みである前提で進めますので、導入までに関しては
Laravelの認証機能でログイン/ユーザ登録/パスワードリセットなどの管理画面を一撃構築する(基本&入門編)
を確認してください。

ユーザの論理削除(ソフトデリート)について

ユーザを削除する際に、物理削除を行う場合は単純にdeleteメソッドを叩けば良いですが、論理削除を行いたい場合は削除フラグ的なものを立て管理していく必要があります。

以下は、マイグレーションを実行し作成したデフォルトでのUsersテーブルのカラム情報です

mysql> show columns from users;
+----------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| email | varchar(255) | NO | UNI | NULL | |
| password | varchar(255) | NO | | NULL | |
| remember_token | varchar(100) | YES | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+----------------+------------------+------+-----+---------+----------------+

デフォルトでは、保持するユーザ情報も最小限の状態なので、論理削除を管理するものは何もありません。

ただし、Laravelでは論理削除のための機能も備わっており、それをこれから実装していきます。

論理削除用のカラム追加

ユーザの論理削除を行う為に、usersテーブルにカラムを追加します。

laravelルートディレクトリに移動し、以下のartisanコマンドを叩いてマイグレーションファイルを追加します。

# Laravelルートディレクトリへ移動する
cd /path/to/laravel

# artisanコマンドでマイグレーションファイルを追加する
php artisan make:migration add_column_softDeletes_users_table --table=users

# 実行結果
[demo@localhost laravel]# php artisan make:migration add_column_softDeletes_users_table --table=users
Created Migration: 2019_04_14_123848_add_column_softDeletes_users_table

laravel/database/migrations 配下に、生成されたマイグレーションファイルが配置されます。

laravel/database/migrations/2019_04_14_123848_add_column_softDeletes_users_table.php
<?php

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

class AddColumnSoftDeletesUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
//
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
//
});
}
}

マイグレーションファイルに、以下のように記述を行います。

<?php

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

class AddColumnSoftDeletesUsersTable extends Migration
{
/**
* 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');
});
}
}

$table->softDeletes(); にてカラムを追加する事で、論理削除を行う事が出来ます。

マイグレーションを実行し、usersテーブルにカラムを追加します。

# Laravelルートディレクトリへ移動する
cd /path/to/laravel

# artisanコマンドでマイグレーションを実行する
php artisan migrate

# 実行結果
[demo@localhost laravel]# php artisan migrate
Migrating: 2018_01_13_084659_add_column_softDeletes_users_table
Migrated: 2018_01_13_084659_add_column_softDeletes_users_table

usersテーブルを確認します。

mysql> show columns from users;
+----------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| email | varchar(255) | NO | UNI | NULL | |
| password | varchar(255) | NO | | NULL | |
| remember_token | varchar(100) | YES | | NULL | |
|
deleted_at | timestamp | YES | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+----------------+------------------+------+-----+---------+----------------+

deleted_at カラムが追加されている事が確認できました。

この deleted_at カラムは、初期値として NULL が入りますが、ここに timestampを挿入する事で、論理削除を行う事が出来ます。

mysql> select id, name, deleted_at from users;
+----+--------+------------+
| id | name | deleted_at |
+----+--------+------------+
| 1 | user01 | NULL |
| 2 | user02 | NULL |
| 3 | user03 | NULL |
| 4 | user04 | NULL |
| 5 | user05 | NULL |
| 6 | user06 | NULL |
| 7 | user07 | NULL |
| 8 | user08 | NULL |
| 9 | user09 | NULL |
| 10 | user10 | NULL |
+----+--------+------------+

モデル側の設定

deleted_atカラムを用いたソフトデリート(論理削除)はEloquent ORMにて行われますので、そのための設定をモデル側に記述します。

まずはUsersテーブルのためのモデルを生成します。
laravel/app 配下に Models ディレクトリを作成します。そして、Laravelルートディレクトリに移動し、以下のartisanコマンドを叩きます。

# Laravelルートディレクトリへ移動する
cd /path/to/laravel

# artisanコマンドでモデルクラスを生成する
php artisan make:model Models/Users

# 実行結果
[demo@localhost laravel]# php artisan make:model Models/Users
Model created successfully.

laravel/app/Models 配下に Users.phpが生成されます。

laravel
├─ app
   ├─ Models
      └─ Users.php
laravel/app/Models/Users.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Users extends Model
{
//
}

ここに、ソフトデリート用の記述を行うと以下のようになります。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Users extends Model
{
use SoftDeletes;

protected $table = 'users';
protected $dates = ['deleted_at'];

}

SoftDeletesトレイトをuseし、メンバ変数$datesに deleted_at をセットする事で、モデル側で論理削除を実行できるようになります。

論理削除のアクション

データベース側の準備ができたので、コントローラ側から論理削除のメソッドを実装します。

ユーザを論理削除する場合は、例えば以下のようなアクションを記述します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Users;


class AccountController extends Controller
{
public function deleteData(Request $request)
{
$user = Users::find($request->input('id'));
$user->delete();
}
}

ここでのポイントは、DBファサードではなく、Eloquent ORM にてdelete()メソッドを実行するという事です。

find()メソッドに削除対象のIDを渡してインスタンスを取得し、delete()メソッドを実行する事でdeleted_atカラムにタイムスタンプが挿入され、論理削除が行われます。

# 論理削除後のユーザテーブル
mysql> select id, name, deleted_at from users;
+----+--------+---------------------+
| id | name | deleted_at |
+----+--------+---------------------+
| 1 | user01 | NULL |
| 2 | user02 | NULL |
| 3 | user03 | NULL |
| 4 | user04 | NULL |
| 5 | user05 | NULL |
| 6 | user06 | NULL |
| 7 | user07 | NULL |
| 8 | user08 | NULL |
| 9 | user09 | NULL |
| 10 | user10 |
2018-01-13 18:11:13 | ← 論理削除が行われた
+----+--------+---------------------+

ちなみに、DBファサードでdelete()メソッドを実行すると、通常の物理削除になります。

実際にユーザ一覧を作成してみてみるとわかりますが、論理削除されたユーザは表示されません。

ただし、ユーザ一覧を取得する際も、DBファサードではなくEloquent ORM で取得を行う必要があります。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Users;

class AccountController extends Controller
{
/**
* 認証ユーザとユーザ一覧情報を返却する
*/
public function index()
{
// Eloquent ORM でユーザ一覧を取得する
$users = Users::all();

return view('account.list', ['users' => $users]);
}

}

どうしてもDBファサードでユーザ一覧を取得したい場合は、単純にWhere句を付ければ論理削除の形になります。

/**
* ユーザ一覧を返却する
* @return mixed
*/
public function getUsers()
{
$users = DB::table($this->table)
->whereNull('deleted_at')
->get();
return $users;
}

そういう意味では、DBファサードでもdeleted_atカラムにタイムスタンプを挿入すれば論理削除にはなります。

ただ、それやるのであれば普通にフラグメント管理してしまった方が良いかなと思うのでここでは割愛します。

論理削除されたユーザの取得と復活処理

通常、論理削除されたデータを参照する場面というのはあまりないと思うのですが、その辺のクエリもLaravel Eloquent ORMには一通り用意されているので紹介しておきます。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Users;

class AccountController extends Controller
{
public function index()
{
// 【1】論理削除済みユーザも含めた一覧を取得する
$users = Users::withTrashed()->get();

// 【2】論理削除済みユーザのみを取得する
$users = Users::onlyTrashed()->get();

// 【3】論理削除されたユーザを復活させる
Users::onlyTrashed()->where('id', 10)->restore();

// 【4】ID 10 より上の論理削除済みユーザを復活させる
Users::withTrashed()->where('id', '>', 10)->restore();

// 【5】論理削除ではなく、物理削除を行う
Users::withTrashed()->where('id', 10)->forceDelete();

// 【6】論理削除ではなく、物理削除を行う(複数件)
Users::withTrashed()->where('id', '>', 5)->forceDelete();

}
}
論理削除済みデータも取得対象にする
// 【1】論理削除済みユーザも含めた一覧を取得する
$users = Users::withTrashed()->get();
withTrashed()メソッドを用いる事で、論理削除済みユーザも含めたユーザの一覧を取得できます。where句などの絞り込みも可能です。
論理削除済みデータのみを取得する
// 【2】論理削除済みユーザのみを取得する
$users = Users::onlyTrashed()->get();
onlyTrashed()メソッドを用いる事で、論理削除済みユーザのみを取得することが出来ます。where句などの絞り込みも可能です。
論理削除からの復活(解除)
// 【3】論理削除されたユーザを復活させる
Users::onlyTrashed()->where('id', 10)->restore();
論理削除済みユーザのモデルインスタンスに対してrestore()メソッドを用いることで、論理削除からもとに戻すことが出来ます。
上記の例の場合、論理削除済みであるユーザID「10」のユーザに対して、リストアメソッドを当てています。
ちなみに上記例の場合、onlyTrashed()とWhere句にて、論理削除されたユーザ1件のみのモデルインスタンスを取得してrestore()メソッドを発動させましたが、withTrashed()メソッドで論理削除されていないユーザも含めたモデルインスタンスにrestore()メソッドを発動させても、リストアは論理削除したユーザにのみ適用され、生きているユーザデータについては無傷です。
そしてその場合、論理削除のユーザが複数件いたとしても、その全てが論理削除を解除されます。
// 【4】ID 10 より上の論理削除済みユーザを復活させる
Users::withTrashed()->where('id', '>', 10)->restore();
物理削除を行う
基本的には論理削除で運用しますが、データ整理などで物理削除も必要になる場合もあります。
forceDelete()メソッドを用いる事で、論理削除ではなく、物理削除を行う事ができます。
// 【5】論理削除ではなく、物理削除を行う
Users::withTrashed()->where('id', 10)->forceDelete();
上記例の場合は、ユーザID「10」のユーザに対して、物理削除を行っています。
また、複数件同時に物理削除を行う事も可能です。その場合は対象を複数件にするだけです。
// 【6】論理削除ではなく、物理削除を行う(複数件)
Users::withTrashed()->where('id', '>', 5)->forceDelete();
上記例の場合は、ユーザID「5」より上のユーザ全てに対して、物理削除を行っています。

まとめ

以上で作業は完了です。
Eloquent ORMを使う事で、デフォルトの認証機能から少しの追加で論理削除を導入することができます。

Laravelの機能を使ってこうして論理削除を実装するもよし、削除フラグを作って自力で論理削除を実装するもよし。どう実装するかは個人の好みによると思いますので、要件によって実装しやすい形で進めていくのが良いかと思います。