juliofarra/yii2-logevent

Yii2 behavior that automatically audits ActiveRecord changes (INSERT, UPDATE, DELETE) into a log table, storing JSON snapshots and diffs, with support for excluded and masked attributes.

Maintainers

Package info

github.com/juliofarra/yii2-logevent

Type:yii2-extension

pkg:composer/juliofarra/yii2-logevent

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.2.0 2026-06-16 04:01 UTC

This package is auto-updated.

Last update: 2026-07-16 04:07:16 UTC


README

Tests Latest Stable Version License: MIT

Automatic audit trail for Yii2 ActiveRecord models.

Attach a single behavior to any ActiveRecord model and every change is logged to a database table:

Event What gets logged
INSERT Full record snapshot, as JSON
UPDATE Only the diff — old and new values of changed attributes, as JSON
DELETE Full record snapshot, as JSON

Each log entry also records who (user ID), when (timestamp) and from where (client IP).

Documentación en español: README.es.md

Features

  • One-line setup: declare the behavior and the model is audited.
  • Excluded attributes: invisible to the log — never stored, and changes to them alone don't create a log entry.
  • Masked attributes: the log records that the attribute was set or changed, but its value is always replaced with ***** (ideal for passwords, tokens and other secrets).
  • Configurable log table: different models can log to different tables.
  • Logging failures never break the operation being logged.
  • Works in web and console applications.
  • Database agnostic: PostgreSQL, MySQL/MariaDB and SQLite covered by tests.

Requirements

  • PHP >= 8.0
  • Yii2 >= 2.0.45
  • For native JSON columns: PostgreSQL 9.4+, MySQL 5.7+ or MariaDB 10.2+. On other databases (e.g. SQLite) the JSON payload is stored in a text column transparently.

Installation

composer require juliofarra/yii2-logevent

Migration

The package ships a namespaced migration that creates the log_event table. Add the namespace to your console configuration:

// console/config/main.php (or config/console.php)
'controllerMap' => [
    'migrate' => [
        'class' => 'yii\console\controllers\MigrateController',
        'migrationNamespaces' => [
            'console\migrations',           // your own migrations
            'lab37\logevent\migrations',    // yii2-logevent
        ],
    ],
],

Then run:

php yii migrate

The table created is:

Column Type Description
id int, PK
entity string Table name of the audited record (polymorphic reference, together with entity_id)
entity_id bigint ID of the audited record
event string INSERT, UPDATE or DELETE
data json Snapshot or diff
user_id int, null User who performed the action
created_at timestamp When it happened (DB default CURRENT_TIMESTAMP)
ip string(45), null Client IP (IPv4/IPv6)

Usage

Declare the behavior in any ActiveRecord model:

use lab37\logevent\LogEventBehavior;

class Order extends \yii\db\ActiveRecord
{
    public function behaviors()
    {
        return [
            'logEvent' => [
                'class' => LogEventBehavior::class,
            ],
        ];
    }
}

That's it. Every insert, update and delete on Order is now logged.

Excluding and masking attributes

'logEvent' => [
    'class'   => LogEventBehavior::class,
    'exclude' => ['internal_token'],   // invisible to the log
    'mask'    => ['password'],         // logged, but value hidden
],
  • exclude — the attribute never appears in the logged JSON. If an update only changes excluded attributes, no log entry is created at all.
  • mask — the attribute appears in the logged JSON, so you know it was set or changed, but its value is always ***** (configurable via maskValue). A null masked value is kept as null, so the log still shows whether the field was loaded.

Example of a logged UPDATE with a masked password:

{
    "status":   {"old": "draft", "new": "sent"},
    "password": {"old": "*****", "new": "*****"}
}

Behavior reference

Property Default Description
idAttribute 'id' Attribute that identifies the owner record
logEventClass LogEvent::class ActiveRecord class used to store log entries
exclude [] Attributes invisible to the log
mask [] Attributes logged with their value hidden
maskValue '*****' Replacement value for masked attributes

