laraarabdev/recordkeeper

Headless audit trail and rollback for any Laravel app — PHP 8 attributes, route auditing, privacy protection, and Artisan tools. No Filament required.

Maintainers

Package info

github.com/LaraArabDev/recordkeeper

pkg:composer/laraarabdev/recordkeeper

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 5

dev-main 2026-07-06 18:28 UTC

README

Recordkeeper Banner

Recordkeeper

Headless audit trail, rollback & data protection for Laravel

Latest Version Total Downloads License PHP Laravel

Tests codecov Static Analysis Security Audit Mutation Testing Code Style

Track every model change, route hit, queued job, Artisan command, and application event — with one-click rollback, privacy controls, and zero config arrays.
Built on owen-it/laravel-auditing · PHP 8.2 – 8.4 · Laravel 11 / 12

LaraArabDev — We build, develop, empower, and contribute. An Arab open-source community crafting production-grade Laravel packages.
LaraArabDev — نبني، نطوّر، نُمكّن، ونُساهم. مجتمع عربي مفتوح المصدر يصنع حزم Laravel احترافية وجاهزة للإنتاج.

Quick Start · Attributes · Rollback · Commands · العربية

What is Recordkeeper?

Recordkeeper is a Laravel package that gives your application a complete audit trail — tracking who did what, when, and the ability to undo it. It goes far beyond simple model change tracking:

  • Model auditing — every create, update, delete, and restore is logged automatically with before/after values
  • Route & API auditing — log every HTTP request with actor, guard, IP, timing, and status code
  • Job lifecycle tracking — follow queued jobs from dispatch through processing to completion or failure
  • Artisan command auditing — track command execution with duration, memory usage, and anomaly detection
  • Application event auditing — capture custom events with full payload
  • Outbound HTTP tracking — record every external API call your jobs make
  • Privacy protection — auto-redact sensitive fields (passwords, tokens, SSNs) and AES-encrypt recoverable fields
  • One-click rollback — revert any change (single or batch) with dry-run preview
  • 8 Artisan commands — search, show, tail, stats, rollback, prune, export, and model discovery from the CLI
  • 4 storage drivers — database, Redis, log, or null for testing

All configured through clean PHP 8 Attributes — no config arrays to maintain.

Built on top of owen-it/laravel-auditing — the most popular audit package in the Laravel ecosystem. Recordkeeper installs it automatically as a dependency. If you already use laravel-auditing, Recordkeeper is a drop-in enhancement — your existing auditable models keep working.

Why Recordkeeper over plain laravel-auditing?

Feature laravel-auditing alone + Recordkeeper
Model CRUD tracking Yes Yes
Route & API auditing No Built-in middleware
Job lifecycle tracking No queued → processing → done/failed
Artisan command auditing No With anomaly detection
Event auditing No Opt-in with payload capture
Outbound HTTP tracking No Linked to parent job
Configuration PHP config arrays PHP 8 Attributes
Privacy protection Manual exclusions Auto-redact + AES encrypt
Rollback transitionTo() manual One-click + batch + dry-run
CLI tools No 8 Artisan commands
Storage backends Database only 4 drivers

🚀 Quick Start

Install

composer require laraarabdev/recordkeeper
php artisan recordkeeper:install
php artisan migrate

Add the trait — done

class Order extends Model implements \OwenIt\Auditing\Contracts\Auditable
{
    use AuditsChanges;  // tracks creates, updates, deletes automatically
}

Audit API routes

Route::middleware('audit.api')->apiResource('orders', OrderController::class);

Protect sensitive data

#[Redact('cvv')]              // stored as ***
#[Encrypt('national_id')]     // AES-encrypted, recoverable on rollback
class Payment extends Model { ... }

Undo a mistake

Recordkeeper::rollback($auditId);

No boilerplate. No custom middleware. Just attributes, traits, and you're auditing.

📄 Default Configuration

After running php artisan recordkeeper:install, a config/recordkeeper.php file is published. Here are the defaults out of the box — everything works with zero changes:

