
Есть два варианта создания рассылки писем подписчикам сайта:
- использование стороннего сервиса;
- написание своего функционала рассылки.
В данной статье мы напишем скрипт своей рассылки, запускать который можно либо вручную, либо повесив на какое-либо событие (к примеру когда посетитель открывает какую-то, либо любую, страницу), или (оптимальный вариант) прописав время выполнения скрипта по расписанию в Cron.
Начнем.
Прежде всего создадим таблицу send_subscr. Для этого выполним миграцию (или создав нужные поля вручную):
public function safeUp() { $tableOptions = null; if ($this->db->driverName === 'mysql') { $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB'; } $this->createTable('{{%send_subscr}}', [ 'id' => $this->primaryKey(), 'post_id' => $this->integer()->notNull(), 'subscriber_id' => $this->integer()->notNull(), 'end' => $this->boolean()->defaultValue(0), ], $tableOptions); } public function safeDown() { $this->dropTable('{{%send_subscr}}'); }Таблица имеет следующие поля:
- id - уникальный идентификатор последней рассылки. Каждая строка рассылки будет соответствовать одному посту по которому будут рассылаться уведомления, то есть строки будут добавляться только когда найден новый пост.
- post_id - уникальный идентификатор статьи (поста). Id поста будем брать из соответствующей таблицы. У меня это "post".
- subscriber_id - уникальный идентификатор последнего подписчика, которому был отправлен выпуск. Данные будем брать из таблицы, которую создавали в предыдущей статье, когда создавали форму подписки "subscription".
- end - закончена рассылка или нет (1-закончена, 0- нет).
Создание модели.
Код модели будет максимально прокомментирован, т.к. он не совсем прост для понимания.В папке console\models создадим файл модели SendSubscr.php с содержимым:<?php namespace console\models; use Yii; use common\models\Post; use common\models\Subscription; class SendSubscr extends \yii\db\ActiveRecord { public static function tableName() { return '{{%send_subscr}}'; } public function rules() { return [ [['post_id', 'subscriber_id'], 'required'], [['post_id', 'subscriber_id'], 'integer'], [['end'],'boolean'], ]; } public function attributeLabels() { return [ 'id' => 'ID', 'post_id' => 'Post ID', 'subscriber_id' => 'Subscriber ID', 'end' => 'End', ]; } public function send(){ //В таблице send_subscr выбираем строку с самым большим id поста (последний пост по которому была рассылка) $query = $this->find() ->limit(1) ->orderBy('post_id DESC') ->all(); $last_subscribe = $query[0]; //Если в этой строке по данному посту нет неотправленных уведомлений или если массива еще нет (1-й раз только) if ($last_subscribe->end == 1 or count($last_subscribe) == 0) { //Получаем id последнего поста по которому была рассылка или ставим 0, если еще не было. $last_post = $last_subscribe->post_id or $last_post = 0; //Проверяем есть ли в таблице post посты с id большим чем id полученного нами поста из таблицы //SendSubscr (то есть новые посты) со статусом PUBLISH $post = Post::find() ->limit(1) ->where(['>', 'id', $last_post]) ->andWhere(['publish_status' => 'PUBLISH']) ->all(); //Если нет новых постов, то выходим if (!$post) exit; //Если новые посты есть, то ставим в очередь В ТАБЛИЦУ send_subscr (создаем новую строку) и выходим $this->post_id = $post[0]->id; $this->subscriber_id = 0; $this->end = 0; $this->save(); //Выходим, а через 10 минут данный скрипт будет снова запущен, и уже пойдёт непосредственно рассылка писем exit; } //Сюда выполнение доходит если есть незаконченные рассылки //Получаем id подписчика, которому было отправлено письмо в последний раз $last_id = $last_subscribe->subscriber_id; //В таблице ПОДПИСЧИКОВ получаем все строки, у которых id > id последнего подписчика из таблицы SendSubscr, //то есть которым не отправлено еще уведомление $subscriptions = Subscription::find() ->where(['>', 'id', $last_id]) ->all(); $max_count = count($subscriptions); //Будем отправлять за 1 раз 10 писем, но если подписчиков < 10, то ставим столько, сколько их. if ($max_count > 10) $max_count = 10; //перебираем подписчиков, отправляем им письма foreach ($subscriptions as $key => $sub){ //получаем id строки в таблице subscription, он же номер подписчика $subscriber_id = $sub->id; /* Метод отправки сообщения. Можно использовать данные подписчика: $sub->email; $sub->addtime; ID поста для ссылки на страницу новой статьи - $last_subscribe->post_id */ $this->sendSub($sub->email, $last_subscribe->post_id); //Если достигло максимума (10) отправок, то устанавливаем в ячейку subscriber_id номер текущего //подписчика в строке с текущей рассылкой и выходим из цикла if ($key >= ($max_count-1)) { $send_subscr = self::findOne($last_subscribe->id); $send_subscr->subscriber_id = $subscriber_id; $send_subscr->update(); break; } } //Если осталось менее 10 подписчиков которым еще не отправлено уведомление, //закрываем строку с текущей рассылкой, В ТАБЛИЦЕ send_subscr ставим `end`='1' в строке с текущей рассылкой if(count($subscriptions) <= $max_count) { $send_subscr = self::findOne($last_subscribe->id); $send_subscr->end = 1; $send_subscr->update(); } } public function sendSub($email,$post_id){ $home_url = 'http://yoursite.com'; //Формирование ссылки на страницу поста $link = 'post/view?id='; $full_link = $link.$post_id; $url = \common\models\Sef::findOne(['link' => $full_link])->link_sef; $post_url = $home_url.'/'.$url.'.html'; $msg = "Здравствуйте! Вы подписаны на рассылку уведомлений о новых статьях по WEB-программированию на сайте $home_url. Сообщаем, что опубликована новая статья. Для просмотра перейдите на $post_url"; $msg_html = "<html><body style='font-family:Arial,sans-serif;'>"; $msg_html .= "<h3 style='font-weight:bold;border-bottom:1px dotted #ccc;'>Здравствуйте! Вы подписаны на рассылку уведомлений о новых статьях по WEB-программированию на сайте " . $home_url . "</h3>\r\n"; $msg_html .= "<p><strong>Сообщаем, что опубликована новая статья. Для просмотра перейдите на </strong><a href='". $post_url ."'>$post_url</a></p>\r\n"; $msg_html .= "</body></html>"; Yii::$app->mailer->compose() //->setFrom('admin@yoursite.com') //e-mail админа, не указываем, если указано в common\config\main-local.php ->setTo($email) // кому отправляем - реальный адрес куда придёт письмо формата asdf@asdf.com ->setSubject('Уведомление о новой WEB-статье') // тема письма ->setTextBody($msg) // текст письма без HTML ->setHtmlBody($msg_html) // текст письма с HTML ->send(); } }Тут нас больше всего интересует метод send(), который при каждом запуске скрипта проверяет наличие новые постов на нашем сайте и добавляет их в очередь на рассылку. Далее вызывается метод sendSub(), который и отправляет письма используя для этого стандартный класс Yii2 Mailer. При этом за 1 раз будет отправлено только 10 писем, чтобы обойти ограничение хостингов.
Таким образом, скрипт можно запускать (автоматически), например раз в 10 минут или раз в час (в зависимости от кол-ва подписчиков) и он будет отправлять письма с перерывами, а когда отправит все, проверит, нет ли в БД новых постов. Если есть - добавляет в таблицу новую строку и начинает рассылку опять.
В методе sendSub() мы из id поста формируем полную ссылку на страницу и сохраняем в переменную $post_url. У меня каждая статья имеет буквенное название (алиас) с префиксом html на конце, поэтому я получаю его из таблицы Sef. Вы можете подстроить под себя, главное, чтобы в переменную $post_url попадала ссылка на данный пост, чтобы посетитель сайта, получив письмо смог перейти по ней. Как создать ЧПУ ссылку с буквенными названиями страниц читайте тут.
Создание контроллера.
В папке console\controllers создайте файл SendController.php с таким содержимым:<?php namespace console\controllers; use Yii; use yii\console\Controller; use console\models\SendSubscr; class SendController extends Controller { public $defaultAction = 'mail'; public function actionMail(){ $model = new SendSubscr(); $model->send(); return 0; } }Тут все просто - одно действие actionMail(), в котором создаем объект класса модели SendSubscr() и вызывается метод send(). Для удобства назначаем наш единственный метод действием по-умолчанию указанием переменной $defaultAction. В консольном приложении принято использовать код возврата. Ноль означает, что команда выполнена удачно.
Функционал рассылки готов, для запуска нужно ввести в консоли:
yii sendНу, а чтобы не писать это каждый раз вручную, лучше всего создать расписание выполнения скрипта на хостинге с помощью Cron.
ответ на комментарий Joma от 14.07.2017
ответ на комментарий Сергей от 14.07.2017
Задача "yii send" выполнена с ошибками за 0 секунд, вывод:
ответ на комментарий Сергей от 15.07.2017
ответ на комментарий Joma от 15.07.2017
Должно получиться что-то вроде этого:
1. это pjax запрос - данные не отправляются без перезагрузки. Разрабатываю проект на локальном сервере Open Server.
2. Теперь нужно создать контроллер и модель, которые будут обрабатывать нашу рассылку. Создавать их будем в консольной среде ???
Если можно дайте мне какие-нибудь ссылки где я могу прочитать про них.
Заранее спасибо!!
ответ на комментарий Хайям от 07.08.2017
По 1-му пункту - если страница перезагружается, то значит pjax не работает. Попробуйте действие actionSubscription изменить таким образом:
Если надпись отобразится без перезагрузки, то ошибка где-то в действии. Иначе в блоке с pjax. Точнее, не видя код сказать нельзя. На счет OpenServer - я тоже на нем тестировал, все работает.
По 2-му пункту, под "создать контроллер и модель в консольной среде" имеется ввиду размещение данных классов в папке "console" для использования консольных команд (запуск скрипта при работе с cron). А создавать данные классы вы можете обычным образом, в т.ч. вручную.
Подскажите, это вы на advanced шаблоне делали? А вот на basic как? Там же папка command и как бы в неё ложатся консольные файлы? Или можно тоже создать в ней для контроллеров, моделей папки?
ответ на комментарий Виктор Шурыгин от 28.05.2018