AuditTrailBundle records Doctrine ORM entity changes and stores audit logs.
By default it captures changes during flush and sends audits after postFlush
so normal writes stay fast. If you need stricter rules, you can also keep
audit delivery inside the transaction.
Many audit bundles do everything in one step. That can slow down writes. AuditTrailBundle splits capture, delivery, and persistence so it fits Doctrine flush rules better and keeps normal writes lighter.
Application Doctrine ORM AuditTrailBundle Queue / Storage
| | | |
| flush() | | |
|----------------->| | |
| | onFlush | |
| |------------------->| |
| | | Compute Diffs |
| | | Persist ORM-safe |
| | | audit rows when |
| | | allowed in-UoW |
| |<-------------------| |
| | | |
| | Execute SQL | |
| | (Transaction) | |
| | | |
| | postFlush | |
| |------------------->| |
| | | Dispatch Audit |
| | | Persist deferred |
| | | database audits via |
| | | direct writer |
| | |-------------------->|
| flush() returns | | |
|<-----------------| | |
| Async Save
- Deferred by default: The bundle captures changes during flush and sends audits after
postFlushunless you choose stricter behavior. - Safe with Doctrine: It does not call
flush()from inside DoctrinepostFlush. Deferred database writes use a separate writer. - Integrity support: You can sign audit logs and transport payloads.
- Simple setup: Use PHP 8 attributes and normal Symfony services.
- Deferred audit delivery by default
- Database, HTTP, and queue transports
- Tracking for to-many collection changes
- Sensitive data masking
- Revert support
- Access audit support for reads
- Conditional auditing
- Request and user context tracking
- Optional AI metadata hooks
- Symfony profiler support
The bundle includes EasyAdmin support for browsing and reviewing audit logs.
The bundle can also help with audit integrity and review.
- Sensitive Data Masking: Native support for
#[SensitiveParameter]on promoted constructor parameters and custom#[Sensitive]attributes. - HMAC Signatures: Audit logs can be signed so tampering can be detected during verification.
- Integrity Verification: Command-line tools to audit your audit logs.
| Topic | Description |
|---|---|
| Installation & Setup | Basic setup. |
| Configuration | Full configuration reference (enabled, transports, integrity, access auditing, export limits, queue limits, collection serialization). |
| Advanced Usage | Attributes, Conditional Auditing, Impersonation, Custom Context. |
| Architecture | A short map of the main runtime pieces and extension points. |
| Transports | Doctrine, HTTP, and Queue (Messenger) transport details. |
| Audit Reader | Querying audit logs programmatically. |
| Revert & Recovery | How revert works. |
| Security & Integrity | Data masking, cryptographic signing, and verification. |
| CLI Commands | Console commands for listing, purging, and exporting logs. |
| Integrations | EasyAdmin and Symfony Profiler support. |
| Serialization | JSON payload shape. |
| Failure & Transaction Safety | Recommended settings for defer_transport_until_commit, fallback, and transport errors. |
composer require rcsofttech/audit-trail-bundleTransport-specific packages are optional:
- Database transport in synchronous mode: no extra package required
- Async database transport or queue transport:
composer require symfony/messenger - HTTP transport:
composer require symfony/http-client - EasyAdmin dashboard:
composer require easycorp/easyadmin-bundle
If you are using the Doctrine Transport (default), update your database schema:
php bin/console make:migration
php bin/console doctrine:migrations:migrateAdd the #[Auditable] attribute to any Doctrine entity you want to track.
<?php
declare(strict_types=1);
use Doctrine\ORM\Mapping as ORM;
use Rcsofttech\AuditTrailBundle\Attribute\Auditable;
#[ORM\Entity]
#[Auditable(ignoredProperties: ['internalCode'])]
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
public private(set) ?int $id = null;
#[ORM\Column]
public private(set) string $name;
}The default transport stores audit logs in the database. If you enable integrity signing, make sure audit_trail.integrity.secret is configured.
If you enable a transport without its supporting package installed, the bundle fails fast during container build with a clear LogicException.
- PHP: 8.4+
- Symfony: 7.4+ or 8.0+
- Doctrine ORM: 3.6+
If you are choosing settings for production:
- Use
defer_transport_until_commit: truefor HTTP or queue-first setups where application write latency matters most. - HTTP and queue transports remain deferred-phase transports; setting
defer_transport_until_commit: falsedoes not move them into Doctrine'sonFlushtransaction window. - If you enable HTTP or queue transport and leave the failure knobs unset, the bundle defaults to stricter remote handling:
fail_on_transport_error: trueandfallback_to_database: false. Override them if you want softer behavior or a local safety net. - Use synchronous database transport with
fail_on_transport_error: truewhen the write and audit must succeed or fail together. - Keep
fallback_to_database: truewhen you want external transport failures to still leave a local audit trail. - Configure a PSR-6
cache_poolif you rely on cross-request access-audit cooldowns.
Feature dependency notes:
database.async: truerequiressymfony/messengerand a Messenger transport namedaudit_trail_databasequeue.enabled: truerequiressymfony/messengerhttp.enabled: truerequiressymfony/http-client- EasyAdmin integration is registered only when
EasyAdminBundleis enabled
Operational notes:
- Audit query limits must be positive integers.
AuditReader,AuditQuery, and the repository reject0or negative limits. - Cursor pagination uses audit-log UUIDs. Invalid cursors are rejected.
- When you use the bundle's container-managed services, audit-log row IDs stay UUID v7 even if the host application changes Symfony's global default UUID version for unrelated identifiers.
- Async database persistence preserves the original audit-log UUID across Messenger dispatch and worker persistence, so UUID cursor ordering and transaction drilldowns stay stable even if workers consume messages out of order.
AuditReader/AuditQuerychangedField()uses database JSON checks on MySQL, PostgreSQL, and SQLite. On other platforms it falls back to batched in-memory matching.- When you use object-based lookups or
ignored_entities, configure the real mapped class such asApp\Entity\Order. The bundle maps proxies and lazy subclasses back to that class. - EasyAdmin transaction drill-down accepts only one cursor at a time:
afterIdorbeforeId, never both. - CLI audit IP handling is conservative. In console use, the bundle prefers values such as
AUDIT_TRAIL_CLI_IP,SSH_CLIENT, orSSH_CONNECTION. If no valid IP is available, it storesnull. - When an entity changes scalar fields and Doctrine collections in the same flush, the bundle records one merged
updateaudit instead of redundant separate entries. - EasyAdmin revert previews handle UUID-backed relations and to-many collections safely, and restored collection values are shown in a readable format.
- The built-in soft-delete restore flow clears
soft_delete_fieldby setting it back tonull, so it should point to a nullable timestamp-like field such asdeletedAtorarchivedAt. Boolean or status-based soft-delete markers need custom restore handling. - Collection audits caused by deleting a related entity are most reliable with bidirectional Doctrine associations. With unidirectional mappings, the bundle may not have enough reverse relation context to emit an owner-side collection update.
- Bugs & Features: Please use the GitHub Issue Tracker.
- Contributing: Check out our Contributing Guide.
MIT License.