Setting Default What it means
enabled true Auditing is active
events ['created', 'updated', 'deleted', 'restored'] All CRUD events tracked
driver 'database' Audits stored in audits table
privacy.mode 'redact' Sensitive fields auto-redacted with ***
privacy.global_exclude ['password', 'remember_token'] Never stored in any audit
rollback.enabled true Rollback feature active
queue.enabled false Sync writes (enable for async)
jobs.enabled false Opt-in per job or enable globally
commands.enabled false Opt-in per command or enable globally
http.enabled false Outbound HTTP tracking off by default
retention.default_days 0 Keep audits forever (set a number to auto-prune)
strict false Failed writes log silently (enable in tests to throw)

Full config file

config/recordkeeper.php
<?php

declare(strict_types=1);

return [
    'enabled' => env('RECORDKEEPER_ENABLED', true),

    'events' => ['created', 'updated', 'deleted', 'restored'],

    'privacy' => [
        'mode' => env('RECORDKEEPER_PRIVACY', 'redact'), // redact|encrypt|off
        'mask' => '***',
        'sensitive_patterns' => [
            'password', 'secret', 'token', 'api_key',
            'authorization', 'card', 'cvv', 'ssn', 'iban',
        ],
        'global_exclude' => ['password', 'remember_token'],
    ],

    'rollback' => [
        'enabled' => true,
        'permission' => 'rollback_audits',
        'restore_deleted' => true,
        'track' => env('RECORDKEEPER_ROLLBACK_TRACK', true),
    ],

    'retention' => [
        'default_days' => (int) env('RECORDKEEPER_RETENTION_DAYS', 0), // 0 = keep forever
        'per_model' => [],
    ],

    'guards' => [
        'web' => true,
        'api' => true,
    ],

    'discovery' => [
        'paths' => ['app/Models'],
    ],

    'strict' => env('RECORDKEEPER_STRICT', false),

    'driver' => env('RECORDKEEPER_DRIVER', 'database'),

    'drivers' => [
        'database' => [
            'chunk_size' => (int) env('RECORDKEEPER_CHUNK_SIZE', 500),
        ],
        'redis' => [
            'connection' => env('RECORDKEEPER_REDIS_CONNECTION', 'default'),
            'ttl' => (int) env('RECORDKEEPER_REDIS_TTL', 0),
        ],
        'log' => [
            'channel' => env('RECORDKEEPER_LOG_CHANNEL', 'stack'),
            'level' => env('RECORDKEEPER_LOG_LEVEL', 'info'),
        ],
        'null' => [],
    ],

    'cache' => [
        'enabled' => env('RECORDKEEPER_CACHE', false),
        'store' => env('RECORDKEEPER_CACHE_STORE', null),
        'ttl' => (int) env('RECORDKEEPER_CACHE_TTL', 300),
    ],

    'pipeline' => [],

    'queue' => [
        'enabled' => env('RECORDKEEPER_QUEUE', false),
        'connection' => env('RECORDKEEPER_QUEUE_CONNECTION', null),
        'queue' => env('RECORDKEEPER_QUEUE_NAME', 'audits'),
    ],

    'jobs' => [
        'enabled' => env('RECORDKEEPER_JOBS', false),
        'exclude' => [],
    ],

    'commands' => [
        'enabled' => env('RECORDKEEPER_COMMANDS', false),
        'exclude' => [
            'schedule:run', 'schedule:finish',
            'queue:work', 'queue:listen',
            'horizon:work', 'horizon:supervisor',
            'recordkeeper:tail',
        ],
        'metrics' => [
            'memory' => true,
            'audit_count' => true,
            'anomaly' => env('RECORDKEEPER_ANOMALY', false),
            'anomaly_multiplier' => 2.0,
            'anomaly_min_runs' => 5,
        ],
    ],

    'http' => [
        'enabled' => env('RECORDKEEPER_HTTP', false),
        'mode' => env('RECORDKEEPER_HTTP_MODE', 'auto'),
        'queue' => env('RECORDKEEPER_HTTP_QUEUE', false),
        'queue_name' => env('RECORDKEEPER_HTTP_QUEUE_NAME', null),
        'capture_headers' => env('RECORDKEEPER_HTTP_HEADERS', false),
        'capture_body' => env('RECORDKEEPER_HTTP_BODY', false),
        'body_limit' => 1000,
        'exclude_hosts' => [],
    ],

    'events_tracking' => [
        'enabled' => env('RECORDKEEPER_EVENTS', false),
        'exclude' => [],
    ],

    'listen' => [
        // \App\Events\UserRegistered::class,
    ],
];

