讲师中心 微信公众号
AI工具推荐 视频效率加速

Laravel 模型观察器与事件系统:精细化控制模型行为与用户活动日志

老瑶小哥_2412

老瑶小哥_2412

发布时间:2025-12-03 11:50:24

|

1040人浏览过

|

来源于php中文网

原创

laravel 模型观察器与事件系统:精细化控制模型行为与用户活动日志

本教程深入探讨 Laravel 模型观察器的 `retrieved` 事件行为,并提供两种解决方案:使用 `Model::withoutEvents()` 精确禁用事件以避免不必要的日志记录,以及利用 Laravel 事件系统高效、解耦地记录用户 IP、User Agent 等活动数据到独立模型,从而实现更灵活、可维护的应用程序逻辑。

1. Laravel 模型观察器 (Observers) 概述

Laravel 的模型观察器(Observers)提供了一种优雅的方式来监听和响应 Eloquent 模型的生命周期事件,例如 created、updated、deleted 和 retrieved 等。它们将模型事件的处理逻辑集中管理,有助于保持模型本身的简洁性,并将相关业务逻辑从控制器或服务中解耦。

在提供的示例中,DictionaryObserver 监听了 Dictionary 模型的多个事件:

// app/Observers/DictionaryObserver.php
class DictionaryObserver
{
    public function created(Dictionary $dictionary)
    {
        Log::info('yyyyyyyyy'); // 示例:创建时记录日志
    }

    public function retrieved(Dictionary $dictionary)
    {
        Log::info('xxxxxxxxxx'.$dictionary); // 示例:检索时记录日志
    }

    public function updated(Dictionary $dictionary)
    {
        // 处理模型更新事件
    }

    public function deleted(Dictionary $dictionary)
    {
        // 处理模型删除事件
    }
}

为了使观察器生效,需要在 App\Providers\AppServiceProvider 的 boot 方法中注册它:

// app/Providers/AppServiceProvider.php
use App\Models\Dictionary;
use App\Observers\DictionaryObserver;
use Illuminate\Pagination\Paginator; // 示例中包含

public function boot()
{
    Paginator::useBootstrap(); // 示例中包含
    Dictionary::observe(DictionaryObserver::class);
}

2. 理解 retrieved 事件的行为与选择性禁用

retrieved 事件会在模型实例从数据库中被检索出来时触发。这意味着无论是通过 find() 获取单个模型,还是通过 get()、paginate() 获取模型集合,retrieved 事件都会为每一个被加载的模型实例触发一次。

Academic Writing Test
Academic Writing Test

提供专业学术写作帮助,涵盖研究论文、文献综述、方法论与学位论文,严格遵守学术规范……

下载

问题描述: 用户希望 DictionaryObserver 中的 retrieved 方法只在编辑或查看单个字典记录时触发(例如,记录用户查看了某个具体记录的行为),但在列表页(index 方法)批量加载字典记录时禁用它,以避免不必要的日志记录或性能开销。

解决方案:使用 Model::withoutEvents() Laravel 提供了一个静态方法 Model::withoutEvents(),它允许你在一个回调函数内部执行 Eloquent 操作,而不会触发任何与该模型相关的事件(包括观察器和事件监听器)。这正是解决批量加载时禁用 retrieved 事件的理想方法。

示例代码:在控制器中应用 withoutEvents()

以下示例展示了如何在 DictionaryController 的 index 方法中利用 withoutEvents() 来避免批量加载时触发 retrieved 事件,同时确保在 show 方法中单独加载时事件正常触发。

// app/Http/Controllers/DictionaryController.php
<?php

namespace App\Http\Controllers;

use App\Models\Dictionary;
use Illuminate\Http\Request;
use App\Http\Resources\DictionaryResource; // 假设存在此资源

class DictionaryController extends Controller
{
    protected $model;

    public function __construct(Dictionary $model)
    {
        $this->model = $model;
    }

