이번에는 이전 포스팅에서 세팅한 이벤트와 리스너를 활용하는 예제에서 이벤트 발생시 리스너에서 실제 메일을 발송하도록 해보자.  



이벤트 발생시 메일을 발송해야 하므로 메엘을 발송하는 로직은 리스너에 있어야 한다. 


참고로 필자는 로컬 개발환경에서 메일을 발송하기 위해 mailtrap(https://mailtrap.io/)같은 Faker를 사용하지 않고 본인의 구글계정에 있는 G메일을 이용하여 실제 메일 발송을 하도록 하였다. 


다양한 테스트를 위해서는 메일 Faker를 이용한 메일 전송 테스트가 편할 것이다. 하지만 만일 본인처럼 실제 메일을 발송하는 환경에서 개발하려고 한다면 G메일을 이용하면 된다. 대부분 구글메일 계정은 가지고 있을 것이므로 구글 메일서버와 관련된 세팅을 해놓으면 간단히 실제 메일을 발송할 수 있다. 라라벨에서 구글 메일을 사용해 실제 메일을 발송하려면 이전 포스팅에서 세팅방법을 적어두었으니 참고하시라.




자 이제 본론으로 돌아와 메일을 발송하도록 하자. 리스너 파일을 열어보자.

<?php

namespace App\Listeners;

use App\Events\TaskCompleted;
use App\Jobs\SendEmailJob;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;

class SendEmailForCompletion
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  TaskCompleted  $event
     * @return void
     */
    public function handle(TaskCompleted $event)
    {
        Log::info(get_class($event) . ' 이벤트를 수신하였습니다.');
        Log::info(sprintf('%s (%s)님에게 완료알림 메일을 보내려고 합니다', $event->task->user->name, $event->task->user->email));

        Mail::to($event->task->user)
            ->send(new \App\Mail\TaskCompleted($event->task));

    }
}
?>

이전 코드와 달라진 점은 Mail 파사드를 이용해 메일을 발송하는 한 줄의 코드 뿐이다.


라라벨 어플리케이션에서 메일을 발송하기 위해서는 Mail 파사드의 to() 메소드로 수신자를 지정하고 send() 메소드를 호출하면 된다. 여기서 send() 메소드는 라라벨의 Mailable 클래스를 받아야 하는데 이 클래스에서 어떤 내용의 메일을 보낼 것인지, 발송자 또는 수신자를 누구로 할 것인지, 블레이드 템플릿을 이용해 HTML 메일을 보낼 것인지 등을 커스터마이징할 수 있다. 


코드를 보면 TaskCompleted라는 클래스의 객체를 send() 메소드의 인자로 지정하였는데 이 TaskCompleted 클래스는 Mailable 클래스를 상속받은 클래스여야 한다. 앞에서 만든 이벤트와 이름은 동일하지만 헷갈리지 말자.


이 클래스는 역시 php artisan 명령어로 생성한다.


php artisan make:mail TaskCompleted


그러면 Mail 폴더가 생성되고 여기에 TaskCompleted 메일 클래스가 생성된다.

<?php

namespace App\Mail;

use App\Models\Task;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;

class TaskCompleted extends Mailable implements ShouldQueue
{
    use Queueable, SerializesModels;

    public $task;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(Task $task)
    {
        $this->task = $task;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('mail')
            ->with([
                'completion' => $this->task->is_complete ? '완료' : '미완료',
                'creationDate' => $this->task->created_at->format('Y-m-d H:i:s')
            ]);
    }
}
?>

이 클래스의 build() 메소드에서 발송할 메일에 대한 여러 가지 옵션을 설정할 수 있다. 여기서는 view라는 이름의 블레이드 템플릿을 사용하여 HTML 메일을 보내고 해당 블레이드 템플릿에서 사용할 변수로 completion과 creationDate라는 이름의 변수를 추가로 세팅하였다.


