wezlo/filament-record-freezer
Composer 安装命令:
composer require wezlo/filament-record-freezer
包简介
Freeze Eloquent records against modification — audit holds, finalized contracts, legal holds — for Filament.
README 文档
README
Freeze individual Eloquent records against modification — finalised contracts, audited financial periods, legal holds — for Filament.
When a record is frozen:
- Any attempt to
update(),save()(with dirty attributes) ordelete()it throws aRecordFrozenException— from the UI, a job, a policy, ortinker. - Filament resources using the
HandlesFrozenRecordstrait have theircanEdit()/canDelete()disabled automatically, and row-level edit/delete actions are hidden viaFreezableActionGroup. - A polymorphic audit trail records who froze it, when, and why. Unfreezing preserves full history (freeze → unfreeze → re-freeze creates a new row each time) with who / when / reason for both sides.
Requirements
- PHP 8.2+
- Laravel 11+ / Laravel 13
- Filament v4+
Installation
The package is path-linked inside this monorepo. From the project root:
composer require wezlo/filament-record-freezer php artisan migrate
Register the plugin on the panel(s) where you want the admin resource:
use Wezlo\FilamentRecordFreezer\FilamentRecordFreezerPlugin; public function panel(Panel $panel): Panel { return $panel ->plugins([ FilamentRecordFreezerPlugin::make(), ]); }
Make a model freezable
Add the HasFreezes trait to any Eloquent model:
use Wezlo\FilamentRecordFreezer\Concerns\HasFreezes; class Contract extends Model { use HasFreezes; }
You can now:
$contract->freeze('Legal hold — case #4412'); // throws if already frozen $contract->isFrozen(); // true $contract->activeFreeze; // current Freeze row (or null) $contract->freezes; // full history, newest first $contract->unfreeze('Case closed — release'); // sets unfrozen_at, keeps history $contract->freeze('Re-opened for audit'); // new Freeze row, history preserved $contract->update(['amount' => 5000]); // → RecordFrozenException $contract->delete(); // → RecordFrozenException
Observer coverage: updating, deleting, and (for models using SoftDeletes) restoring.
Filament integration — FreezableActionGroup
The row-level UX is driven by FreezableActionGroup, a dropdown action group that bundles Freeze + Unfreeze alongside your own row actions. The host's actions (edit, delete, custom workflows) are passed through unfrozenActions() and are automatically hidden when the record is frozen — so there's no path to modifying a frozen record from the table.
use Filament\Actions\DeleteAction; use Filament\Actions\EditAction; use Filament\Actions\ViewAction; use Filament\Resources\Resource; use Wezlo\FilamentRecordFreezer\Actions\FreezableActionGroup; use Wezlo\FilamentRecordFreezer\Concerns\HandlesFrozenRecords; class ContractResource extends Resource { use HandlesFrozenRecords; public static function table(Table $table): Table { return $table ->columns([ TextColumn::make('title'), static::freezableColumn(), // lock icon + tooltip ]) ->recordActions([ ViewAction::make(), // always available FreezableActionGroup::make() ->canFreeze(fn ($record) => auth()->user()->is_admin) ->canUnfreeze(fn ($record) => auth()->user()->hasRole('compliance')) ->unfrozenActions([ EditAction::make(), // auto-hidden when frozen DeleteAction::make(), // auto-hidden when frozen ]), ]); } }
canFreeze() and canUnfreeze() each accept a bool or a closure fn (Model $record, ?Authenticatable $user) => bool. Return false to hide that action for the current user / row. Default: allow.
HandlesFrozenRecords is still recommended as an authorization backstop — it overrides the resource's canEdit() and canDelete() so frozen records can't be modified even if a user hits the edit/delete URL directly or via a bulk action.
Audit trail — who froze / unfroze
Every freeze and unfreeze is recorded on the freezes table as an immutable row. Unfreezing does not delete the row — it sets unfrozen_at, unfrozen_by, and unfreeze_reason on the active row, preserving the full history.
$contract->freezes; // full history, newest first $contract->freezes->first()->frozen_by; // user id who froze $contract->freezes->first()->unfrozenBy?->name; // who released it (if released) $contract->freezes->first()->unfreeze_reason; // why it was released $contract->activeFreeze; // current Freeze row (null if not frozen)
Table schema:
| Column | Purpose |
|---|---|
freezable_type, freezable_id |
Polymorphic target |
frozen_by, frozen_at, reason |
Who froze, when, why |
unfrozen_by, unfrozen_at, unfreeze_reason |
Who released, when, why (null while active) |
Central admin resource
The plugin ships FreezeResource — a central list of every freeze, active or released, across every polymorphic model. Filters by status (active / released) and model type. Columns for both freeze and unfreeze metadata (reason, user, timestamps) are visible by default and toggleable. UnfreezeAction is available inline and only on active rows — released rows never show it, even when the underlying record has a newer active freeze.
Disable it from a specific panel:
FilamentRecordFreezerPlugin::make()->registerResource(false)
Or change its navigation:
FilamentRecordFreezerPlugin::make() ->navigationGroup('Audit & Compliance') ->navigationIcon('heroicon-o-shield-check')
Configuration reference
Publish with php artisan vendor:publish --tag="filament-record-freezer-config".
| Key | Default | Description |
|---|---|---|
user_model |
App\Models\User |
Model used for frozen_by / unfrozen_by relationships. |
table_name |
freezes |
Name of the polymorphic table. |
ignored_columns |
[] |
Columns whose dirty writes are allowed even while frozen (e.g. updated_at). |
require_reason |
true |
Enforce a non-empty reason on freeze / unfreeze. |
min_reason_length |
5 |
Minimum reason length when required. |
resource.enabled |
true |
Whether the central FreezeResource registers itself. |
resource.navigation_group |
Compliance |
Navigation group for the resource. |
resource.navigation_icon |
heroicon-o-lock-closed |
Navigation icon. |
resource.navigation_sort |
90 |
Navigation sort order. |
Events
Subscribe to these events for notifications, mirroring, or analytics:
Wezlo\FilamentRecordFreezer\Events\RecordFrozen— a record just became frozen.Wezlo\FilamentRecordFreezer\Events\RecordUnfrozen— a record was just released.
Both carry the Freeze model (including the freezable morph target).
Low-level engine API
use Wezlo\FilamentRecordFreezer\Services\FreezingEngine; $engine = app(FreezingEngine::class); $engine->freeze($contract, 'Audit hold', frozenBy: $userId); $engine->unfreeze($contract, 'Released by CFO', unfrozenBy: $userId);
The engine performs no authorization of its own — it's a low-level primitive. Enforce permissions at the caller (action, job, command) before invoking it.
Translations
The package ships full translations for:
en(English)ar(Arabic)
Every user-visible string — action labels, modal headings, notifications, table columns, filters, tooltips — is routed through __('filament-record-freezer::freezer.…'). Switch locales via App::setLocale() or your existing locale middleware and the entire freezer UI follows.
Publish the translation files to override them in your host app:
php artisan vendor:publish --tag="filament-record-freezer-translations"
Developer-facing exception messages (RecordFrozenException, engine validation) stay in English intentionally — they go to logs and stack traces, not end users.
Testing
The package is exercised by 27 Pest tests covering the trait, observer, engine, action group, resource, and re-freeze history semantics. Run them from the host app:
php artisan test --compact tests/Feature/FilamentRecordFreezer
wezlo/filament-record-freezer 适用场景与选型建议
wezlo/filament-record-freezer 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 10 次下载、GitHub Stars 达 1, 最近一次更新时间为 2026 年 04 月 11 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「Audit」 「laravel」 「compliance」 「freeze」 「filament」 「legal-hold」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 wezlo/filament-record-freezer 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 wezlo/filament-record-freezer 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 wezlo/filament-record-freezer 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Log adding/updating/deleting of elements
Doctrine ORM provider for the auditor audit-log library.
PB Web Media Audit Bundle for Symfony
Quality control for MediaWiki. Approve pages, assign approvers to categories, view your approval requests, and see the approval status of pages.
Simplest Auditable for Laravel Eloquent/Model
Audit changes of your Eloquent models in Laravel/Lumen
统计信息
- 总下载量: 10
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 39
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-04-11