    public function index(Request $request)
    {
        // 使用 withoutEvents() 包装查询,确保在批量获取时不会触发 retrieved 事件
        $dictionaries = Dictionary::withoutEvents(function () use ($request) {
            $query = $this->model
                          ->orderBy($request->column ?? 'created_at', $request->order ?? 'desc');

            if ($request->search) {
                $query->where(function ($q) use ($request) {
                    $q->where('name', 'like', '%' . $request->search . '%')
                      ->orWhere('id', 'like', '%' . $request->search . '%');
                });
            }
            return $query->paginate($request->per_page);
        });

        return DictionaryResource::collection($dictionaries);
    }

    // 对于单条记录的编辑或查看,例如 show 方法,则无需使用 withoutEvents()
    // 这样 retrieved 事件会正常触发,达到记录单条访问的目的。
    public function show(Dictionary $dictionary)
    {
        // 此处 $dictionary 会触发 retrieved 事件,因为没有使用 withoutEvents()
        // DictionaryObserver 的 retrieved 方法会在此处被调用
        \Log::info('用户查看了字典条目:' . $dictionary->id . ' - ' . $dictionary->name);
        return new DictionaryResource($dictionary);
    }

    // ... 其他方法如 create, store 等
}

工作原理:withoutEvents() 方法接受一个闭包作为参数。在该闭包内部执行的所有 Eloquent 操作,对于 Dictionary 模型而言,都不会触发其注册的观察器或事件监听器。一旦闭包执行完毕,事件系统将恢复正常,后续的 Eloquent 操作会继续触发事件。通过这种方式,我们可以精确控制 retrieved 事件的触发时机,使其仅在需要记录单个记录访问的场景下生效。

3. 记录用户活动数据 (IP, User Agent 等)

需求: 将用户 IP 地址、User Agent、当前登录用户 ID 和操作描述等信息保存到 Action 模型中,用于记录用户行为日志。

// app/Models/Action.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Action extends Model
{
    use SoftDeletes; // 假设有 ScopeActiveTrait,此处简化

    protected $guarded = ['id'];

    protected $fillable = [
        'company_id',
        'user_id',
        'ip',
        'user_agent',
        'description'
    ];

    protected $dates = [
        'deleted_at',
        'created_at',
        'updated_at'
    ];
}

获取用户活动信息:

  • IP 地址: request()->ip() 或 $request->ip()
  • User Agent: request()->userAgent() 或 $request->userAgent()
  • 当前登录用户 ID: auth()->id() 或 Auth::id() (需引入 use Illuminate\Support\Facades\Auth;)

实现方案:使用 Laravel 事件系统 (推荐) 为了实现业务逻辑的解耦和提高代码的可维护性,推荐使用 Laravel 的事件(Events)和监听器(Listeners)系统来记录用户活动。这种模式允许我们将日志记录逻辑从核心业务流程中分离出来。

