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


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

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

1. Lar*el 模型观察器 (Observers) 概述

Lar*el 的模型观察器(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 事件都会为每一个被加载的模型实例触发一次。

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

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

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

察言观数AskTable 察言观数AskTable

企业级AI数据表格智能体平台

察言观数AskTable 72 查看详情 察言观数AskTable

以下示例展示了如何在 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;)

实现方案:使用 Lar*el 事件系统 (推荐) 为了实现业务逻辑的解耦和提高代码的可维护性,推荐使用 Lar*el 的事件(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;

以上就是Lar*el 模型观察器与事件系统:精细化控制模型行为与用户活动日志的详细内容,更多请关注php中文网其它相关文章!


# laravel  # 网站的推广途径有哪些  # 营销推广技巧语言有哪些  # 江宁区推广市场营销策划  # 拌面酱营销推广  # 裂变营销付费推广  # 搜索因请网站推广  # 外贸短信推广怎么做 营销  # 站长网站优化服务如何  # 加密文件  # 单条  # 怎么看  # 并将  # 会在  # 精细化  # 看了  # 创建一个  # 加载  # 回调  # red  # yy  # ai  # ipad  # 回调函数  # app  # cad  # bootstrap  # php  # 清镇seo排名优化  # 芜湖整合营销推广费用 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 优化推广96088 】 【 技术知识133117 】 【 IDC资讯59369 】 【 网络运营7196 】 【 IT资讯61894


相关推荐: 招商淘客入门指南  lol小红书怎么|直播|?lol小红书|直播|是什么意思?  c++中的const关键字用法大全_c++ const正确使用指南  PDF文件去水印平台入口 PDF水印删除网址  解决CSS容器溢出问题:使用calc()实现精确布局与边距控制  在VS Code中进行数据科学和机器学习开发  PHP使用DOMDocument与XPath精准追加XML元素教程  b站如何管理订阅_b站订阅标签分类管理  鸣潮历史学家灯塔位置一览  手机耗电快是什么原因 延长手机电池续航时间的设置方法【详解】  济南公交卡手机充值指南  在J*a中如何实现类的继承与方法重用_OOP继承方法重用技巧分享  如何外贸网站设计-能留住客户提升用户体验!  sublime如何自定义文件类型图标_AFileIcon插件的主题切换与个性化配置  如何编写一个符合 composer 规范的 post-install-cmd 脚本?  Bootstrap 5导航栏折叠功能失效:数据属性迁移指南  动漫岛汉化官网网 动漫岛官方动漫汉化地址  windows server2019显卡驱动怎么安装_winserver2019显卡驱动安装与远程桌面优化  发布小红书怎么屏蔽粉丝?屏蔽粉丝能看到吗?  申通快递物流信息查询 申通快递包裹状态追踪  win11如何开启单声道音频 Win11为听障用户合并左右声道【辅助】  苹果11如何更换iCloud账号_苹果11账号切换的具体步骤  《东方航空》添加乘机人方法  J*a中逻辑运算符如何使用_逻辑与或非的基础用法讲解  C++ optional用法详解_C++17处理可能为空的返回值  智云Q3和Q2有什么升级_智云Q3与Q2手持云台功能与性能对比分析  英国搜索:多数英国人认为语言搜索是未来搜索  胃动力不足?试试这5个调理方法  《知到》打卡课程方法  易车网官网直达入口 易车网在线登录入口  sublime如何配置PHP开发环境_在sublime中运行与调试PHP代码  c++如何链接Boost库_c++准标准库的集成与使用  蛙漫2(台版)正版官网 2025免费网页版分享  《饿了么》拼好饭点外卖教程2025  苹果手机手电筒无法开启  C#中的Record类型有什么优势?C# 9新特性Record与Class的用法区别  《火影忍者:木叶高手》快速升级攻略  漫蛙app官方版手机正版入口-漫蛙漫画manwa在线漫画正版入口  《星露谷物语》克林特好感度事件介绍  斯宾塞称XGP云游戏“蒸蒸日上”:正在构建一个游戏从未如此唾手可得的未来  MySQL多重JOIN技巧:高效关联同一表获取多角色信息  SQL聚合查询、联接与筛选:GROUP BY 子句的正确使用与常见陷阱  《跳跳舞蹈》循环播放方法  荣耀 Magic10 Pro 系统更新提示失败_荣耀 Magic10 Pro 升级修复  Lar*el 中高效执行多列更新:单次查询实现  包子漫画在线观看入口 包子漫画网正版全集链接  店铺如何做视频号推广?做视频号推广有用吗?  J*aScript模块加载器_RequireJS原理分析  iCloud官方网站 iCloud网页版在线登录入口  Selenium自动化:利用键盘模拟解决复杂日期输入框输入问题 

 2025-12-03

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

运城市盐湖区信雨科技有限公司


运城市盐湖区信雨科技有限公司

运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。

 8156699

 13765294890

 8156699@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.