意味がないアカウント削除機能を実装する デモも作ったよ

[某所](https://qiita.com/ponkotuy/items/6049388d564fb4385f4e)でレスしたコメントのついでに、技術的に簡単に処理できるユーザ削除の実装を垂れ流します。
概ね元記事に記載されている事を実装しただけです。

[DEMO](http://my-tweet-demo.herokuapp.com/)

- 会員登録
  - ダミーデータで適当にアカウント作ります
- つぶやき
  - 適当な文言を呟きます
  - 攻撃的な呟き　元記事に書いてあったので再現できるように
- いいね！
  - 一応できるように
- 退会
  - 退会すると論理削除する
  - 付属データも全部消す
  - 30　~~日~~分後、完全に削除する　←元記事の言いたい「アカウント削除機能」はこの事だろうと思う

*元記事とは違い、技術者向け、特にLaravelベースで書きます。Rubyistさんごめんね

- Q. 物理削除？論理削除？
  - A. 論理削除です
- Q. 個人情報とユーザ情報は分ける？
  - A. 分けません。
  分ける場合は1対1のテーブル構造になると思うのですが
  実装上、結局リレーション張ってしまうので結局nullを参照して落ちる可能性があるので・・・


# 要件

元記事を参考に、twitter風なやつを作る

- ユーザが退会した場合、ユーザに関連するコンテンツは削除する
- ユーザが退会した場合、個人情報（名前とメールアドレス）を削除する


# 免責

今のところロジックだけの紹介なのでノーテスト、ノーコメント
ユーザー削除以外については適当に書いた。
トランザクションについて未考慮。


# テーブル類

```migrate.php
Schema::create('users', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->string('email')->unique();
    $table->string('password');
    $table->softDeletes();
    $table->rememberToken();
    $table->timestamps();
});

 Schema::create('tweets', function (Blueprint $table) {
    $table->increments('id');
    $table->unsignedInteger('user_id');
    $table->string('message');
    $table->softDeletes();
    $table->timestamps();
    
    $table->foreign('user_id')->references('id')->on('users');
});

Schema::create('likes', function (Blueprint $table) {
    $table->increments('id');
    $table->unsignedInteger('user_id');
    $table->unsignedInteger('tweet_id');
    $table->softDeletes();
    $table->timestamps();
    $table->foreign('user_id')->references('id')->on('users');
    $table->foreign('tweet_id')->references('id')->on('tweets');
});

```

こんな感じ。
外部制約はきちんと貼る。これはアプリケーション側には影響しない。
むしろ、アプリケーション側で無理に削除してしまう事を防ぐ目的で設定。

# 肝となる実装

```User.php
public static function boot()
{
    parent::boot(); // 親のbootを呼ぶ
    self::deleting(function ($user) {
        /* TODO Transaction begin */
        $user->name = 'deleted'; // 名前を消しておく
        $user->email = 'deleted'.time(); // メールアドレスを消す。ユニークカラムなのでtimeを付け足したがuniqidなどもっとかぶりにくい物の方が良い
        $user->save();
        foreach ($user->tweets as $tweet) {
            $tweet->delete();// 関連要素を削除する。今思えばmap使った方がかっこよかった。
        }
　　　　　　　　　　　　　　　　foreach ($user->likes as $like) {
            $like->delete(); // 同上
        }
        /* TODO Transaction commit */
    });
}
```

```Tweet.php
 public static function boot()
    {
        parent::boot();
        self::creating(function ($tweet) {
            $tweet->user_id = auth()->user()->id; //新規作成される時に自動的に現在ログイン中のユーザIDを書き込む
        });
        self::deleting(function ($tweet) {
            /* TODO Transaction begin */
            foreach ($tweet->likes as $like) {
                $like->delete(); // 関連するいいね！を削除する
            }
            /* TODO Transaction commit */
        });
    }
```
Laravel では、bootメソッドをオーバーライドする事で、作成時、更新時、削除時に任意のフックをかける事ができます。
今回はそれぞれコピペしてしまいましたが。creatingの部分に関しては別途Trait化して対応する方がスマートだったと思います。
話は戻りまして、これを利用することで削除時に必ず走る処理が書けるようになりますので、自身が削除される時に、関連する要素を削除するようにしておけば

> 誰がツイートしたか分からない謎のツイート

は発生しません。

また、添付ファイルがある場合は、同様にdeletingで処理すると良いでしょう。
もし時間がかかる処理/複雑な処理な場合は、deletingのタイミングで削除Jobをキューイングしてworkerで処理をするとより良いと思います。

# まとめ

よくある実装だと思うので特筆する事はないんですが、簡単に実装できるFrameworkってスゲーな！って感じです。
語彙力がなくてコードをコピペしてごまかした感じありますね。


# 元記事について

はてブとかみてるとタイトルだけ一人歩きしている感がある。
Qiitaで釣りタイトル炎上が最近のトレンドですね

> 削除フラグ→○

> 今のところのベストは削除フラグに加えて既存機能への影響とコンプラやユーザの心象を考慮しつつ必要なデータを選んで削除するのが理想というところでしょうか。

ここを読めば元記事は妥当な事を言っていると思うし、当記事と実装の差もないと思う。

ただし、

> 優秀なエンジニアを数週間張り付ける

ことは必要なく、あらかじめテスト書きつつ、削除を想定した上流工程、設計があれば実装コストはそんなに重くないはずです。
（顧客が思っているよりは重い）

# コードの全貌

https://github.com/mikkame/laravel-boot-delete-demo