１年間ログインがないユーザをLaravel のタスクスケジューラで定期削除をした話

# はじめに

こちらは、
`KPI 項目B：「Laravelの勉強をする」　Artisanコンソール/タスクスケジュール`
のアウトプット記事です。

# 本編
今回はLaravelのタスクスケジューリング機能を利用して、
１年間ログインがないユーザを自動的に論理削除する機能の実装を試す。


## 準備
### 環境
Laradock等で環境の用意。
今回は以下の環境。

|パッケージ|バージョン|
|---|---|
|Ubuntu| 16.04.6|
|PHP| 7.3.15 |
|PostgreSQL| 9.6.17 | 
|Laravel | 7.2.2 |

### テストデータ

#### テーブル構造（抜粋）
|カラム名|型||Not Null|
|---|---|---|---|---|
| id                | bigint                         | 連番  | not null | nextval('users_id_seq'::regclass)|
| name              | character varying(255)         | 名前  | not null | 
| deleted_at        | timestamp(0) without time zone | 論理削除日          |          | 
| logined_at        | timestamp(0) without time zone | 最終ログイン日|          | 

#### テストデータ作成
| id|name|logined_at|
|---|----|----------| 
| 1 | 管理者                  | 2020-03-23 00:00:00 |
| 2 | 1年間未ログインのユーザ | 2019-03-22 15:00:00 |

今回は**id=2**のユーザに論理削除を行う。

## Cron設定
まずはCronに設定を追加。
今回は下記のパスにプロジェクトを配置しているので、下記を指定する。

```sh
* * * * * cd /var/www && php artisan schedule:run >> /dev/null 2>&1
```

Cronはこれで終わり。

## バッチ処理の作成
今回は、バッチ処理を毎日 **0:00** に実行するように設定する。
なお、削除は**論理削除**とした。
手動での実行も考慮して、`Artisan`コマンドの作成を行う。

### Artisanコマンド作成
まずArtisanコマンドの雛形クラスを作成する。

`Command`クラスを継承した、`BatDeleteNotLoginUserForOneYear`クラスを作成する。

```sh
php artisan make:command BatDeleteNotLoginUserForOneYear
```

実行する処理はこのクラスにある`handle`メソッドへ記載する。
今回は、**最終ログイン日(logined_at)**が、コマンド実行日**以前**のデータを削除する。
雛形クラスを編集し、下記のように変更。

```php:App/Console/Commands/BatDeleteNotLoginUserForOneYear.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Carbon\Carbon;
use App\User as User;

class BatDeleteNotLoginUserForOneYear extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'batch:notloginuser'; //実行時のコマンド：php artisan batch:notloginuser 

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Batch processing of Not Login User for one-year'; //説明

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        // コマンド実行時の削除対象起算日を取得
        $day =  (Carbon::today())   //実行日の時刻　00:00:00
                    -> subYear(1);  //1年前

        // 1年以内にログインがないユーザを取得
        $unloginedUsers = User::where('logined_at',"<",$day)
                            ->get();

        // 取得したユーザに対して
        foreach ($unloginedUsers as $unloginedUser) {
            // 論理削除を実行
            $unloginedUser->delete();
        }
    }
}
```

### スケジューリング
次に作成したコマンドのスケジューリングを設定する。
今回の設定は毎日 **0:00** 。

```php:app/Console/Kernel.php
<?php

namespace App\Console;

use Illuminate\Console\Command;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Facades\App;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        Commands\BatDeleteNotLoginUserForOneYear::class
    ];

    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {
        // スケジューリングするコマンドと実行する時間を指定する
        $schedule->command('batch:notloginuser')
                 ->at('0:00'); //毎日 0:00
    }

    /**
     * Register the commands for the application.
     *
     * @return void
     */
    protected function commands()
    {
        $this->load(__DIR__.'/Commands');

        require base_path('routes/console.php');
    }

    /**
     * Get the timezone that should be used by default for scheduled events.
     *
     * @return \DateTimeZone|string|null
     */
    protected function scheduleTimezone()
    {
        // スケジューリングする際のタイムゾーンを指定する
        return 'Asia/Tokyo';
    }
}
```

## 結果
### 実行
テスト用にタスクスケジュールの実行時間を13:09で指定した結果は以下の通り。

**実行前**

```sql 
select id,name,logined_at,deleted_at from users;
  1 | 管理者                  | 2020-03-23 00:00:00 | 
  2 | 1年間未ログインのユーザ | 2019-03-22 15:00:00 |
```

**13:09 以降**

```sql 
select id,name,logined_at,deleted_at from users;
  1 | 管理者                  | 2020-03-23 00:00:00 | 
  2 | 1年間未ログインのユーザ | 2019-03-22 15:00:00 | 2020-03-23 13:09:02
```
### 結論

無事に１年間ログインがないユーザをバッチ処理で論理削除することができました。

# 参考
**タスクスケジューリング**
[Task Scheduling(Laravel - Official)](https://laravel.com/docs/7.x/scheduling)
[Laravel 7.x タスクスケジュール](https://readouble.com/laravel/7.x/ja/scheduling.html)

**Artisanコンソール**
[Artisan Console(Laravel - Official)](https://laravel.com/docs/7.x/artisan)
[Laravel 7.x Artisanコンソール](https://readouble.com/laravel/7.x/ja/artisan.html)