68. 生成摘要
简介
在本节里,我们完成新建话题时自动生成摘要信息功能。
需求分解
我们要求话题在保存时截取正文的前 200 个文字做为话题的摘要信息。
上一节,我们用数据模型的 数据完成 功能实现了新建话题保存之前把当前用户 ID 赋值给话题的 user_id
属性。但我们在生成摘要信息时不打算使用该功能来实现,而是使用 事件观察者 来完成。
同时,为了使所有自动完成属性的赋值使用同一设计模式,我们把 user_id
的赋值改成用 事件观察者 来实现。
事件观察者介绍
官方文档 里只介绍了在数据模型里如何注册 模型事件 和 事件观察者 ,但并没有详细介绍这两者的意义和使用场景。实际上 模型事件 和 事件观察者 都属于 ThinkPHP 框架对设计模式 里的 观察者模式 的一种实现,这与 Laravel 事件系统 基本相同。
模型观察者
我们在这里使用 观察者类 来监听事件,大家在使用时需要注意的是如果你项目使用的 ThinkPHP 版本低于
V5.1.13
时只能通过在数据模型里定义before_insert
事件来实现。
因为 ThinkPHP 没有提供创建观察者的命令行,所以和视图页面一样我们需要手动来创建观察者类文件。模型事件 提供了 beforeInsert
、 afterInsert
等 10 个事件监听,考虑到 user_id
只允许创建保存前赋值之后再不允许修改, except
允许在新建或编辑保存前都可以修改,所以我们使用 beforeInsert
和 beforeWrite
事件来实现给这两个属性的赋值。
application/common/observer/Topic.php
<?php
namespace app\common\observer;
use think\helper\Str;
use app\common\model\Topic as TopicModel;
use app\common\model\User as UserModel;
class Topic
{
public function beforeInsert(TopicModel $topic)
{
$current_user = UserModel::currentUser();
if(empty($current_user)){
$topic->user_id = 0;
}else{
$topic->user_id = $current_user->id;
}
}
public function beforeWrite(TopicModel $topic)
{
$topic->excerpt = $this->makeExcerpt($topic->body);
}
/**
* 生成话题摘要
* @Author zhanghong(Laifuzi)
* @DateTime 2019-02-21
* @param string $value 话题正文
* @param integer $length 摘要长度
* @return string 生成摘要文本
*/
protected function makeExcerpt($value, $length = 200)
{
$excerpt = trim(preg_replace('/\r\n|\r|\n+/', ' ', strip_tags($value)));
return Str::substr($value, 0, $length);
}
}
数据模型
接下来,我们在 Topic 数据模型里注册定义的观察者,并删除之前定义的 数据完成 (数据模型里的 $auto 属性 和 setUserIdAttr 方法) 。
'application/common/model/Topic.php'
<?php
namespace app\common\model;
use think\Model;
use app\common\validate\Topic as Validate;
use app\common\observer\Topic as Observer;
class Topic extends Model
{
// 注册事件观察者
protected static function init()
{
self::observe(Observer::class);
}
*删除 insert 属性*
*// 新增实例记录时自动完成user_id字段赋值*
*protected $insert = ['user_id'];*
.
.
.
*删除 setUserIdAttr 方法*
*protected function setUserIdAttr(){*
*// 当前登录用户ID*
*$current_user = User::currentUser();*
*if(empty($current_user)){*
*return 0;*
*}*
*return $current_user->id;*
*}*
/**
* 创建记录
* @Author zhanghong(Laifuzi)
* @DateTime 2019-06-21
* @param array $data 表单提交数据
* @return Topic [description]
*/
public static function createItem($data)
{
$validate = new Validate;
if(!$validate->batch(true)->check($data)){
$e = new ValidateException('数据验证失败');
$e->setData($validate->getError());
throw $e;
}
try{
$topic = new self;
$topic->allowField(true)->save($data);
}catch (\Exception $e){
throw new \Exception('创建话题失败');
}
return $topic;
}
}
代码解读
- 切记删除数据模型里的
insert
属性和setUserIdAttr
方法。
代码优化
上面我们使用观察者完成了创建话题记录时自动给 user_id
赋值,现在我们在页面通过点击『新建帖子』创建话题时框架自动把当前登录用户 ID 赋值给 user_id
。但这时,我们再运行 php think seed:run -s TopicSeed
命令批量模拟话题记录时会发现所有模拟记录的 user_id=0
。
所有模拟记录的 user_id=0
是因为在执行命令行时 UserModel::currentUser()
返回结果是 NULL
。所以,我们打算通过以下方式来实现使用 seed:run
模拟数据时不要把当前登录用户 ID
赋值给 user_id
。
- 在 TopicSeed 里模拟数据时给
$topic
额外添加一个属性参数(属性名只要不是 Topic 对应的数据库表字段就可以,我们这里设置的属性名是is_seeder
),并给它赋值:
database/seeds/TopicSeed.php
.
.
.
$data = [
'title' => $sentence,
'excerpt' => $sentence,
'body' => $faker->text(),
'user_id' => $faker->randomElement($user_ids),
'category_id' => $faker->randomElement($category_ids),
'create_time' => $create_time->getTimestamp(),
'update_time' => $update_time->getTimestamp(),
];
$topic = new Topic($data);
$topic->is_seeder = true;
$topic->save();
.
.
.
- 在观察者类里通过判断 $topic 是否有
is_seeder
这个属性:
application/common/observer/Topic.php
<?php
.
.
.
public function beforeInsert(TopicModel $topic)
{
if(!isset($topic['is_seeder'])){
// 非命令行模拟时自动把当前登录用户 ID 赋值给 user_id
$current_user = UserModel::currentUser();
if(empty($current_user)){
$topic->user_id = 0;
}else{
$topic->user_id = $current_user->id;
}
}
}
.
.
.
效果展示
我们再用浏览器话题创建页面,新建一个话题记录可以看到数据保存正常,并且使用数据库管理工具可以看到 user_id
和 except
字段和我们期望的一样完成自动赋值保存。
我们再次运行 php think seed:run -s TopicSeed
,可以看到模拟的话题数据用户 ID 还是随机的:
知识点
- 我们之所以在这一节我们使用 数据完成 实现对
user_id
的赋值保存,而在这一节里又改成使用 模型事件 来实现,是为了给大家介绍在 ThinkPHP 里同一操作的实现方式可以有多种。 - 模型事件 这涉及到软件设计模式知识,如果大家力所能及的话可以多了解一些该方面的内容,这对大家在项目开发中的帮助很大;
- 在项目里要实现自动赋值时到底该用 数据完成 还是 模型事件 这个问题 ? 以笔者的开发经验建议是,当自动赋值业务比较简单时使用 数据完成 ,而当业务复杂或还要实现一些非自动赋值(如更新或删除后的回调)事件那还是使用 模型事件 比较好。
Git 版本控制
下面把代码纳入到版本管理:
$ git add -A
$ git commit -m "生成话题摘要"
推荐文章: