Это вторая часть серии «Построение стартапа на PHP», в которой я провожу вас от разработки концепции до запуска моего стартапа Meeting Planner . В этой части я предоставлю обзор целей и требований к функциям, а затем проведу вас через первоначальный дизайн и миграцию базы данных.
Поскольку мы не увидим, как много функциональности сразу же воплотится в жизнь, этот эпизод может показаться немного сухим, но важно заложить основу для всего, что последует. Если вы не спроектировали миграцию базы данных Active Record и не использовали скаффолдинговое приложение Yii, Gii, вы, вероятно, многому научитесь.
Требования к функциям
Прежде чем описать особенности базы данных Meeting Planner, я расскажу вам о высокоуровневых функциях, предлагаемых Meeting Planner.
Планирование встреч
- Разрешить пользователям создавать приглашения на собрания и приглашать другого человека (только один человек для нашего минимально жизнеспособного продукта ).
- Разрешить пользователям предлагать и выбирать места.
- Разрешить пользователям предлагать и выбирать дни и время.
- Разрешить пользователям отправлять заметки друг другу в процессе планирования встречи.
- Предложите места, подходящие для встреч, ближе к пользователю.
- Запишите журнал (или историю) всех дополнений и изменений в собраниях.
Вспомогательные места
- Разрешить пользователям добавлять любимые места, где они хотели бы регулярно встречаться.
- Разрешить пользователям добавлять свои собственные места для встреч, таких как дома и в офисе.
- Предложите места рядом с каждым участником или на равном расстоянии, частично основываясь на популярности.
Поддержка пользователей
- Вести списки всех встреч пользователя в процессе, подтвержденных и прошедших.
- Разрешить пользователям предоставлять свои контактные данные, такие как номера телефонов и адреса Skype для онлайн-конференций.
- Разрешить пользователям отключать нежелательные письма, т.е. отписаться.
- Требовать от пользователей аутентифицировать свои электронные письма перед отправкой приглашений.
- Упростите блокировку писем, связанных с нежелательными приглашениями, например спамом.
Делать вещи проще и быстрее
- Разрешить пользователям создавать шаблоны, упрощающие планирование общих встреч, с указанием дней и времени и избранных мест, например, я бы хотел запланировать утренний кофе с тем-то в моем обычном предпочтительном месте, дне недели и времени начала.
- Отправлять электронные письма с изменениями собрания, с URL-ссылками на команды для внесения изменений, например, отмены или запроса изменения места, дня или времени; аутентифицировать пользователей с помощью проверочных кодов в этих ссылках.
- Отправьте напоминание о встрече за день до этого с контактными данными и указаниями.
Доходы
- Разрешить рекламодателям, например ресторанам, кафе и пунктам проката, рекламировать свои места.
Хотя приведенный выше список не является исчерпывающим, он дает вам четкое представление о том, что нам нужно для поддержки схемы базы данных.
Установка репозитория Планировщика собраний
Чтобы начать настройку среды разработки для Meeting Planner, вы можете использовать мое руководство Программирование с Yii2: Начало работы ; следуйте инструкциям по установке Composer.
Все учебники Планировщика встреч будут помечены в нашем бесплатном репозитории Github с открытым исходным кодом. Таким образом, для этой части серии руководств вы можете установить базовую платформу Meeting Planner отсюда .
Для Meeting Planner я установил расширенный шаблон приложений Yii2, который обеспечивает немного более надежную архитектуру для сложных приложений, например, различных приложений для доступа переднего плана (конечный пользователь) и внутреннего (административный).
Чтобы начать работу с кодом, вам нужно клонировать репозиторий, проверить версию с тегами для этой части руководства, запустить инициализацию и попросить Composer обновить файлы:
1
2
3
4
5
6
|
cd ~/Sites
git clone [email protected]:newscloud/mp.git
cd ~/Sites/mp
git checkout p2
sudo -s php init
sudo composer update
|
Я использую MAMP в своей локальной среде разработки. Итак, мне нужно указать предпочитаемый URL-адрес локального хоста на ~/Sites/mp/frontend/web
:
1
2
|
cd /Applications/MAMP/htdocs
ln -s ~/Sites/mp/frontend/web/ /Applications/MAMP/htdocs/mp
|
Если вы перейдете в браузер по адресу http: // localhost: 8888 / mp , вы должны увидеть что-то вроде этого:
Затем вам нужно создать базу данных в MySQL и настроить параметры в \environments\dev\common\main-local.php
:
01
02
03
04
05
06
07
08
09
10
|
<?php
return [
‘components’ => [
‘db’ => [
‘class’ => ‘yii\db\Connection’,
‘dsn’ => ‘mysql:host=localhost;dbname=your-db’,
‘username’ => ‘your-username’,
‘password’ => ‘your-pwd’,
‘charset’ => ‘utf8’,
],
|
Прежде чем мы сможем углубиться в процесс миграции, я хотел бы познакомить вас с предварительным дизайном базы данных.
Разработка схемы базы данных
Поскольку я нахожусь на ранних этапах создания кода, я пытаюсь выполнить тщательную работу по созданию базы данных; однако, вероятно, что дизайн может измениться или эволюционировать по мере продвижения вперед.
Миграции Active Record от Yii позволяют относительно легко создавать программные базы данных в различных средах, например, локальных и производственных, и постепенно наращивать их. Вы можете узнать больше об Active Record Yii здесь .
Таблица пользователей
Первая миграция создает пользовательскую таблицу и включается в расширенный шаблон приложения Yii — см. /mp/console/migrations/m130524_201442_init.php
.
Эта миграция говорит Yii создать новую таблицу SQL с полями, необходимыми для пользовательской таблицы, показанной ниже:
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
|
<?php
use yii\db\Schema;
use yii\db\Migration;
class m130524_201442_init extends Migration
{
public function up()
{
$tableOptions = null;
if ($this->db->driverName === ‘mysql’) {
// http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
$tableOptions = ‘CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB’;
}
$this->createTable(‘{{%user}}’, [
‘id’ => Schema::TYPE_BIGPK,
‘friendly_name’ => Schema::TYPE_STRING .
‘username’ => Schema::TYPE_STRING .
‘auth_key’ => Schema::TYPE_STRING .
‘password_hash’ => Schema::TYPE_STRING .
‘password_reset_token’ => Schema::TYPE_STRING,
’email’ => Schema::TYPE_STRING .
‘role’ => Schema::TYPE_SMALLINT .
‘status’ => Schema::TYPE_SMALLINT .
‘created_at’ => Schema::TYPE_INTEGER .
‘updated_at’ => Schema::TYPE_INTEGER .
], $tableOptions);
}
public function down()
{
$this->dropTable(‘{{%user}}’);
}
}
|
Вы можете запустить первую миграцию, как показано ниже:
1
2
|
cd ~/Sites/mp
./yii migrate/up 1
|
Вы должны увидеть что-то вроде этого:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
Jeffs-Mac-mini:mp Jeff$ ./yii migrate/up 1
Yii Migration Tool (based on Yii v2.0.0)
Creating migration history table «migration»…done.
Total 1 out of 15 new migrations to be applied:
m130524_201442_init
Apply the above migration?
*** applying m130524_201442_init
> create table {{%user}} … done (time: 0.068s)
*** applied m130524_201442_init (time: 0.071s)
Migrated up successfully.
|
Yii предоставляет встроенную веб-поддержку для общих операций, таких как регистрация, вход в систему, выход из системы и многое другое. Эта функциональность и эта таблица обеспечат основу для наших начальных возможностей аутентификации. Позже мы можем расширить его различными способами, например, поддерживая Twitter или Google OAuth для аутентификации.
С миграциями Active Record вы также можете мигрировать назад. Это может быть особенно полезно во время разработки. Например, при миграции вниз будет удалена таблица User:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
Jeffs-Mac-mini:mp Jeff$ ./yii migrate/down 1
Yii Migration Tool (based on Yii v2.0.0)
Total 1 migration to be reverted:
m130524_201442_init
Revert the above migration?
*** reverting m130524_201442_init
> drop table {{%user}} … done (time: 0.001s)
*** reverted m130524_201442_init (time: 0.070s)
Migrated down successfully.
|
Если вам нужно настроить дизайн таблицы, вы можете сделать это и затем выполнить миграцию обратно.
Стол для совещаний
Схема Встречи и все таблицы, связанные с собраниями, будут невероятно важны для функциональности нашего приложения.
Вот базовая схема для Встречи:
1
2
3
4
5
6
7
8
9
|
$this->createTable(‘{{%meeting}}’, [
‘id’ => Schema::TYPE_PK,
‘owner_id’ => Schema::TYPE_BIGINT.’
‘meeting_type’ => Schema::TYPE_SMALLINT.’
‘message’ => Schema::TYPE_TEXT.’
‘status’ => Schema::TYPE_SMALLINT .
‘created_at’ => Schema::TYPE_INTEGER .
‘updated_at’ => Schema::TYPE_INTEGER .
], $tableOptions);
|
Основа собрания состоит из владельца, типа обозначения собрания, сообщения с приглашением, поля состояния, а также стандартных созданных и обновленных полей времени.
С помощью Active Record Yii может помочь нам автоматически создавать отношения между таблицами. В таблице собраний мы создадим отношение, в котором у каждого собрания есть один владелец в таблице пользователей. Мы делаем это в процессе миграции, создавая внешний ключ, соединяющий Meeting -> Owner_ID с User-> ID.
1
|
$this->addForeignKey(‘fk_meeting_owner’, ‘{{%meeting}}’, ‘owner_id’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
|
Нам также нужно отбросить внешний ключ в нисходящей миграции:
1
2
3
4
5
|
public function down()
{
$this->dropForeignKey(‘fk_meeting_owner’, ‘{{%meeting}}’);
$this->dropTable(‘{{%meeting}}’);
}
|
Терпите меня, пока я обрисую в общих чертах схему, прежде чем мы перейдем к автоматизированной системе лесов Yii, Gii.
Вы можете увидеть все миграции в /mp/console/migrations folder
:
Мы рассмотрим большинство из них ниже.
Таблица мест
Места также являются критически важным компонентом в Планировщике собраний, потому что они являются местами, которые все встретят. Они индексируются по геолокации и упоминаются в Google Местах на карте.
Вот схема для места:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
$tableOptions = null;
if ($this->db->driverName === ‘mysql’) {
$tableOptions = ‘CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB’;
}
$this->createTable(‘{{%place}}’, [
‘id’ => Schema::TYPE_PK,
‘name’ => Schema::TYPE_STRING.’
‘place_type’ => Schema::TYPE_SMALLINT.’
‘status’ => Schema::TYPE_SMALLINT .
‘google_place_id’ => Schema::TYPE_STRING.’
‘created_by’ => Schema::TYPE_BIGINT.’
‘created_at’ => Schema::TYPE_INTEGER .
‘updated_at’ => Schema::TYPE_INTEGER .
], $tableOptions);
$this->addForeignKey(‘fk_place_created_by’, ‘{{%place}}’, ‘created_by’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
|
Места состоят из name
, place_type
, status
, created_at
и updated_at
. Но они также включают google_place_id
чтобы связать их с каталогом Google Адресов .
Обратите внимание, что в этой таблице нет геолокации, связанной с местом. Это потому, что движок MySQL InnoDB не поддерживает пространственные индексы . Поэтому я создал дополнительную таблицу, используя таблицу MyISAM для координат геолокации мест. Это таблица Place_GPS:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
class m141025_213611_create_place_gps_table extends Migration
{
public function up()
{
$tableOptions = null;
if ($this->db->driverName === ‘mysql’) {
$tableOptions = ‘CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=MyISAM’;
}
$this->createTable(‘{{%place_gps}}’, [
‘id’ => Schema::TYPE_PK,
‘place_id’ => Schema::TYPE_INTEGER.’
‘gps’=>’POINT NOT NULL’,
], $tableOptions);
$this->execute(‘create spatial index place_gps_gps on ‘.'{{%place_gps}}(gps);’);
$this->addForeignKey(‘fk_place_gps’,'{{%place_gps}}’ , ‘place_id’, ‘{{%place}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
}
|
Обратите внимание, что он связан с таблицей Place с помощью place_id
. Расположение мест — это просто координата GPS или MySQL POINT .
Таблица участников
Участники собрания хранятся в объединительной таблице «Участник». Они присоединяются к таблице собраний по meeting_id
и таблице пользователей по meeting_id
. Если мы хотим иметь более одного участника собрания на одно собрание, эта таблица позволит сделать это в будущем.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class m141025_215701_create_participant_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(‘{{%participant}}’, [
‘id’ => Schema::TYPE_PK,
‘meeting_id’ => Schema::TYPE_INTEGER.’
‘participant_id’ => Schema::TYPE_BIGINT.’
‘invited_by’ => Schema::TYPE_BIGINT.’
‘status’ => Schema::TYPE_SMALLINT .
‘created_at’ => Schema::TYPE_INTEGER .
‘updated_at’ => Schema::TYPE_INTEGER .
], $tableOptions);
$this->addForeignKey(‘fk_participant_meeting’, ‘{{%participant}}’, ‘meeting_id’, ‘{{%meeting}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
$this->addForeignKey(‘fk_participant_participant’, ‘{{%participant}}’, ‘participant_id’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
$this->addForeignKey(‘fk_participant_invited_by’, ‘{{%participant}}’, ‘invited_by’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
}
|
Другие связанные столы для совещаний
Есть несколько других таблиц, которые помогают определить наши варианты встреч для планирования.
Расписание встречи
В этой таблице содержатся все рекомендуемые времена встречи (и даты) по началу, что является отметкой времени. Suggested_by
кто предложил время. И status
определяет, будет ли выбрано время для встречи.
01
02
03
04
05
06
07
08
09
10
11
|
$this->createTable(‘{{%meeting_time}}’, [
‘id’ => Schema::TYPE_PK,
‘meeting_id’ => Schema::TYPE_INTEGER.’
‘start’ => Schema::TYPE_INTEGER.’
‘suggested_by’ => Schema::TYPE_BIGINT.’
‘status’ => Schema::TYPE_SMALLINT .
‘created_at’ => Schema::TYPE_INTEGER .
‘updated_at’ => Schema::TYPE_INTEGER .
], $tableOptions);
$this->addForeignKey(‘fk_meeting_time_meeting’, ‘{{%meeting_time}}’, ‘meeting_id’, ‘{{%meeting}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
$this->addForeignKey(‘fk_participant_suggested_by’, ‘{{%meeting_time}}’, ‘suggested_by’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
|
Стол Место встречи
Эта таблица показывает, какие места были предложены для встречи:
01
02
03
04
05
06
07
08
09
10
11
12
|
$this->createTable(‘{{%meeting_place}}’, [
‘id’ => Schema::TYPE_PK,
‘meeting_id’ => Schema::TYPE_INTEGER.’
‘place_id’ => Schema::TYPE_INTEGER.’
‘suggested_by’ => Schema::TYPE_BIGINT.’
‘status’ => Schema::TYPE_SMALLINT .
‘created_at’ => Schema::TYPE_INTEGER .
‘updated_at’ => Schema::TYPE_INTEGER .
], $tableOptions);
$this->addForeignKey(‘fk_meeting_place_meeting’, ‘{{%meeting_place}}’, ‘meeting_id’, ‘{{%meeting}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
$this->addForeignKey(‘fk_meeting_place_place’, ‘{{%meeting_place}}’, ‘place_id’, ‘{{%place}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
$this->addForeignKey(‘fk_meeting_suggested_by’, ‘{{%meeting_place}}’, ‘suggested_by’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
|
Таблица журнала встреч
В этой таблице записывается история всех добавлений и изменений для конкретной встречи. Каждое действие, предпринимаемое во время планирования собрания, записывается для предоставления хронологической истории событий, связанных со встречей. Это поможет пользователям увидеть все изменения своих собраний с течением времени, а также, вероятно, поможет нам в разработке с отладкой.
01
02
03
04
05
06
07
08
09
10
11
12
|
$this->createTable(‘{{%meeting_log}}’, [
‘id’ => Schema::TYPE_PK,
‘meeting_id’ => Schema::TYPE_INTEGER.’
‘action’ => Schema::TYPE_INTEGER.’
‘actor_id’ => Schema::TYPE_BIGINT.’
‘item_id’ => Schema::TYPE_INTEGER.’
‘extra_id’ => Schema::TYPE_INTEGER.’
‘created_at’ => Schema::TYPE_INTEGER .
‘updated_at’ => Schema::TYPE_INTEGER .
], $tableOptions);
$this->addForeignKey(‘fk_meeting_log_meeting’, ‘{{%meeting_log}}’, ‘meeting_id’, ‘{{%meeting}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
$this->addForeignKey(‘fk_meeting_log_actor’, ‘{{%meeting_log}}’, ‘actor_id’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
|
Таблица заметок о встрече
Пользователи могут отправлять короткие заметки туда и обратно при внесении изменений в собрания. Эта таблица записывает эти заметки.
01
02
03
04
05
06
07
08
09
10
11
|
$this->createTable(‘{{%meeting_note}}’, [
‘id’ => Schema::TYPE_PK,
‘meeting_id’ => Schema::TYPE_INTEGER.’
‘posted_by’ => Schema::TYPE_BIGINT.’
‘note’ => Schema::TYPE_TEXT.’
‘status’ => Schema::TYPE_SMALLINT .
‘created_at’ => Schema::TYPE_INTEGER .
‘updated_at’ => Schema::TYPE_INTEGER .
], $tableOptions);
$this->addForeignKey(‘fk_meeting_note_meeting’, ‘{{%meeting_note}}’, ‘meeting_id’, ‘{{%meeting}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
$this->addForeignKey(‘fk_meeting_note_posted_by’, ‘{{%meeting_note}}’, ‘posted_by’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
|
Другие связанные пользовательские таблицы
Существует несколько таблиц для расширения определения пользователя.
Стол друзей
Это индексная таблица, в которой перечислены друзья каждого пользователя. Он также отслеживает, являются ли они любимыми друзьями, и количество встреч, которые они провели. Это может быть полезно для упрощения планирования, например, сначала показ любимых или частых друзей.
01
02
03
04
05
06
07
08
09
10
11
12
|
$this->createTable(‘{{%friend}}’, [
‘id’ => Schema::TYPE_PK,
‘user_id’ => Schema::TYPE_BIGINT.’
‘friend_id’ => Schema::TYPE_BIGINT.’
‘status’ => Schema::TYPE_SMALLINT .
‘number_meetings’ => Schema::TYPE_INTEGER .
‘is_favorite’ => Schema::TYPE_SMALLINT .
‘created_at’ => Schema::TYPE_INTEGER .
‘updated_at’ => Schema::TYPE_INTEGER .
], $tableOptions);
$this->addForeignKey(‘fk_friend_user_id’, ‘{{%friend}}’, ‘user_id’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
$this->addForeignKey(‘fk_friend_friend_id’, ‘{{%friend}}’, ‘friend_id’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
|
Таблица пользовательских мест
Это индексная таблица мест, в которых пользователь предпочитает встречаться или встречался в прошлом. Мы будем отслеживать любимые места и количество встреч, проведенных этим пользователем здесь. Поле is_special
будет указывать, что место является собственным домом, офисом или местом встречи пользователя.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
$this->createTable(‘{{%user_place}}’, [
‘id’ => Schema::TYPE_PK,
‘user_id’ => Schema::TYPE_BIGINT.’
‘place_id’ => Schema::TYPE_INTEGER.’
‘is_favorite’ => Schema::TYPE_SMALLINT .
‘number_meetings’ => Schema::TYPE_INTEGER .
‘is_special’ => Schema::TYPE_SMALLINT .
‘note’ => Schema::TYPE_STRING .
‘status’ => Schema::TYPE_SMALLINT .
‘created_at’ => Schema::TYPE_INTEGER .
‘updated_at’ => Schema::TYPE_INTEGER .
], $tableOptions);
$this->addForeignKey(‘fk_user_place_user’, ‘{{%user_place}}’, ‘user_id’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
$this->addForeignKey(‘fk_user_place_place’, ‘{{%user_place}}’, ‘place_id’, ‘{{%place}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
|
Таблица контактов пользователя
Эта таблица содержит контактную информацию для конкретного пользователя, например номера телефонов, адреса Skype и любые заметки, связанные с контактами с пользователем в этих местах.
01
02
03
04
05
06
07
08
09
10
11
|
$this->createTable(‘{{%user_contact}}’, [
‘id’ => Schema::TYPE_PK,
‘user_id’ => Schema::TYPE_BIGINT.’
‘contact_type’ => Schema::TYPE_SMALLINT .
‘info’ => Schema::TYPE_STRING .
‘details’ => Schema::TYPE_TEXT .
‘status’ => Schema::TYPE_SMALLINT .
‘created_at’ => Schema::TYPE_INTEGER .
‘updated_at’ => Schema::TYPE_INTEGER .
], $tableOptions);
$this->addForeignKey(‘fk_user_contact_user’, ‘{{%user_contact}}’, ‘user_id’, ‘{{%user}}’, ‘id’, ‘CASCADE’, ‘CASCADE’);
|
Для простоты я пропущу сейчас схему шаблона собрания. И я еще не разработал функции, связанные с доходами. Основная причина этого заключается в том, что в данный момент у меня много работы, чтобы запустить основной набор функций и завершить первые несколько серий учебного цикла. Тем не менее, это обучающий момент. Это хороший пример предпринимателя с ограниченными ресурсами, который сосредотачивается на основной функциональности, не «осознавая», что получение дохода также является основной функцией. Поскольку я считаю, что изначально могу запустить Meeting Planner без дохода, это компромисс, который я могу сделать в настоящее время.
Запуск миграций базы данных
Теперь, когда у вас есть немного больше информации о нашей схеме базы данных и миграции Active Record, давайте запустим остальные из них:
1
2
|
cd ~/Sites/mp
./yii migrate/up all
|
Вы должны увидеть что-то вроде этого:
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
|
Yii Migration Tool (based on Yii v2.0.0)
Total 14 new migrations to be applied:
m141025_212656_create_meeting_table
m141025_213610_create_place_table
m141025_213611_create_place_gps_table
m141025_215701_create_participant_table
m141025_215833_create_meeting_time_table
m141025_220016_create_meeting_place_table
m141025_220133_create_meeting_log_table
m141025_220524_create_friend_table
m141025_220923_create_user_place_table
m141025_221627_create_meeting_note_table
m141025_221902_create_user_contact_table
m141025_222213_create_template_table
m141025_222431_create_template_time_table
m141025_222531_create_template_place_table
Apply the above migrations?
*** applying m141025_212656_create_meeting_table
> create table {{%meeting}} … done (time: 0.124s)
> add foreign key fk_meeting_owner: {{%meeting}} (owner_id) references {{%user}} (id) … done (time: 0.307s)
*** applied m141025_212656_create_meeting_table (time: 0.434s)
*** applying m141025_213610_create_place_table
> create table {{%place}} … done (time: 0.091s)
> add foreign key fk_place_created_by: {{%place}} (created_by) references {{%user}} (id) … done (time: 0.114s)
*** applied m141025_213610_create_place_table (time: 0.206s)
*** applying m141025_213611_create_place_gps_table
> create table {{%place_gps}} … done (time: 0.120s)
> execute SQL: create spatial index place_gps_gps on {{%place_gps}}(gps);
> add foreign key fk_place_gps: {{%place_gps}} (place_id) references {{%place}} (id) … done (time: 0.112s)
*** applied m141025_213611_create_place_gps_table (time: 0.347s)
*** applying m141025_215701_create_participant_table
> create table {{%participant}} … done (time: 0.100s)
> add foreign key fk_participant_meeting: {{%participant}} (meeting_id) references {{%meeting}} (id) … done (time: 0.138s)
> add foreign key fk_participant_participant: {{%participant}} (participant_id) references {{%user}} (id) … done (time: 0.112s)
> add foreign key fk_participant_invited_by: {{%participant}} (invited_by) references {{%user}} (id) … done (time: 0.149s)
*** applied m141025_215701_create_participant_table (time: 0.500s)
*** applying m141025_215833_create_meeting_time_table
> create table {{%meeting_time}} … done (time: 0.142s)
> add foreign key fk_meeting_time_meeting: {{%meeting_time}} (meeting_id) references {{%meeting}} (id) … done (time: 0.148s)
> add foreign key fk_participant_suggested_by: {{%meeting_time}} (suggested_by) references {{%user}} (id) … done (time: 0.122s)
*** applied m141025_215833_create_meeting_time_table (time: 0.413s)
*** applying m141025_220016_create_meeting_place_table
> create table {{%meeting_place}} … done (time: 0.120s)
> add foreign key fk_meeting_place_meeting: {{%meeting_place}} (meeting_id) references {{%meeting}} (id) … done (time: 0.125s)
> add foreign key fk_meeting_place_place: {{%meeting_place}} (place_id) references {{%place}} (id) … done (time: 0.135s)
> add foreign key fk_meeting_suggested_by: {{%meeting_place}} (suggested_by) references {{%user}} (id) … done (time: 0.137s)
*** applied m141025_220016_create_meeting_place_table (time: 0.518s)
*** applying m141025_220133_create_meeting_log_table
> create table {{%meeting_log}} … done (time: 0.109s)
> add foreign key fk_meeting_log_meeting: {{%meeting_log}} (meeting_id) references {{%meeting}} (id) … done (time: 0.126s)
> add foreign key fk_meeting_log_actor: {{%meeting_log}} (actor_id) references {{%user}} (id) … done (time: 0.113s)
*** applied m141025_220133_create_meeting_log_table (time: 0.348s)
*** applying m141025_220524_create_friend_table
> create table {{%friend}} … done (time: 0.109s)
> add foreign key fk_friend_user_id: {{%friend}} (user_id) references {{%user}} (id) … done (time: 0.125s)
> add foreign key fk_friend_friend_id: {{%friend}} (friend_id) references {{%user}} (id) … done (time: 0.102s)
*** applied m141025_220524_create_friend_table (time: 0.337s)
*** applying m141025_220923_create_user_place_table
> create table {{%user_place}} … done (time: 0.109s)
> add foreign key fk_user_place_user: {{%user_place}} (user_id) references {{%user}} (id) … done (time: 0.137s)
> add foreign key fk_user_place_place: {{%user_place}} (place_id) references {{%place}} (id) … done (time: 0.114s)
*** applied m141025_220923_create_user_place_table (time: 0.360s)
*** applying m141025_221627_create_meeting_note_table
> create table {{%meeting_note}} … done (time: 0.109s)
> add foreign key fk_meeting_note_meeting: {{%meeting_note}} (meeting_id) references {{%meeting}} (id) … done (time: 0.125s)
> add foreign key fk_meeting_note_posted_by: {{%meeting_note}} (posted_by) references {{%user}} (id) … done (time: 0.101s)
*** applied m141025_221627_create_meeting_note_table (time: 0.337s)
*** applying m141025_221902_create_user_contact_table
> create table {{%user_contact}} … done (time: 0.098s)
> add foreign key fk_user_contact_user: {{%user_contact}} (user_id) references {{%user}} (id) … done (time: 0.125s)
*** applied m141025_221902_create_user_contact_table (time: 0.225s)
*** applying m141025_222213_create_template_table
> create table {{%template}} … done (time: 0.108s)
> add foreign key fk_template_owner: {{%template}} (owner_id) references {{%user}} (id) … done (time: 0.171s)
*** applied m141025_222213_create_template_table (time: 0.281s)
*** applying m141025_222431_create_template_time_table
> create table {{%template_time}} … done (time: 0.111s)
> add foreign key fk_template_time_template: {{%template_time}} (template_id) references {{%template}} (id) … done (time: 0.114s)
*** applied m141025_222431_create_template_time_table (time: 0.226s)
*** applying m141025_222531_create_template_place_table
> create table {{%template_place}} … done (time: 0.099s)
> add foreign key fk_template_place_template: {{%template_place}} (template_id) references {{%template}} (id) … done (time: 0.103s)
> add foreign key fk_template_place_place: {{%template_place}} (place_id) references {{%place}} (id) … done (time: 0.101s)
*** applied m141025_222531_create_template_place_table (time: 0.304s)
Migrated up successfully.
|
Точно так же, когда мы устанавливаем Meeting Planner в производство, мы будем использовать миграции для создания исходной базы данных. Нет необходимости экспортировать и импортировать файлы SQL, которые могут сломаться в зависимости от множества версий, которые мы можем использовать в разных средах.
Регистрация Административного пользователя
Прежде чем идти дальше, вам необходимо зарегистрироваться в качестве администратора. Нажмите на ссылку регистрации на панели инструментов и просто зарегистрируйтесь в приложении.
Если вы добились успеха, когда вы вернетесь на домашнюю страницу, вы увидите, что на панели инструментов отображается ваш статус входа в систему.
Эти формы и логика приложения включены в расширенный шаблон приложения Yii.
Использование Gii от Yii для создания строительных лесов
Теперь мы можем построить леса для поддержки кода контроллера представления модели для общих операций создания, чтения, обновления и удаления (CRUD).
Мы будем использовать Gii, удивительный автоматический генератор кода Yii, для создания большого количества нашего базового фреймворкового кода. Название может быть глупым, но оно невероятно мощное и занимает центральное место в разработке Yii. Начнем с встреч и мест.
Использование Gii
Укажите ваш браузер на http: // localhost: 8888 / mp / gii . Вы должны увидеть это:
Генерация моделей
При сборке с Gii вы обычно начинаете с Генератора моделей для каждой таблицы. Прежде чем вы сможете использовать Генератор моделей, вы должны запустить свои миграции для создания таблиц в базе данных, как мы делали выше. Gii использует определения таблиц SQL для генерации кода для вашей модели.
Давайте использовать Генератор моделей для генерации кода модели для таблицы Meeting. Код уже будет сгенерирован в вашем репозитории Github, но вы можете снова выполнить эти упражнения. Gii предварительно просмотрит и при желании перезапишет код для вас.
Заполните Генератор моделей следующим образом для модели Встречи:
Затем сгенерируйте модель Place:
Gii довольно удивителен — опираясь на наше определение таблицы, он генерирует массу логики.
В модели /mp/frontend/models/Meeting.php
вы увидите автоматически сгенерированные метки атрибутов:
01
02
03
04
05
06
07
08
09
10
11
12
|
public function attributeLabels()
{
return [
‘id’ => ‘ID’,
‘owner_id’ => ‘Owner ID’,
‘meeting_type’ => ‘Meeting Type’,
‘message’ => ‘Message’,
‘status’ => ‘Status’,
‘created_at’ => ‘Created At’,
‘updated_at’ => ‘Updated At’,
];
}
|
Он генерирует правила проверки полей для форм:
1
2
3
4
5
6
7
8
|
public function rules()
{
return [
[[‘owner_id’, ‘message’, ‘created_at’, ‘updated_at’], ‘required’],
[[‘owner_id’, ‘meeting_type’, ‘status’, ‘created_at’, ‘updated_at’], ‘integer’],
[[‘message’], ‘string’]
];
}
|
И он генерирует отношения с базой данных — вот несколько, например:
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
|
/* @property User $owner
* @property MeetingLog[] $meetingLogs
* @property MeetingNote[] $meetingNotes
* @property MeetingPlace[] $meetingPlaces
* @property MeetingTime[] $meetingTimes
* @property Participant[] $participants
*/
/**
* @return \yii\db\ActiveQuery
*/
public function getMeetingLogs()
{
return $this->hasMany(MeetingLog::className(), [‘meeting_id’ => ‘id’]);
}
/**
* @return \yii\db\ActiveQuery
*/
public function getMeetingNotes()
{
return $this->hasMany(MeetingNote::className(), [‘meeting_id’ => ‘id’]);
}
/**
* @return \yii\db\ActiveQuery
*/
public function getMeetingPlaces()
{
return $this->hasMany(MeetingPlace::className(), [‘meeting_id’ => ‘id’]);
}
|
Генерация CRUD
Теперь мы можем использовать генератор CRUD для создания кода для операций создания, чтения, обновления и удаления.
Посетите Генератор CRUD и создайте его для Встреч. Обратите внимание, что интерфейс — это приложение Yii, которое увидит пользователь.
Когда вы нажимаете Preview , вы должны увидеть что-то вроде этого:
Когда вы нажмете « Создать» , вы должны увидеть следующие результаты:
Затем повторите процесс выше для мест.
Уже сейчас вы можете просматривать собрания и места на нашем сайте, чтобы увидеть код, созданный в действии. Укажите в браузере адрес http: // localhost: 8888 / mp / meeting . Это должно выглядеть примерно так:
Если вы зарегистрировали свою учетную запись, вы сможете создать встречу. Обратите внимание, что Gii не знает разницы между полями, которыми должен управлять наш код, и полями, которые должны быть предоставлены пользователями. Мы очистим их в следующих уроках. Сейчас вам нужно ввести целые числа для owner_id
(используйте 1 — это первый meeting_type
в систему пользователь), meeting_type
, status
, created_at
и updated_at
:
После создания пары собраний страница указателя собрания будет выглядеть следующим образом:
Объединение возможностей Gii и Yii делает создание веб-приложений намного быстрее, чем могло бы быть. Удивительно, что, имея лишь структуру таблицы базы данных и блок кода миграции, мы можем быть в шаге от рабочих контроллеров и форм, созданных в ответ на Bootstrap.
Что дальше?
Надеюсь, вам понравилась база данных и прохождение Gii. Следующая статья в этой серии будет посвящена созданию функциональности вокруг Places. В нем будет описано, как использовать Google Places, Google Maps и HTML5 для определения функций, необходимых для Планировщика собраний. Если вы хотите поближе познакомиться с этими темами, я написал соответствующий учебник « Как использовать Zillow Neighbourhood Maps и HTML5 Geolocation» .
Пожалуйста, не стесняйтесь добавлять свои вопросы и комментарии ниже; Я обычно участвую в обсуждениях. Вы также можете связаться со мной в Twitter @reifman или написать мне напрямую.