panoscape / history
Laravel 的 Eloquent 模型历史跟踪
Requires
- php: ^7.2|^7.2.5|^7.3|^8.0|^8.1|^8.2
- illuminate/support: ^6.0|^7.0|^8.0|^9.0|^10.0|^11.0
Requires (Dev)
- mockery/mockery: ^1.2
- orchestra/testbench: ^4.8|^5.2|^6.2|^7.5|^8.0|^9.0
- php-coveralls/php-coveralls: ^2.1
- phpunit/phpunit: ^8.3|^8.4|^9.0|^10.5
README
历史
Laravel 的 Eloquent 模型历史跟踪
安装
Composer
Laravel 6.x 及以上版本
composer require panoscape/history
Laravel 5.6.x
composer require "panoscape/history:^1.0"
服务提供者和别名
仅适用于 Laravel 5.6.x
config/app.php
'providers' => [ ... Panoscape\History\HistoryServiceProvider::class, ]; 'aliases' => [ ... 'App\History' => Panoscape\History\History::class, ];
迁移
php artisan vendor:publish --provider="Panoscape\History\HistoryServiceProvider" --tag=migrations
配置
php artisan vendor:publish --provider="Panoscape\History\HistoryServiceProvider" --tag=config
本地化
php artisan vendor:publish --provider="Panoscape\History\HistoryServiceProvider" --tag=translations
用法
将 HasOperations
特性添加到执行操作的模型。
<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Database\Eloquent\SoftDeletes; use Panoscape\History\HasOperations; class User extends Authenticatable { use Notifiable, SoftDeletes, HasOperations; }
将 HasHistories
特性添加到需要跟踪的模型。
<?php namespace App; use Illuminate\Database\Eloquent\Model; use Panoscape\History\HasHistories; class Article extends Model { use HasHistories; public function getModelLabel() { return $this->display_name; } }
请注意,您需要实现特质中的抽象方法 getModelLabel
。这为历史记录中模型实例的显示名称提供支持(如在 Who did what
中的 Who
)。
获取模型的历史记录
$model->histories(); //or dynamic property $model->histories;
获取用户的行为
$user->operations(); //or dynamic property $user->operations;
附加查询条件
两者 histories
和 operations
都返回 Eloquent 关联,也用作查询构建器。您可以通过链式条件添加进一步的约束。
// get the lastest 10 records $model->histories()->orderBy('performed_at', 'desc')->take(10) // filter by user id $model->histories()->where('user_id', 10010)
历史
//get the associated model $history->model(); //get the associated user //the user is the authenticated user when the action is being performed //it might be null if the history is performed unauthenticatedly $history->user(); //check user existence $history->hasUser(); //get the message $history->message; //get the meta(only available when it's an updating operation) //the meta will be an array with the properties changing information $history->meta; //get the timestamp the action was performed at $history->performed_at;
示例消息
Created Project my_project
│ │ │
│ │ └───── instance name(returned from `getModelLabel`)
│ └─────────────── model name(class name or localized name)
└─────────────────────── event name(default or localized name)
示例元数据
[ ['key' => 'name', 'old' => 'myName', 'new' => 'myNewName'], ['key' => 'age', 'old' => 10, 'new' => 100], ... ]
自定义历史记录
除了内置的 created/updating/deleting/restoring
事件外,您还可以通过触发 ModelChanged
事件来跟踪自定义历史记录。
use Panoscape\History\Events\ModelChanged; ... //fire a model changed event event(new ModelChanged($user, 'User roles updated', $user->roles()->pluck('id')->toArray()));
ModelChanged
构造函数接受两个/三个/四个参数。第一个是与模型实例相关的;第二个是消息;第三个是可选的,是元数据(数组);第四个也是可选的,是事件的翻译键(见 本地化)。
本地化
您可以本地化模型类型名称。
为此,请将语言行添加到已发布语言文件中的 models
数组中,键为 类的蛇形基本名称。
示例语言配置
/* |-------------------------------------------------------------------------- | Tracker Language Lines |-------------------------------------------------------------------------- | | The following language lines are used across application for various | messages that we need to display to the user. You are free to modify | these language lines according to your application's requirements. | */ 'created' => '创建:model:label', 'updating' => 'actualizar :model :label', 'deleting' => ':model :label löschen', 'restored' => ':model:labelを復元', //you may add your own model name language line here 'models' => [ 'project' => '项目', 'component_template' => '组件模板', // 'model_base_name_in_snake_case' => 'translation', ]
这将翻译您的模型历史记录为
创建项目project_001
您还可以从 ModelChanged
事件翻译自定义历史消息
/* |-------------------------------------------------------------------------- | Tracker Language Lines |-------------------------------------------------------------------------- */ 'switched_role' => ':model switched role',
// if you specified the translation key, the message argument will be ignored, simply just pass `null` event(new ModelChanged($user, null, $user->roles()->pluck('id')->toArray()), 'switched_role');
过滤器
您可以在配置文件中设置白名单和黑名单。请参阅已发布配置文件中的描述指南。
/* |-------------------------------------------------------------- | Events whitelist |-------------------------------------------------------------- | | Events in this array will be recorded. | Available events are: created, updating, deleting, restored | */ 'events_whitelist' => [ 'created', 'updating', 'deleting', 'restored', ], /* |-------------------------------------------------------------- | Attributes blacklist |-------------------------------------------------------------- | | Please add the whole class names. Example: \App\User:class | For each model, attributes in its respect array will NOT be recorded into meta when performing update operation. | */ 'attributes_blacklist' => [ // \App\User::class => [ // 'password' // ], ], /* |-------------------------------------------------------------- | User type blacklist |-------------------------------------------------------------- | | Operations performed by user types in this array will NOT be recorded. | Please add the whole class names. Example: \App\Admin:class | Use 'nobody' to bypass unauthenticated operations | */ 'user_blacklist' => [ // \App\Admin:class, // 'nobody' ], /* |-------------------------------------------------------------- | Enviroments blacklist |-------------------------------------------------------------- | | When application's environment is in the list, tracker will be disabled | */ 'env_blacklist' => [ // 'test' ],
身份验证守卫
如果您的用户使用非默认身份验证守卫,即使历史记录来源是由已验证用户生成的,您也可能会看到所有 $history->hasUser()
都变为 false
。
要修复此问题,您需要在配置文件中启用自定义身份验证守卫扫描。
/* |-------------------------------------------------------------- | Enable auth guards scanning |-------------------------------------------------------------- | | You only need to enable this if your users are using non-default auth guards. | In that case, all tracked user operations will be anonymous. | | - Set to `true` to use a full scan mode: all auth guards will be checked. However this does not ensure guard priority. | - Set to an array to scan only specific auth guards(in the given order). e.g. `['web', 'api', 'admin']` | */ 'auth_guards' => null
自定义元数据
您可以定义自己的方法来定义元数据。默认情况下,对于 updating
事件,元数据由修改的键组成,对于其他事件,元数据为 null
。
只需为特质重新定义方法 getModelMeta
。
示例
class Article extends Model { // if you want to use default trait method, you need to redeclare it with a new name use HasHistories { getModelMeta as protected traitGetModelMeta; }; ... public function getModelMeta($event) { // using defaults for updating if($event == 'updating') return $this->traitGetModelMeta($event); // passing full model to meta // ['key1' => 'value1', 'key2' => 'value2', ...] else return $this; } }
已知问题
- 在更新模型时,如果其模型标签(从
getModelLabel
返回的属性)已更改,则历史记录消息将使用其新属性,这可能不是您期望的结果。
class Article extends Model { use HasHistories; public function getModelLabel() { return $this->title; } } // original title is 'my title' // modify title $article->title = 'new title'; $article->save(); // the updating history message // expect: Updating Article my title // actual: Updating Article new title
一种解决方案
public function getModelLabel() { return $this->getOriginal('title', $this->title); }