Laravel5.1におけるUserの論理削除機能の実装

Qiita初投稿です。

個人のブログを書いていますが、技術的なことはQiitaに書いていこうと思い今回がその初回ということになります。

まだまだ勉強中の身で、情報の発信というよりは自分が学習したことの整理や記録という意味合いが強いですが、もし誰かの役に立ったならばそれはそれで大変嬉しいです。

# 概要
論理削除とは、データベースからデータそのものを物理的に削除するのではなく、そのレコードに対して削除の操作がなされた時刻のタイムスタンプを保持し、それをフラグのように扱うことで見かけ上削除されたような挙動をさせることをいいます。

削除された時刻のタイムスタンプが入るカラムをテーブルに追加し、そこに時刻が入っていればそのデータは削除されたものとして扱うといった形になります（削除されていない通常のデータの場合はnullが入ります）。

このように処理をすることで削除からの復活処理が容易になります。物理的に削除したデータは基本的に戻すことができませんが、論理削除ならばフラグを戻すだけで簡単に復活させることができます。

Laravel5.1を使ったアプリケーションでこの機能を実装した際に、どのようにコードを書いたかを示していきます。

# 環境
- Laravel 5.1
- PHP 5.5
- MySQL
- heroku

# 大まかな流れ
- マイグレーションファイルの作成
- マイグレーションファイルの記述
- マイグレーションの実行
- モデル（User.php）への記述
- tinkerでの動作確認
- コントローラ（UsersController.php）への記述
- ルーティング
- Viewの作成
- 今後
- 参考にさせていただいた記事

# マイグレーションファイルの作成
まずUsersテーブルに論理削除用のカラムを追加するためのマイグレーションファイルを作成します。

```php
>php artisan make:migration add_column_softDeletes_users_table -table=users
```

# マイグレーションファイルの記述

up()とdown()にそれぞれカラムの追加と削除のコードを記述します。

```php:add_column_softDeletes_users_table.php

//中略

public function up()
  {
    Schema::table('users', function (Blueprint $table) {
      $table->softDeletes();
    });
  }

//中略

public function down()
  {
    Schema::table('users', function (Blueprint $table) {
      $table->dropColumn('deleted_at');
    });
  }
```

# マイグレーションの実行
```php
>php artisan migrate
```
この時点でDBのテーブル構成を確認してみるとdeleted_atカラムが追加されているのがわかるかと思います。

# モデル（User.php）への記述
プロジェクトを作成した時点でUserモデルはできているかと思いますが、ここでの記述はSoftDeletesトレイトのuseと、メンバ変数$datesにdeleted_atを追加する一文です。

```php:User.php
class User extends Model implements AuthenticatableContract,
                                    AuthorizableContract,
                                    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword;
    //SoftDeletesトレイトをuse
    use SoftDeletes;

    /**
     * The database table used by the model.
     *
     * @var string
     */
    protected $table = 'users';

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = ['name', 'email', 'password'];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = ['password', 'remember_token'];
    //メンバ変数にdeleted_atを追加
    protected $dates = ['deleted_at'];

}
```

# tinkerでの動作確認
ここまででデータベースに対する操作の準備ができたので、あとはコントローラにメソッドを書いていけばいいのですが、その前にtinkerで動作確認してみることをおすすめします。

```php
>php artisan tinker

>>>$user = App\User::find(3)
>>>$user->delete()
```
User::findメソッドには実在するuserのidを渡してあげてください。ここでは仮に3としていますが、実在していれば何でもいいです。$user->delete()メソッドで論理削除を行っています。

上記を実行した状態でデータベースを確認してみると、今回作成したdeleted_atカラムにタイムスタンプが入っているはずです。

もとに戻すには下記のメソッドを実行します。

```php
>>>App\User::onlyTrashed()->where('id', 3)->restore()
```

# コントローラ（UsersController.php）への記述
tinkerで動作を確認したとおりにコードを書きます。Userの削除はdestroyメソッドに書いていきます。ここでは削除後のリダイレクト処理も併せて書いています。

```php
public function destroy($id)
{
    $user = User::find($id);
    $user->delete();
    return redirect('/');
}
    
```

# ルーティング
```php:routes.php
Route::group(['middleware' => 'auth'], function () {
    Route::resource('users', 'UsersController', ['only' => ['index', 'show', 'destroy']]);
    Route::get('delete_confirm', 'UsersController@delete_confirm')->name('user.delete_confirm');
});
```
上述したとおりUsersControllerのdestroyアクションが実際の削除処理ですが、削除の確認メッセージを出力するためにdelete_confirmというアクションを追加しています。これは単に削除確認メッセージのviewを表示するだけのアクションです。

ただ↑のように書いてはみたものの、適切な書き方なのかどうかよくわかっていません。ルーティングの書き方のセオリーのようなものがわからないため、とりあえず機能の実装に必要なルーティングを特に深い考えもなく書いています。このあたりは今後学習が必要なところだと思っています。

# Viewの作成
削除確認のメッセージを表示するためのviewを作成しました。

```php:views/users/del_confirm.blade.php
@extends('layouts.app')

@section('content')
    <div class="panel panel-default">
    	<div class="panel-body">
    		<p>アカウントを削除すると、ログインができなくなり、登録した文も全て削除されます。</p>
    		<p>本当に削除してよろしいですか？</p>
    	</div>
    </div>
    <div class="col-md-2 col-md-offset-5">
        {!! Form::open(['route' => ['users.destroy', Auth::user()->id], 'method' => 'delete']) !!}
            {!! Form::submit('削除する', ['class' => 'btn btn-danger btn-block']) !!}
        {!! Form::close() !!}
    </div>
    <div class="col-md-2 col-md-offset-5 spacer">
        {!! link_to_route('users.show', '削除しない', ['id' => Auth::user()->id], ['class' => 'btn btn-primary btn-block']) !!}
    </div>
@endsection
```
Bootstrap用のコードがあってちょっとごちゃごちゃしていますが、基本的には↑で書いたusers.destroyへ導くボタンとusers.showに帰るボタンを配置しているだけです。

# 今後
一応これでユーザが自分自身でアカウントを削除することができるようになりました。
今後必要になりそうな処理を想像してみると、以下のような感じかと思います。

- 論理削除したデータの物理削除
- 論理削除のn日後に物理削除を自動で実行
- 論理削除されたデータを復活させる機能

などが考えられるかと思いますが、とりあえずユーザがアカウントを削除したくなったときの最小限の処理としては上記の実装で実現できるかと思います。

# 参考にさせていただいた記事
- [Laravelの認証機能でユーザ情報の論理削除（ソフトデリート）を実装する](https://www.ritolab.com/entry/53) 

↑とても詳しく説明されているので一読をおすすめします。基本的に↑に書いてあることしかやっていません。↑の記事に書いてあることのうち、機能の実装に最低限必要な部分を抜き出したのがこの記事だと思ってください。

- [Laravelのルーティング書き方まとめ - Qiita]
(https://qiita.com/michiomochi@github/items/de19c560bc1dc19d698c)

直接この記事には関係ありませんが、ルーティングの書き方がきれいにまとめられています。