步骤:

  1. 定义事件 (Event):UserActionLogged 创建一个事件类来封装需要记录的用户活动数据。

    php artisan make:event UserActionLogged
    // app/Events/UserActionLogged.php
    <?php
    
    namespace App\Events;
    
    use Illuminate\Foundation\Events\Dispatchable;
    use Illuminate\Queue\SerializesModels;
    
    class UserActionLogged
    {
        use Dispatchable, SerializesModels;
    
        public $userId;
        public $ipAddress;
        public $userAgent;
        public $description;
        public $companyId; // 如果需要,可以添加
    
        /**
         * 创建一个新的事件实例。
         *
         * @param int|null $userId
         * @param string $ipAddress
         * @param string $userAgent
         * @param string $description
         * @param int|null $companyId
         * @return void
         */
        public function __construct($userId, $ipAddress, $userAgent, $description, $companyId = null)
        {
            $this->userId = $userId;
            $this->ipAddress = $ipAddress;
            $this->userAgent = $userAgent;
            $this->description = $description;
            $this->companyId = $companyId;
        }
    }
  2. 定义监听器 (Listener):LogUserAction 创建一个监听器类来处理 UserActionLogged 事件,并将数据保存到 Action 模型。

    php artisan make:listener LogUserAction --event=UserActionLogged
    // app/Listeners/LogUserAction.php
    <?php
    
    namespace App\Listeners;
    
    use App\Events\UserActionLogged;
    use App\Models\Action;
    use Illuminate\Contracts\Queue\ShouldQueue; // 如果希望异步处理
    use Illuminate\Queue\InteractsWithQueue;
    
    class LogUserAction // implements ShouldQueue // 如果是异步处理,取消注释
    {
        // use InteractsWithQueue; // 如果是异步处理,取消注释
    
        /**
         * 处理事件。
         *
         * @param  \App\Events\UserActionLogged  $event
         * @return void
         */
        public function handle(UserActionLogged $event)
        {
            Action::create([
                'company_id' => $event->companyId, // 根据实际情况传递或获取
                'user_id' => $event->userId,
                'ip' => $event->ipAddress,
                'user_agent' => $event->userAgent,
                'description' => $event->description,
            ]);
        }
    }
    • 异步处理提示: 如果日志记录操作可能耗时(例如涉及大量数据写入或外部服务调用),可以将监听器实现 ShouldQueue 接口,并使用 InteractsWithQueue trait,这样日志操作将在后台队列中异步执行,避免阻塞用户请求。
  3. 注册事件和监听器 在 app/Providers/EventServiceProvider.php 文件的 $listen 属性中,将 UserActionLogged 事件与 LogUserAction 监听器关联起来。

    // app/Providers/EventServiceProvider.php
    <?php
    
    namespace App\Providers;
    
    use Illuminate\Auth\Events\Registered;
    use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
    use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
    use Illuminate\Support\Facades\Event;

热门AI工具

更多
讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

花生AI
花生AI Hot

一款AI视频创作工具,主要用于B站推出的AI视频创作工具,适合需要提升相关任务效率的用户。

Flowith
Flowith Hot

Flowith是一款基于画布和智能体工作流的 AI 协作工作空间。

相关专题

更多
laravel组件介绍
laravel组件介绍

laravel 提供了丰富的组件,包括身份验证、模板引擎、缓存、命令行工具、数据库交互、对象关系映射器、事件处理、文件操作、电子邮件发送、队列管理和数据验证。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

877

2024.04.09

laravel中间件介绍
laravel中间件介绍

laravel 中间件分为五种类型:全局、路由、组、终止和自定。想了解更多laravel中间件的相关内容,可以阅读本专题下面的文章。

855

2024.04.09

laravel使用的设计模式有哪些
laravel使用的设计模式有哪些

laravel使用的设计模式有:1、单例模式;2、工厂方法模式;3、建造者模式;4、适配器模式;5、装饰器模式;6、策略模式;7、观察者模式。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

2508

2024.04.09

thinkphp和laravel哪个简单
thinkphp和laravel哪个简单

对于初学者来说,laravel 的入门门槛较低,更易上手,原因包括:1. 更简单的安装和配置;2. 丰富的文档和社区支持;3. 简洁易懂的语法和 api;4. 平缓的学习曲线。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

4721

2024.04.10

laravel入门教程
laravel入门教程

本专题整合了laravel入门教程,想了解更多详细内容,请阅读专题下面的文章。

6890

2025.08.05

laravel实战教程
laravel实战教程

本专题整合了laravel实战教程,阅读专题下面的文章了解更多详细内容。

4536

2025.08.05

laravel面试题
laravel面试题

本专题整合了laravel面试题相关内容,阅读专题下面的文章了解更多详细内容。

8509

2025.08.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

1516

2026.03.04

Docker Desktop怎么设置端口映射
Docker Desktop怎么设置端口映射

本专题聚焦Docker Desktop容器端口映射与访问配置,整理从创建容器、设置端口绑定到验证本地服务访问的操作流程。针对端口被占用、映射配置错误、容器服务无法访问等情况,也提供相应的检查方向,方便用户完成端口配置。

0

2026.11.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
墨刀帮助中心
墨刀帮助中心

共0课时 | 0人学习

MyEclipse学习中心
MyEclipse学习中心

共0课时 | 0人学习

Apache Subversion 官方手册
Apache Subversion 官方手册

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn