Это руководство является частью серии « Создай свой стартап с помощью PHP» на Envato Tuts +. В этой серии я проведу вас через запуск стартапа от концепции до реальности, используя мое приложение Meeting Planner в качестве примера из реальной жизни. На каждом этапе я буду публиковать код Планировщика собраний в качестве примеров с открытым исходным кодом, из которых вы можете извлечь уроки. Я также буду решать вопросы, связанные с бизнесом по мере их возникновения.
Запуск второго веб-домена
Расширение осведомленности и использования Meeting Planner — моя самая большая проблема прямо сейчас. Без большого вклада трудно сделать продукт лучше, а без быстрого роста трудно привлечь инвесторов.
У меня были опасения, что бренд Meeting Planner может привести к тому, что люди неправильно поймут социальную полезность приложения, такие как планирование дружеских встреч, свиданий и вечеринок.
Очевидно, что выбор имен ограничен доступностью домена и / или бюджетом, который вы должны инвестировать в покупку альтернатив. Встреча с Планировщиком казалась лучшей в то время.
Недавно я заметил, что SimplePlanner.io доступен, поэтому я зарегистрировал его и начал интегрировать домен вместе с существующим сервисом Meeting Planner .
Существует несколько разных подходов к добавлению доменов в приложение на основе Yii2. В сегодняшнем уроке я расскажу о самых простых, работающих доменах в одной и той же кодовой базе.
Если вы еще не опробовали Планировщик собраний, запланируйте свою первую встречу в Simple Planner . Я принимаю участие в комментариях ниже, так что скажите мне, что вы думаете! Вы также можете связаться со мной в Twitter @reifman . Мне особенно интересно, если вы хотите предложить новые функции или темы для будущих уроков.
Напоминаем, что весь код для Meeting Planner и Simple Planner написан на Yii2 Framework для PHP. Если вы хотите узнать больше о Yii2, ознакомьтесь с нашей параллельной серией Программирование с Yii2 .
Прежде чем начать, я бы хотел коснуться одного аспекта проблемы создания стартапа.
Взгляд на жизнь запуска
Я работал над этим эпизодом на выходных, «перком» #StartupLife. Я хотел поделиться несколькими забавными вещами об этом.
Во-первых, #StartupLife часто означает отсутствие времени, чтобы закончить собирать мебель IKEA, просто самый большой ящик:
Во-вторых, это также означает работу в кофейнях в забавных местах … У пятипостового ростера в Портленде, штат Орегон, есть соседний пузырьковый аппарат (или люди там дышат пузырьками):
Теперь вернемся к теме сегодняшнего урока …
Реализация нескольких доменов в Yii
Расширенный шаблон Yii2 позволяет запускать несколько сайтов в одном дереве кода. Я использовал его интерфейсное дерево для построения Meeting Planner и его фоновое дерево для построения административного набора инструментов для службы. Однако сегодня я сосредоточусь на запуске другого домена поверх существующего интерфейсного дерева — и всех мелких и больших сложностей, которые сопровождают это.
В следующем эпизоде я расскажу о строительных сайтах на третьем или четвертом дереве, например, API-интерфейсе REST или связанном с ним запуске в Meeting Planner (да, у нас впереди захватывающая идея).
Я предполагал, что запускать Simple Planner будет довольно просто (без каламбура), но в итоге это заняло несколько дней работы.
Простая работа заключалась в базовой конфигурации сервера и некоторой обработке кода с помощью дерева Yii. Тем не менее, поскольку Meeting Planner является службой с интенсивным использованием электронной почты (приглашения на собрания, подтверждения, объявления, обновления и т. Д.), Важно было отправлять эти электронные письма с доменом, из которого были созданы собрания, либо SimplePlanner.io, либо MeetingPlanner.io.
Я также хотел, чтобы между этими двумя сайтами было некоторое визуальное различие.
Давайте начнем, и я постепенно раскрою некоторые сложности, с которыми я столкнулся.
Конфигурация на основе адреса домена
Когда люди приходят на наш сайт через запросы браузера, нам нужен центральный способ в Yii, чтобы указать всему исполняемому коду, какой сервис показывать: Планировщик собраний или Простой планировщик?
В /frontend/config/main.php есть, к сожалению, названная конфигурация начальной загрузки (поскольку она перекрывается с Bootstrap ), которая будет запускать код в начале вызова платформы:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
<?php
$config = parse_ini_file(‘/var/secure/mp.ini’, true);
$params = array_merge(
require(__DIR__ . ‘/../../common/config/params.php’),
require(__DIR__ . ‘/../../common/config/params-local.php’),
require(__DIR__ . ‘/params.php’),
require(__DIR__ . ‘/params-local.php’)
);
return [
‘id’ => ‘mp-frontend’,
‘name’ => ‘Meeting Planner’,
‘basePath’ => dirname(__DIR__),
‘bootstrap’ => [‘log’,’\common\components\SiteHelper’],
‘controllerNamespace’ => ‘frontend\controllers’,
|
В дополнение к вышеперечисленному SiteHelper
журнала я создал для него компонент с именем SiteHelper
:
Таким образом, SiteHelper имеет весь код для настройки службы в зависимости от того, какой сайт работает. Например:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
private function commonMeetingPlanner() {
Yii::$app->params[‘site’][‘id’] = SiteHelper::SITE_MP;
Yii::$app->params[‘site’][‘domain’] = ‘meetingplanner.io’;
Yii::$app->params[‘site’][‘url’] = ‘https://meetingplanner.io’;
Yii::$app->params[‘site’][‘title’] = Yii::t(‘frontend’, ‘Meeting Planner’);
Yii::$app->params[‘site’][‘mtg’] = Yii::t(‘frontend’, ‘Meetings’);
Yii::$app->params[‘site’][‘img’] = rand(2,3);
Yii::$app->params[‘site’][‘navbar’] = ‘navbar-inverse’;
Yii::$app->params[‘site’][’email_logo’] = ‘https://meetingplanner.io/img/email-logo-mp.gif’;
Yii::$app->params[‘site’][‘ga’] = ‘UA-37244292-18’;
}
private function commonSimplePlanner() {
Yii::$app->params[‘site’][‘id’] = SiteHelper::SITE_SP;
Yii::$app->params[‘site’][‘domain’] = ‘simpleplanner.io’;
Yii::$app->params[‘site’][‘url’] = ‘https://simpleplanner.io’;
Yii::$app->params[‘site’][‘title’] = Yii::t(‘frontend’, ‘Simple Planner’);
Yii::$app->params[‘site’][‘mtg’] = Yii::t(‘frontend’, ‘Meetups’);
Yii::$app->params[‘site’][‘img’] = rand(0,1);
Yii::$app->params[‘site’][‘navbar’] = ‘navbar-default’;
Yii::$app->params[‘site’][’email_logo’] = ‘https://simpleplanner.io/img/email-logo-sp.gif’;
Yii::$app->params[‘site’][‘ga’] = ‘UA-37244292-21’;
}
|
Эти функции изменяют переменные, URL-адреса и строки в зависимости от запрошенного сервиса. Они вызываются SiteHelper::init()
:
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
|
<?php
namespace common\components;
use yii;
use yii\helpers\Url;
class SiteHelper extends \yii\base\Component{
const SITE_MP = 0;
const SITE_SP = 1;
const SITE_FD = 2;
public function init() {
$baseUrl = Url::home(true);
if (stristr($baseUrl,’/mp/’)!==false) {
// local mp
$this->commonMeetingPlanner();
Yii::$app->params[‘site’][‘url’] = ‘http://localhost:8888/mp/’;
Yii::$app->params[‘site’][‘ga’] = »;
} else if (stristr($baseUrl,’/sp/’)!==false) {
// local sp
$this->commonSimplePlanner();
Yii::$app->params[‘site’][‘url’] = ‘http://localhost:8888/sp/’;
Yii::$app->params[‘site’][‘ga’] = »;
} else if (stristr($baseUrl,’simple’)!==false) {
// simpleplanner.io
$this->commonSimplePlanner();
} else {
// default meetingplanner.io
$this->commonMeetingPlanner();
}
parent::init();
}
|
Вышеуказанные функции также отменяют настройки при вызове с localhost:8888
сайтов разработки.
Планировщик собраний — SITE_MP
, Простой планировщик — SITE_SP
, а SITE_FD
— мой секрет от вас (на данный момент).
Уникальный внешний вид домашней страницы
На данный момент я решил быстро изменить внешний вид Simple Planner (SP) и Meeting Planner (MP), используя две панели навигации Bootstrap 3.0 по умолчанию.
Вы заметите, что MP использует Yii::$app->params['site']['navbar'] = 'navbar-default';
и SP использует 'navbar-inverse';
,
В /frontend/views/layouts/home.php и main.php они применяются следующим образом:
1
2
3
4
5
6
7
|
<?php
NavBar::begin([
‘brandLabel’ => Yii::$app->params[‘site’][‘title’].’ <span class=»badge»>preview
‘brandUrl’ => Yii::$app->homeUrl,
‘options’ => [
‘class’ => Yii::$app->params[‘site’][‘navbar’].’
],
|
Это создает две разные цветовые схемы navbar:
Но как насчет изображений на обложке? Я лицензировал два игровых социальных изображения для SP и два более серьезных профессиональных изображения для MP.
Изображения чередуются случайным образом в зависимости от активного сервиса, а имена файлов изображений нумеруются как 0, 1, 2 или 3, например /img/home/home-#.jpg.
1
|
Yii::$app->params[‘site’][‘img’] = rand(2,3);
|
Вот код макета home.php, который применяет это к выбранному изображению:
1
2
3
4
5
6
7
|
<body>
<?php $this->beginBody() ?>
<div class=»row»>
<div class=»col-md-12 bgimage»
style=»background-image:
url(‘<?= $urlPrefix ?>/img/home/home-<?= Yii::$app->params[‘site’][‘img’] ?>.jpg’);»>
<div class=»bgimage-inside»></div>
|
Если вы обновите домашние страницы для SimplePlanner.io или MeetingPlanner.io , вы увидите, что изображения колеблются.
Обновление текста, изображений и ссылок
Переменные из SiteHelper выше помогают настроить текстовые метки по всему сайту. И в будущем я смогу сделать это более широко:
1
2
|
Yii::$app->params[‘site’][‘title’] = Yii::t(‘frontend’, ‘Meeting Planner’);
Yii::$app->params[‘site’][‘mtg’] = Yii::t(‘frontend’, ‘Meetings’);
|
В то время как MP называет вещи Meetings, я мог бы глобально изменить SP, чтобы использовать более социальную фразу Meetups:
1
2
|
Yii::$app->params[‘site’][‘title’] = Yii::t(‘frontend’, ‘Simple Planner’);
Yii::$app->params[‘site’][‘mtg’] = Yii::t(‘frontend’, ‘Meetups’);
|
Настройка сервисов
Meeting Planner использует множество различных сервисов для составления расписания. Их инициализация заняла больше всего времени.
Google Analytics и консоль поиска
Google немного затрудняет использование нескольких доменов с Google Analytics, поэтому я просто разделил их на разные аккаунты. SiteHelper устанавливает код GA:
1
|
Yii::$app->params[‘site’][‘ga’] = ‘UA-37244292-18’;
|
Затем они устанавливаются в представлениях макетов Home.php и Main.php:
1
2
3
4
5
6
7
8
|
<script>
(function(i,s,o,g,r,a,m){i[‘GoogleAnalyticsObject’]=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,’script’,’https://www.google-analytics.com/analytics.js’,’ga’);
ga(‘create’, ‘<?php echo Yii::$app->params[‘site’][‘ga’]; ?>’, ‘auto’);
ga(‘send’, ‘pageview’);
</script>
|
Также есть настройка домена для Google Search Console, которую я настроил у своего регистратора (показано ниже):
OAuth Войти
И так как я добавил социальную регистрацию через OAuth для Facebook, Google и LinkedIn, мне пришлось рассказать обо всех этих сервисах о домене SimplerPlanner.io. Google и LinkedIn больше всего сбивают с толку, потому что каждый вариант аргумента запроса должен быть зарегистрирован этими гениями :
Вот Google:
Вот простой LinkedIn:
Но Facebook является самым простым и наименее требовательным (просто сведите к минимуму фотографии для кормления грудью ):
Mailgun Email
Поскольку я использую Mailgun для доставки электронной почты , я создал учетную запись домена для Simple Planner с ними:
Это потребовало тщательных изменений в моем регистраторе домена:
SSL
Я использую Let’s Encrypt для https (SSL) с Meeting Planner и его административной службой. Настройка для меня прошла довольно гладко. Но добавление SimplePlanner.io не сработало, и в конечном итоге мне пришлось создать отдельные файлы конфигурации Apache (.conf) для SP и настроить файлы самостоятельно.
Вот файл http sp.conf, который перенаправляет на https:
01
02
03
04
05
06
07
08
09
10
11
12
|
<VirtualHost *:80>
ServerName simpleplanner.io
ServerAlias www.simpleplanner.io
DocumentRoot «/var/www/mp/frontend/web»
<Directory «/var/www/mp/frontend/web»>
AllowOverride All
</Directory>
RewriteEngine on
RewriteCond %{SERVER_NAME} =simpleplanner.io [OR]
RewriteCond %{SERVER_NAME} =www.simpleplanner.io
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
</VirtualHost>
|
Перенаправления просто не работали с двумя именами серверов и двумя псевдонимами серверов. Вот файл sp-ssl.conf:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName simpleplanner.io
ServerAlias www.simpleplanner.io
DocumentRoot «/var/www/mp/frontend/web»
<Directory «/var/www/mp/frontend/web»>
AllowOverride All
</Directory>
RewriteEngine On
RewriteCond %{SERVER_NAME} =www.simpleplanner.io
RewriteRule ^ https://simpleplanner.io%{REQUEST_URI} [END,QSA,R=permanent]
SSLCertificateFile /etc/letsencrypt/live/simpleplanner.io/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/simpleplanner.io/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateChainFile /etc/letsencrypt/live/simpleplanner.io/chain.pem
</VirtualHost>
</IfModule>
|
Вот как я в конечном итоге создал SSL-сертификаты с помощью Let’s Encrypt:
1
2
3
|
cd /opt/letsencrypt/
sudo ./letsencrypt-auto —apache
-d simpleplanner.io -d www.simpleplanner.io -d
|
Let’s Encrypt — это круто, но они все еще молоды, и хотя их сценарии продолжают становиться более надежными, иногда возникают проблемы, подобные этой.
Фоновая дифференциация электронной почты
Самой большой проблемой для меня при запуске Simple Planner была обработка фоновых электронных писем так, чтобы они приходили от надлежащего сервиса, то есть MP или SP, и все их ссылки делали так же.
Поскольку я сохранил базу данных унифицированной для SP и MP, я также оставил фоновую обработку для домена MP. Поэтому мне пришлось расширить базу данных, чтобы таблица «Пользователь и собрание» сохранила столбец site_id
чтобы указать, какая служба создала каждую запись.
Вот миграция базы данных Yii:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<?php
use yii\db\Schema;
use yii\db\Migration;
use frontend\models\Meeting;
class m161016_204028_extend_user_and_meeting_for_simple extends Migration
{
public function up()
{
$tableOptions = null;
if ($this->db->driverName === ‘mysql’) {
$tableOptions = ‘CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB’;
}
$this->addColumn(‘{{%meeting}}’,’site_id’,Schema::TYPE_SMALLINT.’ NOT NULL DEFAULT 0′);
$this->addColumn(‘{{%user}}’,’site_id’,Schema::TYPE_SMALLINT.’ NOT NULL DEFAULT 0′);
}
public function down()
{
$this->dropColumn(‘{{%meeting}}’,’site_id’);
$this->dropColumn(‘{{%user}}’,’site_id’);
}
}
|
Я использовал метод beforeSave()
в модели Meeting, чтобы всегда настраивать site_id
на основе текущей службы — вы можете помнить, что я создал этот метод, чтобы всегда генерировать безопасный уникальный идентификатор для каждой встречи :
01
02
03
04
05
06
07
08
09
10
|
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
if ($insert) {
$this->identifier = Yii::$app->security->generateRandomString(8);
$this->site_id = Yii::$app->params[‘site’][‘id’];
}
}
return true;
}
|
Я создал аналогичный для модели User:
1
2
3
4
5
6
7
8
9
|
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
if ($insert) {
$this->site_id = Yii::$app->params[‘site’][‘id’];
}
}
return true;
}
|
Во всем сервисе я использую метод MiscHelpers::buildCommand()
для создания безопасных ссылок, которые не требуют, чтобы пользователи MiscHelpers::buildCommand()
в систему каждый раз, когда они отвечают на электронную почту.
Я думал, что будет легко настроить эту команду в одном месте, чтобы связать с соответствующим доменом сайта. Однако для этого потребуется, чтобы этот часто используемый метод неоднократно запрашивал таблицу Meeting для site_id
. Например, buildCommand()
вызывается несколько раз для каждого участника каждой встречи.
По соображениям производительности я решил изменить все вызовы этой функции, чтобы включить site_id
. Например, все эти вызовы должны были быть изменены, как показано ниже:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
foreach ($this->participants as $p) {
if ($p->status !=Participant::STATUS_DEFAULT) {
continue;
}
// Build the absolute links to the meeting and commands
$auth_key=\common\models\User::find()->where([‘id’=>$p->participant_id])->one()->auth_key;
$links=[
‘home’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_HOME,0,$p->participant_id,$auth_key,$this->site_id),
‘view’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_VIEW,0,$p->participant_id,$auth_key,$this->site_id),
‘finalize’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_FINALIZE,0,$p->participant_id,$auth_key,$this->site_id),
‘cancel’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_CANCEL,0,$p->participant_id,$auth_key,$this->site_id),
‘decline’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_DECLINE,0,$p->participant_id,$auth_key,$this->site_id),
‘acceptall’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_ACCEPT_ALL,0,$p->participant_id,$auth_key,$this->site_id),
‘acceptplaces’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_ACCEPT_ALL_PLACES,0,$p->participant_id,$auth_key,$this->site_id),
‘accepttimes’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_ACCEPT_ALL_TIMES,0,$p->participant_id,$auth_key,$this->site_id),
‘addplace’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_ADD_PLACE,0,$p->participant_id,$auth_key,$this->site_id),
‘addtime’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_ADD_TIME,0,$p->participant_id,$auth_key,$this->site_id),
‘addnote’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_ADD_NOTE,0,$p->participant_id,$auth_key,$this->site_id),
‘footer_email’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_FOOTER_EMAIL,0,$p->participant_id,$auth_key,$this->site_id),
‘footer_block’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_FOOTER_BLOCK,$this->owner_id,$p->participant_id,$auth_key,$this->site_id),
‘footer_block_all’=>MiscHelpers::buildCommand($this->id,Meeting::COMMAND_FOOTER_BLOCK_ALL,0,$p->participant_id,$auth_key,$this->site_id),
];
|
Модель Meeting всегда загружается до этих вызовов, поэтому доступ к site_id
происходит быстрее всего.
Что дальше?
Хотя все конфигурации и небольшие изменения переменных этого сайта через некоторое время стали немного раздражать, он демонстрирует некоторые довольно интересные функции Yii Framework. Если вы еще этого не сделали, попробуйте составить расписание на новом сайте Simple Planner .
Разделите Meeting Planner со своими деловыми партнерами и Simple Planner со своими друзьями и семьей.
Есть свои мысли? Идеи? Обратная связь? Вы всегда можете связаться со мной в Twitter @reifman напрямую. Следите за будущими уроками здесь в серии « Построение стартапа с помощью PHP» .
Я также все ближе к запуску эксперимента с WeFunder, основанного на реализации новых правил краудфандинга SEC . Вы можете следить за нашим профилем там, если хотите. Я также напишу больше об этом в следующем уроке.