vzool/revisionable

无需思考即可为您的模型保留修订历史,作为Laravel的包使用

1.38.12 2021-04-08 08:18 UTC

README

Revisionable for Laravel

Latest Version Downloads License

不是很好吗?在你的项目中为任何模型拥有修订历史,无需为此做任何工作。只需简单地将 RevisionableTrait 特性添加到你的模型中,你就可以立即拥有它,并能够显示类似于以下的历史记录

  • Chris 将标题从 'Something' 改为 'Something else'
  • Chris 将类别从 'News' 改为 'Breaking news'
  • Matt 将类别从 'Breaking news' 改为 'News'

所以,你不仅可以看到发生了什么,还可以看到谁做了什么,这样就有责任了。

Revisionable 是一个 Laravel 包,允许您无需思考即可为您的模型保留修订历史。有关背景信息和信息,请参阅这篇文章

与第三方 Auth / Eloquent 扩展一起工作

Revisionable 支持 Auth,由

(推荐) Revisionable 现在也可以用作 特质,因此您的模型可以继续扩展 Eloquent 或任何扩展 Eloquent 的类(如 Ardent)。

安装

Revisionable 可以通过 composer 安装,详细信息请见 packagist,这里。

将以下内容添加到您的项目 composer.json 文件的 require 部分

"vzool/revisionable": "1.*",

运行 composer update 下载包

php composer.phar update

打开 config/app.php 并注册所需的服务提供者(Laravel 5.x)

'providers' => [
	Vzool\Revisionable\RevisionableServiceProvider::class,
]

发布配置和迁移(Laravel 5.x)

php artisan vendor:publish --provider="Vzool\Revisionable\RevisionableServiceProvider"

最后,您还需要在包上运行迁移(Laravel 5.x)

php artisan migrate --path=vendor/vzool/revisionable/src/migrations

# OR

php artisan migrate

对于 Laravel 4.x 用户

php artisan migrate --package=vzool/revisionable

如果您将频繁地进行完全的迁移(使用 migrate:refresh),您可以做的事情之一是将包中的迁移文件复制到您的 app/database 文件夹中,并将类名从 CreateRevisionsTable 更改为类似 CreateRevisionTable 的名称(不使用 's',否则您将收到一个错误消息,表明有重复的类)

cp vendor/vzool/revisionable/src/migrations/2013_04_09_062329_create_revisions_table.php app/database/migrations/

文档

实现

基于特质的新实现(推荐)

特质需要 PHP >= 5.4

对于您想要保留修订历史的任何模型,请包含 Vzool\Revisionable 命名空间,并在您的模型中使用 RevisionableTrait,例如

namespace App;

use \Vzool\Revisionable\RevisionableTrait;

class Article extends \Illuminate\Database\Eloquent\Model {
    use RevisionableTrait;
}

作为特质,Revisionable 现在可以与标准的 Eloquent 模型一起使用,或与任何扩展 Eloquent 的类(如 Ardent)一起使用。

基于旧类的方法

新的特质方法与现有的 Revisionable 安装向后兼容。您仍然可以使用以下安装说明,这实际上是一个扩展特质的包装器。

对于您想要保留修订历史的任何模型,请包含 Vzool\Revisionable 命名空间,并在您的模型中使用 RevisionableTrait,例如

use Vzool\Revisionable\Revisionable;

namespace App;

class Article extends Revisionable { }

注意:这也适用于命名空间模型。

实现说明

如果需要,您可以通过将模型中的 $revisionEnabled 设置为 false 来禁用修订版本。这在您想暂时禁用修订版本或想创建一个扩展了可修订版本的基模型时很有用,其中所有模型都扩展了此模型,但您想关闭某些模型的修订版本。

namespace App;

use \Vzool\Revisionable\RevisionableTrait;

class Article extends \Illuminate\Database\Eloquent\Model {
    protected $revisionEnabled = false;
}

您还可以在完成 X 次修订后禁用修订版本,通过将 $historyLimit 设置为您希望在停止修订之前保留的修订次数。

namespace App;

use \Vzool\Revisionable\RevisionableTrait;

class Article extends \Illuminate\Database\Eloquent\Model {
    protected $revisionEnabled = true;
    protected $historyLimit = 500; //Stop tracking revisions after 500 changes have been made.
}