What works immediately after install

With the default config, model auditing is fully active the moment you add the AuditsChanges trait:

  • All created, updated, deleted, restored events are tracked
  • Fields are auto-discovered from $fillable and $casts
  • password and remember_token are globally excluded
  • Fields matching password, secret, token, api_key, card, cvv, ssn, iban are auto-redacted with ***
  • Audits are stored in the audits database table with no expiry
  • Rollback is enabled and ready to use

Everything else (jobs, commands, events, HTTP tracking, queue writes) is opt-in — enable when you need it.

📦 Prerequisites

Requirement Version
PHP 8.2, 8.3, or 8.4
Laravel 11 or 12
owen-it/laravel-auditing ^13.0 or ^14.0 (auto-installed)

Already using laravel-auditing? Recordkeeper is a drop-in enhancement. Your existing auditable models keep working — add AuditsChanges to unlock the extra features.

🏷️ PHP 8 Attributes

Model Attributes

Attribute Description
#[Auditable] Enable auditing: events, only, exclude, redact, encrypt, retentionDays, threshold, tags
#[AuditExclude('field')] Never store this field (repeatable)
#[Redact('field')] Replace with *** before storage (repeatable)
#[Encrypt('field')] AES-encrypt, auto-decrypt on rollback (repeatable)

Non-Model Attributes

Attribute Target Description
#[Audit] Controller method Route auditing: tag, body, response, sample
#[AuditJob] Job class Job lifecycle: queued, processing, processed, failed, tags
#[AuditCommand] Command class Command tracking: tags
#[AuditEvent] Event class Event tracking: tags, capturePayload

Traits (for method-based overrides)

Trait Overridable Methods
AuditsJob auditJobTags(), shouldAuditQueued(), shouldAuditProcessing(), shouldAuditProcessed(), shouldAuditFailed()
AuditsCommand auditCommandTags()
AuditsEvent auditEventTags(), shouldCapturePayload()

Priority: Attribute > Trait > Config. When both are present, the attribute wins.

Full Model Example

#[Auditable(
    events: ['created', 'updated', 'deleted'],
    tags: ['payments'],
    retentionDays: 180,
    threshold: 500,
)]
#[AuditExclude('internal_notes')]
#[Redact('cvv')]
#[Encrypt('national_id')]
class Payment extends Model implements \OwenIt\Auditing\Contracts\Auditable
{
    use AuditsChanges;
}

⚙️ Config Defaults & Overrides

config/recordkeeper.php   →  Global defaults (all models)
       ↓
#[Auditable(...)]         →  Per-model overrides
       ↓
Auto-detection            →  Fields from $fillable + $casts
       ↓
Auto-redaction            →  Pattern matching on field names

With just the trait (no attribute), Recordkeeper automatically:

  1. Tracks created, updated, deleted, restored
  2. Discovers fields from $fillable and $casts
  3. Excludes password and remember_token
  4. Auto-redacts fields matching password, secret, token, api_key, card, cvv, ssn, iban
  5. Uses global retention policy

Override per model only when needed:

#[Auditable(
    events: ['updated', 'deleted'],
    retentionDays: 90,
    tags: ['finance'],
)]
class Invoice extends Model implements \OwenIt\Auditing\Contracts\Auditable
{
    use AuditsChanges;
}

🌐 Route & API Auditing

Two middleware aliases registered automatically:

Middleware Guard Actor Resolution
audit web auth()->user()
audit.api api Iterates all non-web guards (Sanctum, Passport, etc.)
// Web routes
Route::middleware('audit')->group(function () {
    Route::post('/pay', PayController::class);
});

// API routes
Route::middleware(['auth:sanctum', 'audit.api'])->group(function () {
    Route::apiResource('orders', OrderApiController::class);
});

Fine-grained control

#[Audit(tag: 'checkout', body: true, response: true, sample: 0.5)]
public function store(Request $request) { ... }

🔧 Job, Command & Event Auditing

Three ways to opt in — use whichever fits:

Method How Best for
Trait use AuditsJob; Quick opt-in, override methods
Attribute #[AuditJob] Declarative, all config in one place
Config jobs.enabled = true Audit everything globally

Jobs

use LaraArabDev\Recordkeeper\Concerns\AuditsJob;

class ProcessPayment implements ShouldQueue
{
    use AuditsJob;
    // Tracks: queued → processing → processed/failed
}

Commands

#[AuditCommand(tags: ['maintenance'])]
class PruneInactiveUsers extends Command { ... }

Includes anomaly detection — flags runs where duration or audit count exceeds the historical average.

Events

#[AuditEvent(capturePayload: true, tags: ['shipping'])]
class OrderShipped { ... }

🔒 Privacy & Data Protection

Sensitive data is transformed before it reaches the audit store — never plaintext.

Method How Recoverable?
#[Redact('field')] Replaced with *** No
#[Encrypt('field')] AES-encrypted Yes — auto-decrypted on rollback
Auto-redaction Pattern-matched fields No

Auto-redacted patterns: password · secret · token · api_key · authorization · card · cvv · ssn · iban

#[Redact('ssn', 'date_of_birth')]
#[Encrypt('api_secret')]
class Patient extends Model { ... }

⏪ Rollback

Undo any audited change — single or batch, with dry-run preview.

// Preview first
$preview = Recordkeeper::rollback($auditId, dryRun: true);

// Apply
Recordkeeper::rollback($auditId);

// Batch rollback — atomic, in reverse order
Recordkeeper::rollbackBatch('nightly-import');
Original Event Rollback Action
created Model is force-deleted
updated Old values restored
deleted Model restored (supports SoftDeletes)

Encrypted values are auto-decrypted before restoration. Auditing is disabled during rollback.

Via Artisan

php artisan recordkeeper:rollback 1842 --dry-run   # preview
php artisan recordkeeper:rollback 1842 --yes        # apply
php artisan recordkeeper:rollback --batch=nightly    # batch

📦 Batch Auditing

Group related changes for atomic rollback:

Recordkeeper::batch('nightly-import-2025-01', function () {
    $order1 = Order::create([...]);
    $order2 = Order::create([...]);
    $inventory->decrement('stock', 50);
    // All 3 audits share the same batch_id
});

// Roll back the entire import:
Recordkeeper::rollbackBatch('nightly-import-2025-01');

📝 Manual Logging

Record custom events outside the Eloquent/middleware flow:

Recordkeeper::log('payment.gateway.timeout', context: [
    'gateway' => 'stripe',
    'attempt' => 3,
]);

Recordkeeper::log('export.triggered', subject: $order, context: [
    'format' => 'csv',
    'rows'   => 1500,
]);

🔍 Querying Audits

Eloquent Scopes

Audit::forModel('Order')->latest()->get();
Audit::forSubject($order)->get();
Audit::forActor($admin)->get();
Audit::forGuard('api')->get();
Audit::forBatch('nightly-import')->get();
Audit::rollbackable()->get();
Audit::routeHits()->whereDate('created_at', today())->get();
Audit::jobAudits()->latest()->get();

Fluent Query Builder

$audits = app(AuditQuery::class)
    ->model('Order')
    ->event(['created', 'updated'])
    ->actor(42, 'Admin')
    ->guard('api')
    ->tag('finance')
    ->since('-7 days')
    ->rollbackable()
    ->latest()
    ->limit(50)
    ->builder()
    ->get();
