Laravel Mailables - Những thay đổi về việc sử dụng mail trong Laravel 5.3

Introduction

Laravel luôn cung cấp những clean APIs giúp cho việc hoàn thành các tác vụ khi xây dựng các ứng dụng web trở nên dễ dàng và hiệu quả hơn. Việc tạo lập và gửi mail cũng không phải là một ngoại lệ, chỉ cần ba đến bốn dòng lệnh để thực hiện việc đó. Tuy nhiên đôi lúc lặp lại những dòng lệnh đơn giản đó cũng không phải là điều mang lại hiệu quả cao. Trước khi phiên bản 5.3 được ra mắt, việc gửi mail trong Laravel có thể được thực hiện bằng cách sử dụng Mail facade như sau:

Mail::send('emails.password.reset', ['user' => $user], function ($message) use ($user) {
    $message->from('[email protected]', 'Example Application');
    $message->to($user->email, $user->name)->subject('Your Password Reset Confirmation!');
});

Như chúng ta thấy trong ví dụ trên, việc gửi mail có thể thực hiện bằng cách gọi đến phương thức send (hoặc queue nếu chúng ta muốn thực hiện việc gửi mail dưới background thay vì trong request lifecycle) với các đối số như: view hay template của email, dữ liệu kèm theo email, và một closure trong đó chúng ta có thể định rõ nhưng thông tin chi tiết khác của email như from address, to address, email subject,... Vì việc gửi mail có thể được thực hiện nhiều lần trong quá trình xây dựng ứng dụng chúng ta có thể tách phương thức trên ra một abstract class và tạo các lớp khác extends từ abstract class đó, ví dụ như:

<?php

namespace Mailers;

abstract class AbstractMailer
{
    /**
     * A generic method to send email throughout application.
     *
     * @param $user
     * @param $subject
     * @param $view
     * @param array $data
     */
    public function sendTo($user, $subject, $view, $data = [])
    {
        $mailer = app('Illuminate\Mail\Mailer');
        $mailer->queue($view, $data, function ($message) use ($user, $subject) {
            $message->to($user->email)->subject($subject);
        });
    }
}

Tuy nhiên việc sử dụng closure khi gửi mail đôi khi cũng làm một số người mới làm quen với framework (như mình chẳng hạn) cảm thấy khá confused khi phải ghi nhớ thứ tự của các tham số cũng như những phương thức bên trong closure. Phiên bản 5.3 giới thiệu một số thay đổi đến việc sử dụng mail trong framework, cụ thể là Mailable class. Trong đó các cài đặt liên quan đến email sẽ được xử lý bên trong một lớp thay vì sử dụng closure. Tất nhiên sự thay đổi này vẫn đảm bảo backward compatibility, chúng ta vẫn có thể sử dụng cấu trúc cũ để gửi mail mà không làm ảnh hưởng đến ứng dụng hiện tại.

Mailables

Giả sử chúng ta muốn gửi email đến người dùng khi họ đăng ký tài khoản trên ứng dụng, việc đó có thể thực hiện khá ngắn gọn trong Laravel 5.3:

Mail::to('[email protected]')->send(new WelcomeMessage);

Trong đó WelcomeMessage sẽ là lớp chứa các logic liên quan đến việc xây dựng nội dung cho email cần gửi. Việc tạo lớp WelcomeMessage có thể thực hiện bằng cách sử dụng một Artisan console command mới và lớp đó mặc định sẽ được lưu bên trong thư mục app\Mail. Mặc định một số thư mục như Mail, Jobs, Events, Listenerssẽ chỉ được tạo khi chúng ta có những lớp liên quan đến chúng (sử dụng artisan command để tạo boilerplate cho các lớp trên chẳng hạn)

php artisan make:mail WelcomeMessage

Sau khi thực hiện command trên chúng ta sẽ có một class như sau:

<?php

namespace App\Mail;

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

class WelcomeMessage extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('view.name');
    }
}

Mọi thao tác liên quan đến việc định nghĩa nội dung email sẽ được thực hiện trong phương thức build của mailable class. Dữ liệu liên quan đến email sẽ là các public properties bên trong mailable class. Giả sử chúng ta cần biết một số thông tin về người dùng để hiển thị trong email template, chúng ta có thể thực hiện như sau:

<?php

---

class WelcomeMessage extends Mailable
{
    use Queueable, SerializesModels;

    public $user;

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

