Yii 2 - Отправка писем с помощью Swiftmailer

2016-08-20
Yii

В Yii 2 уже встроен по умолчанию компонент отправки писем, в качестве которого используется Swiftmailer. Рассмотрим на примере тонкости работы с этим компонентом, такие как передача параметров в layout и пр.

Задача

Необходимо настроить отправку писем, так что бы:

  • они не отправлялись, а сохранялись в определенную папку, т.к. это удобно для локальной отладки;
  • использовались как шаблоны (views) так и макеты писем (layouts);
  • в layout письма передавались параметры так же как и во view;
  • отправитель подставлялся по умолчанию;
  • подставлялись имя и e-mail получателя;
  • кодировка писем была UTF-8;
  • отправка писем происходила в двух видах: HTML и текстовом.

В данной статье будет использован "чистый" basic шаблон приложения Yii 2, который можно установить через Composer следуя этой инструкции.

Конфигурирование

Начинаем реализацию с внесения параметров в конфиг файл \config\web.php:

...
'components' => [
    ...
    'mailer' => [
        'class' => 'yii\swiftmailer\Mailer',
        'viewPath' => '@app/mail',
        'htmlLayout' => 'layouts/main-html',
        'textLayout' => 'layouts/main-text',
        'messageConfig' => [
            'charset' => 'UTF-8',
            'from' => ['noreply@site.com' => 'Site Name'],
        ],
        'useFileTransport' => true,
    ],
    ...
]
...

В этой конфигурации:

  • viewPath - путь по которому будут находиться шаблоны писем;
  • htmlLayout и textLayout - макеты писем (layouts), HTML и текстовая версия соответственно;
  • messageConfig -> charset - кодировка писем UTF-8;
  • messageConfig -> from - задаем e-mail адрес и имя отправителя по умолчанию;
  • useFileTransport - флаг указывающий на то, что бы письма не отправлялись а сохранялись в папку \runtime\mail;

Почему указано две версии макетов, HTML и текстовая? Вообще это не обязательно, но желательно. Т.к. при отправке письма, к нему будут прикреплены обе версии. Если приложение пользователя, с помощью которого он читает письма, поддерживает HTML разметку, отобразиться HTML версия, в обратном случает текстовая. Конечно сейчас мало у кого не поддерживаются HTML письма, но так лучше скорее всего и для почтовиков, возможно это уменьшит вероятность попадания в спам, если возникнет такая ситуация.

Добавляем макет (layout)

Соответственно конфигурации указанной выше, макеты будут храниться в папке \mail\layouts.

В basic шаблоне приложения Yii 2, который используется в этой статье, уже есть один макет html.php, нам он не подходит, удалим его.

Далее создаем два новых макета, HTML и текстовую версии. HTML версия должна находиться в файле \mail\layouts\main-html.php:

<?php
use yii\helpers\Html;

/* @var $this \yii\web\View view component instance */
/* @var $message \yii\mail\MessageInterface the message being composed */
/* @var $content string main view render result */
?>
<?php $this->beginPage() ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=<?= Yii::$app->charset ?>" />
    <title><?= Html::encode($this->title) ?></title>
    <?php $this->head() ?>
</head>
<body>
    <?php $this->beginBody() ?>

    <h4>Здравствуйте <?= $this->params['userName'] ?></h4>

    <?= $content ?>

    <?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>

Текстовая версия должна находиться в файле \mail\layouts\main-text.php:

<?php

/* @var $this \yii\web\View view component instance */
/* @var $message \yii\mail\MessageInterface the message being composed */
/* @var $content string main view render result */

?>
Здравствуйте <?= $this->params['userName'] ?>

<?= $content ?>

В данных макетах <?= $this->params['userName'] ?> - это подстановка параметров, в нашем случае имени пользователя.

Добавляем шаблон (view)

Теперь добавим пробный шаблон письма. Также как и с layouts, для views необходимо создавать две версии: HTML и текстовую. Создадим файл \mail\views\example-html.php который будет содержать HTML версию письма:

<?php

/** @var $this \yii\web\View */
/** @var $link string */
/** @var $paramExample string */

?>
<p>Текст письма...</p>

<p>Переданный параметр: <?= $paramExample ?></p>

Также создадим файл \mail\views\example-text.php содержащий текстовую версию письма:

<?php

/** @var $this \yii\web\View */
/** @var $link string */
/** @var $paramExample string */

?>
Текст письма...

Переданный параметр: <?= $paramExample ?>

В этих шаблонах присутствует параметр <?= $paramExample ?> который мы будем передавать в письмо в качестве примера, для того что бы показать как именно передаются параметры в письмо.

Подготовка модели пользователя

В basic шаблоне приложения Yii 2, уже есть модель \models\User.php предназначенная для работы с пользователями. Но она имеет упрощенный вид, и даже не взаимодействует с БД. Для примера описываемого в этой статье, этой упрощенной модели вполне достаточно. Единственное, чего в ней не хватает, это поля e-mail, в котором бы хранился e-mail адрес пользователя, добавим его:

<?php

namespace app\models;

class User extends \yii\base\Object implements \yii\web\IdentityInterface
{
    ...
    public $email;
    ...

    private static $users = [
        '100' => [
            ...
            'email' => 'admin@site.com',
            ...
        ],
        '101' => [
            ...
            'email' => 'demo@site.com',
            ...
        ],
    ];

    ...
}

Теперь все наши пользователи, их сейчас два: admin и demo, имеют поле email в котором содержаться их e-mail адреса.

Создаем метод для отправки писем