All query methods
Method Description
->model(string) Filter by model (short name or FQCN)
->subjectId(int|string) Filter by auditable ID
->event(string|array) Filter by event name(s)
->actor(id, type?) Filter by user_id + optional user_type
->actorType(string) Filter by actor type only
->onlyAuthenticated() Exclude system/anonymous audits
->guard(string) Filter by auth guard
->tag(string|array) Filter by tag(s)
->batch(string) Filter by batch_id
->between(from, until) Date range filter
->since(from) Created after a date
->search(string) Free-text search
->rollbackable() Only model-change events
->jobs() Only job lifecycle events
->commands() Only command events
->events() Only application events
->latest() Order by newest first
->limit(int) Limit results
->offset(int) Offset for pagination
->builder() Get the Eloquent Builder

🖥️ Artisan Commands

Command Description
recordkeeper:install Publish config and migrations
recordkeeper:search Search audits with rich filters
recordkeeper:show {id} Display audit with color-coded diff
recordkeeper:tail Live-follow audits in real time
recordkeeper:stats Statistics dashboard
recordkeeper:models List auditable models with config
recordkeeper:prune Delete old records by retention policy
recordkeeper:rollback Revert single audit or batch
php artisan recordkeeper:search --model=Order --event=updated --since="-7 days"
php artisan recordkeeper:show 1842
php artisan recordkeeper:tail --model=Order --guard=api
php artisan recordkeeper:stats --since="-30 days"
php artisan recordkeeper:prune --days=365 --dry-run
php artisan recordkeeper:rollback 1842 --dry-run
php artisan recordkeeper:rollback --batch=nightly-import --yes
php artisan recordkeeper:models --json

💾 Storage Drivers

Driver Best For Rollback Queries
database Full-featured (default) Yes Yes
redis High-throughput writes No Limited
log Observability / debugging No No
null Tests / disabled environments No No
// config/recordkeeper.php
'driver' => env('RECORDKEEPER_DRIVER', 'database'),

Custom drivers: Implement AuditDriver and register via AuditDriverManager::extend().

🛠️ Customization

Custom Actor Resolver

Recordkeeper::resolveActorUsing(function () {
    return auth()->guard('admin')->user()
        ?? auth()->guard('api')->user()
        ?? auth()->user();
});

Context Enrichment

Recordkeeper::pushContext([
    'deployment' => config('app.version'),
    'server'     => gethostname(),
]);

$order->auditContext(['reason' => 'Customer requested change']);
$order->update(['status' => 'refunded']);

Tags

#[Auditable(tags: ['billing', 'critical'])]
class Invoice extends Model { ... }

// Runtime
Recordkeeper::withTags(['nightly-sync']);

React to Audit Writes

use LaraArabDev\Recordkeeper\Events\ChangeRecorded;

class NotifyOnCriticalChange
{
    public function handle(ChangeRecorded $event): void
    {
        if (str_contains($event->audit->tags, 'critical')) {
            // Send Slack alert, create ticket, etc.
        }
    }
}

🗄️ Database Schema

audits table (added columns)

Column Type Index Purpose
guard varchar Yes Authentication guard
batch_id varchar Yes Groups audits for batch rollback
context json Route info, duration, metrics

audit_http_requests table

Column Type Purpose
audit_id bigint FK Links to parent job audit
method varchar(10) HTTP verb
url text Full request URL
status_code int Response status
duration_ms int Round-trip time
failed boolean Connection failed?

⚡ Performance

Operation Overhead
Model audit (sync) ~1-2ms
Model audit (async) < 0.5ms
Route middleware ~1-2ms
Job/Command/Event ~0.5-1ms
HTTP tracking ~0.3ms

Why it's fast: opt-in architecture (zero overhead for disabled features), early exits everywhere, no reflection in hot paths, lightweight DTOs, and sampling support.

Production Tips

Scenario Recommendation
High-traffic API (1000+ req/s) Enable queue.enabled, use sample: 0.1
Write-heavy app Use redis driver
Testing Use null driver
Large audit tables Set retention.default_days, schedule recordkeeper:prune daily
# Run benchmarks
composer bench           # full suite
composer bench:quick     # fast run