为了限制历史记录,但如果您想删除旧修订版本,而不是停止跟踪修订版本,您可以通过设置 $revisionCleanup 来实现该功能。

namespace App;

use \Vzool\Revisionable\RevisionableTrait;

class Article extends \Illuminate\Database\Eloquent\Model {
    protected $revisionEnabled = true;
    protected $revisionCleanup = true; //Remove old revisions (works only when used with $historyLimit)
    protected $historyLimit = 500; //Maintain a maximum of 500 changes at any point of time, while cleaning up old revisions.
}

存储软删除

默认情况下,如果您的模型支持软删除,可修订版本将存储此删除和任何恢复作为模型上的更新。

您可以通过将 deleted_at 添加到您的 $dontKeepRevisionOf 数组中来选择忽略删除和恢复。

为了更好地格式化 deleted_at 条目的输出,您可以使用 isEmpty 格式化器(有关此示例,请参阅格式化输出)。

存储强制删除

默认情况下,模型的强制删除不会作为修订版本存储。

如果您想将强制删除作为修订版本存储,可以通过将以下内容添加到您的模型中来覆盖此行为,将 revisionForceDeleteEnabled 设置为 true

protected $revisionForceDeleteEnabled = true;

在这种情况下,created_at 字段将以 oldValue() 等于模型创建日期和 newValue() 等于 null 的键值存储。

注意!请谨慎打开此设置!由于修订中保存的模型现在不存在,因此您将无法获取其对象或其关系。

存储创建

默认情况下,新模型的创建不会作为修订版本存储。只有模型的后续更改会被存储。

如果您想将创建作为修订版本存储,可以通过将 revisionCreationsEnabled 设置为 true 来覆盖此行为,将以下内容添加到您的模型中

protected $revisionCreationsEnabled = true;

更多控制

毫无疑问,会有一些情况您只想为模型的部分字段存储修订历史记录,这可以通过两种不同的方式实现。在您的模型中,您可以指定您要显式跟踪的字段,其他所有字段将被忽略

protected $keepRevisionOf = ['title'];

或者,您可以指定您要显式忽略跟踪的字段。所有其他字段都将被跟踪。

protected $dontKeepRevisionOf = ['category_id'];

$keepRevisionOf 设置优先于 $dontKeepRevisionOf

在修订版本中存储附加字段

在某些情况下,您可能希望从每个修订版本中的模型中获取额外的元数据。例如,如果您必须跟踪账户以及用户。只需创建自己的新迁移来添加您想要添加到修订版本模型中的字段,并将它们添加到您的 config/revisionable.php 配置文件中,如下所示

'additional_fields' => ['account_id', 'permissions_id', 'other_id'], 

如果列存在于模型中,它将被包含在修订版本中。

请确保如果您无法保证每个模型中都有该列,您应在迁移中将该列设置为 nullable()

事件

每次创建模型修订版本时都会触发一个事件。您可以监听 revisionable.created
revisionable.savedrevisionable.deleted

// app/Providers/EventServiceProvider.php

public function boot()
{
    parent::boot();

    $events->listen('revisionable.*', function($model, $revisions) {
        // Do something with the revisions or the changed model. 
        dd($model, $revisions);
    });
}

输出格式

您可以继续(并鼓励)使用 Eloquent 访问器 在您的模型中设置值的输出,有关访问器的更多信息,请参阅 Laravel 文档。以下文档已过时。

在某些情况下,您想要控制值的输出格式,例如布尔字段,您可以在模型中的 $revisionFormattedFields 数组中设置它们。例如:

protected $revisionFormattedFields = [
    'title'      => 'string:<strong>%s</strong>',
    'public'     => 'boolean:No|Yes',
    'modified'   => 'datetime:m/d/Y g:i A',
    'deleted_at' => 'isEmpty:Active|Deleted'
];

您还可以使用模型中的 $revisionFormattedFieldNames 数组覆盖字段名称输出,例如:

protected $revisionFormattedFieldNames = [
    'title'      => 'Title',
    'small_name' => 'Nickname',
    'deleted_at' => 'Deleted At'
];

当使用 $revision->fieldName() 输出修订字段名称时,这会发挥作用。

字符串

要格式化字符串,只需在值前加上 string: 前缀,并确保包含 %s(这是实际值将在格式化响应中出现的位置),例如:

string:<strong>%s</strong>

布尔值

