Laravel的模型观察器和事件系统都是用于处理模型生命周期相关逻辑的工具,二者定位不同但能够配合使用,帮助开发者在不污染控制器代码的前提下,实现对模型行为的精细化管控,同时高效完成用户活动日志这类通用需求的开发。

模型观察器与事件系统的核心区别
模型观察器是Laravel为模型生命周期事件提供的专属处理方案,主要监听模型的增删改查等内置事件,配置简单且和模型强绑定。事件系统则是更通用的解耦工具,既可以监听模型内置事件,也可以自定义业务事件,支持多监听器、事件订阅者等更复杂的场景。
模型观察器的适用场景
当需要处理单个模型的生命周期逻辑,比如创建时自动填充字段、删除时清理关联数据、记录该模型的变更日志时,模型观察器是最直接的选择。它的优势是配置成本低,不需要额外的事件注册逻辑。
事件系统的适用场景
当业务逻辑涉及多个模型联动、或者需要跨模块触发操作、甚至需要异步处理时,事件系统更合适。比如用户下单后,需要同时更新商品库存、发送通知、记录活动日志,这类多步骤操作就适合用事件系统解耦。
使用模型观察器记录用户活动日志
下面我们通过一个实际案例,演示如何用模型观察器实现用户操作文章模型的日志记录。
第一步:创建模型观察器
首先通过Artisan命令创建观察器类:
<?php
namespace AppObservers;
use AppModelsArticle;
use AppModelsUserActivityLog;
use IlluminateSupportFacadesAuth;
class ArticleObserver
{
/**
* 监听文章创建事件
*/
public function created(Article $article): void
{
if (Auth::check()) {
UserActivityLog::create([
'user_id' => Auth::id(),
'action' => 'create_article',
'target_type' => Article::class,
'target_id' => $article->id,
'content' => '用户创建了文章:' . $article->title,
'created_at' => now()
]);
}
}
/**
* 监听文章更新事件
*/
public function updated(Article $article): void
{
if (Auth::check()) {
// 获取变更的字段
$changed = $article->getDirty();
$changeDesc = '用户更新了文章:' . $article->title . ',变更字段:' . implode(',', array_keys($changed));
UserActivityLog::create([
'user_id' => Auth::id(),
'action' => 'update_article',
'target_type' => Article::class,
'target_id' => $article->id,
'content' => $changeDesc,
'created_at' => now()
]);
}
}
/**
* 监听文章删除事件
*/
public function deleted(Article $article): void
{
if (Auth::check()) {
UserActivityLog::create([
'user_id' => Auth::id(),
'action' => 'delete_article',
'target_type' => Article::class,
'target_id' => $article->id,
'content' => '用户删除了文章:' . $article->title,
'created_at' => now()
]);
}
}
}
第二步:注册观察器
在服务提供者或者模型的boot方法中注册观察器,这里我们在Article模型的boot方法里注册:
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
use AppObserversArticleObserver;
class Article extends Model
{
protected static function boot(): void
{
parent::boot();
// 注册观察器
static::observe(ArticleObserver::class);
}
}
也可以在AppServiceProvider的register方法中批量注册:
<?php
namespace AppProviders;
use IlluminateSupportServiceProvider;
use AppModelsArticle;
use AppObserversArticleObserver;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
//
}
public function boot(): void
{
// 注册文章模型观察器
Article::observe(ArticleObserver::class);
}
}
第三步:验证效果
当我们执行文章的新增、修改、删除操作时,会自动在user_activity_logs表中生成对应的记录,不需要在控制器里手动编写日志逻辑。
结合事件系统处理复杂场景
如果日志记录需要额外的异步处理,比如同时发送通知、同步到外部系统,就可以结合事件系统实现:
1. 创建自定义事件
<?php
namespace AppEvents;
use AppModelsArticle;
use IlluminateFoundationEventsDispatchable;
use IlluminateQueueSerializesModels;
class ArticleActionEvent
{
use Dispatchable, SerializesModels;
public Article $article;
public string $action;
public int $userId;
public function __construct(int $userId, string $action, Article $article)
{
$this->userId = $userId;
$this->action = $action;
$this->article = $article;
}
}
2. 创建事件监听器
<?php
namespace AppListeners;
use AppEventsArticleActionEvent;
use AppModelsUserActivityLog;
use IlluminateContractsQueueShouldQueue;
class LogArticleActionListener implements ShouldQueue
{
public function handle(ArticleActionEvent $event): void
{
$actionMap = [
'create' => '创建了文章',
'update' => '更新了文章',
'delete' => '删除了文章'
];
$actionDesc = $actionMap[$event->action] ?? $event->action;
UserActivityLog::create([
'user_id' => $event->userId,
'action' => $event->action . '_article',
'target_type' => Article::class,
'target_id' => $event->article->id,
'content' => '用户' . $actionDesc . ':' . $event->article->title,
'created_at' => now()
]);
}
}
3. 在观察器中触发事件
修改之前的ArticleObserver,在对应方法中触发事件:
<?php
namespace AppObservers;
use AppModelsArticle;
use AppEventsArticleActionEvent;
use IlluminateSupportFacadesAuth;
class ArticleObserver
{
public function created(Article $article): void
{
if (Auth::check()) {
// 触发自定义事件
ArticleActionEvent::dispatch(Auth::id(), 'create', $article);
}
}
public function updated(Article $article): void
{
if (Auth::check()) {
ArticleActionEvent::dispatch(Auth::id(), 'update', $article);
}
}
public function deleted(Article $article): void
{
if (Auth::check()) {
ArticleActionEvent::dispatch(Auth::id(), 'delete', $article);
}
}
}
这样日志写入逻辑就放到了异步监听器中,不会阻塞主业务流程,后续如果需要增加其他处理逻辑,只需要新增监听器并绑定到事件即可,不需要修改观察器的代码。
注意事项
- 观察器中的方法名需要和模型事件名完全对应,比如
creating、created、updating、updated等,大小写不敏感。 - 如果观察器中不需要处理某个事件,可以不定义对应的方法,Laravel不会报错。
- 事件监听器如果需要异步执行,需要实现
ShouldQueue接口,并且配置好队列驱动。 - 避免在观察器中编写过于复杂的业务逻辑,保持观察器的职责单一,复杂逻辑可以拆分到事件或者其他服务类中。