Laravelでcron実行、その日に追加されたDBデータをメール送信

Laravelでcron実行し、当日にテーブルに追加されたデータを抽出して、翌日0時にメール送信するバッチ処理のメモ。
userテーブル1に対しtodoテーブル多のリレーションの、todoテーブルに当日追加されたデータをまとめてメールで送信する。

# 環境
- Laravel 10.4.1
- さくらVPSサーバー CentOS7
- PHP 8.2.4 

# 前提
Laravelでのメール送信設定ができていること

# Commandの作成
SendTodosAddedTodayというコマンドを作成。
```bash
php artisan make:command SendTodosAddedToday
```
\Laravelapp\app\Console\Commands\SendTodosAddedToday.php
が作成される。このファイルにコマンドの実行内容を追記する。

```PHP:SendTodosAddedToday.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\Todo;

class SendTodosAddedToday extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'SendTodosAddedToday';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Send Todos Added Today';

    /**
     * Execute the console command.
     */
    public function handle(): void
    {

        // 0時0分に実行するので、昨日の日付をtodayに入れる
        $today = date("Y-m-d", mktime(0, 0, 0, date("m"), date("d") - 1, date("Y")));
        // $today = date("Y-m-d", mktime(0, 0, 0, date("m"), date("d"), date("Y")));    //テスト用（今日）


        $todos = Todo::select('*')   //todoモデルを使用
            ->join('users', 'todo.user_id', '=', 'users.id')
            ->whereDate('add_date', $today)
            ->orderBy('todo.id','asc')
            ->get();

        //結果が0件の場合
        if($todos->isEmpty()) { 
            echo "\n" . '本日のTodo追加はありませんでした。' . "\n";
        }
        //結果が1件以上ある場合
        foreach($todos as $todo) {
            echo "\n" . '-------------------' . "\n";
            echo 'title：' . $todo->title . "\n";
            echo 'name：' . $todo->name .'(' . $todo->email . ')' . "\n";
            echo 'add_date：' . $todo->add_date . "\n";
            echo 'todo：' . "\n" . $todo->todo . "\n";
        }
        //最後に追加
        echo "\n" . '送信元：' . config('app.url') . "\n";
        return;
    }
}
```

$signatureにコマンドの名前を登録。コマンドを実行する際に利用する。
```PHP:SendTodosAddedToday.php
protected $signature = 'SendTodosAddedToday';
```

$descriptionにはコマンドの説明を記載。
```PHP:SendTodosAddedToday.php
protected $description = 'Send Todos Added Today';
```

handle()には実行する処理内容を記述。
```PHP:SendTodosAddedToday.php
public function handle(): void
{
// 0時0分に実行するので、昨日の日付をtodayに入れる
$today = date("Y-m-d", mktime(0, 0, 0, date("m"), date("d") - 1, date("Y")));
// $today = date("Y-m-d", mktime(0, 0, 0, date("m"), date("d"), date("Y")));    //テスト用（今日）


$todos = Todo::select('*')   //todoモデルを使用
    ->join('users', 'todo.user_id', '=', 'users.id')
    ->whereDate('add_date', $today)
    ->orderBy('todo.id','asc')
    ->get();

//結果が0件の場合
if($todos->isEmpty()) { 
    echo "\n" . '本日のTodo追加はありませんでした。' . "\n";
}
//結果が1件以上ある場合
foreach($todos as $todo) {
    echo "\n" . '-------------------' . "\n";
    echo 'title：' . $todo->title . "\n";
    echo 'name：' . $todo->name .'(' . $todo->email . ')' . "\n";
    echo 'add_date：' . $todo->add_date . "\n";
    echo 'todo：' . "\n" . $todo->todo . "\n";
}
//最後に追加
echo "\n" . '送信元：' . config('app.url') . "\n";
return;

}
```
上部で、
use App\Models\Todo;
してtodoモデルを使い、当日に追加された、userテーブルとjoinしたtodoテーブルのデータを引っ張ってくる、という仕組みです。

ちなみに、Laravel10以前では（？）\Laravelapp\app\Console\Kernel.php
に手動で追記してコマンドの登録が必要でしたが、Laravel10では、Kernel.phpの中身が、commandsフォルダ以下にあるファイルを自動的に読みに行くようになっており、登録は不要でした。
（コマンドファイルは、commandsフォルダ以下に自動で作成され、それが自動でロードされる）
```PHP:Kernel.php
protected function commands(): void
{
    $this->load(__DIR__.'/Commands');
    require base_path('routes/console.php');
}
```



# Commandの登録確認（呼び出して使えるか）
php artisan listコマンドの実行で、下記のように先ほどSendInspiring.phpで登録した「SendTodosAddedToday」コマンドが出てくるか確認。
確認できました。
```bash
> php artisan list
・・・（略）
Available commands:
  SendInspiring           Send Inspiring message via email
  SendTodosAddedToday     Send Todos Added Today
  about                   Display basic information about your application
  clear-compiled          Remove the compiled class file
・・・（略）
```

