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


相关推荐: 《sketchbook》选中部分图案移动方法  荣耀盒子应用管理技巧  济南公交卡手机充值指南  win11怎么更改账户类型 Win11标准用户和管理员权限切换【教程】  word文档中的分隔符有哪些不同类型和用途_Word分隔符类型与用途方法  优化2xN网格最大路径和的动态规划算法实践  《兴业银行》注册登录方法  使用Google服务账号实现Google Drive API无缝集成与文件访问  iPhone 14 Pro如何更改区域设置_iPhone 14 Pro地区语言修改教程  PHP中获取HTTP响应状态消息:方法与限制  抖音如何解除|直播|权限绑定_抖音关闭并解绑|直播|功能的方法  如何解决Casbin日志与应用日志不统一的问题,使用casbin/psr3-bridge实现无缝集成  冬季去哪个城市旅游更有可能观测到极光  mysql如何回滚事务_mysql ROLLBACK事务回滚方法  J*aScript事件处理:优化键盘输入与表单提交的实践指南  51漫画网实时入口 51漫画网页版官方免费漫画入口  韩剧圈正版官网入口_韩剧圈官方指定登录  如何在CSS中使用伪类选择器_hover实现悬停效果  DeepSeek超全面指南:入门必看  使用VS Code调试Python代码:从入门到精通  CSS动画如何实现图标旋转并放大_transform rotate scale @keyframes实现  PHP动态导航按钮:根据用户登录状态切换链接与文本  Retrofit根路径POST请求:@POST("/") 的应用与解析  C++ bind函数使用教程_C++参数绑定与函数适配器的应用  淘口令快速解析技巧  Word如何将文字快速转成表格 Word文本转换成表格功能使用技巧【效率】  高德地图怎么查看未来行程规划_高德地图未来行程规划查看方法  鲨鱼剧场app金币获取方法  Keras中Convolution2D层及其核心辅助层详解  Win11怎么录屏_Windows 11自带Xbox Game Bar录制视频  BunnyStream TUS视频上传指南:解决401认证错误与参数配置  CSS如何控制元素外边距_margin实现布局间隔  《桃源记2》资源采集攻略  Lar*el Dusk 测试中管理浏览器权限:以剪贴板访问为例  FullCalendar自定义按钮样式定制指南  Teambition网盘如何共享文件  百度地图离线地图无法加载如何解决 百度地图离线地图加载优化方法  win11怎么设置默认终端为Windows Terminal Win11替代CMD和PowerShell【技巧】  C++ optional用法详解_C++17处理可能为空的返回值  QQ阅读小说搜索入口地址_QQ阅读小说搜索入口地址搜索在线阅读  t3出行如何使用微信支付  视频号视频怎么提取文案?提取的文案如何优化与使用?  《淘宝联盟》推广自己的店铺方法  猫眼app抢票快还是小程序快  J*a里如何处理ArithmeticException并防止除零_算术异常防护策略解析  4399小游戏下装链接 4399小游戏下载链接入口  Highcharts雷达图轴线交点数值标注指南  修复UI元素交互障碍:从“开始”按钮到信息框的平滑过渡实现  谷歌邮箱怎么换绑定邮箱Gmail安全备份邮箱修改方法  《淘票票》添加到苹果钱包教程 

 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.