Logging different models to different tables

Create a subclass of LogEvent that points to another table (create that table with the same structure first):

use lab37\logevent\models\LogEvent;

class OrderLogEvent extends LogEvent
{
    public static function tableName()
    {
        return '{{%order_log_event}}';
    }
}

And configure it in the behavior:

'logEvent' => [
    'class'         => LogEventBehavior::class,
    'logEventClass' => OrderLogEvent::class,
],

Querying the log

The behavior adds a logEvents relation to the owner model (newest first):

foreach ($order->logEvents as $log) {
    echo $log->event;         // INSERT | UPDATE | DELETE
    echo $log->created_at;    // timestamp
    print_r($log->dataArray); // payload decoded as PHP array, on any DB
}

Or query directly:

use lab37\logevent\models\LogEvent;

$logs = LogEvent::find()
    ->forEntity(Order::tableName(), $order->id)
    ->ofEvent(LogEvent::EVENT_UPDATE)
    ->ordered()
    ->all();

Display widget

The package ships an optional widget that renders a model's change log inside the page where you call it — no popup, no Bootstrap, no jQuery. It uses a native HTML5 <details> element so the log expands and collapses without JavaScript.

use lab37\logevent\widgets\LogEventWidget;

echo LogEventWidget::widget(['model' => $order]);

This outputs a collapsible block listing every event for $order, each showing its timestamp, IP, user and the changed fields (old → new for updates, the full snapshot for inserts and deletes). Values are rendered through Yii::$app->formatter, and field labels come from the model's attributeLabels().

Widget reference

Property Default Description
model (required) The audited ActiveRecord instance whose log is displayed
initiallyOpen false Whether the <details> block starts expanded
buttonLabel 'Event Log' Text shown on the toggle
customViewPath null Path/alias of a custom view that takes over rendering

Global configuration

LogEventWidget::widget() instantiates the widget through Yii's DI container, so you can set defaults for every call once in your application configuration — ideal for pointing all widgets at the same custom view without repeating it:

// config/web.php
'container' => [
    'definitions' => [
        \lab37\logevent\widgets\LogEventWidget::class => [
            'customViewPath' => '@app/views/audit/_log',
            'buttonLabel'    => 'Change log',
        ],
    ],
],

From then on, every call uses those defaults, and any individual call can still override them:

echo LogEventWidget::widget(['model' => $order]);                         // uses the global defaults
echo LogEventWidget::widget(['model' => $order, 'initiallyOpen' => true]); // overrides per call

Precedence is: per-call config > container default > property default.

Styling

A minimal stylesheet is bundled as an asset and registered automatically. To style the widget yourself, disable the bundle in your asset manager configuration and target the .log-event-* classes:

// config: components.assetManager
'bundles' => [
    'lab37\logevent\widgets\assets\LogEventWidgetAsset' => false,
],

Custom view

For full control over the markup, point customViewPath to your own view. When set, the widget renders only that view — the built-in <details> shell and the bundled stylesheet are skipped entirely, so your view owns the whole output (including any toggle button and its styling).

The view receives $model, $logEvents, $initiallyOpen and $buttonLabel:

echo LogEventWidget::widget([
    'model'          => $order,
    'customViewPath' => '@app/views/audit/_log',
]);
// @app/views/audit/_log.php
/** @var yii\db\ActiveRecord $model */
/** @var lab37\logevent\models\LogEvent[] $logEvents */
/** @var bool $initiallyOpen */
/** @var string $buttonLabel */

foreach ($logEvents as $log) {
    // render however you like
}

Performance note

The default view resolves $logEvent->user per row to show the user's name (falling back to user_id). For models with long histories, eager-load the relation to avoid N+1 queries — or use a customViewPath that displays user_id directly.

Tests

composer install
composer test

The suite runs against an in-memory SQLite database and also exercises the package migration.

License

MIT. See LICENSE.