Auditable Trait

Introduction

The Auditable trait records every create, update, and delete on a model, together with the acting user and the values that changed. It is useful for audit trails, change history, and compliance requirements.

Setup

Publish and run the migration that creates the audit table:

php artisan vendor:publish --tag=lara-util-x-migrations
php artisan migrate

Then add the trait to any model you want tracked:

use Illuminate\Database\Eloquent\Model;
use LaraUtilX\Traits\Auditable;

class Post extends Model
{
    use Auditable;
}

That is all the setup required. The trait hooks the model's created, updated, and deleted events automatically.

What Gets Recorded

Each event writes one row to the model_audits table:

Column Contents
model_type Fully qualified class name of the model
model_id Primary key of the record
event created, updated, or deleted
old_values JSON of the previous values
new_values JSON of the new values
user_id Auth::id() at the time of the change, or null
created_at / updated_at Timestamps

What lands in old_values and new_values depends on the event:

Event old_values new_values
created empty all attributes
updated the original values only the changed attributes
deleted the original values empty

Excluding Sensitive Attributes

Attributes such as passwords and tokens are never written to the trail. The defaults cover the common cases:

password, password_confirmation, remember_token,
api_token, access_token, refresh_token, secret, token

So a model with a password column records the change without recording the value:

$user->update(['name' => 'Grace', 'password' => Hash::make('new-secret')]);

// new_values => {"name": "Grace"}

To exclude more attributes on a specific model, override auditExcludedAttributes() and merge onto the defaults:

class Invoice extends Model
{
    use Auditable;

    protected static function auditExcludedAttributes(): array
    {
        return array_merge(parent::auditExcludedAttributes(), ['tax_id', 'bank_account']);
    }
}

Returning your own list without merging replaces the defaults entirely, so merge unless you intend to drop them.

Configuration

Publish the config file to change the defaults globally:

php artisan vendor:publish --tag=lara-util-x-config
'audit' => [
    'table' => 'model_audits',

    'excluded_attributes' => [
        'password',
        'password_confirmation',
        'remember_token',
        'api_token',
        'access_token',
        'refresh_token',
        'secret',
        'token',
    ],
],

Set table to write the trail somewhere other than model_audits. The table must have the same columns as the published migration.

Reading the Trail

The trait writes with the query builder and does not ship a model of its own, so query the table directly:

$history = DB::table('model_audits')
    ->where('model_type', Post::class)
    ->where('model_id', $post->id)
    ->orderByDesc('created_at')
    ->get();

Changes in v1.5.2

Before v1.5.2 the trait recorded every attribute verbatim. Applying it to a model with credentials, such as User, stored password hashes and tokens in the audit table in readable form.

As of v1.5.2:

  • Sensitive attributes are excluded by default, on both the old and new value sides.
  • The exclusion list is configurable, and extendable per model.
  • The audit table name is configurable rather than hardcoded.

If you adopted the trait before v1.5.2, existing rows still hold whatever was captured at the time. Review the table and purge any sensitive values it already contains, since this release changes what is written going forward but does not rewrite history.