nanisore oishisou

Webエンジニアあるまさんのゆるふわ奮闘記。

Laravel5.4でイベント&キューを使ってメールをキュー送信する手順(2)

8月はコラボキャンプなどの準備もあって、だいぶ時間が空いてしまいました。。

気を取り直して、メールをキュー送信する手順の続きを書いていきますよ★

↓前回の記事はこちら arm4.hatenablog.com

今北さんのために(1)を3行にまとめます。

  • Laravelのアプリを作って初期設定をした
  • ユーザーがログインできるようにした
  • ダミーのユーザーとデータを作った

まとめたら、逆にちょっと悲しくなりましたが、頑張って続きを書くぞ!

ダミーの投稿記事データを作ったので投稿記事一覧ページを作成する

まずはviewを作ろう。

もう一度おさらいするが、今回つくるのはブログ記事にユーザが「いいね」を付けられるようなアプリケーションである。

記事の本文とそれに「いいね」する用の「いいね」ボタンを設置しておけばいいだろう。

今回は簡単に、ホーム画面に一覧を表示することにするので/resources/views/home.blade.phpを以下のように書き換える。

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            @include('_alert')
        @foreach($posts as $post)
            @include('_post_panel')
        @endforeach
            <div class="text-center">{{ $posts->links() }}</div>
        </div>
    </div>
</div>
@endsection

投稿記事の部分、メール送信のお知らせメッセージを表示する部分などは詳細ページなどでも使いそうなので、パーシャルファイルにしておく。

/resources/views/_post_panel.blade.php

<div class="panel panel-default js-post" data-post_id="{{ $post->id }}">
    <div class="panel-heading">{{ $post->title }}</div>
    <div class="panel-body">
        <p class="small text-right">{{ $post->user->name }} {{ $post->updated_at->format('Y-m-d') }}</p>
        {!! nl2br(e($post->content)) !!}
    </div>
    <div class="panel-footer text-center">
        <button class="btn btn-primary js-send-stamp" data-type="like" type="button">いいねする</button>
        <button class="btn btn-danger js-send-stamp" data-type="dislike" type="button">やだねする</button>
        <button class="btn btn-info js-send-stamp" data-type="whatever" type="button">どうでもいい</button>
        <button class="btn btn-warning js-send-stamp" data-type="yourname" type="button">君の名は?</button>
    </div>
</div>

/resources/views/_alert.blade.php

<div class="alert alert-success hidden">
    <strong>メール完了</strong> メールを送りました!
</div>

今回はviewsディレクトリの直下にパーシャルファイルを突っ込んでしまったが、本番のアプリケーションでは/resources/views/partialsなどのようにパーシャルファイルだけを入れておくディレクトリを切ったほうがいいだろう。

投稿記事を1つだけ表示する詳細画面のviewも作る

/resources/views/post.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            @include('_alert')
            
            @include('_post_panel')
        </div>
    </div>
</div>
@endsection

詳細画面のルーティング設定を追加

詳細画面用のルーティングを追加しておこう。

routing設定では、{}で囲まれた部分でパラメータを受け取ることができるのだが、この変数名をコントローラーのほうの引数のタイプヒンティングされた変数と合わせておくと
パラメータとして渡されたidに対応するModelのインスタンスをLaravelが自動的に取得してきてくれる。

もし見つからなかった場合は404を返してくる。

これがLaravelのルートモデルバインディングという機構である。

これで、いちいちコントローラーのアクションの中でModel::find($id)のように当たり前のことを何度も書かなくてよくなるので、コントローラーを綺麗に保つことができる。

とても便利!!

/routes/web.php

・
・
Route::get('/home', 'HomeController@index')->name('home');
Route::get('/posts/{post}', 'HomeController@posts')->name('posts.detail');

コントローラーの設定をする

詳細画面のアクションでは、ルートモデルバインディングを使いたいのでPostクラスをタイプヒンティングし変数をルーティングのほうと揃えておく。

/app/Http/Controllers/HomeController.php

<?php

namespace App\Http\Controllers;

use App\Post;

class HomeController extends Controller
{
・
・
・
    public function index()
    {
        $posts = Post::latest('updated_at')
                            ->paginate(5);
        return view(
                        'home',
                        compact('posts')
                    );
    }

    public function posts(Post $post)
    {
        return view(
                        'post',
                        compact('post')
                    );
    }

ここまで設定したらページがちゃんと表示されているか確認してみよう。

これで必要な画面が出来上がった。

メール送信の設定をする

fromはアプリケーションから送信される際の送信元の設定。

toのほうは通常は設定しないのだが、開発時に送信されるメールを確認したい&一意のアドレスに送信したい場合のみ、このように設定するとコードで送信先を設定しても、必ずここのtoで設定されたアドレスに送信されるので、開発時は設定しておくことをオススメする。

toにはダミーのアドレスではなく普段自分が使っている生きているアドレスを書くこと!

そうじゃないとテストのメールが送信されないのでメールが送信できているかどうか確認ができない。

config/mail.php

    'from' => [
        'address' => 'noreply@example.com',
        'name' => 'mailable_test',
    ],

