本书未发布

68. 生成摘要

未匹配的标注

简介

在本节里,我们完成新建话题时自动生成摘要信息功能。

需求分解

我们要求话题在保存时截取正文的前 200 个文字做为话题的摘要信息。

上一节,我们用数据模型的 数据完成 功能实现了新建话题保存之前把当前用户 ID 赋值给话题的 user_id 属性。但我们在生成摘要信息时不打算使用该功能来实现,而是使用 事件观察者 来完成。

同时,为了使所有自动完成属性的赋值使用同一设计模式,我们把 user_id 的赋值改成用 事件观察者 来实现。

事件观察者介绍

官方文档 里只介绍了在数据模型里如何注册 模型事件事件观察者 ,但并没有详细介绍这两者的意义和使用场景。实际上 模型事件事件观察者 都属于 ThinkPHP 框架对设计模式 里的 观察者模式 的一种实现,这与 Laravel 事件系统 基本相同。

模型观察者

我们在这里使用 观察者类 来监听事件,大家在使用时需要注意的是如果你项目使用的 ThinkPHP 版本低于 V5.1.13 时只能通过在数据模型里定义 before_insert 事件来实现。

因为 ThinkPHP 没有提供创建观察者的命令行,所以和视图页面一样我们需要手动来创建观察者类文件。模型事件 提供了 beforeInsertafterInsert 等 10 个事件监听,考虑到 user_id 只允许创建保存前赋值之后再不允许修改, except 允许在新建或编辑保存前都可以修改,所以我们使用 beforeInsertbeforeWrite 事件来实现给这两个属性的赋值。

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

  1. 在 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();
.
.
.
  1. 在观察者类里通过判断 $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_idexcept 字段和我们期望的一样完成自动赋值保存。

我们再次运行 php think seed:run -s TopicSeed ,可以看到模拟的话题数据用户 ID 还是随机的:

知识点

  • 我们之所以在这一节我们使用 数据完成 实现对 user_id 的赋值保存,而在这一节里又改成使用 模型事件 来实现,是为了给大家介绍在 ThinkPHP 里同一操作的实现方式可以有多种。
  • 模型事件 这涉及到软件设计模式知识,如果大家力所能及的话可以多了解一些该方面的内容,这对大家在项目开发中的帮助很大;
  • 在项目里要实现自动赋值时到底该用 数据完成 还是 模型事件 这个问题 ? 以笔者的开发经验建议是,当自动赋值业务比较简单时使用 数据完成 ,而当业务复杂或还要实现一些非自动赋值(如更新或删除后的回调)事件那还是使用 模型事件 比较好。

Git 版本控制

下面把代码纳入到版本管理:

$ git add -A
$ git commit -m "生成话题摘要"

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~