anexia / laravel-changeset
一个用于监控模型变更并将它们记录到数据库中的Laravel模块
Requires
- laravel/framework: >=5.8.0
This package is auto-updated.
Last update: 2024-09-17 21:10:54 UTC
README
一个用于检测您Eloquent模型(写入数据库)的永久更改并将它们记录到单独数据库表的Laravel包。
安装和配置
通过Composer安装模块,因此请修改您的composer.json
中的require
部分。
"require": {
"anexia/laravel-changeset": "1.1.0"
}
在项目的config/app.php
中添加新的服务提供者。这将包括包的迁移文件到项目的迁移路径。
return [
'providers' => [
/*
* Anexia Changeset Service Providers...
*/
Anexia\Changeset\Providers\ChangesetServiceProvider::class,
]
];
现在运行
composer update [-o]
将包的源代码添加到您的/vendor
目录并更新自动加载。
自定义数据库配置
默认情况下,更改集表会添加到应用程序的主数据库中。默认更改集数据库连接为'changeset_mysql',它在包的/config/connections.php
中定义。
// from package's /config/connections.php:
'changeset_mysql' => [
'driver' => 'mysql',
'host' => env('CHANGESET_DB_HOST', env('DB_HOST', '127.0.0.1')),
'port' => env('CHANGESET_DB_PORT', env('DB_PORT', '3306')),
'database' => env('CHANGESET_DB_DATABASE', env('DB_DATABASE', 'forge')),
'username' => env('CHANGESET_DB_USERNAME', env('DB_USERNAME', 'forge')),
'password' => env('CHANGESET_DB_PASSWORD', env('DB_PASSWORD', '')),
'unix_socket' => env('CHANGESET_DB_SOCKET', env('DB_SOCKET', '')),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
因此更改集数据库默认使用.env中的'DB_'变量。要使用不同的更改集数据库,可以配置另一组.env变量。
# from .env:
CHANGESET_DB_CONNECTION=changeset_pgsql
CHANGESET_DB_HOST=127.0.0.1
CHANGESET_DB_PORT=5432
CHANGESET_DB_DATABASE=database
CHANGESET_DB_USERNAME=user
CHANGESET_DB_PASSWORD=pwd
/config/connections.php
包含一系列预设的数据库配置(changeset_sqlite、changeset_mysql、changeset_pgsql、changeset_sqlsrv),类似于Laravel的默认数据库配置。在应用程序启动时,这些条目被添加到应用程序的'database.connections'配置字段。
如果需要不同的配置,可以在应用程序的/config/database.php
中以通常的Laravel方式定义它,然后通过.env变量设置,例如:
// from application's /config/database.php
'connections' => [
...
'changeset_new' => [
'driver' => 'mysql',
'host' => env('CHANGESET_DB_HOST', env('DB_HOST', '127.0.0.1')),
'port' => env('CHANGESET_DB_PORT', env('DB_PORT', '3306')),
'db' => env('CHANGESET_DB_DATABASE', env('DB_DATABASE', 'forge')),
'user' => env('CHANGESET_DB_USERNAME', env('DB_USERNAME', 'forge')),
'pwd' => env('CHANGESET_DB_PASSWORD', env('DB_PASSWORD', '')),
'socket' => env('CHANGESET_DB_SOCKET', env('DB_SOCKET', '')),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
],
# from .env:
CHANGESET_DB_CONNECTION=changeset_new
工作原理
Changeset包包含三个新的模型
Changeset
* id
* action_id (uniquid() for each request/user action; related changesets share the same action_id; not nullable)
* changeset_type (I = INSERT, U = UPDATE, D = DELETE; not nullable)
* display (info text that describes the changes within the changeset; not nullable)
* object_type_id (references to the name of the class that got changed within the changeset; foreign_key to
object_type; not nullable)
* object_uuid (tracked identifier of the object; 'id' property by default; not nullable)
* related_changeset_id (child changeset that triggered an additional changeset for a parent model; the parent model
is defined by $trackRelated in the child class; foreign_key to changeset; nullable)
* user_id (identifier of the user that triggered the change; must implement the ChangesetUserInterface; App\User by
default; nullable)
* created_at (timestamp of the changeset entry's creation in the database; auto-managed by Eloquent)
* updated_at (timestamp of the changeset entry's last update in the database; auto-managed by Eloquent)
Changerecord
* id
* field_name (property that got changed; nullable)
* changeset_id (One-To-Many relation to a changeset; one changeset can contain multiple changerecords; foreign_key
to changeset; not nullable)
* display (info text that describes the change on the field_name; not nullable)
* is_deletion (boolean that marks a 'DELETE' event; 0 by default)
* is_related (boolean that marks if the associated changeset has a related_changeset_id defined; 0 by default)
* new_value (value of the field_name after the change; nullable)
* old_value (value of the field_name before the change; nullable)
* created_at (timestamp of the changerecord entry's creation in the database; auto-managed by Eloquent)
* updated_at (timestamp of the changerecord entry's last update in the database; auto-managed by Eloquent)
ObjectType
* id
* name (class that is trackable = uses ChangesetTrackable trait; unique; not nullable)
* created_at (timestamp of the object type entry's creation in the database; auto-managed by Eloquent)
* updated_at (timestamp of the object type entry's last update in the database; auto-managed by Eloquent)
Changeset包添加了对模型'created'、'updated'和'deleted'事件的钩子。因此,任何对可追踪模型的更改(save()、update()、delete()、forceDelete())都会触发更改集的创建。
根据执行的操作,会创建多个变更记录并将其关联到新的更改集中
- 如果新模型被保存,所有其属性都会收到一个变更记录,并将其关联到其'CREATE'更改集(因为它们都已被'更改')。
- 如果现有模型被更新,所有更改的属性(与更改前的值不同的值)都会收到一个变更记录,并将其关联到其'UPDATE'更改集。
- 如果现有模型被删除,不会将任何变更记录与它的'delete'更改集相关联。
无论执行更改的类型如何,都会触发配置的父级关系(通过$trackRelated)的进一步更改集。父级关系本身可以定义自己的$trackRelated关联,因此可能触发多层相关更改集的树(请参阅“相关更改集”的使用)。
使用
在一个基模型类中使用ChangesetTrackable特质。所有需要被更改集追踪的模型都必须从这个基模型类扩展。
// base model class app/BaseModel.php
<?php
namespace App;
use Anexia\Changeset\Traits\ChangesetTrackable;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model
{
use ChangesetTrackable;
// possible other "global" behaviour for all models can be put here
}
----------------------------------
// actual model class app/Post.php
<?php
namespace App;
class Post extends BaseModel
{
protected $table = 'posts';
protected $fillable = ['author_id', 'name', 'content'];
protected $trackBy = 'id';
protected $trackFields = ['author_id', 'name', 'content'];
protected $tackRelated = ['author' => 'posts'];
public function author()
{
return $this->belongsTo('App\User', 'author_id');
}
}
----------------------------------
// actual model class app/User.php
<?php
namespace App;
class User extends BaseModel implements ChangesetUserInterface
{
protected $table = 'users';
protected $fillable = ['name', 'email'];
protected $trackBy = 'id';
protected $trackFields = ['name', 'email'];
/**
* Implementation of required interface method
*
* @return string
*/
public function getUserName()
{
return $this->first_name . ' ' . $this->last_name;
}
/**
* Implementation of required interface method
*
* Relation to Anexia\Changeset\Changeset model
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function triggeredChangesets()
{
return $this->hasMany('Anexia\Changeset\Changeset', 'user_id');
}
public function posts()
{
return $this->hasMany('App\Post', 'author_id');
}
}
通过一个“中间”BaseModel类进行绕行是必要的,以便允许ChangesetTrackable特质的受保护字段在每个可追踪模型中被重写。这意味着$trackBy、$trackFields和$trackRelated不能在BaseModel类本身中修改/重新定义,而是在每个子类(如Post)中修改。
对象类型
更改集包使用ObjectType模型来管理使用包的ChangesetTrackable特质的所有类。应用程序在其生命周期中“学习”并记住可追踪类的名称。在第一次发生每个使用ChanesetTrackable特质的类的追踪更改之后,类名将被存储在一个新的“ObjectType”条目中。
播种
为了一次性学习所有可能的对象类型(即使用 ChangesetTrackable 特性的类名),该包提供了一个 ChangesetObjectTypeSeeder。该种子会遍历 "app" 和 "vendor" 目录下的所有类,并检查它们(或它们的父类之一)是否使用了 ChangesetTrackable 特性。如果是的话,类的名称将被存储为新的对象类型。
运行种子器有两种选择
- 显式运行
php artisan db:seed --class="Anexia\\Changeset\\Database\\Seeds\\ChangesetObjectTypeSeeder"
- 将其包含在通用数据库种子器中,通常位于 /database/seeds/DatabaseSeeder.php
<?php
use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
class DatabaseSeeder extends Seeder {
public function run()
{
Model::unguard();
// Changeset Seeds (ObjectType)
$this->call(\Anexia\Changeset\Database\Seeds\ChangesetObjectTypeSeeder::class);
$this->command->info('Changeset object types seeded!');
// other seeds
$this->call('AnotherSeeder');
$this->command->info('Other data seeded!');
...
}
}
使用常见的种子命令
php artisan db:seed
对象类型种子器将与所有其他定义的种子器(按给定顺序)一起处理。
相关变更集
由于 Post.php 与另一个模型 User.php(通过 'author')有关系,并且已将其关系配置为 'trackable',因此对帖子对象的任何更改不仅会触发该帖子的变更集,还会触发其关联作者的变更集。所以
- 如果保存了一个新的模型,则会创建一个带有对象类型 App\Post 的新 'CREATE' 变更集,所有属性都收到一个与该变更集关联的 changerecord。同时,还会创建一个带有对象类型 App\User 的新 'UPDATE' 变更集,其中包含字段名 'posts'、is_related = true 和显示信息,表明一个帖子对象已被新关联。
假设由管理员(即用户 1)为用户 2 创建了一个 id 为 1 的新帖子,将生成以下对象类型、变更集和变更记录
// object types
[
{
"id":1,
"name":"App\Post"
},
{
"id":2,
"name":"App\User"
}
]
// changeset and changerecords for post
{
"id":1,
"action_id":"59d5ced79454e",
"changeset_type":"I",
"display":"INSERT App\Post 1 at date 2017-10-05 10:02:46 by admin",
"object_type_id":1,
"object_uuid":1,
"related_changeset_id":null,
"user_id":1,
"created_at":"2017-10-05 10:02:46",
"updated_at":"2017-10-05 10:02:46",
"changerecords":[
{
"id":1,
"field_name":"author_id",
"changeset_id":1,
"display":"Set author_id to 1",
"is_deletion":false,
"is_related":false,
"new_value":"1",
"old_value":null,
"created_at":"2017-10-05 10:02:46",
"updated_at":"2017-10-05 10:02:46"
},
{
"id":2,
"field_name":"name",
"changeset_id":1,
"display":"Set name to First Post",
"is_deletion":false,
"is_related":false,
"new_value":"First Post",
"old_value":null,
"created_at":"2017-10-05 10:02:46",
"updated_at":"2017-10-05 10:02:46"
},
{
"id":3,
"field_name":"content",
"changeset_id":1,
"display":"Set content to This is my first post",
"is_deletion":false,
"is_related":false,
"new_value":"This is my first post",
"old_value":null,
"created_at":"2017-10-05 10:02:46",
"updated_at":"2017-10-05 10:02:46"
}
]
}
// related changeset and changerecord for user (author)
{
"id":2,
"action_id":"59d5ced79454e",
"changeset_type":"U",
"display":"UPDATE App\User 1 at date 2017-10-05 10:02:46 after INSERT App\Post 1 by admin",
"object_type_id":2,
"object_uuid":1,
"related_changeset_id":1,
"user_id":1,
"created_at":"2017-10-05 10:02:46",
"updated_at":"2017-10-05 10:02:46",
"changerecords":[
{
"id":4,
"field_name":"posts",
"changeset_id":2,
"display":"Changed posts associations to [{"id":1}]",
"is_deletion":false,
"is_related":true,
"new_value":"[{"id":1}]",
"old_value":null,
"created_at":"2017-10-05 10:02:46",
"updated_at":"2017-10-05 10:02:46"
}
]
}
- 如果现有的帖子被更新,则会创建一个新的带有对象类型 App\Post 的 'UPDATE' 变更集,所有更改的属性都会收到一个与该变更集关联的 changerecord。同时,还会创建一个新的带有对象类型 App\User 的 'UPDATE' 变更集,其中包含字段名 'posts'、is_related = true 和显示信息,表明某个关联的帖子已被更改。假设现有的帖子(id 为 1)被管理员(即用户 1)更改,将生成以下变更集和变更记录
// changeset and changerecords for post
{
"id":3,
"action_id":"59d5f7c928e33",
"changeset_type":"U",
"display":"UPDATE App\Post 1 at date 2017-10-05 12:03:10 by admin",
"object_type_id":1,
"object_uuid":1,
"related_changeset_id":null,
"user_id":1,
"created_at":"2017-10-05 12:03:10",
"updated_at":"2017-10-05 12:03:10",
"changerecords":[
{
"id":1,
"field_name":"author_id",
"changeset_id":3,
"display":"Set author_id to 1",
"is_deletion":false,
"is_related":false,
"new_value":"1",
"old_value":null,
"created_at":"2017-10-05 12:03:10",
"updated_at":"2017-10-05 12:03:10"
}
]
}
// related changeset and changerecord for user (author)
{
"id":4,
"action_id":"59d5f7c928e33",
"changeset_type":"U",
"display":"UPDATE App\User 1 at date 2017-10-05 12:03:10 after UPDATE App\Post 1 by admin",
"object_type_id":2,
"object_uuid":1,
"related_changeset_id":3,
"user_id":1,
"created_at":"2017-10-05 12:03:10",
"updated_at":"2017-10-05 12:03:10",
"changerecords":[
{
"id":4,
"field_name":"posts",
"changeset_id":4,
"display":"Associated posts still are [{"id":1}]",
"is_deletion":false,
"is_related":true,
"new_value":"[{"id":1}]",
"old_value":null,
"created_at":"2017-10-05 12:03:10",
"updated_at":"2017-10-05 12:03:10"
}
]
}
- 如果现有的帖子被删除,则会创建一个新的带有对象类型 App\Post 的 'DELETE' 变更集。同时,还会创建一个新的带有对象类型 App\User 的 'UPDATE' 变更集,其中包含字段名 'posts'、is_related = true、is_deletion = true 和显示信息,表明某个关联的帖子已被删除。
// changeset (with no changerecords) for post
{
"id":5,
"action_id":"59d5fd3f34bf9",
"changeset_type":"D",
"display":"DELETE App\Post 1 at date 2017-10-05 12:05:51 by admin",
"object_type_id":1,
"object_uuid":1,
"related_changeset_id":null,
"user_id":1,
"created_at":"2017-10-05 12:05:51",
"updated_at":"2017-10-05 12:05:51"
}
// related changeset and changerecord for user (author)
{
"id":6,
"action_id":"59d5fd3f34bf9",
"changeset_type":"U",
"display":"UPDATE App\User 1 at date 2017-10-05 12:05:51 after DELETE App\Post 1 by admin",
"object_type_id":2,
"object_uuid":1,
"related_changeset_id":5,
"user_id":1,
"created_at":"2017-10-05 12:05:51",
"updated_at":"2017-10-05 12:05:51",
"changerecords":[
{
"id":5,
"field_name":"posts",
"changeset_id":6,
"display":"Deleted posts associations",
"is_deletion":true,
"is_related":true,
"new_value":"[]",
"old_value":null,
"created_at":"2017-10-05 12:03:10",
"updated_at":"2017-10-05 12:03:10"
}
]
}
开发者列表
- Alexandra Bruckner ABruckner@anexia-it.com,首席开发者