Статьи

Создание вашего стартапа: подготовка к напоминаниям

Конечный продукт
Что вы будете создавать

Это руководство является частью серии « Создай свой стартап с помощью PHP» на Envato Tuts +. В этой серии я проведу вас через запуск стартапа от концепции до реальности, используя мое приложение Meeting Planner в качестве примера из реальной жизни. На каждом этапе я буду публиковать код Планировщика собраний в качестве примеров с открытым исходным кодом, из которых вы можете извлечь уроки. Я также буду решать вопросы, связанные с бизнесом по мере их возникновения

В этой серии из двух частей я опишу, как мы создали инфраструктуру для напоминаний и их доставки. В этом эпизоде ​​основное внимание будет уделено инфраструктуре и опыту пользователей, которые стоят за настройкой напоминаний

Если вы еще не опробовали Планировщик собраний, запланируйте свою первую встречу . Я принимаю участие в комментариях ниже, так что скажите мне, что вы думаете! Мне особенно интересно, если вы хотите новые функции или предложить темы для будущих уроков.

Напоминаем, что весь код для Meeting Planner написан на Yii2 Framework для PHP. Если вы хотите узнать больше о Yii2, ознакомьтесь с нашей параллельной серией Программирование с Yii2 .

Первоначально я создал несколько простых опций напоминания в таблице UserSetting. Однако я понял, что пользователям потребуется гораздо больше гибкости и контроля над тем, когда и как поступают их напоминания.

Люди должны иметь возможность устанавливать напоминания за 30 минут до, за 3 часа до и за 48 часов до — или только за 1 час до. Это должно быть полностью до них. Они также должны иметь возможность выбирать, хотят ли они получать напоминания по электронной почте, SMS или обоими способами. Meeting Planner пока не поддерживает SMS, но скоро это произойдет — об этом тоже будет руководство.

Вот пример гибкости, которую предлагает Apple Calendar:

Напоминания Планировщика Встреч - Предупреждения Напоминания Календаря Apple

Давайте создадим инфраструктуру для поддержки любого количества настраиваемых пользователем напоминаний о встречах.

Напоминания Планировщика Встреч - Создать Напоминание

Во-первых, я создал таблицу напоминаний для поддержки одного или нескольких запросов напоминаний пользователями для всех своих собраний. Как обычно в Yii2, я создал таблицу с миграцией.

Вот консольная команда Yii для создания миграции базы данных:

1
./yii migrate/create create_reminder_table