📋 Configuration Reference

Full configuration table
Key Default Description
General
enabled true Global on/off switch
events ['created','updated','deleted','restored'] Default events
strict false Throw on failure
driver 'database' Storage backend
Privacy
privacy.mode 'redact' redact / encrypt / off
privacy.mask '***' Redaction string
privacy.sensitive_patterns ['password','secret',...] Auto-redacted patterns
privacy.global_exclude ['remember_token'] Always excluded fields
Rollback
rollback.enabled true Enable rollback
rollback.permission 'rollback_audits' Required permission
Retention
retention.default_days 0 Prune after N days (0 = forever)
retention.per_model [] Per-model overrides
Queue
queue.enabled false Async audit writes
queue.connection null Queue connection
queue.queue 'audits' Queue name
Jobs
jobs.enabled false Track job lifecycle
jobs.exclude [] Jobs to skip
Commands
commands.enabled false Track commands
commands.exclude ['schedule:run',...] Commands to skip
commands.metrics.anomaly false Anomaly detection
HTTP
http.enabled false Track outbound HTTP
http.capture_headers false Store headers
http.capture_body false Store body
Cache
cache.enabled false Read cache
cache.ttl 300 Cache TTL (seconds)

❓ FAQ

Does this require a lot of configuration?

No. Install, migrate, add the trait — that's it. Recordkeeper works with sensible defaults. AuditsChanges automatically tracks CRUD events, discovers fields, excludes passwords, and auto-redacts sensitive patterns.

Will this slow down my app?

No. ~1-2ms per audit write synchronously. For high-traffic apps: enable queue.enabled (< 0.5ms sync), use sample: 0.1 on busy routes, or use the redis driver. Disabled features have zero overhead.

Compatible with existing laravel-auditing?

Yes. Recordkeeper is a drop-in enhancement — your existing auditable models keep working. Add AuditsChanges to unlock the extra features.

How does priority work with both trait and attribute?

Attribute > Trait > Config. The attribute always wins when both are present.

Can I keep audit writes on a separate queue?

Yes. Enable queue.enabled and audits automatically go to a dedicated audits queue:

php artisan queue:work --queue=default    # your app jobs
php artisan queue:work --queue=audits     # audit writes (separate)

For full isolation, set RECORDKEEPER_QUEUE_CONNECTION=redis-audits.

What about sensitive data?

Three layers: (1) Global exclusion — password, remember_token never stored. (2) Auto-redaction — fields matching password, secret, token, api_key, card, cvv, ssn, iban replaced with ***. (3) Explicit encryption — #[Encrypt] for recoverable fields.

Multi-tenant support?

Yes. Use tags and context enrichment:

Recordkeeper::withTags(['tenant:'.$tenant->id]);
Audit::query()->where('tags', 'like', '%tenant:42%')->get();

🧪 Testing & Quality

composer test           # Pest test suite
composer test:coverage  # With code coverage
composer analyse        # PHPStan (level 6)
composer format         # Laravel Pint
composer infection      # Mutation testing
PHP Laravel Status
8.2 11 / 12 Supported
8.3 11 / 12 Supported
8.4 11 / 12 Supported

🔐 Security

Please review our security policy on how to report security vulnerabilities.

📄 Credits & License

MIT License — see LICENSE for details.

🇸🇦 العربية

Recordkeeper بانر

ما هو Recordkeeper؟

Recordkeeper هو حزمة Laravel تُعزّز owen-it/laravel-auditing وتوسّع قدراته بشكل كبير. بينما laravel-auditing يتتبع تغييرات الموديلات فقط، Recordkeeper يتتبع كل شيء في تطبيقك: المسارات (Routes)، طلبات API، الـ Jobs، أوامر Artisan، والأحداث (Events).