    ---
}

Thực chất Laravel đã sử dụng Refection API để đọc các public properties và sử dụng chúng làm dữ liệu cho email template:

/**
     * Build the view data for the message.
     *
     * @return array
     */
    protected function buildViewData()
    {
        $data = $this->viewData;
        foreach ((new ReflectionClass($this))->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
            $data[$property->getName()] = $property->getValue($this);
        }
        return $data;
    }

Một cách khác để gửi dữ liệu kèm theo email là sử dụng phương thức with bên trong phương thức build của mailable class. Lợi ích của việc sử dụng phương thức with là chúng ta có thể định dạng dữ liệu trước khi sử dụng trong template do đó hạn chế được những dữ liệu không cần thiết. Tất nhiên dữ liệu đầu vào sẽ vẫn được lấy từ các property của mailable class thông qua constructor, tuy nhiên các property đó sẽ có access modifierprotected hay private thay vì public, ví dụ như:

<?php

namespace App\Mail;

---

class WelcomeMessage extends Mailable
{
    use Queueable, SerializesModels;

    protected $user;

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

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('emails.welcome')
        	->with([
        		'name' => $this->user->getIdentifier(),
        	]);
    }
}

Laravel Mailable còn cung cấp một số các setter methods để định nghĩa các thông tin khác liên quan đến email như: from, subject, to, cc, bcc, replyTo, attachments,...

public function build()
{

	$this->from('[email protected]')
		->to($this->user)
		->cc($users)
		->bcc($moreUsers)
		->with([
			'name' => $this->user->getIdentifier(),
		]);
}

Queued Email

Tương tự như trong các phiên bản trước, vì việc gửi email mất khá nhiều thời gian nếu số lượng email lớn, thông thường chúng ta sẽ sử dụng queue để thực hiện việc gửi mail dưới background thay vì phải hoàn thành trong request. Việc trên có thể được thực hiện bằng cách sử dụng phương thức queue thay vì send như trong các ví dụ trước.

Mail::to('[email protected]')->queue(new WelcomeMessage);

Chúng ta cũng có thể chỉ định thời gian mà queue job sẽ được thực thi bằng cách sử dụng phương thức later. Đối số đầu tiên cho phương thức này sẽ là một object DateTime - thời gian mà queue job sẽ được xử lý, và một mailable object cho đối số thứ hai.

$time = Carbon\Carbon::now()->addMinutes(15);

Mail::to('[email protected]')->later($time, new WelcomeMessage);

Khi sử dụng phương thưc later chúng ta có thể chỉ định tên của queue và queue connection sẽ được sử dụng để xử lý email bằng cách định nghĩa hai property là $queue$connection bên trong mailable class. Laravel sẽ kiểm tra sự tồn tại và sử dụng những property này nếu có.

Một điều khác cần chú ý là mặc định bên trong mailable class Illuminate\Bus\Queueable trait sẽ được sử dụng. Trait này cung cấp một số phương thức cần thiết khi làm việc với queue ví dụ như: định nghĩa queue connection name, delay time,... Do đó thay vì sử dụng hai property nói trên, chúng ta có thể custom lại mailable object với các thông tin liên quan đến queue và sử dụng phương thức queue để thực hiện việc gửi mail:

$message = (new WelcomeMessage)->onConnection('beanstalkd')->onQueue('user-emails');
Mail::to($request->user())->queue($message);

Email Events

Laravel sẽ fire một event trước khi email được gửi đi Illuminate\Mail\Events\MessageSending. Event này sẽ chứa thông tin về message bên trong email. Chúng ta có thể listen event này và thực hiện các công việc cần thiết như log message đó chẳng hạn. Tuy nhiên event này sẽ không được fired khi sử dụng phương thức queue.

Conclusion

Mailables là một sử cải tiến nhỏ nhưng rất hữu ích khi làm việc với mail trong Laravel. Trên thực tế, Mailables chỉ là một wrapper cho cấu trúc closure cũ, chúng ta có thể không cần sử dụng Mailables nếu muốn. Mailables cung cấp một cách hướng đối tượng hơn khi làm việc với mail, và cung cấp một cách diễn đạt chính xác hơn về nội dụng của email cần gửi. Thay vì phải decode mail closure để biết xem email đó đang thực hiện công việc gì, chúng ta có thể dựa vào tên của Mailable class để dễ dàng nhận diện điều đó.


All Rights Reserved