    'to' => [
        'address' => 'test@example.com',
        'name' => 'お客様',
    ],

メールドライバをsendmailに設定。

.env

MAIL_DRIVER=sendmail

メール送信用のクラスを作成

いよいよ本題のメール送信部分を作っていく。

今回はLaravel5.3から実装されたMailableというメール専用クラスを作成して実装していくことにする。

ひとまず私はSendMailという名前でクラスを作ることにした。

本当のアプリケーションでは様々なメールを送信するはずなので、登録お知らせメールならRegisteredとか、出荷お知らせメールならOrderShippedなどと「何をした」のように命名するとよさげ。

php artisan make:mail SendMail

/app/Mail/というディレクトリは、プロジェクト作成時は存在しないが、コマンドを実行すると勝手に作成されて、その中にSendMail.phpというファイルが格納される。

それでは作成されたSendMailを編集していこう。

/app/Mail/SendMail.php

<?php
use App\Post;
use App\User;
・
・
・
     public $post;
     public $triggered_user;
     public $data;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(Post $post, User $triggered_user, $data)
    {
        $this->post = $post;
        $this->triggered_user = $triggered_user;
        $this->data = $data;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('emails.alert')
                    ->subject(env('APP_NAME').'からの通知')
                    ->with([
                            'to_user' => $this->to[0],
                        ]);
    }

メールテンプレート用のviewを作成

/resources/views/emails/alert.blade.php

<div>{{ $to_user['name'] }}さんへのお知らせです。</div>
<div>{{ $triggered_user->name }}さんが「{{ $post->title }}」という投稿に{{ $data['type'] }}しました!</div>
<br>
<div>▼投稿を確認する</div>
{{ route('posts.detail', ['post' => $post->id]) }}

コントローラーにいいね送信用のアクションを追加する

/app/Http/Controllers/HomeController.php

<?php
・
・
use Auth;
use App\Post;
use App\Mail\SendMail;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;

class HomeController extends Controller
{
・
・
・
    public function sendLike(Request $request, Post $post)
    {
        $triggered_user = Auth::user();

        $data = [
            'type' => 'いいね',
        ];

        Mail::to($post->user)
                ->send(new SendMail($post, $triggered_user, $data));

        return response()->json(['status' => 0]);
    }

いいね用ルーティングを追加する

/routes/web.php

<?php

Route::post('/send-like/{post}', 'HomeController@sendLike')->name('send.like');

app.jsにajaxでpostするコードを追加

今回はajaxでpostすることにする。

app.jsに以下のコードを追加しよう。

/resources/assets/js/app.js

$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

$(function() {
   $(document).on('click', '.js-send-stamp', (e)=> {
        const $this = $(e.target);
        const type = $this.data('type');
        const post_id = $this.closest('.js-post').data('post_id');

        var data = {
            type,
        };

        $.ajax({
            url: '/send-' + type + '/' + post_id,
            type: 'POST',
            dataType: 'json',
            data: data,
        })
        .done(function(result) {
            $('.alert').removeClass('hidden');
            console.log("success");
        })
        .fail(function() {
            console.log("error");
        })
        .always(function() {
            console.log("complete");
        });
   })
});

f:id:arm4:20170906170139p:plain

「いいねする」ボタンをクリックして「メール完了 メールを送りました!」のお知らせが表示されていたら、toに設定したメールアドレスの受信箱を確認してみよう。

メールが届いていたら、ひとまずメールの送信まではOKだ。

いいねを拡張し、様々なパターンのお知らせを作成する

ここまでは、初心者用チュートリアルと同じようなことしか書いていないので引っかかる場所はないと思う。

今度はこのコードを拡張して「やだねする」「どうでもいい」「君の名は?」ボタンの実装を行っていく。

ここでいったん、コードを提示する前に要件を確認していこう。

君はプログラマだ。

ブログに「いいね」されると、記事を書いた本人にメールでお知らせしてくれるアプリケーションの実装は完了している。

ここで、SEにユーザーのリアクションを「いいねする」「やだねする」「どうでもいい」「君の名は?」という4種類に拡張したいと言われる。

さらに、4種類のボタンを押した際のお知らせメールの送信先を以下のようにしたいとも言われた。

  • いいねのときは登録ユーザー全員に知らせる
  • やだねのときは本人だけに知らせる
  • どうでもいいときは誰にも知らせない
  • 君の名は?のときは本人だけに知らせる

さて、どのようにコードを修正するべきだろうか。

コントローラーのアクションを4種類作って、その中に別々の処理を書く?

それとも1つのアクションでif文をつかって分岐させる?

urlを別々にしたいなら、「やだね」と「君の名は」の処理は一緒だからコントローラーにprivateメソッドを作って呼び出すようにする?

様々な実装方法があると思うが、今回はそれぞれ別アクションにして、そのアクション内でイベントを発火させて、そのイベントが発火したら実行されるリスナークラスにメール送信のコードを書いていこうと思う。

このようにすれば、今後いろいろなボタンが増えたとしても、メール送信パターンは決まっているだろうから、簡単に拡張することができる。

もし、アクションをやっぱり1つにして分岐にしようと思っても容易に対応できる。

さらに、イベントリスナーを使うメリットとしては、そのリスナーの実行コードを簡単にキュー化(非同期処理)にできるので、例えばユーザーが2000人いて、そのユーザー全員にメールを送信しなければいけない場合でも、イベント発火後にバックグラウンドで送信処理をしてくれるため、ユーザーは送信処理の終了を待たずに、次のアクションに移れるというわけだ。

ということで次回は、今回の手順の肝であるイベントを作成していく。

待て、次回!!