Теперь мы подошли к самому главному, написанию метода, который будет отправлять письма пользователям. Для удобства, этот метод будет располагаться в модели пользователя, т.е. в классе app\models\User. Откроем файл \models\User.php содержащий класс модели пользователя, и добавим в его конец новый метод:

<?php

namespace app\models;

class User extends \yii\base\Object implements \yii\web\IdentityInterface
{
    ...

    /**
     * @param string $view
     * @param string $subject
     * @param array $params
     * @return bool
     */
    public function sendMail($view, $subject, $params = []) {
        // Set layout params
        \Yii::$app->mailer->getView()->params['userName'] = $this->username;

        $result = \Yii::$app->mailer->compose([
            'html' => 'views/' . $view . '-html',
            'text' => 'views/' . $view . '-text',
        ], $params)->setTo([$this->email => $this->username])
            ->setSubject($subject)
            ->send();

        // Reset layout params
        \Yii::$app->mailer->getView()->params['userName'] = null;

        return $result;
    }
}

Рассмотрим подробно что происходит в этом методе.

В следующем участке кода, мы передаем параметры в layout, в данном случае имя пользователя:

// Set layout params
\Yii::$app->mailer->getView()->params['userName'] = $this->username;

Кроме имени пользователя, можно конечно передавать и любые другие параметры.

После того как был вызван метод send() и письмо было отправлено, нам необходимо очистить параметры, которые мы передавали в layout, делается это в этом участке кода:

// Reset layout params
\Yii::$app->mailer->getView()->params['userName'] = null;

Эта очистка нужна для того, что бы эти параметры не передались в следующее письмо, которое может быть отправлено где либо в другом месте кода.

Как было описано выше, мы хотим что бы пользователю отправлялись две версии письма: HTML и текстовая. Для этого в методе compose() нам необходимо указать два вида шаблона (view), один для HTML версии письма, второй для текстовой:

$result = \Yii::$app->mailer->compose([
    'html' => 'views/' . $view . '-html',
    'text' => 'views/' . $view . '-text',
], $params)

В методе setTo() мы передаем в виде массива имя и e-mail адрес получателя:

setTo([$this->email => $this->username])

Отправка пробного письма

Пора проверить, как именно отправляется письмо и в каком виде оно приходит. Для этого, в контроллере app\controllers\SiteController добавим новый метод:

...
class SiteController extends Controller
{
    ...
    public function actionTestMailer() {
        \app\models\User::findByUsername('admin')->sendMail('example', 'Пример письма', ['paramExample' => '123']);
    }
    ...
}

Благодаря тому, что мы добавили метод отправки письма в модель пользователя, мы можем после поиска пользователя findByUsername('admin') сразу же отправить ему письмо методом sendMail(). Удобство такого подхода в том, что нам не нужно передавать имя и e-mail адрес получателя вручную, т.к. эти данные итак будут доступны в модели пользователя, в которой у нас находиться метод отправки письма sendMail(). И для того что бы в этом методе подставить имя и e-mail адрес получателя, в данном случае пользователя которого мы только что нашли с помощью метода \app\models\User::findByUsername('admin'), можно просто использовать соответствующие поля класса: $this->username и $this->email.

Теперь рассмотрим параметры, которые необходимо передавать в метод sendMail(). Первым параметром мы передали 'example' - это алиас шаблона (view) письма. Второй параметр - Пример письма - это тема письма. Третий параметр - ['paramExample' => '123'] - это переменные которые будут переданы в письмо, в данном случае у нас в шаблоне письма есть только одна переменная, которую мы используем для примера, что бы показать как передаются параметры из кода в письмо.

На этом реализация экшена actionTestMailer() закончена, пора открыть его в браузере и проверить как будет выглядеть отправленное письмо. Открываем в браузере адрес вида /index.php?r=site/test-mailer, при этом скорее всего отобразиться пустая страница, так и нужно.

Теперь проверим в каком виде пришло письмо. Но в нашем случае оно никуда не отправлялось, а должно было сохраниться в папку \runtime\mail. Письмо должно выглядеть примерно так:

Yii 2 - Отправка писем с помощью Swiftmailer

Как видно в результате в письмо подставились все те параметры, которые мы передавали, также был применен макет (layout).

Настройка SMTP

Для того что бы завершить поставленную задачу, осталось настроить отправку почты через SMTP.

В качестве почтового ящика, с которого будут отправляться письма, будет использован обычный почтовый ящик зарегистрированный на mail.yandex.ru.

Для того что бы Swiftmailer начал отправлять письма через SMTP, нужно сделать кое-какие изменения в конфиге \config\web.php:

...
'components' => [
    ...
    'mailer' => [
        ...
        'messageConfig' => [
            ...
            'from' => ['test-user@yandex.ru' => 'test-user'],
            ...
        ],
        ...
        'useFileTransport' => false,
        ...
        'transport' => [
            'class' => 'Swift_SmtpTransport',
            'host' => 'smtp.yandex.ru',
            'username' => 'test-user',
            'password' => 'test-user-password',
            'port' => '465',
            'encryption' => 'ssl',
        ],
        ...
    ],
    ...
]
...

Какие изменения были сделаны:

  • messageConfig -> from - тут необходимо указать тот почтовый ящик, который используется для отправки писем;
  • 'useFileTransport' => false - отключаем сохранение писем в папку \runtime\mail;
  • в секции transport задаем настройки SMTP сервера, с которого будет отправляться почта, в данном случае указаны настройки SMTP сервера Yandex и логин/пароль пробного почтового ящика test-user@yandex.ru.

На этом настройка SMTP завершена, для того что бы проверить как отправиться письмо, достаточно зайти на адрес вида /index.php?r=site/test-mailer.