Затем я настроил этот файл скелета с нужными мне свойствами:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
class m160503_234630_create_reminder_table extends Migration
{
    public function up()
    {
        $tableOptions = null;
        if ($this->db->driverName === ‘mysql’) {
            $tableOptions = ‘CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB’;
        }
 
        $this->createTable(‘{{%reminder}}’, [
            ‘id’ => Schema::TYPE_PK,
            ‘user_id’ => Schema::TYPE_BIGINT.’
            ‘duration_friendly’ => Schema::TYPE_INTEGER.’
            ‘unit’ => Schema::TYPE_SMALLINT.’
            ‘duration’ => Schema::TYPE_INTEGER.’
            ‘reminder_type’ => Schema::TYPE_SMALLINT.’
            ‘created_at’ => Schema::TYPE_INTEGER .
            ‘updated_at’ => Schema::TYPE_INTEGER .
        ], $tableOptions);
        $this->addForeignKey(‘fk_reminder_user’, ‘{{%reminder}}’, ‘user_id’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
    }

Если напоминание UNIT_HOURS 48 часов до этого, duration_friendly и unit будут UNIT_HOURS 48 и UNIT_HOURS , а поле duration будет сохраняться в секундах, например, за 48 * 60 минут * 60 секунд или 172 800 секунд до встречи. Это поможет как в упрощении пользовательского интерфейса, так и в обработке напоминаний.

Reminder_type будет указывать адрес электронной почты, SMS или оба.

Затем я использовал Gii, генератор его кода, для быстрого создания кода MVC для контроллера, модели и представлений. Первоначальный пользовательский интерфейс был готов через несколько минут. Вы можете увидеть форму создания напоминания выше и индекс напоминания ниже.

Советы по планированию встреч - персонализированные напоминания

К тому времени, когда я начал работать с напоминаниями, люди уже использовали Планировщик собраний для планирования встреч. Поэтому мне нужно инициализировать таблицу напоминаний для существующих людей, а также для каждого вновь зарегистрированного человека.

Я решил, что сначала должно быть три напоминания по умолчанию для пользователей, запланированное за 3 часа, 1 день и 3 дня до встречи. Код ниже создает эти напоминания для пользователя:

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
public static function initialize($user_id) {
     // create initial reminders for a user
     $r1 = new Reminder();
     $r1->user_id = $user_id;
     $r1->duration_friendly = 3;
     $r1->unit = Reminder::UNIT_HOURS;
     $r1->reminder_type = Reminder::TYPE_EMAIL;
     $r1->duration = 3600;
     $r1->validate();
     $r1->save();
     $r2 = new Reminder();
     $r2->user_id = $user_id;
     $r2->duration_friendly = 1;
     $r2->unit = Reminder::UNIT_DAYS;
     $r2->reminder_type = Reminder::TYPE_EMAIL;
     $r2->duration = 1*24*3600;
     $r2->save();
     $r3 = new Reminder();
     $r3->user_id = $user_id;
     $r3->duration_friendly = 3;
     $r3->unit = Reminder::UNIT_DAYS;
     $r3->reminder_type = Reminder::TYPE_EMAIL;
     $r3->duration = $r3->duration_friendly*24*3600;
     $r3->save();
     Reminder::processNewReminder($r1->id);
     Reminder::processNewReminder($r2->id);
     Reminder::processNewReminder($r3->id);
   }

Но что делает processNewReminder ? Он строит строки в другой таблице, которую я опишу ниже.

Хорошо, что теперь у нас есть способ предоставить пользователям возможность выбора напоминаний по умолчанию для собраний. Но как система узнает, когда отправлять напоминания каждому пользователю для его встреч? Это сложнее.

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

По мере обновления времени MeetingReminder записи в таблице MeetingReminder для этого собрания должны будут измениться. Аналогичным образом, если человек обновляет свои настройки напоминаний, запланированные напоминания в таблице MeetingReminder также необходимо MeetingReminder .

Давайте создадим миграцию для таблицы MeetingReminder :

1
./yii migrate/create create_meeting_reminder_table

Вот код миграции; это довольно просто По сути, для каждого напоминания существует MeetingReminder который соответствует напоминанию пользователя о встрече. Он знает, что напоминание due_at в определенное время и имеет статус, который определяет, было ли оно отправлено:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
class m160510_062936_create_meeting_reminder_table extends Migration
{
  public function up()
  {
      $tableOptions = null;
      if ($this->db->driverName === ‘mysql’) {
          $tableOptions = ‘CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB’;
      }
 
      $this->createTable(‘{{%meeting_reminder}}’, [
          ‘id’ => Schema::TYPE_PK,
          ‘meeting_id’ => Schema::TYPE_INTEGER.’
          ‘reminder_id’ => Schema::TYPE_BIGINT.’
          ‘user_id’ => Schema::TYPE_BIGINT.’
          ‘due_at’ => Schema::TYPE_INTEGER .
          ‘status’ => Schema::TYPE_SMALLINT.’
      ], $tableOptions);
      $this->addForeignKey(‘fk_meeting_reminder_user’, ‘{{%meeting_reminder}}’, ‘user_id’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
      $this->addForeignKey(‘fk_meeting_reminder_meeting’, ‘{{%meeting_reminder}}’, ‘meeting_id’, ‘{{%meeting}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
  }

MeetingReminder фонового мониторинга сможет отсортировать таблицу MeetingReminder по времени и быстро узнать, какой небольшой набор напоминаний действительно необходимо доставить. И он может отслеживать, какие из них были отправлены для каждой встречи и участника.

Примечание. В настоящее время нет функции, позволяющей настраивать напоминания для конкретного собрания, поэтому нет пользовательского интерфейса с таблицей MeetingReminder. Я мог бы добавить это позже.

Как я уже MeetingReminder ранее, таблица MeetingReminder оказалась очень сложной:

  • Если люди добавляют, редактируют или удаляют напоминания, это должно быть отражено в предварительно настроенных напоминаниях о собраниях.
  • Если люди меняют время встречи или отменяют ее, напоминания о встрече должны быть обновлены, чтобы отразить это.
  • Если человек решает не посещать собрание, эти напоминания должны быть отключены.

В конечном счете, создание функции напоминания требовало много вспомогательных функций.

Вот вспомогательная функция, которая создает MeetingReminder для конкретного пользователя для его напоминания для конкретной встречи. Если встреча уже прошла, статус отражает, что:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public static function create($meeting_id,$user_id,$reminder_id,$differential) {
  // delete any previously existing meetingreminder for this reminder_id and meeting_id
   MeetingReminder::deleteAll([‘meeting_id’=>$meeting_id,’reminder_id’=>$reminder_id]);
   $mtg = Meeting::findOne($meeting_id);
   if (is_null($mtg)) {
     return false;
   }
   $chosen_time = Meeting::getChosenTime($meeting_id);
   $mr = new MeetingReminder;
   $mr->reminder_id = $reminder_id;
   $mr->meeting_id = $meeting_id;
   $mr->user_id = $user_id;
   $mr->due_at = $chosen_time->start-$differential;
   if ($mr->due_at>time()) {
     $mr->status=MeetingReminder::STATUS_PENDING;
   } else {
     $mr->status=MeetingReminder::STATUS_COMPLETE;
   }
   $mr->save();
}

Итак, всякий раз, когда создается напоминание, вот код, который создает все записи MeetingReminder для каждого из собраний пользователя:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public static function processNewReminder($reminder_id) {
  $rem = Reminder::findOne($reminder_id);
  // find all the meetings this user is a part of
  // create meeting reminder for all meetings where this reminder’s creator is the organizer
  $mtgs = Meeting::find()->where([‘owner_id’=>$rem->user_id])->all();
  // to do performance — could add an open join above to participants
  foreach ($mtgs as $m) {
    MeetingReminder::create($m->id,$rem->user_id,$rem->id,$rem->duration);
  }
  // create meeting reminder for all meetings where this reminder’s creator is a participant
  $part_mtgs = Participant::find()->where([‘participant_id’=>$rem->user_id])->all();
  foreach ($part_mtgs as $m) {
    MeetingReminder::create($m->id,$rem->user_id,$rem->id,$rem->duration);
  }
}

По сути, код находит все собрания для человека, будь то организатор или участник, и создает запись MeetingReminder для каждого из напоминаний этого человека. Например, человек с тремя предпочтениями напоминания по умолчанию и тремя запланированными собраниями будет иметь девять MeetingReminder таблицы MeetingReminder .

Когда человек создает новое напоминание, у нас есть код, который устанавливает продолжительность на основе его настроек, а затем создает новый MeetingReminder для всех ожидающих его собраний:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public function actionCreate()
   {
       $model = new Reminder();
       $model->user_id = Yii::$app->user->getId();
       $model->duration = 0;
       if ($model->load(Yii::$app->request->post())) {
         $model->duration = $model->setDuration($model->duration_friendly,$model->unit);
         if ($model->validate()) {
           $model->save();
           Reminder::processNewReminder($model->id);
           Yii::$app->getSession()->setFlash(‘success’, Yii::t(‘frontend’,’Your reminder has been created for all current and future meetings.’));
           return $this->redirect(‘index’);
         } else {
           // to do set flash
           Yii::$app->getSession()->setFlash(‘error’, Yii::t(‘frontend’,’There was a problem creating your reminder.’));
         }
       }
       return $this->render(‘create’, [
           ‘model’ => $model,
       ]);
   }

Если пользователь изменяет напоминание, нам нужно обновить таблицу MeetingReminder для этого reminder_id :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public static function updateReminder($reminder_id) {
     // when user updates a reminder, update all the meeting reminders
     $new_reminder = Reminder::findOne($reminder_id);
     $mrs = MeetingReminder::find()->where([‘reminder_id’=>$reminder_id])->all();
     // update each meeting reminder
     foreach ($mrs as $mr) {
       $chosen_time = Meeting::getChosenTime($mr->meeting_id);
       $mr->due_at = $chosen_time->start-$new_reminder->duration;
       if ($mr->due_at>time()) {
         $mr->status=MeetingReminder::STATUS_PENDING;
       } else {
         $mr->status=MeetingReminder::STATUS_COMPLETE;
       }
       $mr->update();
     }
   }

Если время для напоминания уже due_at , мы устанавливаем его состояние как завершенное.

Когда совещание завершено, устанавливается время, и нам нужно настроить MeetingReminders основе настроек напоминания каждого участника. Метод setMeetingReminders делает это:

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
public static function setMeetingReminders($meeting_id,$chosen_time=false) {
     // when a meeting is finalized, set reminders for the chosen time for all participants
     $mtg = Meeting::findOne($meeting_id);
     if ($chosen_time ===false) {
       $chosen_time = Meeting::getChosenTime($meeting_id);
     }
     // create attendees list for organizer and participants
     $attendees = array();
     $attendees[0]=$mtg->owner_id;
     $cnt =1;
     foreach ($mtg->participants as $p) {
       if ($p->status ==Participant::STATUS_DEFAULT) {
         $attendees[$cnt]=$p->participant_id;
         $cnt+=1;
       }
     }
     // for each attendee
     foreach ($attendees as $a) {
       // for their reminders
       $rems = Reminder::find()->where([‘user_id’=>$a])->all();
       foreach ($rems as $rem) {
         // create a meeting reminder for that reminder at that time
           MeetingReminder::create($meeting_id,$a,$rem->id,$rem->duration);
       }
     }
   }

Точно так же, когда время встречи изменяется после факта (пока не поддерживается в текущем наборе функций), я создал простую функцию для удаления и перестройки MeetingReminders для нового времени:

1
2
3
4
5
6
7
8
public static function processTimeChange($meeting_id,$chosen_time) {
     // when a meeting time is set or changes, reset the reminders for all participants
     // clear out old meeting reminders for all users for this meeting
     MeetingReminder::deleteAll([‘meeting_id’=>$meeting_id]);
     // set meeting reminders for all users for this meeting
     // note each user has different reminders
     Reminder::setMeetingReminders($meeting_id,$chosen_time);
   }

Функция, которая на первый взгляд кажется простой, требует много деталей и недосмотра.

Вы видели основу для напоминаний. В следующем уроке я покажу вам, как мы контролируем время, чтобы знать, когда и как доставлять напоминания. И я покажу вам, как мы доставляем напоминания по электронной почте (смс придет позже).

Напоминания планировщика собраний - пример напоминания по электронной почте

Пока вы ждете, опробуйте функцию напоминания, запланируйте свою первую встречу , а затем обновите настройки напоминания . Кроме того, я буду признателен, если вы поделитесь своим опытом ниже в комментариях, и мне всегда интересны ваши предложения. Вы также можете связаться со мной через Twitter @reifman напрямую.

Я также начинаю экспериментировать с WeFunder, основываясь на внедрении новых правил краудфандинга SEC . Пожалуйста, рассмотрите наш профиль там . Я могу написать об этом больше как часть нашей серии.

Следите за будущими уроками в серии « Построение стартапа с помощью PHP» . В дополнение к напоминаниям, есть еще много доработок и еще несколько важных функций.