布尔值默认会显示为 0 或 1,这相当平淡,对最终用户来说没有太多意义,因此可以使用此格式化程序输出更漂亮的内容。在值前加上 boolean: 前缀,然后使用管道分隔 false 和 true 选项,例如:

boolean:No|Yes

选项

类似于 "布尔值",任何文本或数值都可以作为源值(通常标志存储在数据库中)。该格式允许您根据值指定不同的输出。将其视为关联数组,其中键与值之间由点分隔。数组元素由垂直线分隔。

options: search.On the search|network.In networks

日期时间

日期时间默认显示为 Y-m-d H:i:s。在值前加上 datetime: 前缀,然后添加您的日期时间格式,例如:

datetime:m/d/Y g:i A

为空

这依赖于布尔值,但它不是测试 true 或 false 值,而是检查值是否为 null 或空字符串。

isEmpty:No|Yes

它也可以接受 %s,如果您想输出值,如下所示,如果值为空,则显示 'Nothing',如果存在则显示实际值

isEmpty:Nothing|%s

加载修订历史

要加载给定模型的修订历史记录,只需在该模型上调用 revisionHistory 方法,例如:

$article = Article::find($id);
$history = $article->revisionHistory;

显示历史记录

大部分情况下,修订历史将包含足够的信息以直接输出更改历史,然而在更新外键的情况下,我们需要能够进行映射并显示比 plan_id 从 3 更改为 1 更好的内容。

为此,有几个辅助方法可以显示更有洞察力的信息,因此您可以显示类似 Chris 将计划从青铜更改为黄金 的内容。

上面的结果将是以下内容

@foreach($account->revisionHistory as $history )
    <li>{{ $history->userResponsible()->first_name }} changed {{ $history->fieldName() }} from {{ $history->oldValue() }} to {{ $history->newValue() }}</li>
@endforeach

如果您已启用创建修订,您可以这样显示

@foreach($resource->revisionHistory as $history)
  @if($history->key == 'created_at' && !$history->old_value)
    <li>{{ $history->userResponsible()->first_name }} created this resource at {{ $history->newValue() }}</li>
  @else
    <li>{{ $history->userResponsible()->first_name }} changed {{ $history->fieldName() }} from {{ $history->oldValue() }} to {{ $history->newValue() }}</li>
  @endif
@endforeach

userResponsible()

返回负责修订的用户。返回用户模型,如果没有记录用户则返回 null。

加载的用户模型取决于您在 config/auth.php 文件中为 model 变量设置的值。

fieldName()

返回已更新的字段名称,如果更新的字段是外键(在此阶段,它只是查看字段是否具有 _id 后缀),则返回 _id 之前的内容。例如,如果字段是 plan_id,则返回 plan

记得从上面,您可以通过模型中的 $revisionFormattedFieldNames 数组覆盖字段名称的输出。

identifiableName()

当值(旧值或新值)是外键关系的 ID 时使用。

默认情况下,它只返回已更新的模型的 ID。您可以根据自己的模型覆盖此方法以返回有意义的内容。例如:

use Vzool\Revisionable\Revisionable;

class Article extends Revisionable
{
    public function identifiableName()
    {
        return $this->title;
    }
}

oldValue() 和 newValue()

获取更新前或更新后的模型值。如果是外键,则调用 identifiableName()。

未知或无效的外键作为修订

在旧版本或新版本的值是一个不再存在的外键,或者实际上为空的情况下,您可以在模型中设置两个变量来控制这些情况下的输出

protected $revisionNullString = 'nothing';
protected $revisionUnknownString = 'unknown';

disableRevisionField()

有时暂时禁用一个可修订字段可能很有用,如果您想保存更新,但不需要保留变更记录。

$object->disableRevisionField('title'); // Disables title

或者

$object->disableRevisionField(['title', 'content']); // Disables title and content

贡献

鼓励并欢迎贡献;为了保持事物有序,所有错误和请求都应该在主项目的GitHub issues标签中打开,网址为vzool/revisionable/issues

所有拉取请求都应该提交到develop分支,以便在合并到master分支之前进行测试。

遇到麻烦了吗?

如果您在使用此包时遇到麻烦,很可能其他人已经遇到过相同的问题。您可以在以下两个地方查找您问题的常见答案:

如果您更愿意在StackOverflow上公开提问,请使用'revisionable'标签。