# 登録したCommandの実行
コマンドの登録が確認できたので、php artisan を使って実行させてみる。
データを入れず実行すると、データなしの結果、
```bash
# php artisan SendTodosAddedToday

本日のTodo追加はありませんでした。

送信元：http://***.***.***.***
```
データを追加して実行すると、データありの結果が表示された。
```bash
# php artisan SendTodosAddedToday

-------------------
title：図書館本返却
name：test name(****@gmail.com)
add_date：2023-04-20 16:49:46
todo：
**図書館に***を返却

送信元：http://***.***.***.***
```
※ちなみに、テストのため、今日追加されたデータを出るように
一時的にコードを変更した。動作が確認できたら戻す。
```diff_PHP:SendTodosAddedToday.php
- $today = date("Y-m-d", mktime(0, 0, 0, date("m"), date("d") - 1, date("Y")));
+ $today = date("Y-m-d", mktime(0, 0, 0, date("m"), date("d"), date("Y")));   //テスト用（今日）
```

# サーバーにCronへの登録
laravelのタスクスケジュールを設定するために、サーバーでcronに下記の1行を追加。`crontab -e`コマンドで。
※path-to-your-projectはLaravelをインストールしたフォルダを指定。各自の環境により異なる。

```bash
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
```
cron設定がすべて*(アスタリスク)になっているので、毎分`php artisan schedule:run`が実行される。
なお、laravelでのコマンド実行は、Laravelで実際にKernel.phpファイルにスケジュールされた頻度になる。
→Kernel.php に`->daily()`を指定すると、毎日0時実行、
　　　　　 　`->dailyAt('07:00')`だと、毎日AM7時になる。

# Laravelのタスクスケジュールへの登録
コマンドの単独での動作確認ができたので、定期的に実行されるように、Kernel.phpファイルのschedule()に登録を行います。

```PHP:Kernel.php
protected function schedule(Schedule $schedule): void
{
    // その日追加されたTodoを24時に送る。
    //昨日の日付を取得（0時に実行、前日のTodoを取得）
    $today_tmp = date("Y/m/d", mktime(0, 0, 0, date("m"), date("d") - 1, date("Y"))) ;
    $date = date("w", strtotime("-1 day"));
    $today = $today_tmp . '(' . \App\Consts\CommonConst::TODO_WEEK[$date] . ')';

    $schedule->command('SendTodosAddedToday')
                // ->everyMinute()  //テスト用
                ->daily()           //毎日深夜１２時に実行
                ->name($today . " に追加されたTodo")    //emailのタイトルになる
                ->sendOutputTo(storage_path('logs/TodosAddedToday.log'))
                ->emailOutputTo(\App\Consts\CommonConst::EMAIL_SendTodosAddedToday);   
}
```
まずは、Kernel.phpでの頻度を`->everyMinute()`にし、実際にcronでメールが送信されるかを確認します。 
（Laravelでのメール送信設定は既にできていて、メール送信ができることを前提とします）
ここでは、コマンドを実行すると、
`/laravelapp/storage/logs/TodosAddedToday.log`
が作成され、同じ内容がemailで届く、という流れになります。
`\App\Consts\CommonConst::EMAIL_SendTodosAddedToday`
は自分で指定した定数で、送信先メールアドレスになります。
（'****@gmail.com' のような直書きでも動作します）
`\App\Consts\CommonConst::TODO_WEEK`　も自分で指定した定数で、漢字の曜日配列になります。
`public const TODO_WEEK = ["日", "月", "火", "水", "木", "金", "土"];`

- 動作確認成功。毎分1回、スケジュールで送信されたメールの例
```e-mail
subject：2023/04/18(火) に追加されたTodo
to:		****@gmail.com
from:	Laravel APP <****@gmail.com>
本文：
-------------------
title：図書館本返却
name：test name(****@gmail.com)
add_date：2023-04-20 16:49:46
todo：
**図書館に***を返却

送信元：http://***.***.***.***
-------------------

title：スーパーで買い物
name：test name2(****@gmail.com)
add_date：2023-04-20 17:53:46
todo：
買い物１
買い物２

送信元：http://***.***.***.***
```
toの送信先、subjectはKernel.phpで指定したもの、
from部分は、Laravelの環境設定（.envファイル）の
`MAIL_FROM_ADDRESS=`
`MAIL_FROM_NAME=`
で指定したものになります。


ここで、Kernel.phpの記載を、
```diff_PHP:Kernel.php
- ->everyMinute()   //テスト用
+ ->daily()         //毎日深夜１２時に実行
```
のように変えると、毎日AM0時に、前日にtodoテーブルに追加されたデータがメール送信される、バッチ処理が動作したことを確認できました。