本来这一章节的标题是富文本编辑器和日期组件的使用,老朋友们应该都清楚,关于富文本和日期组件,前面我们有相关博客已经写的那是相当的明白,我是实在也写不出来新鲜的感觉了,现在一周写1-3篇文章也很赶,毕竟只是业余时间去写。
但是写文章的动力和激情那是磨灭不了的!好了,废话不多说了,我们这里贴一下富文本编辑器和日期时间组件使用的链接,一路跟着实战的朋友可以去参考一下,有任何问题留言即可。
我们来看今天的重点:如何实现多表关联操作。
任务需求:
假设我们要创建文章,在创建文章的表单页面,要同时选择相应的栏目。且文章跟栏目是一对多的关系,也就是说创建文章的时候要选择多个所属栏目。
数据表的分析:
需要博客表 blog
需要栏目表 category
需要博客和栏目的关联表 blog_category
很显然,一篇文章可以对应多个栏目,而一个栏目也对应着多篇文章。即二者之间是多对多的关系。
blog表和category表先前已创建,这里我们再创建一个关联表 blog_category1
2
3
4
5
6CREATE TABLE `blog_category` (
`blog_id` int(11) NOT NULL COMMENT '文章ID',
`category_id` int(11) NOT NULL COMMENT '栏目ID',
KEY `blog_id` (`blog_id`),
KEY `category_id` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='文章-栏目关联表';
具体实现:
先看controller层,如果我们要单纯的创建一个表单模型,只需要为 ActiveForm 传递该模型的实例即可。但是,对于 Blog 模型表单,如何增加显示一个blog数据表不存在的栏目选项呢?
这个很简单,我们只需要在 common\models\Blog 模型中声明一个栏目属性,并添加相应的rules。
提醒:如果已经存在了某字段,尽量不要再声明属性,避免踩坑。1
2
3
4
5
6
7
8
9
10
11
12
13class Blog extends \yii\db\ActiveRecord
{
// ...
public $category;
//...
public function rules()
{
return [
[['title', 'content', 'category'], 'required'],
//...
];
}
}
我们想要该字段的展示形式自然是复选框checkbox。构建该字段之前,我们先在 common\models\Category 模型类中增加一个可以获取所有栏目的方法,该方法中涉及到关于工具类 yii\helpers\ArrayHelper 的使用,需要提醒的是,该工具类的使用日后那绝对是非常普遍的。这里不再说明 yii\helpers\ArrayHelper::map的用法,可手动打印 dropDownList 方法的返回值自行体会,亦可自己去研究下源码哦。
common\models\Category类增加 dropDownList 方法,获取栏目id,栏目名的键值对1
2
3
4
5
6
7
8
9
10
11use yii\helpers\ArrayHelper;
/**
* 获取栏目的枚举值,
* key=>value的形式组合:key表示栏目ID,value表示栏目名称
*/
public static function dropDownList ()
{
$query = static::find();
$enums = $query->all();
return $enums ? ArrayHelper::map($enums, 'id', 'name') : [];
}
回到文章的表单界面(views\blog_form.php),我们来构建下栏目字段1
2
3
4
5
6
7
8
9
10
11use common\models\Category;
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'title')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'content')->textArea(['maxlength' => true, 'rows' => 10]) ?>
<?= $form->field($model, 'is_delete')->dropDownList(Blog::dropDownList('is_delete')) ?>
<?= $form->field($model, 'category')->label('栏目')->checkboxList(Category::dropDownList()) ?>
<div class="form-group">
<?= Html::submitButton($model->isNewRecord ? '添加' : '更新', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
我们看到关于模型新增属性的使用并无特殊之处。
注意:这里的 Blog::dropDownList 方法需要自己手动构建,参考我们刚刚写的Category::dropDownList。
表单构建好之后,先不急着提交,我们来看另外一个问题,在 yii2 中如何使用事务?
通常情况下你可以参考下面这个样本。1
2
3
4
5
6
7
8
9
10
11
12$transaction = Yii::$app->db->beginTransaction();
try {
// current model save
// batch insert category
// 提交
$transaction->commit();
} catch (\Exception $e) {
// 回滚
$transaction->rollback();
// 抛出异常
throw $e;
}
剩下的就是事务中涉及到业务逻辑代码的实现了,来看下controller层具体又是如何实现的,细节比较多,都在代码注释中,仔细看,细心品。1
2
3
4
5
6
7
8
9
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
52use common\models\Blog;
use yii\base\Exception;
use common\models\BlogCategory;
public function actionCreate()
{
$model = new Blog();
// 注意这里调用的是validate,非save,save我们放在了事务中处理了
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
$transaction = Yii::$app->db->beginTransaction();
try {
// ($file = Upload::up($model, 'file')) && $model->file = $file;
/**
* current model save
*/
$model->save(false);
// 注意我们这里是获取刚刚插入blog表的id
$blogId = $model->id;
/**
* batch insert category
* 我们在Blog模型中设置过category字段的验证方式是required,因此下面foreach使用之前无需再做判断
*/
$data = [];
foreach ($model->category as $k => $v) {
// 注意这里的属组形式[blog_id, category_id],一定要跟下面batchInsert方法的第二个参数保持一致
$data[] = [$blogId, $v];
}
// 获取BlogCategory模型的所有属性和表名
$blogCategory = new BlogCategory;
$attributes = ['blog_id', 'category_id'];
$tableName = $blogCategory::tableName();
$db = BlogCategory::getDb();
// 批量插入栏目到BlogCategory::tableName表
$db->createCommand()->batchInsert(
$tableName,
$attributes,
$data
)->execute();
// 提交
$transaction->commit();
return $this->redirect(['index']);
} catch (\Exception $e) {
// 回滚
$transaction->rollback();
throw $e;
}
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
虽然添加的操作已经ok了,但是此时我们访问 /index.php?r=blog/update&id=1 会发现问题,栏目没有选中哎,心塞,他咋就没选中呢……
来简单分析一下:category 是我们添加的属性,没有值,要是修改的时候默认有了选中值那才见鬼了呢!那我们看下如何显示关联的栏目呢?
1、BlogCategory 模型增加一个返回博客关联的栏目ID1
2
3
4
5
6
7
8
9use yii\helpers\ArrayHelper;
/**
* 获取博客关联的栏目,返回的是获取到的category_id
*/
public static function getRelationCategorys ($blogId)
{
$res = static::find()->select('category_id')->where(['blog_id' => $blogId])->all();
return $res ? ArrayHelper::getColumn($res, 'category_id') : [];
}
2、下面就简单了,我们在渲染 update 页面之前为 Blog 模型的 category 赋值即可1
2// 获取博客关联的栏目
$model->category = BlogCategory::getRelationCategorys($id);
刷新下界面,看看是不是有了呢?有了?谁的,谁的,的,的…我跟你说这后面这绝对是回声。
简直了,问题无处不在。更新的时候更新对应的栏目呢?增加的栏目做插入处理,删减的栏目做 delete 处理?这可就有点麻烦了。我们这里暂且先给定一种全部删除,再批量插入的简单解决方案。即在 create 操作的基础之上再增加一个删除的动作,来看下 update 操作的完整实现:1
2
3
4
5
6
7
8
9
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/**
* Updates an existing Blog model.
* @param integer $id
* @return mixed
*/
public function actionUpdate($id)
{
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
$transaction = Yii::$app->db->beginTransaction();
try {
/**
* current model save
*/
$model->save(false);
// 注意我们这里是获取刚刚插入blog表的id
$blogId = $model->id;
/**
* batch insert category
* 我们在Blog模型中设置过category字段的验证方式是required,因此下面foreach使用之前无需再做判断
*/
$data = [];
foreach ($model->category as $k => $v) {
// 注意这里的属组形式[blog_id, category_id],一定要跟下面batchInsert方法的第二个参数保持一致
$data[] = [$blogId, $v];
}
// 获取BlogCategory模型的所有属性和表名
$blogCategory = new BlogCategory;
$attributes = ['blog_id', 'category_id'];
$tableName = $blogCategory::tableName();
$db = BlogCategory::getDb();
// 先全部删除对应的栏目
$sql = "DELETE FROM `{$tableName}` WHERE `blog_id` = :bid";
$db->createCommand($sql, ['bid' => $id])->execute();
// 再批量插入栏目到BlogCategory::tableName()表
$db->createCommand()->batchInsert(
$tableName,
$attributes,
$data
)->execute();
// 提交
$transaction->commit();
return $this->redirect(['index']);
} catch (Exception $e) {
// 回滚
$transaction->rollback();
throw $e;
}
} else {
// 获取博客关联的栏目
$model->category = BlogCategory::getRelationCategorys($id);
return $this->render('update', [
'model' => $model,
]);
}
}
本篇文章我们仅仅给出了一种多表关联操作的实战案例,关于多表关联你也可以有其他更好的实现方式,这里就不多说了。后面我们可以要实现一些更复杂的操作,有时间的话,我们所讲的案例多练练哦,绝对实打实案例操作!