Это руководство является частью серии « Создай свой стартап с помощью PHP» на Envato Tuts +. В этой серии я проведу вас через запуск стартапа от концепции до реальности, используя мое приложение Meeting Planner в качестве примера из реальной жизни. На каждом этапе я буду публиковать код Планировщика собраний в качестве примеров с открытым исходным кодом, из которых вы можете извлечь уроки. Я также буду решать вопросы, связанные с бизнесом по мере их возникновения
В этом уроке, состоящем из двух частей, я описываю, как я создал инфраструктуру для напоминаний и их доставки. Сегодня я проведу вас через мониторинг, когда доставлять напоминания и как отправлять электронные письма.
Если вы еще не опробовали Планировщик собраний, запланируйте свою первую встречу . Я принимаю участие в комментариях ниже, так что скажите мне, что вы думаете! Мне особенно интересно, если вы хотите предложить новые функции или темы для будущих уроков.
Напоминаем, что весь код для Meeting Planner написан на Yii2 Framework для PHP. Если вы хотите узнать больше о Yii2, ознакомьтесь с нашей параллельной серией Программирование с Yii2 .
Время мониторинга для напоминаний
Со временем нам нужно следить за таблицей MeetingReminder
чтобы знать, когда доставлять напоминания. В идеале мы хотим, чтобы напоминания доставлялись точно в срок, например, с точностью до минуты.
Запуск фоновых задач
Своевременность зависит от того, насколько регулярно мы выполняем фоновые задачи для мониторинга. В настоящее время, на нашей пре-альфа-стадии, я запускаю их каждые пять минут:
1
2
|
# mh dom mon dow command
*/5 * * * * wget -O /dev/null http://meetingplanner.io/daemon/frequent
|
Этот скрипт вызывает MeetingReminder::check()
, который находит напоминания о собраниях, которые должны быть выполнены, и просит их process()
:
1
2
3
4
5
6
7
8
|
// frequent cron task will call to check on due reminders
public static function check() {
$mrs = MeetingReminder::find()->where(‘due_at<=’.time().’ and status=’.MeetingReminder::STATUS_PENDING)->all();
foreach ($mrs as $mr) {
// process each meeting reminder
MeetingReminder::process($mr);
}
}
|
Обработка напоминания
MeetingReminder::process()
собирает детали, необходимые для создания напоминания по электронной почте. Это включает получателя напоминания, сведения о встрече и время:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
public static function process($mr) {
// fetch the reminder
// deliver the email or sms
// send updates about recent meeting changes made by $user_id
$user_id = $mr->user_id;
$meeting_id = $mr->meeting_id;
$mtg = Meeting::findOne($meeting_id);
// only send reminders for meetings that are confirmed
if ($mtg->status!=Meeting::STATUS_CONFIRMED) return false;
// only send reminders that are less than a day late — to do — remove after testing period
if ((time()-$mr->due_at)>(24*3600+1)) return false;
$u = \common\models\User::findOne($user_id);
// ensure there is an auth key for the recipient user
if (empty($u->auth_key)) {
return false;
}
// prepare data for the message
// get time
$chosen_time = Meeting::getChosenTime($meeting_id);
$timezone = MiscHelpers::fetchUserTimezone($user_id);
$display_time = Meeting::friendlyDateFromTimestamp($chosen_time->start,$timezone);
// get place
$chosen_place = Meeting::getChosenPlace($meeting_id);
$a=[‘user_id’=>$user_id,
‘auth_key’=>$u->auth_key,
’email’=>$u->email,
‘username’=>$u->username
];
// check if email is okay and okay from this sender_id
if (User::checkEmailDelivery($user_id,0)) {
// Build the absolute links to the meeting and commands
$links=[
‘home’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_HOME,0,$a[‘user_id’],$a[‘auth_key’]),
‘view’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_VIEW,0,$a[‘user_id’],$a[‘auth_key’]),
‘footer_email’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_EMAIL,0,$a[‘user_id’],$a[‘auth_key’]),
‘footer_block’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK,0,$a[‘user_id’],$a[‘auth_key’]),
‘footer_block_all’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK_ALL,0,$a[‘user_id’],$a[‘auth_key’]),
‘running_late’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_RUNNING_LATE,0,$a[‘user_id’],$a[‘auth_key’]),
‘view_map’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_VIEW_MAP,0,$a[‘user_id’],$a[‘auth_key’])
];
// send the message
$message = Yii::$app->mailer->compose([
‘html’ => ‘reminder-html’,
‘text’ => ‘reminder-text’
],
[
‘meeting_id’ => $mtg->id,
‘sender_id’=> $user_id,
‘user_id’ => $a[‘user_id’],
‘auth_key’ => $a[‘auth_key’],
‘display_time’ => $display_time,
‘chosen_place’ => $chosen_place,
‘links’ => $links,
‘meetingSettings’ => $mtg->meetingSettings,
]);
if (!empty($a[’email’])) {
$message->setFrom([‘[email protected]’=>’Meeting Planner’]);
$message->setTo($a[’email’])
->setSubject(Yii::t(‘frontend’,’Meeting Reminder: ‘).$mtg->subject)
->send();
}
}
$mr->status=MeetingReminder::STATUS_COMPLETE;
$mr->update();
}
|
Функция User::checkEmailDelivery()
проверяет, что пользователь не заблокировал электронные письма от системы (или от определенных людей). Это гарантирует, что можно отправить напоминание:
01
02
03
04
05
06
07
08
09
10
11
12
|
// check if email is okay and okay from this sender_id
if (User::checkEmailDelivery($user_id,0)) {
// Build the absolute links to the meeting and commands
$links=[
‘home’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_HOME,0,$a[‘user_id’],$a[‘auth_key’]),
‘view’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_VIEW,0,$a[‘user_id’],$a[‘auth_key’]),
‘footer_email’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_EMAIL,0,$a[‘user_id’],$a[‘auth_key’]),
‘footer_block’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK,0,$a[‘user_id’],$a[‘auth_key’]),
‘footer_block_all’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_FOOTER_BLOCK_ALL,0,$a[‘user_id’],$a[‘auth_key’]),
‘running_late’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_RUNNING_LATE,0,$a[‘user_id’],$a[‘auth_key’]),
‘view_map’=>MiscHelpers::buildCommand($mtg->id,Meeting::COMMAND_VIEW_MAP,0,$a[‘user_id’],$a[‘auth_key’])
];
|
Вот User::checkEmailDelivery
метод. Во-первых, он проверяет, полностью ли заблокировал пользователь всю электронную почту (надеюсь, нет), или сообщение отправлено от заблокированного пользователя:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
public static function checkEmailDelivery($user_id,$sender_id) {
// check if this user_id receives email and if sender_id not blocked
// check if all email is turned off
$us = UserSetting::safeGet($user_id);
if ($us->no_email != UserSetting::EMAIL_OK) {
return false;
}
// check if no sender ie system notification
if ($sender_id==0) {
return true;
}
// check if sender is blocked
$ub = UserBlock::find()->where([‘user_id’=>$user_id,’blocked_user_id’=>$sender_id])->one();
if (!is_null($ub)) {
return false;
}
return true;
}
|
Новый шаблон напоминания по электронной почте
В эпизоде «Приглашение на встречу» я писал об отправке сообщений электронной почты в рамках Yii Framework. В разделе «Уточнение шаблонов электронной почты» я описал обновление шаблонов для наших новых адаптивных шаблонов на основе кислорода.
Вот новый шаблон электронной почты remder_html.php:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
<?php
use yii\helpers\Html;
use yii\helpers\Url;
use common\components\MiscHelpers;
use frontend\models\Meeting;
use frontend\models\UserContact;
/* @var $this \yii\web\View view component instance */
/* @var $message \yii\mail\BaseMessage instance of newly created mail message */
?>
<tr>
<td align=»center» valign=»top» width=»100%» style=»background-color: #f7f7f7;»
<center>
<table cellspacing=»0″ cellpadding=»0″ width=»600″ class=»w320″>
<tr>
<td class=»header-lg»>
Reminder of Your Meeting
</td>
</tr>
<tr>
<td class=»free-text»>
Just a reminder about your upcoming meeting <?php echo $display_time;
<?php
// this code is similar to code in finalize-html
if ($chosen_place!==false) {
?>
at <?php echo $chosen_place->place->name;
(<?php echo $chosen_place->place->vicinity; ?>, <?php echo HTML::a(Yii::t(‘frontend’,’map’),$links[‘view_map’]); ?>)
<?php
} else {
?>
via phone or video conference.
<?php
}
?>
<br />
Click below to view more details to view the meeting page.
</td>
</tr>
<tr>
<td class=»button»>
<div><!—[if mso]>
<v:roundrect xmlns:v=»urn:schemas-microsoft-com:vml» xmlns:w=»urn:schemas-microsoft-com:office:word» href=»http://» style=»height:45px;v-text-anchor:middle;width:155px;»
<w:anchorlock/>
<center style=»color:#ffffff;font-family:Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;»>My Account</center>
</v:roundrect>
<![endif]—><a class=»button-mobile» href=»<?php echo $links[‘view’] ?>»
style=»background-color:#ff6f6f;border-radius:5px;color:#ffffff;display:inline-block;font-family:’Cabin’, Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;line-height:45px;text-align:center;text-decoration:none;width:155px;-webkit-text-size-adjust:none;mso-hide:all;»>Visit Meeting Page</a></div>
</td>
</tr>
<tr>
<td class=»mini-large-block-container»>
<table cellspacing=»0″ cellpadding=»0″ width=»100%» style=»border-collapse:separate !important;»>
<tr>
<td class=»mini-large-block»>
<table cellpadding=»0″ cellspacing=»0″ width=»100%»>
<tr>
<td style=»text-align:left; padding-bottom: 30px;»>
<strong>Helpful options:</strong>
<p>
<?php
echo HTML::a(Yii::t(‘frontend’,’Inform them I\’m running late.’),$links[‘running_late’]);
?>
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</center>
</td>
</tr>
<?php echo \Yii::$app->view->renderFile(‘@common/mail/section-footer-dynamic.php’,[‘links’=>$links]) ?>
|
Он включает в себя дату, время и выбранное местоположение (с адресом и ссылкой на карту). Я также добавил начало области полезных опций с помощью начальной команды «Сообщить им, что я опаздываю»:
При нажатии мы отправим по электронной почте или SMS другому участнику (-ям), что вы можете опоздать на пять-десять минут. Больше нечего делать или печатать, пока вы спешите.
Возможно, возможная мобильная версия Meeting Planner будет знать ваше местоположение GPS и сообщать им о том, как далеко вы находитесь. Я начал отслеживать идеи, подобные этой, в Asana для планирования продукта — я попрошу редакторов Envato Tuts + (как показано ниже), могу ли я написать о реализации функции и отслеживании проблем в будущем учебнике.
Улучшения для напоминаний
Письмо с напоминанием может на самом деле использовать несколько расширенных функций:
- Завершение поздней реализации электронной почты.
- Отображение контактной информации других участников, таких как номера телефонов и адреса электронной почты. Позднее электронное письмо может содержать только контактную информацию опоздавшего человека.
- Отображение статической карты Google с указанием места встречи.
- Ссылка на функцию, чтобы запросить или потребовать переназначения встречи.
- Ссылка не только на карту, но и как добраться до места.
- Ссылка для настройки ваших напоминаний.
Оказывается, что большинство из этих функций требуют больше работы, чем предусмотрено в этом руководстве.
Например, идея отправить сообщение с опозданием кажется простой функцией, верно? Это хороший пример проблемы, которую MVC иногда навязывают разработчикам. Реализация функции поздней электронной почты требует кода для нескольких файлов, включая новый шаблон электронной почты.
Реализация функции позднего запуска
Вместо того, чтобы делиться всеми изменениями кода, необходимыми для этой функции, я просто суммирую места, где изменения были необходимы в рамках:
- В письме с напоминанием требовалась ссылка с новой командой
-
COMMAND_RUNNING_LATE
должен быть определен в модели Meeting и контроллере, и он должен отображать подтверждающее сообщение.
Вот пример того, что вы видите после запроса о позднем уведомлении:
- Метод
sendLateNotice()
должен был быть встроен в Meeting.php - Шаблон письма поздней html.php должен был быть создан. Это включает возможность для другого участника объявить, что они также «опоздали».
- Метод
UserContact::buildContactString()
должен был быть завершен, чтобы включить контактную информацию для человека, опаздывающего. -
ACTION_SENT_RUNNING_LATE
должен был быть создан для записи отправки позднего уведомления от имени этого человека в MeetingLog. - Метод
sendLateNotice()
должен был проверить журнал и отобразить ошибку, если позднее уведомление уже было отправлено один раз.
Вот что показывает уже отправленное уведомление:
Было много кода для реализации того, что казалось простым дополнением.
Я ждал, чтобы протестировать эту функцию, пока все вышеперечисленные изменения не были внесены, и я был приятно удивлен, что все они работали точно так, как предполагалось. Мне только нужно было сделать несколько косметических изменений в тексте.
Реализация отображения контактной информации участника
Хотя эта функция уже существует для файлов iCal, мне нужно было выполнить эту функцию для приглашений на собрания по электронной почте. Поэтому я расширил UserContact::buildContactString($user_id,$mode)
для $mode='html'
.
Вот обновленный код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public static function buildContactString($user_id,$mode=’ical’) {
// to do — create a view for this that can be rendered
$contacts = UserContact::getUserContactList($user_id);
if (count($contacts)==0) return »;
if ($mode==’ical’) {
$str=»;
} else if ($mode ==’html’) {
$str='<p>’;
}
$str = \common\components\MiscHelpers::getDisplayName($user_id).’: ‘;
if ($mode==’ical’) {
$str.=’ \\n’;
} else if ($mode ==’html’) {
$str.='<br />’;
}
foreach ($contacts as $c) {
if ($mode==’ical’) {
$str.=$c->friendly_type.’: ‘.$c->info.’
} else if ($mode ==’html’) {
$str.=$c->friendly_type.’: ‘.$c->info.'<br />’.$c->details.'<br />’;
}
}
if ($mode==’ical’) {
$str.=’ \\n’;
} else if ($mode ==’html’) {
$str.='</p>’;
}
return $str;
}
}
|
Я уверен, что это потребует некоторой доработки, когда мы перейдем к альфа- и бета-тестам, но функциональность уже есть.
Вы можете увидеть контактные данные, отображенные в полном конце уведомления выше, но вот сегмент, который он генерирует:
Полировка Напоминания
С этими поздними мини-функциями в целом все шло так хорошо, что я добавил ссылку, чтобы настроить ваши напоминания на оригинальное письмо с напоминанием.
Со всем этим новым кодом я уверен, что буду улучшать функцию напоминаний и регулярно улучшать ее в течение следующих нескольких недель. Тем не менее, так как Meeting Planner объединился, часто возможны дополнительные функциональные возможности — часто с небольшим количеством работы, потому что есть структура и фундамент. Модель чистых данных и структура MVC регулярно делают постепенные улучшения относительно простыми.
Все это делает создание стартапа интересным и увлекательным. И работа с драконами (иногда я не могу поверить, что они платят мне за это).
Что дальше?
Встреча Планировщика сделала огромный прогресс за последние несколько месяцев. Я начинаю экспериментировать с WeFunder, основываясь на внедрении новых правил краудфандинга SEC . Пожалуйста, обратите внимание на наш профиль . Я надеюсь написать об этом больше в будущем уроке.
И, конечно же, у приложения все еще есть много недостатков — пожалуйста, обязательно оставьте свой отзыв в комментариях или откройте заявку в службу поддержки .
Я надеюсь, вам понравился этот эпизод. Следите за будущими уроками в нашей серии « Построение стартапа с помощью PHP» — впереди еще много работы, но есть и более широкие возможности.
Пожалуйста, не стесняйтесь добавлять свои вопросы и комментарии ниже; Я обычно участвую в обсуждениях. Вы также можете связаться со мной через Twitter @reifman напрямую.