vouchedfor/audit-ui-bundle
A Symfony bundle from VouchedFor that adds a UI for browsing and reviewing audit trail entries. It provides the interface layer over your recorded audit data, helping teams inspect activity logs in the browser. Minimal adoption so far; check the docs for setup and compatibility.
Read this first: despite the framing, vouchedfor/audit-ui-bundle is a Symfony bundle, not a Laravel package. It exists to put a read-only browsing UI (built on EasyAdmin) in front of audit entries produced by dh/auditor-bundle (historically damienharper/audit-bundle). If you are working in Laravel day-to-day, treat this repo as a design reference for what you'd build with owen-it/laravel-auditing + Filament, not as a dependency you'll composer require.
For a Symfony team, the minimal path is:
composer require dh/auditor-bundle easycorp/easyadmin-bundle
composer require vouchedfor/audit-ui-bundle
// config/bundles.php
return [
DH\AuditorBundle\DHAuditorBundle::class => ['all' => true],
EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true],
VouchedFor\AuditUiBundle\AuditUiBundle::class => ['all' => true],
];
Then ensure at least one entity is actually audited and its schema is generated:
bin/console make:migration # audit tables are created by the auditor bundle
bin/console doctrine:migrations:migrate
Where to look first. After install, open the bundle's own src/Controller/ directory — that is where the EasyAdmin CRUD controller lives — and its src/Resources/config/ for services and route prefixes. Read that controller before wiring anything up: with an archived 0-star package, the source is the documentation. You'll need the controller's FQCN to register it in your dashboard:
// src/Controller/Admin/DashboardController.php
public function configureMenuItems(): iterable
{
yield MenuItem::linkToCrud('Audit Log', 'fa fa-history', AuditController::class);
}
First use case. "Who changed this order's status, from what, to what, and when?" — that is the report this bundle is built to answer, and it is the one thing to validate end-to-end before adopting it. Make an edit to an audited entity, then confirm a row lands in the audit table and renders as a readable diff in the UI.
Register the CRUD controller, don't fork the bundle. The intended integration is a one-line dashboard registration plus a route prefix in config/routes.yaml. If the bundle ships a dashboard of its own, you can mount it as-is; otherwise you're composing it into your existing EasyAdmin dashboard, which is the more common and more maintainable shape.
Gate the route with your normal security layer. Audit logs are among the most sensitive tables you own — they frequently contain field-level values that were later redacted, PII in old_values, and impersonation traces. Do not rely on the bundle's defaults:
# config/packages/security.yaml
access_control:
- { path: '^/admin/audit', roles: [ROLE_AUDIT_READER] }
Pair this with a tenant-scoped AuditReader / query filter if your app is multi-tenant; the default provider will happily show every tenant's entries.
Scope the log per entity. The core workflow is "open a record, see its history." Two viable patterns:
<a href="{{ path('admin_audit', {entity: 'App\\Entity\\Order', id: order.id}) }}">History</a>
detail page, with a link to the full view. This is the pattern most teams actually ship, because reviewers rarely want to leave the record.Filtering. The UI is only useful if you can answer "everything user X did last Tuesday." Make sure created_at, type, object_id, and user_id/blame_id are indexed and exposed as filters. On a busy application the audit table will dwarf your domain tables within months.
Retention is a design decision, not an afterthought. Schedule a pruning command (the auditor bundle ships one; verify the exact name with bin/console list dh_auditor) into your scheduler, and decide the retention window per entity class. Auditing payment records and auditing "last_login_at" have very different legal weight.
If you're porting to Laravel. The mapping is direct and worth documenting for your team: Dh\Auditor → owen-it/laravel-auditing; the EasyAdmin CRUD controller → a Filament Resource with a read-only table and an infolist diff view; AuditReader → a scoped Eloquent query on audits. The one thing worth copying wholesale is the diff renderer's handling of scalar vs. JSON vs. null-to-value transitions, because that is where every audit UI gets ugly.
The package is archived with zero dependents — you are the maintainer now. Do not composer require a floating constraint. Fork it, pin the fork, or vendor the controller into your own src/ and drop the dependency. An archived bundle with no dependents means no security patches, no EasyAdmin compatibility fixes, and no one to ask when it breaks on your next Symfony upgrade.
EasyAdmin field-API churn is the likely breakage point. EasyAdmin 3 → 4 changed Field configuration significantly. Check the composer.json constraint of the bundle against your EasyAdmin version before installing; if it pins EA3, expect to rewrite the configureFields() method and the templates rather than debug them.
Audit table performance will surprise you. Every write to an audited entity writes a full row with serialized old/new values. Symptoms show up as slow admin pages (unindexed ORDER BY created_at) and slow writes (large JSON blobs). Add composite indexes matching your actual filter combinations, and consider excluding high-churn, low-value entities from auditing entirely rather than filtering them out at read time.
Diff rendering edge cases to test explicitly: null → value and value → null (must be distinguishable from "field not present in this revision"), numeric strings vs. integers, datetime serialization/timezones, binary/blob columns, and JSON columns where key order changes produce a spurious diff. Write fixtures for these before you trust the UI in a compliance conversation.
Extension points, in order of safety: (1) override Twig templates via templates/bundles/AuditUiBundle/; (2) extend the CRUD controller and override configureFields()/configureFilters(); (3) decorate the audit reader service. Avoid editing vendor/ — but if you must, fork the repo and use a VCS repositories entry so the change survives composer install.
Debugging checklist when entries don't appear: confirm the entity is in dh_auditor.providers.doctrine.entities; confirm the audit table suffix (audit vs audit_<connection>) matches what the UI queries; check that the audit insert isn't being swallowed by a rolled-back transaction or a postFlush ordering issue; and verify the blame resolver is actually populating the user (a null blame column silently renders as "System" and looks like a bug).
Debug the wiring with the container, not by guesswork: bin/console debug:router | grep -i audit and bin/console debug:container --tag=controller.service_arguments will tell you the real route names and controller service IDs, which is faster than reading an unmaintained README.
How can I help you explore Laravel packages today?