لماذا تستخدم Recordkeeper؟

  • تتبع شامل — لا يقتصر على الموديلات فقط، بل يشمل كل عملية في تطبيقك
  • PHP 8 Attributes — بدل ملفات الإعدادات الطويلة، استخدم #[Auditable] و #[Redact] و #[Encrypt] مباشرة على الكلاس
  • حماية البيانات تلقائياً — كلمات المرور والتوكنات وأرقام البطاقات تُخفى تلقائياً قبل التخزين
  • التراجع بضغطة واحدة — استرجع أي تغيير فردي أو مجموعة تغييرات كاملة مع معاينة قبل التطبيق
  • 8 أوامر Artisan — ابحث، تتبع مباشرة، اعرض إحصائيات، نظّف، واسترجع من سطر الأوامر
  • 4 محركات تخزين — قاعدة البيانات، Redis، سجلات، أو Null للاختبارات

التثبيت

composer require laraarabdev/recordkeeper
php artisan recordkeeper:install
php artisan migrate

ملاحظة: الحزمة تثبّت owen-it/laravel-auditing تلقائياً كاعتماد — لا تحتاج لتثبيته يدوياً.

البداية السريعة

أضف الـ trait والـ attribute لأي موديل Eloquent — هذا كل ما تحتاجه:

use LaraArabDev\Recordkeeper\Attributes\Auditable;
use LaraArabDev\Recordkeeper\Attributes\Redact;
use LaraArabDev\Recordkeeper\Attributes\Encrypt;
use LaraArabDev\Recordkeeper\Concerns\AuditsChanges;

#[Auditable(events: ['created', 'updated', 'deleted'])]
#[Redact('cvv')]           // يُستبدل بـ *** في سجل التدقيق
#[Encrypt('national_id')]  // يُشفّر بـ AES ويُفك تلقائياً عند التراجع
class Payment extends Model implements \OwenIt\Auditing\Contracts\Auditable
{
    use AuditsChanges;
}

تدقيق المسارات (Routes)

// مسارات الويب
Route::middleware('audit')->group(function () {
    Route::post('/pay', PayController::class);
});

// مسارات API
Route::middleware(['auth:sanctum', 'audit.api'])->group(function () {
    Route::apiResource('orders', OrderApiController::class);
});

التراجع عن التغييرات

use LaraArabDev\Recordkeeper\Facades\Recordkeeper;

// معاينة قبل التطبيق
$preview = Recordkeeper::rollback($auditId, dryRun: true);

// تطبيق التراجع
Recordkeeper::rollback($auditId);

// التراجع عن مجموعة كاملة
Recordkeeper::rollbackBatch('nightly-import');

أوامر Artisan

php artisan recordkeeper:search --model=Order --event=updated    # البحث في السجلات
php artisan recordkeeper:show 1842                                # عرض تدقيق مع الفروقات
php artisan recordkeeper:tail --model=Order                       # تتبع مباشر
php artisan recordkeeper:stats --since="-30 days"                 # لوحة الإحصائيات
php artisan recordkeeper:rollback 1842 --dry-run                  # معاينة التراجع
php artisan recordkeeper:prune --days=365 --yes                   # تنظيف السجلات القديمة
php artisan recordkeeper:models                                    # عرض الموديلات المُراقبة

المقارنة مع laravel-auditing وحده

الميزة laravel-auditing فقط + Recordkeeper
تتبع الموديلات نعم نعم
تدقيق المسارات وAPI لا نعم
تتبع الـ Jobs لا نعم
تدقيق أوامر Artisan لا نعم
تتبع الأحداث لا نعم
الإعداد مصفوفات PHP PHP 8 Attributes + Traits
حماية الخصوصية أساسية إخفاء تلقائي + تشفير AES
التراجع transitionTo() يدوي تراجع بضغطة واحدة مع معاينة
التجميع (Batching) لا نعم — تراجع ذري لمجموعة
أدوات سطر الأوامر لا 8 أوامر Artisan
محركات التخزين قاعدة بيانات فقط 4 محركات

للمزيد من التفاصيل والتوثيق الكامل، راجع الأقسام الإنجليزية أعلاه.