생성자를 보면 Task 모델 객체를 받는 것을 알 수 있다. 아무래도 발송할 메일에는 완료된 태스크와 관련된 정보를 보여주려고 할 것이므로 완료처리된 태스크의 객체가 필요한데 이렇게 생성자를 통해 클래스에 public 한정자를 갖는 모델 객체를 선언하면 with() 메소드를 사용하지 않고도 블레이드 템플릿에서 참조할 수 있다. 


mail.blade.php 파일의 내용을 보자. 이 파일이 실제로 수신자에게 발송될 메일의 내용이 된다.

<body> <div> 태스크명 : {{ $task->title }}<br/> 등록자 : {{ $task->user->name }} ({{ $task->user->email }})<br/> 완료여부 : {{ $completion }}<br/> 등록일 : {{ $creationDate }} </div> </body>

여기서 태스크명이나 등록자는 메일 클래스에서 선언한 Task 모델객체($task)의 속성을 그대로 출력하는 것을 알 수 있다. 이 모델객체는 물론 Eloquent 모델이 될 것이다. 그리고 나머지 2개 변수는 with() 메소드로 넘겨 받은 별도의 변수로 완료여부와 등록일 정보를 출력한다.  


이제 이렇게 리스너에서 메일을 발송하면 실제로 태스크 등록자의 메일주소로 전송된다. 여기서 코드를 보면 따로 송신자를 지정하지 않았는데 그렇게 되면 기본적으로 메일 환경설정(mail.php)에 세팅된 송신자와 송신자 메일주소로 전송된다. 아래와 같이 mail.php 파일에 정의되어 있다.


/*
|--------------------------------------------------------------------------
| Global "From" Address
|--------------------------------------------------------------------------
|
| You may wish for all e-mails sent by your application to be sent from
| the same address. Here, you may specify a name and address that is
| used globally for all e-mails that are sent by your application.
|
*/

'from' => [
    'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
    'name' => env('MAIL_FROM_NAME', 'Example'),
],


여기서 환경설정 파일(.env)에 세팅된 값을 참조한다. 따라서 환경설정 파일에  원하는 송신자명과 송신자 메일주소로 MAIL_FROM_ADDRESS, MAIL_FROM_NAME 항목의 값을 변경하면 된다.


이제 태스크를 완료처리해보자.



그러면 실제 몇 초의 시간이 소요되지만 메일이 발송되며 완료처리된 태스크의 등록자 메일주소 즉, 현재 로그인한 사용자의 메일주소로 발송된다. 



build() 메소드에서 메일 제목을 별도로 지정하지 않으면 메일 클래스명에서 제목을 뽑아 보낸다. 위의 경우 클래스명이 TaskCompleted였기 때문에 'Task Completed'라는 제목으로 전송된 것이다. 만일 메일 제목을 지정하고 싶다면 build() 메소드내에서 subject() 메소드를 사용하면 된다. 


    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('mail')
            ->subject('태스크가 완료처리되었습니다.')
            ->with([
                'completion' => $this->task->is_complete ? '완료' : '미완료',
                'creationDate' => $this->task->created_at->format('Y-m-d H:i:s')
            ]);
    }


지금까지 라라벨에서 이벤트가 발생했을 때 해당 이벤트의 리스너에서 메일을 발송하는 방법에 대해 알아보았다. 


이 방식에서의 문제점은 이벤트를 처리하는 시간이 너무 오래걸린다는 것이다. 즉, 실제 태스크를 완료처리할 때 까지 몇 초의 시간이 걸리는데 대부분의 지연은 메일을 보내는데서 발생한다. 하지만 실제 대고객 운영환경에서의 이러한 초 단위의 처리시간 지연은 있어서는 안되기 때문에 이렇게 메일을 발송하는 부분을 라라벨이 제공하는 큐(Queue)로 대체하여 문제를 해결해야 한다. 이 부분에 대해서는 다음 포스팅에서 다룰 것이다.


Posted by 라스모르
,