这是“使用 PHP 构建您的初创公司”系列的第二部分,在其中我将引导您完成我的初创公司 Meeting Planner 从概念到启动的整个开发过程。在这一部分中,我将概述功能目标和要求,然后引导您完成初始数据库设计和迁移。
因为我们不会看到很多功能立即生效,所以这一集可能看起来有点枯燥,但为接下来的所有内容奠定基础至关重要。如果您还没有设计过 Active Record 数据库迁移并使用过 Yii 的脚手架应用程序 Gii,那么您可能会学到很多东西。
在描述 Meeting Planner 数据库的具体信息之前,我将引导您了解 Meeting Planner 将提供的高级功能。
虽然上面并不是详尽的功能列表,但它使您清楚地了解我们需要数据库架构支持什么。
要开始为 Meeting Planner 设置开发环境,您可以使用我的指南《使用 Yii2 编程:入门》;按照说明安装 Composer。
所有会议策划者教程都将在我们的免费开源 Github 存储库中进行标记。因此,对于本教程系列的这一部分,您可以从此处安装基本的会议规划器框架。
对于 Meeting Planner,我安装了 Yii2 的高级应用程序模板,它为复杂的应用程序提供了稍微更强大的架构,例如用于前端(最终用户)和后端(管理)访问的不同应用程序。
要开始使用代码,您需要克隆存储库,查看本部分教程的标记版本,运行初始化,并要求 Composer 更新文件:
cd ~/Sites git clone git@github.com:newscloud/mp.git cd ~/Sites/mp git checkout p2 sudo -s php init sudo composer update
我在本地开发环境中使用 MAMP。因此,我需要将我首选的前端本地主机 URL 指向 ~/Sites/mp/frontend/web
:
cd /Applications/MAMP/htdocs ln -s ~/Sites/mp/frontend/web/ /Applications/MAMP/htdocs/mp
如果您将浏览器导航到 http://localhost:8888/mp,您应该会看到如下内容:
然后,您需要在 MySQL 中创建数据库并在 环境devcommonmain-local.php
:
<?php return [ 'components' => [ 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=your-db', 'username' => 'your-username', 'password' => 'your-pwd', 'charset' => 'utf8', ],
在我们进一步深入运行迁移之前,我想引导您完成初步的数据库设计。
因为我正处于构建代码的早期阶段,所以我试图彻底完成数据库的布局;然而,随着我的前进,设计可能需要改变或发展。
Yii 的 Active Record 迁移使得在不同环境中以编程方式创建数据库变得相对容易,例如:本地和生产,并逐步发展它们。您可以在此处了解有关 Yii Active Record 的更多信息。
第一次迁移构建了用户表,它包含在 Yii 的高级应用程序模板中 - 请参阅 /mp/console/migrations/m130524_201442_init.php
。
此迁移告诉 Yii 创建一个新的 SQL 表,其中包含用户表所需的字段,如下所示:
<?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 . ' NOT NULL', 'username' => Schema::TYPE_STRING . ' NOT NULL', 'auth_key' => Schema::TYPE_STRING . '(32) NOT NULL', 'password_hash' => Schema::TYPE_STRING . ' NOT NULL', 'password_reset_token' => Schema::TYPE_STRING, 'email' => Schema::TYPE_STRING . ' NOT NULL', 'role' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 10', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 10', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $tableOptions); } public function down() { $this->dropTable('{{%user}}'); } }
您可以运行第一次迁移,如下所示:
cd ~/Sites/mp ./yii migrate/up 1
您应该看到类似这样的内容:
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? (yes|no) [no]:yes *** applying m130524_201442_init > create table {{%user}} ... done (time: 0.068s) *** applied m130524_201442_init (time: 0.071s) Migrated up successfully.
Yii 为注册、登录、注销等常见操作提供内置 Web 支持。此功能和此表将为我们的初始身份验证功能提供支持基础。我们稍后可能会以各种方式对其进行扩展,例如支持 Twitter 或 Google OAuth 进行身份验证。
通过 Active Record 迁移,您还可以向后迁移。这在开发过程中特别有帮助。例如向下迁移会删除User表:
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? (yes|no) [no]:yes *** reverting m130524_201442_init > drop table {{%user}} ... done (time: 0.001s) *** reverted m130524_201442_init (time: 0.070s) Migrated down successfully.
如果您需要调整表格设计,可以先进行调整,然后再迁移回来。
会议架构以及与会议相关的所有表格对于我们应用程序的功能非常重要。
这是会议的基本架构:
$this->createTable('{{%meeting}}', [ 'id' => Schema::TYPE_PK, 'owner_id' => Schema::TYPE_BIGINT.' NOT NULL', 'meeting_type' => Schema::TYPE_SMALLINT.' NOT NULL DEFAULT 0', 'message' => Schema::TYPE_TEXT.' NOT NULL DEFAULT ""', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $tableOptions);
会议的基础由所有者、会议指示符类型、邀请消息、状态字段以及标准创建和更新时间字段组成。
通过 Active Record,Yii 可以帮助我们自动创建表之间的关系。在会议表中,我们将创建一个关系,即每个会议在用户表中都有一个所有者。我们在迁移中通过创建一个外键将会议 -> Owner_ID 连接到用户 -> ID 来完成此操作。
$this->addForeignKey('fk_meeting_owner', '{{%meeting}}', 'owner_id', '{{%user}}', 'id', 'CASCADE', 'CASCADE');
我们还需要在向下迁移中删除外键:
public function down() { $this->dropForeignKey('fk_meeting_owner', '{{%meeting}}'); $this->dropTable('{{%meeting}}'); }
在我们进入 Yii 的自动化脚手架系统 Gii 之前,请耐心等待我概述更多架构。
您可以在 /mp/console/migrations 文件夹
中看到所有迁移:
我们将在下面回顾其中的大部分内容。
地点也是 Meeting Planner 中的一个重要组成部分,因为它们是每个人都会见面的地点。它们按地理位置编入索引,并在 Google 地方信息中引用。
这是地点的架构:
$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.' NOT NULL', 'place_type' => Schema::TYPE_SMALLINT.' NOT NULL DEFAULT 0', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'google_place_id' => Schema::TYPE_STRING.' NOT NULL', // e.g. google places id 'created_by' => Schema::TYPE_BIGINT.' NOT NULL', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $tableOptions); $this->addForeignKey('fk_place_created_by', '{{%place}}', 'created_by', '{{%user}}', 'id', 'CASCADE', 'CASCADE');
地点由 name
、place_type
、status
、created_at
和 更新_at
。但它们还包含 google_place_id
将它们与 Google Places 目录关联起来。
请注意,没有任何与此表中的地点关联的地理位置。这是因为 MySQL InnoDB 引擎不支持空间索引。因此,我使用 MyISAM 表创建了一个辅助表来存储地点的地理位置坐标。这是 Place_GPS 表:
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.' NOT NULL', '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_id
与 Place 表相关。地点的位置只是 GPS 坐标,或 MySQL POINT。
会议参与者存储在名为“参与者”的连接表中。他们通过 meeting_id
加入会议表,并通过 participant_id
加入用户表。如果我们希望每次会议有多个会议参与者,此表将在将来实现这一点。
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.' NOT NULL', 'participant_id' => Schema::TYPE_BIGINT.' NOT NULL', 'invited_by' => Schema::TYPE_BIGINT.' NOT NULL', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $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
决定是否选择会议时间。
$this->createTable('{{%meeting_time}}', [ 'id' => Schema::TYPE_PK, 'meeting_id' => Schema::TYPE_INTEGER.' NOT NULL', 'start' => Schema::TYPE_INTEGER.' NOT NULL', 'suggested_by' => Schema::TYPE_BIGINT.' NOT NULL', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $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');
此表显示了为会议建议的地点:
$this->createTable('{{%meeting_place}}', [ 'id' => Schema::TYPE_PK, 'meeting_id' => Schema::TYPE_INTEGER.' NOT NULL', 'place_id' => Schema::TYPE_INTEGER.' NOT NULL', 'suggested_by' => Schema::TYPE_BIGINT.' NOT NULL', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $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');
此表记录特定会议的所有添加和更改的历史记录。会议安排期间采取的每个操作都会被记录下来,以提供与会议相关的事件的时间历史记录。它将帮助用户查看一段时间内会议的所有更改的记录,并且还可能帮助我们进行开发调试。
$this->createTable('{{%meeting_log}}', [ 'id' => Schema::TYPE_PK, 'meeting_id' => Schema::TYPE_INTEGER.' NOT NULL', 'action' => Schema::TYPE_INTEGER.' NOT NULL', 'actor_id' => Schema::TYPE_BIGINT.' NOT NULL', 'item_id' => Schema::TYPE_INTEGER.' NOT NULL', 'extra_id' => Schema::TYPE_INTEGER.' NOT NULL', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $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');
用户在对会议进行更改时可以来回发送简短的注释。该表记录了这些注释。
$this->createTable('{{%meeting_note}}', [ 'id' => Schema::TYPE_PK, 'meeting_id' => Schema::TYPE_INTEGER.' NOT NULL', 'posted_by' => Schema::TYPE_BIGINT.' NOT NULL DEFAULT 0', 'note' => Schema::TYPE_TEXT.' NOT NULL DEFAULT ""', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $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');
有几个表可以扩展用户定义。
这是一个索引表,列出了每个用户的好友。它还跟踪他们是否是最喜欢的朋友以及他们举行的会议数量。这可能有助于简化调度体验,例如首先显示最喜欢的或经常的朋友。
$this->createTable('{{%friend}}', [ 'id' => Schema::TYPE_PK, 'user_id' => Schema::TYPE_BIGINT.' NOT NULL', 'friend_id' => Schema::TYPE_BIGINT.' NOT NULL', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'number_meetings' => Schema::TYPE_INTEGER . ' NOT NULL DEFAULT 0', 'is_favorite' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $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
字段将指示某个地点是用户自己的家、办公室或会议地点。
$this->createTable('{{%user_place}}', [ 'id' => Schema::TYPE_PK, 'user_id' => Schema::TYPE_BIGINT.' NOT NULL', 'place_id' => Schema::TYPE_INTEGER.' NOT NULL', 'is_favorite' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'number_meetings' => Schema::TYPE_INTEGER . ' NOT NULL DEFAULT 0', 'is_special' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'note' => Schema::TYPE_STRING . ' NOT NULL', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $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 地址以及与在这些地方联系用户相关的任何注释。
$this->createTable('{{%user_contact}}', [ 'id' => Schema::TYPE_PK, 'user_id' => Schema::TYPE_BIGINT.' NOT NULL', 'contact_type' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'info' => Schema::TYPE_STRING . ' NOT NULL', 'details' => Schema::TYPE_TEXT . ' NOT NULL', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $tableOptions); $this->addForeignKey('fk_user_contact_user', '{{%user_contact}}', 'user_id', '{{%user}}', 'id', 'CASCADE', 'CASCADE');
为了简单起见,我现在将跳过会议模板架构。而且我还没有设计与收入相关的功能。这样做的主要原因是,我目前有很多工作要做,以运行核心功能集并完成本教程系列的前几集。然而,这是一个值得教育的时刻。这是一个很好的例子,说明企业家的资源有限,专注于核心功能,但没有“意识到”创收也是一个核心功能。因为我相信我最初可以在没有收入的情况下启动 Meeting Planner,所以这是我目前能够做出的妥协。
既然您对我们的数据库架构和 Active Record 迁移有了更多的背景知识,那么让我们运行其余部分:
cd ~/Sites/mp ./yii migrate/up all
您应该看到类似这样的内容:
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? (yes|no) [no]:yes *** 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); ... done (time: 0.114s) > 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 的高级应用模板中。
现在我们可以构建脚手架来支持常见的创建、读取、更新和删除操作 (CRUD) 的模型视图控制器代码。
我们将使用 Gii(Yii 令人惊叹的自动代码生成器)来构建许多基本框架代码。这个名字可能很愚蠢,但它非常强大并且是 Yii 开发的核心。我们将从会议和地点开始。
将浏览器指向 http://localhost:8888/mp/gii。你应该看到这个:
使用 Gii 构建时,通常从每个表的模型生成器开始。在使用模型生成器之前,您必须先运行迁移以在数据库中创建表,就像我们上面所做的那样。 Gii 使用 SQL 表定义为您的模型生成代码。
让我们使用模型生成器为会议桌生成模型代码。代码已在您的 Github 存储库中生成,但您可以随意再次运行这些练习。 Gii 将为您预览并可选择覆盖代码。
按如下所示为会议模型填写模型生成器:
然后,生成 Place 模型:
Gii 非常令人惊奇——基于我们的表定义,它生成了大量的逻辑。
在 /mp/frontend/models/Meeting.php
模型中,您将看到自动生成的属性标签:
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', ]; }
它为表单生成字段验证规则:
public function rules() { return [ [['owner_id', 'message', 'created_at', 'updated_at'], 'required'], [['owner_id', 'meeting_type', 'status', 'created_at', 'updated_at'], 'integer'], [['message'], 'string'] ]; }
它会生成数据库关系 - 以下是一些示例:
/* @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 生成器并为会议创建它。请注意,前端是用户将看到的 Yii 应用程序。
当您点击预览时,您应该会看到如下内容:
当您点击生成时,您应该会看到以下结果:
接下来,对地点重复上述过程。
现在,您实际上可以浏览我们网站上的会议和地点,以查看实际生成的代码。将浏览器指向 http://localhost:8888/mp/meeting。它应该看起来像这样:
如果您注册了帐户,则应该能够创建会议。请注意,Gii 不知道我们的代码应管理的字段和用户要提供的字段之间的区别。我们将在接下来的教程中清理这些内容。目前,您需要为 owner_id
输入整数(使用 1 - 这是第一个登录的用户)、meeting_type
、status
、created_at
和 updated_at
:
创建几次会议后,会议索引页面将如下所示:
p>
结合 Gii 和 Yii 的强大功能,可以比其他方式更快地构建 Web 应用程序。令人惊奇的是,只需一个数据库表结构和一段迁移代码,我们就可以轻松使用 Bootstrap 响应式构建的工作控制器和表单。
我希望您对数据库和 Gii 演练感兴趣。本系列的下一篇文章将重点介绍围绕地点构建功能。它将介绍如何使用 Google 地方信息、Google 地图和 HTML5 地理定位来构建 Meeting Planner 所需的功能。如果您想先了解一下这些主题,我编写了一个相关教程:如何使用 Zillow Neighborhood Maps 和 HTML5 Geolocation。
请随时在下面添加您的问题和评论;我一般都会参与讨论。您还可以通过 Twitter @reifman 联系我或直接给我发送电子邮件。
以上是使用 PHP 设计初创公司的数据库并概述功能需求的详细内容。更多信息请关注PHP中文网其他相关文章!