Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Auditable Bundle Laravel Package

zhortein/auditable-bundle

An auditing package for Laravel applications that automatically tracks model changes. It logs create, update, and delete events with changed attributes, original values, user IDs, and timestamps, making it easy to see who changed what and when. Simple configuration and Eloquent integration.

View on GitHub
Deep Wiki
Context7

Getting Started

Start with UPGRADE-2.0.md, not the README. The headline fact of 2.0.0 is that nothing changes unless you opt in: without new configuration you keep the legacy AuditEntry mapping, the audit_entry table, integer identifiers, the automatic listener, and fail-open behaviour. That means an existing install can take the version bump with no migration and no code changes — verify that first before reading anything else.

Then answer one question: is your app staying legacy, or moving to strict transactional audit mode? Everything downstream depends on it.

  • Legacy (default): the bundle wires an auto-listener that records audit entries and swallows failures. Good for "we want a trail, but auditing must never break checkout." Zero setup.
  • Transactional-only (opt-in): auditing becomes fail-closed. If the audit cannot be written, the operation fails. This is the mode the release is actually about, and it is where the day-to-day work lives.

Minimum viable first use case for the strict mode:

  1. Enable transactional-only mode in config and opt out of the legacy Doctrine mapping.
  2. Provide an application-owned audit entity — you own the class, the table, the columns, the indexes.
  3. Provide an audit entry factory (builds your entity from the change payload) and an audit storage (persists it).
  4. Provide a PSR-20 clock — do not let the package reach for new DateTimeImmutable().
  5. Run it against PostgreSQL 16 in your test suite before trusting it, because that is the only backend the release validated for commit/rollback/failure atomicity.

If you are on Laravel rather than Symfony, stop here: 2.0.0 requires Doctrine ORM 3, DoctrineBundle 2.19/3.x, Symfony 7.4/8.x and Symfony Security for actor resolution. There is no Eloquent path and no Laravel Security integration in this release.

Implementation Patterns

Wrap the audited operation, don't audit inside the write. The bundle deliberately performs no hidden flush and no transaction control. The idiomatic pattern is a service method that opens the transaction, performs the domain write, records the audit, and commits once:

public function rename(Product $p, string $name): void
{
    $this->em->wrapInTransaction(function () use ($p, $name) {
        $p->rename($name);
        $this->recorder->record($p);   // no flush here
    });
}

That is what makes the PostgreSQL atomicity proofs meaningful: either both the domain row and the audit row land, or neither does. Fight the temptation to call flush() inside your storage adapter — you will get interleaved flushes and partial commits, which is exactly the failure mode strict mode exists to prevent.

Treat fail-closed as a product decision, not a config flag. In strict mode a broken audit storage or a bad factory takes down the user-facing operation. Before enabling it, decide which flows may fail and confirm your error handling turns an audit failure into a clean 5xx rather than a half-applied write.

Model your audit entity for how you will query it. Because the entity is application-owned, you choose the shape: actor identifier, impersonator identifier, subject class plus identifier, action verb, before/after payload, timestamp. Add indexes on (subject_class, subject_id, occurred_at) from day one — you will be reading "history of this record" far more than you write.

Use deterministic identifiers. 2.0.0 supports UUIDs and primitive composites, not just integers. This matters when your domain uses UUID keys: the audit row can reference the subject directly without a lookup table. Keep the identifier representation in the factory and storage identical, or queries will silently miss rows.

Lean on actor + impersonation context. Symfony Security resolution gives you both the acting user and the impersonator. Surface both in the audit entity from the start; retrofitting "who was really doing this" after an incident is not possible.

Test against PostgreSQL, in transactions. Build a small suite that asserts three things per happy path: commit persists both rows, an exception rolls back both, and a storage failure in strict mode aborts the domain write. SQLite will pass locally and tell you nothing about the semantics that matter.

Keep a compatibility shim while migrating. Legacy and strict modes can coexist during a transition: leave legacy mapping on, introduce your own entity and recorder for new flows, and backfill only when you are ready to make the cut. There is no mandatory migration for staying legacy, so this can be a slow, boring rollout.

Gotchas and Tips

  • The upgrade is not free if you are on older infrastructure. ORM 2, DoctrineBundle below 2.19, PHP below 8.3, and Symfony below 7.4 are all hard breaks. The library code may not change, but Composer will not resolve. Check composer why doctrine/orm before bumping.
  • Fail-open legacy mode hides real failures. If you have been running with defaults, you may already be losing audit rows to swallowed exceptions and have no signal. Turning on logging around the legacy listener is worth an afternoon before you claim you have an audit trail.
  • Fail-closed is not retroactive. Switching to transactional-only does not validate, backfill, or repair historical entries. Plan the cutover point explicitly and document that entries before date X came from the old path.
  • No hidden flush means no audit row. The single most common misintegration will be "the record was written but nothing appears in the audit table" — you forgot to flush, or you flushed inside the storage adapter and hit a transaction boundary. Check the flush ownership first, always.
  • Opting out of legacy Doctrine mapping is a schema change. The table and mapping are the bundle's, not yours; once you drop the mapping you need your own migration. Keep the drop in its own deploy, after your new entity and table are live, so you have a rollback path.
  • Inject the PSR-20 clock or your tests will be flaky. Timestamps are not optional in an audit trail, and clock injection is the only supported way to make them deterministic. Constructing DateTimeImmutable anywhere in the audit path is a test-smell that will eventually cost you a debugging day.
  • Composite and UUID identifiers need consistency end to end. Factory, storage, and any query helper must agree on the string representation (binary UUID vs. hyphenated string). A mismatch produces empty histories rather than errors.
  • Atomicity is only proven on PostgreSQL 16. The release validated commit, rollback, and failure atomicity there and nowhere else. If you run MySQL or MariaDB, budget time to reproduce those three proofs yourself; DDL and implicit-commit behaviour differ enough to matter.
  • PHPStan at maximum level is a signal about the package's types, not yours. It tells you the public API is well-annotated. It does not mean your audit entity will pass — expect generics and array-shape annotations on payloads if you hold yourself to the same level.
  • Extension points to reach for: the audit entry factory (change payload to entity), the storage adapter (persistence strategy, batching, out-of-band sinks), and the clock. Anything deeper is likely a fork.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log