Laravel、ajaxを用いて、非同期のメール送信APIを作成し、問い合わせ内容をgmailで送信する。

## 目次
[1. 概要](#1.概要)
[2. 追加機能の概要](#2.追加機能の概要)
[3. ルート設定](#3.ルート設定)
[4. メール送信クラスの作成](#4.メール送信クラスの作成)
[5. メール送信APIの作成](#5.メール送信APIの作成)
[6. InquirySendMailメイラブルクラス編集](#6.InquirySendMailメイラブルクラス編集)
[7. メール文面のビューの作成](#7.メール文面のビューの作成)
[8. APIで返却したメッセージを画面表示させる](#8.APIで返却したメッセージを画面表示させる)
[9. おわりに](#8.おわりに)

## 1. 概要

問い合わせフォームから、
問い合わせ内容をgmailで送信するロジックを説明します。



## 2. 追加機能の概要

* お問い合わせページを追加
  * ページURLは、/contact
  * お名前、メールアドレス、タイトル、お問い合わせ内容を入力。
  * SUBMITボタンを押す事で、ユーザーにgmail送信する。

* お問い合わせ内容をgmailで送信（API）
  * ポストメソッドの宛先URLは、/contact/thanks とする。
  * InquiryController内で処理する。
  * データベースへの保存はしない。
  * メールを、管理者と問い合わせした人に送信する。

## 3. ルート設定

```php
Route::controller(InquiryController::class)->group(function() {
    Route::get('/contact', 'index');
});
```

対象ファイルは、 `routes\web.php`
/contact にGETメソッドでアクセスすると InquiryController.phpのindex()メソッドを呼び出し、お問い合わせ画面が表示される。

```php
Route::controller(InquiryController::class)->group(function() {
    Route::post('/contact/thanks', 'store');
});
```

対象ファイルは、 `routes\api.php`
同様に、/contact/thanks にPOSTメソッドでアクセス（SUBMITボタン押下）すると、同じInquiryController.phpのstore()メソッドを呼び出す。

## 4. メール送信クラスの作成

laravelではメール送信するためにMailable(メイラブル)クラスを作成する。
Mailableとメール送信の方法について詳しくは以下参照。

[メール 9.x Laravel](https://readouble.com/laravel/9.x/ja/mail.html)

### **Mailableクラスは、artisan makeコマンドで作成できる。**
```
php artisan make:mail InquirySendMail
```

対象 `app\Mail\InquirySendMail.php`
 →このファイルとMailファサードのtoメソッド使って、メールを送信する。

### **設定ファイルにメールサーバーの情報を入力する。**
gmailのメールアカウントとSMTPサーバーを使ってメールを送信する。
<font color="Red">gmailでメール送信するため、以下手順が必要。</font>

1. gmailで２段階認証の設定。
2. アプリパスワードの発行。
3. SMTPサーバーの設定を`.env`に追加。
（参考：https://qiita.com/hiro5963/items/df062ab19e8ceba4573f）

```
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=admin@gmail.com　// 自身のgmailを設定。
MAIL_PASSWORD=アプリパスワード // 2で作成したアプリパスワードを記載。
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="admin@gmail.com"　// 自身のgmailを設定。
```
→ローカル環境でgmail送信のテストを行いたい場合は、環境設定ファイル`.env` に上記を設定する。

## 5. メール送信APIの作成

対象は `app\Http\Controllers\InquiryController.php `

```php
use App\Mail\InquirySendMail;
use Illuminate\Support\Facades\Mail;
```
MailファサードとInquirySendMailメイラブルクラスを使用するので、use文を記述する。

### **storeメソッド**

```php
/**
* inquiry store
*
* @param Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
    $request->validate([
        'name' => 'required',
        'email' => 'required|email',
    ]);

    Mail::send(new InquirySendmail($request));

    // 再送信を防ぐためにトークンを再発行
    $request->session()->regenerateToken();

    return response(
        [
            'result' => true,
            'message' => 'ありがとうございます。メッセージが送信されました。',
            'csrf_token' => $request->session()->token(),
        ],
    );
}
```

1. `api.php`に定義した、メール送信APIにて呼び出されるメソッド。
2. お名前、メールアドレスは必須入力のバリデーションを指定。
3. 入力データのバリデーションを行った後、MailファサードとInquirySendMailクラスを使用して、メールを送っている。
4. `Mail::`の部分がMailファサード。(sendメソッドを呼び出している。)
5. sendメソッドでは、InquirySendMailクラスで作成するメール内容を実際に送信している。
6. ※`$request->session()->regenerateToken();`は、view側で作成したcsrf_tokenを再発行する事で、お問い合わせの2重送信を防いでいる。
 laravelは、**csrf_token（クロス・サイト・リクエスト・フォージェリ(CSRF)から、アプリケーションを簡単に守ること）** を発行する事が出来、Postリクエストを送る際は、以下のように「**@csrf**」を記載しないとエラーが発生する。 

```html
// metaタグにその値を保存しておく。
<meta name="csrf-token" content="{{ csrf_token() }}">

...略

<form method="POST">
    @csrf
    ...
</form>
```

## 6. InquirySendMailメイラブルクラス編集

**全体コード**

```php
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Http\Request;
use Illuminate\Queue\SerializesModels;

class InquirySendmail extends Mailable
{
    use Queueable, SerializesModels;

    private $name;
    private $email;
    private $title;
    private $body;

    /**
     * Create a new message instance.
     *
     * @param Request $request
     * @return void
     */
    public function __construct(Request $request)
    {
        $this->name = $request->get('name');
        $this->email = $request->get('email');
        $this->title = $request->get('title');
        $this->body = $request->get('body');
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        $mail_admin = config('mail.from.address');

        return $this
            ->from($mail_admin, '自身の名前')
            ->to($this->email)
            ->bcc($mail_admin)
            ->subject('お問い合わせを受け付けました')
            ->text('inquiry.mail')
            ->with([
                'name' => $this->name,
                'email' => $this->email,
                'title' => $this->title,
                'body' => $this->body,
            ]);
    }
}
```
### **バリデート済みのデータを受け取るので、Requestクラスごと受け取る。**

```php
public function __construct(Request $request)
{
    $this->name = $request->get('name');
    $this->email = $request->get('email');
    $this->title = $request->get('title');
    $this->body = $request->get('body');
}
```
`$this->`が、このクラス内のデータで、constructメソッドの引数が受け渡される仮引数。

```php
public function build()
{
    $mail_admin = config('mail.from.address');

    return $this
        ->from($mail_admin, '自身の名前')
        ->to($this->email)
        ->bcc($mail_admin)
        ->subject('お問い合わせを受け付けました')
        ->text('inquiry.mail')
        ->with([
            'name' => $this->name,
            'email' => $this->email,
            'title' => $this->title,
            'body' => $this->body,
        ]);
}
```

`resources\views\mail\mail.blade.php`をビューとして指定する。

->演算子(アロー演算子)でつなげて、`text`メソッドで、ビューにデータを渡す。キーが’name’なのでviewの中で`$name`という名前で参照することができる。

さらに、アロー演算子で subjectメソッドをつなげることで、メールの表題を設定することができる。

## 7. メール文面のビューの作成

メール文面は、 `resources\views\mail\mail.blade.php`を指定。

```html
{{ $name }} さま

お問い合わせいただき、
ありがとうございます。

【お問い合わせ内容】

お名前　: {{ $name }}

メール　: {{ $email }}

タイトル　: {{ $title }}

内　容　:
{!! $body !!}
```

`$name`等で渡されたデータを`{{ }}`内にて表示。

## 8. APIで返却したメッセージを画面表示させる。

1. ajaxを用いて、非同期でメール送信APIを呼び出す。
 →**jquery**を使用する。　
 対象 `app\public\js\contact_ajax.js`
 
### **初期状態では、レスポンスメッセージ表示用クラスを非表示にしておく**

```javascript
$(function () {
    $(".response-output").hide();
    
    ....以下略
```
1行目の`$(function(){`では、即時関数を宣言している。`.response-output`は、htmlの親要素。（後のajax処理で、子要素を追加する。）

### **送信ボタンが押された時だけ、API通信を開始する。**

```javascript
$(function () {
    $(".response-output").hide();

    $("#submit-button").click(function () {
        $(".response-output").hide();
        $(".success-response").remove();
        $(".error-response").remove();
        
....以下略
```

メッセージの画面表示有無に関わらず、最初にhide()で要素を隠し、removeで要素を削除する。
 　→メッセージはjqueryのアニメーションでスライド表示させる為、hide()で隠す。
 　→ボタン多重押下時に、メッセージが重複して表示されるのを防ぐための対応。

### **form入力データを取得し、変数に代入する**

```javascript
var name = $("input[name=your-name]").val();
var email = $("input[name=your-email]").val();
var title = $("input[name=your-title]").val();
var body = $("textarea[name=your-body]").val();
```

### **$.ajaxメソッドで通信を行う。**

```javascript
    $.ajax({
        headers: {
            "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
        },
        type: "POST",
        dataType: "json",
        url: "api/contact/thanks",　　
        timeout: 30000,
        data: { name: name, email: email, title: title, body: body }, 
    })
    .done((data) => {
            
    })
    .fail((data) => {
            
    });
```
1. `headers`は、リクエスト時に送信するヘッダー。
**<font color="Red">Ajax通信でも同様に、X-CSRF-TOKENを含めないと419エラーが発生するため、headerに含める必要がある。</font>**

2. `type`は通信に利用するHTTPメソッド（今回はメール送信なので、POST）
3. `dataType`は、応答データの種類（レスポンスはjson型の為、’json’を指定）
4. `url`は、アクセス先のパス（inquiryController@storeメソッドにアクセスする。）
5. `timeout`は、指定秒数を経過しても通信が完了しない場合、エラー処理が実行される。
6. `data`は、inquiryController@storeメソッドに送信するデータ（form入力値）
7. `done`は、通信に成功した時に実行される。
8. `fail`は、通信に失敗した時に実行される。

### **API通信成功時、サクセスメッセージを表示させる**

```javascript
.done((data) => {
    $(".response-output")
        .append(
            '<p class="success-response">' + data.message + '</p>'
        )
        .slideDown();
})
```
* `data`はサーバーが返すデータで、お問い合わせAPIのレスポンスメッセージは、`data.`キーで取得できる。
* 「`data.message`」`response-output`というhtmlの親要素に、`append()`でレスポンスメッセージを表示させるための子要素を追加している。
* 親要素は、`hide()`で隠しておいたので、`.slideDown()`で追加した子要素をスライド表示させる事ができる。

### **API通信失敗時、アラートメッセージを表示させる**

```javascript
.fail((data) => {
    var error_text = "入力内容に問題があります。確認して再度お試しください。";
    if (data.statusText == "timeout") {
        error_text = "通信に失敗しました。画面を更新して再度お試しください。";
    }
    $(".response-output")
        .append(
            '<p class="error-response">' + error_text + '</p>'
        )
        .slideDown();
    });
```
* ajax通信が、30秒経っても成功しない場合は、error_textの中身を変更して親要素に追加する。

**全体コード**

```javascript
$(function () {
    $(".response-output").hide();

　   $("#submit-button").click(function () {
        $(".response-output").hide();
        $(".success-response").remove();
        $(".error-response").remove();

        var name = $("input[name=your-name]").val();
        var email = $("input[name=your-email]").val();
        var title = $("input[name=your-title]").val();
        var body = $("textarea[name=your-body]").val();
        
        $.ajax({
            headers: {
                "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
            },
            type: "POST",
            dataType: "json",
            url: "api/contact/thanks",　　
            timeout: 30000,
            data: { name: name, email: email, title: title, body: body }, 
        })
        .done((data) => {
            $(".response-output")
                .append(
                    '<p class="success-response">' + data.message + '</p>'
                )
                .slideDown();
        })
        .fail((data) => {
            var error_text = "入力内容に問題があります。確認して再度お試しください。";
            if (data.statusText == "timeout") {
                error_text = "通信に失敗しました。画面を更新して再度お試しください。";
            }
            $(".response-output")
                .append(
                    '<p class="error-response">' + error_text + '</p>'
                )
                .slideDown();
        });
    });
});
```

### 9. おわりに
上記手順で非同期にメール送信APIを実行できます。
今回、詳細なviewは記載していないので、お任せでお願いします。
環境によってうまく動かない場合があると思いますが、あくまでも自身のアウトプットのためにまとめており、誰かの参考程度になれば嬉しいな程度のため、ご理解いただけますと幸いです。