前言:
最近新项目中有一项新的需求,就是对于某些表,如果进行了增删改操作,那么就对这些操作进行日志记录。
概述:
首先,我该项目是基于Laravel 7.0开发。其实该需求说难也不难,相信很多读者一看到日志,那么肯定想到的就是钩子,只要对这些表的操作做些关联的钩子,那么自然增删改操作也有日志了。说到钩子,可能Laravel中大家用得比较多的功能就是中间件,中间件可以作为某个请求前的钩子,也可以作为请求后的钩子。但是,中间件是针对请求的,而这次我们是针对表来做日志的,那么有没有类似中间件的功能呢?其实Laravel作为一个MVC框架,对表的操作,就是对模型的操作,也就是说我们对模型做个钩子功能就能实现需求了。而Laravel也提供两种方式来处理这种情况。第一种方式通过模型事件 (Event) ,第二种方式基于一种更先进的概念:模型观察者 (Observers)。
事件 (Event) :
首先,我们来看看Laravel的官方文档是如何介绍事件的:事件系统
大概总结一下,建立一个事件很简单:
- 在 EventServiceProvider 中你可以添加一个特定的事件监听器,并绑定一个闭包函数
- 在闭包函数中,你不需要接触模型代码就可以添加新的行为
- 绑定操作必须放在类的 boot() 方法中
需要注意的是,Laravel预设计了一些方法提供给使用者对模式的操作。
- creating
- created
- updating
- updated
- saving
- saved
- deleting
- deleted
- restoring
- restored
其实看方法名,都能大概理解到,create、update、save、delete、restore这几个方法不就是模型的操作吗?而比如creating代表的是将要进行,而created代表的是已完成了。因此,我们可以根据自己需求选择方法。
这样看来,模型事件完全就能满足我们需求了,轻松能为我们的表操作日志功能做服务了。然而,每个操作你都需要为对应的操作在boot的方法中绑定操作
public function boot(DispatcherContract $events)
{
parent::boot($events);
User::created(function($user)
{
// doing something here, after User creation...
});
}
就例如我们这个操作,一个表就起码有created、updated、deleted这三个绑定操作。那如果我有十张,二十张表呢?boot方法里面就绑定几十个操作,这样的代码是非常难阅读的,也是比较难维护的。
此时,模型观察者 (Observers)就能拯救我们了。
观察者 (Observers):
老规矩,我们先看看官方文档是如何介绍观察者的:
如果在一个模型上监听了多个事件,可以使用观察者来将这些监听器组织到一个单独的类中。观察者类的方法名映射到你希望监听的 Eloquent 事件。 这些方法都以模型作为其唯一参数。
也就是说,观察者可以整理同一个模型的多个事件到单独的一个类里面。这样一来,可维护性就大大提升了,我们以后改哪个模型只需要对应的模型名的观察者来改就可以了,不需要在boot方法里面慢慢翻查了。
于是,我也选择了观察者来实现我这个项目的功能。
功能开发:
第一步 创建观察者类
我们创建观察者类,可以用Artisan 命令快速建立新观察者类:
php artisan make:observer ProductLogObserver --model=products
注意 --model 后面指的是对应的模型名,必须是已经建立了的模型。
此命令将在 App/Observers 文件夹放置新的观察者类(ProductLogObserver)。
第二步 修改观察者类
我们根据自己的需求,对新建的观察者类进行修改,来实现业务逻辑:
<?php
namespace App\Observers;
use App\Models\JdModelLog;
use Illuminate\Support\Facades\Auth;
class ProductLogObserver
{
private $table = 'product';
public function created($data)
{
$user = Auth::guard()->user();
$user_id = $user->id;
$username = $user->username;
$id = (new JdModelLog())->getLastOneId() + 1;
JdModelLog::create([
'type' => __FUNCTION__
,'table' => $this->table
, 'record_detail' => $data->toJson()
, 'record_id' => $data->id
, 'operate_user_id' => $user_id
, 'operate_username' => $username
, 'id' => $id
]);
}
public function updated($data)
{
$user = Auth::guard()->user();
$user_id = $user->id;
$username = $user->username;
$id = (new ModelLog())->getLastOneId() + 1;
JdModelLog::create([
'type' => __FUNCTION__
,'table' => $this->table
, 'record_detail' => $data->toJson()
, 'record_id' => $data->id
, 'operate_user_id' => $user_id
, 'operate_username' => $username
, 'id' => $id
]);
}
public function deleted($data)
{
$user = Auth::guard()->user();
$user_id = $user->id;
$username = $user->username;
$id = (new ModelLog())->getLastOneId() + 1;
JdModelLog::create([
'type' => __FUNCTION__
,'table' => $this->table
, 'record_detail' => $data->toJson()
, 'record_id' => $data->id
, 'operate_user_id' => $user_id
, 'operate_username' => $username
, 'id' => $id
]);
}
}
这里的方法代表的就是模型的操作,是跟事件的一样的:
- creating
- created
- updating
- updated
- saving
- saved
- deleting
- deleted
- restoring
- restored
第三步 注册观察者
在你希望观察的模型上使用 observe 方法注册观察者。也可以在服务提供者的 boot 方法注册观察者。我们用artisan命令虽然会帮我们注册,但是如果我们手动创建观察者,那么就得自己手动进行注册。打开文件
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();
//关联产品的操作观察者
Products::observe(new ProductLogObserver());
}
这样我们的观察者每当对应的模型有观察者类中的定义的方法操作时就能生效了。
躲坑小指南:
无论是Laravel提供的模型时间还是说模型观察者,实际上只能监听 ORM Query,而不能监听Database Query。如果不注意这点,你高高兴兴弄完观察者,但实际上测试时,发现updated操作死活监听不到update()方法。
我们先看看是ORM Query 和 Database Query的区别是什么呢?
Laravel Query Builder & Eloquent ORM 介绍
例如:
ProductModel::where('id',100)->update(['status'=>1]);
这种操作就是属于Database Query操作,是无法监听,也就无法生成日志的。
因此,我们需要改造成ORM Query语法:
ProductModel::where('id',100)->first()->update(['status'=>1]);
但是这是单行数据的处理,如果是多行数据那就相对更麻烦了:
//如果直接条件查询批量update,不属于ORM Query,无法监听updated操作,就无法生成更新模型的操作日志
$updateArr = $product::where('id',100)-->get();
foreach ($updateArr as $update){
$update->update(['status'=>1]);
}
PS:我知道此处是一次更新,变成了多次更新,会加大数据库的负载。但是暂时还没找到替代的写法,如果有朋友有其他更好的写法,请评论分享给大家学习哈。
总结:
Laravel 提供的这个观察者模式真的很强大,使用起来也很方便,解决了开发者自己造轮子的痛苦。不过我这个功能还有一些细节的地方是没有分享出来的,比如我们考虑用的是mongodb来存储的,如果用mysql可能就不太适合做这种日志记录。感谢您们的阅读,希望我这篇文章能带给您们小小的帮助,除此之外,如果有朋友发现我该文章有写得不好的地方,请评论留言不啬指教。