simplethings/entity-audit-bundle
Doctrine 2 auditing/versioning bundle inspired by Hibernate Envers. Tracks entity changes and associations over time, stores revisions, and lets you inspect historical states for debugging, compliance, and change history in Symfony/Doctrine apps.
composer require sonata-project/entity-audit-bundle
config/bundles.php:
SimpleThings\EntityAudit\SimpleThingsEntityAuditBundle::class => ['all' => true],
config/packages/entity_audit.yaml:
simple_things_entity_audit:
audited_entities:
- App\Entity\Post
- App\Entity\User
php bin/console doctrine:schema:update --dump-sql
Inject AuditReader into a controller/service to query audit data:
use SimpleThings\EntityAudit\AuditReader;
class PostController extends AbstractController
{
public function showHistory(AuditReader $auditReader, Post $post)
{
$revisions = $auditReader->findRevisions(Post::class, $post->getId());
// Render revisions or redirect to audit UI
}
}
$post = new Post();
$post->setTitle('Draft');
$entityManager->persist($post);
$entityManager->flush();
// Verify audit table has an entry
$revision = $auditReader->getCurrentRevision(Post::class, $post->getId());
global_ignore_columns:
simple_things_entity_audit:
global_ignore_columns: [created_at, updated_at]
simple_things_entity_audit:
service:
username_callable: app.audit.username_resolver
$oldPost = $auditReader->find(Post::class, 1, 5); // Revision 5
$revision1 = $auditReader->find(Post::class, 1, 3);
$revision2 = $auditReader->find(Post::class, 1, 7);
// Manually diff $revision1->getTitle() vs $revision2->getTitle()
Use the built-in routes (secure them!):
# config/routes.yaml
simple_things_entity_audit:
resource: "@SimpleThingsEntityAuditBundle/Resources/config/routing/audit.xml"
prefix: /admin/audit
/admin/audit → Revision list./admin/audit/entity/Post/1 → Post history./admin/audit/compare/Post/1?rev1=3&rev2=7 → Diff tool.Schema Updates:
*_audit) are auto-generated but not migrated via doctrine:migrations. Run schema:update manually after adding/removing audited entities.--dump-sql first to review changes:
php bin/console doctrine:schema:update --dump-sql
Performance:
rev and id columns in audit tables:
// In a migration
$this->addSql('CREATE INDEX idx_post_audit_rev ON post_audit(rev)');
findRevisions() with LIMIT for paginated history.ManyToMany Associations:
post_tag_audit) may throw NoRevisionFoundException. Workaround: Ensure the join table’s primary key is audited or use disable_foreign_keys: true in config.Username Resolution:
simple_things_entity_audit:
service:
username_callable: 'function() { return "system"; }'
Global Ignore Columns:
updated_at) still triggers a revision. Fix: Use global_ignore_columns and explicitly ignore in entity metadata (if supported in future versions).Check audit logs:
php bin/console doctrine:query-log
Look for INSERT INTO *audit queries.
Verify revisions:
$revisions = $auditReader->findRevisions(Post::class, 1);
dd($revisions); // Debug revision data
Custom Audit Tables:
audit_table_name in entity metadata (not officially supported; use events).Post-Audit Actions:
entity_audit.post_revision event:
$eventDispatcher->addListener(
'entity_audit.post_revision',
function ($event) {
// Log to external system
}
);
Standalone Usage:
AuditManager and AuditReader as shown in the README.connection and entity_manager in config:
simple_things_entity_audit:
connection: pgsql
entity_manager: custom_em
doctrine/persistence v4. Tip: If using older Doctrine, pin versions in composer.json.Audit Selective Fields:
Use global_ignore_columns for non-critical fields (e.g., token, password_hash).
Compare Revisions Programmatically:
$diff = [];
$entity1 = $auditReader->find(Post::class, 1, 3);
$entity2 = $auditReader->find(Post::class, 1, 7);
foreach (get_object_vars($entity1) as $field => $value) {
if ($entity1->$field !== $entity2->$field) {
$diff[$field] = [$entity1->$field, $entity2->$field];
}
}
Disable for Tests:
Override config in phpunit.xml:
<env name="AUDIT_ENABLED" value="false"/>
Then conditionally enable in config/packages/test/entity_audit.yaml:
simple_things_entity_audit: { audited_entities: [] }
How can I help you explore Laravel packages today?